From b688a9fd7a2eecb191c03374d268abf68ade37c8 Mon Sep 17 00:00:00 2001 From: VariantXYZ Date: Fri, 21 Jun 2024 20:28:32 -0700 Subject: [PATCH] Dump and rebuild lists, closes #5 --- Makefile | 33 +- game/src/text/buffer_text.asm | 31 +- game/src/version/kabuto/buffer_text.asm | 33 -- game/src/version/kabuto/ptrlist_data.asm | 3 + game/src/version/kuwagata/buffer_text.asm | 33 -- game/src/version/kuwagata/ptrlist_data.asm | 3 + game/src/version/ptrlist_data.asm | 106 ++++++ scripts/dump_ptrlists.py | 229 +++++++++++++ scripts/ptrlist2bin.py | 169 ++++++++++ scripts/ptrlistbin2asm.py | 74 +++++ text/ptrlists/Attacks.txt | 256 +++++++++++++++ text/ptrlists/Attributes.txt | 14 + text/ptrlists/CharacterNames.txt | 261 +++++++++++++++ text/ptrlists/Items.txt | 53 +++ text/ptrlists/Medaforce.txt | 113 +++++++ text/ptrlists/Medals.txt | 41 +++ text/ptrlists/Medarots.txt | 261 +++++++++++++++ text/ptrlists/Medarotters.txt | 23 ++ text/ptrlists/Movement.txt | 12 + text/ptrlists/MusicTitles.txt | 80 +++++ text/ptrlists/PartsHead.txt | 256 +++++++++++++++ text/ptrlists/PartsLArm.txt | 256 +++++++++++++++ text/ptrlists/PartsLegs.txt | 256 +++++++++++++++ text/ptrlists/PartsRArm.txt | 256 +++++++++++++++ text/ptrlists/Personalities.txt | 12 + text/ptrlists/Skills.txt | 15 + text/ptrlists/Terrain.txt | 145 +++++++++ text/ptrlists/Unknown00.txt | 357 +++++++++++++++++++++ text/ptrlists/Unknown08.txt | 41 +++ text/ptrlists/Unknown0C.txt | 35 ++ text/ptrlists/Unknown0E.txt | 96 ++++++ text/ptrlists/Unknown10.txt | 98 ++++++ text/ptrlists/Unknown12.txt | 171 ++++++++++ text/ptrlists/Unknown16.txt | 296 +++++++++++++++++ text/ptrlists/Unknown17.txt | 320 ++++++++++++++++++ text/ptrlists/Unknown18.txt | 41 +++ 36 files changed, 4409 insertions(+), 70 deletions(-) delete mode 100644 game/src/version/kabuto/buffer_text.asm create mode 100644 game/src/version/kabuto/ptrlist_data.asm delete mode 100644 game/src/version/kuwagata/buffer_text.asm create mode 100644 game/src/version/kuwagata/ptrlist_data.asm create mode 100644 game/src/version/ptrlist_data.asm create mode 100644 scripts/dump_ptrlists.py create mode 100644 scripts/ptrlist2bin.py create mode 100644 scripts/ptrlistbin2asm.py create mode 100644 text/ptrlists/Attacks.txt create mode 100644 text/ptrlists/Attributes.txt create mode 100644 text/ptrlists/CharacterNames.txt create mode 100644 text/ptrlists/Items.txt create mode 100644 text/ptrlists/Medaforce.txt create mode 100644 text/ptrlists/Medals.txt create mode 100644 text/ptrlists/Medarots.txt create mode 100644 text/ptrlists/Medarotters.txt create mode 100644 text/ptrlists/Movement.txt create mode 100644 text/ptrlists/MusicTitles.txt create mode 100644 text/ptrlists/PartsHead.txt create mode 100644 text/ptrlists/PartsLArm.txt create mode 100644 text/ptrlists/PartsLegs.txt create mode 100644 text/ptrlists/PartsRArm.txt create mode 100644 text/ptrlists/Personalities.txt create mode 100644 text/ptrlists/Skills.txt create mode 100644 text/ptrlists/Terrain.txt create mode 100644 text/ptrlists/Unknown00.txt create mode 100644 text/ptrlists/Unknown08.txt create mode 100644 text/ptrlists/Unknown0C.txt create mode 100644 text/ptrlists/Unknown0E.txt create mode 100644 text/ptrlists/Unknown10.txt create mode 100644 text/ptrlists/Unknown12.txt create mode 100644 text/ptrlists/Unknown16.txt create mode 100644 text/ptrlists/Unknown17.txt create mode 100644 text/ptrlists/Unknown18.txt diff --git a/Makefile b/Makefile index d8e2e41..e771788 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ TILEMAP_GFX := $(GFX)/tilemaps TILEMAP_PREBUILT := $(GFX)/prebuilt/tilemaps ATTRIBMAP_GFX := $(GFX)/attribmaps ATTRIBMAP_PREBUILT := $(GFX)/prebuilt/attribmaps +PTRLISTS_TEXT := $(TEXT)/ptrlists # Build Directories VERSION_OUT := $(BUILD)/version @@ -57,6 +58,8 @@ DIALOG_OUT := $(BUILD)/dialog TILESET_OUT := $(BUILD)/tilesets TILEMAP_OUT := $(BUILD)/tilemaps ATTRIBMAP_OUT := $(BUILD)/attribmaps +PTRLISTS_INT := $(BUILD)/intermediate/ptrlists +PTRLISTS_OUT := $(BUILD)/ptrlists # Source Modules (directories in SRC), version directories (kuwagata/kabuto) are implied # We explicitly separate this with newlines to avoid silly conflicts with tr_EN @@ -109,6 +112,7 @@ TILEMAPS_VERSIONED := $(call FILTER,_,$(TILEMAPS)) ATTRIBMAPS := $(notdir $(basename $(wildcard $(ATTRIBMAP_GFX)/*.$(TEXT_TYPE)))) ATTRIBMAPS_COMMON := $(call FILTER_OUT,_,$(ATTRIBMAPS)) ATTRIBMAPS_VERSIONED := $(call FILTER,_,$(ATTRIBMAPS)) +PTRLISTS := $(notdir $(basename $(wildcard $(PTRLISTS_TEXT)/*.$(TEXT_TYPE)))) # Intermediates for common sources (not in version folder) ## We explicitly rely on second expansion to handle version-specific files in the version specific objects @@ -129,6 +133,7 @@ version_text_tables_ADDITIONAL := $(DIALOG_OUT)/text_table_constants_PLACEHOLDER version_tileset_table_ADDITIONAL := $(COMPRESSED_TILESET_FILES_COMMON) $(VERSION_SRC)/tileset_table.asm $(COMPRESSED_TILESET_FILES_GAMEVERSION) $(TILESET_OUT)/PLACEHOLDER_VERSION.stamp version_tilemap_table_ADDITIONAL := $(TILEMAP_FILES_COMMON) $(VERSION_SRC)/tilemap_table.asm $(TILEMAP_OUT)/PLACEHOLDER_VERSION.stamp version_attribmap_table_ADDITIONAL := $(ATTRIBMAP_FILES_COMMON) $(VERSION_SRC)/attribmap_table.asm $(ATTRIBMAP_OUT)/PLACEHOLDER_VERSION.stamp +version_ptrlist_data_ADDITIONAL := $(PTRLISTS_OUT)/ptrlist_data_constants_PLACEHOLDER_VERSION.asm .PHONY: $(VERSIONS) all clean default default: kabuto @@ -166,6 +171,11 @@ $(BUILD)/%.$(INT_TYPE): $(SRC)/$$(firstword $$(subst ., ,$$*))/$$(lastword $$(su $(DIALOG_INT)/%.$(DIALOG_TYPE): $(DIALOG_TEXT)/$$(word 1, $$(subst _, ,$$*)).$(CSV_TYPE) $(SCRIPT_RES)/ptrs.tbl | $(DIALOG_INT) $(PYTHON) $(SCRIPT)/dialog2bin.py $@ $^ "Original" $(subst $(subst .$(CSV_TYPE),,$( "$@" }' $< @@ -190,6 +200,10 @@ $(TILESET_OUT)/%.stamp: $$(call FILTER,%,$(COMPRESSED_TILESET_FILES_VERSIONED)) $(DIALOG_OUT)/text_table_constants_%.asm: $(SRC)/version/text_tables.asm $(SRC)/version/%/text_tables.asm $$(foreach FILE,$(DIALOG),$(DIALOG_INT)/$$(FILE)_$$*.$(DIALOG_TYPE)) | $(DIALOG_OUT) $(PYTHON) $(SCRIPT)/dialogbin2asm.py $@ $(DIALOG_OUT) $* $^ +.SECONDEXPANSION: +$(PTRLISTS_OUT)/ptrlist_data_constants_%.asm: $(SRC)/version/ptrlist_data.asm $(SRC)/version/%/ptrlist_data.asm $$(foreach FILE,$(PTRLISTS),$(PTRLISTS_INT)/$$(FILE)_$$*.$(PTRLIST_TYPE)) | $(PTRLISTS_OUT) + $(PYTHON) $(SCRIPT)/ptrlistbin2asm.py $@ $(PTRLISTS_OUT) $* $^ + # build/tilemaps/*.map from tilemaps txt $(TILEMAP_OUT)/%.$(TMAP_TYPE): $(TILEMAP_GFX)/%.$(TEXT_TYPE) | $(TILEMAP_OUT) $(PYTHON) $(SCRIPT)/txt2map.py $@ $^ $(TILEMAP_PREBUILT) @@ -209,8 +223,8 @@ $(ATTRIBMAP_OUT)/%.stamp: $$(call FILTER,%,$(ATTRIBMAP_FILES_VERSIONED)) touch $@ # Dump scripts -.PHONY: dump dump_text dump_tilesets dump_tilemaps dump_attribmaps -dump: dump_text dump_tilesets dump_tilemaps dump_attribmaps +.PHONY: dump dump_text dump_tilesets dump_tilemaps dump_attribmaps dump_ptrlists +dump: dump_text dump_tilesets dump_tilemaps dump_attribmaps dump_ptrlists dump_text: | $(DIALOG_TEXT) $(SCRIPT_RES) rm $(DIALOG_TEXT)/*.$(CSV_TYPE) || echo "" @@ -231,6 +245,10 @@ dump_attribmaps: | $(ATTRIBMAP_GFX) $(ATTRIBMAP_PREBUILT) $(SCRIPT_RES) rm $(ATTRIBMAP_GFX)/*.$(TEXT_TYPE) || echo "" $(PYTHON) $(SCRIPT)/dump_maps.py attribmap "$(ATTRIBMAP_GFX)" "$(ATTRIBMAP_PREBUILT)" "$(ATTRIBMAP_OUT)" "$(SCRIPT_RES)" "$(VERSION_SRC)" a02 a0b 9 +dump_ptrlists: | $(PTRLISTS_TEXT) + rm $(PTRLISTS_TEXT)/*.$(TEXT_TYPE) || echo "" + $(PYTHON) $(SCRIPT)/dump_ptrlists.py "$(VERSION_SRC)" "$(PTRLISTS_TEXT)" "$(PTRLISTS_OUT)" + # Tests .PHONY: test_tilemaps test_attribmaps @@ -284,4 +302,13 @@ $(ATTRIBMAP_GFX): mkdir -p $(ATTRIBMAP_GFX) $(ATTRIBMAP_OUT): - mkdir -p $(ATTRIBMAP_OUT) \ No newline at end of file + mkdir -p $(ATTRIBMAP_OUT) + +$(PTRLISTS_TEXT): + mkdir -p $(PTRLISTS_TEXT) + +$(PTRLISTS_INT): + mkdir -p $(PTRLISTS_INT) + +$(PTRLISTS_OUT): + mkdir -p $(PTRLISTS_OUT) \ No newline at end of file diff --git a/game/src/text/buffer_text.asm b/game/src/text/buffer_text.asm index 8489965..c80cd74 100644 --- a/game/src/text/buffer_text.asm +++ b/game/src/text/buffer_text.asm @@ -40,4 +40,33 @@ BufferTextFromList:: jr nz, .copyLoop ret -; Look in version folders for ListPointerTable +SECTION "List Pointer Table", ROM0[$29C6] +ListPointerTable:: + dbw BANK(PtrListUnknown00), PtrListUnknown00 + dbw BANK(PtrListPartsHead), PtrListPartsHead + dbw BANK(PtrListPartsRArm), PtrListPartsRArm + dbw BANK(PtrListPartsLArm), PtrListPartsLArm + dbw BANK(PtrListPartsLegs), PtrListPartsLegs + dbw BANK(PtrListAttributes), PtrListAttributes + dbw BANK(PtrListSkills), PtrListSkills + dbw BANK(PtrListMovement), PtrListMovement + dbw BANK(PtrListUnknown08),PtrListUnknown08 + dbw BANK(PtrListPersonalities), PtrListPersonalities + dbw BANK(PtrListMedaforce), PtrListMedaforce + dbw BANK(PtrListMedals), PtrListMedals + dbw BANK(PtrListUnknown0C), PtrListUnknown0C + dbw BANK(PtrListItems), PtrListItems + dbw BANK(PtrListUnknown0E), PtrListUnknown0E + dbw $00, $0000 + dbw $00, $0000 + dbw BANK(PtrListMedarotters), PtrListMedarotters + dbw BANK(PtrListUnknown10), PtrListUnknown10 + dbw BANK(PtrListTerrain), PtrListTerrain + dbw BANK(PtrListUnknown12), PtrListUnknown12 + dbw BANK(PtrListAttacks), PtrListAttacks + dbw BANK(PtrListCharacterNames), PtrListCharacterNames + dbw BANK(PtrListMedarots), PtrListMedarots + dbw BANK(PtrListUnknown16), PtrListUnknown16 + dbw BANK(PtrListUnknown17), PtrListUnknown17 + dbw BANK(PtrListUnknown18), PtrListUnknown18 + dbw BANK(PtrListMusicTitles), PtrListMusicTitles \ No newline at end of file diff --git a/game/src/version/kabuto/buffer_text.asm b/game/src/version/kabuto/buffer_text.asm deleted file mode 100644 index f8358c1..0000000 --- a/game/src/version/kabuto/buffer_text.asm +++ /dev/null @@ -1,33 +0,0 @@ -INCLUDE "game/src/common/constants.asm" -INCLUDE "game/src/common/macros.asm" - -SECTION "List Pointer Table", ROM0[$29C6] -ListPointerTable:: - dbw $7F, $5379 - dbw $49, $51B5 - dbw $4E, $46C0 - dbw $4E, $597C - dbw $49, $6471 - dbw $27, $7EC0 - dbw $27, $7F4C - dbw $27, $7F9A - dbw $26, $5EDD - dbw $26, $6141 - dbw $26, $5800 - dbw $49, $4B89 - dbw $26, $6186 - dbw $49, $4939 - dbw $29, $4000 - dbw $00, $0000 - dbw $00, $0000 - dbw $49, $4DD7 - dbw $49, $4E56 - dbw $2A, $4000 - dbw $2A, $4364 - dbw $49, $4000 - dbw $29, $44D0 - dbw $2B, $4630 - dbw $29, $4F42 - dbw $2B, $5290 - dbw $49, $4C78 - dbw $03, $737E diff --git a/game/src/version/kabuto/ptrlist_data.asm b/game/src/version/kabuto/ptrlist_data.asm new file mode 100644 index 0000000..97f134c --- /dev/null +++ b/game/src/version/kabuto/ptrlist_data.asm @@ -0,0 +1,3 @@ +DEF cAddressMusicTitles EQU $737e +DEF cBankMusicTitles EQU $03 +INCLUDE "./game/src/version/ptrlist_data.asm" diff --git a/game/src/version/kuwagata/buffer_text.asm b/game/src/version/kuwagata/buffer_text.asm deleted file mode 100644 index bd7cc2a..0000000 --- a/game/src/version/kuwagata/buffer_text.asm +++ /dev/null @@ -1,33 +0,0 @@ -INCLUDE "game/src/common/constants.asm" -INCLUDE "game/src/common/macros.asm" - -SECTION "List Pointer Table", ROM0[$29C6] -ListPointerTable:: - dbw $7F, $5379 - dbw $49, $51B5 - dbw $4E, $46C0 - dbw $4E, $597C - dbw $49, $6471 - dbw $27, $7EC0 - dbw $27, $7F4C - dbw $27, $7F9A - dbw $26, $5EDD - dbw $26, $6141 - dbw $26, $5800 - dbw $49, $4B89 - dbw $26, $6186 - dbw $49, $4939 - dbw $29, $4000 - dbw $00, $0000 - dbw $00, $0000 - dbw $49, $4DD7 - dbw $49, $4E56 - dbw $2A, $4000 - dbw $2A, $4364 - dbw $49, $4000 - dbw $29, $44D0 - dbw $2B, $4630 - dbw $29, $4F42 - dbw $2B, $5290 - dbw $49, $4C78 - dbw $03, $7383 diff --git a/game/src/version/kuwagata/ptrlist_data.asm b/game/src/version/kuwagata/ptrlist_data.asm new file mode 100644 index 0000000..c9b06e9 --- /dev/null +++ b/game/src/version/kuwagata/ptrlist_data.asm @@ -0,0 +1,3 @@ +DEF cAddressMusicTitles EQU $7383 +DEF cBankMusicTitles EQU $03 +INCLUDE "./game/src/version/ptrlist_data.asm" diff --git a/game/src/version/ptrlist_data.asm b/game/src/version/ptrlist_data.asm new file mode 100644 index 0000000..112d87b --- /dev/null +++ b/game/src/version/ptrlist_data.asm @@ -0,0 +1,106 @@ +INCLUDE "./build/ptrlists/ptrlist_data_constants_{GAMEVERSION}.asm" + +SECTION "Pointer List - Unknown00", ROMX[$5379], BANK[$7f] +PtrListUnknown00:: + INCBIN cUnknown00 + +SECTION "Pointer List - PartsHead", ROMX[$51b5], BANK[$49] +PtrListPartsHead:: + INCBIN cPartsHead + +SECTION "Pointer List - PartsRArm", ROMX[$46c0], BANK[$4e] +PtrListPartsRArm:: + INCBIN cPartsRArm + +SECTION "Pointer List - PartsLArm", ROMX[$597c], BANK[$4e] +PtrListPartsLArm:: + INCBIN cPartsLArm + +SECTION "Pointer List - PartsLegs", ROMX[$6471], BANK[$49] +PtrListPartsLegs:: + INCBIN cPartsLegs + +SECTION "Pointer List - Attributes", ROMX[$7ec0], BANK[$27] +PtrListAttributes:: + INCBIN cAttributes + +SECTION "Pointer List - Skills", ROMX[$7f4c], BANK[$27] +PtrListSkills:: + INCBIN cSkills + +SECTION "Pointer List - Movement", ROMX[$7f9a], BANK[$27] +PtrListMovement:: + INCBIN cMovement + +SECTION "Pointer List - Unknown08", ROMX[$5edd], BANK[$26] +PtrListUnknown08:: + INCBIN cUnknown08 + +SECTION "Pointer List - Personalities", ROMX[$6141], BANK[$26] +PtrListPersonalities:: + INCBIN cPersonalities + +SECTION "Pointer List - Medaforce", ROMX[$5800], BANK[$26] +PtrListMedaforce:: + INCBIN cMedaforce + +SECTION "Pointer List - Medals", ROMX[$4b89], BANK[$49] +PtrListMedals:: + INCBIN cMedals + +SECTION "Pointer List - Unknown0C", ROMX[$6186], BANK[$26] +PtrListUnknown0C:: + INCBIN cUnknown0C + +SECTION "Pointer List - Items", ROMX[$4939], BANK[$49] +PtrListItems:: + INCBIN cItems + +SECTION "Pointer List - Unknown0E", ROMX[$4000], BANK[$29] +PtrListUnknown0E:: + INCBIN cUnknown0E + +SECTION "Pointer List - Medarotters", ROMX[$4dd7], BANK[$49] +PtrListMedarotters:: + INCBIN cMedarotters + +SECTION "Pointer List - Unknown10", ROMX[$4e56], BANK[$49] +PtrListUnknown10:: + INCBIN cUnknown10 + +SECTION "Pointer List - Terrain", ROMX[$4000], BANK[$2a] +PtrListTerrain:: + INCBIN cTerrain + +SECTION "Pointer List - Unknown12", ROMX[$4364], BANK[$2a] +PtrListUnknown12:: + INCBIN cUnknown12 + +SECTION "Pointer List - Attacks", ROMX[$4000], BANK[$49] +PtrListAttacks:: + INCBIN cAttacks + +SECTION "Pointer List - CharacterNames", ROMX[$44d0], BANK[$29] +PtrListCharacterNames:: + INCBIN cCharacterNames + +SECTION "Pointer List - Medarots", ROMX[$4630], BANK[$2b] +PtrListMedarots:: + INCBIN cMedarots + +SECTION "Pointer List - Unknown16", ROMX[$4f42], BANK[$29] +PtrListUnknown16:: + INCBIN cUnknown16 + +SECTION "Pointer List - Unknown17", ROMX[$5290], BANK[$2b] +PtrListUnknown17:: + INCBIN cUnknown17 + +SECTION "Pointer List - Unknown18", ROMX[$4c78], BANK[$49] +PtrListUnknown18:: + INCBIN cUnknown18 + +SECTION "Pointer List - MusicTitles", ROMX[cAddressMusicTitles], BANK[cBankMusicTitles] +PtrListMusicTitles:: + INCBIN cMusicTitles + diff --git a/scripts/dump_ptrlists.py b/scripts/dump_ptrlists.py new file mode 100644 index 0000000..9adc223 --- /dev/null +++ b/scripts/dump_ptrlists.py @@ -0,0 +1,229 @@ +#!/bin/python + +# Script to dump text lists with pointers +# We make an assumption that objects will be adjacent to each other + +import csv +import os, sys +from collections import OrderedDict +from functools import partial +from itertools import zip_longest +sys.path.append(os.path.join(os.path.dirname(__file__), 'common')) +from common import utils, tilesets + +version_src_path = sys.argv[1] +text_src_path = sys.argv[2] +text_build_path = sys.argv[3] + +roms = ({ + "kabuto" : "baserom_kabuto.gbc", + "kuwagata" : "baserom_kuwagata.gbc", +}) + +default_version = "kabuto" + +default_tileset = utils.merge_dicts([ + tilesets.get_tileset("MainDialog1", override_offset=0x0), + tilesets.get_tileset("MainDialog2", override_offset=0x80), + tilesets.get_tileset("Special", override_offset=0xE0) + ]) + +kanji = tilesets.get_tileset("Kanji", override_offset=0x0) + + +list_map = ({ + # 'Type' : (Start of Pointers, Strings per pointer, Label(s), Terminator(s), (fixed length, fixed padding), print hex, 'null' indicator, data prefix, in general pointer list, special tileset) + 'Unknown00' : ([(0x7F, 0x5379)], 1, [], [None], [(29, 0x00)], [True], None, None, True, None), + 'PartsHead' : ([(0x49, 0x51b5)], 3, ["Model", "Name", "IsFemale"], [0xCB, 0xCB, None], [(7, 0x00), (9, 0x00), (1, 0x00)], [False, False, True], None, None, True, None), + 'PartsRArm' : ([(0x4E, 0x46c0)], 3, ["Model", "Name", "IsFemale"], [0xCB, 0xCB, None], [(7, 0x00), (9, 0x00), (1, 0x00)], [False, False, True], None, None, True, None), + 'PartsLArm' : ([(0x4E, 0x597c)], 3, ["Model", "Name", "IsFemale"], [0xCB, 0xCB, None], [(7, 0x00), (9, 0x00), (1, 0x00)], [False, False, True], None, None, True, None), + 'PartsLegs' : ([(0x49, 0x6471)], 3, ["Model", "Name", "IsFemale"], [0xCB, 0xCB, None], [(7, 0x00), (9, 0x00), (1, 0x00)], [False, False, True], None, None, True, None), + 'Attributes' : ([(0x27, 0x7ec0)], 1, ["AttributeName"], [0xCB], [(None, None)], [False], None, None, True, None), + 'Skills' : ([(0x27, 0x7f4c)], 1, ["SkillName"], [0xCB], [(None, None)], [False], None, None, True, None), + 'Movement' : ([(0x27, 0x7f9a)], 1, ["Movement"], [0xCB], [(None, None)], [False], None, None, True, None), + 'Unknown08' : ([(0x26, 0x5edd)], 1, [], [None], [(15, 0x00)], [True], None, None, True, None), + 'Personalities' : ([(0x26, 0x6141)], 1, ["Personality"], [0xCB], [(None, None)], [False], None, None, True, None), + 'Medaforce' : ([(0x26, 0x5800)], 2, ["Unknown", "Medaforce"], [None, 0xCB], [(6, 0x00), (None, None)], [True, False], r'\x00\x00\x00\x00\x00\x00', None, True, None), + 'Medals' : ([(0x49, 0x4b89)], 1, ["MedalName"], [0xCB], [(None, None)], [False], None, None, True, None), + 'Unknown0C' : ([(0x26, 0x6186)], 1, [], [None], [(2, 0x00)], [True], None, None, True, None), + 'Items' : ([(0x49, 0x4939)], 2, ["ItemName", "Flags"], [0xCB, None], [(9, 0x00), (1, None)], [False, True], None, None, True, None), + 'Unknown0E' : ([(0x29, 0x4000)], 1, [], [None], [(8, 0x00)], [True], None, None, True, None), + 'Medarotters' : ([(0x49, 0x4dd7)], 2, ["Unknown", "Name"], [None, 0xCB], [(3, 0x00), (None, None)], [True, False], None, None, True, None), + 'Unknown10' : ([(0x49, 0x4E56)], 1, [], [0xCB], [(None, None)], [False], None, None, True, None), + 'Terrain' : ([(0x2A, 0x4000)], 1, ["Terrain"], [0xCB], [(None, None)], [False], None, None, True, None), + 'Unknown12' : ([(0x2A, 0x4364)], 1, [], [None], [(11, 0x00)], [True], None, None, True, None), + 'Attacks' : ([(0x49, 0x4000)], 1, ["AttackName"], [0xCB], [(None, None)], [False], None, None, True, None), + 'CharacterNames' : ([(0x29, 0x44D0)], 1, ["CharacterName"], [0xCB], [(None, None)], [False], None, None, True, None), + 'Medarots' : ([(0x2B, 0x4630)], 1, ["MedarotName"], [0xCB], [(None, None)], [False], None, None, True, None), + 'Unknown16' : ([(0x29, 0x4F42)], 1, [], [None], [(35, 0x00)], [True], None, None, True, None), + 'Unknown17' : ([(0x2B, 0x5290)], 1, [], [None], [(35, 0x00)], [True], None, None, True, None), + 'Unknown18' : ([(0x49, 0x4c78)], 1, [], [0xCB], [(None, None)], [False], None, None, True, None), + 'MusicTitles' : ([(0x03, 0x737e), (0x03, 0x7383)], 1, [], [0xCB], [(None, None)], [False], None, None, True, None), +}) + +# Create the files per version once, and then proceed to only append to it +for version_suffix in roms: + open(os.path.join(version_src_path, f"{version_suffix}/ptrlist_data.asm"), 'w').close() + +with open(os.path.join(version_src_path, "ptrlist_data.asm"), "w") as datafile: + constants_file = os.path.join(text_build_path, f"ptrlist_data_constants_{{GAMEVERSION}}.asm") + datafile.write(f'INCLUDE "{constants_file}"\n\n') + for l in list_map: + addresses, spp, labels, term, fix_len, print_hex, null_indicator, data_prefix, is_general, special_tileset = list_map[l] + + if special_tileset: + tileset = tilesets.get_tileset(special_tileset, override_offset=0x0) + else: + tileset = default_tileset + + assert len(labels) == 0 or len(labels) == spp, f"Incorrect number of labels for {l}" + + assert len(addresses) == 1 or len(addresses) == len(roms), f"Number of addresses for {l} needs to be 1 or {len(roms)}" + for idx in range(0, len(addresses)): + addr = addresses[idx] + if isinstance(addr, tuple): + bank = addr[0] + addr = utils.rom2realaddr(addr) + addresses[idx] = (bank, addr) + else: + bank = utils.real2romaddr(addr)[0] + addresses[idx] = (bank, addr) + + if len(addresses) == 1: + bank = addresses[0][0] + addr = addresses[0][1] + 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') + else: + datafile.write(f'SECTION "Pointer List - {l}", ROMX[cAddress{l}], BANK[cBank{l}]\n') + datafile.write(f'PtrList{l}::\n') + datafile.write(f' INCBIN c{l}\n\n') + + entries = OrderedDict() + with open(os.path.join(text_src_path, f"{l}.txt"), "w", encoding="utf-8-sig") as output: + output.write(str(list_map[l][1:]) + "\n") + count_written = False + for address, version_suffix in zip_longest(addresses, roms, fillvalue = addresses[0]): + bank = address[0] + addr = address[1] + with open(os.path.join(version_src_path, f"{version_suffix}/ptrlist_data.asm"), 'a') as datafile_version: + if len(addresses) > 1: + datafile_version.write(f'DEF cAddress{l} EQU ${utils.real2romaddr(addr)[1]:04x}\n') + datafile_version.write(f'DEF cBank{l} EQU ${bank:02x}\n') + with open(roms[version_suffix], "rb") as rom: + rom.seek(addr) + # Make the (probably) safe assumption that the end of the table is the pointer + # before the first pointer + end = utils.rom2realaddr((bank, utils.read_short(rom) - 2)) + # Seeing the same pointer twice is an + # indicator that we've hit the last actual value in the table + rom.seek(end) + dummy_pointer = utils.read_short(rom) + rom.seek(-4, 1) + if utils.read_short(rom) != dummy_pointer: + dummy_pointer = -1 + + rom.seek(addr) + + ptrs = [] + + # We assume the number of entries is the same between versions, but the pointer might be different + if not count_written: + output.write(f"{((end - addr) // 2) + 1}\n") + count_written = True + + output.write(f"{version_suffix}#{dummy_pointer}\n") + + while rom.tell() <= end: + val = utils.read_short(rom) + if val == dummy_pointer: + break + if val in ptrs: + # Duplicate + ptrs.append(f"##={ptrs.index(val)}") + elif val > 0x7fff: + # A reference to RAM (e.g. the player name in credits) + # Annoyingly, if the entry is fixed length, we still need to write something + entry_length = 0 + try: + entry_length = sum([x[0] for x in fix_len]) + except: + entry_length = None + if entry_length: + ptrs.append(f"##&{val:04X}={ptrs[-1] + entry_length:04X}") + else: + ptrs.append(val) + + for idx, ptr in enumerate(ptrs): + + if idx not in entries: + entries[idx] = {} + + entries[idx][version_suffix] = [] + + if isinstance(ptr, str): + if ptr.startswith('##='): + entries[idx][version_suffix].append(ptr) + continue + elif ptr.startswith('##&'): + info = ptr.split('=') + entries[idx][version_suffix] = info[0] + '=' + ptr = int(info[1], 16) + + real_addr = utils.rom2realaddr((bank, ptr)) + + rom.seek(real_addr) + for i in range(0, spp): + t = term[i] + + fl = fix_len[i][0] + ph = print_hex[i] + + b = [] + + if fl != None: + b = [utils.read_byte(rom) for i in range(0, fl)] + else: + b = list(iter(partial(utils.read_byte, rom), t)) + if len(b) == 0: + b = [t] + txt = "" + i = 0 + while i < len(b): + if b[i] == 0xD3 and not ph: # Kanji + i += 1 + txt += kanji[b[i]] + else: + if ph or b[i] not in tileset: + txt += f'\\x{b[i]:02x}' + else: + txt += tileset[b[i]] + i += 1 + if not isinstance(entries[idx][version_suffix], list): + entries[idx][version_suffix] = [entries[idx][version_suffix] + txt] + else: + entries[idx][version_suffix].append(txt) + if txt == null_indicator: + break + + # Output as a CSV + writer = csv.writer(output, lineterminator='\n', delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) + if(len(labels) == 0): + for i in range(0, spp): + labels.append(f"Entry{i:02X}") + writer.writerow(["Index[#version]"] + labels) # Always print the index, at the bare minimum + for idx in entries: + unique_set = set("".join(v) for v in entries[idx].values()) + if len(entries[idx]) != 2 or len(unique_set) > 1: + for version in entries[idx]: + index = f"{idx}#{version}" + writer.writerow([index] + entries[idx][version]) + else: + writer.writerow([idx] + entries[idx][default_version]) + +# Include after doing constant definitions +for version_suffix in roms: + with open(os.path.join(version_src_path, f"{version_suffix}/ptrlist_data.asm"), 'a') as datafile_version: + include_file = os.path.join(version_src_path, "ptrlist_data.asm") + datafile_version.write(f'INCLUDE "{include_file}"\n') diff --git a/scripts/ptrlist2bin.py b/scripts/ptrlist2bin.py new file mode 100644 index 0000000..078ed9f --- /dev/null +++ b/scripts/ptrlist2bin.py @@ -0,0 +1,169 @@ +#!/bin/python + +import csv +import os, sys +from collections import OrderedDict +from functools import reduce +from struct import * +from ast import literal_eval +sys.path.append(os.path.join(os.path.dirname(__file__), 'common')) +from common import utils, tilesets + +default_char_table = utils.reverse_dict(utils.merge_dicts([ + tilesets.get_tileset("MainDialog1", override_offset=0x0), + tilesets.get_tileset("MainDialog2", override_offset=0x80), + tilesets.get_tileset("Special", override_offset=0xE0) + ])) +kanji = utils.reverse_dict(tilesets.get_tileset("Kanji", override_offset=0x0)) +assert((set(kanji.keys()) - set(default_char_table.keys())) == set(kanji.keys())) + +def chr2bin(c): + retval = None + if c in kanji: + retval = [0xD3, kanji[c]] + elif c in char_table: + retval = char_table[c] + else: + retval = char_table['?'] + return retval + +def convert_text(txt, term, fix_len): + assert(term != None or fix_len != None) + + retval = [] + length = None + padding = None + term_found = False + + if fix_len != None: + length, padding = fix_len + + idx = 0 + c = None + + while idx < len(txt): + try: + c = txt[idx] + if c == '\\': + assert(txt[idx+1] == 'x') + c = txt[idx+2] + c += txt[idx+3] + idx += 3 + c = int(c, 16) + else: + c = chr2bin(c) + + if c == term: + term_found = True + + if isinstance(c, list): + retval += c + else: + retval.append(c) + finally: + idx += 1 + + if term != None and not term_found: + retval.append(term) + + if length and len(retval) < length: + retval += [padding] * (length - len(retval)) + elif length and len(retval) > length: + print(retval) + raise Exception(f"{txt} is too long ({len(retval)} > {length})") + + return bytearray(retval) + + +output_file = sys.argv[1] +input_file = sys.argv[2] +version_suffix = sys.argv[3] + +base_name = os.path.splitext(os.path.basename(input_file))[0] + +bintext = bytearray() +idx_offset_map = OrderedDict() +idx_length_map = OrderedDict() +count = 0 +dummy_ptr = -1 + +with open(input_file, 'r', encoding='utf-8-sig') as fp: + spp, labels, term, fix_len, _, null_indicator, data_prefix, is_general, special_tileset = literal_eval(fp.readline().strip()) + + if special_tileset: + char_table = utils.reverse_dict(tilesets.get_tileset(special_tileset, override_offset=0x00)) + else: + char_table = default_char_table + + assert spp > 0, f"{input_file} is marked as having 0 strings per pointer" + assert len(labels) == 0 or len(labels) == spp, f"{input_file} has a label count that doesn't match strings per pointer" + # Total count, includes empty entries in the table + count = int(fp.readline().strip()) + dummy_ptr = fp.readline().strip() + while not dummy_ptr.startswith(version_suffix): + dummy_ptr = fp.readline().strip() + dummy_ptr = int(dummy_ptr.split('#')[1]) + + next(fp) # Ignore the next line as it's effectively a CSV header, treat it like a comment since 'labels' already fulfills this for us + + reader = csv.reader(fp, delimiter=',', quotechar='"') + + if data_prefix: + bintext += bytearray(data_prefix) + + for line in reader: + idx = line[0].split("#") + if len(idx) > 1: + # Check if versioned, and if version matches + if idx[1] != version_suffix: + continue + else: + idx = int(idx[0]) + else: + idx = int(idx[0]) + + data = line[1:] + assert len(data) == spp or data[0] == null_indicator, f"Index {idx} in {input_file} does not have enough entries ({len(data)}/{spp})" + + # Duplicate pointer, check first entry + if data[0].startswith("##="): + i = int(data[0].split('=')[1]) + idx_offset_map[idx] = idx_offset_map[i] + idx_length_map[idx] = 0 + continue + + current_offset = len(bintext) + idx_offset_map[idx] = current_offset + + # The pointer should be set to something specific (RAM pointer), and it may have 'useless' data where the pointer should be + if data[0].startswith('##&'): + info = data[0].split('=') + alias = int(info[0].lstrip('##&'), 16) + idx_offset_map[idx] = alias + print(alias) + if len(info) < 2: + continue + data[0] = info[1] + + # Not a duplicate, and valid for this version + for s, d in enumerate(data): + d = d.strip('\r\n') + bintext += convert_text(d, term[s], fix_len[s]) + else: + # On a clean exit, update the length, which is just the old length minus the new length + idx_length_map[idx] = len(bintext) - current_offset + +# Generate binary +with open(output_file, 'wb') as bin_file: + bin_file.write(pack("B", is_general)) + bin_file.write(pack("