105 lines
3.6 KiB
Python
105 lines
3.6 KiB
Python
from includes.helpers import load_tsv, dump_tsv
|
|
from includes.rom_serde import FF5SNESHandler, FF5GBAHandler
|
|
from argparse import ArgumentParser
|
|
from configparser import ConfigParser
|
|
from glob import glob
|
|
import re
|
|
|
|
|
|
parser = ArgumentParser(description='The ROMhacking Table Compiler.')
|
|
parser.add_argument('action', choices=['extract', 'build'])
|
|
parser.add_argument('rom', help='The ROM to use as a basis for extracting data.')
|
|
parser.add_argument('project', help='The project folder to extract data to, or compile data from.')
|
|
parser.add_argument('tables', nargs='*', help='Specify which tables to extract or compile, separated by spaces. If left empty, nothing will be extracted, or all tables in a project will be compiled. See the labels in https://git.ufeff.net/birdulon/ChocolateBirdData/src/branch/master/5/addresses_SNES_PSX.tsv for a list of values which may be used, though bear in mind things such as graphics and maps are currently not supported in a sensible way.')
|
|
|
|
|
|
known_roms = {
|
|
'Final Fantasy V': {
|
|
'SNES': {
|
|
'any': {'filename': r'.*Final Fantasy [V5].*\.sfc', 'handler': FF5SNESHandler()},
|
|
},
|
|
'GBA': {
|
|
'U': {'filename': r'2564 - .*\.gba', 'handler': FF5GBAHandler('U')},
|
|
'E': {'filename': r'2727 - .*\.gba', 'handler': FF5GBAHandler('E')},
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
def guess_rom(filename: str) -> dict:
|
|
for game, gd in known_roms.items():
|
|
for platform, pd in gd.items():
|
|
for region, rd in pd.items():
|
|
if re.fullmatch(rd['filename'], filename):
|
|
return {'Game': game, 'Platform': platform, 'Region': region}
|
|
|
|
|
|
def main():
|
|
args = parser.parse_args()
|
|
|
|
if not args.rom:
|
|
print('No ROM specified!')
|
|
return
|
|
if not args.project:
|
|
return
|
|
|
|
rom_id = guess_rom(args.rom)
|
|
|
|
project_folder = args.project.rstrip('/') + '/'
|
|
project_folder_len = len(project_folder)
|
|
config = ConfigParser()
|
|
config['TabComp.Project'] = rom_id # {'Game': 'Final Fantasy V', 'Platform': 'SNES', 'Region': 'any'}
|
|
try:
|
|
with open(f'{project_folder}project.ini', 'r') as configfile:
|
|
config.read_file(configfile)
|
|
except FileNotFoundError:
|
|
pass
|
|
with open(f'{project_folder}project.ini', 'w') as configfile:
|
|
config.write(configfile)
|
|
|
|
game = config['TabComp.Project']['Game']
|
|
platform = config['TabComp.Project']['Platform']
|
|
region = config['TabComp.Project']['Region']
|
|
try:
|
|
handler = known_roms[game][platform][region]['handler']
|
|
except IndexError:
|
|
print(f'Unsupported ROM for project - "{game}" on "{platform}" with region "{region}"')
|
|
return
|
|
|
|
with open(args.rom, 'rb') as file:
|
|
rom_bytes = file.read()
|
|
in_buffer = bytearray(rom_bytes)
|
|
match args.action:
|
|
case 'extract':
|
|
if not args.tables:
|
|
print('Must specify tables to extract!')
|
|
return
|
|
tables = [table for table in args.tables]
|
|
print(f'Attempting to extract tables {tables}')
|
|
for table in tables:
|
|
data = handler.extract(table, in_buffer)
|
|
dump_tsv(f'{project_folder}{table}.tsv', data)
|
|
print('Done extracting!')
|
|
|
|
case 'build':
|
|
tables = [table for table in args.tables]
|
|
if not args.tables:
|
|
# Find all .tsv files in project folder
|
|
tables = [file[project_folder_len:-4] for file in glob(f'{project_folder}*.tsv')]
|
|
print(f'Attempting to build tables {tables}')
|
|
out_buffer = bytearray(rom_bytes)
|
|
for table in tables:
|
|
data = load_tsv(f'{project_folder}{table}.tsv')
|
|
handler.build_partial(table, data, in_buffer, out_buffer)
|
|
out_filename = f'{project_folder}rom.sfc'
|
|
with open(out_filename, 'wb') as file:
|
|
file.write(out_buffer)
|
|
print(f'Compiled to "{out_filename}", make your own .ips from this')
|
|
case _:
|
|
'Invalid action!'
|
|
return
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|