120 lines
3.3 KiB
Python
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
|
|
|