diff --git a/.gitignore b/.gitignore index 1781a676c..8bbe0b2a5 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ ST/ ctx.c ctx.c.m2c *.ld +*.map *auto.*.txt generated.symbols.*.txt __pycache__ @@ -33,6 +34,8 @@ disks/ .vscode/settings.json tools/go + +!tools/sotn-debugmodule/sotn-debugmodule.ld tools/saturn_toolchain/GCCSH config/saturn/game_syms.txt config/saturn/stage_02_syms.txt diff --git a/.vscode/launch.json b/.vscode/launch.json index 3becb3663..4e9998443 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,6 +4,25 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "name": "Debug SOTN (PCSX Redux)", + "type": "gdb", + "request": "attach", + "target": "localhost:3333", + "remote": true, + "stopAtConnect": true, + "executable": "build/sotn-debugmodule.elf", + "linux": { + "gdbpath": "/usr/bin/gdb-multiarch" + }, + "cwd": "${workspaceRoot}", + "autorun": [ + "set substitute-path /project .", + "file build/sotn-debugmodule.elf", + "continue", + ], + "valuesFormatting": "parseText" + }, { "name": "Debug sotn-disk (extract)", "type": "go", diff --git a/Makefile b/Makefile index 257e6d233..409e0f2cd 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ AS_FLAGS += -Iinclude -march=r3000 -mtune=r3000 -no-pad-sections -O1 -G0 PSXCC_FLAGS := -quiet -mcpu=3000 -fgnu-linker -mgas -gcoff CC_FLAGS += -G0 -w -O2 -funsigned-char -fpeephole -ffunction-cse -fpcc-struct-return -fcommon -fverbose-asm -msoft-float -g CPP_FLAGS += -Iinclude -undef -Wall -fno-builtin -CPP_FLAGS += -Dmips -D__GNUC__=2 -D__OPTIMIZE__ -D__mips__ -D__mips -Dpsx -D__psx__ -D__psx -D_PSYQ -D__EXTENSIONS__ -D_MIPSEL -D_LANGUAGE_C -DLANGUAGE_C -DHACKS +CPP_FLAGS += -Dmips -D__GNUC__=2 -D__OPTIMIZE__ -D__mips__ -D__mips -Dpsx -D__psx__ -D__psx -D_PSYQ -D__EXTENSIONS__ -D_MIPSEL -D_LANGUAGE_C -DLANGUAGE_C -DHACKS -DUSE_INCLUDE_ASM CPP_FLAGS += -D_internal_version_$(VERSION) LD_FLAGS := -nostdlib @@ -337,7 +337,7 @@ extract_disk_ps1%: $(SOTNDISK) extract_disk_saturn: bchunk disks/sotn.saturn.bin disks/sotn.saturn.cue disks/sotn.saturn.iso 7z x disks/sotn.saturn.iso01.iso -odisks/saturn/ || true -disk: build $(SOTNDISK) +disk_prepare: build $(SOTNDISK) mkdir -p $(DISK_DIR) cp -r disks/${VERSION}/* $(DISK_DIR) cp $(BUILD_DIR)/main.exe $(DISK_DIR)/SLUS_000.67 @@ -363,6 +363,11 @@ disk: build $(SOTNDISK) cp $(BUILD_DIR)/WRP.BIN $(DISK_DIR)/ST/WRP/WRP.BIN cp $(BUILD_DIR)/F_WRP.BIN $(DISK_DIR)/ST/WRP/F_WRP.BIN cp $(BUILD_DIR)/TT_000.BIN $(DISK_DIR)/SERVANT/TT_000.BIN +disk: disk_prepare + $(SOTNDISK) make build/sotn.$(VERSION).cue $(DISK_DIR) $(CONFIG_DIR)/disk.us.lba +disk_debug: disk_prepare + cd tools/sotn-debugmodule && make + cp $(BUILD_DIR)/../sotn-debugmodule.bin $(DISK_DIR)/SERVANT/TT_000.BIN $(SOTNDISK) make build/sotn.$(VERSION).cue $(DISK_DIR) $(CONFIG_DIR)/disk.us.lba update-dependencies: $(SPLAT_APP) $(ASMDIFFER_APP) $(M2CTX_APP) $(M2C_APP) $(MASPSX_APP) $(SATURN_SPLITTER_APP) $(GO) @@ -600,4 +605,4 @@ include tools/tools.mk .PHONY: main, dra, ric, cen, dre, mad, no3, np3, nz0, st0, wrp, rwrp, tt_000 .PHONY: %_dirs .PHONY: extract, extract_% -.PHONY: require-tools,update-dependencies +.PHONY: update-dependencies diff --git a/include/include_asm.h b/include/include_asm.h index a796e8dd1..eff5aeba1 100644 --- a/include/include_asm.h +++ b/include/include_asm.h @@ -33,7 +33,9 @@ #endif // omit .global +#ifdef USE_INCLUDE_ASM __asm__(".include \"macro.inc\"\n"); +#endif #else #define INCLUDE_ASM(FOLDER, NAME) diff --git a/src/dra/46358.c b/src/dra/46358.c index 5f34a61d2..db5de6113 100644 --- a/src/dra/46358.c +++ b/src/dra/46358.c @@ -101,7 +101,7 @@ void HandleNowLoading(void) { } if (g_UseDisk) { g_CdStep = CdStep_LoadInit; - g_LoadFile = 2; + g_LoadFile = CdFile_GameChr; } else { var_s0 = STAGE_ST0; if (g_StageId == var_s0 || diff --git a/tools/sotn-debugmodule/Makefile b/tools/sotn-debugmodule/Makefile new file mode 100644 index 000000000..e2dc77252 --- /dev/null +++ b/tools/sotn-debugmodule/Makefile @@ -0,0 +1,42 @@ +.SECONDEXPANSION: +.SECONDARY: + +VERSION ?= us +MODULE_NAME := sotn-debugmodule + +# Directories +INCLUDE_DIR := ../../include +BUILD_DIR := ../../build +CONFIG_DIR := ../../config + +# Compilers +CROSS := mipsel-linux-gnu- +CC := $(CROSS)gcc +LD := $(CROSS)ld +CPP := $(CROSS)cpp +OBJCOPY := $(CROSS)objcopy +CC_FLAGS += -c -march=r3000 -fno-stack-protector -mtune=r3000 -EL -G0 -O2 -g -funsigned-char -w -mno-abicalls -msoft-float +CPP_FLAGS += -I$(INCLUDE_DIR) -undef +CPP_FLAGS += -D_internal_version_$(VERSION) -Dmips -D__OPTIMIZE__ -D__mips__ -D__mips -Dpsx -D__psx__ -D__psx -D_PSYQ -D__EXTENSIONS__ -D_MIPSEL -D_LANGUAGE_C -DLANGUAGE_C + +all: build +build: $(BUILD_DIR)/$(MODULE_NAME).bin +clean: + rm -f $(BUILD_DIR)/$(MODULE_NAME).* + rm -rf $(BUILD_DIR)/tools/$(MODULE_NAME) +format: + clang-format -i $$(find . -type f -name "*.c") + clang-format -i $$(find . -type f -name "*.h") + +$(BUILD_DIR)/$(MODULE_NAME).bin: $(BUILD_DIR)/$(MODULE_NAME).elf + $(OBJCOPY) -O binary $< $@ + printf '\x00' | dd of=$@ bs=1 seek=40959 count=1 conv=notrunc + +$(BUILD_DIR)/%.elf: $(BUILD_DIR)/tools/%/inject.o $(BUILD_DIR)/tools/%/debugmode.o $(BUILD_DIR)/tools/%/menu.o $(BUILD_DIR)/tools/%/fontmanager.o $(BUILD_DIR)/tools/%/debug_flags.o $(BUILD_DIR)/tools/%/entity_spawn.o $(BUILD_DIR)/tools/%/sfx_player.o $(BUILD_DIR)/tools/%/flag_checker.o + $(LD) -o $@ -Map $*.map -T $*.ld -T $(CONFIG_DIR)/symbols.$(VERSION).txt \$^ + +$(BUILD_DIR)/tools/$(MODULE_NAME)/%.o: $(BUILD_DIR)/tools/$(MODULE_NAME)/%.c + $(CC) $(CC_FLAGS) -o $@ $< +$(BUILD_DIR)/tools/$(MODULE_NAME)/%.c: %.c + mkdir -p $(BUILD_DIR)/tools/$(MODULE_NAME) + $(CPP) $(CPP_FLAGS) -o $@ $< diff --git a/tools/sotn-debugmodule/debug_flags.c b/tools/sotn-debugmodule/debug_flags.c new file mode 100644 index 000000000..376404b06 --- /dev/null +++ b/tools/sotn-debugmodule/debug_flags.c @@ -0,0 +1,194 @@ +#include "debugmode.h" +#include "../../src/dra/dra.h" +#include "sfx.h" + +u32* const g_DraDebugHitboxViewMode = 0x801362B0; +u32* const g_DraDebugHitboxViewEnabled = 0x800BD1C0; + +int g_DbgLoadState; +int HookLoadStage(); +int HookLoadAlucard(); +int HookLoadRichter(); + +void ChangeStage(int param) { + g_DbgLoadState = 0; + g_mapTilesetId = param; + g_StageId = param; + SetHook(HookLoadStage); +} +void ChangePlayer(int param) { + g_DbgLoadState = 0; + switch (param) { + case 0: + SetHook(HookLoadAlucard); + break; + case 1: + SetHook(HookLoadRichter); + break; + } +} +void SetNoClip(int param) { g_DebugPlayer = param; } +void SetFrameByFrame(bool isEnabled) { g_FrameByFrame = isEnabled; } +void SetShowHitboxes(int param) { + *g_DraDebugHitboxViewMode = param; + *g_DraDebugHitboxViewEnabled = param; +} +void SetShowDebugMessages(bool isVisible) { + g_ShowDebugMessages = isVisible; + if (isVisible) { + g_ShowCollisionLayer = false; + g_ShowDrawCalls = 0; + g_ShowHBlankInfo = false; + } +} +void SetShowCollisionLayer(bool isVisible) { + g_ShowCollisionLayer = isVisible; + if (isVisible) { + g_ShowDebugMessages = false; + g_ShowDrawCalls = 0; + g_ShowHBlankInfo = false; + } +} +void SetShowDrawCalls(int param) { + g_ShowDrawCalls = param; + if (param != 0) { + g_ShowCollisionLayer = false; + g_ShowDebugMessages = false; + g_ShowHBlankInfo = false; + } +} +void SetShowHBlankInfo(bool isVisible) { + g_ShowHBlankInfo = isVisible; + if (isVisible) { + g_ShowCollisionLayer = false; + g_ShowDebugMessages = false; + g_ShowDrawCalls = 0; + } +} + +DbgMenuItem g_DebugFlagsItems[] = { + /**/ {0, 0, 0x4F, ChangeStage, DbgMenu_ActionOnInput}, + /**/ {0, 0, 1, ChangePlayer, DbgMenu_ActionOnInput}, + /**/ {0, false, true, SetNoClip, DbgMenu_ActionOnChange}, + /**/ {0, false, true, SetFrameByFrame, DbgMenu_ActionOnChange}, + /**/ {0, false, true, SetShowHitboxes, DbgMenu_ActionOnChange}, + /**/ {0, false, true, SetShowDebugMessages, DbgMenu_ActionOnChange}, + /**/ {0, false, true, SetShowCollisionLayer, DbgMenu_ActionOnChange}, + /**/ {0, 0, 2, SetShowDrawCalls, DbgMenu_ActionOnChange}, + /**/ {0, 0, 2, SetShowHBlankInfo, DbgMenu_ActionOnChange}, + /**/ MENU_END, +}; +DbgMenuCtrl g_DebugFlagsCtrl = { + g_DebugFlagsItems, + 236, + 0x40, + false, +}; + +const char* const c_DrawCalls[] = { + "off", + "current", + "max", +}; + +void InitDebugFlagsPlayer(void) { + g_DebugFlagsItems[0].param = g_StageId; + g_DebugFlagsItems[1].param = g_CurrentPlayableCharacter; +} +void UpdateDebugFlagsPlayer(void) { + int stageCursor = g_DebugFlagsItems[0].param; + Lba* lba = DbgGetStageLba(stageCursor); + + FntPrint("Stage: %02X %s (%s, %s)\n", stageCursor, lba->name, lba->ovlName, + lba->gfxName); + FntPrint( + "Player: %s\n", g_DebugFlagsItems[1].param ? "Richter" : "Alucard"); + FntPrint("No clip: %s\n", g_DebugPlayer ? "on" : "off"); + FntPrint("Frame by frame: %s (L1+L2)\n", g_FrameByFrame ? "on" : "off"); + FntPrint("Show hitboxes: %s\n", *g_DraDebugHitboxViewMode ? "on" : "off"); + FntPrint("Show debug messages: %s\n", g_ShowDebugMessages ? "on" : "off"); + FntPrint("Show collision layer: %s\n", g_ShowCollisionLayer ? "on" : "off"); + FntPrint("Show draw calls: %s\n", c_DrawCalls[g_DebugFlagsItems[7].param]); + FntPrint("Show HBlank info: %s\n", g_ShowHBlankInfo ? "on" : "off"); + + DbgMenuNavigate(&g_DebugFlagsCtrl); +} + +int HookLoadStage() { + if (g_GameState != Game_Play) { + return false; + } + + switch (g_DbgLoadState++) { + case 0: + g_CurrentPlayableCharacter = PLAYER_RICHTER; + g_GameState = Game_NowLoading; + g_GameStep = 2; + break; + default: + if (g_GameState == Game_Play) { + SetHook(NULL); + } + break; + } + + return true; +} + +int HookLoadRichter() { + if (g_IsUsingCd) { + return false; + } + + switch (g_DbgLoadState++) { + case 0: + g_CdStep = CdStep_LoadInit; + g_LoadFile = CdFile_RichterPrg; + break; + case 1: + g_CdStep = CdStep_LoadInit; + g_LoadFile = CdFile_GameChr; + break; + case 2: + g_CurrentPlayableCharacter = PLAYER_RICHTER; + g_GameState = Game_NowLoading; + g_GameStep = 2; + break; + default: + if (g_GameState == Game_Play) { + SetHook(NULL); + } + break; + } + + return true; +} + +int HookLoadAlucard() { + if (g_IsUsingCd) { + return false; + } + + switch (g_DbgLoadState++) { + case 0: + g_CdStep = CdStep_LoadInit; + g_LoadFile = CdFile_AlucardPrg; + break; + case 1: + g_CdStep = CdStep_LoadInit; + g_LoadFile = CdFile_GameChr; + break; + case 2: + g_CurrentPlayableCharacter = PLAYER_ALUCARD; + g_GameState = Game_NowLoading; + g_GameStep = 2; + break; + default: + if (g_GameState == Game_Play) { + SetHook(NULL); + } + break; + } + + return true; +} \ No newline at end of file diff --git a/tools/sotn-debugmodule/debugmode.c b/tools/sotn-debugmodule/debugmode.c new file mode 100644 index 000000000..087d35119 --- /dev/null +++ b/tools/sotn-debugmodule/debugmode.c @@ -0,0 +1,236 @@ +#include "debugmode.h" +#include "sfx.h" + +typedef struct { + void (*Init)(); + void (*Update)(); + bool showMenu; + bool pauseGame; + const char* name; +} DebugMenu; + +void DummyDummyDummy() {} +void InitDebugFlagsPlayer(void); +void InitEntitySpawn(void); +void InitSfxPlayer(void); +void InitFlagChecker(void); +void UpdateDebugFlagsPlayer(); +void UpdateEntitySpawn(); +void UpdateSfxPlayer(void); +void UpdateFlagChecker(void); + +DebugMenu g_DebugMenus[] = { + DummyDummyDummy, DummyDummyDummy, true, false, "R2 = debug", + InitDebugFlagsPlayer, UpdateDebugFlagsPlayer, true, true, "Debug mode", + InitEntitySpawn, UpdateEntitySpawn, true, true, "Entity spwn", + InitSfxPlayer, UpdateSfxPlayer, true, true, "Snd player", + InitFlagChecker, UpdateFlagChecker, true, true, "Castleflags", +}; + +int g_DebugMode; +bool g_DebugModePaused; +bool g_EntitiesPaused; +bool g_ShowDebugMessages; +bool g_ShowCollisionLayer; +bool g_FrameByFrame; +int g_ShowDrawCalls; +bool g_ShowHBlankInfo; +int (*g_Hook)(void); + +void DrawCollisionLayer(); +void ShowDrawCalls(int mode); +void ShowHBlankInfo(); +void DestroyEntity(Entity* item); + +void Init(void) { + int i; + + g_EntitiesPaused = false; + g_DebugMode = 0; + g_DebugModePaused = false; + g_ShowDebugMessages = false; + g_ShowCollisionLayer = false; + g_FrameByFrame = false; + g_ShowDrawCalls = 0; + g_ShowHBlankInfo = false; + g_Hook = NULL; + for (i = 0; i < LEN(g_DebugMenus); i++) { + g_DebugMenus[i].Init(); + } +} + +bool UpdateLogic() { + if (g_pads->tapped & PAD_R2) { + if (!g_DebugModePaused) { + g_DebugMode++; + if (g_DebugMode >= LEN(g_DebugMenus)) { + g_DebugMode = 0; + } + } else { + g_DebugModePaused = false; + } + PLAY_MENU_SOUND(); + } + + if (g_DebugModePaused) { + return; + } + + if (g_pads->pressed & PAD_TRIANGLE || g_pads->pressed & PAD_START) { + PauseDebugMode(); + return; + } + + if (g_DebugMenus[g_DebugMode].pauseGame != g_EntitiesPaused) { + g_EntitiesPaused ^= 1; + } + + if (g_DebugMenus[g_DebugMode].showMenu) { + DbgDrawMenuRect(159, 22, 90, 14); + SetFontCoord(160, 26); + FntPrint("%s\n", g_DebugMenus[g_DebugMode].name); + } + SetFontCoord(8, 48); + g_DebugMenus[g_DebugMode].Update(); + + return !g_EntitiesPaused; +} + +bool Update(void) { + bool entityPaused = false; + bool isDebugMenuVisible = g_DebugModePaused == false && g_DebugMode != 0; + bool skipFntOverride = g_ShowDebugMessages && !isDebugMenuVisible; + + if (!skipFntOverride) { + BeginFont(); + DbgBeginDrawMenu(); + } + if (g_ShowDebugMessages) { + PrintDefaultFont(); + } + + if (!skipFntOverride && !isDebugMenuVisible) { + if (g_ShowCollisionLayer) { + if (g_GameState == Game_Play) { + DrawCollisionLayer(); + } + } else if (g_ShowDrawCalls) { + ShowDrawCalls(g_ShowDrawCalls); + } else if (g_ShowHBlankInfo) { + ShowHBlankInfo(); + } + } + + entityPaused = g_Hook ? !!g_Hook() : UpdateLogic(); + if (g_Hook) { + entityPaused = !!g_Hook(); + } + + if (!skipFntOverride) { + DbgEndDrawMenu(); + EndFont(); + } + + if (g_FrameByFrame) { + return g_pads->repeat & PAD_L2 || g_pads->tapped & PAD_L1; + } + + return entityPaused; +} + +void PauseDebugMode() { g_DebugModePaused = true; } + +void SetHook(int (*hook)(void)) { g_Hook = hook; } + +u8 GetColType(s32 x, s32 y) { + // borrowing first part of CheckCollision + s32 absX; + s32 absY; + u8 colType; + int new_var; + // g_Camera.posX.i.lo doesn't seem to work like I expect + u16* cameraX = (u16*)0x80073074; + u16* cameraY = (u16*)0x8007307C; + absX = x + *cameraX; + absY = y + *cameraY; + new_var = 0x10; + if (absX < 0 || (u32)absX >= g_CurrentRoom.hSize << 8 || absY < 0 || + (u32)absY >= g_CurrentRoom.vSize << 8) { + colType = 0; + } else { + + // 16x16 blocks + u16 colTile = g_CurrentRoomTileLayout + .fg[(absX >> 4) + + (((absY >> 4) * g_CurrentRoom.hSize) * new_var)]; + colType = D_80073088->collision[colTile]; + } + return colType; +} + +void DrawCollisionLayer() { + int x; + int y; + u8 colType; + u16 cameraX = *(u16*)0x80073074; + u16 cameraY = *(u16*)0x8007307C; + + // skip first 4 rows since we are stuck with their FntOpen settings + SetFontCoord(-(cameraX & 0xF) + 8, -(cameraY & 0xF) + 16); + for (y = 16; y < 224; y += 16) { + // skip first column since we are stuck with their FntOpen settings + FntPrint(" ", colType); + for (x = 16; x < 256; x += 16) { + colType = GetColType(x, y); + + // skip empty tiles + if (colType == 0) { + FntPrint(" ", colType); + } else { + FntPrint("%02x", colType); + } + } + FntPrint("\n\n"); + } +} + +void ShowDrawCalls(int mode) { + const GpuUsage* const gpuMaxUsage = (GpuUsage*)0x801362DCU; + GpuUsage* gpuUsage; + + switch (mode) { + case 1: + gpuUsage = &g_GpuUsage; + FntPrint("cur call count\n"); + FntPrint("NOT IMPLEMENTED YET\n"); + break; + case 2: + gpuUsage = gpuMaxUsage; + FntPrint("max call count\n"); + break; + default: + return; + } + + FntPrint("dr :%03x\n", gpuUsage->drawModes); + FntPrint("gt4 :%03x\n", gpuUsage->gt4); + FntPrint("g4 :%03x\n", gpuUsage->g4); + FntPrint("gt3 :%03x\n", gpuUsage->gt3); + FntPrint("line:%03x\n", gpuUsage->line); + FntPrint("sp16:%03x\n", gpuUsage->sp16); + FntPrint("sp :%03x\n", gpuUsage->sp); + FntPrint("tile:%03x\n", gpuUsage->tile); + FntPrint("env :%03x\n", gpuUsage->env); +} + +void ShowHBlankInfo() { + GpuUsage* const gpuMaxUsage = (GpuUsage*)0x801362D0U; + if (g_blinkTimer & 1) { + FntPrint("l=%03x/100\n", gpuMaxUsage[1]); + FntPrint("l=%03x/100\n", gpuMaxUsage[0]); + } else { + FntPrint("l=%03x/100\n", gpuMaxUsage[0]); + FntPrint("l=%03x/100\n", gpuMaxUsage[1]); + } + gpuMaxUsage[0] = gpuMaxUsage[1]; +} diff --git a/tools/sotn-debugmodule/debugmode.h b/tools/sotn-debugmodule/debugmode.h new file mode 100644 index 000000000..d9f80ca5f --- /dev/null +++ b/tools/sotn-debugmodule/debugmode.h @@ -0,0 +1,56 @@ +#include + +#define MENU_END \ + { -1, -1, -1, NULL } + +typedef enum { + DbgMenu_ActionOnInput, + DbgMenu_ActionOnChange, + DbgMenu_ActionOnFrame, +} DbgMenuKind; + +typedef struct { + int param; + int min; + int max; + void (*action)(int param); + DbgMenuKind kind; +} DbgMenuItem; + +typedef struct { + DbgMenuItem* items; + int menuWidth; + int pageScroll; + int isInit; + + // no need to set all these values + int nItems; + int option; +} DbgMenuCtrl; + +#define PLAY_MENU_SOUND() g_api.PlaySfx(0x67B) + +void InitFont(); +void SetFontCoord(int x, int y); +void BeginFont(); +void EndFont(); +void PrintDefaultFont(); +void PauseDebugMode(); + +extern bool g_ShowDebugMessages; +extern bool g_ShowCollisionLayer; +extern bool g_FrameByFrame; +extern bool g_EntitiesPaused; +extern int g_ShowDrawCalls; +extern bool g_ShowHBlankInfo; + +void SetHook(int (*hook)(void)); + +void DbgBeginDrawMenu(void); +void DbgEndDrawMenu(void); +void DbgDrawMenuRect(int x, int y, int w, int h); +void DbgDrawCursor(int x, int y, int w, int h); +Lba* DbgGetStageLba(int stageId); + +// High level menu navigator that takes care of all the boilerplate +void DbgMenuNavigate(DbgMenuCtrl* ctrl); diff --git a/tools/sotn-debugmodule/entity_spawn.c b/tools/sotn-debugmodule/entity_spawn.c new file mode 100644 index 000000000..64dfb2ade --- /dev/null +++ b/tools/sotn-debugmodule/entity_spawn.c @@ -0,0 +1,421 @@ +#include "debugmode.h" + +typedef struct { + int length; + void** values; +} EntityDef; + +#define ENTITY_DEF(first, last) \ + { ((last) - (first)) / 4 + 1, first } +#define ENTITY_NONE \ + { 0, NULL } + +EntityDef g_DraEntities[] = { + ENTITY_DEF(0x800AD0C4, 0x800AD1D0), +}; +EntityDef g_RicEntities[] = { + ENTITY_DEF(0x8015495C, 0x80154A68), +}; +EntityDef g_StageEntities[] = { + ENTITY_NONE, // STAGE_NO0 + ENTITY_NONE, // STAGE_NO1 + ENTITY_NONE, // STAGE_LIB + ENTITY_NONE, // STAGE_CAT + ENTITY_NONE, // STAGE_NO2 + ENTITY_NONE, // STAGE_CHI + ENTITY_NONE, // STAGE_DAI + ENTITY_DEF(0x801808D0, 0x80180A38), // STAGE_NP3 + ENTITY_DEF(0x80180394, 0x80180400), // STAGE_CEN + ENTITY_NONE, // STAGE_NO4 + ENTITY_NONE, // STAGE_ARE + ENTITY_NONE, // STAGE_TOP + ENTITY_DEF(0x80180A94, 0x80180BC4), // STAGE_NZ0 + ENTITY_NONE, // STAGE_NZ1 + ENTITY_NONE, // STAGE_WRP + ENTITY_NONE, // STAGE_NO1_ALT + ENTITY_NONE, // STAGE_NO0_ALT + ENTITY_NONE, // + ENTITY_DEF(0x801803C8, 0x80180454), // STAGE_DRE + ENTITY_DEF(0x80180A94, 0x80180BC4), // STAGE_UNK_13 + ENTITY_NONE, // STAGE_UNK_14 + ENTITY_NONE, // STAGE_UNK_15 + ENTITY_NONE, // STAGE_BO7 + ENTITY_NONE, // STAGE_MAR + ENTITY_NONE, // STAGE_BO6 + ENTITY_NONE, // STAGE_BO5 + ENTITY_NONE, // STAGE_BO4 + ENTITY_NONE, // STAGE_BO3 + ENTITY_NONE, // STAGE_BO2 + ENTITY_NONE, // STAGE_BO1 + ENTITY_NONE, // STAGE_BO0 + ENTITY_DEF(0x801804C0, 0x80180570), // STAGE_ST0 + ENTITY_NONE, // STAGE_RNO0 + ENTITY_NONE, // STAGE_RNO1 + ENTITY_NONE, // STAGE_RLIB + ENTITY_NONE, // STAGE_RCAT + ENTITY_NONE, // STAGE_RNO2 + ENTITY_NONE, // STAGE_RCHI + ENTITY_NONE, // STAGE_RDAI + ENTITY_NONE, // STAGE_RNO3 + ENTITY_NONE, // STAGE_RCEN + ENTITY_NONE, // STAGE_RNO4 + ENTITY_NONE, // STAGE_RARE + ENTITY_NONE, // STAGE_RTOP + ENTITY_NONE, // STAGE_RNZ0 + ENTITY_NONE, // STAGE_RNZ1 + ENTITY_NONE, // STAGE_RWRP + ENTITY_NONE, // STAGE_NO1 + ENTITY_NONE, // STAGE_NO1 + ENTITY_NONE, // STAGE_NO1 + ENTITY_NONE, // STAGE_NO1 + ENTITY_NONE, // STAGE_NO1 + ENTITY_NONE, // STAGE_NO1 + ENTITY_NONE, // STAGE_RNZ1 + ENTITY_NONE, // STAGE_RBO8 + ENTITY_NONE, // STAGE_RBO7 + ENTITY_NONE, // STAGE_RBO6 + ENTITY_NONE, // STAGE_RBO5 + ENTITY_NONE, // STAGE_RBO4 + ENTITY_NONE, // STAGE_RBO3 + ENTITY_NONE, // STAGE_RBO2 + ENTITY_NONE, // STAGE_RBO1 + ENTITY_NONE, // STAGE_RBO0 + ENTITY_NONE, // STAGE_NO1 + ENTITY_DEF(0x8018049C, 0x801804EC), // STAGE_MAD + ENTITY_DEF(0x80180924, 0x80180AA8), // STAGE_NO3 + ENTITY_NONE, // STAGE_DAI + ENTITY_NONE, // STAGE_LIB + ENTITY_NONE, // STAGE_NO1 + ENTITY_NONE, // STAGE_SEL + ENTITY_NONE, // STAGE_TE1 + ENTITY_NONE, // STAGE_TE2 + ENTITY_NONE, // STAGE_TE3 + ENTITY_NONE, // STAGE_TE4 + ENTITY_NONE, // STAGE_TE5 + ENTITY_NONE, // STAGE_TOP + ENTITY_NONE, // STAGE_TE2 + ENTITY_NONE, // STAGE_TE2 + ENTITY_NONE, // STAGE_TE2 + ENTITY_NONE, // STAGE_TE2 +}; + +void EntitySpawner(void* ptrs); +Entity* AllocEntity(Entity* start, Entity* end); +void DestroyEntity(Entity* item); + +const int EntityPreview = 4; +int g_SpawnOption; +int g_Mode; +u8 g_SpawnEntityId; +u16 g_SpawnEntityParams; +u16 g_EntityStart; +u16 g_EntityEnd; +s16 g_SpawnX; +s16 g_SpawnY; +bool g_IsSpawnPreviewEnabled; +bool g_IsSpawnPlaceMode; +Entity* g_SpawnPlaceEntity; + +const char* g_ModeNames[] = { + "DRA", + "STAGE", + "RIC", +}; + +int GetEntityAllocationCount() { + s32 allocated = 0; + s32 i; + + for (i = g_EntityStart; i < g_EntityEnd; i++) { + if (g_Entities[i].entityId) { + allocated++; + } + } + + return allocated; +} + +EntityDef* GetEntityDef(int mode) { + switch (mode) { + case 0: + return g_DraEntities; + case 1: + if (g_StageId >= LEN(g_StageEntities)) { + return NULL; + } + return &g_StageEntities[g_StageId]; + case 2: + return g_RicEntities; + default: + return NULL; + } +} + +void* GetEntityUpdateFunc(int mode, u16 id) { + EntityDef* entityDefs; + + entityDefs = GetEntityDef(mode); + if (entityDefs == NULL || id >= entityDefs->length) { + return NULL; + } + + return entityDefs->values[id]; +} + +void SetEntityPreview(int mode, u16 id, u16 params) { + Entity* e = &g_Entities[EntityPreview]; + void* pfnUpdate = GetEntityUpdateFunc(mode, id); + DestroyEntity(e); + + if (pfnUpdate != NULL) { + e->entityId = id; + e->params = params; + e->pfnUpdate = pfnUpdate; + e->zPriority = 0x1F0; + e->posX.i.hi = g_SpawnX; + e->posY.i.hi = g_SpawnY; + g_CurrentEntity = e; + e->pfnUpdate(e); // runs three cycles to ensure we can preview + e->pfnUpdate(e); // some graphics from the entity + e->pfnUpdate(e); + } +} + +void DelEntityPreview() { DestroyEntity(&g_Entities[EntityPreview]); } + +Entity* SpawnEntity(int mode, u16 id, u16 params) { + Entity* e; + void* pfnUpdate; + + e = AllocEntity(g_Entities + g_EntityStart, g_Entities + g_EntityEnd); + if (e == NULL) { + return; + } + + pfnUpdate = GetEntityUpdateFunc(mode, id); + DestroyEntity(e); + + if (pfnUpdate != NULL) { + e->entityId = id; + e->params = params; + e->pfnUpdate = pfnUpdate; + e->zPriority = PLAYER.zPriority + 0x20; + e->posX.i.hi = g_SpawnX; + e->posY.i.hi = g_SpawnY; + e->pfnUpdate(e); + } + + return e; +} + +void UpdateEntityPlacement(int mode) { + if (g_SpawnPlaceEntity == NULL) { + // it should never be NULL. Exit before this crashes. + g_IsSpawnPlaceMode = false; + return; + } + + DbgDrawMenuRect(4, 44, 96 + 10, 16); + FntPrint("(%d, %d)\n", g_SpawnX, g_SpawnY); + if (g_pads->pressed & PAD_LEFT) { + g_SpawnX = g_SpawnPlaceEntity->posX.i.hi--; + } + if (g_pads->pressed & PAD_RIGHT) { + g_SpawnX = g_SpawnPlaceEntity->posX.i.hi++; + } + if (g_pads->pressed & PAD_UP) { + g_SpawnY = g_SpawnPlaceEntity->posY.i.hi--; + } + if (g_pads->pressed & PAD_DOWN) { + g_SpawnY = g_SpawnPlaceEntity->posY.i.hi++; + } + if (g_pads->tapped & PAD_CIRCLE) { + DestroyEntity(g_SpawnPlaceEntity); + g_IsSpawnPlaceMode = false; + g_SpawnPlaceEntity = NULL; + } + if (g_pads->tapped & PAD_SQUARE) { + SpawnEntity(mode, g_SpawnEntityId, g_SpawnEntityParams); + } + if (g_pads->tapped & PAD_CROSS) { + PauseDebugMode(); + g_IsSpawnPlaceMode = false; + g_SpawnPlaceEntity = NULL; + } +} + +void InitEntitySpawn(void) { + g_Mode = 1; + g_SpawnOption = 0; + g_SpawnEntityId = 1; + g_SpawnEntityParams = 0; + g_EntityStart = 0x40; + g_EntityEnd = 0x80; + g_SpawnX = 176; + g_SpawnY = 122; + g_IsSpawnPreviewEnabled = false; + g_IsSpawnPlaceMode = false; +} + +void UpdateEntitySpawn(void) { + const int MenuWidth = 220; + const int NOptions = 5; + const int NItems = NOptions; + + int i; + EntityDef* entityDefs; + u16 prevId, prevParams; + + if (g_IsSpawnPlaceMode) { + UpdateEntityPlacement(g_Mode); + return; + } + + entityDefs = GetEntityDef(g_Mode); + if (entityDefs == NULL || entityDefs->length == 0) { + DbgDrawMenuRect(4, 44, MenuWidth + 10, 8 + 1 * 8); + FntPrint("no entities"); + return; + } + + DbgDrawMenuRect(4, 44, MenuWidth + 10, 8 + NItems * 8); + DbgDrawCursor(9, 48 + g_SpawnOption * 8, MenuWidth, 8); + + FntPrint("Mode: %s (Alloc %d/%d)\n", g_ModeNames[g_Mode], + GetEntityAllocationCount(), g_EntityEnd - g_EntityStart); + FntPrint("ID %02X/%02X (func %08X)\n", g_SpawnEntityId, entityDefs->length, + GetEntityUpdateFunc(g_Mode, g_SpawnEntityId)); + FntPrint("Params %04X (Square=flag)\n", g_SpawnEntityParams); + FntPrint("Entity preview: %s\n", g_IsSpawnPreviewEnabled ? "ON" : "OFF"); + FntPrint("Place entity (Square=spam)\n", g_SpawnEntityParams); + + if (g_pads->repeat & PAD_UP) { + g_SpawnOption--; + if (g_SpawnOption < 0) { + g_SpawnOption = NOptions - 1; + } + PLAY_MENU_SOUND(); + } + if (g_pads->repeat & PAD_DOWN) { + g_SpawnOption++; + if (g_SpawnOption >= NOptions) { + g_SpawnOption = 0; + } + PLAY_MENU_SOUND(); + } + + prevId = g_SpawnEntityId; + prevParams = g_SpawnEntityParams; + switch (g_SpawnOption) { + case 0: + if (g_pads->repeat & PAD_LEFT || g_pads->repeat & PAD_RIGHT || + g_pads->repeat & PAD_CROSS) { + g_Mode++; + if (g_CurrentPlayableCharacter == PLAYER_ALUCARD) { + if (g_Mode > 1) { + g_Mode = 0; + } + } else if (g_CurrentPlayableCharacter == PLAYER_RICHTER) { + if (g_Mode > 2) { + g_Mode = 0; + } + } + + g_SpawnEntityId = 1; + g_SpawnEntityParams = 0; + } + break; + case 1: // ID option + if (g_pads->repeat & PAD_LEFT) { + g_SpawnEntityId--; + if (g_SpawnEntityId < 1) { + g_SpawnEntityId = 1; + } else { + g_SpawnEntityParams = 0; + } + } + if (g_pads->repeat & PAD_RIGHT) { + g_SpawnEntityId++; + if (g_SpawnEntityId >= entityDefs->length) { + g_SpawnEntityId = entityDefs->length - 1; + } else { + g_SpawnEntityParams = 0; + } + } + if (g_pads->tapped & PAD_CROSS) { + SpawnEntity(g_Mode, g_SpawnEntityId, g_SpawnEntityParams); + } + break; + case 2: // Params option + if (g_pads->repeat & PAD_LEFT) { + g_SpawnEntityParams--; + } + if (g_pads->repeat & PAD_RIGHT) { + g_SpawnEntityParams++; + } + if (g_pads->repeat & PAD_SQUARE) { + g_SpawnEntityParams ^= 0x8000; + } + if (g_pads->tapped & PAD_CROSS) { + SpawnEntity(g_Mode, g_SpawnEntityId, g_SpawnEntityParams); + } + break; + case 3: + if (g_pads->repeat & PAD_LEFT || g_pads->repeat & PAD_RIGHT || + g_pads->repeat & PAD_CROSS) { + g_IsSpawnPreviewEnabled ^= 1; + if (g_IsSpawnPreviewEnabled) { + prevId = -1; + } else { + DelEntityPreview(); + } + PLAY_MENU_SOUND(); + } + break; + case 4: // Place option + if (g_pads->tapped & PAD_CROSS) { + g_IsSpawnPlaceMode = true; + g_SpawnPlaceEntity = + SpawnEntity(g_Mode, g_SpawnEntityId, g_SpawnEntityParams); + DelEntityPreview(); + } + if (g_pads->tapped & PAD_SQUARE) { + SpawnEntity(g_Mode, g_SpawnEntityId, g_SpawnEntityParams); + } + break; + } + + if (prevId != g_SpawnEntityId || prevParams != g_SpawnEntityParams) { + if (g_IsSpawnPreviewEnabled) { + SetEntityPreview(g_Mode, g_SpawnEntityId, g_SpawnEntityParams); + } + PLAY_MENU_SOUND(); + } +} + +Entity* AllocEntity(Entity* start, Entity* end) { + Entity* current = start; + while (current < end) { + if (current->entityId == 0) { + return current; + } + + current++; + } + return NULL; +} + +void DestroyEntity(Entity* item) { + s32 i; + s32 length; + u32* ptr; + + if (item->flags & 0x800000) { + g_api.FreePrimitives(item->primIndex); + } + + ptr = (u32*)item; + length = sizeof(Entity) / sizeof(s32); + for (i = 0; i < length; i++) + *ptr++ = 0; +} diff --git a/tools/sotn-debugmodule/flag_checker.c b/tools/sotn-debugmodule/flag_checker.c new file mode 100644 index 000000000..ff2f75f05 --- /dev/null +++ b/tools/sotn-debugmodule/flag_checker.c @@ -0,0 +1,190 @@ +#include "debugmode.h" + +#define MaxFlagChangeLogCount 4 + +const char ChToHex[] = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + +int g_FlagOffset; +int g_FlagCursor; +bool g_IsFlagEditMode; +u8 g_CastleFlagsCopy[LEN(D_8003BDEC)]; +char g_CastleFlagChanges[MaxFlagChangeLogCount][8]; +int g_CastleFlagChangeCursor; + +void InitFlagChecker(void) { + int i; + + g_FlagOffset = 0; + g_FlagCursor = 0; + g_IsFlagEditMode = true; + g_CastleFlagChangeCursor = 0; + for (i = 0; i < MaxFlagChangeLogCount; i++) + g_CastleFlagChanges[i][0] = '\0'; +} + +void FlipBit(int offset, int bit) { D_8003BDEC[offset] ^= 1 << bit; } + +void ReportFlagChange(int offset, u8 prev, u8 cur) { + char* str = + g_CastleFlagChanges[g_CastleFlagChangeCursor % MaxFlagChangeLogCount]; + g_CastleFlagChangeCursor++; + + str[0] = ChToHex[(offset >> 8) & 15]; + str[1] = ChToHex[(offset >> 4) & 15]; + str[2] = ChToHex[(offset >> 0) & 15]; + str[3] = ' '; + str[4] = ChToHex[(cur >> 4) & 15]; + str[5] = ChToHex[(cur >> 0) & 15]; + str[6] = '\n'; + str[7] = '\0'; +} + +int UpdateFlagCheckerListenMode(void) { + const int MenuWidth = 64; + int i; + + if (g_pads->tapped & PAD_R2) { + // exit listen mode + g_CastleFlagChangeCursor = -1; + return; + } + + for (i = 0; i < LEN(D_8003BDEC); i++) { + if (g_CastleFlagsCopy[i] != D_8003BDEC[i]) { + ReportFlagChange(i, g_CastleFlagsCopy[i], D_8003BDEC[i]); + g_CastleFlagsCopy[i] = D_8003BDEC[i]; + } + } + + DbgDrawMenuRect(4, 44, MenuWidth + 10, 8 + MaxFlagChangeLogCount * 8); + for (i = 0; i < MaxFlagChangeLogCount; i++) { + int cursor = (i + g_CastleFlagChangeCursor) % MaxFlagChangeLogCount; + FntPrint(g_CastleFlagChanges[cursor]); + } + + return true; +} + +void InitFlagCheckerListenMode(void) { + g_CastleFlagChangeCursor = 0; + __builtin_memcpy(g_CastleFlagsCopy, D_8003BDEC, LEN(D_8003BDEC)); +} + +void PrintFlagLine(int offset, int columnCount) { + char buf[10]; + u8 ch; + + FntPrint("%03X ", offset); + while (columnCount--) { + if (offset >= sizeof(D_8003BDEC)) { + break; + } + ch = D_8003BDEC[offset++]; + buf[0] = '0' + !!(ch & 0x01); + buf[1] = '0' + !!(ch & 0x02); + buf[2] = '0' + !!(ch & 0x04); + buf[3] = '0' + !!(ch & 0x08); + buf[4] = '0' + !!(ch & 0x10); + buf[5] = '0' + !!(ch & 0x20); + buf[6] = '0' + !!(ch & 0x40); + buf[7] = '0' + !!(ch & 0x80); + buf[8] = ' '; + buf[9] = '\0'; + FntPrint(buf); + } + FntPrint("\n"); +} + +void UpdateFlagChecker(void) { + const int MenuWidth = 236; + const int BitsCount = 8; + const int RowCount = 8; + const int ColumnCount = 3; + const int MaxOffset = LEN(D_8003BDEC) - RowCount * ColumnCount; + const int MaxCursor = BitsCount * RowCount * ColumnCount; + + int i; + int offset; + int cx; + int cy; + + if (g_CastleFlagChangeCursor >= 0) { + g_EntitiesPaused = !UpdateFlagCheckerListenMode(); + return; + } + + DbgDrawMenuRect(4, 44, MenuWidth + 10, 16 + RowCount * 8); + for (i = 0, offset = g_FlagOffset; i < RowCount; i++) { + PrintFlagLine(offset, ColumnCount); + offset += ColumnCount; + } + + if (g_IsFlagEditMode) { + FntPrint("CIRCLE=View SQUARE=Listen"); + + cx = (g_FlagCursor % BitsCount) + + ((g_FlagCursor / BitsCount) % ColumnCount) * (BitsCount + 1); + cy = (g_FlagCursor / BitsCount) / ColumnCount; + DbgDrawCursor(40 + cx * 8, 48 + cy * 8, 8, 8); + + if (g_pads->repeat & PAD_UP) { + g_FlagCursor -= BitsCount * ColumnCount; + } + if (g_pads->repeat & PAD_DOWN) { + g_FlagCursor += BitsCount * ColumnCount; + } + if (g_pads->repeat & PAD_LEFT) { + g_FlagCursor--; + } + if (g_pads->repeat & PAD_RIGHT) { + g_FlagCursor++; + } + + if (g_FlagCursor < 0) { + g_FlagCursor += MaxCursor; + } + g_FlagCursor %= MaxCursor; + + if (g_pads->tapped & PAD_CROSS) { + FlipBit(g_FlagOffset + (g_FlagCursor / BitsCount), + g_FlagCursor % BitsCount); + } + if (g_pads->tapped & PAD_CIRCLE) { + g_IsFlagEditMode = false; + } + } else { + FntPrint("CROSS=Edit SQUARE=Listen"); + if (g_pads->repeat & PAD_UP) { + g_FlagOffset -= ColumnCount; + } + if (g_pads->repeat & PAD_DOWN) { + g_FlagOffset += ColumnCount; + } + if (g_pads->repeat & PAD_LEFT) { + g_FlagOffset--; + } + if (g_pads->repeat & PAD_RIGHT) { + g_FlagOffset++; + } + if (g_pads->tapped & PAD_CROSS) { + g_IsFlagEditMode = true; + } + } + + if (g_pads->repeat & PAD_L1) { + g_FlagOffset -= ColumnCount * RowCount; + } + if (g_pads->repeat & PAD_R1) { + g_FlagOffset += ColumnCount * RowCount; + } + if (g_pads->tapped & PAD_SQUARE) { + InitFlagCheckerListenMode(); + } + + if (g_FlagOffset < 0) { + g_FlagOffset = 0; + } else if (g_FlagOffset > MaxOffset) { + g_FlagOffset = MaxOffset; + } +} diff --git a/tools/sotn-debugmodule/fontmanager.c b/tools/sotn-debugmodule/fontmanager.c new file mode 100644 index 000000000..4402530ac --- /dev/null +++ b/tools/sotn-debugmodule/fontmanager.c @@ -0,0 +1,64 @@ +#include "debugmode.h" + +typedef struct { + u32 unk00; + u32 unk04; + s16 x; + s16 y; + s16 w; + s16 h; + u32 unk10; + u32 unk14; + u32 unk18; + u32 unk1C; + u32 unk20; + u32 unk24; + u32 unk28; +} FntStream; + +const s16 DefaultFontX = 8; +const s16 DefaultFontY = 48; + +int g_FontStreamId; +int g_PrevFontStreamId; +s16 g_FontCoordX; +s16 g_FontCoordY; +FntStream Font[]; + +void InitFont() { + g_FontStreamId = 0; + g_FontCoordX = DefaultFontX; + g_FontCoordY = DefaultFontY; +} + +void SetFontCoord(int x, int y) { + if (g_FontCoordX == x && g_FontCoordY == y) { + return; + } + +#ifndef DISABLE_FONT_COORD + FntFlush(g_FontStreamId); + Font[g_FontStreamId].x = g_FontCoordX = LOH(x); + Font[g_FontStreamId].y = g_FontCoordY = LOH(y); +#endif +} + +void BeginFont() { + Font[g_FontStreamId].x = -255; + Font[g_FontStreamId].y = -255; + FntFlush(-1); + Font[g_FontStreamId].x = g_FontCoordX = DefaultFontX; + Font[g_FontStreamId].y = g_FontCoordY = DefaultFontY; + SetDumpFnt(0); +} + +void EndFont() { + FntFlush(g_FontStreamId); + SetDumpFnt(0); +} + +void PrintDefaultFont() { + Font[g_FontStreamId].x = DefaultFontX; + Font[g_FontStreamId].y = DefaultFontY; + FntFlush(-1); +} diff --git a/tools/sotn-debugmodule/inject.c b/tools/sotn-debugmodule/inject.c new file mode 100644 index 000000000..1231e976f --- /dev/null +++ b/tools/sotn-debugmodule/inject.c @@ -0,0 +1,62 @@ +#include + +typedef struct { + u32 Init; + u32 Update; + u32 Unk08; + u32 Unk0C; + u32 Unk10; + u32 Unk14; + u32 Unk18; + u32 Unk1C; + u32 Unk20; + u32 Unk24; + u32 Unk28; + u32 Unk2C; + u32 Unk30; + u32 Unk34; + u32 Unk38; + u32 Unk3C; +} ServantDesc; + +const u32 INJECT_MAIN_ADDR = 0x80280040; // sotn-debugmodule.map +const u32 MODULE_ADDR = 0x80280000; // sotn-debugmodule.ld +const u32 LOAD_ADDR = 0x80170000; // Familiar overlay address +const u32 INJECT = INJECT_MAIN_ADDR - (MODULE_ADDR - LOAD_ADDR); + +void Dummy(Entity* e); +ServantDesc g_ServantDesc __attribute__((section(".inject-head"))) = { + INJECT, Dummy, Dummy, Dummy, Dummy, Dummy, Dummy, Dummy, + Dummy, Dummy, Dummy, Dummy, Dummy, Dummy, Dummy, Dummy, + +}; + +#define JAL(addr) ((((u32)(addr)&0x3FFFFFFU) >> 2) | 0x0C000000U) + +int MainLoop(); +void __attribute__((section(".inject-func"))) InjectMain(void) { + u32* const InjectPoint = 0x800E3D00U; + int i; + + // Enable 8MB of ram + *(unsigned int*)0x1F801060 |= 0xE00; + + // The game loads this overlay in 0x80170000, but as this module is compiled + // to use 0x80280000 as a base, we copy the overlay to there. + __builtin_memcpy((void*)MODULE_ADDR, (void*)LOAD_ADDR, 40960U); + + // Now we say to 'entrypoint_sotn' to call our main every frame + *InjectPoint = JAL(MainLoop); +} +void Dummy(Entity* e) {} + +bool g_Init = false; +void Init(void); +bool Update(void); +int MainLoop() { + if (!g_Init) { + Init(); + g_Init = true; + } + return Update(); +} \ No newline at end of file diff --git a/tools/sotn-debugmodule/menu.c b/tools/sotn-debugmodule/menu.c new file mode 100644 index 000000000..d1a2e2e49 --- /dev/null +++ b/tools/sotn-debugmodule/menu.c @@ -0,0 +1,172 @@ +#include "debugmode.h" + +int g_OtIdx; + +POLY_G4* GetNextPolyG4() { + POLY_G4* poly; + + if (g_GpuUsage.g4 >= LEN(g_CurrentBuffer->polyG4)) { + return NULL; + } + if (g_OtIdx >= LEN(g_CurrentBuffer->ot)) { + return NULL; + } + + poly = &g_CurrentBuffer->polyG4[g_GpuUsage.g4++]; + poly->code &= 0xFC; + AddPrim(&g_CurrentBuffer->ot[g_OtIdx++], poly); + return poly; +} + +void DbgBeginDrawMenu(void) { g_OtIdx = 0x1F0; } + +void DbgEndDrawMenu(void) {} + +void DbgDrawMenuRect(int x, int y, int w, int h) { + POLY_G4* poly; + + // Draw border + if (!(poly = GetNextPolyG4())) { + return; + } + poly->x2 = poly->x0 = x; + poly->y1 = poly->y0 = y; + poly->x3 = poly->x1 = x + w; + poly->y3 = poly->y2 = y + h; + poly->r0 = poly->r1 = poly->r2 = poly->r3 = 255; + poly->g0 = poly->g1 = poly->g2 = poly->g3 = 255; + poly->b0 = poly->b1 = poly->b2 = poly->b3 = 255; + AddPrim(&g_CurrentBuffer->ot[g_OtIdx++], poly); + + // Draw background + if (!(poly = GetNextPolyG4())) { + return; + } + poly->x2 = poly->x0 = x + 1; + poly->y1 = poly->y0 = y + 1; + poly->x3 = poly->x1 = x + w - 1; + poly->y3 = poly->y2 = y + h - 1; + poly->r0 = poly->r1 = poly->r2 = poly->r3 = 0; + poly->g0 = poly->g1 = poly->g2 = poly->g3 = 0; + poly->b0 = 176; + poly->b1 = 128; + poly->b2 = 80; + poly->b3 = 32; +} + +void DbgDrawCursor(int x, int y, int w, int h) { + POLY_G4* poly; + u8 blinkValue; + + if (g_blinkTimer & 0x20) { + blinkValue = g_blinkTimer & 0x1F; + } else { + blinkValue = 0x1F - (g_blinkTimer & 0x1F); + } + blinkValue *= 4; + blinkValue -= 0x80; + + if (!(poly = GetNextPolyG4())) { + return; + } + poly->x2 = poly->x0 = x; + poly->y1 = poly->y0 = y; + poly->x3 = poly->x1 = x + w; + poly->y3 = poly->y2 = y + h; + poly->r0 = poly->r1 = poly->r2 = poly->r3 = blinkValue - 0x20; + poly->g0 = poly->g1 = poly->g2 = poly->g3 = 0; + poly->b0 = poly->b1 = poly->b2 = poly->b3 = 0; +} + +int DbgGetMenuItemCount(DbgMenuCtrl* ctrl) { + int i; + + for (i = 0; ctrl->items[i].action != NULL; i++) { + } + return i; +} + +void DbgMenuCtrlInit(DbgMenuCtrl* ctrl) { + DbgMenuItem* item; + int i; + + ctrl->nItems = DbgGetMenuItemCount(ctrl); + for (i = 0; i < ctrl->nItems; i++) { + item = &ctrl->items[i]; + if (item->param < item->min) { + item->param = item->min; + } else if (item->param >= item->max) { + item->param = item->max - 1; + } + } + ctrl->option = 0; +} + +void DbgMenuNavigate(DbgMenuCtrl* ctrl) { + DbgMenuItem* item; + int prevParam; + + if (!ctrl->isInit) { + DbgMenuCtrlInit(ctrl); + ctrl->isInit = true; + } + + DbgDrawMenuRect(4, 44, ctrl->menuWidth + 10, 8 + ctrl->nItems * 8); + DbgDrawCursor(9, 48 + ctrl->option * 8, ctrl->menuWidth, 8); + + item = &ctrl->items[ctrl->option]; + prevParam = item->param; + if (g_pads->tapped & PAD_L1) { + item->param -= ctrl->pageScroll; + } + if (g_pads->tapped & PAD_R1) { + item->param += ctrl->pageScroll; + } + if (g_pads->repeat & PAD_LEFT) { + item->param--; + } + if (g_pads->repeat & PAD_RIGHT) { + item->param++; + } + if (item->param < item->min) { + item->param = item->max; + } else if (item->param > item->max) { + item->param = item->min; + } + + switch (item->kind) { + case DbgMenu_ActionOnInput: + if (g_pads->tapped & PAD_CROSS && item->action) { + item->action(item->param); + } + break; + case DbgMenu_ActionOnChange: + if (item->param != prevParam) { + item->action(item->param); + } + break; + case DbgMenu_ActionOnFrame: + item->action(item->param); + break; + } + + if (g_pads->tapped & PAD_UP) { + ctrl->option--; + if (ctrl->option < 0) { + ctrl->option = ctrl->nItems - 1; + } + PLAY_MENU_SOUND(); + } + if (g_pads->tapped & PAD_DOWN) { + ctrl->option++; + if (ctrl->option >= ctrl->nItems) { + ctrl->option = 0; + } + PLAY_MENU_SOUND(); + } +} + +Lba* DbgGetStageLba(int stageId) { + Lba* lba = 0x800A3C40; + return &lba[stageId]; +} diff --git a/tools/sotn-debugmodule/sfx_player.c b/tools/sotn-debugmodule/sfx_player.c new file mode 100644 index 000000000..ede9dba4f --- /dev/null +++ b/tools/sotn-debugmodule/sfx_player.c @@ -0,0 +1,56 @@ +#include "debugmode.h" +#include "../../src/dra/dra.h" +#include "sfx.h" + +void Play(int param) { g_api.PlaySfx(param); } +void StopAllSounds(int param) { g_api.PlaySfx(SET_STOP_MUSIC); } +void ChangeSoundMode(int param) { + g_api.PlaySfx(param ? SET_SOUNDMODE_STEREO : SET_SOUNDMODE_MONO); +} +void LoadStageSounds(int param) { + g_CdStep = CdStep_LoadInit; + g_LoadFile = CdFile_StageSfx; + g_mapTilesetId = param; +} +void LoadServant(int param) { + g_CdStep = CdStep_LoadInit; + g_LoadFile = CdFile_ServantChr; + g_mapTilesetId = param; +} + +DbgMenuItem g_SoundItems[] = { + {0, 0x001, 0x0FF, Play, DbgMenu_ActionOnInput}, + {0, 0x300, 0x5FF, Play, DbgMenu_ActionOnInput}, + {0, 0x600, 0xFFF, Play, DbgMenu_ActionOnInput}, + {0, 0, 0, StopAllSounds, DbgMenu_ActionOnInput}, + {STEREO_SOUND, MONO_SOUND, STEREO_SOUND, ChangeSoundMode, + DbgMenu_ActionOnChange}, + {0, 0, 0x4F, LoadStageSounds, DbgMenu_ActionOnInput}, + {0, 0, 6, LoadServant, DbgMenu_ActionOnInput}, + MENU_END, +}; +DbgMenuCtrl g_SoundCtrl = { + g_SoundItems, + 212, + 0x40, + false, +}; + +void InitSfxPlayer(void) {} +void UpdateSfxPlayer(void) { + const char* stageName = DbgGetStageLba(g_SoundItems[5].param)->ovlName; + + if (g_IsUsingCd) { + FntPrint("CD loading...\n"); + return; + } + + FntPrint("Kind 1 ID: %03X\n", g_SoundItems[0].param); + FntPrint("Kind 2 ID: %03X\n", g_SoundItems[1].param); + FntPrint("Kind 3 ID: %03X\n", g_SoundItems[2].param); + FntPrint("Stop all sounds\n"); + FntPrint("Sound mode: %s\n", g_SoundItems[4].param ? "MONO" : "STEREO"); + FntPrint("Load stage: SD_%s.VH (%02X)\n", stageName, g_SoundItems[5].param); + FntPrint("Load servant: SD_00%d.VH\n", g_SoundItems[6].param); + DbgMenuNavigate(&g_SoundCtrl); +} \ No newline at end of file diff --git a/tools/sotn-debugmodule/sotn-debugmodule.ld b/tools/sotn-debugmodule/sotn-debugmodule.ld new file mode 100644 index 000000000..302e34e37 --- /dev/null +++ b/tools/sotn-debugmodule/sotn-debugmodule.ld @@ -0,0 +1,22 @@ +SECTIONS { + . = 0x80280000; + + .inject : { + *(.inject-head*) + *(.inject-func*) + } + + .data : { + *(.data*) + } + + .rodata : { + *(.rodata*) + } + + .text : { + *(.text*) + } + + end = .; +}