193 lines
5.2 KiB
Python
193 lines
5.2 KiB
Python
import os
|
|
import enum
|
|
import zlib
|
|
import destruct
|
|
from destruct import Type, Union, Struct
|
|
|
|
|
|
def rol(v, b, n):
|
|
return ((v << n) & ((1 << b) - 1)) | (v >> (b - n))
|
|
|
|
def ror(v, b, n):
|
|
return (v >> n) | ((v << (b - n)) & ((1 << b) - 1))
|
|
|
|
class RNG:
|
|
def __init__(self):
|
|
self.state = 0
|
|
|
|
def seed(self, seed):
|
|
self.state = 0xFA07673E
|
|
for _ in range(seed):
|
|
self.next()
|
|
|
|
def next(self):
|
|
res = self.state
|
|
self.state = (self.state + ror(self.state, 32, 7)) & 0xFFFFFFFF
|
|
return res
|
|
|
|
def decrypt(buf, rng):
|
|
halfway = len(buf) // 2
|
|
left = reversed(buf[0:halfway])
|
|
right = reversed(buf[halfway:])
|
|
|
|
out = bytearray()
|
|
for (l, r) in zip(left, right):
|
|
rand = rng.next() & 7
|
|
val = rol(r | (l << 8), 16, rand)
|
|
out.append(val & 0xFF)
|
|
out.append(val >> 8)
|
|
|
|
return out
|
|
|
|
class GhostEncrypted(Type):
|
|
def __init__(self, child, seed=None, length=None):
|
|
self.child = child
|
|
self.seed = seed
|
|
self.length = length
|
|
|
|
def parse(self, input, context):
|
|
r = RNG()
|
|
r.seed(self.seed)
|
|
data = input.read(self.length)
|
|
return destruct.parse(self.child, decrypt(data, r), context)
|
|
|
|
def emit(self, value, output, context):
|
|
raise NotImplementedError
|
|
|
|
def sizeof(self, value, context):
|
|
return self.length
|
|
|
|
def __repr__(self):
|
|
return '<{}: {!r} (seed={}, length={})>'.format(class_name(self), self.child, self.seed, self.length)
|
|
|
|
class GhostCompressionType(enum.Enum):
|
|
Uncompressed = 0
|
|
Old = 1
|
|
LZRW3 = 2
|
|
Deflate = 3
|
|
|
|
class GhostCompressed(Type):
|
|
def __init__(self, child, type=None, length=None):
|
|
self.child = child
|
|
self.type = type
|
|
self.length = length
|
|
|
|
def parse(self, input, context):
|
|
data = input.read(self.length)
|
|
if self.type == GhostCompressionType.Deflate:
|
|
data = zlib.decompress(data)
|
|
elif self.type != GhostCompressionType.Uncompressed:
|
|
raise ValueError('compression type {} not implemented!'.format(self.type.name))
|
|
print('got', data)
|
|
v = destruct.parse(self.child, data, context)
|
|
return v
|
|
|
|
def emit(self, output, value, context):
|
|
b = io.BytesIO()
|
|
val = destruct.emit(self.child, value, b, context)
|
|
data = b.getvalue()
|
|
|
|
if self.type == GhostCompressionType.Deflate:
|
|
data = zlib.compress(data)
|
|
elif self.type != GhostCompressionType.Uncompressed:
|
|
raise ValueError('compression type {} not implemented!'.format(self.type.name))
|
|
output.write(data)
|
|
|
|
def sizeof(self, value, context):
|
|
return self.length
|
|
|
|
def __repr__(self):
|
|
return '<{}(type: {}, length: {})>'.format(destruct.class_name(self), self.type.name, self.length)
|
|
|
|
class GhostCompressedBuffer(Struct, generics=['D']):
|
|
length = UInt(16)
|
|
data = GhostCompressed(D, GhostCompressionType.Deflate)
|
|
|
|
def on_length(self, spec, context):
|
|
spec.data.length = self.length - 2
|
|
|
|
class GhostCompressedFile:
|
|
def __init__(self, handle, type):
|
|
self._handle = handle
|
|
self._type = type
|
|
self._pos = handle.tell()
|
|
self._buf = None
|
|
self._bufpos = 0
|
|
self._seeks = {}
|
|
|
|
def _read_buffer(self):
|
|
self._pos = self._handle.tell()
|
|
buf = destruct.parse(GhostCompressedBuffer[destruct.Data(None)], self._handle)
|
|
self._buf = buf.data
|
|
|
|
def seek(self, pos, whence=os.SEEK_SET):
|
|
if whence == os.SEEK_CUR:
|
|
pos += self._pos + self._bufpos
|
|
whence = os.SEEK_SET
|
|
|
|
if whence == os.SEEK_SET and self._buf is not None:
|
|
if self._pos <= pos <= self._pos + len(self._buf):
|
|
self._bufpos = pos - self._pos
|
|
return
|
|
|
|
if pos in self._seeks:
|
|
offset = pos - self._seeks[pos]
|
|
pos = self._seeks[pos]
|
|
else:
|
|
offset = 0
|
|
|
|
if self._buf is not None and self._bufpos < len(self._buf):
|
|
self._seeks[self._pos + self._bufpos] = self._pos
|
|
self._handle.seek(pos, whence)
|
|
self._pos = pos
|
|
self._buf = None
|
|
self._bufpos = offset
|
|
|
|
def tell(self):
|
|
if self._buf is not None:
|
|
return self._pos + self._bufpos
|
|
return self._handle.tell()
|
|
|
|
def read(self, n=-1):
|
|
data = b''
|
|
|
|
if self._buf is None:
|
|
self._read_buffer()
|
|
self._bufpos = 0
|
|
|
|
while n != 0:
|
|
remaining = len(self._buf) - self._bufpos
|
|
if n >= 0:
|
|
from_buf = min(n, remaining)
|
|
from_file = n - from_buf
|
|
else:
|
|
from_buf = remaining
|
|
from_file = -1
|
|
|
|
data += self._buf[self._bufpos:self._bufpos + from_buf]
|
|
self._bufpos += from_buf
|
|
n = from_file
|
|
|
|
if n != 0:
|
|
try:
|
|
self._read_buffer()
|
|
self._bufpos = 0
|
|
except:
|
|
if n > 0:
|
|
raise
|
|
break
|
|
|
|
return data
|
|
|
|
|
|
class GhostTime(Union):
|
|
timestamp = DateTime(UInt(32), timestamp=True)
|
|
seed = Data(4)
|
|
|
|
class GhostHeaderType(enum.Enum):
|
|
Drive = 1
|
|
Unk2 = 2
|
|
Unk3 = 3
|
|
Partition = 4
|
|
Unk5 = 5
|