Merge branch 'master' into cornucopia

This commit is contained in:
Ethan Roseman 2020-10-22 17:16:58 -04:00
commit 2462200b48
18 changed files with 389 additions and 118 deletions

View File

@ -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/

View File

@ -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

View File

@ -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)

View File

@ -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;
}

View File

@ -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;

View File

@ -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

View File

@ -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) }

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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) &&

View File

@ -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(),

View File

@ -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;
}

View File

@ -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);

188
tools/disasm_map.py Executable file
View File

@ -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 <file.midx>")
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))

View File

@ -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])}),")

@ -1 +1 @@
Subproject commit cc1ce6a64b60e3b0dbb032a2d198cd67210ad827
Subproject commit 895abeff31cc0bd5de3a05a55715d3457f8425eb

View File

@ -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