mirror of
https://github.com/HarbourMasters/Starship.git
synced 2025-04-28 12:27:59 +03:00
Comptool support for byteswapped ROMs (#249)
* comptool update * further updates * lodgenet * comptool and main split * one last thing
This commit is contained in:
parent
6dd8ceb923
commit
1a5569bef5
10 changed files with 351 additions and 187 deletions
|
@ -6,18 +6,7 @@ import struct
|
|||
import argparse
|
||||
import sys
|
||||
|
||||
file_table_dict = {0xDE480:"US 1.1", 0xD9A90:"US 1.0", 0xE93C0:"JP 1.0"}
|
||||
|
||||
file_names_us = [
|
||||
"makerom", "main", "dma_table", "audio_seq", "audio_bank", "audio_table", "ast_common", "ast_bg_space", "ast_bg_planet",
|
||||
"ast_arwing", "ast_landmaster", "ast_blue_marine", "ast_versus", "ast_enmy_planet", "ast_enmy_space", "ast_great_fox",
|
||||
"ast_star_wolf", "ast_allies", "ast_corneria", "ast_meteo", "ast_titania", "ast_7_ti_2", "ast_8_ti", "ast_9_ti", "ast_A_ti",
|
||||
"ast_7_ti_1", "ast_sector_x", "ast_sector_z", "ast_aquas", "ast_area_6", "ast_venom_1", "ast_venom_2", "ast_ve1_boss",
|
||||
"ast_bolse", "ast_fortuna", "ast_sector_y", "ast_solar", "ast_zoness", "ast_katina", "ast_macbeth", "ast_warp_zone",
|
||||
"ast_title", "ast_map", "ast_option", "ast_vs_menu", "ast_text", "ast_font_3d", "ast_andross", "ast_logo", "ast_ending",
|
||||
"ast_ending_award_front", "ast_ending_award_back", "ast_ending_expert", "ast_training", "ast_radio", "ovl_i1", "ovl_i2",
|
||||
"ovl_i3", "ovl_i4", "ovl_i5", "ovl_i6", "ovl_menu", "ovl_ending", "ovl_unused"
|
||||
]
|
||||
file_table_dict = {"US 1.1":0xDE480, "US 1.0":0xD9A90, "JP 1.0":0xE93C0, "JP 1.1":0xF2A10, "EU 1.0":0xE0570, "AU 1.0":0xE0470, "LN 1.0":0xE44F0}
|
||||
|
||||
file_names_jp = [
|
||||
"makerom", "main", "dma_table", "audio_seq", "audio_bank", "audio_table", "ast_common", "ast_bg_space", "ast_bg_planet",
|
||||
|
@ -30,7 +19,63 @@ file_names_jp = [
|
|||
"ovl_i3", "ovl_i4", "ovl_i5", "ovl_i6", "ovl_menu", "ovl_ending", "ovl_unused"
|
||||
]
|
||||
|
||||
file_names_critical = {"makerom", "main", "dma_table", "audio_seq", "audio_bank", "audio_table"}
|
||||
file_names_us = [
|
||||
"makerom", "main", "dma_table", "audio_seq", "audio_bank", "audio_table", "ast_common", "ast_bg_space", "ast_bg_planet",
|
||||
"ast_arwing", "ast_landmaster", "ast_blue_marine", "ast_versus", "ast_enmy_planet", "ast_enmy_space", "ast_great_fox",
|
||||
"ast_star_wolf", "ast_allies", "ast_corneria", "ast_meteo", "ast_titania", "ast_7_ti_2", "ast_8_ti", "ast_9_ti", "ast_A_ti",
|
||||
"ast_7_ti_1", "ast_sector_x", "ast_sector_z", "ast_aquas", "ast_area_6", "ast_venom_1", "ast_venom_2", "ast_ve1_boss",
|
||||
"ast_bolse", "ast_fortuna", "ast_sector_y", "ast_solar", "ast_zoness", "ast_katina", "ast_macbeth", "ast_warp_zone",
|
||||
"ast_title", "ast_map", "ast_option", "ast_vs_menu", "ast_text", "ast_font_3d", "ast_andross", "ast_logo", "ast_ending",
|
||||
"ast_ending_award_front", "ast_ending_award_back", "ast_ending_expert", "ast_training", "ast_radio", "ovl_i1", "ovl_i2",
|
||||
"ovl_i3", "ovl_i4", "ovl_i5", "ovl_i6", "ovl_menu", "ovl_ending", "ovl_unused"
|
||||
]
|
||||
|
||||
file_names_pal = [
|
||||
"makerom", "main", "dma_table", "audio_seq", "audio_bank", "audio_table", "ast_common", "ast_bg_space", "ast_bg_planet",
|
||||
"ast_arwing", "ast_landmaster", "ast_blue_marine", "ast_versus", "ast_enmy_planet", "ast_enmy_space", "ast_great_fox",
|
||||
"ast_star_wolf", "ast_allies", "ast_corneria", "ast_meteo", "ast_titania", "ast_7_ti_2", "ast_8_ti", "ast_9_ti", "ast_A_ti",
|
||||
"ast_7_ti_1", "ast_sector_x", "ast_sector_z", "ast_aquas", "ast_area_6", "ast_venom_1", "ast_venom_2", "ast_ve1_boss",
|
||||
"ast_bolse", "ast_fortuna", "ast_sector_y", "ast_solar", "ast_zoness", "ast_katina", "ast_macbeth", "ast_warp_zone",
|
||||
"ast_title", "ast_map", "ast_option", "ast_vs_menu", "ast_text", "ast_unk_1", "ast_unk_2", "ast_unk_3",
|
||||
"ast_unk_4", "ast_unk_5", "ast_unk_6", "ast_unk_7", "ast_unk_8", "ast_unk_9", "ast_font_3d", "ast_andross","ast_logo", "ast_ending",
|
||||
"ast_ending_award_front", "ast_ending_award_back", "ast_ending_expert", "ast_training", "ast_radio_de", "ovl_i1", "ovl_i2", "ovl_i3",
|
||||
"ovl_i4", "ovl_i5", "ovl_i6", "ovl_menu", "ovl_ending", "ovl_unused", "ast_radio_en", "ast_radio_fr"
|
||||
]
|
||||
|
||||
file_names_critical = ["makerom", "main", "dma_table", "audio_seq", "audio_bank", "audio_table"]
|
||||
|
||||
decomp_inds_ntsc = [0, 1, 2, 3, 4, 5, 15, 16, 21, 22, 23, 24, 48]
|
||||
decomp_inds_pal = [0, 1, 2, 3, 4, 5, 15, 16, 21, 22, 23, 24, 57]
|
||||
|
||||
def get_version_info(ROM):
|
||||
with open(ROM, "rb") as ROMfile:
|
||||
ROMfile.seek(0x3E, 0)
|
||||
region = ROMfile.read(1).decode()
|
||||
rev =" 1.%d" % int.from_bytes(ROMfile.read(1), 'big')
|
||||
|
||||
|
||||
if region == "J":
|
||||
file_names = file_names_jp
|
||||
decomp_inds = decomp_inds_ntsc
|
||||
version = "JP"
|
||||
elif region == "E" or region == "G":
|
||||
file_names = file_names_us
|
||||
decomp_inds = decomp_inds_ntsc
|
||||
version = "LN" if region == "G" else "US"
|
||||
elif region == "P" or region == "U":
|
||||
print("Warning: PAL menu assets are not fully documented.")
|
||||
file_names = file_names_pal
|
||||
decomp_inds = decomp_inds_pal
|
||||
version = "AU" if region == "U" else "EU"
|
||||
else:
|
||||
file_names = "file_%d_%X"
|
||||
decomp_inds = None
|
||||
version = "Unknown"
|
||||
|
||||
if version != "Unknown":
|
||||
version += rev
|
||||
|
||||
return (version, file_names, decomp_inds)
|
||||
|
||||
def int32(x):
|
||||
return x & 0xFFFFFFFF
|
||||
|
@ -101,35 +146,102 @@ def mio0_dec_bytes(comp_bytes, mio0):
|
|||
|
||||
return decomp_bytes
|
||||
|
||||
swap_backup = False
|
||||
|
||||
def fix_byte_swap(ROM, outROM):
|
||||
with open(ROM, 'rb') as ROMfile:
|
||||
ROMfile.seek(0x20,0)
|
||||
|
||||
game_str = ROMfile.read(4).decode()
|
||||
|
||||
if game_str == "STAR":
|
||||
print("Provided ROM is big endian.")
|
||||
return ROM
|
||||
|
||||
ROMfile.seek(0,0)
|
||||
|
||||
ROM_bytes = ROMfile.read()
|
||||
|
||||
s = game_str.find("S")
|
||||
t = game_str.find("T")
|
||||
a = game_str.find("A")
|
||||
r = game_str.find("R")
|
||||
|
||||
if(s == -1 or t == -1 or a == -1 or r==-1):
|
||||
print('Name string absent. There may be a problem with your ROM.')
|
||||
sys.exit(2)
|
||||
|
||||
if game_str == "RATS":
|
||||
print("Provided ROM is little endian.")
|
||||
byte_order = "LE"
|
||||
suffix = ".LE.n64"
|
||||
elif game_str == "TSRA":
|
||||
print("Provided ROM is byteswapped.")
|
||||
byte_order = "BS"
|
||||
suffix = ".BS.v64"
|
||||
else:
|
||||
byte_order = "%d%d%d%d" % (s, t, a, r)
|
||||
suffix = "." + byte_order + ".u64"
|
||||
print("Provided ROM has unusual byte order " + byte_order)
|
||||
if swap_backup:
|
||||
backup = os.path.splitext(ROM)[0] + suffix
|
||||
with open(backup, "wb") as bakfile:
|
||||
print("Writing backup file " + backup)
|
||||
bakfile.write(ROM_bytes)
|
||||
outROM = ROM
|
||||
|
||||
ROM_array = [bytearray([ROM_bytes[4*x + s], ROM_bytes[4*x + t], ROM_bytes[4*x + a], ROM_bytes[4*x + r]])
|
||||
for x in range(len(ROM_bytes) // 4)
|
||||
]
|
||||
|
||||
with open(outROM, 'wb') as tempROMfile:
|
||||
tempROMfile.write(b''.join(ROM_array))
|
||||
|
||||
return outROM
|
||||
|
||||
|
||||
def find_file_table(ROM):
|
||||
with open(ROM, 'rb') as ROMfile:
|
||||
|
||||
ROMfile.seek(0,0)
|
||||
|
||||
main_area = ROMfile.read(0x100000)
|
||||
main_area = ROMfile.read()
|
||||
|
||||
file_table_start = main_area.find(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x50\x00\x00\x00\x00')
|
||||
if file_table_start == -1:
|
||||
file_table_start = main_area.find(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x60\x00\x00\x00\x00')
|
||||
|
||||
if(file_table_start == -1):
|
||||
if file_table_start == -1:
|
||||
print('File table not found.')
|
||||
sys.exit(2)
|
||||
elif(file_table_start > 0x100000):
|
||||
elif file_table_start > 0x100000:
|
||||
print("Warning: Detected file table offset 0x%X is larger than expected." % file_table_start)
|
||||
# print(file_table_start)
|
||||
|
||||
return file_table_start
|
||||
|
||||
decomp_inds = [0, 1, 2, 3, 4, 5, 15, 16, 21, 22, 23, 24, 48]
|
||||
|
||||
def compress(baserom, comprom, mio0, dma_table=None, verbose=False):
|
||||
if dma_table:
|
||||
file_table = int(dma_table, 0)
|
||||
print("Using provided DMA table offset 0x%X" % file_table)
|
||||
if verbose:
|
||||
print("Using provided DMA table offset 0x%X" % file_table)
|
||||
else:
|
||||
file_table = find_file_table(baserom)
|
||||
print("DMA table found at 0x%X" % file_table)
|
||||
if verbose:
|
||||
print("DMA table found at 0x%X" % file_table)
|
||||
|
||||
print("Detected ROM version is " + file_table_dict.get(file_table, "Unknown"))
|
||||
|
||||
(version, file_names, decomp_inds) = get_version_info(baserom)
|
||||
ft_version = file_table_dict.get(version)
|
||||
|
||||
if version == "Unknown":
|
||||
print("Unknown version. Unable to determine compression scheme.")
|
||||
sys.exit(2)
|
||||
elif ft_version and ft_version != file_table:
|
||||
print("Warning: No record of DMA table at 0x%X for %s" % (file_table, version))
|
||||
elif verbose:
|
||||
print("Detected ROM version is " + version)
|
||||
|
||||
# comp_const = 0xFFFEFFFFFE1E7FC0
|
||||
|
||||
|
@ -137,7 +249,6 @@ def compress(baserom, comprom, mio0, dma_table=None, verbose=False):
|
|||
file_count = 0
|
||||
p_file_begin = 0
|
||||
|
||||
|
||||
while True:
|
||||
file_entry = file_table + 0x10 * file_count
|
||||
basefile.seek(file_entry + 4)
|
||||
|
@ -147,7 +258,7 @@ def compress(baserom, comprom, mio0, dma_table=None, verbose=False):
|
|||
|
||||
v_file_size = v_file_end - v_file_begin
|
||||
|
||||
if(v_file_begin == 0 and v_file_end == 0):
|
||||
if v_file_begin == 0 and v_file_end == 0:
|
||||
break
|
||||
|
||||
basefile.seek(v_file_begin)
|
||||
|
@ -155,10 +266,7 @@ def compress(baserom, comprom, mio0, dma_table=None, verbose=False):
|
|||
|
||||
file_bytes = basefile.read(v_file_size)
|
||||
|
||||
if file_table_dict.get(file_table).startswith("JP"):
|
||||
file_name = file_names_jp[file_count]
|
||||
else:
|
||||
file_name = file_names_us[file_count]
|
||||
file_name = file_names[file_count]
|
||||
|
||||
if (file_count in decomp_inds) or (file_name in file_names_critical):
|
||||
# if (1 << file_count) & comp_flags:
|
||||
|
@ -210,20 +318,26 @@ def compress(baserom, comprom, mio0, dma_table=None, verbose=False):
|
|||
return
|
||||
|
||||
def decompress(baserom, decomprom, mio0, extract_dest=None, dma_table=None, print_inds=False, verbose=False):
|
||||
baserom = fix_byte_swap(baserom, baserom + "zxqj")
|
||||
|
||||
if dma_table:
|
||||
file_table = int(dma_table, 0)
|
||||
print("Using provided DMA table offset 0x%X" % file_table)
|
||||
else:
|
||||
file_table = find_file_table(baserom)
|
||||
print("DMA table found at 0x%X" % file_table)
|
||||
|
||||
(version, file_names, decomp_inds) = get_version_info(baserom)
|
||||
ft_version = file_table_dict.get(version)
|
||||
|
||||
|
||||
if file_table_dict.get(file_table):
|
||||
version = file_table_dict.get(file_table)
|
||||
print("Detected ROM version is " + version)
|
||||
if version == "Unknown":
|
||||
print("Could not detect version")
|
||||
elif ft_version and ft_version != file_table:
|
||||
print("Warning: No record of DMA table at 0x%X for %s" % (file_table, version))
|
||||
else:
|
||||
print("Could not detect version.")
|
||||
version = "Unknown"
|
||||
|
||||
print("Detected ROM version is " + version)
|
||||
|
||||
with open(decomprom, 'w+b') as decompfile, open(baserom, 'rb') as basefile:
|
||||
file_count = 0
|
||||
decomp_file_inds = []
|
||||
|
@ -241,7 +355,7 @@ def decompress(baserom, decomprom, mio0, extract_dest=None, dma_table=None, prin
|
|||
|
||||
#print(v_file_begin, p_file_begin, p_file_end, comp_flag)
|
||||
|
||||
if(v_file_begin == 0 and p_file_end == 0):
|
||||
if v_file_begin == 0 and p_file_end == 0:
|
||||
break
|
||||
|
||||
decompfile.truncate(v_file_begin)
|
||||
|
@ -254,6 +368,7 @@ def decompress(baserom, decomprom, mio0, extract_dest=None, dma_table=None, prin
|
|||
v_file_size = p_file_size
|
||||
decomp_file_inds += [file_count]
|
||||
dec_msg = 'uncompressed'
|
||||
|
||||
elif comp_flag == 1:
|
||||
file_bytes = mio0_dec_bytes(file_bytes, mio0)
|
||||
dec_msg = 'compressed'
|
||||
|
@ -268,14 +383,15 @@ def decompress(baserom, decomprom, mio0, extract_dest=None, dma_table=None, prin
|
|||
|
||||
v_file_end = v_file_begin + v_file_size
|
||||
|
||||
if file_table_dict.get(file_table).startswith("JP"):
|
||||
file_name = file_names_jp[file_count]
|
||||
if decomp_inds:
|
||||
file_name = file_names[file_count]
|
||||
else:
|
||||
file_name = file_names_us[file_count]
|
||||
|
||||
if(verbose):
|
||||
file_name = file_names % (file_count, v_file_begin)
|
||||
|
||||
if verbose:
|
||||
print("name: " + file_name)
|
||||
print("start: 0x%X" % v_file_begin)
|
||||
# print("index", file_count, dec_msg, "; size: 0x%X" % v_file_size)
|
||||
|
||||
if extract_dest is not None:
|
||||
if not os.path.exists(extract_dest):
|
||||
|
@ -284,7 +400,7 @@ def decompress(baserom, decomprom, mio0, extract_dest=None, dma_table=None, prin
|
|||
if version == "Unknown":
|
||||
suffix = "%X" % file_table
|
||||
else:
|
||||
suffix = version.replace("1.", ".rev").lower()
|
||||
suffix = version.replace(" 1.", ".rev").lower()
|
||||
|
||||
out_file_name = file_name + "." + suffix + ".bin"
|
||||
with open(extract_dest + os.sep + out_file_name, 'wb') as extract_file:
|
||||
|
@ -312,21 +428,25 @@ def decompress(baserom, decomprom, mio0, extract_dest=None, dma_table=None, prin
|
|||
elif decomp_file_inds != decomp_inds:
|
||||
print("Warning: Unusual compression scheme. These files were uncompressed:")
|
||||
print(decomp_file_inds)
|
||||
|
||||
if baserom.endswith("zxqj"):
|
||||
run(["rm", baserom])
|
||||
|
||||
return
|
||||
|
||||
parser = argparse.ArgumentParser(description='Compress or decompress a Star Fox 64 ROM')
|
||||
parser.add_argument('inROM', help="ROM file to compress or decompress")
|
||||
parser.add_argument('outROM', help="output file for processed ROM.")
|
||||
parser.add_argument('-c', action='store_true',help='compress provided ROM')
|
||||
parser.add_argument('-d', action='store_true',help='decompress provided ROM')
|
||||
parser.add_argument('-e', metavar='extract',dest='extract',help='directory for extracted decompressed files. Use with -d')
|
||||
parser.add_argument('-r', action="store_true",help='Fix crc without compressing or decompressing')
|
||||
parser.add_argument('-m', metavar='mio0',dest='mio0',help='Path to mio0 tool if not in same directory')
|
||||
parser.add_argument('inROM', help="ROM file to process")
|
||||
parser.add_argument('outROM', help="Output file for processed ROM.")
|
||||
parser.add_argument('-c', action='store_true',help='Compress a big endian uncompressed Star Fox 64 ROM')
|
||||
parser.add_argument('-d', action='store_true',help='Decompress a Star Fox 64 ROM. Use with -s to also make a big endian compressed ROM.')
|
||||
parser.add_argument('-e', metavar='extract',dest='extract',help='Directory for extracted decompressed files. Use with -d')
|
||||
parser.add_argument('-r', action="store_true",help='Fix crc of Star Fox 64 ROM without compressing or decompressing')
|
||||
parser.add_argument('-s', action='store_true',help='Swap a Star Fox 64 ROM to big endian (.z64). Use . as second argument to swap in-place or .b to also make a backup')
|
||||
parser.add_argument('-m', metavar='mio0',dest='mio0',help='Path to mio0 tool if not named "mio0" and in same directory')
|
||||
parser.add_argument('-i', action='store_true',help='Print indices of uncompressed files during decompression.')
|
||||
parser.add_argument('-v', action='store_true',help='Print details about the ROM files.')
|
||||
parser.add_argument('-t', metavar='dma_table', dest='dma_table',help='Provide DMA table explicitly instead of autodetecting')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -340,7 +460,14 @@ if __name__ == '__main__':
|
|||
elif args.c:
|
||||
compress(args.inROM, args.outROM, mio0, dma_table=args.dma_table, verbose=args.v)
|
||||
elif args.d or args.extract:
|
||||
swap_backup = args.s
|
||||
decompress(args.inROM, args.outROM, mio0, extract_dest=args.extract, dma_table=args.dma_table, print_inds=args.i, verbose=args.v)
|
||||
elif args.s:
|
||||
if args.outROM[0] == ".":
|
||||
args.outRom = args.inRom
|
||||
if args.outROM == ".b":
|
||||
swap_backup = True
|
||||
fix_byte_swap(args.inROM, args.outROM)
|
||||
else:
|
||||
print("Something went wrong.")
|
||||
print("No action specified. Use -c, -d, -e, -r, or -s to specify an action")
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue