tilesets.get_tileset("MainDialog2", override_offset=0x80), tilesets.get_tileset("Special", override_offset=0xE0) ]) kanji = tilesets.get_tileset("Kanji", override_offset=0x0) with open("./game/src/version/ptrlist_data.asm", "w") as datafile: datafile.write( f'INCLUDE "build/ptrlists/ptrlist_data_constants_{{GAMEVERSION}}.asm"\n\n' ) for l in list_map: addr, spp, term, fix_len, print_hex, null_indicator, data_prefix = list_map[ l] if isinstance(addr, tuple): bank = addr[0] addr = utils.rom2realaddr(addr) else: bank = utils.real2romaddr(addr)[0] datafile.write( f'SECTION "Pointer List - {l}", ROMX[${utils.real2romaddr(addr)[1]:04x}], BANK[${bank:02x}]\n' ) datafile.write(f'PtrList{l}::\n') datafile.write(f' INCBIN c{l}\n\n') entries = OrderedDict() with open(f"./text/ptrlists/{l}.txt", "w", encoding="utf-8") as output: output.write(str(list_map[l][1:]) + "\n") count_written = False for version_suffix in roms: with open(
ptrs = [utils.read_short(rom) for i in range(0, count)] data = {} for ptr in ptrs: rom.seek(ptr) # Bank 0, no need to convert addresses # (Bank, Pointer, VRAM Offset, Name) if namefile: # We assume the table file not existing is fine nametable[ptr] = "{:04X}".format(ptr) namefile.write("{:04X}={}\n".format(ptr, nametable[ptr])) data[ptr] = (utils.read_byte(rom), utils.read_short(rom), utils.read_short(rom), nametable[ptr]) output.write('INCLUDE "game/src/common/macros.asm"\n\n') output.write('SECTION "Tileset Table", ROM0[${:04X}]\n'.format(tiletable)) output.write('TilesetTable::\n') for i in ptrs: output.write(" dw TilesetInfo{}\n".format(data[i][3])) output.write("TilesetTableEnd::\n"); with open("game/src/gfx/tileset_files.asm", "w") as outputf: for ptr in sorted(data): with open("game/tilesets/{}.malias".format(data[ptr][3]),"wb") as compressed, open("text/tilesets/{}.2bpp".format(data[ptr][3]),"wb") as uncompressed: f = tilesets.decompress_tileset(rom, utils.rom2realaddr((data[ptr][0],data[ptr][1]))) uncompressed.write(bytearray(f[0])) compressed.write(bytearray(f[1])) output.write('SECTION "TilesetInfo {0}", ROM0[${1:04X}]\n'.format(data[ptr][3], ptr)) output.write("TilesetInfo{}::\n".format(data[ptr][3])) output.write(' dbww BANK(Tileset{0}), Tileset{0}, ${1:04X}\n'.format(data[ptr][3], data[ptr][2])) outputf.write('SECTION "Tileset Data {0}", ROMX[${1:04X}], BANK[${2:02X}]\n'.format(data[ptr][3], data[ptr][1], data[ptr][0])) outputf.write("Tileset{}::\n".format(data[ptr][3])) outputf.write("TilesetStart{}::\n".format(data[ptr][3])) outputf.write(' INCBIN "build/tilesets/{}.malias"\n'.format(data[ptr][3])) outputf.write("TilesetEnd{}::\n\n".format(data[ptr][3])) output.write("TilesetInfoEnd::\n");
sys.path.append(os.path.join(os.path.dirname(__file__), 'common')) from common import utils, tilesets rom_info = ("baserom_parts_collection.gb", "parts_collection", (0x16, 0x539b), 63) # [ROM File, Version Suffix, Credits Ptr, Entry Count] table = utils.merge_dicts([ tilesets.get_tileset("MainSpecial"), tilesets.get_tileset("MainDialog"), tilesets.get_tileset("dakuten", override_offset=0x0) ]) filename = rom_info[0] suffix = rom_info[1] text_ptr_table = rom_info[2] if isinstance( rom_info[2], int) else utils.rom2realaddr(rom_info[2]) bank = utils.real2romaddr(text_ptr_table)[0] entry_count = rom_info[3] with open(filename, 'rb') as rom, open(f"./text/credits/Credits.csv", "w", encoding="utf-8") as fp: writer = csv.writer(fp, lineterminator='\n', delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) writer.writerow(["Pointer", "VRAMOffset", "Original", "Translated"]) rom.seek(text_ptr_table) pointers = [(rom.tell(), utils.read_short(rom))
default_version = roms[0][1] nametable = OrderedDict() namefile = None realptr_key_map = {} # Map real address to a defined name if os.path.exists("scripts/res/meta_tileset_names.tbl"): nametable = utils.read_table("scripts/res/meta_tileset_names.tbl") else: namefile = open("scripts/res/meta_tileset_names.tbl", "w") # M3 keeps source addresses in a table in bank 0 and banks + destination info in a separate table # This is pretty much a single table that's been split over 2 banks, and there may be duplicate tileset entries count = ((terminator - tiletable) // 2) terminator_key = utils.rom2realaddr((0, terminator)) nametable[terminator] = "TilesetSourceAddressTableEnd" tileset_data = OrderedDict() tileset_metadata = {} common_tilesets = [] unique_pointer_and_tilesets = [] unique_tilesets = [] unique_pointers = [] missing_pointers = [] tileset_alias = {} for version in roms: ver = version[1] realptr_key_map[ver] = OrderedDict() realptr_key_map[ver][terminator_key] = terminator
table[0xce] = SpecialCharacter('S') # Text Speed table[0xcf] = SpecialCharacter("CF", bts=0, always_print=True) # Create new text box table[0xd0] = SpecialCharacter("&", bts=2, names=name_table) # Pull text from RAM table[0xd1] = SpecialCharacter("D1", bts=0, always_print=True) # New page (keeps portrait) # Portrait, [Orientation:{00, 01, 10, 11, FF}][Character:1][Expression:1] table[0xd2] = SpecialCharacter('@', bts=3, parser=lambda x: "{},{:02X},{:02X}".format({0x00: 'LL', 0x01: 'LR', 0x10: 'RL', 0x11: 'RR', 0xFF: 'CC' }[utils.read_byte(x)], utils.read_byte(x), utils.read_byte(x)) ) table[0xd3] = SpecialCharacter('K', print_control_code=False, parser=lambda x: kanji[utils.read_byte(x)]) # Kanji terminator_pointers = [] for i, entry in enumerate([t for t in text_ptrs if t[0] != 0]): csv_filename = f"./text/dialog/TextSection{i:02}.csv" text_version_specific[csv_filename] = {} text_shifted_pointers[csv_filename] = {} text_unused[csv_filename] = {} realaddr = utils.rom2realaddr(entry) rom.seek(realaddr) end = utils.rom2realaddr((entry[0], utils.read_short(rom))) rom.seek(end - 2) terminator_pointers.append(utils.rom2realaddr((entry[0], utils.read_short(rom)))) rom.seek(realaddr + 2) pointers = OrderedDict() pointer_lengths = OrderedDict() pointer_lengths_key = realaddr pointer_lengths[pointer_lengths_key] = end pointers[realaddr] = end # Treat pointers[0] as the end of the list of addresses reverse_map = {} realaddr = rom.tell() last_ptr = 0 if realaddr >= end: # An empty bank, so clear pointers to avoid any parsing but still create the csv file pointer_lengths.clear()
# Medarotters have a 3-byte prefix before the names 'Medarotters': ((0x17, 0x63e2), 0x50, 80, 3), }) tileset = utils.merge_dicts([ tilesets.get_tileset("MainSpecial"), tilesets.get_tileset("MainDialog"), tilesets.get_tileset("dakuten", override_offset=0x0) ]) with open("baserom_parts_collection.gb", "rb") as rom: for l in list_map: addr, term, n, prefixlen = list_map[l] if isinstance(addr, tuple): bank = addr[0] addr = utils.rom2realaddr(addr) else: bank = utils.real2romaddr(addr)[0] rom.seek(addr) with open('text/ptrlists/{}.txt'.format(l), 'w', encoding="utf-8") as output: output.write(f"({term},{prefixlen})\n") ptrs = [utils.read_short(rom) for i in range(0, n)] for ptr in ptrs: rom.seek(utils.rom2realaddr((bank, ptr))) prefix = [utils.read_byte(rom) for i in range(0, prefixlen)] # If the first 2 are 00, there's no data if prefixlen > 0 and prefix[0] == 0x0: b = [] else: b = list(iter(partial(utils.read_byte, rom), term))
if i == terminator: output.write(f" dw TilesetInfoEnd ; {x:02X}\n") else: output.write(f" dw TilesetInfo{data[i][3]} ; {x:02X}\n") offsetfile.write(f"{x:02X}={data[i][2]:04X}\n") indexfile.write(f"{x:02X}={data[i][3]}\n") output.write("TilesetTableEnd::\n") with open("game/src/gfx/tileset_files.asm", "w") as outputf: for ptr in sorted(data): output.write('SECTION "TilesetInfo {0}", ROM0[${1:04X}]\n'.format( data[ptr][3], ptr)) output.write("TilesetInfo{}::\n".format(data[ptr][3])) with open("game/tilesets/{}.malias".format(data[ptr][3]),"wb") as compressed, \ open("text/tilesets/{}.png".format(data[ptr][3]),"wb") as uncompressed: f = tilesets.decompress_tileset( rom, utils.rom2realaddr((data[ptr][0], data[ptr][1]))) width, height, palette, greyscale, bitdepth, px_map = gfx.convert_2bpp_to_png( f[0]) w = png.Writer(width, height, palette=palette, compression=9, greyscale=greyscale, bitdepth=bitdepth) w.write(uncompressed, px_map) compressed.write(bytearray(f[1])) output.write( ' dbww BANK(Tileset{0}), Tileset{0}, ${1:04X}\n'.format( data[ptr][3], data[ptr][2])) outputf.write(
0xFF: 'CC' }[utils.read_byte(x)], utils.read_byte(x), utils.read_byte(x))) table[0xd3] = SpecialCharacter( 'K', print_control_code=False, parser=lambda x: kanji[utils.read_byte(x)]) # Kanji terminator_pointers = [] for i, entry in enumerate([t for t in text_ptrs if t[0] != 0]): csv_filename = os.path.join(text_src_path, f"TextSection{i:02}.csv") text_version_specific[csv_filename] = {} text_shifted_pointers[csv_filename] = {} text_unused[csv_filename] = {} realaddr = utils.rom2realaddr(entry) rom.seek(realaddr) end = utils.rom2realaddr((entry[0], utils.read_short(rom))) rom.seek(end - 2) terminator_pointers.append( utils.rom2realaddr((entry[0], utils.read_short(rom)))) rom.seek(realaddr + 2) pointers = OrderedDict() pointer_lengths = OrderedDict() pointer_lengths_key = realaddr pointer_lengths[pointer_lengths_key] = end pointers[ realaddr] = end # Treat pointers[0] as the end of the list of addresses reverse_map = {} realaddr = rom.tell() last_ptr = 0
VERSIONS = [("baserom_kabuto.gb", "kabuto"), ("baserom_kuwagata.gb", "kuwagata")] tileset = utils.merge_dicts([ utils.read_table("scripts/res/tileset_MainDialog.tbl"), utils.read_table("scripts/res/tileset_MainSpecial.tbl"), utils.read_table("scripts/res/tileset_BoldLetters.tbl"), utils.read_table("scripts/res/dakuten.tbl") ]) for lst in list_map: merged_dict = {} addr, length, term, pad, n = list_map[lst] for version in VERSIONS: with open(version[0], "rb") as rom: if isinstance(addr, tuple): addr = utils.rom2realaddr(addr) rom.seek(addr) for i in range(0, n): c = 0 for l in length if isinstance(length, tuple) else (length, ): b = list(iter(partial(utils.read_byte, rom), term)) rom.seek(l - len(b) - 1, 1) # Account for the terminator value = "{}\n".format("".join( utils.bin2txt(bytearray(b), tileset))) key = "{:03}_{:02}".format(i, c) c += 1 if key in merged_dict and merged_dict[ key] != value: # Just assume 2 versions, so the existence of a previous entry must belong to the 'default' version merged_dict[key + VERSIONS[0][1]] = "[{}]{}".format( VERSIONS[0][1], merged_dict[key]) del merged_dict[key]
#!/bin/python # Script to initially dump tilemaps, shouldn't really need to be run anymore, and is mainly here as a reference import os, sys from functools import partial sys.path.append(os.path.join(os.path.dirname(__file__), 'common')) from common import utils, tilemaps, tilesets # tilemap bank is 1e (0x78000) BANK_SIZE = 0x4000 BANK = 0x1e BASE = 0x4000 BASE_ADDR = utils.rom2realaddr((BANK, BASE)) MAX_ADDR = BASE_ADDR + BANK_SIZE - 1 tilemap_ptr = {} tilemap_bytes = {} tilemap_files = [] TERMINATOR = 0 with open("baserom_parts_collection.gb", "rb") as rom: rom.seek(BASE_ADDR) ptr = utils.read_short(rom) tilemap_ptr[0] = ptr end = utils.rom2realaddr((BANK, ptr)) i = 1 while rom.tell() < end: # The first tilemap is where the table should end tilemap_ptr[i] = utils.read_short(rom) i += 1
duplicate_map = {} # [version] -> [identifier] -> identity version_specific_ids = set() basename_map = OrderedDict() version_suffixes = [i[1] for i in roms] for versionidx, version in enumerate(roms): rom_filename = version[0] ver = version[1] tilemap_data[versionidx] = OrderedDict() duplicate_map[versionidx] = {} with open(rom_filename, "rb") as rom: pointers = None for tableidx, tableptr in enumerate(tilemap_tables): current_bank = tableptr[0] rom.seek(utils.rom2realaddr(tableptr)) pointers = [utils.read_short(rom)] # Assume the end of the table is the first data pointer table_end = utils.rom2realaddr((current_bank, pointers[0])) while rom.tell() < table_end: pointers.append(utils.read_short(rom)) # Empty spaces in the table are the terminator, but we need to verify terminator = pointers[-1] # A hack to guess if something is a terminator or not is_terminator = pointers[-2] == terminator duplicate_pointers = [] for pointeridx, pointer in enumerate(pointers): realaddr = utils.rom2realaddr((current_bank, pointer))