exorcise/exorcise/properties.py

120 lines
3.3 KiB
Python

import hashlib
import enum
import destruct
from destruct import Type, Struct, Bool, Int, UInt, Data, Str
PROPERTY_OBFUSCATION_KEY = b'[one](two)*three*^four^!eleven!{ninetytwo}#3.141_592_653_589_793#|seventeen|@299792458@\x00'
def encode_property_tag(n):
d = hashlib.md5(PROPERTY_OBFUSCATION_KEY + n.encode('ascii')).digest()
return d[1] | (d[6] << 8) | (d[11] << 16) | (d[12] << 24)
def make_property_enum(name, values):
return enum.Enum(name, {n: encode_property_tag(n) for n in values})
class PropertyType(enum.Enum):
Void = 0xC1
I8 = 0xC2
U8 = 0xC3
I16 = 0xC4
U16 = 0xC5
I32 = 0xC6
U32 = 0xC7
I64 = 0xC8
U64 = 0xC9
Data = 0xCA
Buf2 = 0xCB
Bool = 0xCC
Time = 0xCD
Str = 0xCE
PSet = 0xCF
Buf5 = 0xD1
class PropertyBuf(Struct, generics=['D']):
length = UInt(32)
data = Capped(D)
def parse(self, input, context):
res = super().parse(input, context)
return res.data
def on_length(self, spec, context):
spec.data.limit = self.length
class PropertyParser(Type):
PARSERS = {
PropertyType.I8: lambda: Int(8),
PropertyType.U8: lambda: UInt(8),
PropertyType.I16: lambda: Int(16),
PropertyType.U16: lambda: UInt(16),
PropertyType.I32: lambda: Int(32),
PropertyType.U32: lambda: UInt(32),
PropertyType.I64: lambda: Int(64),
PropertyType.U64: lambda: UInt(64),
PropertyType.Data: PropertyBuf[Data(None)],
PropertyType.Buf2: PropertyBuf[Data(None)],
PropertyType.Str: PropertyBuf[Str(kind='raw')],
PropertyType.Buf5: PropertyBuf[Data(None)],
PropertyType.Bool: Bool,
PropertyType.Time: lambda: UInt(64),
}
def __init__(self, type=None, tag_type=None):
self.type = type
self.tag_type = tag_type
def parse(self, input, context):
if self.type == PropertyType.PSet:
parser = PropertyBuf[PropertySet[self.tag_type]]
else:
parser = self.PARSERS[self.type]
return destruct.parse(parser(), input, context)
class Property(Struct, generics=['TT']):
tag = Enum(TT, UInt(32))
type = Enum(PropertyType, UInt(8))
amount = UInt(32)
value = Arr(PropertyParser(tag_type=TT))
def on_type(self, spec, context):
spec.value.child.type = self.type
def on_amount(self, spec, context):
spec.value.count = self.amount
def __str__(self):
return '{} ({}): {}'.format(self.tag, self.type.name, destruct.format_value(self.value, str))
def pad_to(v, n):
return (n - (v % n)) % n
def round_to(v, n):
return v + pad_to(v, n)
class PropertySet(Struct, generics=['TT']):
magic = Sig(b'GHPR')
version = UInt(32)
length = UInt(32)
properties = Arr(Property[TT])
_pad = Data(0)
length2 = UInt(32)
version2 = UInt(32)
magic2 = Sig(b'RPHG')
def on_length(self, spec, context):
spec.properties.max_length = self.length
spec._pad.length = pad_to(self.length, 4)
def parse(self, input, context):
value = super().parse(input, context)
res = {}
for prop in value.properties:
if prop.tag in res:
raise ValueError('Tag {} already in result!'.format(prop.tag))
res[prop.tag] = prop.value
return res