-
-
Save xyzz/e027d4f1505c6f4dd7d8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import sys | |
import binascii | |
from struct import unpack, pack | |
lines = [] | |
first = None | |
def gets(data, start): | |
global first | |
if not first: | |
first = start | |
x = b"" | |
cur = start | |
while (cur - start) % 2 != 0 or data[cur:cur + 2] != b"\x00\x00": | |
x += pack("B", data[cur]) | |
cur += 1 | |
s = x.decode("utf-16le") | |
return s | |
def dump(data, start): | |
lines.append(gets(data, start)) | |
def check(data, pos, what): | |
for x in what: | |
if data[pos:pos + len(x)] == x: | |
return True | |
return False | |
def hexify(data): | |
return binascii.hexlify(data).decode("ascii") | |
skip = { | |
b"\x0c\x0f": 15, | |
b"\x18\x06": 6, | |
} | |
def to_int(data): | |
return int.from_bytes(data, byteorder="little") | |
def decode(input_file, output_file): | |
# print("working with ", input_name) | |
data = open(input_file, "rb").read() | |
size = unpack("<I", data[6:10])[0] | |
commands = [] | |
commands.append([ | |
"header", | |
data[2] | |
]) | |
cur = 0x20 | |
counter = 0 | |
while counter < size and cur < len(data): | |
banner = data[cur:cur + 2] | |
# 09 08 AA AA BB BB BB BB | |
# ^ ^ string address | |
# | number, in order | |
if banner == b"\x09\x08": | |
commands.append([ | |
"msg1", | |
gets(data, to_int(data[cur + 4:cur + 8])) | |
]) | |
cur += 8 | |
counter += 1 | |
# 0B 0F AA AA XX XX XX XX XX XX BB BB BB BB XX | |
# ^ ^ unknown ^ ^ unknown | |
# | number, in order | string address | |
elif banner == b"\x0b\x0f": | |
commands.append([ | |
"msg2", | |
hexify(data[cur + 4:cur + 10]), | |
int(data[cur + 14]), | |
gets(data, to_int(data[cur + 10:cur + 14])) | |
]) | |
cur += 15 | |
counter += 1 | |
# 13 03 AA | |
# ^ number of choices | |
# for each choice: | |
# XX XX XX XX XX XX BB BB BB BB | |
# ^ unknown ^ choice string address | |
elif banner == b"\x13\x03": | |
cnt = data[cur + 2] | |
cur += 3 | |
choices = [] | |
for x in range(cnt): | |
choices.append([ | |
hexify(data[cur:cur + 6]), | |
gets(data, to_int(data[cur + 6:cur + 10])) | |
]) | |
cur += 10 | |
commands.append([ | |
"choice", | |
choices | |
]) | |
elif banner in skip: | |
commands.append([ | |
"raw", | |
hexify(data[cur:cur + skip[banner]]) | |
]) | |
cur += skip[banner] | |
else: | |
offset = 0 | |
# i'm stupid so let's have a stupid heuristic here | |
while cur < len(data) and not check(data, cur, [b"\x09\x08" + pack("<H", counter), b"\x0b\x0f" + pack("<H", counter), b"\x13\x03"]): | |
cur += 1 | |
offset += 1 | |
data_skip = data[cur - offset:cur] | |
commands.append([ | |
"raw", | |
hexify(data_skip) | |
]) | |
if cur < first: | |
commands.append([ | |
"raw", | |
hexify(data[cur:first]) | |
]) | |
if counter != size: | |
print("WARN: counter != size") | |
fout = open(output_file, "w") | |
for command in commands: | |
fout.write(json.dumps(command, ensure_ascii=False)) | |
fout.write("\n") | |
fout.close() | |
# decrease = 0 | |
# while data[-1 - decrease] == 0: | |
# decrease += 1 | |
# total = len(" ".join(lines).encode("utf-16le")) | |
# original = len(data) - first - decrease + 1 | |
# print("total is: ", total) | |
# print("original is: ", original) | |
#if total == original: | |
# print("all fine", total) | |
#if total > original: | |
# print("WARNING: total > original") | |
#if original - total > 10: | |
# print("WARNING: original - total = ", original - total)""" | |
def encode(input_file, output_file): | |
commands = [] | |
fin = open(input_file, "r") | |
for line in fin.readlines(): | |
commands.append(json.loads(line)) | |
fin.close() | |
data = bytearray() | |
count = 0 | |
strings = [] | |
magic_header_constant = 0 | |
for command in commands: | |
if command[0] == "raw": | |
data += binascii.unhexlify(command[1].encode("ascii")) | |
elif command[0] == "msg1": | |
t = b"\x09\x08" + count.to_bytes(2, byteorder="little") + b"\x00\x00\x00\x00" | |
strings.append([command[1], len(data) + 4]) | |
data += t | |
count += 1 | |
elif command[0] == "msg2": | |
t = b"\x0b\x0f" + count.to_bytes(2, byteorder="little") + binascii.unhexlify(command[1].encode("ascii")) + b"\x00\x00\x00\x00" + command[2].to_bytes(1, byteorder="little") | |
strings.append([command[3], len(data) + 10]) | |
data += t | |
count += 1 | |
elif command[0] == "choice": | |
t = b"\x13\x03" + len(command[1]).to_bytes(1, byteorder="little") | |
for choice in command[1]: | |
t += binascii.unhexlify(choice[0].encode("ascii")) + b"\x00\x00\x00\x00" | |
strings.append([choice[1], len(data) + len(t) - 4]) | |
data += t | |
elif command[0] == "header": | |
magic_header_constant = command[1] | |
else: | |
assert(False == "unknown command") | |
# generate the header | |
header = b"\x18\x06" + magic_header_constant.to_bytes(4, byteorder="little") + count.to_bytes(4, byteorder="little") + b"\x00" * 22 | |
data = bytearray(header + data) | |
# process strings | |
for string, put_to in strings: | |
offset = len(data) | |
data += string.encode("utf-16le") + b"\x00\x00" | |
# print(string.encode("utf-16le")) | |
put_to += len(header) | |
assert(data[put_to:put_to + 4] == b'\x00\x00\x00\x00') | |
data[put_to:put_to + 4] = offset.to_bytes(4, byteorder="little") | |
if len(data) % 32 != 0: | |
data += b"\x00" * (32 - len(data) % 32) | |
fout = open(output_file, "wb") | |
fout.write(data) | |
fout.close() | |
if __name__ == "__main__": | |
if len(sys.argv) != 4: | |
print("Usage: script.py decode|encode INPUT OUTPUT") | |
print(" - decode decodes .tbl to text") | |
print(" - encode compiles text to .tbl") | |
sys.exit(0) | |
command = sys.argv[1] | |
if command == "decode": | |
decode(sys.argv[2], sys.argv[3]) | |
elif command == "encode": | |
encode(sys.argv[2], sys.argv[3]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment