mirror of
https://github.com/SickGear/SickGear.git
synced 2024-12-05 02:43:37 +00:00
0d9fbc1ad7
This version of SickBeard uses both TVDB and TVRage to search and gather it's series data from allowing you to now have access to and download shows that you couldn't before because of being locked into only what TheTVDB had to offer. Also this edition is based off the code we used in our XEM editon so it does come with scene numbering support as well as all the other features our XEM edition has to offer. Please before using this with your existing database (sickbeard.db) please make a backup copy of it and delete any other database files such as cache.db and failed.db if present, we HIGHLY recommend starting out with no database files at all to make this a fresh start but the choice is at your own risk! Enjoy!
445 lines
15 KiB
Python
445 lines
15 KiB
Python
"""
|
|
Parser for resource of Microsoft Windows Portable Executable (PE).
|
|
|
|
Documentation:
|
|
- Wine project
|
|
VS_FIXEDFILEINFO structure, file include/winver.h
|
|
|
|
Author: Victor Stinner
|
|
Creation date: 2007-01-19
|
|
"""
|
|
|
|
from lib.hachoir_core.field import (FieldSet, ParserError, Enum,
|
|
Bit, Bits, SeekableFieldSet,
|
|
UInt16, UInt32, TimestampUnix32,
|
|
RawBytes, PaddingBytes, NullBytes, NullBits,
|
|
CString, String)
|
|
from lib.hachoir_core.text_handler import textHandler, filesizeHandler, hexadecimal
|
|
from lib.hachoir_core.tools import createDict, paddingSize, alignValue, makePrintable
|
|
from lib.hachoir_core.error import HACHOIR_ERRORS
|
|
from lib.hachoir_parser.common.win32 import BitmapInfoHeader
|
|
|
|
MAX_DEPTH = 5
|
|
MAX_INDEX_PER_HEADER = 300
|
|
MAX_NAME_PER_HEADER = MAX_INDEX_PER_HEADER
|
|
|
|
class Version(FieldSet):
|
|
static_size = 32
|
|
def createFields(self):
|
|
yield textHandler(UInt16(self, "minor", "Minor version number"), hexadecimal)
|
|
yield textHandler(UInt16(self, "major", "Major version number"), hexadecimal)
|
|
def createValue(self):
|
|
return self["major"].value + float(self["minor"].value) / 10000
|
|
|
|
MAJOR_OS_NAME = {
|
|
1: "DOS",
|
|
2: "OS/2 16-bit",
|
|
3: "OS/2 32-bit",
|
|
4: "Windows NT",
|
|
}
|
|
|
|
MINOR_OS_BASE = 0
|
|
MINOR_OS_NAME = {
|
|
0: "Base",
|
|
1: "Windows 16-bit",
|
|
2: "Presentation Manager 16-bit",
|
|
3: "Presentation Manager 32-bit",
|
|
4: "Windows 32-bit",
|
|
}
|
|
|
|
FILETYPE_DRIVER = 3
|
|
FILETYPE_FONT = 4
|
|
FILETYPE_NAME = {
|
|
1: "Application",
|
|
2: "DLL",
|
|
3: "Driver",
|
|
4: "Font",
|
|
5: "VXD",
|
|
7: "Static library",
|
|
}
|
|
|
|
DRIVER_SUBTYPE_NAME = {
|
|
1: "Printer",
|
|
2: "Keyboard",
|
|
3: "Language",
|
|
4: "Display",
|
|
5: "Mouse",
|
|
6: "Network",
|
|
7: "System",
|
|
8: "Installable",
|
|
9: "Sound",
|
|
10: "Communications",
|
|
}
|
|
|
|
FONT_SUBTYPE_NAME = {
|
|
1: "Raster",
|
|
2: "Vector",
|
|
3: "TrueType",
|
|
}
|
|
|
|
class VersionInfoBinary(FieldSet):
|
|
def createFields(self):
|
|
yield textHandler(UInt32(self, "magic", "File information magic (0xFEEF04BD)"), hexadecimal)
|
|
if self["magic"].value != 0xFEEF04BD:
|
|
raise ParserError("EXE resource: invalid file info magic")
|
|
yield Version(self, "struct_ver", "Structure version (1.0)")
|
|
yield Version(self, "file_ver_ms", "File version MS")
|
|
yield Version(self, "file_ver_ls", "File version LS")
|
|
yield Version(self, "product_ver_ms", "Product version MS")
|
|
yield Version(self, "product_ver_ls", "Product version LS")
|
|
yield textHandler(UInt32(self, "file_flags_mask"), hexadecimal)
|
|
|
|
yield Bit(self, "debug")
|
|
yield Bit(self, "prerelease")
|
|
yield Bit(self, "patched")
|
|
yield Bit(self, "private_build")
|
|
yield Bit(self, "info_inferred")
|
|
yield Bit(self, "special_build")
|
|
yield NullBits(self, "reserved", 26)
|
|
|
|
yield Enum(textHandler(UInt16(self, "file_os_major"), hexadecimal), MAJOR_OS_NAME)
|
|
yield Enum(textHandler(UInt16(self, "file_os_minor"), hexadecimal), MINOR_OS_NAME)
|
|
yield Enum(textHandler(UInt32(self, "file_type"), hexadecimal), FILETYPE_NAME)
|
|
field = textHandler(UInt32(self, "file_subfile"), hexadecimal)
|
|
if field.value == FILETYPE_DRIVER:
|
|
field = Enum(field, DRIVER_SUBTYPE_NAME)
|
|
elif field.value == FILETYPE_FONT:
|
|
field = Enum(field, FONT_SUBTYPE_NAME)
|
|
yield field
|
|
yield TimestampUnix32(self, "date_ms")
|
|
yield TimestampUnix32(self, "date_ls")
|
|
|
|
class VersionInfoNode(FieldSet):
|
|
TYPE_STRING = 1
|
|
TYPE_NAME = {
|
|
0: "binary",
|
|
1: "string",
|
|
}
|
|
|
|
def __init__(self, parent, name, is_32bit=True):
|
|
FieldSet.__init__(self, parent, name)
|
|
self._size = alignValue(self["size"].value, 4) * 8
|
|
self.is_32bit = is_32bit
|
|
|
|
def createFields(self):
|
|
yield UInt16(self, "size", "Node size (in bytes)")
|
|
yield UInt16(self, "data_size")
|
|
yield Enum(UInt16(self, "type"), self.TYPE_NAME)
|
|
yield CString(self, "name", charset="UTF-16-LE")
|
|
|
|
size = paddingSize(self.current_size//8, 4)
|
|
if size:
|
|
yield NullBytes(self, "padding[]", size)
|
|
size = self["data_size"].value
|
|
if size:
|
|
if self["type"].value == self.TYPE_STRING:
|
|
if self.is_32bit:
|
|
size *= 2
|
|
yield String(self, "value", size, charset="UTF-16-LE", truncate="\0")
|
|
elif self["name"].value == "VS_VERSION_INFO":
|
|
yield VersionInfoBinary(self, "value", size=size*8)
|
|
if self["value/file_flags_mask"].value == 0:
|
|
self.is_32bit = False
|
|
else:
|
|
yield RawBytes(self, "value", size)
|
|
while 12 <= (self.size - self.current_size) // 8:
|
|
yield VersionInfoNode(self, "node[]", self.is_32bit)
|
|
size = (self.size - self.current_size) // 8
|
|
if size:
|
|
yield NullBytes(self, "padding[]", size)
|
|
|
|
|
|
def createDescription(self):
|
|
text = "Version info node: %s" % self["name"].value
|
|
if self["type"].value == self.TYPE_STRING and "value" in self:
|
|
text += "=%s" % self["value"].value
|
|
return text
|
|
|
|
def parseVersionInfo(parent):
|
|
yield VersionInfoNode(parent, "node[]")
|
|
|
|
def parseIcon(parent):
|
|
yield BitmapInfoHeader(parent, "bmp_header")
|
|
size = (parent.size - parent.current_size) // 8
|
|
if size:
|
|
yield RawBytes(parent, "raw", size)
|
|
|
|
class WindowsString(FieldSet):
|
|
def createFields(self):
|
|
yield UInt16(self, "length", "Number of 16-bit characters")
|
|
size = self["length"].value * 2
|
|
if size:
|
|
yield String(self, "text", size, charset="UTF-16-LE")
|
|
|
|
def createValue(self):
|
|
if "text" in self:
|
|
return self["text"].value
|
|
else:
|
|
return u""
|
|
|
|
def createDisplay(self):
|
|
return makePrintable(self.value, "UTF-8", to_unicode=True, quote='"')
|
|
|
|
def parseStringTable(parent):
|
|
while not parent.eof:
|
|
yield WindowsString(parent, "string[]")
|
|
|
|
RESOURCE_TYPE = {
|
|
1: ("cursor[]", "Cursor", None),
|
|
2: ("bitmap[]", "Bitmap", None),
|
|
3: ("icon[]", "Icon", parseIcon),
|
|
4: ("menu[]", "Menu", None),
|
|
5: ("dialog[]", "Dialog", None),
|
|
6: ("string_table[]", "String table", parseStringTable),
|
|
7: ("font_dir[]", "Font directory", None),
|
|
8: ("font[]", "Font", None),
|
|
9: ("accelerators[]", "Accelerators", None),
|
|
10: ("raw_res[]", "Unformatted resource data", None),
|
|
11: ("message_table[]", "Message table", None),
|
|
12: ("group_cursor[]", "Group cursor", None),
|
|
14: ("group_icon[]", "Group icon", None),
|
|
16: ("version_info", "Version information", parseVersionInfo),
|
|
}
|
|
|
|
class Entry(FieldSet):
|
|
static_size = 16*8
|
|
|
|
def __init__(self, parent, name, inode=None):
|
|
FieldSet.__init__(self, parent, name)
|
|
self.inode = inode
|
|
|
|
def createFields(self):
|
|
yield textHandler(UInt32(self, "rva"), hexadecimal)
|
|
yield filesizeHandler(UInt32(self, "size"))
|
|
yield UInt32(self, "codepage")
|
|
yield NullBytes(self, "reserved", 4)
|
|
|
|
def createDescription(self):
|
|
return "Entry #%u: offset=%s size=%s" % (
|
|
self.inode["offset"].value, self["rva"].display, self["size"].display)
|
|
|
|
class NameOffset(FieldSet):
|
|
def createFields(self):
|
|
yield UInt32(self, "name")
|
|
yield Bits(self, "offset", 31)
|
|
yield Bit(self, "is_name")
|
|
|
|
class IndexOffset(FieldSet):
|
|
TYPE_DESC = createDict(RESOURCE_TYPE, 1)
|
|
|
|
def __init__(self, parent, name, res_type=None):
|
|
FieldSet.__init__(self, parent, name)
|
|
self.res_type = res_type
|
|
|
|
def createFields(self):
|
|
yield Enum(UInt32(self, "type"), self.TYPE_DESC)
|
|
yield Bits(self, "offset", 31)
|
|
yield Bit(self, "is_subdir")
|
|
|
|
def createDescription(self):
|
|
if self["is_subdir"].value:
|
|
return "Sub-directory: %s at %s" % (self["type"].display, self["offset"].value)
|
|
else:
|
|
return "Index: ID %s at %s" % (self["type"].display, self["offset"].value)
|
|
|
|
class ResourceContent(FieldSet):
|
|
def __init__(self, parent, name, entry, size=None):
|
|
FieldSet.__init__(self, parent, name, size=entry["size"].value*8)
|
|
self.entry = entry
|
|
res_type = self.getResType()
|
|
if res_type in RESOURCE_TYPE:
|
|
self._name, description, self._parser = RESOURCE_TYPE[res_type]
|
|
else:
|
|
self._parser = None
|
|
|
|
def getResID(self):
|
|
return self.entry.inode["offset"].value
|
|
|
|
def getResType(self):
|
|
return self.entry.inode.res_type
|
|
|
|
def createFields(self):
|
|
if self._parser:
|
|
for field in self._parser(self):
|
|
yield field
|
|
else:
|
|
yield RawBytes(self, "content", self.size//8)
|
|
|
|
def createDescription(self):
|
|
return "Resource #%u content: type=%s" % (
|
|
self.getResID(), self.getResType())
|
|
|
|
class Header(FieldSet):
|
|
static_size = 16*8
|
|
def createFields(self):
|
|
yield NullBytes(self, "options", 4)
|
|
yield TimestampUnix32(self, "creation_date")
|
|
yield UInt16(self, "maj_ver", "Major version")
|
|
yield UInt16(self, "min_ver", "Minor version")
|
|
yield UInt16(self, "nb_name", "Number of named entries")
|
|
yield UInt16(self, "nb_index", "Number of indexed entries")
|
|
|
|
def createDescription(self):
|
|
text = "Resource header"
|
|
info = []
|
|
if self["nb_name"].value:
|
|
info.append("%u name" % self["nb_name"].value)
|
|
if self["nb_index"].value:
|
|
info.append("%u index" % self["nb_index"].value)
|
|
if self["creation_date"].value:
|
|
info.append(self["creation_date"].display)
|
|
if info:
|
|
return "%s: %s" % (text, ", ".join(info))
|
|
else:
|
|
return text
|
|
|
|
class Name(FieldSet):
|
|
def createFields(self):
|
|
yield UInt16(self, "length")
|
|
size = min(self["length"].value, 255)
|
|
if size:
|
|
yield String(self, "name", size, charset="UTF-16LE")
|
|
|
|
class Directory(FieldSet):
|
|
def __init__(self, parent, name, res_type=None):
|
|
FieldSet.__init__(self, parent, name)
|
|
nb_entries = self["header/nb_name"].value + self["header/nb_index"].value
|
|
self._size = Header.static_size + nb_entries * 64
|
|
self.res_type = res_type
|
|
|
|
def createFields(self):
|
|
yield Header(self, "header")
|
|
|
|
if MAX_NAME_PER_HEADER < self["header/nb_name"].value:
|
|
raise ParserError("EXE resource: invalid number of name (%s)"
|
|
% self["header/nb_name"].value)
|
|
if MAX_INDEX_PER_HEADER < self["header/nb_index"].value:
|
|
raise ParserError("EXE resource: invalid number of index (%s)"
|
|
% self["header/nb_index"].value)
|
|
|
|
hdr = self["header"]
|
|
for index in xrange(hdr["nb_name"].value):
|
|
yield NameOffset(self, "name[]")
|
|
for index in xrange(hdr["nb_index"].value):
|
|
yield IndexOffset(self, "index[]", self.res_type)
|
|
|
|
def createDescription(self):
|
|
return self["header"].description
|
|
|
|
class PE_Resource(SeekableFieldSet):
|
|
def __init__(self, parent, name, section, size):
|
|
SeekableFieldSet.__init__(self, parent, name, size=size)
|
|
self.section = section
|
|
|
|
def parseSub(self, directory, name, depth):
|
|
indexes = []
|
|
for index in directory.array("index"):
|
|
if index["is_subdir"].value:
|
|
indexes.append(index)
|
|
|
|
#indexes.sort(key=lambda index: index["offset"].value)
|
|
for index in indexes:
|
|
self.seekByte(index["offset"].value)
|
|
if depth == 1:
|
|
res_type = index["type"].value
|
|
else:
|
|
res_type = directory.res_type
|
|
yield Directory(self, name, res_type)
|
|
|
|
def createFields(self):
|
|
# Parse directories
|
|
depth = 0
|
|
subdir = Directory(self, "root")
|
|
yield subdir
|
|
subdirs = [subdir]
|
|
alldirs = [subdir]
|
|
while subdirs:
|
|
depth += 1
|
|
if MAX_DEPTH < depth:
|
|
self.error("EXE resource: depth too high (%s), stop parsing directories" % depth)
|
|
break
|
|
newsubdirs = []
|
|
for index, subdir in enumerate(subdirs):
|
|
name = "directory[%u][%u][]" % (depth, index)
|
|
try:
|
|
for field in self.parseSub(subdir, name, depth):
|
|
if field.__class__ == Directory:
|
|
newsubdirs.append(field)
|
|
yield field
|
|
except HACHOIR_ERRORS, err:
|
|
self.error("Unable to create directory %s: %s" % (name, err))
|
|
subdirs = newsubdirs
|
|
alldirs.extend(subdirs)
|
|
|
|
# Create resource list
|
|
resources = []
|
|
for directory in alldirs:
|
|
for index in directory.array("index"):
|
|
if not index["is_subdir"].value:
|
|
resources.append(index)
|
|
|
|
# Parse entries
|
|
entries = []
|
|
for resource in resources:
|
|
offset = resource["offset"].value
|
|
if offset is None:
|
|
continue
|
|
self.seekByte(offset)
|
|
entry = Entry(self, "entry[]", inode=resource)
|
|
yield entry
|
|
entries.append(entry)
|
|
entries.sort(key=lambda entry: entry["rva"].value)
|
|
|
|
# Parse resource content
|
|
for entry in entries:
|
|
try:
|
|
offset = self.section.rva2file(entry["rva"].value)
|
|
padding = self.seekByte(offset, relative=False)
|
|
if padding:
|
|
yield padding
|
|
yield ResourceContent(self, "content[]", entry)
|
|
except HACHOIR_ERRORS, err:
|
|
self.warning("Error when parsing entry %s: %s" % (entry.path, err))
|
|
|
|
size = (self.size - self.current_size) // 8
|
|
if size:
|
|
yield PaddingBytes(self, "padding_end", size)
|
|
|
|
class NE_VersionInfoNode(FieldSet):
|
|
TYPE_STRING = 1
|
|
TYPE_NAME = {
|
|
0: "binary",
|
|
1: "string",
|
|
}
|
|
|
|
def __init__(self, parent, name):
|
|
FieldSet.__init__(self, parent, name)
|
|
self._size = alignValue(self["size"].value, 4) * 8
|
|
|
|
def createFields(self):
|
|
yield UInt16(self, "size", "Node size (in bytes)")
|
|
yield UInt16(self, "data_size")
|
|
yield CString(self, "name", charset="ISO-8859-1")
|
|
|
|
size = paddingSize(self.current_size//8, 4)
|
|
if size:
|
|
yield NullBytes(self, "padding[]", size)
|
|
size = self["data_size"].value
|
|
if size:
|
|
if self["name"].value == "VS_VERSION_INFO":
|
|
yield VersionInfoBinary(self, "value", size=size*8)
|
|
else:
|
|
yield String(self, "value", size, charset="ISO-8859-1")
|
|
while 12 <= (self.size - self.current_size) // 8:
|
|
yield NE_VersionInfoNode(self, "node[]")
|
|
size = (self.size - self.current_size) // 8
|
|
if size:
|
|
yield NullBytes(self, "padding[]", size)
|
|
|
|
|
|
def createDescription(self):
|
|
text = "Version info node: %s" % self["name"].value
|
|
# if self["type"].value == self.TYPE_STRING and "value" in self:
|
|
# text += "=%s" % self["value"].value
|
|
return text
|
|
|