mirror of
https://github.com/SickGear/SickGear.git
synced 2024-11-28 07:33:38 +00:00
637 lines
24 KiB
Python
637 lines
24 KiB
Python
"""
|
|
Windows Shortcut (.lnk) parser.
|
|
|
|
Documents:
|
|
- The Windows Shortcut File Format (document version 1.0)
|
|
Reverse-engineered by Jesse Hager
|
|
http://www.i2s-lab.com/Papers/The_Windows_Shortcut_File_Format.pdf
|
|
- Wine source code:
|
|
http://source.winehq.org/source/include/shlobj.h (SHELL_LINK_DATA_FLAGS enum)
|
|
http://source.winehq.org/source/dlls/shell32/pidl.h
|
|
- Microsoft:
|
|
http://msdn2.microsoft.com/en-us/library/ms538128.aspx
|
|
|
|
Author: Robert Xiao, Victor Stinner
|
|
|
|
Changes:
|
|
2007-06-27 - Robert Xiao
|
|
* Fixes to FileLocationInfo to correctly handle Unicode paths
|
|
2007-06-13 - Robert Xiao
|
|
* ItemID, FileLocationInfo and ExtraInfo structs, correct Unicode string handling
|
|
2007-03-15 - Victor Stinner
|
|
* Creation of the parser
|
|
"""
|
|
|
|
from hachoir.parser import Parser
|
|
from hachoir.field import (FieldSet,
|
|
CString, String,
|
|
UInt32, UInt16, UInt8,
|
|
Bit, Bits, PaddingBits,
|
|
TimestampWin64, DateTimeMSDOS32,
|
|
NullBytes, PaddingBytes, RawBytes, Enum)
|
|
from hachoir.core.endian import LITTLE_ENDIAN
|
|
from hachoir.core.text_handler import textHandler, hexadecimal
|
|
from hachoir.parser.common.win32 import GUID
|
|
from hachoir.parser.common.msdos import MSDOSFileAttr16, MSDOSFileAttr32
|
|
from hachoir.core.text_handler import filesizeHandler
|
|
|
|
from hachoir.core.tools import paddingSize
|
|
|
|
|
|
class ItemIdList(FieldSet):
|
|
|
|
def __init__(self, *args, **kw):
|
|
FieldSet.__init__(self, *args, **kw)
|
|
self._size = (self["size"].value + 2) * 8
|
|
|
|
def createFields(self):
|
|
yield UInt16(self, "size", "Size of item ID list")
|
|
while True:
|
|
item = ItemId(self, "itemid[]")
|
|
yield item
|
|
if not item["length"].value:
|
|
break
|
|
|
|
|
|
class ItemId(FieldSet):
|
|
ITEM_TYPE = {
|
|
0x1F: "GUID",
|
|
0x23: "Drive",
|
|
0x25: "Drive",
|
|
0x29: "Drive",
|
|
0x2E: "Shell Extension",
|
|
0x2F: "Drive",
|
|
0x30: "Dir/File",
|
|
0x31: "Directory",
|
|
0x32: "File",
|
|
0x34: "File [Unicode Name]",
|
|
0x41: "Workgroup",
|
|
0x42: "Computer",
|
|
0x46: "Net Provider",
|
|
0x47: "Whole Network",
|
|
0x4C: "Web Folder",
|
|
0x61: "MSITStore",
|
|
0x70: "Printer/RAS Connection",
|
|
0xB1: "History/Favorite",
|
|
0xC3: "Network Share",
|
|
}
|
|
|
|
def __init__(self, *args, **kw):
|
|
FieldSet.__init__(self, *args, **kw)
|
|
if self["length"].value:
|
|
self._size = self["length"].value * 8
|
|
else:
|
|
self._size = 16
|
|
|
|
def createFields(self):
|
|
yield UInt16(self, "length", "Length of Item ID Entry")
|
|
if not self["length"].value:
|
|
return
|
|
|
|
yield Enum(UInt8(self, "type"), self.ITEM_TYPE)
|
|
entrytype = self["type"].value
|
|
if entrytype in (0x1F, 0x70):
|
|
# GUID
|
|
yield RawBytes(self, "dummy", 1, "should be 0x50")
|
|
yield GUID(self, "guid")
|
|
|
|
elif entrytype == 0x2E:
|
|
# Shell extension
|
|
yield RawBytes(self, "dummy", 1, "should be 0x50")
|
|
if self["dummy"].value == '\0':
|
|
yield UInt16(self, "length_data", "Length of shell extension-specific data")
|
|
if self["length_data"].value:
|
|
yield RawBytes(self, "data", self["length_data"].value, "Shell extension-specific data")
|
|
yield GUID(self, "handler_guid")
|
|
yield GUID(self, "guid")
|
|
|
|
elif entrytype in (0x23, 0x25, 0x29, 0x2F):
|
|
# Drive
|
|
yield String(self, "drive", self["length"].value - 3, strip="\0")
|
|
|
|
elif entrytype in (0x30, 0x31, 0x32, 0x61, 0xb1):
|
|
yield RawBytes(self, "dummy", 1, "should be 0x00")
|
|
yield UInt32(self, "size", "size of file; 0 for folders")
|
|
yield DateTimeMSDOS32(self, "date_time", "File/folder date and time")
|
|
yield MSDOSFileAttr16(self, "attribs", "File/folder attributes")
|
|
yield CString(self, "name", "File/folder name")
|
|
if self.root.hasUnicodeNames():
|
|
# Align to 2-bytes
|
|
n = paddingSize(self.current_size // 8, 2)
|
|
if n:
|
|
yield PaddingBytes(self, "pad", n)
|
|
|
|
yield UInt16(self, "length_w", "Length of wide struct member")
|
|
yield RawBytes(self, "unknown[]", 6)
|
|
yield DateTimeMSDOS32(self, "creation_date_time", "File/folder creation date and time")
|
|
yield DateTimeMSDOS32(self, "access_date_time", "File/folder last access date and time")
|
|
yield RawBytes(self, "unknown[]", 2)
|
|
yield UInt16(self, "length_next", "Length of next two strings (if zero, ignore this field)")
|
|
yield CString(self, "unicode_name", "File/folder name", charset="UTF-16-LE")
|
|
if self["length_next"].value:
|
|
yield CString(self, "localized_name", "Localized name")
|
|
yield RawBytes(self, "unknown[]", 2)
|
|
else:
|
|
yield CString(self, "name_short", "File/folder short name")
|
|
|
|
elif entrytype in (0x41, 0x42, 0x46):
|
|
yield RawBytes(self, "unknown[]", 2)
|
|
yield CString(self, "name")
|
|
yield CString(self, "protocol")
|
|
yield RawBytes(self, "unknown[]", 2)
|
|
|
|
elif entrytype == 0x47:
|
|
# Whole Network
|
|
yield RawBytes(self, "unknown[]", 2)
|
|
yield CString(self, "name")
|
|
|
|
elif entrytype == 0xC3:
|
|
# Network Share
|
|
yield RawBytes(self, "unknown[]", 2)
|
|
yield CString(self, "name")
|
|
yield CString(self, "protocol")
|
|
yield CString(self, "description")
|
|
yield RawBytes(self, "unknown[]", 2)
|
|
|
|
elif entrytype == 0x4C:
|
|
# Web Folder
|
|
yield RawBytes(self, "unknown[]", 5)
|
|
yield TimestampWin64(self, "modification_time")
|
|
yield UInt32(self, "unknown[]")
|
|
yield UInt32(self, "unknown[]")
|
|
yield UInt32(self, "unknown[]")
|
|
yield LnkString(self, "name")
|
|
yield RawBytes(self, "padding[]", 2)
|
|
yield LnkString(self, "address")
|
|
if self["address/length"].value:
|
|
yield RawBytes(self, "padding[]", 2)
|
|
|
|
else:
|
|
yield RawBytes(self, "raw", self["length"].value - 3)
|
|
|
|
def createDescription(self):
|
|
if self["length"].value:
|
|
return "Item ID Entry: " + self.ITEM_TYPE.get(self["type"].value, "Unknown")
|
|
else:
|
|
return "End of Item ID List"
|
|
|
|
|
|
def formatVolumeSerial(field):
|
|
val = field.value
|
|
return '%04X-%04X' % (val >> 16, val & 0xFFFF)
|
|
|
|
|
|
class LocalVolumeTable(FieldSet):
|
|
VOLUME_TYPE = {
|
|
1: "No root directory",
|
|
2: "Removable (Floppy, Zip, etc.)",
|
|
3: "Fixed (Hard disk)",
|
|
4: "Remote (Network drive)",
|
|
5: "CD-ROM",
|
|
6: "Ram drive",
|
|
}
|
|
|
|
def createFields(self):
|
|
yield UInt32(self, "length", "Length of this structure")
|
|
yield Enum(UInt32(self, "volume_type", "Volume Type"), self.VOLUME_TYPE)
|
|
yield textHandler(UInt32(self, "volume_serial", "Volume Serial Number"), formatVolumeSerial)
|
|
|
|
yield UInt32(self, "label_offset", "Offset to volume label")
|
|
padding = self.seekByte(self["label_offset"].value)
|
|
if padding:
|
|
yield padding
|
|
yield CString(self, "drive")
|
|
|
|
def hasValue(self):
|
|
return bool(self["drive"].value)
|
|
|
|
def createValue(self):
|
|
return self["drive"].value
|
|
|
|
|
|
class NetworkVolumeTable(FieldSet):
|
|
|
|
def createFields(self):
|
|
yield UInt32(self, "length", "Length of this structure")
|
|
yield UInt32(self, "unknown[]")
|
|
yield UInt32(self, "share_name_offset", "Offset to share name")
|
|
yield UInt32(self, "unknown[]")
|
|
yield UInt32(self, "unknown[]")
|
|
padding = self.seekByte(self["share_name_offset"].value)
|
|
if padding:
|
|
yield padding
|
|
yield CString(self, "share_name")
|
|
|
|
def createValue(self):
|
|
return self["share_name"].value
|
|
|
|
|
|
class FileLocationInfo(FieldSet):
|
|
|
|
def createFields(self):
|
|
yield UInt32(self, "length", "Length of this structure")
|
|
if not self["length"].value:
|
|
return
|
|
|
|
yield UInt32(self, "first_offset_pos", "Position of first offset")
|
|
has_unicode_paths = (self["first_offset_pos"].value == 0x24)
|
|
yield Bit(self, "on_local_volume")
|
|
yield Bit(self, "on_network_volume")
|
|
yield PaddingBits(self, "reserved[]", 30)
|
|
yield UInt32(self, "local_info_offset", "Offset to local volume table; only meaningful if on_local_volume = 1")
|
|
yield UInt32(self, "local_pathname_offset", "Offset to local base pathname; only meaningful if on_local_volume = 1")
|
|
yield UInt32(self, "remote_info_offset", "Offset to network volume table; only meaningful if on_network_volume = 1")
|
|
yield UInt32(self, "pathname_offset", "Offset of remaining pathname")
|
|
if has_unicode_paths:
|
|
yield UInt32(self, "local_pathname_unicode_offset", "Offset to Unicode version of local base pathname; only meaningful if on_local_volume = 1")
|
|
yield UInt32(self, "pathname_unicode_offset", "Offset to Unicode version of remaining pathname")
|
|
if self["on_local_volume"].value:
|
|
padding = self.seekByte(self["local_info_offset"].value)
|
|
if padding:
|
|
yield padding
|
|
yield LocalVolumeTable(self, "local_volume_table", "Local Volume Table")
|
|
|
|
padding = self.seekByte(self["local_pathname_offset"].value)
|
|
if padding:
|
|
yield padding
|
|
yield CString(self, "local_base_pathname", "Local Base Pathname")
|
|
if has_unicode_paths:
|
|
padding = self.seekByte(
|
|
self["local_pathname_unicode_offset"].value)
|
|
if padding:
|
|
yield padding
|
|
yield CString(self, "local_base_pathname_unicode", "Local Base Pathname in Unicode", charset="UTF-16-LE")
|
|
|
|
if self["on_network_volume"].value:
|
|
padding = self.seekByte(self["remote_info_offset"].value)
|
|
if padding:
|
|
yield padding
|
|
yield NetworkVolumeTable(self, "network_volume_table")
|
|
|
|
padding = self.seekByte(self["pathname_offset"].value)
|
|
if padding:
|
|
yield padding
|
|
yield CString(self, "final_pathname", "Final component of the pathname")
|
|
|
|
if has_unicode_paths:
|
|
padding = self.seekByte(self["pathname_unicode_offset"].value)
|
|
if padding:
|
|
yield padding
|
|
yield CString(self, "final_pathname_unicode", "Final component of the pathname in Unicode", charset="UTF-16-LE")
|
|
|
|
padding = self.seekByte(self["length"].value)
|
|
if padding:
|
|
yield padding
|
|
|
|
|
|
class LnkString(FieldSet):
|
|
|
|
def createFields(self):
|
|
yield UInt16(self, "length", "Length of this string")
|
|
if self["length"].value:
|
|
if self.root.hasUnicodeNames():
|
|
yield String(self, "data", self["length"].value * 2, charset="UTF-16-LE")
|
|
else:
|
|
yield String(self, "data", self["length"].value, charset="ASCII")
|
|
|
|
def createValue(self):
|
|
if self["length"].value:
|
|
return self["data"].value
|
|
else:
|
|
return ""
|
|
|
|
|
|
class ColorRef(FieldSet):
|
|
''' COLORREF struct, 0x00bbggrr '''
|
|
static_size = 32
|
|
|
|
def createFields(self):
|
|
yield UInt8(self, "red", "Red")
|
|
yield UInt8(self, "green", "Green")
|
|
yield UInt8(self, "blue", "Blue")
|
|
yield PaddingBytes(self, "pad", 1, "Padding (must be 0)")
|
|
|
|
def createDescription(self):
|
|
rgb = self["red"].value, self["green"].value, self["blue"].value
|
|
return "RGB Color: #%02X%02X%02X" % rgb
|
|
|
|
|
|
class ColorTableIndex(Bits):
|
|
|
|
def __init__(self, parent, name, size, description=None):
|
|
Bits.__init__(self, parent, name, size, None)
|
|
self.desc = description
|
|
|
|
def createDescription(self):
|
|
assert hasattr(self, 'parent') and hasattr(self, 'value')
|
|
return "%s: %s" % (self.desc,
|
|
self.parent["color[%i]" % self.value].description)
|
|
|
|
|
|
class ExtraInfo(FieldSet):
|
|
INFO_TYPE = {
|
|
0xA0000001: "Link Target Information", # EXP_SZ_LINK_SIG
|
|
0xA0000002: "Console Window Properties", # NT_CONSOLE_PROPS_SIG
|
|
0xA0000003: "Hostname and Other Stuff",
|
|
0xA0000004: "Console Codepage Information", # NT_FE_CONSOLE_PROPS_SIG
|
|
0xA0000005: "Special Folder Info", # EXP_SPECIAL_FOLDER_SIG
|
|
# EXP_DARWIN_ID_SIG
|
|
0xA0000006: "DarwinID (Windows Installer ID) Information",
|
|
0xA0000007: "Custom Icon Details", # EXP_LOGO3_ID_SIG or EXP_SZ_ICON_SIG
|
|
}
|
|
SPECIAL_FOLDER = {
|
|
0: "DESKTOP",
|
|
1: "INTERNET",
|
|
2: "PROGRAMS",
|
|
3: "CONTROLS",
|
|
4: "PRINTERS",
|
|
5: "PERSONAL",
|
|
6: "FAVORITES",
|
|
7: "STARTUP",
|
|
8: "RECENT",
|
|
9: "SENDTO",
|
|
10: "BITBUCKET",
|
|
11: "STARTMENU",
|
|
16: "DESKTOPDIRECTORY",
|
|
17: "DRIVES",
|
|
18: "NETWORK",
|
|
19: "NETHOOD",
|
|
20: "FONTS",
|
|
21: "TEMPLATES",
|
|
22: "COMMON_STARTMENU",
|
|
23: "COMMON_PROGRAMS",
|
|
24: "COMMON_STARTUP",
|
|
25: "COMMON_DESKTOPDIRECTORY",
|
|
26: "APPDATA",
|
|
27: "PRINTHOOD",
|
|
28: "LOCAL_APPDATA",
|
|
29: "ALTSTARTUP",
|
|
30: "COMMON_ALTSTARTUP",
|
|
31: "COMMON_FAVORITES",
|
|
32: "INTERNET_CACHE",
|
|
33: "COOKIES",
|
|
34: "HISTORY",
|
|
35: "COMMON_APPDATA",
|
|
36: "WINDOWS",
|
|
37: "SYSTEM",
|
|
38: "PROGRAM_FILES",
|
|
39: "MYPICTURES",
|
|
40: "PROFILE",
|
|
41: "SYSTEMX86",
|
|
42: "PROGRAM_FILESX86",
|
|
43: "PROGRAM_FILES_COMMON",
|
|
44: "PROGRAM_FILES_COMMONX86",
|
|
45: "COMMON_TEMPLATES",
|
|
46: "COMMON_DOCUMENTS",
|
|
47: "COMMON_ADMINTOOLS",
|
|
48: "ADMINTOOLS",
|
|
49: "CONNECTIONS",
|
|
53: "COMMON_MUSIC",
|
|
54: "COMMON_PICTURES",
|
|
55: "COMMON_VIDEO",
|
|
56: "RESOURCES",
|
|
57: "RESOURCES_LOCALIZED",
|
|
58: "COMMON_OEM_LINKS",
|
|
59: "CDBURN_AREA",
|
|
61: "COMPUTERSNEARME",
|
|
}
|
|
BOOL_ENUM = {
|
|
0: "False",
|
|
1: "True",
|
|
}
|
|
|
|
def __init__(self, *args, **kw):
|
|
FieldSet.__init__(self, *args, **kw)
|
|
if self["length"].value:
|
|
self._size = self["length"].value * 8
|
|
else:
|
|
self._size = 32
|
|
|
|
def createFields(self):
|
|
yield UInt32(self, "length", "Length of this structure")
|
|
if not self["length"].value:
|
|
return
|
|
|
|
yield Enum(textHandler(UInt32(self, "signature", "Signature determining the function of this structure"), hexadecimal), self.INFO_TYPE)
|
|
|
|
if self["signature"].value == 0xA0000003:
|
|
# Hostname and Other Stuff
|
|
yield UInt32(self, "remaining_length")
|
|
yield UInt32(self, "unknown[]")
|
|
yield String(self, "hostname", 16, "Computer hostname on which shortcut was last modified", strip="\0")
|
|
yield RawBytes(self, "unknown[]", 32)
|
|
yield RawBytes(self, "unknown[]", 32)
|
|
|
|
elif self["signature"].value == 0xA0000005:
|
|
# Special Folder Info
|
|
yield Enum(UInt32(self, "special_folder_id", "ID of the special folder"), self.SPECIAL_FOLDER)
|
|
yield UInt32(self, "offset", "Offset to Item ID entry")
|
|
|
|
elif self["signature"].value in (0xA0000001, 0xA0000006, 0xA0000007):
|
|
if self["signature"].value == 0xA0000001: # Link Target Information
|
|
object_name = "target"
|
|
# DarwinID (Windows Installer ID) Information
|
|
elif self["signature"].value == 0xA0000006:
|
|
object_name = "darwinID"
|
|
else: # Custom Icon Details
|
|
object_name = "icon_path"
|
|
yield CString(self, object_name, "Data (ASCII format)", charset="ASCII")
|
|
# 260*2 = size of next part
|
|
remaining = self["length"].value - self.current_size // 8 - 260 * 2
|
|
if remaining:
|
|
yield RawBytes(self, "slack_space[]", remaining, "Data beyond end of string")
|
|
yield CString(self, object_name + '_unicode', "Data (Unicode format)", charset="UTF-16-LE", truncate="\0")
|
|
remaining = self["length"].value - self.current_size // 8
|
|
if remaining:
|
|
yield RawBytes(self, "slack_space[]", remaining, "Data beyond end of string")
|
|
|
|
elif self["signature"].value == 0xA0000002:
|
|
# Console Window Properties
|
|
yield ColorTableIndex(self, "color_text", 4, "Screen text color index")
|
|
yield ColorTableIndex(self, "color_bg", 4, "Screen background color index")
|
|
yield NullBytes(self, "reserved[]", 1)
|
|
yield ColorTableIndex(self, "color_popup_text", 4, "Pop-up text color index")
|
|
yield ColorTableIndex(self, "color_popup_bg", 4, "Pop-up background color index")
|
|
yield NullBytes(self, "reserved[]", 1)
|
|
yield UInt16(self, "buffer_width", "Screen buffer width (character cells)")
|
|
yield UInt16(self, "buffer_height", "Screen buffer height (character cells)")
|
|
yield UInt16(self, "window_width", "Window width (character cells)")
|
|
yield UInt16(self, "window_height", "Window height (character cells)")
|
|
yield UInt16(self, "position_left", "Window distance from left edge (screen coords)")
|
|
yield UInt16(self, "position_top", "Window distance from top edge (screen coords)")
|
|
yield UInt32(self, "font_number")
|
|
yield UInt32(self, "input_buffer_size")
|
|
yield UInt16(self, "font_width", "Font width in pixels; 0 for a non-raster font")
|
|
yield UInt16(self, "font_height", "Font height in pixels; equal to the font size for non-raster fonts")
|
|
yield UInt32(self, "font_family")
|
|
yield UInt32(self, "font_weight")
|
|
yield String(self, "font_name_unicode", 64, "Font Name (Unicode format)", charset="UTF-16-LE", truncate="\0")
|
|
yield UInt32(self, "cursor_size", "Relative size of cursor (% of character size)")
|
|
yield Enum(UInt32(self, "full_screen", "Run console in full screen?"), self.BOOL_ENUM)
|
|
yield Enum(UInt32(self, "quick_edit", "Console uses quick-edit feature (using mouse to cut & paste)?"), self.BOOL_ENUM)
|
|
yield Enum(UInt32(self, "insert_mode", "Console uses insertion mode?"), self.BOOL_ENUM)
|
|
yield Enum(UInt32(self, "auto_position", "System automatically positions window?"), self.BOOL_ENUM)
|
|
yield UInt32(self, "history_size", "Size of the history buffer (in lines)")
|
|
yield UInt32(self, "history_count", "Number of history buffers (each process gets one up to this limit)")
|
|
yield Enum(UInt32(self, "history_no_dup", "Automatically eliminate duplicate lines in the history buffer?"), self.BOOL_ENUM)
|
|
for index in range(16):
|
|
yield ColorRef(self, "color[]")
|
|
|
|
elif self["signature"].value == 0xA0000004:
|
|
# Console Codepage Information
|
|
yield UInt32(self, "codepage", "Console's code page")
|
|
|
|
else:
|
|
yield RawBytes(self, "raw", self["length"].value - self.current_size // 8)
|
|
|
|
def createDescription(self):
|
|
if self["length"].value:
|
|
return "Extra Info Entry: " + self["signature"].display
|
|
else:
|
|
return "End of Extra Info"
|
|
|
|
|
|
HOT_KEYS = {
|
|
0x00: 'None',
|
|
0x13: 'Pause',
|
|
0x14: 'Caps Lock',
|
|
0x21: 'Page Up',
|
|
0x22: 'Page Down',
|
|
0x23: 'End',
|
|
0x24: 'Home',
|
|
0x25: 'Left',
|
|
0x26: 'Up',
|
|
0x27: 'Right',
|
|
0x28: 'Down',
|
|
0x2d: 'Insert',
|
|
0x2e: 'Delete',
|
|
0x6a: 'Num *',
|
|
0x6b: 'Num +',
|
|
0x6d: 'Num -',
|
|
0x6e: 'Num .',
|
|
0x6f: 'Num /',
|
|
0x90: 'Num Lock',
|
|
0x91: 'Scroll Lock',
|
|
0xba: ';',
|
|
0xbb: '=',
|
|
0xbc: ',',
|
|
0xbd: '-',
|
|
0xbe: '.',
|
|
0xbf: '/',
|
|
0xc0: '`',
|
|
0xdb: '[',
|
|
0xdc: '\\',
|
|
0xdd: ']',
|
|
0xde: "'",
|
|
}
|
|
|
|
|
|
def text_hot_key(field):
|
|
assert hasattr(field, "value")
|
|
val = field.value
|
|
if 0x30 <= val <= 0x39:
|
|
return chr(val)
|
|
elif 0x41 <= val <= 0x5A:
|
|
return chr(val)
|
|
elif 0x60 <= val <= 0x69:
|
|
return 'Numpad %c' % chr(val - 0x30)
|
|
elif 0x70 <= val <= 0x87:
|
|
return 'F%i' % (val - 0x6F)
|
|
elif val in HOT_KEYS:
|
|
return HOT_KEYS[val]
|
|
return str(val)
|
|
|
|
|
|
class LnkFile(Parser):
|
|
MAGIC = b"\x4C\0\0\0\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46"
|
|
PARSER_TAGS = {
|
|
"id": "lnk",
|
|
"category": "misc",
|
|
"file_ext": ("lnk",),
|
|
"mime": ("application/x-ms-shortcut",),
|
|
"magic": ((MAGIC, 0),),
|
|
"min_size": len(MAGIC) * 8, # signature + guid = 20 bytes
|
|
"description": "Windows Shortcut (.lnk)",
|
|
}
|
|
endian = LITTLE_ENDIAN
|
|
|
|
SHOW_WINDOW_STATE = {
|
|
0: "Hide",
|
|
1: "Show Normal",
|
|
2: "Show Minimized",
|
|
3: "Show Maximized",
|
|
4: "Show Normal, not activated",
|
|
5: "Show",
|
|
6: "Minimize",
|
|
7: "Show Minimized, not activated",
|
|
8: "Show, not activated",
|
|
9: "Restore",
|
|
10: "Show Default",
|
|
}
|
|
|
|
def validate(self):
|
|
if self["signature"].value != 0x0000004C:
|
|
return "Invalid signature"
|
|
if self["guid"].value != "00021401-0000-0000-C000-000000000046":
|
|
return "Invalid GUID"
|
|
return True
|
|
|
|
def hasUnicodeNames(self):
|
|
return self["has_unicode_names"].value
|
|
|
|
def createFields(self):
|
|
yield UInt32(self, "signature", "Shortcut signature (0x0000004C)")
|
|
yield GUID(self, "guid", "Shortcut GUID (00021401-0000-0000-C000-000000000046)")
|
|
|
|
yield Bit(self, "has_shell_id", "Is the Item ID List present?")
|
|
yield Bit(self, "target_is_file", "Is a file or a directory?")
|
|
yield Bit(self, "has_description", "Is the Description field present?")
|
|
yield Bit(self, "has_rel_path", "Is the relative path to the target available?")
|
|
yield Bit(self, "has_working_dir", "Is there a working directory?")
|
|
yield Bit(self, "has_cmd_line_args", "Are there any command line arguments?")
|
|
yield Bit(self, "has_custom_icon", "Is there a custom icon?")
|
|
yield Bit(self, "has_unicode_names", "Are Unicode names used?")
|
|
yield Bit(self, "force_no_linkinfo")
|
|
yield Bit(self, "has_exp_sz")
|
|
yield Bit(self, "run_in_separate")
|
|
yield Bit(self, "has_logo3id", "Is LOGO3 ID info present?")
|
|
yield Bit(self, "has_darwinid", "Is the DarwinID info present?")
|
|
yield Bit(self, "runas_user", "Is the target run as another user?")
|
|
yield Bit(self, "has_exp_icon_sz", "Is custom icon information available?")
|
|
yield Bit(self, "no_pidl_alias")
|
|
yield Bit(self, "force_unc_name")
|
|
yield Bit(self, "run_with_shim_layer")
|
|
yield PaddingBits(self, "reserved[]", 14, "Flag bits reserved for future use")
|
|
|
|
yield MSDOSFileAttr32(self, "target_attr")
|
|
|
|
yield TimestampWin64(self, "creation_time")
|
|
yield TimestampWin64(self, "modification_time")
|
|
yield TimestampWin64(self, "last_access_time")
|
|
yield filesizeHandler(UInt32(self, "target_filesize"))
|
|
yield UInt32(self, "icon_number")
|
|
yield Enum(UInt32(self, "show_window"), self.SHOW_WINDOW_STATE)
|
|
yield textHandler(UInt8(self, "hot_key", "Hot key used for quick access"), text_hot_key)
|
|
yield Bit(self, "hot_key_shift", "Hot key: is Shift used?")
|
|
yield Bit(self, "hot_key_ctrl", "Hot key: is Ctrl used?")
|
|
yield Bit(self, "hot_key_alt", "Hot key: is Alt used?")
|
|
yield PaddingBits(self, "hot_key_reserved", 21, "Hot key: (reserved)")
|
|
yield NullBytes(self, "reserved[]", 8)
|
|
|
|
if self["has_shell_id"].value:
|
|
yield ItemIdList(self, "item_idlist", "Item ID List")
|
|
if self["target_is_file"].value:
|
|
yield FileLocationInfo(self, "file_location_info", "File Location Info")
|
|
if self["has_description"].value:
|
|
yield LnkString(self, "description")
|
|
if self["has_rel_path"].value:
|
|
yield LnkString(self, "relative_path", "Relative path to target")
|
|
if self["has_working_dir"].value:
|
|
yield LnkString(self, "working_dir", "Working directory (dir to start target in)")
|
|
if self["has_cmd_line_args"].value:
|
|
yield LnkString(self, "cmd_line_args", "Command Line Arguments")
|
|
if self["has_custom_icon"].value:
|
|
yield LnkString(self, "custom_icon", "Custom Icon Path")
|
|
|
|
while not self.eof:
|
|
yield ExtraInfo(self, "extra_info[]")
|