mirror of
https://github.com/a2-4am/4cade.git
synced 2024-11-29 22:49:26 +00:00
107 lines
3.7 KiB
Python
Executable File
107 lines
3.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# parameters
|
|
# stdin - input containing list of game metadata, filename, display name (e.g. GAMES.CONF or some subset of it)
|
|
# stdout - binary OKVS data structure
|
|
# 1 - output filename for game count in assembler code format
|
|
# 2 - input filename of HGR titles, offsets, and sizes
|
|
# 3 - input filename of DHGR titles, offsets, and sizes
|
|
|
|
import argparse
|
|
import pprint
|
|
import struct
|
|
import sys
|
|
|
|
gSearchIndex = 0x6000 # must match gSearchIndex in src/constants.a
|
|
|
|
# indexes into |flags| as string
|
|
iDHGRTitle = 2
|
|
iCheatCategory = 3
|
|
iSingleLoad = 4
|
|
|
|
# maps of flag raw string value -> value in final flags byte
|
|
kHasDHGRTitle = {'0': 0, '1': 128}
|
|
kSingleLoad = {'0': 0, '1': 64}
|
|
|
|
def parse_log_file(filename):
|
|
rv = {}
|
|
if filename:
|
|
with open(filename, 'r') as f:
|
|
lines = [x.strip().split(',') for x in f.readlines()]
|
|
for title, offset, size in lines:
|
|
rv[title] = (int(offset), int(size))
|
|
return rv
|
|
|
|
def build(records, args):
|
|
# records is [(flags, key, value), (flags, key, value) ...]
|
|
hgr_cache = parse_log_file(args.input_hgr_log_file)
|
|
dhgr_cache = parse_log_file(args.input_dhgr_log_file)
|
|
cache_ptr = {'0': hgr_cache, '1': dhgr_cache}
|
|
record_count = len(records)
|
|
|
|
# generate source file with game count
|
|
with open(args.output_game_count_file, 'w') as file_handle:
|
|
file_handle.write(f""";
|
|
; Game count
|
|
;
|
|
; This file is automatically generated
|
|
;
|
|
!word {record_count:>8}
|
|
""")
|
|
|
|
# lookup table is stored after all record data, so first calculate total record size
|
|
# record_count * (length-prefixed key + length-prefixed value + 8 other bytes)
|
|
# then lookup table address is that + gSearchIndex + 4 bytes for the OKVS header
|
|
total_record_size = len("".join([x for xs in records for x in xs[1:]])) + 10*record_count
|
|
|
|
# yield OKVS header (2 x 2 bytes, unsigned int, little-endian)
|
|
yield struct.pack('<2H', record_count, total_record_size + gSearchIndex + 4)
|
|
|
|
rec_key_address = gSearchIndex + 5
|
|
key_addresses = []
|
|
for flags, key, value in records:
|
|
key_addresses.append(rec_key_address)
|
|
rec_length = len(key) + len(value) + 10
|
|
rec_key_address += rec_length
|
|
|
|
# yield record length (1 byte)
|
|
yield struct.pack('B', rec_length)
|
|
|
|
# yield key (Pascal-style string)
|
|
yield struct.pack(f'{len(key)+1}p', key.encode('ascii'))
|
|
|
|
# yield value (Pascal-style string)
|
|
yield struct.pack(f'{len(value)+1}p', value.encode('ascii'))
|
|
|
|
yield struct.pack('B', 1)
|
|
|
|
# yield flags
|
|
has_dhgr_title = dhgr_cache and flags[iDHGRTitle] or '0'
|
|
yield struct.pack('B', kHasDHGRTitle[has_dhgr_title] + \
|
|
kSingleLoad[flags[iSingleLoad]] + \
|
|
int(flags[iCheatCategory]))
|
|
|
|
rec_offset, rec_size = cache_ptr[has_dhgr_title][key]
|
|
|
|
# yield record offset (3 bytes, big-endian, unsigned long)
|
|
yield struct.pack('>L', rec_offset)[1:]
|
|
|
|
# yield record size (2 bytes, little-endian, unsigned short)
|
|
yield struct.pack('<H', rec_size)
|
|
|
|
# yield lookup table
|
|
yield struct.pack(f'<{record_count}H', *key_addresses)
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Build indexed OKVS structure for search cache")
|
|
parser.add_argument("output_game_count_file")
|
|
parser.add_argument("input_hgr_log_file")
|
|
parser.add_argument("input_dhgr_log_file")
|
|
args = parser.parse_args()
|
|
records = [x.strip() for x in sys.stdin.readlines()]
|
|
records = [x.replace('=',',').split(',')
|
|
for x in records
|
|
if x and x[0] not in ('#', '[')]
|
|
for b in build(records, args):
|
|
sys.stdout.buffer.write(b)
|