1 /** 2 * Parse Shell Link files (.lnk). 3 * Authors: 4 * $(LINK2 https://github.com/FreeSlave, Roman Chistokhodov) 5 * Copyright: 6 * Roman Chistokhodov, 2016 7 * License: 8 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 9 * See_Also: 10 * $(LINK2 https://msdn.microsoft.com/en-us/library/dd871305.aspx, Shell Link Binary File Format) 11 */ 12 13 module lnk; 14 15 private { 16 import std.traits; 17 import std.bitmanip; 18 import std.file; 19 import std.path; 20 import std.system; 21 import std.exception; 22 import std.utf; 23 } 24 25 private @nogc @trusted void swapByteOrder(T)(ref T t) nothrow pure { 26 27 static if( __VERSION__ < 2067 ) { //swapEndian was not @nogc 28 ubyte[] bytes = (cast(ubyte*)&t)[0..T.sizeof]; 29 for (size_t i=0; i<bytes.length/2; ++i) { 30 ubyte tmp = bytes[i]; 31 bytes[i] = bytes[T.sizeof-1-i]; 32 bytes[T.sizeof-1-i] = tmp; 33 } 34 } else { 35 t = swapEndian(t); 36 } 37 } 38 39 private @trusted T readValue(T)(const(ubyte)[] data) if (isIntegral!T || isSomeChar!T) 40 { 41 if (data.length >= T.sizeof) { 42 T value = *(cast(const(T)*)data[0..T.sizeof].ptr); 43 static if (endian == Endian.bigEndian) { 44 swapByteOrder(value); 45 } 46 return value; 47 } else { 48 throw new ShellLinkException("Value of requrested size is out of data bounds"); 49 } 50 } 51 52 private @trusted T eatValue(T)(ref const(ubyte)[] data) if (isIntegral!T || isSomeChar!T) 53 { 54 auto value = readValue!T(data); 55 data = data[T.sizeof..$]; 56 return value; 57 } 58 59 private @trusted const(T)[] readSlice(T = ubyte)(const(ubyte)[] data, size_t count) if (isIntegral!T || isSomeChar!T) 60 { 61 if (data.length >= count*T.sizeof) { 62 return cast(typeof(return))data[0..count*T.sizeof]; 63 } else { 64 throw new ShellLinkException("Slice of requsted size is out of data bounds"); 65 } 66 } 67 68 private @trusted const(T)[] eatSlice(T = ubyte)(ref const(ubyte)[] data, size_t count) if (isIntegral!T || isSomeChar!T) 69 { 70 auto slice = readSlice!T(data, count); 71 data = data[count*T.sizeof..$]; 72 return slice; 73 } 74 75 private @trusted const(char)[] readString(const(ubyte)[] data) 76 { 77 auto str = cast(const(char)[])data; 78 for (size_t i=0; i<str.length; ++i) { 79 if (data[i] == 0) { 80 return str[0..i]; 81 } 82 } 83 throw new ShellLinkException("Could not read null-terminated string"); 84 } 85 86 private @trusted const(wchar)[] readWString(const(ubyte)[] data) 87 { 88 auto possibleWcharCount = data.length/2; //to chop the last byte if count is odd, avoid misalignment 89 auto str = cast(const(wchar)[])data[0..possibleWcharCount*2]; 90 for (size_t i=0; i<str.length; ++i) { 91 if (str[i] == 0) { 92 return str[0..i]; 93 } 94 } 95 throw new ShellLinkException("Could not read null-terminated wide string"); 96 } 97 98 99 /** 100 * Exception thrown if shell link file data could not be parsed. 101 */ 102 final class ShellLinkException : Exception 103 { 104 this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable next = null) pure nothrow @safe { 105 super(msg, file, line, next); 106 } 107 } 108 109 version(Windows) { 110 import core.sys.windows.windows : CommandLineToArgvW, LocalFree; 111 import core.stdc.wchar_; 112 113 private @trusted string[] parseCommandLine(string commandLine) 114 { 115 auto wCommandLineZ = ("Dummy.exe " ~ commandLine).toUTF16z(); 116 int argc; 117 auto argv = CommandLineToArgvW(wCommandLineZ, &argc); 118 if (argv is null || argc == 0) { 119 return null; 120 } 121 scope(exit) LocalFree(argv); 122 123 string[] args; 124 args.length = argc-1; 125 for (size_t i=0; i<args.length; ++i) { 126 args[i] = argv[i+1][0..wcslen(argv[i+1])].toUTF8(); 127 } 128 return args; 129 } 130 } 131 132 private @trusted string fromANSIToUnicode(const(char)[] ansi) 133 { 134 //Don't convert ascii. 135 bool needConvert; 136 for (size_t i=0; i<ansi.length; ++i) { 137 if (!(ansi[i] >= 0 && ansi[i] < 0x80)) { 138 needConvert = true; 139 break; 140 } 141 } 142 if (!needConvert) { 143 return ansi.idup; 144 } 145 146 version(Windows) { 147 import core.sys.windows.windows : MultiByteToWideChar; 148 auto requiredLength = MultiByteToWideChar(0, 0, ansi.ptr, ansi.length, null, 0); 149 if (requiredLength) { 150 auto wstr = new wchar[requiredLength]; 151 auto bytesWritten = MultiByteToWideChar(0, 0, ansi.ptr, ansi.length, wstr.ptr, wstr.length); 152 if (bytesWritten) { 153 if (wstr[$-1] == 0) { 154 wstr = wstr[0..$-1]; 155 } 156 return wstr.toUTF8(); 157 } 158 } 159 return null; 160 } else { 161 ///TODO: implement for non-Windows. 162 return ansi.idup; 163 } 164 } 165 166 private interface DataReader 167 { 168 uint readInt(); 169 ushort eatShort(); 170 const(ubyte)[] eatBytes(size_t count); 171 const(wchar)[] eatUnicode(size_t count); 172 } 173 174 private final class FileReader : DataReader 175 { 176 import std.stdio : File, stderr; 177 178 this(string fileName) { 179 _file = File(fileName); 180 } 181 182 uint readInt() { 183 _wasRead = true; 184 return readValue!uint(_file.rawRead(_read[])); 185 } 186 187 ushort eatShort() { 188 ubyte[ushort.sizeof] shortBuf; 189 return readValue!ushort(_file.rawRead(shortBuf[])); 190 } 191 192 const(ubyte)[] eatBytes(size_t count) { 193 enforce!ShellLinkException(!_wasRead || count >= _read.length, "Invalid data size"); 194 auto buf = new ubyte[count - (_wasRead ? _read.length : 0)]; 195 auto toReturn = (_wasRead ? _read[] : (ubyte[]).init) ~ _file.rawRead(buf); 196 _wasRead = false; 197 return toReturn; 198 } 199 200 const(wchar)[] eatUnicode(size_t count) { 201 auto buf = new wchar[count]; 202 return _file.rawRead(buf); 203 } 204 205 private: 206 File _file; 207 ubyte[uint.sizeof] _read; 208 bool _wasRead; 209 } 210 211 private final class BufferReader : DataReader 212 { 213 this(const(ubyte)[] data) { 214 _data = data; 215 } 216 217 uint readInt() { 218 return readValue!uint(_data); 219 } 220 221 ushort eatShort() { 222 return eatValue!ushort(_data); 223 } 224 225 const(ubyte)[] eatBytes(size_t count) { 226 return eatSlice(_data, count); 227 } 228 229 const(wchar)[] eatUnicode(size_t count) { 230 return eatSlice!wchar(_data, count); 231 } 232 233 private: 234 const(ubyte)[] _data; 235 } 236 237 /** 238 * Class for accessing Shell Link objects (.lnk files) 239 */ 240 final class ShellLink 241 { 242 private: 243 Header _header; 244 ubyte[][] _itemIdList; 245 LinkInfoHeader _linkInfoHeader; 246 Volume _volume; 247 CommonNetworkRelativeLink _networkLink; 248 249 string _localBasePath; 250 string _commonPathSuffix; 251 252 string _description; 253 string _relativePath; 254 string _workingDir; 255 string _arguments; 256 string _iconLocation; 257 258 string _netName; 259 string _deviceName; 260 261 string _fileName; 262 263 public: 264 /** 265 * Read Shell Link from fileName. 266 * Throws: 267 * ErrnoException if file could not be read. 268 * ShellLinkException if file could not be parsed. 269 * Note: file will be read as whole. 270 */ 271 @trusted this(string fileName) 272 { 273 this(new FileReader(fileName)); 274 } 275 276 @trusted this(const(ubyte)[] data, string fileName = null) 277 { 278 this(new BufferReader(data), fileName); 279 } 280 281 /** 282 * Read Shell Link from data. fileName should be path to the .lnk file where data was read from. 283 * Throws: 284 * ShellLinkException if data could not be parsed. 285 */ 286 private @trusted this(DataReader reader, string fileName = null) 287 { 288 _fileName = fileName; 289 auto headerSize = reader.readInt(); 290 enforce!ShellLinkException(headerSize == Header.requiredHeaderSize, "Wrong Shell Link Header size"); 291 auto headerData = reader.eatBytes(headerSize); 292 _header = parseHeader(headerData); 293 294 if (_header.linkFlags & HasLinkTargetIDList) { 295 auto idListSize = reader.eatShort(); 296 auto idListData = reader.eatBytes(idListSize); 297 _itemIdList = parseItemIdList(idListData); 298 } 299 300 if (_header.linkFlags & HasLinkInfo) { 301 auto linkInfoSize = reader.readInt(); 302 auto linkInfoData = reader.eatBytes(linkInfoSize); 303 _linkInfoHeader = parseLinkInfo(linkInfoData); 304 305 if (_linkInfoHeader.localBasePathOffsetUnicode) { 306 _localBasePath = readWString(linkInfoData[_linkInfoHeader.localBasePathOffsetUnicode..$]).toUTF8(); 307 } else if (_linkInfoHeader.localBasePathOffset) { 308 auto str = readString(linkInfoData[_linkInfoHeader.localBasePathOffset..$]); 309 _localBasePath = fromANSIToUnicode(str); 310 } 311 if (_linkInfoHeader.commonPathSuffixOffsetUnicode) { 312 _commonPathSuffix = readWString(linkInfoData[_linkInfoHeader.commonPathSuffixOffsetUnicode..$]).toUTF8(); 313 } else if (_linkInfoHeader.commonPathSuffixOffset) { 314 auto str = readString(linkInfoData[_linkInfoHeader.commonPathSuffixOffset..$]); 315 _commonPathSuffix = fromANSIToUnicode(str); 316 } 317 318 if (_linkInfoHeader.flags & VolumeIDAndLocalBasePath && _linkInfoHeader.volumeIdOffset) { 319 auto volumeIdSize = readValue!uint(linkInfoData[_linkInfoHeader.volumeIdOffset..$]); 320 enforce!ShellLinkException(volumeIdSize > Volume.minimumSize, "Wrong VolumeID size"); 321 auto volumeIdData = readSlice(linkInfoData[_linkInfoHeader.volumeIdOffset..$], volumeIdSize); 322 _volume = parseVolumeData(volumeIdData); 323 } 324 325 if (_linkInfoHeader.flags & CommonNetworkRelativeLinkAndPathSuffix && _linkInfoHeader.commonNetworkRelativeLinkOffset) { 326 auto networkLinkSize = readValue!uint(linkInfoData[_linkInfoHeader.commonNetworkRelativeLinkOffset..$]); 327 enforce!ShellLinkException(networkLinkSize >= CommonNetworkRelativeLink.minimumSize, "Wrong common network relative path link size"); 328 auto networkLinkData = readSlice(linkInfoData[_linkInfoHeader.commonNetworkRelativeLinkOffset..$], networkLinkSize); 329 _networkLink = parseNetworkLink(networkLinkData); 330 331 if (_networkLink.netNameOffsetUnicode) { 332 _netName = readWString(networkLinkData[_networkLink.netNameOffsetUnicode..$]).toUTF8(); 333 } else if (_networkLink.netNameOffset) { 334 auto str = readString(networkLinkData[_networkLink.netNameOffset..$]); 335 _netName = fromANSIToUnicode(str); 336 } 337 338 if (_networkLink.deviceNameOffsetUnicode) { 339 _deviceName = readWString(networkLinkData[_networkLink.deviceNameOffsetUnicode..$]).toUTF8(); 340 } else if (_networkLink.deviceNameOffset) { 341 auto str = readString(networkLinkData[_networkLink.deviceNameOffset..$]); 342 _deviceName = fromANSIToUnicode(str); 343 } 344 } 345 } 346 347 if (_header.linkFlags & HasName) { 348 _description = consumeStringData(reader); 349 } 350 if (_header.linkFlags & HasRelativePath) { 351 _relativePath = consumeStringData(reader); 352 } 353 if (_header.linkFlags & HasWorkingDir) { 354 _workingDir = consumeStringData(reader); 355 } 356 if (_header.linkFlags & HasArguments) { 357 _arguments = consumeStringData(reader); 358 } 359 if (_header.linkFlags & HasIconLocation) { 360 _iconLocation = consumeStringData(reader); 361 } 362 } 363 364 /** 365 * Get description for a Shell Link object. 366 */ 367 @nogc @safe string description() const nothrow { 368 return _description; 369 } 370 371 /** 372 * Get relative path for a Shell Link object. 373 */ 374 @nogc @safe string relativePath() const nothrow { 375 return _relativePath; 376 } 377 378 /** 379 * Get working directory for a Shell Link object. 380 */ 381 @nogc @safe string workingDirectory() const nothrow { 382 return _workingDir; 383 } 384 385 /** 386 * Get arguments of for a Shell Link object as one string. Target file path is NOT included. 387 */ 388 @nogc @safe string argumentsString() const nothrow { 389 return _arguments; 390 } 391 392 version(Windows) { 393 /** 394 * Get command line arguments. Target file path is NOT included. 395 * Note: This function is Windows only. Currently this function allocates on each call. 396 */ 397 @safe string[] arguments() const { 398 return parseCommandLine(_arguments); 399 } 400 } 401 402 /** 403 * Icon location to be used when displaying a shell link item in an icon view. 404 * Icon location can be of program (.exe), library (.dll) or icon (.ico). 405 * Returns: Location of icon or empty string if not specified. 406 * Params: 407 * iconIndex = The index of an icon within a given icon location. 408 * Note: Icon location may contain environment variable within. It lefts as is if expanding failed. 409 */ 410 @trusted string getIconLocation(out int iconIndex) const { 411 iconIndex = _header.iconIndex; 412 version(Windows) { 413 import core.sys.windows.windows : ExpandEnvironmentStringsW, DWORD; 414 import std.process : environment; 415 416 auto wstrz = _iconLocation.toUTF16z(); 417 if (wstrz) { 418 auto requiredLength = ExpandEnvironmentStringsW(wstrz, null, 0); 419 auto buffer = new wchar[requiredLength]; 420 auto result = ExpandEnvironmentStringsW(wstrz, buffer.ptr, cast(DWORD)buffer.length); 421 if (result) { 422 if (buffer[$-1] == 0) { 423 buffer = buffer[0..$-1]; 424 } 425 return buffer.toUTF8(); 426 } 427 } 428 } 429 return _iconLocation; 430 } 431 432 version(Windows) { 433 /** 434 * Resolve link target location. Windows-only. 435 * Returns: Resolved location of link target or null if evaluated path does not exist or target location could not be resolved. 436 * Note: In case path parts were stored only as ANSI 437 * the result string may contain garbage characters 438 * if user changed default code page and shell link had not get updated yet. 439 * If path parts were stored as Unicode it should not have problems. 440 */ 441 @safe string resolve() const nothrow { 442 string targetPath; 443 bool pathExists; 444 445 if (_localBasePath.length) { 446 if (_commonPathSuffix.length) { 447 targetPath = _localBasePath ~ _commonPathSuffix; 448 pathExists = targetPath.exists; 449 } else if (_localBasePath.isAbsolute) { 450 targetPath = _localBasePath; 451 pathExists = targetPath.exists; 452 } 453 } 454 455 if (!pathExists && _relativePath.length && _workingDir.length) { 456 targetPath = buildPath(_workingDir, _relativePath); 457 pathExists = targetPath.exists; 458 } 459 460 if (!pathExists && _netName.length) { 461 if (_commonPathSuffix.length) { 462 targetPath = _netName ~ '\\' ~ _commonPathSuffix; 463 pathExists = targetPath.exists; 464 } else if (_netName.isAbsolute) { 465 targetPath = _netName; 466 pathExists = targetPath.exists; 467 } 468 } 469 470 if (pathExists) { 471 return buildNormalizedPath(targetPath); 472 } else { 473 return null; 474 } 475 } 476 } 477 478 /** 479 * Get path of link object as was specified upon constructing. 480 */ 481 @nogc @safe string fileName() const nothrow { 482 return _fileName; 483 } 484 485 /** 486 * The name of a shell link, i.e. part of file name with directory and extension parts stripped. 487 */ 488 @nogc @safe string name() const nothrow { 489 return _fileName.baseName.stripExtension; 490 } 491 492 /** 493 * The expected window state of an application launched by the link. 494 * See_Also: $(LINK2 https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx, ShowWindow) 495 */ 496 enum ShowCommand : uint { 497 normal = 0x1, ///The application is open and its window is open in a normal fashion. 498 maximized = 0x3, ///The application is open, and keyboard focus is given to the application, but its window is not shown. 499 minNoActive = 0x7 ///The application is open, but its window is not shown. It is not given the keyboard focus. 500 } 501 502 /** 503 * The expected window state of an application launched by the link. 504 * See $(LINK2 https://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx, ShowWindow). 505 */ 506 @nogc @safe ShowCommand showCommand() const nothrow { 507 switch(_header.showCommand) { 508 case ShowCommand.normal: 509 case ShowCommand.maximized: 510 case ShowCommand.minNoActive: 511 return cast(ShowCommand)_header.showCommand; 512 default: 513 return ShowCommand.normal; 514 } 515 } 516 517 /** 518 * Get hot key used to start link target. 519 * Returns: 2-byte value with virtual key code in low byte and 520 * $(LINK2 https://msdn.microsoft.com/en-us/library/windows/desktop/ms646278(v=vs.85).aspx, modifier keys) in high byte. 521 */ 522 @nogc @safe uint hotKey() const nothrow { 523 return _header.hotKey; 524 } 525 526 private: 527 @trusted static string consumeStringData(DataReader reader) 528 { 529 auto size = reader.eatShort(); 530 return reader.eatUnicode(size).toUTF8(); 531 } 532 533 enum : uint { 534 HasLinkTargetIDList = 1 << 0, 535 HasLinkInfo = 1 << 1, 536 HasName = 1 << 2, 537 HasRelativePath = 1 << 3, 538 HasWorkingDir = 1 << 4, 539 HasArguments = 1 << 5, 540 HasIconLocation = 1 << 6, 541 IsUnicode = 1 << 7, 542 ForceNoLinkInfo = 1 << 8, 543 HasExpString = 1 << 9, 544 RunInSeparateProcess = 1 << 10, 545 Unused1 = 1 << 11, 546 HasDarwinID = 1 << 12, 547 RunAsUser = 1 << 13, 548 HasExpIcon = 1 << 14, 549 NoPidlAlias = 1 << 15, 550 Unused2 = 1 << 16, 551 RunWithShimLayer = 1 << 17, 552 ForceNoLinkTrack = 1 << 18, 553 EnableTargetMetadata = 1 << 19, 554 DisableLinkPathTracking = 1 << 20, 555 DisableKnownFolderTracking = 1 << 21, 556 DisableKnownFolderAlias = 1 << 22, 557 AllowLinkToLink = 1 << 23, 558 UnaliasOnSave = 1 << 24, 559 PreferEnvironmentPath = 1 << 25, 560 KeepLocalIDListForUNCTarget = 1 << 26 561 } 562 563 struct Header 564 { 565 alias ubyte[16] CLSID; 566 567 enum uint requiredHeaderSize = 0x0000004C; 568 enum CLSID requiredLinkCLSID = [1, 20, 2, 0, 0, 0, 0, 0, 192, 0, 0, 0, 0, 0, 0, 70]; 569 uint headerSize; 570 CLSID linkCLSID; 571 uint linkFlags; 572 uint fileAttributes; 573 ulong creationTime; 574 ulong accessTime; 575 ulong writeTime; 576 uint fileSize; 577 int iconIndex; 578 uint showCommand; 579 580 enum { 581 SW_SHOWNORMAL = 0x00000001, 582 SW_SHOWMAXIMIZED = 0x00000003, 583 SW_SHOWMINNOACTIVE = 0x00000007 584 } 585 586 ushort hotKey; 587 ushort reserved1; 588 uint reserved2; 589 uint reserved3; 590 } 591 592 @trusted static Header parseHeader(const(ubyte)[] headerData) 593 { 594 Header header; 595 header.headerSize = eatValue!uint(headerData); 596 auto linkCLSIDSlice = eatSlice(headerData, 16); 597 598 enforce!ShellLinkException(linkCLSIDSlice == Header.requiredLinkCLSID[], "Invalid Link CLSID"); 599 for (size_t i=0; i<16; ++i) { 600 header.linkCLSID[i] = linkCLSIDSlice[i]; 601 } 602 603 header.linkFlags = eatValue!uint(headerData); 604 605 header.fileAttributes = eatValue!uint(headerData); 606 header.creationTime = eatValue!ulong(headerData); 607 header.accessTime = eatValue!ulong(headerData); 608 header.writeTime = eatValue!ulong(headerData); 609 header.fileSize = eatValue!uint(headerData); 610 header.iconIndex = eatValue!int(headerData); 611 header.showCommand = eatValue!uint(headerData); 612 header.hotKey = eatValue!ushort(headerData); 613 614 header.reserved1 = eatValue!ushort(headerData); 615 header.reserved2 = eatValue!uint(headerData); 616 header.reserved3 = eatValue!uint(headerData); 617 return header; 618 } 619 620 @trusted static ubyte[][] parseItemIdList(const(ubyte)[] idListData) 621 { 622 ubyte[][] itemIdList; 623 while(true) { 624 auto itemSize = eatValue!ushort(idListData); 625 if (itemSize) { 626 enforce(itemSize >= 2, "Item size must be at least 2"); 627 auto dataSize = itemSize - 2; 628 auto itemData = eatSlice(idListData, dataSize); 629 itemIdList ~= itemData.dup; 630 } else { 631 break; 632 } 633 } 634 return itemIdList; 635 } 636 637 enum { 638 VolumeIDAndLocalBasePath = 1 << 0, 639 CommonNetworkRelativeLinkAndPathSuffix = 1 << 1 640 } 641 642 struct LinkInfoHeader 643 { 644 enum uint defaultHeaderSize = 0x1C; 645 enum uint minimumExtendedHeaderSize = 0x24; 646 647 uint infoSize; 648 uint headerSize; 649 uint flags; 650 uint volumeIdOffset; 651 uint localBasePathOffset; 652 uint commonNetworkRelativeLinkOffset; 653 uint commonPathSuffixOffset; 654 uint localBasePathOffsetUnicode; 655 uint commonPathSuffixOffsetUnicode; 656 } 657 658 @trusted static LinkInfoHeader parseLinkInfo(const(ubyte[]) linkInfoData) 659 { 660 LinkInfoHeader linkInfoHeader; 661 linkInfoHeader.infoSize = readValue!uint(linkInfoData); 662 linkInfoHeader.headerSize = readValue!uint(linkInfoData[uint.sizeof..$]); 663 664 auto linkInfoHeaderData = readSlice(linkInfoData, linkInfoHeader.headerSize); 665 eatSlice(linkInfoHeaderData, uint.sizeof*2); 666 667 linkInfoHeader.flags = eatValue!uint(linkInfoHeaderData); 668 linkInfoHeader.volumeIdOffset = eatValue!uint(linkInfoHeaderData); 669 linkInfoHeader.localBasePathOffset = eatValue!uint(linkInfoHeaderData); 670 linkInfoHeader.commonNetworkRelativeLinkOffset = eatValue!uint(linkInfoHeaderData); 671 linkInfoHeader.commonPathSuffixOffset = eatValue!uint(linkInfoHeaderData); 672 673 if (linkInfoHeader.headerSize == LinkInfoHeader.defaultHeaderSize) { 674 //ok, no additional fields 675 } else if (linkInfoHeader.headerSize >= LinkInfoHeader.minimumExtendedHeaderSize) { 676 linkInfoHeader.localBasePathOffsetUnicode = eatValue!uint(linkInfoHeaderData); 677 linkInfoHeader.commonPathSuffixOffsetUnicode = eatValue!uint(linkInfoHeaderData); 678 } else { 679 throw new ShellLinkException("Bad LinkInfoHeaderSize"); 680 } 681 return linkInfoHeader; 682 } 683 684 struct Volume 685 { 686 enum uint minimumSize = 0x10; 687 uint size; 688 uint driveType; 689 uint driveSerialNumber; 690 uint labelOffset; 691 uint labelOffsetUnicode; 692 ubyte[] data; 693 } 694 695 @trusted static Volume parseVolumeData(const(ubyte)[] volumeIdData) 696 { 697 Volume volume; 698 volume.size = eatValue!uint(volumeIdData); 699 volume.driveType = eatValue!uint(volumeIdData); 700 volume.driveSerialNumber = eatValue!uint(volumeIdData); 701 volume.labelOffset = eatValue!uint(volumeIdData); 702 if (volume.labelOffset == 0x14) { 703 volume.labelOffsetUnicode = eatValue!uint(volumeIdData); 704 } 705 volume.data = volumeIdData.dup; 706 return volume; 707 } 708 709 struct CommonNetworkRelativeLink 710 { 711 enum uint minimumSize = 0x14; 712 uint size; 713 uint flags; 714 uint netNameOffset; 715 uint deviceNameOffset; 716 uint networkProviderType; 717 uint netNameOffsetUnicode; 718 uint deviceNameOffsetUnicode; 719 } 720 721 @trusted static CommonNetworkRelativeLink parseNetworkLink(const(ubyte)[] networkLinkData) 722 { 723 CommonNetworkRelativeLink networkLink; 724 networkLink.size = eatValue!uint(networkLinkData); 725 networkLink.flags = eatValue!uint(networkLinkData); 726 networkLink.netNameOffset = eatValue!uint(networkLinkData); 727 networkLink.deviceNameOffset = eatValue!uint(networkLinkData); 728 networkLink.networkProviderType = eatValue!uint(networkLinkData); 729 730 if (networkLink.netNameOffset > CommonNetworkRelativeLink.minimumSize) { 731 networkLink.netNameOffsetUnicode = eatValue!uint(networkLinkData); 732 networkLink.deviceNameOffsetUnicode = eatValue!uint(networkLinkData); 733 } 734 735 return networkLink; 736 } 737 }