diff --git a/.clang-tidy b/.clang-tidy index bbfbd5c7db..5bea913a68 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,2 +1,20 @@ -Checks: '-*,clang-analyzer-core.*,clang-analyzer-deadcode.*,readability-*,-readability-magic-numbers,-readability-else-after-return,-readability-named-parameter,-readability-braces-around-statements,-readability-isolate-declaration,-readability-uppercase-literal-suffix,-readability-function-size,-readability-non-const-parameter' -HeaderFilterRegex: '(src|include)\/.*\.h' +Checks: '-*,clang-analyzer-core.*,clang-analyzer-deadcode.*,readability-*,-readability-magic-numbers,-readability-else-after-return,-readability-named-parameter,-readability-braces-around-statements,-readability-isolate-declaration,-readability-uppercase-literal-suffix,-readability-function-size,-readability-non-const-parameter,readability-identifier-naming' +CheckOptions: + - key: readability-identifier-naming.LocalVariableCase + value: camelBack + - key: readability-identifier-naming.ParameterCase + value: camelBack + + - key: readability-identifier-naming.TypedefCase + value: CamelCase + + # TODO: rename all unk_XXX members + #- key: readability-identifier-naming.MemberCase + # value: camelBack + + # TODO: rename all D_XXXXXXXX symbols + #- key: readability-identifier-naming.GlobalVariableCase + # value: CamelCase + #- key: readability-identifier-naming.GlobalVariablePrefix + # value: g +HeaderFilterRegex: '(src|include)\/[^P].*\.h' # ignore inclue/PR/ diff --git a/Doxyfile b/Doxyfile index 8c73332737..343634104a 100644 --- a/Doxyfile +++ b/Doxyfile @@ -104,7 +104,7 @@ ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = YES PREDEFINED = DOXYGEN NON_MATCHING __attribute__((x))= -EXPAND_AS_DEFINED = INCLUDE_ASM UNK_TYPE UNK_PTR UNK_RET UNK_FUN_ARG UNK_FUN_PTR UNK_ARGS +EXPAND_AS_DEFINED = INCLUDE_ASM UNK_TYPE UNK_PTR UNK_RET UNK_FUN_ARG UNK_FUN_PTR UNK_ARGS M SKIP_FUNCTION_MACROS = YES SEARCH_INCLUDES = YES diff --git a/Makefile b/Makefile index 68397813df..ae7543bc27 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ SHELL=/bin/bash -o pipefail +MAKEFLAGS += --no-builtin-rules +MAKEFLAGS += --no-builtin-variables + ################ Target Executable and Sources ############### # BUILD_DIR is location where all build artifacts are placed @@ -8,9 +11,10 @@ BUILD_DIR = build SRC_DIRS := $(shell find src -type d) ASM_DIRS := asm asm/os INCLUDE_DIRS := include include/PR src -DATA_DIRS := $(shell find bin -type d -not -name Yay0) +DATA_DIRS := $(shell mkdir -p bin && find bin -type d -not -name Yay0) YAY0_DIRS := bin/Yay0 ASSETS_FS_DIRS := assets/fs +ASSETS_FS_BIN := $(BUILD_DIR)/assets/fs.bin # Source code files C_FILES := $(foreach dir,$(SRC_DIRS),$(wildcard $(dir)/*.c)) @@ -23,10 +27,11 @@ YAY0_FILES := $(foreach dir,$(YAY0_DIRS),$(wildcard $(dir)/*.bin)) ASSETS_FS_FILES := $(foreach dir,$(ASSETS_FS_DIRS),$(wildcard $(dir)/*.*)) # Object files -O_FILES := $(foreach file,$(C_FILES),$(BUILD_DIR)/$(file:.c=.o)) \ - $(foreach file,$(S_FILES),$(BUILD_DIR)/$(file:.s=.o)) \ - $(foreach file,$(DATA_FILES),$(BUILD_DIR)/$(file:.bin=.o)) \ - $(foreach dir,$(ASSETS_FS_DIRS),$(BUILD_DIR)/$(dir).o) \ +ASSETS_FS_O := $(ASSETS_FS_BIN:.bin=.o) +O_FILES := $(foreach file,$(C_FILES),$(BUILD_DIR)/$(file:.c=.c.o)) \ + $(foreach file,$(S_FILES),$(BUILD_DIR)/$(file:.s=.s.o)) \ + $(foreach file,$(DATA_FILES),$(BUILD_DIR)/$(file:.bin=.bin.o)) \ + $(ASSETS_FS_O) \ $(foreach file,$(YAY0_FILES),$(BUILD_DIR)/$(file:.bin=.Yay0.o)) ####################### Tools ######################### @@ -49,13 +54,11 @@ TARGET = papermario CPPFLAGS = -Iinclude -Isrc -D _LANGUAGE_C -ffreestanding -DF3DEX_GBI_2 ASFLAGS = -EB -Iinclude -march=vr4300 -mtune=vr4300 OLDASFLAGS = -EB -Iinclude -G 0 -CFLAGS = -O2 -quiet -G 0 -mcpu=vr4300 -mfix4300 -mips3 -mgp32 -mfp32 +CFLAGS = -O2 -quiet -G 0 -mcpu=vr4300 -mfix4300 -mips3 -mgp32 -mfp32 -Wimplicit -Wuninitialized -Wshadow LDFLAGS = -T undefined_syms.txt -T undefined_funcs.txt -T $(LD_SCRIPT) -Map $(BUILD_DIR)/papermario.map --no-check-sections ######################## Targets ############################# -$(foreach dir,$(SRC_DIRS) $(ASM_DIRS) $(DATA_DIRS) $(ASSETS_FS_DIRS) ,$(shell mkdir -p build/$(dir))) - default: all LD_SCRIPT = $(TARGET).ld @@ -64,6 +67,11 @@ all: $(TARGET).ld $(BUILD_DIR) $(TARGET).z64 verify clean: rm -rf $(BUILD_DIR) $(TARGET).z64 + make $(BUILD_DIR) + +clean-code: + rm -rf $(BUILD_DIR)/src $(TARGET).z64 + make $(BUILD_DIR) submodules: git submodule update --init --recursive @@ -81,23 +89,25 @@ print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true $(BUILD_DIR): mkdir -p $(BUILD_DIR) + @mkdir -p $(foreach dir,$(SRC_DIRS) $(ASM_DIRS) $(DATA_DIRS) $(ASSETS_FS_DIRS),build/$(dir)) -$(BUILD_DIR)/%.o: %.s +$(BUILD_DIR)/%.s.o: %.s $(AS) $(ASFLAGS) -o $@ $< -$(BUILD_DIR)/%.o: %.c $(H_FILES) +$(BUILD_DIR)/%.c.o: %.c $(H_FILES) cpp $(CPPFLAGS) $< | $(CC) $(CFLAGS) -o - | $(OLD_AS) $(OLDASFLAGS) - -o $@ -$(BUILD_DIR)/%.o: %.bin +$(BUILD_DIR)/%.bin.o: %.bin $(LD) -r -b binary -o $@ $< -$(BUILD_DIR)/assets/fs/%: $(ASSETS_FS_FILES) +$(BUILD_DIR)/assets/fs/%: assets/fs/% + @rm -f $@ $(TOOLS)/build_assets_fs.py $* $(BUILD_DIR)/assets/fs.bin: assets/fs.json $(TOOLS)/build_assets_fs.py $(foreach file,$(ASSETS_FS_FILES),build/$(file)) $(TOOLS)/build_assets_fs.py -$(BUILD_DIR)/assets/fs.o: $(BUILD_DIR)/assets/fs.bin +$(ASSETS_FS_O): $(ASSETS_FS_BIN) $(LD) -r -b binary -o $@ $< $(BUILD_DIR)/%.Yay0.o: %.bin @@ -122,4 +132,4 @@ $(TARGET).z64: $(BUILD_DIR)/$(TARGET).bin verify: $(TARGET).z64 sha1sum -c checksum.sha1 -.PHONY: all clean default +.PHONY: all clean default $(BUILD_DIR) diff --git a/docs/doxygen_custom.css b/docs/doxygen_custom.css index 877606101a..943a4fe917 100644 --- a/docs/doxygen_custom.css +++ b/docs/doxygen_custom.css @@ -2,7 +2,7 @@ body, table, div, p, dl { font: inherit } -body, table, div, p, dl, p.reference, p.definition, .memberdecls .header { +body, table, div, p, dl, p.reference, p.definition, .memberdecls .header, .mdescRight { color: #121212; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji; } @@ -48,7 +48,7 @@ body { flex-direction: row; width: 100%; - max-width: 1000px; + max-width: 1100px; min-height: 100%; margin: 0 auto; } @@ -92,9 +92,7 @@ table { #details + .groupheader, .definition, -.memtitle, -.contents > p:first-child > a:first-child /* "Go to the source code of this file." */ -{ display: none } +.memtitle { display: none } table.memname * { display: inline; @@ -125,19 +123,22 @@ code { display: block !important; border-left: 1px solid #eee; padding-left: 1em; - margin: 1em 0; + margin: 1.5em 0; width: 100%; box-sizing: border-box; } .memproto { padding: 8px; - margin-bottom: .5em; box-shadow: none; text-shadow: none; display: inline-block; } +.memdoc p:first-child { + margin-top: 0; +} + .memname { margin-left: 0; } diff --git a/include/common_structs.h b/include/common_structs.h index 4b5f63a4cd..767b243f08 100644 --- a/include/common_structs.h +++ b/include/common_structs.h @@ -115,6 +115,8 @@ typedef struct Npc { /* 0x0AB */ char unk_AB[661]; } Npc; // size = 0x340 +typedef Npc* NpcList[MAX_NPCS]; + typedef struct PlayerData { /* 0x000 */ s8 bootsLevel; /* 0x001 */ s8 hammerLevel; @@ -204,6 +206,8 @@ typedef struct Trigger { /* 0x34 */ ScriptID runningScriptID; } Trigger; // size = 0x38 +typedef Trigger* TriggerList[MAX_TRIGGERS]; + typedef struct ScriptInstance { /* 0x000 */ u8 state; /* 0x001 */ u8 currentArgc; @@ -281,6 +285,10 @@ typedef struct Entity { /* 0xB1 */ char unk_B1[71]; } Entity; // size = 0xF8 +typedef Entity* EntityList[MAX_ENTITIES]; + +typedef UNK_TYPE* DynamicEntityList[MAX_DYNAMIC_ENTITIES]; + typedef struct StaticEntityData { /* 0x00 */ s16 flags; /* 0x02 */ s16 argSize; @@ -374,6 +382,7 @@ typedef struct UiStatus { /* 0x68 */ s32 iconIndex13; /* 0x6C */ s8 unk_6C[4]; } UiStatus; // size = 0x70 + typedef struct Collider { /* 0x00 */ s32 flags; /* 0x04 */ s16 nextSibling; @@ -682,6 +691,8 @@ typedef struct Model { /* 0xAA */ char unk_AA[6]; } Model; // size = 0xB0 +typedef Model* ModelList[MAX_MODELS]; + typedef struct AnimatedMesh { /* 0x000 */ s32 flags; /* 0x004 */ u8 renderMode; @@ -931,6 +942,8 @@ typedef struct Shadow { /* 0x28 */ char unk_28[80]; } Shadow; // size = 0x78 +typedef Shadow* ShadowList[MAX_SHADOWS]; + typedef struct PushBlockGrid { /* 0x00 */ s8* cells; /* 0x04 */ u8 numCellsX; diff --git a/include/macros.h b/include/macros.h index d682a2b243..527aa5c3d1 100644 --- a/include/macros.h +++ b/include/macros.h @@ -15,6 +15,7 @@ #define ASSERT(condition) if (!(condition)) { while (1) {} } #define PANIC() ASSERT(0) +#define STATIC_ASSERT(condition) enum { static_assert_fail = 1/(!!(condition)) } // Causes division by zero ("not integer constant") if false #define GAME_STATUS (*gGameStatusPtr) #define PLAYER_STATUS (&gPlayerStatus) @@ -23,7 +24,13 @@ #define MAX_MAPVARS 16 #define MAX_MAPFLAGS 3 +#define MAX_MODELS 256 #define MAX_SCRIPTS 128 +#define MAX_NPCS 64 +#define MAX_TRIGGERS 64 +#define MAX_SHADOWS 60 +#define MAX_ENTITIES 30 +#define MAX_DYNAMIC_ENTITIES 16 //NOTE: SCRIPT_ALLOC is probably not quite correct, but this is the closest thing to matching for the functions its used in. Needs more work. #define SCRIPT_ALLOC(new, index) \ @@ -50,4 +57,7 @@ // Fixed-point short literal #define F16(f) (s16)(f * 327.67f) +#define _NAMESPACE(x, y) x ## _ ## y +#define NAMESPACE(x, y) _NAMESPACE(x, y) + #endif diff --git a/include/map.h b/include/map.h index eda5ad5bf1..2671514a12 100644 --- a/include/map.h +++ b/include/map.h @@ -7,6 +7,8 @@ // TODO: consider moving Npc here +#define M(sym) NAMESPACE(MAP_NAME, sym) + #define ENTRY_COUNT(entryList) (sizeof(entryList) / sizeof(Vec4f)) typedef Vec4f EntryList[]; @@ -61,12 +63,25 @@ typedef struct ItemDrop { /* 0x04 */ s16 unk_08; } ItemDrop; // size = 0x06 +/// @brief Describes heart/flower drop chances after defeating an Npc in the overworld. +/// +/// The algorithm for calculating the number of hearts/flowers from a StatDrop is: +/// - If current HP/FP > cutoff, drop 0. +/// - Roll generalChance. If it fails, drop 0. +/// - Roll chancePerAttempt attempts times. For each success, drop a heart/flower. +/// +/// StaticNpc holds a table of StatDrops for each stat (hearts, flowers). All are checked together +/// and the number of hearts/flowers to drop is the total number of successful attempts for each stat. +/// +/// Each heart/flower is worth 1 HP and 1 FP respectively, if picked up. +/// +/// cutoff, generalChance, and chancePerAttempt are short fixed-point percentage values. +/// That is, `F16(0)` is a 0% chance and `F16(100)` is a 100% chance. typedef struct StatDrop { - // NOTE: these %s are F16 - /* 0x00 */ s16 hpCutoff; // % of max HP/FP - /* 0x02 */ s16 generalChance; // % - /* 0x04 */ s16 attempts; - /* 0x06 */ s16 chancePerAttempt; // % + /* 0x00 */ s16 cutoff; ///< % of max HP/FP. If current HP/FP > cutoff, no hearts/flowers can be dropped. + /* 0x02 */ s16 generalChance; ///< % chance for any hearts/flowers to be dropped at all from this StatDrop. + /* 0x04 */ s16 attempts; ///< Maximum number of hearts/flowers that can be dropped from this StatDrop. + /* 0x06 */ s16 chancePerAttempt; ///< % chance for a single heart/flower to be dropped from each attempt. } StatDrop; // size = 0x08 #define NO_DROPS { F16(100), F16(0), 0, F16(0) } diff --git a/include/script_api/common.h b/include/script_api/common.h index 4af0f69b8d..2d51df65ec 100644 --- a/include/script_api/common.h +++ b/include/script_api/common.h @@ -4,7 +4,7 @@ #include "common_structs.h" #include "si.h" -typedef Bytecode Script[]; +typedef Bytecode Script[0]; ApiStatus FadeBackgroundToBlack(ScriptInstance* script, s32 isInitialCall); ApiStatus UnfadeBackgroundFromBlack(ScriptInstance* script, s32 isInitialCall); diff --git a/include/variables.h b/include/variables.h index acdf8f3fd4..62b185a0b0 100644 --- a/include/variables.h +++ b/include/variables.h @@ -38,30 +38,31 @@ extern s32 gScriptIndexList[MAX_SCRIPTS]; extern s32 gMoveScriptTable[][4]; -extern Model* gWorldModelList[256]; -extern Model* gBattleModelList[256]; -extern Model** gCurrentModelListPtr[256]; +extern ModelList gWorldModelList; +extern ModelList gBattleModelList; +extern ModelList* gCurrentModelListPtr; +// TODO: potentially a display list, figure this out extern u32* gWorldModelSpecialDls[32]; extern u32* gBattleModelSpecialDls[32]; extern u32** gCurrentModelSpecialDlsPtr[32]; -extern Entity* gWorldEntityList[30]; -extern Entity* gBattleEntityList[30]; -extern Entity** gCurrentEntityListPtr[30]; +extern EntityList gWorldEntityList; +extern EntityList gBattleEntityList; +extern EntityList* gCurrentEntityListPtr; extern s32 gLastCreatedEntityIndex; -extern UNK_TYPE* gWorldDynamicEntityList[16]; -extern UNK_TYPE* gBattleDynamicEntityList[16]; -extern UNK_TYPE*** gCurrentDynamicEntityListPtr; +extern DynamicEntityList gWorldDynamicEntityList; +extern DynamicEntityList gBattleDynamicEntityList; +extern DynamicEntityList* gCurrentDynamicEntityListPtr; -extern Npc* gWorldNpcList[64]; -extern Npc* gBattleNpcList[64]; -extern Npc** gCurrentNpcListPtr[64]; +extern NpcList gWorldNpcList; +extern NpcList gBattleNpcList; +extern NpcList* gCurrentNpcListPtr; -extern Shadow* gWorldShadowList[60]; -extern Shadow* gBattleShadowList[60]; -extern Shadow** gCurrentShadowListPtr[60]; +extern ShadowList gWorldShadowList; +extern ShadowList gBattleShadowList; +extern ShadowList* gCurrentShadowListPtr; extern Camera gCameras[4]; extern s32 gCurrentCameraID; @@ -89,9 +90,9 @@ extern PrintContext* D_802DB268; // Triggers extern s16 gTriggerCount; -extern Trigger* gTriggerList1[64]; -extern Trigger* gTriggerList2[64]; -extern Trigger** gCurrentTriggerListPtr[64]; +extern TriggerList gTriggerList1; +extern TriggerList gTriggerList2; +extern TriggerList* gCurrentTriggerListPtr; // Map transition data. Should probably be a struct extern u16 gMapTransitionAlpha; diff --git a/src/code_a5dd0_len_114e0.c b/src/code_a5dd0_len_114e0.c index 3c67595af5..26d7166ec7 100644 --- a/src/code_a5dd0_len_114e0.c +++ b/src/code_a5dd0_len_114e0.c @@ -34,24 +34,24 @@ Shadow* get_shadow_by_index(s32 index) { return (*gCurrentShadowListPtr)[index & 0xFFF]; } -Entity** get_entity_list(void) { - Entity** ret; +EntityList* get_entity_list(void) { + EntityList* ret; if (!GAME_STATUS->isBattle) { - ret = gWorldEntityList; + ret = &gWorldEntityList; } else { - ret = gBattleEntityList; + ret = &gBattleEntityList; } return ret; } -Shadow** get_shadow_list(void) { - Shadow** ret; +ShadowList* get_shadow_list(void) { + ShadowList* ret; if (!GAME_STATUS->isBattle) { - ret = gWorldShadowList; + ret = &gWorldShadowList; } else { - ret = gBattleShadowList; + ret = &gBattleShadowList; } return ret; } diff --git a/src/code_dbd70_len_700.c b/src/code_dbd70_len_700.c index 9045c95c38..e5c36c58ce 100644 --- a/src/code_dbd70_len_700.c +++ b/src/code_dbd70_len_700.c @@ -8,9 +8,9 @@ INCLUDE_ASM(s32, "code_dbd70_len_700", clear_trigger_data); void init_trigger_list(void) { if (!GAME_STATUS->isBattle) { - *gCurrentTriggerListPtr = gTriggerList1; + gCurrentTriggerListPtr = &gTriggerList1; } else { - *gCurrentTriggerListPtr = gTriggerList2; + gCurrentTriggerListPtr = &gTriggerList2; } gTriggerCount = 0; @@ -22,16 +22,17 @@ INCLUDE_ASM(s32, "code_dbd70_len_700", update_triggers); void delete_trigger(Trigger* toDelete) { s32 i; + TriggerList** currentTriggerListPtr = &gCurrentTriggerListPtr; - for (i = 0; i < ARRAY_COUNT(gCurrentTriggerListPtr); i++) { - if ((*gCurrentTriggerListPtr)[i] == toDelete) { + for (i = 0; i < MAX_TRIGGERS; i++) { + if ((**currentTriggerListPtr)[i] == toDelete) { break; } } - if (i < ARRAY_COUNT(gCurrentTriggerListPtr)) { - heap_free((*gCurrentTriggerListPtr)[i]); - (*gCurrentTriggerListPtr)[i] = NULL; + if (i < MAX_TRIGGERS) { + heap_free((**currentTriggerListPtr)[i]); + (**currentTriggerListPtr)[i] = NULL; } } @@ -48,7 +49,7 @@ s32 func_80145CE8(s32 arg0) { return 0; } - for (i = 0; i < ARRAY_COUNT(gCurrentTriggerListPtr); i++) { + for (i = 0; i < MAX_TRIGGERS; i++) { Trigger* trigger = (*gCurrentTriggerListPtr)[i]; if ((trigger != NULL) && diff --git a/src/world/area_kmr/kmr_12/events.c b/src/world/area_kmr/kmr_12/events.c index 70f3923b3d..b56c1e1d21 100644 --- a/src/world/area_kmr/kmr_12/events.c +++ b/src/world/area_kmr/kmr_12/events.c @@ -1,37 +1,33 @@ #include "kmr_12.h" -static Script make_entities; -static Script read_west_sign; -static NpcGroupList npc_groups; +Script M(ExitWest) = EXIT_WALK_SCRIPT(60, 0, "kmr_07", 1); +Script M(ExitEast) = EXIT_WALK_SCRIPT(60, 1, "kmr_11", 0); -static Script exit_west = EXIT_WALK_SCRIPT(60, 0, "kmr_07", 1); -static Script exit_east = EXIT_WALK_SCRIPT(60, 1, "kmr_11", 0); - -static Script bind_exits = { - SI_BIND(exit_west, TriggerFlag_FLOOR_ABOVE, 0 /* deili1 */, NULL), - SI_BIND(exit_east, TriggerFlag_FLOOR_ABOVE, 3 /* deili2 */, NULL), +Script M(BindExits) = { + SI_BIND(M(ExitWest), TriggerFlag_FLOOR_ABOVE, 0 /* deili1 */, NULL), + SI_BIND(M(ExitEast), TriggerFlag_FLOOR_ABOVE, 3 /* deili2 */, NULL), SI_RETURN(), SI_END(), }; -Script kmr_12_main = { +Script M(Main) = { SI_SET(SI_SAVE_VAR(425), 31), SI_CALL(SetSpriteShading, -1), SI_CALL(SetCamPerspective, 0, 3, 25, 16, 4096), SI_CALL(SetCamBGColor, 0, 0, 0, 0), SI_CALL(SetCamEnabled, 0, 1), - SI_CALL(MakeNpcs, 0, npc_groups), - SI_EXEC_WAIT(make_entities), - SI_EXEC(kmr_12_play_music), - SI_SET(SI_VAR(0), bind_exits), + SI_CALL(MakeNpcs, 0, M(npcGroupList)), + SI_EXEC_WAIT(M(MakeEntities)), + SI_EXEC(M(PlayMusic)), + SI_SET(SI_VAR(0), M(BindExits)), SI_EXEC(EnterWalk), SI_WAIT_FRAMES(1), - SI_BIND(read_west_sign, TriggerFlag_WALL_INTERACT, 10, NULL), + SI_BIND(M(ReadWestSign), TriggerFlag_WALL_INTERACT, 10, NULL), SI_RETURN(), SI_END(), }; -static NpcAISettings goomba_ai_settings = { +NpcAISettings M(goombaAISettings) = { .moveSpeed = 1.5f, .moveTime = 30, .waitTime = 30, @@ -46,16 +42,16 @@ static NpcAISettings goomba_ai_settings = { .unk_2C = TRUE, }; -static Script goomba_ai = { - SI_CALL(DoBasicAI, &goomba_ai_settings), +Script M(GoombaAI) = { + SI_CALL(DoBasicAI, &M(goombaAISettings)), SI_RETURN(), SI_END(), }; -static NpcSettings goomba_npc_settings = { +NpcSettings M(goombaNpcSettings) = { .height = 20, .radius = 23, - .ai = &goomba_ai, + .ai = &M(GoombaAI), .onHit = EnemyNpcHit, .onDefeat = EnemyNpcDefeat, .level = 5, @@ -63,7 +59,7 @@ static NpcSettings goomba_npc_settings = { // *INDENT-OFF* /// @bug The RETURN command is after the END command, so this script will never terminate. -static Script read_west_sign = { +Script M(ReadWestSign) = { SI_GROUP(0), // "Eat a Mushroom to regain your energy!" @@ -73,7 +69,7 @@ static Script read_west_sign = { SI_RESUME_GROUP(1), SI_SET(SI_FLAG(0), FALSE), - SI_CALL(kmr_12_get_goomba_ref), + SI_CALL(GetGoomba), SI_IF_NE(SI_VAR(0), FALSE), SI_CALL(GetNpcVar, NpcId_GOOMBA, 0, SI_VAR(0)), SI_IF_EQ(SI_VAR(0), FALSE), @@ -92,7 +88,7 @@ static Script read_west_sign = { SI_RETURN(), }; -static Script goomba_idle = { +Script M(GoombaIdle) = { SI_WAIT_FRAMES(1), SI_CALL(SetSelfVar, 0, FALSE), @@ -142,28 +138,28 @@ static Script goomba_idle = { SI_CALL(SetSelfEnemyFlagBits, 0x40000000, TRUE), // We're done jumping off; the player can read the sign again - SI_BIND(read_west_sign, TriggerFlag_WALL_INTERACT, 10, NULL), + SI_BIND(M(ReadWestSign), TriggerFlag_WALL_INTERACT, 10, NULL), // Behave like a normal enemy from now on - SI_CALL(BindNpcAI, NpcId_SELF, &goomba_ai), + SI_CALL(BindNpcAI, NpcId_SELF, &M(GoombaAI)), SI_RETURN(), SI_END(), }; -static Script goomba_init = { - SI_CALL(BindNpcIdle, NpcId_SELF, &goomba_idle), +Script M(GoombaInit) = { + SI_CALL(BindNpcIdle, NpcId_SELF, &M(GoombaIdle)), SI_RETURN(), SI_END(), }; // *INDENT-ON* -static StaticNpc goomba_npc = { +StaticNpc M(goombaNpc) = { .id = NpcId_GOOMBA, - .settings = &goomba_npc_settings, + .settings = &M(goombaNpcSettings), .pos = { -33.0f, 30.0f, -25.0f }, .flags = 0x00000C00, - .init = goomba_init, + .init = M(GoombaInit), .yaw = 90, .dropFlags = 0x80, .itemDropChance = 5, @@ -204,13 +200,13 @@ static StaticNpc goomba_npc = { }, }; -static NpcGroupList npc_groups = { - NPC_GROUP(goomba_npc, FORMATION_ID(1, 0, 3)), +NpcGroupList M(npcGroupList) = { + NPC_GROUP(M(goombaNpc), FORMATION_ID(1, 0, 3)), NPC_GROUP_LIST_END(), }; // *INDENT-OFF* -static Script read_east_sign = { +Script M(ReadEastSign) = { SI_CALL(func_800441F0, SI_VAR(0)), SI_IF_EQ(SI_VAR(0), 1), SI_RETURN(), @@ -228,9 +224,9 @@ static Script read_east_sign = { SI_END(), }; -static Script make_entities = { +Script M(MakeEntities) = { SI_CALL(MakeEntity, 0x802EAFDC, 436, 0, -42, 0, 0x80000000), - SI_CALL(AssignScript, &read_east_sign), + SI_CALL(AssignScript, &M(ReadEastSign)), SI_RETURN(), SI_END(), diff --git a/src/world/area_kmr/kmr_12/header.c b/src/world/area_kmr/kmr_12/header.c index f8b4cba3f5..5d9a96fdf0 100644 --- a/src/world/area_kmr/kmr_12/header.c +++ b/src/world/area_kmr/kmr_12/header.c @@ -1,25 +1,25 @@ #include "kmr_12.h" -static EntryList entryList = { +EntryList M(entryList) = { { -126.0f, 0.0f, 12.0f, 90.0f }, // west, towards Red/Blue Goomba miniboss room { 471.0f, 0.0f, 12.0f, 270.0f }, // east, towards Goomba King's Fortress }; -MapConfig config = { - .main = kmr_12_main, - .entryList = entryList, - .entryCount = ENTRY_COUNT(entryList), +MapConfig M(config) = { + .main = M(Main), + .entryList = M(entryList), + .entryCount = ENTRY_COUNT(M(entryList)), .background = &gBackgroundImage, .tattle = MessageID_TATTLE_KMR_12, }; -Script kmr_12_play_music = { +Script M(PlayMusic) = { SI_CALL(SetMusicTrack, 0, Song_PLEASANT_PATH, 0, 8), SI_RETURN(), SI_END(), }; -ApiStatus kmr_12_get_goomba_ref(ScriptInstance* script, s32 isInitialCall) { +ApiStatus GetGoomba(ScriptInstance* script, s32 isInitialCall) { script->varTable[0] = get_enemy_safe(NpcId_GOOMBA); return ApiStatus_DONE2; } diff --git a/src/world/area_kmr/kmr_12/kmr_12.h b/src/world/area_kmr/kmr_12/kmr_12.h index 10708cb84c..406b7f4299 100644 --- a/src/world/area_kmr/kmr_12/kmr_12.h +++ b/src/world/area_kmr/kmr_12/kmr_12.h @@ -1,8 +1,16 @@ #include "common.h" #include "map.h" +#define MAP_NAME kmr_12 + #define NpcId_GOOMBA 0 -Script kmr_12_main; -Script kmr_12_play_music; -ApiStatus kmr_12_get_goomba_ref(ScriptInstance* script, s32 isInitialCall); +#define GetGoomba M(GetGoomba) +ApiStatus GetGoomba(ScriptInstance* script, s32 isInitialCall); + +Script M(Main); +Script M(PlayMusic); +Script M(MakeEntities); +Script M(ReadWestSign); + +NpcGroupList M(npcGroupList); diff --git a/tools/disasm_map.py b/tools/disasm_map.py new file mode 100755 index 0000000000..842912d30b --- /dev/null +++ b/tools/disasm_map.py @@ -0,0 +1,188 @@ +#! /usr/bin/python3 + +import sys +import os +import yaml +from struct import unpack +from disasm_script import disassemble as disassemble_script + +def disassemble(bytes, offset, midx, symbol_map = {}, map_name = "map"): + out = "" + found_data = False + + while len(midx) > 0: + struct = midx.pop(0) + name = struct["name"] + if name == "Script_Main": name = f"M(Main)" + + #print(f"{offset:X} ({name}, start = {struct['start']:X}, len = {struct['length']:X})") + + if struct["start"] == offset: + found_data = True + + if struct["start"] != offset: + # end of data / padding + break + + # format struct + if struct["type"].startswith("Script"): + out += disassemble_script(bytes, f"M({name})", symbol_map) + elif struct["type"] == "Padding": + # nops at end of file + bytes.seek(offset % 4, 1) + return out + elif struct["type"] == "EntryList": + out += f"EntryList M(entryList) = {{" + for i in range(0, struct["length"], 4 * 4): + x,y,z,yaw = unpack(">ffff", bytes.read(4 * 4)) + out += f"\n {{ {x}f, {y}f, {z}f, {yaw}f }}," + out += f"\n}};\n" + elif struct["type"] == "Header": + out += f"MapConfig M(config) = {{\n" + + bytes.read(0x10) + + main,entry_list,entry_count = unpack(">IIi", bytes.read(4 * 3)) + out += f" .main = M(Main)\n" + out += f" .entryList = M(entryList)\n" + out += f" .entryCount = {entry_count}, // prefer ENTRY_COUNT(M(entryList)) if it matches\n" + + bytes.read(0x1C) + + bg,tattle = unpack(">II", bytes.read(4 * 2)) + out += f" .background = {'&gBackgroundImage' if bg == 0x80200000 else 'NULL'},\n" + out += f" .tattle = {tattle:X},\n" + + out += f"}};\n" + else: # unknown type of struct + out += f"s32 M({name})[] = {{" + for i in range(0, struct["length"], 4): + if (i % 0x20) == 0: + out += f"\n " + + word = int.from_bytes(bytes.read(4), byteorder="big") + + if word in symbol_map: + out += f" M({symbol_map[word]})," + else: + out += f" 0x{word:08X}," + + out += f"\n}};\n" + + out += "\n" + elif found_data: + if struct["type"] != "Padding": + # put struct back on list + midx.insert(0, struct) + + # nops at end of file + bytes.seek(offset % 4, 1) + + return out + + if struct["type"] != "Function" and not struct["type"] == "Padding" and not (struct["type"] == "Missing" and not found_data): + offset += struct["length"] + + # end of data + return out + +def parse_midx(file, prefix = ""): + structs = [] + + for line in file.readlines(): + s = line.split("#") + if len(s) == 5: + if s[0] == "$Start": continue + if s[0] == "$End": continue + structs.append({ + "name": prefix + name_struct(s[0]), + "type": s[1], + "start": int(s[2], 16), + "vaddr": int(s[3], 16), + "length": int(s[4], 16), + "end": int(s[2], 16) + int(s[4], 16), + }) + elif "Missing" in s: + start = int(s[1], 16) + end = int(s[2], 16) + vaddr = start + 0x80240000 + structs.append({ + "name": f"{prefix}unk_missing_{vaddr:X}", + "type": "Missing", + "start": start, + "vaddr": vaddr, + "length": end - start, + "end": end, + }) + elif "Padding" in s: + start = int(s[1], 16) + end = int(s[2], 16) + vaddr = start + 0x80240000 + structs.append({ + "name": f"{prefix}__padding__", + "type": "Padding", + "start": start, + "vaddr": vaddr, + "length": end - start, + "end": end, + }) + + structs.sort(key=lambda s: s["start"]) + return structs + +def name_struct(s): + s = s[1:].replace("???", "unk") + + # use ThisCase for scripts + if s.startswith("$Script"): + return s[0].upper() + s[1:] + + return s[0].lower() + s[1:] + +if __name__ == "__main__": + if len(sys.argv) == 1: + print("usage: ./disasm_map.py ") + print("Converts split map data into C files using a .midx file from Star Rod.") + exit() + + map_name = os.path.splitext(os.path.basename(sys.argv[1]))[0] + area_name = "area_" + map_name.split("_")[0] + if len(area_name) > 8: + area_name = area_name[:8] + + with open(sys.argv[1], "r") as f: + midx = parse_midx(f) + + symbol_map = {} + for struct in midx: + symbol_map[struct["vaddr"]] = struct["name"] + + bin_dir = f"bin/world/{area_name}/{map_name}" + src_dir = f"src/world/{area_name}/{map_name}" + + splits = [] + rom_start = 0 + with open(os.path.join(os.path.dirname(__file__), "splat.yaml")) as splat: + splat = yaml.safe_load(splat) + + for segment in splat["segments"]: + if type(segment) == dict and segment.get("name") == f"world/{area_name}/{map_name}/": + rom_start = segment.get("start", 0) + splits = segment.get("files", []) + continue + if len(splits) == 0: + print(f"unable to find {map_name} in splat.yaml") + exit(1) + + # advance to the EntryList (start of data) + while midx[0]["type"] != "EntryList": + midx.pop(0) + + for split in splits: + rom_addr = split[0] + filetype = split[1] + + if filetype == "bin": + with open(f"{bin_dir}/{rom_addr:X}.bin", "rb") as bytes: + print(f"// {rom_addr:X}") + print(disassemble(bytes, rom_addr - rom_start, midx, symbol_map, map_name)) diff --git a/tools/disasm_script.py b/tools/disasm_script.py index d9699500db..33c834df2a 100755 --- a/tools/disasm_script.py +++ b/tools/disasm_script.py @@ -42,14 +42,11 @@ def star_rod_lib(): return _star_rod_lib -def addr_ref(addr): - return star_rod_lib().get(addr, f"0x{addr:08X}") - -def disassemble(bytes, indent = 0, script_name = "script"): +def disassemble(bytes, script_name = "script", symbol_map = {}): out = "" prefix = "" - indent += 1 + indent = 1 indent_used = False def write_line(line): @@ -65,6 +62,9 @@ def disassemble(bytes, indent = 0, script_name = "script"): prefix += "\n" def var(arg): + if arg in symbol_map: + return symbol_map[arg] + v = arg - 2**32 # convert to s32 if v > -250000000: if v <= -220000000: return f"SI_FIXED({(v + 230000000) / 1024}f)" @@ -86,6 +86,11 @@ def disassemble(bytes, indent = 0, script_name = "script"): else: return f"{arg}" + def addr_ref(addr): + if addr in symbol_map: + return symbol_map[addr] + return star_rod_lib().get(addr, f"0x{addr:08X}") + def trigger(trigger): if trigger == 0x00000080: trigger = "TriggerFlag_FLOOR_TOUCH" if trigger == 0x00800000: trigger = "TriggerFlag_FLOOR_ABOVE" @@ -107,6 +112,10 @@ def disassemble(bytes, indent = 0, script_name = "script"): while True: opcode = read_word() argc = read_word() + + if opcode > 0xFF or argc > 0xFF: + return f"/* malformed script: {script_name} */\n" + argv = [] for i in range(0, argc): argv.append(read_word()) @@ -167,7 +176,7 @@ def disassemble(bytes, indent = 0, script_name = "script"): write_line(f"SI_END_IF(),") elif opcode == 0x14: write_line(f"SI_SWITCH({var(argv[0])}),") - indent += 1 + indent += 2 elif opcode == 0x15: write_line(f"SI_SWITCH_CONST(0x{argv[0]:X}),") indent += 2 @@ -197,7 +206,7 @@ def disassemble(bytes, indent = 0, script_name = "script"): indent += 1 elif opcode == 0x1C: indent -= 1 - write_line(f"SI_CASE_DEFAULT({var(argv[0])}),") + write_line(f"SI_CASE_DEFAULT(),") indent += 1 elif opcode == 0x1D: indent -= 1 @@ -265,7 +274,7 @@ def disassemble(bytes, indent = 0, script_name = "script"): elif opcode == 0x4E: if argv[4] != 0: raise "BIND_PADLOCK argv[4] != NULL" - if argv[5] != 0: + if argv[5] != 1: raise "BIND_PADLOCK argv[5] != 1" write_line(f"SI_BIND_PADLOCK({addr_ref(argv[0])}, {trigger(argv[1])}, {var(argv[2])}, {var(argv[3])}),") diff --git a/tools/n64splat b/tools/n64splat index cc1ce6a64b..895abeff31 160000 --- a/tools/n64splat +++ b/tools/n64splat @@ -1 +1 @@ -Subproject commit cc1ce6a64b60e3b0dbb032a2d198cd67210ad827 +Subproject commit 895abeff31cc0bd5de3a05a55715d3457f8425eb diff --git a/tools/splat.yaml b/tools/splat.yaml index 55958fd9c5..aa6a048f39 100644 --- a/tools/splat.yaml +++ b/tools/splat.yaml @@ -5,6 +5,7 @@ options: pycparser_flags: ["-Iinclude", "-D_LANGUAGE_C", "-ffreestanding", "-DF3DEX_GBI_2", "-DSPLAT"] compiler: "GCC" mnemonic_ljust: 10 + ld_o_replace_extension: no segments: - name: header type: header