258 lines
9.0 KiB
Python
258 lines
9.0 KiB
Python
import destruct
|
|
|
|
from .util import cached_property
|
|
from .common import CLIStreamType, CLIStreamIndex, CLITableType, CLITableIndex, CLITableRange, CLIToken, CLICodedToken
|
|
from .tables import CLITypeClassSemantics, CLIAssemblyRefTable
|
|
from .code import determine_header
|
|
|
|
|
|
WRAPPERS = {}
|
|
|
|
def register_wrapper(type, check=None):
|
|
def inner(c):
|
|
WRAPPERS.setdefault(type, []).append((c, check))
|
|
return c
|
|
return inner
|
|
|
|
def wrap(file, table, index, c):
|
|
for (w, check) in WRAPPERS.get(table, []):
|
|
if check is None or check(c):
|
|
return w(file, table, index, c)
|
|
return CLITableWrapper(file, table, index, c)
|
|
|
|
|
|
class CLIOffsetWrapper:
|
|
def __init__(self, file, offset, raw):
|
|
self._file = file
|
|
self._offset = offset
|
|
self._raw = raw
|
|
|
|
def __hash__(self):
|
|
return hash(self._raw)
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, CLIOffsetWrapper):
|
|
raise TypeError
|
|
return self._raw == other._raw
|
|
|
|
def __dir__(self):
|
|
return dir(self._raw)
|
|
|
|
def __getattr__(self, name):
|
|
val = getattr(self._raw, name)
|
|
if isinstance(self._raw, destruct.Struct):
|
|
type = self._raw._spec[name]
|
|
if isinstance(type, CLIStreamIndex):
|
|
stream = type.type
|
|
if stream == CLIStreamType.String:
|
|
val = self._file.get_string_at(val)
|
|
elif stream == CLIStreamType.Blob:
|
|
val = self._file.get_blob_at(val)
|
|
elif stream == CLIStreamType.UserString:
|
|
val = self._file.get_user_string_at(val)
|
|
elif stream == CLIStreamType.GUID:
|
|
val = self._file.get_guid_at(val)
|
|
if type.child:
|
|
c = destruct.Context(type.child)
|
|
c.user.metadata = self._file.streams[CLIStreamType.Metadata]
|
|
val = destruct.parse(type.child, val, c)
|
|
return val
|
|
elif isinstance(type, CLITableIndex):
|
|
table = type.type
|
|
return self._file.get_table_entry(table, val)
|
|
elif type == CLIToken or isinstance(type, CLICodedToken):
|
|
return self._file.get_by_token(val)
|
|
return val
|
|
|
|
class CLICode(CLIOffsetWrapper):
|
|
def __repr__(self):
|
|
return '<CLICode: {} bytes @ {}>'.format(self.code_size, self._offset)
|
|
|
|
|
|
class CLITableWrapper:
|
|
#__slots__ = ('__file', '__table', '__index', '__child')
|
|
|
|
def __init__(self, file, table, index, raw):
|
|
self._file = file
|
|
self._table = table
|
|
self._index = index
|
|
self._raw = raw
|
|
|
|
def __hash__(self):
|
|
return hash(self._raw)
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, CLITableWrapper):
|
|
raise TypeError
|
|
return self._raw == other._raw
|
|
|
|
def __dir__(self):
|
|
return dir(self._raw)
|
|
|
|
def __getattr__(self, name):
|
|
val = getattr(self._raw, name)
|
|
if isinstance(self._raw, destruct.Struct):
|
|
type = self._raw._spec[name]
|
|
if isinstance(type, CLIStreamIndex):
|
|
stream = type.type
|
|
if stream == CLIStreamType.String:
|
|
val = self._file.get_string_at(val)
|
|
elif stream == CLIStreamType.Blob:
|
|
val = self._file.get_blob_at(val)
|
|
elif stream == CLIStreamType.UserString:
|
|
val = self._file.get_user_string_at(val)
|
|
elif stream == CLIStreamType.GUID:
|
|
val = self._file.get_guid_at(val)
|
|
if type.child:
|
|
c = destruct.Context(type.child)
|
|
c.user.metadata = self._file.streams[CLIStreamType.Metadata]
|
|
val = destruct.parse(type.child, val, c)
|
|
return val
|
|
elif isinstance(type, CLITableIndex):
|
|
table = type.type
|
|
return self._file.get_table_entry(table, val)
|
|
elif isinstance(type, CLITableRange):
|
|
table = type.type
|
|
if self._index < self._file.get_table_size(self._table):
|
|
next = self._file.get_table_entry(self._table, self._index + 1)
|
|
end = min(self._file.get_table_size(table), getattr(next._raw, name))
|
|
else:
|
|
end = self._file.get_table_size(table)
|
|
return [self._file.get_table_entry(table, i) for i in range(val, end)]
|
|
elif type == CLIToken or isinstance(type, CLICodedToken):
|
|
return self._file.get_by_token(val)
|
|
return val
|
|
|
|
def __repr__(self):
|
|
return '<{}: {!r} in {!r}>'.format(self.__class__.__name__, self._raw, self._file)
|
|
|
|
@register_wrapper(CLITableType.TypeRef)
|
|
class CLITypeRef(CLITableWrapper):
|
|
def __repr__(self):
|
|
namespace = self.namespace
|
|
return '<{}: {}{} (from {})>'.format(self.__class__.__name__, namespace + '.' if namespace else '', self.name, self.scope.name)
|
|
|
|
@register_wrapper(CLITableType.Property)
|
|
class CLIProperty(CLITableWrapper):
|
|
@cached_property
|
|
def constant(self):
|
|
try:
|
|
return next(c for c in self._file.get_table(CLITableType.Constant) if c.parent == self)
|
|
except StopIteration:
|
|
return None
|
|
|
|
def __repr__(self):
|
|
return '<{}: {} {}>'.format(self.__class__.__name__, self.signature, self.name)
|
|
|
|
@register_wrapper(CLITableType.Param)
|
|
class CLIParam(CLITableWrapper):
|
|
@cached_property
|
|
def marshal(self):
|
|
try:
|
|
return next(l for l in self._file.get_table(CLITableType.FieldMarshal) if l.parent == self)
|
|
except StopIteration:
|
|
return None
|
|
|
|
@cached_property
|
|
def constant(self):
|
|
try:
|
|
return next(c for c in self._file.get_table(CLITableType.Constant) if c.parent == self)
|
|
except StopIteration:
|
|
return None
|
|
|
|
def __repr__(self):
|
|
return '<{} ({}): {}>'.format(self.__class__.__name__, self.sequence, self.name)
|
|
|
|
@register_wrapper(CLITableType.Field)
|
|
class CLIField(CLITableWrapper):
|
|
@cached_property
|
|
def parent(self):
|
|
return next(c for c in self._file.classes if self in c.fields)
|
|
|
|
@cached_property
|
|
def layout(self):
|
|
try:
|
|
return next(l for l in self._file.get_table(CLITableType.FieldLayout) if l.field == self)
|
|
except StopIteration:
|
|
return None
|
|
|
|
@cached_property
|
|
def marshal(self):
|
|
try:
|
|
return next(l for l in self._file.get_table(CLITableType.FieldMarshal) if l.parent == self)
|
|
except StopIteration:
|
|
return None
|
|
|
|
@cached_property
|
|
def constant(self):
|
|
try:
|
|
return next(c for c in self._file.get_table(CLITableType.Constant) if c.parent == self)
|
|
except StopIteration:
|
|
return None
|
|
|
|
def __repr__(self):
|
|
return '<{}: {} {}>'.format(self.__class__.__name__, self.signature, self.name)
|
|
|
|
@register_wrapper(CLITableType.MethodDef)
|
|
class CLIMethod(CLITableWrapper):
|
|
@cached_property
|
|
def parent(self):
|
|
return next(c for c in self._file.classes if self in c.methods)
|
|
|
|
@property
|
|
def code(self):
|
|
b = self._file.read_at(self.rva, 1)
|
|
header = determine_header(b)
|
|
return CLICode(self._file, self.rva, self._file.parse_at(header, self.rva))
|
|
|
|
def __repr__(self):
|
|
return '<{}: {} = {}>'.format(self.__class__.__name__, self.name, self.signature)
|
|
|
|
@register_wrapper(CLITableType.TypeDef, check=lambda x: x.flags.semantics == CLITypeClassSemantics.Class)
|
|
class CLIClass(CLITableWrapper):
|
|
@cached_property
|
|
def parent(self):
|
|
try:
|
|
return next(c.enclosing for c in self._file.get_table(CLITableType.NestedClass) if c.nested == self)
|
|
except StopIteration:
|
|
return None
|
|
|
|
@cached_property
|
|
def children(self):
|
|
return (c.nested for c in self._file.get_table(CLITableType.NestedClass) if c.enclosing == self)
|
|
|
|
@cached_property
|
|
def layout(self):
|
|
try:
|
|
return next(l for l in self._file.get_table(CLITableType.ClassLayout) if l.parent == self)
|
|
except StopIteration:
|
|
return None
|
|
|
|
@property
|
|
def implements(self):
|
|
return (i.interface for i in self._file.get_table(CLITableType.InterfaceImpl) if i.type == self)
|
|
|
|
@property
|
|
def properties(self):
|
|
try:
|
|
return next(p.properties for p in self._file.get_table(CLITableType.PropertyMap) if p.parent == self)
|
|
except StopIteration:
|
|
return []
|
|
|
|
def __repr__(self):
|
|
namespace = self.namespace
|
|
return '<{}: {}{}>'.format(self.__class__.__name__, namespace + '.' if namespace else '', self.name)
|
|
|
|
@register_wrapper(CLITableType.Assembly)
|
|
@register_wrapper(CLITableType.AssemblyRef)
|
|
class CLIAssembly(CLITableWrapper):
|
|
@property
|
|
def external(self):
|
|
return isinstance(self._raw, CLIAssemblyRefTable)
|
|
|
|
def __repr__(self):
|
|
return '<{}: {} (v{}.{}.{}.{})>'.format(
|
|
self.__class__.__name__, self.name,
|
|
self.version_major, self.version_minor, self.build_number, self.rev_number
|
|
)
|