2024-06-27 17:16:55 +09:30
from includes . helpers import load_tsv , dump_tsv
2024-06-30 21:39:02 +09:30
from includes . rom_serde import FF5SNESHandler , FF5GBAHandler
from argparse import ArgumentParser
from configparser import ConfigParser
from glob import glob
import re
2024-06-26 23:59:10 +09:30
2024-06-30 21:39:02 +09:30
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 ( ) :
2024-06-26 23:59:10 +09:30
args = parser . parse_args ( )
2024-06-30 21:39:02 +09:30
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! ' )
2024-06-26 23:59:10 +09:30
return
2024-06-30 21:39:02 +09:30
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 ( )