dethrace/tools/decode_datatxt.py

283 lines
6.8 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import enum
import sys
LONG_KEY = (
0x6c, 0x1b, 0x99, 0x5f, 0xb9, 0xcd, 0x5f, 0x13,
0xcb, 0x04, 0x20, 0x0e, 0x5e, 0x1c, 0xa1, 0x0e,
)
OTHER_LONG_KEY = (
0x67, 0xa8, 0xd6, 0x26, 0xb6, 0xdd, 0x45, 0x1b,
0x32, 0x7e, 0x22, 0x13, 0x15, 0xc2, 0x94, 0x37,
)
DEMO_KEY = (
0x58, 0x50, 0x3A, 0x76, 0xCB, 0xB6, 0x85, 0x65,
0x15, 0xCD, 0x5B, 0x07, 0xB1, 0x68, 0xDE, 0x3A,
)
class Method(enum.Enum):
Method1 = "1"
Method2 = "2"
Demo = "demo"
class Byte:
def __init__(self, v: int):
self.v = v & 0xff
def __add__(self, v: int):
return Byte(self.v + v)
def __iadd__(self, v: "Byte"):
self.v = (self.v + v) & 0xff
return self
def __sub__(self, v: int):
return Byte(self.v - v)
def __isub__(self, v: "Byte"):
self.v = (self.v - v) & 0xff
return self
def __mod__(self, v: int):
return Byte(self.v % v)
def __imod__(self, v: int):
self.v %= v
return self
def __xor__(self, v: int):
return Byte(self.v ^ v)
def __ixor__(self, v: int):
self.v = (self.v ^ v) & 0xff
return self
def __and__(self, v: int):
return Byte(self.v & v)
def __iand__(self, v: int):
self.v = (self.v & v) & 0xff
return self
def __or__(self, v: int):
return Byte(self.v | v)
def __ior__(self, v: int):
self.v = (self.v | v) & 0xff
return self
def __eq__(self, other):
if isinstance(other, Byte):
return self.v == other.v
elif isinstance(other, int):
return self.v == (other & 0xff)
raise ValueError(f"Object {other:r} of invalid type {type(other)}")
def __repr__(self):
return f"(byte 0x{self.v:02x})"
class Codec1:
def encode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
count = 0
eline = bytearray(len(line))
for i, c in enumerate(line):
if count == 2:
key = OTHER_LONG_KEY
if c == ord('/'):
count += 1
else:
count = 0
if c == ord('\t'):
c = 0x9f
b = Byte(c)
b -= 0x20
b ^= key[seed]
b &= 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x9f:
b = Byte(ord('\t'))
eline[i] = b.v
return bytes(eline)
def decode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if dline[i - 2:i] == b'//':
key = OTHER_LONG_KEY
if b == ord(b'\t'):
b = Byte(0x9f)
b -= 0x20
b ^= key[seed]
b &= 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x9f:
b = Byte(ord(b'\t'))
dline[i] = b.v
return dline
class Codec2:
def encode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
count = 0
eline = bytearray(len(line))
for i, c in enumerate(line):
if count == 2:
key = OTHER_LONG_KEY
if c == ord('/'):
count += 1
else:
count = 0
if c == ord('\t'):
c = 0x80
b = Byte(c - 0x20)
if (b & 0x80) == 0:
b ^= key[seed] & 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x80:
b = Byte(ord('\t'))
eline[i] = b.v
return bytes(eline)
def decode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = LONG_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if dline[i - 2:i] == b'//':
key = OTHER_LONG_KEY
if b == ord(b'\t'):
b = Byte(0x80)
b -= 0x20
if (b & 0x80) == 0:
b ^= key[seed] & 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x80:
b = Byte(ord(b'\t'))
dline[i] = b.v
return dline
class CodecDemo:
def encode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = DEMO_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if b == ord('\t'):
b = Byte(0x9f)
b -= 0x20
b ^= key[seed]
b &= 0x7f
b += 0x20
if b == 0x9f:
b = Byte(ord('\t'))
if b == ord('\n') or b == ord('\r'):
b |= 0x80
seed += 7
seed %= len(key)
dline[i] = b.v
return dline
def decode_line(self, line: bytes) -> bytes:
line = line.rstrip(b"\r\n")
key = DEMO_KEY
seed = len(line) % len(key)
dline = bytearray(len(line))
for i, c in enumerate(line):
b = Byte(c)
if b == ord('\t'):
b = Byte(0x9f)
b -= 0x20
b ^= key[seed]
b &= 0x7f
b += 0x20
seed += 7
seed %= len(key)
if b == 0x9f:
b = Byte(ord('\t'))
dline[i] = b.v
return dline
CODECS = {
Method.Method1: Codec1,
Method.Method2: Codec2,
Method.Demo: CodecDemo,
}
def main():
method_choices = tuple(e.value for e in Method.__members__.values())
parser = argparse.ArgumentParser(allow_abbrev=False, description="Decode/encode a Carmageddon text file")
parser.add_argument("file", metavar="FILE", nargs="?", help="input file (default=stdin)")
parser.add_argument("--method", choices=method_choices, default=Method.Method2.value,
help=f"encryption method to use (default={Method.Method2.value}, choices={','.join(method_choices)})")
args = parser.parse_args()
method = Method(args.method)
codec = CODECS[method]()
istream = open(args.file, "rb") if args.file else sys.stdin.buffer
for line in istream.readlines():
if line[0] == ord(b"@"):
dline = codec.decode_line(line[1:])
sys.stdout.buffer.write(dline)
else:
eline = b"@" + codec.encode_line(line)
sys.stdout.buffer.write(eline)
sys.stdout.buffer.write(b'\n')
if __name__ == "__main__":
raise SystemExit(main())