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 }