mirror of
https://github.com/Xeeynamo/sotn-decomp.git
synced 2024-11-22 20:49:50 +00:00
Custom debugging module (#108)
# What Adds a custom debug menu. It is written in C and it is meant to replace the Bat familar `SERVANT/TT_000.BIN`. Once loaded you can long-press SELECT+START to soft-reset the game and keep using the debug menu everywhere, including when playing with Richter or during the credits. One key requirement to run this is to have an emulator that emulates the 8MB of RAM. This is a key requirement to have the debug module surviving soft-resets or accessing to the in-game menu. I personally used [PCSX Redux](https://github.com/grumpycoders/pcsx-redux) to build this module. I am not sure about the compatibility with other emulators. This does **NOT** work on real hardware and it is a choice by design. The debug module is intended to test different areas of the game and help decompiling. It is not intended to be used in normal gameplay. # Build Simply invoke `make disk_debug` to create a disk image of the game in `build/` with the debug module replacing the Bat familiar. # Usage ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/15a040b6-6191-41c4-b2b8-a4a906ed59eb) On PCSX Redux go to Configuration, Emulation and tick the box `8MB`. ## Loading the module ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/65b7ccb3-800e-4b66-84f5-5703fc91babe) You need to enable the Bat Card from the menu. This will load the debug module from the disk. If you want to re-load the module you need to select another Familar Card, un-pause, pause again and select the Bat Card once more. ## The main screen ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/b528425a-ea6c-4c10-9c19-522612d0ad2a) You will know you have loaded the debug module when you see the blue rectangle on the top right. You can press R2 to cycle between the menus. Some menus will temporarily freeze the game, some not. To quickly return in-game you can either press TRIANGLE or START. To bring back the paused debug menu just press R2 once again. ## Debug Mode ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/12cac1ee-725b-484f-8564-89c03b6a755b) ### Stage Teleports the player to a different stage. It is not stable and it can crash often. ### Player Switches between Alucard and Richter. Currently switching from Richter to Alucard consistently crashes the game. ### No Clip Allows to freely move the player within the room and without the collisions on. Once the flag is enabled from the debug menu, pressing L2 while in-game will temporarily freeze the player movement and make it immune to collision checking. You can then press the directional buttons to slowly move the player or you can hold CROSS to move it faster. You can also use SQUARE or CIRCLE to cycle between the player frames. Press L2 again to deactivate the NoClip mode. ### Frame by frame Freezes the game outside the debug module. Press L1 to advance by 1 frame. Hold L2 to put the game in slow-motion. ### Show hitboxes ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/ea8dd918-cf37-4be1-bb87-541a66ac7f16) As shown in the image ### Show debug messages ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/5c9bc7b2-6bc7-4831-949a-bf73cdf910e8) When the debug menu is un-paused, prints on the top left all the debug messages from the game itself. ### Show collision layer ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/959ad1ab-dfe0-42fe-a8d0-65c619469280) This prints the internal collision value for every 16x16 tile on the screen. Look the CheckCollision function for more information on how each printed value is used. ### Show draw calls ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/a52970fc-bf30-430d-98e3-5fca33c9f6f7) Shows the maximum GPU resource usage since the game started. Currently only the `max` option works. The `current` option will not show anything. ### Show HBlank ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/81759019-6bf9-4cf2-b88c-aa60eaa5ddcb) Prints the current horizontal blank interrupt count. ## Entity Spawn ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/f0fdc024-4537-4c07-a80d-816d605a3583) Allows to immediately spawn new entities in the current map ### Mode There are three list of entities, each one with their own ID: DRA, Stage and RIC. The option RIC is hidden if Richter is not the current playing character. As the list of entities per stage is maintained manually, stage entities might not be available for all stages. The `Alloc` shows how many entities are reserved or actually used. Pressing the SQUARE button here will destroy all the entities within that range. ### ID Press Left or Right to cycle between the different IDs available. Some of them might crash the game immediately once spawned. Press CROSS to immediately spawn the entity. ### Params Each entity might have its own parameters. Sometimes the flag 0x8000 is used, which can be toggled with the SQUARE button. Press CROSS to immediately spawn the entity. ### Entity preview Shows the entity before spawning it. This is turned off by default as it can immediately crash by cycling through the available entity IDs. ### Place entity Pressing CROSS will allow to move the entity across the screen before placing it. Press CROSS again to place the entity and return to the previous screen. Press SQUARE to quickly place multiple entities of the same type. ## Sound player ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/9d8ad3a5-bc78-4674-abd0-94537bbfae98) There are three macro categories the sound player is split into. For what is currently known only the sounds within the Kind 3 changes based on the loaded stage. ### Stop all sounds This will also disable the SPU IRQ, effectively unlocking the frame rate. ### Load Stage Loads a different sound font than the current loaded stage. This can help to quickly preview and test SFXs from other stages without necessarily moving the player there. ### Load Servant Loads the sound font of a specific servant without necessarily equipping the Familiar Card. ## Castle flags Preview all the flags used to modify the behaviour of different parts of the two castles. ### Edit mode ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/b03ada2f-0fb4-4345-ad50-81209702032c) You can move the cursor with the directional buttons and flip the flag with CROSS. Press L1 or R1 to cycle between the pages. The cursor warps when reaching the border of the flag grid, allowing a faster navigation. ### View mode Allows to move between the flags more flexibly. ### Listen mode ![image](https://github.com/Xeeynamo/sotn-decomp/assets/6128729/4f9ed118-d09d-4658-ba25-5865dbebbf3c) Listens for the modified flags while playing. Every time a flag is modified the offset and its value is registered on the top left up to 4 rows. When all the rows are occupied, new values will just remove the oldest one. The last modified flag will always be displayed at the bottom. --------- Co-authored-by: Alejandro Asenjo Nitti <sonicdcer@users.noreply.github.com> Co-authored-by: sozud <sozud@users.noreply.github.com>
This commit is contained in:
parent
bdddb80978
commit
9b9b3f649f
3
.gitignore
vendored
3
.gitignore
vendored
@ -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
|
||||
|
19
.vscode/launch.json
vendored
19
.vscode/launch.json
vendored
@ -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",
|
||||
|
11
Makefile
11
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
|
||||
|
@ -33,7 +33,9 @@
|
||||
#endif
|
||||
|
||||
// omit .global
|
||||
#ifdef USE_INCLUDE_ASM
|
||||
__asm__(".include \"macro.inc\"\n");
|
||||
#endif
|
||||
|
||||
#else
|
||||
#define INCLUDE_ASM(FOLDER, NAME)
|
||||
|
@ -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 ||
|
||||
|
42
tools/sotn-debugmodule/Makefile
Normal file
42
tools/sotn-debugmodule/Makefile
Normal file
@ -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 $@ $<
|
194
tools/sotn-debugmodule/debug_flags.c
Normal file
194
tools/sotn-debugmodule/debug_flags.c
Normal file
@ -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;
|
||||
}
|
236
tools/sotn-debugmodule/debugmode.c
Normal file
236
tools/sotn-debugmodule/debugmode.c
Normal file
@ -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];
|
||||
}
|
56
tools/sotn-debugmodule/debugmode.h
Normal file
56
tools/sotn-debugmodule/debugmode.h
Normal file
@ -0,0 +1,56 @@
|
||||
#include <game.h>
|
||||
|
||||
#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);
|
421
tools/sotn-debugmodule/entity_spawn.c
Normal file
421
tools/sotn-debugmodule/entity_spawn.c
Normal file
@ -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;
|
||||
}
|
190
tools/sotn-debugmodule/flag_checker.c
Normal file
190
tools/sotn-debugmodule/flag_checker.c
Normal file
@ -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;
|
||||
}
|
||||
}
|
64
tools/sotn-debugmodule/fontmanager.c
Normal file
64
tools/sotn-debugmodule/fontmanager.c
Normal file
@ -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);
|
||||
}
|
62
tools/sotn-debugmodule/inject.c
Normal file
62
tools/sotn-debugmodule/inject.c
Normal file
@ -0,0 +1,62 @@
|
||||
#include <game.h>
|
||||
|
||||
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();
|
||||
}
|
172
tools/sotn-debugmodule/menu.c
Normal file
172
tools/sotn-debugmodule/menu.c
Normal file
@ -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];
|
||||
}
|
56
tools/sotn-debugmodule/sfx_player.c
Normal file
56
tools/sotn-debugmodule/sfx_player.c
Normal file
@ -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);
|
||||
}
|
22
tools/sotn-debugmodule/sotn-debugmodule.ld
Normal file
22
tools/sotn-debugmodule/sotn-debugmodule.ld
Normal file
@ -0,0 +1,22 @@
|
||||
SECTIONS {
|
||||
. = 0x80280000;
|
||||
|
||||
.inject : {
|
||||
*(.inject-head*)
|
||||
*(.inject-func*)
|
||||
}
|
||||
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
|
||||
.text : {
|
||||
*(.text*)
|
||||
}
|
||||
|
||||
end = .;
|
||||
}
|
Loading…
Reference in New Issue
Block a user