From 536352ab36e6116f08fed13655981e6a64bac48f Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Tue, 2 Sep 2025 21:47:30 +0200 Subject: [PATCH 01/13] begin region rework + clangd stuff --- .clangd | 9 +++ Makefile | 4 +- README.md | 2 + include/Megamix.hpp | 4 +- include/Megamix/Error.hpp | 13 ++++ include/Megamix/Region.hpp | 46 ++++++++++++-- src/Megamix/Error.cpp | 11 ++-- src/Megamix/Region.cpp | 119 +++++++++++++++++++++++-------------- src/main.cpp | 14 ++++- 9 files changed, 160 insertions(+), 62 deletions(-) create mode 100644 .clangd diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..6df5362 --- /dev/null +++ b/.clangd @@ -0,0 +1,9 @@ +CompileFlags: + Add: + - "-D__INT32_TYPE__=long" + - "-D__UINT32_TYPE__=unsigned long" + Remove: + - "-mword-relocations" +Diagnostics: + Suppress: + - unused-includes \ No newline at end of file diff --git a/Makefile b/Makefile index f3eb2b2..b5b662b 100644 --- a/Makefile +++ b/Makefile @@ -58,7 +58,7 @@ ifneq ($(RELEASE), 0) CFLAGS += -DRELEASE endif -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++20 +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++23 ASFLAGS := $(ARCH) LDFLAGS := -T $(TOPDIR)/3gx.ld $(ARCH) -Os -Wl,--gc-sections @@ -87,7 +87,7 @@ SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) export LD := $(CXX) export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) export INCLUDE := $(foreach dir,$(INCLUDES),-I $(CURDIR)/$(dir) ) \ - $(foreach dir,$(LIBDIRS),-I $(dir)/include) \ + $(foreach dir,$(LIBDIRS),-isystem $(dir)/include) \ -I $(CURDIR)/$(BUILD) export LIBPATHS := $(foreach dir,$(LIBDIRS),-L $(dir)/lib) diff --git a/README.md b/README.md index 40e45fc..ccfa7dc 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,8 @@ To install them, you'll need [devkitPro pacman](https://devkitpro.org/wiki/devki Run `make` to build the plugin. +> If you're trying to use clangd, you'll need to add `--query-driver=$DEVKITARM/bin/arm-none-eabi-*` to its arguments + ## Credits * patataofcourse, 0xadk, and TheAlternateDoctor for programming the actual plugin * EstexNT for a LOT of the research used in this project - thank you so much! diff --git a/include/Megamix.hpp b/include/Megamix.hpp index 482bfd8..e8dc485 100644 --- a/include/Megamix.hpp +++ b/include/Megamix.hpp @@ -7,8 +7,6 @@ #define MEGAMIX_MODS_PATH "/spicerack/mods/" #define MEGAMIX_CONFIG_PATH MEGAMIX_BIN_PATH "saltwater.cfg" -#endif - #include "Saltwater.hpp" #include "Megamix/Region.hpp" #include "Megamix/Hooks.hpp" @@ -17,3 +15,5 @@ #include "Megamix/Error.hpp" #include "Megamix/Commands.hpp" #include "Megamix/Types.hpp" + +#endif \ No newline at end of file diff --git a/include/Megamix/Error.hpp b/include/Megamix/Error.hpp index e5060a3..1fd3102 100644 --- a/include/Megamix/Error.hpp +++ b/include/Megamix/Error.hpp @@ -1,6 +1,7 @@ #ifndef MEGAMIX_ERROR_HPP #define MEGAMIX_ERROR_HPP +#include #include #include @@ -8,6 +9,8 @@ using CTRPluginFramework::Process; +struct Void{}; + namespace Megamix { std::string ErrorMessage(int code); @@ -20,6 +23,16 @@ namespace Megamix { Short, }; + inline void panic(std::string info) { + CTRPluginFramework::MessageBox( + "Unrecoverable internal error!", + info, + CTRPluginFramework::DialogType::DialogOk, + CTRPluginFramework::ClearScreen::Both + )(); + Process::ReturnToHomeMenu(); + } + struct ShortCrashInfo { CrashType type; u8 region; diff --git a/include/Megamix/Region.hpp b/include/Megamix/Region.hpp index 8af09b0..7e9c32c 100644 --- a/include/Megamix/Region.hpp +++ b/include/Megamix/Region.hpp @@ -1,15 +1,54 @@ #ifndef RHMREGION_H #define RHMREGION_H +#include +#include +#include #include #include #include "types.h" -#include "Megamix.hpp" +#include "Megamix/Error.hpp" +#include "Megamix/Types.hpp" extern u8 region; +namespace Megamix { + struct GameInterface { + u32 gameCode; + u32 revision; // for potential future use? + const char* regionName; + + // general code regions + u32 textEnd; + u32 rodataEnd; + u32 dataEnd; + u32 bssEnd; + }; + + // Rhythm Tengoku: The Best + (Japan) (0004000000155a00) (rev0) + extern const GameInterface jpCode; + // Rhythm Heaven Megamix (Americas) (000400000018a400) (rev0) + extern const GameInterface usCode; + // Rhythm Paradise Megamix (Europe) (000400000018a500) (rev0) + extern const GameInterface euCode; + // Rhythm Sesang: The Best + (Korea) (000400000018a600) (rev0) + extern const GameInterface krCode; + + extern const GameInterface* pointers; + + std::expected initGameInterface(u32 gameCode); + + namespace Game { + inline u32 _textEnd () { return pointers->textEnd; } + inline u32 _rodataEnd() { return pointers->rodataEnd; } + inline u32 _dataEnd() { return pointers->dataEnd; } + inline u32 _bssEnd() { return pointers->dataEnd; } + } +} + + namespace Region { enum { @@ -44,11 +83,6 @@ namespace Region { u32 SeqTempoHookFunc(); u32 AllTempoHookFunc(); - u32 TextEnd(); - u32 RodataEnd(); - u32 DataEnd(); - u32 BssEnd(); - u32 TickflowCommandsSwitch(); u32 TickflowCommandsEnd(); u32 TickflowAsyncSubLocation(); diff --git a/src/Megamix/Error.cpp b/src/Megamix/Error.cpp index 3dce500..0614976 100644 --- a/src/Megamix/Error.cpp +++ b/src/Megamix/Error.cpp @@ -4,6 +4,7 @@ #include #include "Megamix.hpp" +#include "Megamix/Region.hpp" using CTRPluginFramework::Process; using CTRPluginFramework::Utils; @@ -65,13 +66,13 @@ namespace Megamix { static const char* MemSection(u32 far) { if (far < 0x00100000) { return "NULL"; - } else if (far < Region::TextEnd()) { + } else if (far < Game::_textEnd()) { return "TEXT"; - } else if (far < Region::RodataEnd()) { + } else if (far < Game::_rodataEnd()) { return "RODA"; - } else if (far < Region::DataEnd()) { + } else if (far < Game::_dataEnd()) { return "DATA"; - } else if (far < Region::BssEnd()) { + } else if (far < Game::_bssEnd()) { return "BSSO"; } else if (far >= 0x06000000 && far < 0x07000000) { return "SLWM"; @@ -166,7 +167,7 @@ namespace Megamix { for (int i = 0; i < CALL_STACK_SIZE; i++) { while ((u32)stack >= 0x06000000 || (u32)(stack + stack_offset) < 0x01000000) { u32 val = *(u32*)(regs->sp + stack_offset); - if ((val >= 0x0010000 && val < Region::TextEnd() || (val >= (u32)_start && val < _TEXT_END))) { + if ((val >= 0x0010000 && val < Game::_textEnd() || (val >= (u32)_start && val < _TEXT_END))) { crash.info.callStack[i] = val; stack_offset += 4; break; diff --git a/src/Megamix/Region.cpp b/src/Megamix/Region.cpp index 60832e8..5007909 100644 --- a/src/Megamix/Region.cpp +++ b/src/Megamix/Region.cpp @@ -3,8 +3,81 @@ #include "Megamix.hpp" +#include + u8 region; +namespace Megamix { + const GameInterface* pointers = nullptr; + + std::expected initGameInterface(u32 gameCode) { + switch (gameCode) { + case 0x155a00: + pointers = &jpCode; + break; + case 0x18a400: + pointers = &usCode; + break; + case 0x18a500: + pointers = &euCode; + break; + case 0x18a600: + pointers = &krCode; + break; + default: + return std::unexpected(gameCode); + } + return {}; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic error "-Wmissing-field-initializers" + const GameInterface jpCode = { + 0x155a00, + 0, + "Japan", + + 0x39a000, + 0x518000, + 0x540754, + 0x5ce1f0, + }; + + const GameInterface usCode = { + 0x18a400, + 0, + "Americas", + + 0x39a000, + 0x521000, + 0x54f074, + 0x5dc2f0, + }; + + const GameInterface euCode = { + 0x18a500, + 0, + "Europe", + + 0x39a000, + 0x521000, + 0x54f16c, + 0x5dc2f0, + }; + + const GameInterface krCode = { + 0x18a600, + 0, + "Korea", + + 0x39a000, + 0x521000, + 0x54f16c, //TODO: check + 0x5dc2f0, + }; +#pragma GCC diagnostic pop +} + namespace Region { u8 FromCode(u32 code) { switch (code) { @@ -245,52 +318,6 @@ namespace Region { } } - // Code sections - - u32 TextEnd() { - return 0x39a000; - } - - u32 RodataEnd() { - switch (region) { - case JP: - return 0x518000; - case US: - case EU: - case KR: - return 0x521000; - default: - return 0; - } - } - - u32 DataEnd() { - switch (region) { - case JP: - return 0x540754; - case US: - return 0x54f074; - case EU: - return 0x54f16c; - case KR: - default: - return 0; - } - } - - u32 BssEnd() { - switch (region) { - case JP: - return 0x5ce1f0; - case US: - return 0x5dc2f0; - case EU: - case KR: - return 0x5dc2f0; - default: - return 0; - } - } // Various locations used for the Tickflow Command flow diff --git a/src/main.cpp b/src/main.cpp index 89348a8..651e060 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,8 @@ #include <3ds.h> #include +#include +#include "Megamix/Region.hpp" #include "csvc.h" #include "external/plgldr.h" @@ -94,7 +96,17 @@ void ctrpf::PatchProcess(ctrpf::FwkSettings &settings) { } // Init region and config - region = Region::FromCode(ctrpf::Process::GetTitleID()); //TODO: what if US code in JP? + auto region_res = Megamix::initGameInterface(ctrpf::Process::GetTitleID()); + if (!region_res.has_value()) { + MessageBox( + "panic!", + "what the hell how did you get this\nyou're running saltwater on something that isn't megamix", + DialogType::DialogOk, + ClearScreen::Both + )(); + Process::ReturnToHomeMenu(); + } + region = Region::FromCode(ctrpf::Process::GetTitleID()); //TODO: remove config = Config::FromFile(MEGAMIX_CONFIG_PATH); // Remix retry sub patch From a740f22d154fb00a81bb929606383e3ac28387a8 Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Tue, 2 Sep 2025 22:29:30 +0200 Subject: [PATCH 02/13] refactor to region rework: RHMPatch tables, tickflow hooks --- Makefile | 2 +- include/Megamix/Region.hpp | 36 ++++++-- include/Megamix/Types.hpp | 30 ++++++ src/Megamix/Error.cpp | 2 +- src/Megamix/Hooks.cpp | 13 +-- src/Megamix/Region.cpp | 182 +++++++++++++------------------------ 6 files changed, 132 insertions(+), 133 deletions(-) diff --git a/Makefile b/Makefile index b5b662b..0bf81fc 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ SOURCES := src src/Megamix src/external #src/Helpers RELEASE ?= 0 FLATPAK := 0 -BARISTA_DIR ?= ../Barista/Barista +BARISTA_DIR ?= ../Barista # Assume flatpak ifeq ($(shell which citra 2> /dev/null || which citra-qt 2> /dev/null || true),) diff --git a/include/Megamix/Region.hpp b/include/Megamix/Region.hpp index 7e9c32c..2597d4f 100644 --- a/include/Megamix/Region.hpp +++ b/include/Megamix/Region.hpp @@ -21,10 +21,26 @@ namespace Megamix { const char* regionName; // general code regions + u32 textEnd; u32 rodataEnd; u32 dataEnd; u32 bssEnd; + + // game definitions + + GameDef* gameTable; + GateGameDef* gateTable; + + // tickflow loading + + u32 tickflowHookPos; + u32 gateHookPos; + u32 gatePracHookPos; + + // music tempo + + TempoTable* tempoTable; }; // Rhythm Tengoku: The Best + (Japan) (0004000000155a00) (rev0) @@ -45,6 +61,18 @@ namespace Megamix { inline u32 _rodataEnd() { return pointers->rodataEnd; } inline u32 _dataEnd() { return pointers->dataEnd; } inline u32 _bssEnd() { return pointers->dataEnd; } + + inline const char* _regionName() { return pointers->regionName; } + + inline GameDef* gGameTable() { return pointers->gameTable; } + inline GateGameDef* gGateTable() { return pointers->gateTable; } + inline TempoTable* gTempoTable() { return pointers->tempoTable; } + + namespace Hooks { + inline u32 tickflow() { return pointers->tickflowHookPos; } + inline u32 gate() { return pointers->gateHookPos; } + inline u32 gatePractice() { return pointers->gatePracHookPos; } + } } } @@ -71,14 +99,6 @@ namespace Region { std::vector MuseumRowsR1Cmps(); std::vector MuseumRowsR8Cmps(); - u32 GameTable(); - u32 TempoTable(); - u32 GateTable(); - - u32 TickflowHookFunc(); - u32 GateHookFunc(); - u32 GatePracHookFunc(); - u32 StrmTempoHookFunc(); u32 SeqTempoHookFunc(); u32 AllTempoHookFunc(); diff --git a/include/Megamix/Types.hpp b/include/Megamix/Types.hpp index 34171ae..ced1826 100644 --- a/include/Megamix/Types.hpp +++ b/include/Megamix/Types.hpp @@ -6,6 +6,36 @@ #include "types.h" namespace Megamix { + struct GameDef { + u8 console; + void* tfStart; + void* tfAsset; + char16_t* prologueArc; + char16_t* epilogueArc; + char* titleEntry; + char* infoEntry; + char* scoreHiEntry; + char* scoreOkEntry; + char* scoreNgEntry; + u32 prologueSfx; + u32 x2C; + u8 x30; + u8 x31; + u8 x32; + }; + + struct GateGameDef { + u8 console; + void* tfStart; + void* tfGatePractice; + char16_t* prologueArc; + char* titleEntry; + char* infoEntry; + char* unkEntry; + u32 prologueSfx; + u32 x20; + }; + struct Tempo { float beats; u32 time; diff --git a/src/Megamix/Error.cpp b/src/Megamix/Error.cpp index 0614976..aeb498b 100644 --- a/src/Megamix/Error.cpp +++ b/src/Megamix/Error.cpp @@ -217,7 +217,7 @@ namespace Megamix { u32 posY = 20; - posY = screen.Draw("Debug info", 20, posY); + posY = screen.Draw(Utils::Format("Debug info (%s)", Game::_regionName()), 20, posY); posY = screen.Draw(Utils::Format("@ %08x -> %08x (@ PC -> LR)", regs->pc, regs->lr), 20, posY); posY = screen.Draw(Utils::Format("Exception type %d", info->type), 20, posY); diff --git a/src/Megamix/Hooks.cpp b/src/Megamix/Hooks.cpp index 6c0241d..5a86e96 100644 --- a/src/Megamix/Hooks.cpp +++ b/src/Megamix/Hooks.cpp @@ -1,6 +1,7 @@ #include <3ds.h> #include +#include "Megamix/Region.hpp" #include "external/rt.h" #include "Megamix.hpp" @@ -33,7 +34,7 @@ namespace Megamix::Hooks { OSD::Notify(CTRPluginFramework::Utils::Format("Error: %s", Megamix::ErrorMessage(result).c_str())); } } - return *(void**)(Region::GameTable() + index * 0x34 + 4); // og code + return Game::gGameTable()[index].tfStart; // og code } void* getGateTickflowOffset(int index) { @@ -45,7 +46,7 @@ namespace Megamix::Hooks { OSD::Notify(CTRPluginFramework::Utils::Format("Error: %s", Megamix::ErrorMessage(result).c_str())); } } - return *(void**)(Region::GateTable() + index * 0x24 + 4); // og code + return Game::gGateTable()[index].tfStart; // og code } void* getGatePracticeTickflowOffset(int index) { @@ -57,7 +58,7 @@ namespace Megamix::Hooks { OSD::Notify(CTRPluginFramework::Utils::Format("Error: %s", Megamix::ErrorMessage(result).c_str())); } } - return *(void**)(Region::GateTable() + index * 0x24 + 8); // og code + return Game::gGateTable()[index].tfGatePractice; // og code } TempoTable* getTempoStrm(Megamix::CSoundManager* this_, u32 id) { @@ -117,11 +118,11 @@ namespace Megamix::Hooks { void TickflowHooks() { - rtInitHook(&tickflowHook, Region::TickflowHookFunc(), (u32)getTickflowOffset); + rtInitHook(&tickflowHook, Game::Hooks::tickflow(), (u32)getTickflowOffset); rtEnableHook(&tickflowHook); - rtInitHook(&gateHook, Region::GateHookFunc(), (u32)getGateTickflowOffset); + rtInitHook(&gateHook, Game::Hooks::gate(), (u32)getGateTickflowOffset); rtEnableHook(&gateHook); - rtInitHook(&gatePracHook, Region::GatePracHookFunc(), (u32)getGatePracticeTickflowOffset); + rtInitHook(&gatePracHook, Game::Hooks::gatePractice(), (u32)getGatePracticeTickflowOffset); rtEnableHook(&gatePracHook); } diff --git a/src/Megamix/Region.cpp b/src/Megamix/Region.cpp index 5007909..9a8f36f 100644 --- a/src/Megamix/Region.cpp +++ b/src/Megamix/Region.cpp @@ -2,6 +2,7 @@ #include #include "Megamix.hpp" +#include "Megamix/Types.hpp" #include @@ -33,47 +34,79 @@ namespace Megamix { #pragma GCC diagnostic push #pragma GCC diagnostic error "-Wmissing-field-initializers" const GameInterface jpCode = { - 0x155a00, - 0, - "Japan", - - 0x39a000, - 0x518000, - 0x540754, - 0x5ce1f0, + .gameCode= 0x155a00, + .revision= 0, + .regionName="Japan", + + .textEnd= 0x39a000, + .rodataEnd= 0x518000, + .dataEnd= 0x540754, + .bssEnd= 0x5ce1f0, + + .gameTable= (GameDef*)0x522498, + .gateTable= (GateGameDef*)0x525488, + .tickflowHookPos= 0x25a1b4, + .gateHookPos= 0x242510, + .gatePracHookPos= 0x32e01c, + + .tempoTable= (TempoTable*)0x5324B0, }; const GameInterface usCode = { - 0x18a400, - 0, - "Americas", - - 0x39a000, - 0x521000, - 0x54f074, - 0x5dc2f0, + .gameCode= 0x18a400, + .revision= 0, + .regionName="Americas", + + .textEnd= 0x39a000, + .rodataEnd= 0x521000, + .dataEnd= 0x54f074, + .bssEnd= 0x5dc2f0, + + .gameTable= (GameDef*)0x52b498, + .gateTable= (GateGameDef*)0x52e488, + .tickflowHookPos= 0x258df4, + .gateHookPos= 0x240f9c, + .gatePracHookPos= 0x32d630, + + .tempoTable= (TempoTable*)0x53EF54, }; const GameInterface euCode = { - 0x18a500, - 0, - "Europe", - - 0x39a000, - 0x521000, - 0x54f16c, - 0x5dc2f0, + .gameCode= 0x18a500, + .revision= 0, + .regionName="Europe", + + .textEnd= 0x39a000, + .rodataEnd= 0x521000, + .dataEnd= 0x54f16c, + .bssEnd= 0x5dc2f0, + + .gameTable= (GameDef*)0x52b498, + .gateTable= (GateGameDef*)0x52e488, + .tickflowHookPos= 0x258df4, + .gateHookPos= 0x240f9c, + .gatePracHookPos= 0x32d630, + + .tempoTable= (TempoTable*)0x53F04C, }; const GameInterface krCode = { - 0x18a600, - 0, - "Korea", - - 0x39a000, - 0x521000, - 0x54f16c, //TODO: check - 0x5dc2f0, + .gameCode= 0x18a600, + .revision= 0, + .regionName="Korea", + + .textEnd= 0x39a000, + .rodataEnd= 0x521000, + .dataEnd= 0x54f16c, //TODO: check + .bssEnd= 0x5dc2f0, + + .gameTable= (GameDef*)0x52b498, + .gateTable= (GateGameDef*)0x52e488, + .tickflowHookPos= 0x258dcc, + .gateHookPos= 0x240f74, + .gatePracHookPos= 0x32d630, + + .tempoTable= (TempoTable*)0x53F04C, }; #pragma GCC diagnostic pop } @@ -191,91 +224,6 @@ namespace Region { } } - // Tables and stuff - - u32 GameTable() { - switch (region) { - case US: - case EU: - case KR: - return 0x52b498; - case JP: - return 0x522498; - default: - return 0; - } - } - - u32 TempoTable() { - switch (region) { - case US: - return 0x53EF54; - case EU: - case KR: - return 0x53F04C; - case JP: - return 0x5324B0; - default: - return 0; - } - } - - u32 GateTable() { - switch (region) { - case US: - case EU: - case KR: - return 0x52E488; - case JP: - return 0x525488; - default: - return 0; - } - } - - // Hooks - Tickflow - - u32 TickflowHookFunc() { - switch (region) { - case JP: - return 0x25a1b4; - case US: - case EU: - return 0x258df4; - case KR: - return 0x258dcc; - default: - return 0; - } - } - - u32 GateHookFunc() { - switch (region) { - case JP: - return 0x242510; - case US: - case EU: - return 0x240f9c; - case KR: - return 0x240f74; - default: - return 0; - } - } - - u32 GatePracHookFunc() { - switch (region) { - case JP: - return 0x32e01c; - case US: - case EU: - case KR: - return 0x32d630; - default: - return 0; - } - } - // Hooks - Tempo u32 StrmTempoHookFunc() { From 0c630f81510d79b6ff399d17ff9ce630d42af0e7 Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Fri, 5 Sep 2025 20:20:06 +0200 Subject: [PATCH 03/13] refactor to region rework: tempo, museum rows --- include/Megamix/Region.hpp | 48 +++++-- src/Megamix/Hooks.cpp | 6 +- src/Megamix/Patches.cpp | 12 +- src/Megamix/Region.cpp | 257 +++++++++++++------------------------ 4 files changed, 135 insertions(+), 188 deletions(-) diff --git a/include/Megamix/Region.hpp b/include/Megamix/Region.hpp index 2597d4f..5db6259 100644 --- a/include/Megamix/Region.hpp +++ b/include/Megamix/Region.hpp @@ -2,7 +2,6 @@ #define RHMREGION_H #include -#include #include #include #include @@ -41,6 +40,23 @@ namespace Megamix { // music tempo TempoTable* tempoTable; + + u32 strmTempoHookPos; + u32 seqTempoHookPos; + u32 allTempoHookPos; + + // museum row hax + + mutable std::vector ptrsToMuseumRowInfo; // what the fuck is this language + mutable std::vector ptrsToMuseumRowColors; + u32 rowColorsInitHookPos; + mutable std::vector museumRowsR1Cmps; + mutable std::vector museumRowsR8Cmps; + + // placeholders + + static constexpr u32 NO_PTR = 0x404; // specific region has no applicable func/data + static constexpr u32 UNIMPLEMENTED = 0xdead; // specific region has applicable func/data but it's not yet implemented }; // Rhythm Tengoku: The Best + (Japan) (0004000000155a00) (rev0) @@ -52,6 +68,8 @@ namespace Megamix { // Rhythm Sesang: The Best + (Korea) (000400000018a600) (rev0) extern const GameInterface krCode; + static const GameInterface* allRegions[4] = {&jpCode, &usCode, &euCode, &krCode}; + extern const GameInterface* pointers; std::expected initGameInterface(u32 gameCode); @@ -68,10 +86,25 @@ namespace Megamix { inline GateGameDef* gGateTable() { return pointers->gateTable; } inline TempoTable* gTempoTable() { return pointers->tempoTable; } + // for hooks: feel free to drop em here, or make a namespace named hHookGroupName if you feel it needs more info namespace Hooks { inline u32 tickflow() { return pointers->tickflowHookPos; } inline u32 gate() { return pointers->gateHookPos; } inline u32 gatePractice() { return pointers->gatePracHookPos; } + + inline u32 strmTempo() { return pointers->strmTempoHookPos; } + inline u32 seqTempo() { return pointers->seqTempoHookPos; } + inline u32 allTempo() { return pointers->allTempoHookPos; } + } + + // for patches: make a namespace named pPatchName + // if there's any simple hooks (stubs for example) involved you can put em in here + namespace pMuseumRows { + inline const std::vector ptrsToInfo() { return pointers->ptrsToMuseumRowInfo; } + inline const std::vector ptrsToColors() { return pointers->ptrsToMuseumRowColors; } + inline u32 colorInitHookPos() { return pointers->rowColorsInitHookPos; } + inline const std::vector r1Cmps() { return pointers->museumRowsR1Cmps; } + inline const std::vector r8Cmps() { return pointers->museumRowsR8Cmps; } } } } @@ -89,19 +122,6 @@ namespace Region { }; u8 FromCode(u32 code); - std::string Name(); - - std::vector MuseumRowsInfoAddresses(); - std::vector MuseumRowsColorsAddresses(); - u32 MuseumRowsColorsInitFunc(); - // TODO: maybe make this into MuseumRowsCmps that returns a - // map of int -> vector other regions might use different registers - std::vector MuseumRowsR1Cmps(); - std::vector MuseumRowsR8Cmps(); - - u32 StrmTempoHookFunc(); - u32 SeqTempoHookFunc(); - u32 AllTempoHookFunc(); u32 TickflowCommandsSwitch(); u32 TickflowCommandsEnd(); diff --git a/src/Megamix/Hooks.cpp b/src/Megamix/Hooks.cpp index 5a86e96..88b1c5c 100644 --- a/src/Megamix/Hooks.cpp +++ b/src/Megamix/Hooks.cpp @@ -127,11 +127,11 @@ namespace Megamix::Hooks { } void TempoHooks() { - rtInitHook(&tempoStrmHook, Region::StrmTempoHookFunc(), (u32)getTempoStrm); + rtInitHook(&tempoStrmHook, Game::Hooks::strmTempo(), (u32)getTempoStrm); rtEnableHook(&tempoStrmHook); - rtInitHook(&tempoSeqHook, Region::SeqTempoHookFunc(), (u32)getTempoSeq); + rtInitHook(&tempoSeqHook, Game::Hooks::seqTempo(), (u32)getTempoSeq); rtEnableHook(&tempoSeqHook); - rtInitHook(&tempoAllHook, Region::AllTempoHookFunc(), (u32)getTempoAll); + rtInitHook(&tempoAllHook, Game::Hooks::allTempo(), (u32)getTempoAll); rtEnableHook(&tempoAllHook); } diff --git a/src/Megamix/Patches.cpp b/src/Megamix/Patches.cpp index 5663662..c29ef40 100644 --- a/src/Megamix/Patches.cpp +++ b/src/Megamix/Patches.cpp @@ -190,28 +190,28 @@ namespace Megamix::Patches { AddExtraRowsToFront(); // patch in new museum rows - for (auto address : Region::MuseumRowsInfoAddresses()) { + for (auto address : Game::pMuseumRows::ptrsToInfo()) { Process::Patch(address, (u32) museumRows.data()); } u32 compare_r1_instruction = // cmp r1, MUSEUM_ROW_COUNT make_cmp_immediate_instruction(1, museumRows.size()); - for (auto address : Region::MuseumRowsR1Cmps()) { + for (auto address : Game::pMuseumRows::r1Cmps()) { Process::Patch(address, compare_r1_instruction); } u32 compare_r8_instruction = // cmp r8, MUSEUM_ROW_COUNT make_cmp_immediate_instruction(8, museumRows.size()); - for (auto address : Region::MuseumRowsR8Cmps()) { + for (auto address : Game::pMuseumRows::r8Cmps()) { Process::Patch(address, compare_r8_instruction); } - Hooks::StubFunction(Region::MuseumRowsColorsInitFunc()); + Hooks::StubFunction(Game::pMuseumRows::colorInitHookPos()); - for (auto address : Region::MuseumRowsColorsAddresses()) { + for (auto address : Game::pMuseumRows::ptrsToColors()) { Process::Patch(address, (u32) museumRowColors.data()); } } -} +} // namespace Megamix::Patches diff --git a/src/Megamix/Region.cpp b/src/Megamix/Region.cpp index 9a8f36f..2431eea 100644 --- a/src/Megamix/Region.cpp +++ b/src/Megamix/Region.cpp @@ -2,35 +2,12 @@ #include #include "Megamix.hpp" -#include "Megamix/Types.hpp" #include u8 region; namespace Megamix { - const GameInterface* pointers = nullptr; - - std::expected initGameInterface(u32 gameCode) { - switch (gameCode) { - case 0x155a00: - pointers = &jpCode; - break; - case 0x18a400: - pointers = &usCode; - break; - case 0x18a500: - pointers = &euCode; - break; - case 0x18a600: - pointers = &krCode; - break; - default: - return std::unexpected(gameCode); - } - return {}; - } - #pragma GCC diagnostic push #pragma GCC diagnostic error "-Wmissing-field-initializers" const GameInterface jpCode = { @@ -49,7 +26,18 @@ namespace Megamix { .gateHookPos= 0x242510, .gatePracHookPos= 0x32e01c, - .tempoTable= (TempoTable*)0x5324B0, + .tempoTable= (TempoTable*)0x5324B0, + .strmTempoHookPos= GameInterface::NO_PTR, + .seqTempoHookPos= GameInterface::NO_PTR, + // TODO: this function combines getTempoFromTable and US' func_0024f47c, adapt the hook + .allTempoHookPos= GameInterface::UNIMPLEMENTED, // 0x25098c + + // TODO: museum rows.......... in japan + .ptrsToMuseumRowInfo= {GameInterface::UNIMPLEMENTED}, + .ptrsToMuseumRowColors= {GameInterface::UNIMPLEMENTED}, + .rowColorsInitHookPos= GameInterface::UNIMPLEMENTED, + .museumRowsR1Cmps= {GameInterface::UNIMPLEMENTED}, + .museumRowsR8Cmps= {GameInterface::UNIMPLEMENTED}, }; const GameInterface usCode = { @@ -68,7 +56,21 @@ namespace Megamix { .gateHookPos= 0x240f9c, .gatePracHookPos= 0x32d630, - .tempoTable= (TempoTable*)0x53EF54, + .tempoTable= (TempoTable*)0x53EF54, + .strmTempoHookPos= 0x276424, + .seqTempoHookPos= 0x2763c8, + .allTempoHookPos= 0x203c08, + + .ptrsToMuseumRowInfo = { + 0x2421e8, 0x24c480, 0x24d408, // gRowInfo + 0x1979e0, 0x1983fc, 0x242350, 0x2424bc, 0x24e010, // gRowInfo1 + 0x2423d4, 0x2619c0, // gRowInfo2 + 0x224fe8, 0x225008, 0x225024, 0x225044, 0x225068, // gRowInfo3 + }, + .ptrsToMuseumRowColors = { 0x17d8b4, 0x17e318, 0x17e4c4, 0x241fc4, 0x38e6d8 }, + .rowColorsInitHookPos = 0x38de58, + .museumRowsR1Cmps = { 0x2423c4, 0x2423DC, 0x2619B0 }, + .museumRowsR8Cmps = { 0x242400, 0x2424a0 }, }; const GameInterface euCode = { @@ -87,7 +89,16 @@ namespace Megamix { .gateHookPos= 0x240f9c, .gatePracHookPos= 0x32d630, - .tempoTable= (TempoTable*)0x53F04C, + .tempoTable= (TempoTable*)0x53F04C, + .strmTempoHookPos= 0x276424, + .seqTempoHookPos= 0x2763c8, + .allTempoHookPos= 0x203c08, + + .ptrsToMuseumRowInfo = usCode.ptrsToMuseumRowInfo, + .ptrsToMuseumRowColors = usCode.ptrsToMuseumRowColors, + .rowColorsInitHookPos = 0x38de58, + .museumRowsR1Cmps = usCode.museumRowsR1Cmps, + .museumRowsR8Cmps = usCode.museumRowsR8Cmps, }; const GameInterface krCode = { @@ -106,167 +117,83 @@ namespace Megamix { .gateHookPos= 0x240f74, .gatePracHookPos= 0x32d630, - .tempoTable= (TempoTable*)0x53F04C, - }; -#pragma GCC diagnostic pop -} + .tempoTable= (TempoTable*)0x53F04C, + .strmTempoHookPos= 0x276424, + .seqTempoHookPos= 0x2763c8, + .allTempoHookPos= 0x203c08, -namespace Region { - u8 FromCode(u32 code) { - switch (code) { - case 0x155a00: - return JP; - case 0x18a400: - return US; - case 0x18a500: - return EU; - case 0x18a600: - return KR; - default: - return UNK; - } - } - std::string Name() { - switch (region) { - case US: return "US"; - case EU: return "EU"; - case JP: return "JP"; - case KR: return "KR"; - default: return "UNK"; - } - } - - // Extra museum rows patch - - std::vector MuseumRowsInfoAddresses() { - switch (region) { - case US: - case EU: - return { - 0x2421e8, 0x24c480, 0x24d408, // gRowInfo - 0x1979e0, 0x1983fc, 0x242350, 0x2424bc, 0x24e010, // gRowInfo1 - 0x2423d4, 0x2619c0, // gRowInfo2 - 0x224fe8, 0x225008, 0x225024, 0x225044, 0x225068, // gRowInfo3 - }; - case KR: - return { + .ptrsToMuseumRowInfo = { 0x2421c0, 0x24c458, 0x24d3e0, // gRowInfo 0x1979e0, 0x1983fc, 0x242328, 0x242494, 0x24dfe8, // gRowInfo1 0x2423ac, 0x261998, // gRowInfo2 0x224fe8, 0x225008, 0x225024, 0x225044, 0x225068, // gRowInfo3 - }; - - // TODO: - case JP: - - default: return {}; - } - } - - u32 MuseumRowsColorsInitFunc() { - switch (region) { - case US: - case EU: - case KR: - return 0x38de58; - - // TODO: - case JP: - - default: return {}; - } - } - - std::vector MuseumRowsColorsAddresses() { - switch (region) { - case US: - case EU: - return { 0x17d8b4, 0x17e318, 0x17e4c4, 0x241fc4, 0x38e6d8 }; - case KR: - return { 0x17d8b4, 0x17e318, 0x17e4c4, 0x241f9c, 0x38e6d8 }; - - // TODO: - case JP: - - default: return {}; - } - } - - std::vector MuseumRowsR1Cmps() { - switch (region) { - case US: - case EU: - return { 0x2423c4, 0x2423DC, 0x2619B0 }; + }, + .ptrsToMuseumRowColors = { 0x17d8b4, 0x17e318, 0x17e4c4, 0x241fc4, 0x38e6d8 }, + .rowColorsInitHookPos = 0x38de58, + .museumRowsR1Cmps = { 0x24239c, 0x2423b4, 0x261988 }, + .museumRowsR8Cmps = { 0x2423d8, 0x242478 }, + }; +#pragma GCC diagnostic pop - case KR: - return { 0x24239c, 0x2423b4, 0x261988 }; + // everything down here is initializing code - don't pay it any mind unless you have any struct that allocates heap mem - // TODO: - case JP: + const GameInterface* pointers = nullptr; - default: return {}; + std::expected initGameInterface(u32 gameCode) { + switch (gameCode) { + case 0x155a00: + pointers = &jpCode; + break; + case 0x18a400: + pointers = &usCode; + break; + case 0x18a500: + pointers = &euCode; + break; + case 0x18a600: + pointers = &krCode; + break; + default: + return std::unexpected(gameCode); } - } - - std::vector MuseumRowsR8Cmps() { - switch (region) { - case US: - case EU: - return { 0x242400, 0x2424a0 }; - case KR: - return { 0x2423d8, 0x242478 }; - //TODO: - case JP: + // return memory by freeing all heap data in the unused GameInterfaces - default: return {}; - } - } + for (auto region: allRegions) { + if (region == pointers) continue; - // Hooks - Tempo + auto free_vec = [](std::vector vec){ + vec.resize(0); + vec.shrink_to_fit(); + }; - u32 StrmTempoHookFunc() { - switch (region) { - // can't seem to find a JP equivalent? - case US: - case EU: - return 0x276424; - case KR: - return 0x2763fc; - default: - return 0; + free_vec(region->ptrsToMuseumRowInfo); + free_vec(region->ptrsToMuseumRowColors); + free_vec(region->museumRowsR1Cmps); + free_vec(region->museumRowsR8Cmps); } - } - u32 SeqTempoHookFunc() { - switch (region) { - // can't seem to find a JP equivalent? - case US: - case EU: - return 0x2763c8; - case KR: - return 0x2763a0; - default: - return 0; - } + return {}; } +} - u32 AllTempoHookFunc() { - switch (region) { - case JP: - // TODO: this function combines getTempoFromTable and US' func_0024f47c, adapt the hook - return 0x25098c; - case US: - case EU: - case KR: - return 0x203c08; +namespace Region { + u8 FromCode(u32 code) { + switch (code) { + case 0x155a00: + return JP; + case 0x18a400: + return US; + case 0x18a500: + return EU; + case 0x18a600: + return KR; default: - return 0; + return UNK; } } - // Various locations used for the Tickflow Command flow u32 TickflowCommandsSwitch() { From 49ee4f2a59209b286dff861737e76ce07acee72d Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Fri, 5 Sep 2025 20:27:11 +0200 Subject: [PATCH 04/13] actually use the panic function --- src/main.cpp | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 651e060..d516f44 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include #include +#include "Megamix/Error.hpp" #include "Megamix/Region.hpp" #include "csvc.h" #include "external/plgldr.h" @@ -98,13 +99,7 @@ void ctrpf::PatchProcess(ctrpf::FwkSettings &settings) { // Init region and config auto region_res = Megamix::initGameInterface(ctrpf::Process::GetTitleID()); if (!region_res.has_value()) { - MessageBox( - "panic!", - "what the hell how did you get this\nyou're running saltwater on something that isn't megamix", - DialogType::DialogOk, - ClearScreen::Both - )(); - Process::ReturnToHomeMenu(); + Megamix::panic("what the hell how did you get this\nyou're running saltwater on something that isn't megamix"); } region = Region::FromCode(ctrpf::Process::GetTitleID()); //TODO: remove config = Config::FromFile(MEGAMIX_CONFIG_PATH); @@ -159,7 +154,7 @@ void InitMenu(ctrpf::PluginMenu &menu) { }); menu += new ctrpf::MenuEntry("Force a crash (data)", nullptr, [](ctrpf::MenuEntry *entry) { - *(int*)nullptr = 100; + *(volatile int*)nullptr = 100; }); } #endif @@ -169,8 +164,7 @@ int ctrpf::main(void) { Process::exceptionCallback = Megamix::CrashHandler; if (params.barista != 0xD06) { - ctrpf::MessageBox("Barista not used!", "You must run Saltwater from the Barista launcher!")(); - Process::ReturnToHomeMenu(); + Megamix::panic("You seem to be running Saltwater as a regular CTRPlugin. You must run Saltwater from the Barista launcher!"); } #ifdef RELEASE From efde8e034a9ffd4f3956606edc9af616794b13d5 Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Fri, 5 Sep 2025 21:14:17 +0200 Subject: [PATCH 05/13] refactor to region rework: retry remix (also make that better), global data / mgrs --- include/Megamix/Patches.hpp | 1 + include/Megamix/Region.hpp | 40 ++++++----- src/Megamix/Commands.cpp | 18 ++--- src/Megamix/Patches.cpp | 23 ++++++- src/Megamix/Region.cpp | 131 ++++++++++++++---------------------- src/main.cpp | 20 +++--- 6 files changed, 115 insertions(+), 118 deletions(-) diff --git a/include/Megamix/Patches.hpp b/include/Megamix/Patches.hpp index 221b482..47edf0c 100644 --- a/include/Megamix/Patches.hpp +++ b/include/Megamix/Patches.hpp @@ -3,6 +3,7 @@ namespace Megamix::Patches { void PatchMuseumExtraRows(); + void PatchRetryRemix(); } #endif diff --git a/include/Megamix/Region.hpp b/include/Megamix/Region.hpp index 5db6259..14b65bf 100644 --- a/include/Megamix/Region.hpp +++ b/include/Megamix/Region.hpp @@ -36,6 +36,7 @@ namespace Megamix { u32 tickflowHookPos; u32 gateHookPos; u32 gatePracHookPos; + mutable std::vector ptrsToRetryRemix; // music tempo @@ -47,13 +48,20 @@ namespace Megamix { // museum row hax - mutable std::vector ptrsToMuseumRowInfo; // what the fuck is this language - mutable std::vector ptrsToMuseumRowColors; - u32 rowColorsInitHookPos; - mutable std::vector museumRowsR1Cmps; - mutable std::vector museumRowsR8Cmps; + mutable std::vector ptrsToMuseumRowInfo; // what the fuck is this language + mutable std::vector ptrsToMuseumRowColors; + u32 rowColorsInitHookPos; + mutable std::vector museumRowsR1Cmps; + mutable std::vector museumRowsR8Cmps; - // placeholders + // some common globals / managers + + CSaveData** saveData; + CInputManager** inputManager; + CFileManager** fileManager; + + + // placeholders for missing/unimplemented values static constexpr u32 NO_PTR = 0x404; // specific region has no applicable func/data static constexpr u32 UNIMPLEMENTED = 0xdead; // specific region has applicable func/data but it's not yet implemented @@ -86,7 +94,11 @@ namespace Megamix { inline GateGameDef* gGateTable() { return pointers->gateTable; } inline TempoTable* gTempoTable() { return pointers->tempoTable; } - // for hooks: feel free to drop em here, or make a namespace named hHookGroupName if you feel it needs more info + inline CSaveData* gSaveData() { return *pointers->saveData; } + inline CInputManager* gInputManager() { return *pointers->inputManager; } + inline CFileManager* gFileManager() { return *pointers->fileManager; } + + // for hooks: feel free to drop em here, or make a namespace named hHookGroupName if you feel it needs more context namespace Hooks { inline u32 tickflow() { return pointers->tickflowHookPos; } inline u32 gate() { return pointers->gateHookPos; } @@ -97,8 +109,12 @@ namespace Megamix { inline u32 allTempo() { return pointers->allTempoHookPos; } } - // for patches: make a namespace named pPatchName - // if there's any simple hooks (stubs for example) involved you can put em in here + // for patches: feel free to drop em here, or make a namespace named pPatchName if you feel it needs more context + namespace Patches { + inline const std::vector ptrsToRetryRemix() { return pointers->ptrsToRetryRemix; } + } + + // if there's any simple hooks (stubs for example) involved you can put em in the patch namespace namespace pMuseumRows { inline const std::vector ptrsToInfo() { return pointers->ptrsToMuseumRowInfo; } inline const std::vector ptrsToColors() { return pointers->ptrsToMuseumRowColors; } @@ -127,12 +143,6 @@ namespace Region { u32 TickflowCommandsEnd(); u32 TickflowAsyncSubLocation(); - u32 GlobalSaveDataPointer(); - u32 GlobalInputManagerPointer(); - u32 GlobalFileManagerPointer(); - - std::vector RetryRemixLocs(); - u32 RegionFSHookFunc(); u32 RegionOtherHookFunc(); diff --git a/src/Megamix/Commands.cpp b/src/Megamix/Commands.cpp index 8056766..0f02cfd 100644 --- a/src/Megamix/Commands.cpp +++ b/src/Megamix/Commands.cpp @@ -2,6 +2,7 @@ #include #include "Megamix.hpp" +#include "Megamix/Region.hpp" using CTRPluginFramework::OSD; using CTRPluginFramework::Utils; @@ -50,21 +51,22 @@ namespace Megamix { self->condvar = 0; return; } else if (arg0 == 2) { - CSaveData** gSaveData = (CSaveData**)Region::GlobalSaveDataPointer(); // Here, arg0 gets replaced by the playstyle - 0 for buttons, 1 for tap - Results in playstyle-dependant reading - arg0 = (u32)(*gSaveData)->fileData[(*gSaveData)->currentFile].playStyle; + arg0 = (u32)Game::gSaveData()->fileData[Game::gSaveData()->currentFile].playStyle; } - CInputManager** gInputManager = (CInputManager**)Region::GlobalInputManagerPointer(); if (arg0 == 0) { + // set condvar to 1 if button with flag = 2<= 32) { // We're working with a 32-bit integer here, so flags are limited to bits 1-31 self->condvar = 0; return; } - self->condvar = ((u32)(*gInputManager)->padHandler->holdButtons >> args[0]) & 1; + self->condvar = ((u32)Game::gInputManager()->padHandler->holdButtons >> args[0]) & 1; } else { - self->condvar = ((*gInputManager)->touchPanelHandler->touchPanelStatus.touch); + // set condvar to 1 if screen is pressed + self->condvar = Game::gInputManager()->touchPanelHandler->touchPanelStatus.touch; } } @@ -78,14 +80,12 @@ namespace Megamix { void languageCheck(CTickflow* self, u32 arg0, u32* args) { if (arg0 != 0) return; - CSaveData** gSaveData = (CSaveData**)Region::GlobalSaveDataPointer(); - CFileManager** gFileManager = (CFileManager**)Region::GlobalFileManagerPointer(); - int saveLanguage = (*gSaveData)->fileData[(*gSaveData)->currentFile].locale; + int saveLanguage = Game::gSaveData()->fileData[Game::gSaveData()->currentFile].locale; if(saveLanguage == 1){ self->condvar = 0; } else { wchar_t sublocale[5]; - utf16_to_utf32((u32*)sublocale, (*gFileManager)->sublocale, 4); + utf16_to_utf32((u32*)sublocale, Game::gFileManager()->sublocale, 4); sublocale[4] = '\0'; std::wstring localews(sublocale); if(localews.find(L"JP") != (unsigned int)-1){ diff --git a/src/Megamix/Patches.cpp b/src/Megamix/Patches.cpp index c29ef40..aa2034e 100644 --- a/src/Megamix/Patches.cpp +++ b/src/Megamix/Patches.cpp @@ -8,6 +8,7 @@ #include "Megamix.hpp" #include "Config.hpp" +#include "Megamix/Region.hpp" namespace Megamix::Patches { std::vector museumRows { @@ -76,7 +77,7 @@ namespace Megamix::Patches { // see section F5.1.35 in the arm A-profile reference manual // register is 4 bits, value is 12 bits - u32 make_cmp_immediate_instruction(u32 reg, u32 value) { + constexpr u32 make_cmp_immediate_instruction(u32 reg, u32 value) { // 1110 == no condition const u32 cond = 0b1110 << 28; const u32 cmp_imm_base = 0b0000'00110'10'1 << 20; @@ -214,4 +215,24 @@ namespace Megamix::Patches { Process::Patch(address, (u32) museumRowColors.data()); } } + + // see section F5.1.122 in the arm A-profile reference manual + // register is 4 bits, value is 12 bits + constexpr u32 make_mov_immediate_instruction(u32 reg, u32 value) { + // 1110 == no condition + const u32 cond = 0b1110 << 28; + const u32 cmp_imm_base = 0b0000'00111'01'0 << 20; + reg <<= 12; + + return cond | cmp_imm_base | reg | value; + } + + void PatchRetryRemix() { + u32 instr = // mov r2, #0xE + make_mov_immediate_instruction(2, 0xE); + + for (auto loc: Game::Patches::ptrsToRetryRemix()){ + Process::Patch(loc, instr); + } + } } // namespace Megamix::Patches diff --git a/src/Megamix/Region.cpp b/src/Megamix/Region.cpp index 2431eea..319eb97 100644 --- a/src/Megamix/Region.cpp +++ b/src/Megamix/Region.cpp @@ -1,7 +1,9 @@ +#include "Megamix/Region.hpp" #include <3ds.h> #include #include "Megamix.hpp" +#include "Megamix/Types.hpp" #include @@ -20,11 +22,12 @@ namespace Megamix { .dataEnd= 0x540754, .bssEnd= 0x5ce1f0, - .gameTable= (GameDef*)0x522498, - .gateTable= (GateGameDef*)0x525488, - .tickflowHookPos= 0x25a1b4, - .gateHookPos= 0x242510, - .gatePracHookPos= 0x32e01c, + .gameTable= (GameDef*)0x522498, + .gateTable= (GateGameDef*)0x525488, + .tickflowHookPos= 0x25a1b4, + .gateHookPos= 0x242510, + .gatePracHookPos= 0x32e01c, + .ptrsToRetryRemix= {0x19a180, 0x16485c, 0x1f8e78}, .tempoTable= (TempoTable*)0x5324B0, .strmTempoHookPos= GameInterface::NO_PTR, @@ -38,6 +41,11 @@ namespace Megamix { .rowColorsInitHookPos= GameInterface::UNIMPLEMENTED, .museumRowsR1Cmps= {GameInterface::UNIMPLEMENTED}, .museumRowsR8Cmps= {GameInterface::UNIMPLEMENTED}, + + // TODO: any of these could have been made incompatible by version differences + .saveData= (CSaveData**)GameInterface::UNIMPLEMENTED, + .inputManager= (CInputManager**)GameInterface::UNIMPLEMENTED, + .fileManager= (CFileManager**)GameInterface::UNIMPLEMENTED, }; const GameInterface usCode = { @@ -50,27 +58,32 @@ namespace Megamix { .dataEnd= 0x54f074, .bssEnd= 0x5dc2f0, - .gameTable= (GameDef*)0x52b498, - .gateTable= (GateGameDef*)0x52e488, - .tickflowHookPos= 0x258df4, - .gateHookPos= 0x240f9c, - .gatePracHookPos= 0x32d630, + .gameTable= (GameDef*)0x52b498, + .gateTable= (GateGameDef*)0x52e488, + .tickflowHookPos= 0x258df4, + .gateHookPos= 0x240f9c, + .gatePracHookPos= 0x32d630, + .ptrsToRetryRemix= {0x198c9c, 0x16302c, 0x1f7f84}, .tempoTable= (TempoTable*)0x53EF54, .strmTempoHookPos= 0x276424, .seqTempoHookPos= 0x2763c8, .allTempoHookPos= 0x203c08, - .ptrsToMuseumRowInfo = { + .ptrsToMuseumRowInfo= { 0x2421e8, 0x24c480, 0x24d408, // gRowInfo 0x1979e0, 0x1983fc, 0x242350, 0x2424bc, 0x24e010, // gRowInfo1 0x2423d4, 0x2619c0, // gRowInfo2 0x224fe8, 0x225008, 0x225024, 0x225044, 0x225068, // gRowInfo3 }, - .ptrsToMuseumRowColors = { 0x17d8b4, 0x17e318, 0x17e4c4, 0x241fc4, 0x38e6d8 }, - .rowColorsInitHookPos = 0x38de58, - .museumRowsR1Cmps = { 0x2423c4, 0x2423DC, 0x2619B0 }, - .museumRowsR8Cmps = { 0x242400, 0x2424a0 }, + .ptrsToMuseumRowColors= { 0x17d8b4, 0x17e318, 0x17e4c4, 0x241fc4, 0x38e6d8 }, + .rowColorsInitHookPos= 0x38de58, + .museumRowsR1Cmps= { 0x2423c4, 0x2423DC, 0x2619B0 }, + .museumRowsR8Cmps= { 0x242400, 0x2424a0 }, + + .saveData= (CSaveData**)0x54d350, + .inputManager= (CInputManager**)0x54eed0, + .fileManager= (CFileManager**)0x54eedc, }; const GameInterface euCode = { @@ -88,17 +101,22 @@ namespace Megamix { .tickflowHookPos= 0x258df4, .gateHookPos= 0x240f9c, .gatePracHookPos= 0x32d630, + .ptrsToRetryRemix= usCode.ptrsToRetryRemix, .tempoTable= (TempoTable*)0x53F04C, .strmTempoHookPos= 0x276424, .seqTempoHookPos= 0x2763c8, .allTempoHookPos= 0x203c08, - .ptrsToMuseumRowInfo = usCode.ptrsToMuseumRowInfo, - .ptrsToMuseumRowColors = usCode.ptrsToMuseumRowColors, - .rowColorsInitHookPos = 0x38de58, - .museumRowsR1Cmps = usCode.museumRowsR1Cmps, - .museumRowsR8Cmps = usCode.museumRowsR8Cmps, + .ptrsToMuseumRowInfo= usCode.ptrsToMuseumRowInfo, + .ptrsToMuseumRowColors= usCode.ptrsToMuseumRowColors, + .rowColorsInitHookPos= 0x38de58, + .museumRowsR1Cmps= usCode.museumRowsR1Cmps, + .museumRowsR8Cmps= usCode.museumRowsR8Cmps, + + .saveData= (CSaveData**)0x54d448, + .inputManager= (CInputManager**)0x54efc8, + .fileManager= (CFileManager**)0x54efd4, }; const GameInterface krCode = { @@ -116,23 +134,27 @@ namespace Megamix { .tickflowHookPos= 0x258dcc, .gateHookPos= 0x240f74, .gatePracHookPos= 0x32d630, + .ptrsToRetryRemix= usCode.ptrsToRetryRemix, .tempoTable= (TempoTable*)0x53F04C, .strmTempoHookPos= 0x276424, .seqTempoHookPos= 0x2763c8, .allTempoHookPos= 0x203c08, - - .ptrsToMuseumRowInfo = { + .ptrsToMuseumRowInfo= { 0x2421c0, 0x24c458, 0x24d3e0, // gRowInfo 0x1979e0, 0x1983fc, 0x242328, 0x242494, 0x24dfe8, // gRowInfo1 0x2423ac, 0x261998, // gRowInfo2 0x224fe8, 0x225008, 0x225024, 0x225044, 0x225068, // gRowInfo3 }, - .ptrsToMuseumRowColors = { 0x17d8b4, 0x17e318, 0x17e4c4, 0x241fc4, 0x38e6d8 }, - .rowColorsInitHookPos = 0x38de58, - .museumRowsR1Cmps = { 0x24239c, 0x2423b4, 0x261988 }, - .museumRowsR8Cmps = { 0x2423d8, 0x242478 }, + .ptrsToMuseumRowColors= { 0x17d8b4, 0x17e318, 0x17e4c4, 0x241fc4, 0x38e6d8 }, + .rowColorsInitHookPos= 0x38de58, + .museumRowsR1Cmps= { 0x24239c, 0x2423b4, 0x261988 }, + .museumRowsR8Cmps= { 0x2423d8, 0x242478 }, + + .saveData= (CSaveData**)0x54d448, + .inputManager= (CInputManager**)0x54efc8, + .fileManager= (CFileManager**)0x54efd4, }; #pragma GCC diagnostic pop @@ -168,6 +190,8 @@ namespace Megamix { vec.shrink_to_fit(); }; + free_vec(region->ptrsToRetryRemix); + free_vec(region->ptrsToMuseumRowInfo); free_vec(region->ptrsToMuseumRowColors); free_vec(region->museumRowsR1Cmps); @@ -240,61 +264,6 @@ namespace Region { } } - // Locations of global variables - - u32 GlobalSaveDataPointer(){ - switch (region) { - case US: - return 0x54d350; - case EU: - case KR: - return 0x54d448; - case JP: - default: - return 0; - } - } - - u32 GlobalInputManagerPointer(){ - switch (region) { - case US: - return 0x54eed0; - case EU: - case KR: - return 0x54efc8; - case JP: - default: - return 0; - } - } - - u32 GlobalFileManagerPointer(){ - switch (region) { - case US: - return 0x54eedc; - case EU: - case KR: - return 0x54efd4; - case JP: - default: - return 0; - } - } - - // RHMPatch's retry remix sub patch locations - std::vector RetryRemixLocs() { - switch (region) { - case JP: - return {0x19a180, 0x16485c, 0x1f8e78}; - case US: - case EU: - case KR: - return {0x198c9c, 0x16302c, 0x1f7f84}; - default: - return {}; - } - } - // Region checker u32 RegionFSHookFunc() { switch (region) { diff --git a/src/main.cpp b/src/main.cpp index d516f44..5d31eb9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -76,7 +76,7 @@ static void ToggleTouchscreenForceOn(void) { // This function is called before main and before the game starts // Useful to do code edits safely -void ctrpf::PatchProcess(ctrpf::FwkSettings &settings) { +void ctrpf::PatchProcess(ctrpf::FwkSettings&) { ToggleTouchscreenForceOn(); // le params :D @@ -104,14 +104,10 @@ void ctrpf::PatchProcess(ctrpf::FwkSettings &settings) { region = Region::FromCode(ctrpf::Process::GetTitleID()); //TODO: remove config = Config::FromFile(MEGAMIX_CONFIG_PATH); - // Remix retry sub patch - for (auto loc : Region::RetryRemixLocs()){ - Process::Patch(loc, 0xE3A0200E); // mov r2, #0xE - } - - // Start hooks + // Start hooks, apply patches Megamix::Hooks::TickflowHooks(); Megamix::Hooks::RegionHooks(); + Megamix::Patches::PatchRetryRemix(); if (region != Region::JP) { //TODO: find out how to make the tempo hooks JP-compatible Megamix::Hooks::TempoHooks(); @@ -134,26 +130,26 @@ void ctrpf::OnProcessExit(void) { #ifndef RELEASE void InitMenu(ctrpf::PluginMenu &menu) { - menu += new ctrpf::MenuEntry("Config values", nullptr, [](ctrpf::MenuEntry *entry) { + menu += new ctrpf::MenuEntry("Config values", nullptr, [](ctrpf::MenuEntry*) { ctrpf::MessageBox("Settings", Utils::Format( "Result: %d", configResult ))(); }); - menu += new ctrpf::MenuEntry("Tickflow contents", nullptr, [](ctrpf::MenuEntry *entry) { + menu += new ctrpf::MenuEntry("Tickflow contents", nullptr, [](ctrpf::MenuEntry*) { ctrpf::MessageBox("Map shit", Stuff::FileMapToString(config->tickflows))(); }); - menu += new ctrpf::MenuEntry("Tempo contents (do this w a loaded btks)", nullptr, [](ctrpf::MenuEntry *entry) { + menu += new ctrpf::MenuEntry("Tempo contents (do this w a loaded btks)", nullptr, [](ctrpf::MenuEntry*) { ctrpf::MessageBox("Map shit", Stuff::TempoMapToString(btks.tempos))(); }); - menu += new ctrpf::MenuEntry("Force a crash (prefetch)", nullptr, [](ctrpf::MenuEntry *entry) { + menu += new ctrpf::MenuEntry("Force a crash (prefetch)", nullptr, [](ctrpf::MenuEntry*) { ((void(*)(void))nullptr)(); }); - menu += new ctrpf::MenuEntry("Force a crash (data)", nullptr, [](ctrpf::MenuEntry *entry) { + menu += new ctrpf::MenuEntry("Force a crash (data)", nullptr, [](ctrpf::MenuEntry*) { *(volatile int*)nullptr = 100; }); } From 0494d34e362531b10f91762d156cab47c30a7c6e Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Fri, 5 Sep 2025 22:19:03 +0200 Subject: [PATCH 06/13] [UNTESTED] first round of changes --- include/Config.hpp | 10 ++-- .../{Unicode.h => Helpers/ButtonSymbols.h} | 0 include/Helpers/KeySequence.hpp | 2 +- include/Helpers/QuickMenu.hpp | 2 +- include/Megamix.hpp | 1 - include/Megamix/BTKS.hpp | 2 + include/Megamix/Hooks.hpp | 2 +- include/Megamix/Region.hpp | 4 +- include/Megamix/Types.hpp | 8 +-- include/Stuff.hpp | 4 +- include/csvc.h | 2 +- include/types.h | 2 + src/Config.cpp | 52 +++++++++++-------- src/Helpers/Strings.cpp | 2 +- src/Megamix/BTKS.cpp | 1 + src/Megamix/Commands.cpp | 25 ++++----- src/Megamix/Error.cpp | 2 +- src/Megamix/Hooks.cpp | 12 ++--- src/Megamix/Patches.cpp | 8 +-- src/main.cpp | 21 +++----- 20 files changed, 87 insertions(+), 75 deletions(-) rename include/{Unicode.h => Helpers/ButtonSymbols.h} (100%) diff --git a/include/Config.hpp b/include/Config.hpp index ce159c7..1d218ba 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -8,16 +8,16 @@ #include struct Config { - typedef std::map map; + typedef std::map TickflowMap; - map tickflows; + TickflowMap tickflows; Config(); - Config(map map); - static Config* FromFile(std::string fname); + Config(Config::TickflowMap map); + static Config FromFile(std::string fname); }; -extern Config* config; +extern Config config; extern int configResult; #endif \ No newline at end of file diff --git a/include/Unicode.h b/include/Helpers/ButtonSymbols.h similarity index 100% rename from include/Unicode.h rename to include/Helpers/ButtonSymbols.h diff --git a/include/Helpers/KeySequence.hpp b/include/Helpers/KeySequence.hpp index b3b265f..dcbf486 100644 --- a/include/Helpers/KeySequence.hpp +++ b/include/Helpers/KeySequence.hpp @@ -1,7 +1,7 @@ #ifndef HELPERS_KEYSEQUENCE_HPP #define HELPERS_KEYSEQUENCE_HPP -#include "types.h" +#include <3ds/types.h> #include "CTRPluginFramework/System/Controller.hpp" #include "CTRPluginFramework/System/Clock.hpp" diff --git a/include/Helpers/QuickMenu.hpp b/include/Helpers/QuickMenu.hpp index 687cf45..c4687bc 100644 --- a/include/Helpers/QuickMenu.hpp +++ b/include/Helpers/QuickMenu.hpp @@ -1,6 +1,6 @@ #ifndef HELPERS_QUICKMENU_HPP #define HELPERS_QUICKMENU_HPP -#include "types.h" +#include <3ds/types.h> #include #include #include diff --git a/include/Megamix.hpp b/include/Megamix.hpp index e8dc485..b8a1c59 100644 --- a/include/Megamix.hpp +++ b/include/Megamix.hpp @@ -7,7 +7,6 @@ #define MEGAMIX_MODS_PATH "/spicerack/mods/" #define MEGAMIX_CONFIG_PATH MEGAMIX_BIN_PATH "saltwater.cfg" -#include "Saltwater.hpp" #include "Megamix/Region.hpp" #include "Megamix/Hooks.hpp" #include "Megamix/Patches.hpp" diff --git a/include/Megamix/BTKS.hpp b/include/Megamix/BTKS.hpp index 7c25eae..17b2af2 100644 --- a/include/Megamix/BTKS.hpp +++ b/include/Megamix/BTKS.hpp @@ -2,6 +2,8 @@ #define MEGAMIX_BTKS_H #include +#include + #include "Megamix/Types.hpp" namespace Megamix { diff --git a/include/Megamix/Hooks.hpp b/include/Megamix/Hooks.hpp index e8c8c62..1012426 100644 --- a/include/Megamix/Hooks.hpp +++ b/include/Megamix/Hooks.hpp @@ -1,7 +1,7 @@ #ifndef MEGAMIX_HOOKS_H #define MEGAMIX_HOOKS_H -#include "types.h" +#include <3ds/types.h> namespace Megamix::Hooks { void TickflowHooks(); diff --git a/include/Megamix/Region.hpp b/include/Megamix/Region.hpp index 14b65bf..4b6915a 100644 --- a/include/Megamix/Region.hpp +++ b/include/Megamix/Region.hpp @@ -1,12 +1,12 @@ #ifndef RHMREGION_H #define RHMREGION_H -#include +#include #include #include #include -#include "types.h" +#include <3ds/types.h> #include "Megamix/Error.hpp" #include "Megamix/Types.hpp" diff --git a/include/Megamix/Types.hpp b/include/Megamix/Types.hpp index ced1826..00ad90c 100644 --- a/include/Megamix/Types.hpp +++ b/include/Megamix/Types.hpp @@ -3,7 +3,7 @@ #include -#include "types.h" +#include <3ds/types.h> namespace Megamix { struct GameDef { @@ -139,7 +139,7 @@ namespace Megamix { RvlManjuL = 0x37, RvlMuscleL = 0x38, RvlRapL = 0x39, - RvlRecieveL = 0x3a, + RvlReceiveL = 0x3a, RvlRobotL = 0x3b, RvlRocketL = 0x3c, RvlRotationL = 0x3d, @@ -523,8 +523,8 @@ namespace Megamix { u8 field12_0x2a; u8 field13_0x2b; u32 field14_0x2c; - u16 locale[9]; - u16 sublocale[9]; + char16_t locale[9]; + char16_t sublocale[9]; }; struct FileInfo { diff --git a/include/Stuff.hpp b/include/Stuff.hpp index a17e580..94c7b43 100644 --- a/include/Stuff.hpp +++ b/include/Stuff.hpp @@ -1,12 +1,14 @@ #include +#include "Config.hpp" + #include "Megamix.hpp" using CTRPluginFramework::Utils; using Megamix::BTKS; namespace Stuff { - static inline std::string FileMapToString(std::map in) { + static inline std::string FileMapToString(Config::TickflowMap in) { std::string out = "{"; for (auto item = in.cbegin(); item != in.cend(); ++item) { out += Utils::Format("%X: %s, ", item->first, item->second.c_str()); diff --git a/include/csvc.h b/include/csvc.h index 1acbdc8..36f341c 100644 --- a/include/csvc.h +++ b/include/csvc.h @@ -20,7 +20,7 @@ extern "C" { #endif -#include +#include <3ds/types.h> #define PA_RWX(add) (add == 0 ? 0 : (add < 0x30000000 ? (u32)((add) | (1u << 31)) : add)) #define PA_FROM_VA(addr) PA_RWX(svcConvertVAToPA((void *)addr, false)) diff --git a/include/types.h b/include/types.h index b6aa614..0a5e341 100644 --- a/include/types.h +++ b/include/types.h @@ -6,6 +6,8 @@ #ifndef TYPES_H #define TYPES_H +// ctrpf REQUIRES having this file, don't @ me + #include #include #include diff --git a/src/Config.cpp b/src/Config.cpp index e92f641..5c44a81 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -10,39 +10,49 @@ using CTRPluginFramework::File; int configResult; +Config config; + +#define CHAR4_LE(char1, char2, char3, char4) ( \ + ((u32)(char4) << 24) | ((u32)(char3) << 16) | \ + ((u32)(char2) << 8) | ((u32)(char1) << 0) \ +) + Config::Config() { tickflows = {}; } -Config::Config(map tf) { +Config::Config(TickflowMap tf) { tickflows = tf; } -Config* Config::FromFile(std::string fname) { +Config Config::FromFile(std::string fname) { File file(fname, File::Mode::READ); - char* contents = new char[4]; - configResult = file.Read(contents, 4); - if (configResult || file.GetSize() < 4) return new Config(); - if (configResult == 0 && strcmp(contents, "SCF\2")) { - map tfmap = {}; + u32 magic; + configResult = file.Read(&magic, 4); + if (configResult || file.GetSize() < 4) return Config(); + + if (configResult == 0 && magic == CHAR4_LE('S', 'C', 'F', '\2')) { + TickflowMap tfmap = {}; + while (true) { - u16* index = new u16; - configResult = file.Read(index, 2); - if (configResult) return new Config(); - if (*index == 0xC000) break; + u16 index; + configResult = file.Read(&index, 2); + if (configResult) return Config(); + if (index == 0xC000) break; - u16* strlen = new u16; - configResult = file.Read(strlen, 2); - if (configResult) return new Config(); + u16 strlen; + configResult = file.Read(&strlen, 2); + if (configResult) return Config(); - char* str = new char[*strlen + 1]; - configResult = file.Read(str, *strlen); - if (configResult) return new Config(); - str[*strlen] = 0; + std::string str; + str.resize(strlen); + configResult = file.Read(str.data(), strlen); + if (configResult) return Config(); + str[strlen] = 0; - tfmap[*index] = std::string(str); + tfmap[index] = str; } - return new Config(tfmap); + return Config(tfmap); } - return new Config(); + return Config(); } \ No newline at end of file diff --git a/src/Helpers/Strings.cpp b/src/Helpers/Strings.cpp index 7cf5f64..7a029cb 100644 --- a/src/Helpers/Strings.cpp +++ b/src/Helpers/Strings.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include <3ds/types.h> namespace CTRPluginFramework { diff --git a/src/Megamix/BTKS.cpp b/src/Megamix/BTKS.cpp index 93fe94d..4028d75 100644 --- a/src/Megamix/BTKS.cpp +++ b/src/Megamix/BTKS.cpp @@ -3,6 +3,7 @@ #include #include "Megamix.hpp" +#include "Saltwater.hpp" using CTRPluginFramework::File; using CTRPluginFramework::OSD; diff --git a/src/Megamix/Commands.cpp b/src/Megamix/Commands.cpp index 0f02cfd..3f94450 100644 --- a/src/Megamix/Commands.cpp +++ b/src/Megamix/Commands.cpp @@ -1,8 +1,10 @@ #include <3ds.h> #include +#include + #include "Megamix.hpp" -#include "Megamix/Region.hpp" +#include "Saltwater.hpp" using CTRPluginFramework::OSD; using CTRPluginFramework::Utils; @@ -84,23 +86,22 @@ namespace Megamix { if(saveLanguage == 1){ self->condvar = 0; } else { - wchar_t sublocale[5]; - utf16_to_utf32((u32*)sublocale, Game::gFileManager()->sublocale, 4); - sublocale[4] = '\0'; - std::wstring localews(sublocale); - if(localews.find(L"JP") != (unsigned int)-1){ + std::u16string sublocale(Game::gFileManager()->sublocale); + + sublocale = sublocale.substr(2, 2); + if (sublocale == u"JP") { self->condvar = 0; - } else if (localews.find(L"EN") != (unsigned int)-1){ + } else if (sublocale == u"EN") { self->condvar = 1; - } else if (localews.find(L"FR") != (unsigned int)-1) { + } else if (sublocale == u"FR") { self->condvar = 2; - } else if (localews.find(L"GE") != (unsigned int)-1) { + } else if (sublocale == u"GE") { self->condvar = 3; - } else if (localews.find(L"IT") != (unsigned int)-1) { + } else if (sublocale == u"IT") { self->condvar = 4; - } else if (localews.find(L"SP") != (unsigned int)-1) { + } else if (sublocale == u"SP") { self->condvar = 5; - } else if (localews.find(L"KR") != (unsigned int)-1) { + } else if (sublocale == u"KR") { self->condvar = 6; } else { self->condvar = -1; diff --git a/src/Megamix/Error.cpp b/src/Megamix/Error.cpp index aeb498b..6574885 100644 --- a/src/Megamix/Error.cpp +++ b/src/Megamix/Error.cpp @@ -4,7 +4,7 @@ #include #include "Megamix.hpp" -#include "Megamix/Region.hpp" +#include "Saltwater.hpp" using CTRPluginFramework::Process; using CTRPluginFramework::Utils; diff --git a/src/Megamix/Hooks.cpp b/src/Megamix/Hooks.cpp index 88b1c5c..a456e71 100644 --- a/src/Megamix/Hooks.cpp +++ b/src/Megamix/Hooks.cpp @@ -26,8 +26,8 @@ namespace Megamix::Hooks { RT_HOOK tickflowCommandsHook; void* getTickflowOffset(int index) { - if (config->tickflows.contains(index)) { - int result = Megamix::btks.LoadFile(config->tickflows[index]); + if (config.tickflows.contains(index)) { + int result = Megamix::btks.LoadFile(config.tickflows[index]); if (!result) { return (void*)(Megamix::btks.start); } else { @@ -38,8 +38,8 @@ namespace Megamix::Hooks { } void* getGateTickflowOffset(int index) { - if (config->tickflows.contains(index + 0x100)) { - int result = Megamix::btks.LoadFile(config->tickflows[index + 0x100]); + if (config.tickflows.contains(index + 0x100)) { + int result = Megamix::btks.LoadFile(config.tickflows[index + 0x100]); if (!result) { return (void*)(Megamix::btks.start); } else { @@ -50,8 +50,8 @@ namespace Megamix::Hooks { } void* getGatePracticeTickflowOffset(int index) { - if (config->tickflows.contains((index >> 2) + 0x110)) { - int result = Megamix::btks.LoadFile(config->tickflows[(index >> 2) + 0x110]); + if (config.tickflows.contains((index >> 2) + 0x110)) { + int result = Megamix::btks.LoadFile(config.tickflows[(index >> 2) + 0x110]); if (!result) { return (void*)(Megamix::btks.start); } else { diff --git a/src/Megamix/Patches.cpp b/src/Megamix/Patches.cpp index aa2034e..ab48e24 100644 --- a/src/Megamix/Patches.cpp +++ b/src/Megamix/Patches.cpp @@ -38,7 +38,7 @@ namespace Megamix::Patches { /* 23 */ MuseumRow({ AgbHoppingL, AgbNightWalkL, AgbQuizL, None, None }, "bonus_AGB", 0, 0), /* 24 */ MuseumRow({ NtrBoxShowL, NtrShortLiveL, RvlKarate2, None, None }, "bonus_NTR", 0, 0), /* 25 */ MuseumRow({ RvlAssembleL, RvlDateL, RvlFishingL, None, None }, "bonus_RVL0", 0, 0), - /* 26 */ MuseumRow({ RvlForkL, RvlRapL, RvlRecieveL, None, None }, "bonus_RVL1", 0, 0), + /* 26 */ MuseumRow({ RvlForkL, RvlRapL, RvlReceiveL, None, None }, "bonus_RVL1", 0, 0), /* 27 */ MuseumRow({ RvlRobotL, RvlRotationL, RvlSamuraiL, None, None }, "bonus_RVL2", 0, 0), /* 28 */ MuseumRow({ RvlSortL, RvlWatchL, RvlKarate3, None, None }, "bonus_RVL3", 0, 0), }; @@ -112,7 +112,7 @@ namespace Megamix::Patches { // not all slots are valid museum games u32 validGameIds = 0; - for (auto &[key, _] : config->tickflows) { + for (auto &[key, _] : config.tickflows) { if (SlotIdToMuseumGameId(key).has_value()) { validGameIds += 1; } @@ -120,7 +120,7 @@ namespace Megamix::Patches { // museum don't support rows with only 2 games if (validGameIds == 2) { - for (auto &pair : config->tickflows) { + for (auto &pair : config.tickflows) { std::optional id = SlotIdToMuseumGameId(pair.first); if (!id.has_value()) { continue; @@ -132,7 +132,7 @@ namespace Megamix::Patches { std::array newRowIds { None, None, None, None, None }; size_t newRowLength = 0; - for (auto &pair : config->tickflows) { + for (auto &pair : config.tickflows) { std::optional id = SlotIdToMuseumGameId(pair.first); if (!id.has_value()) { continue; diff --git a/src/main.cpp b/src/main.cpp index 5d31eb9..a9d9703 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,19 +2,14 @@ #include #include -#include "Megamix/Error.hpp" -#include "Megamix/Region.hpp" #include "csvc.h" #include "external/plgldr.h" #include "Megamix.hpp" #include "Config.hpp" - +#include "Saltwater.hpp" #include "Stuff.hpp" -Config* config; -using Megamix::btks; - const char* version = VERSION; SaltwaterParams params; @@ -79,18 +74,19 @@ static void ToggleTouchscreenForceOn(void) { void ctrpf::PatchProcess(ctrpf::FwkSettings&) { ToggleTouchscreenForceOn(); - // le params :D - params = *(SaltwaterParams*)ctrpf::FwkSettings::Header->config; + // plugin params - these are used for shorter types (bools usually) + params = *reinterpret_cast(ctrpf::FwkSettings::Header->config); if (params.rhmpatch) { - // move RHMPatch back to where it was + // barista moved RHMPatch's code.ips, move it back to where it was ctrpf::File::Rename( "/luma/titles/000400000018a400/code.old.ips", "/luma/titles/000400000018a400/code.ips" ); } + if (params.plgldr) { - // disable plugin loader + // had to specifically turn on the plgldr, turn it off to not cause any issues plgLdrInit(); PLGLDR__SetPluginLoaderState(false); plgLdrExit(); @@ -125,7 +121,6 @@ void ctrpf::PatchProcess(ctrpf::FwkSettings&) { void ctrpf::OnProcessExit(void) { Megamix::Hooks::DisableAllHooks(); // Probably not needed, but still ToggleTouchscreenForceOn(); - delete config; } #ifndef RELEASE @@ -138,11 +133,11 @@ void InitMenu(ctrpf::PluginMenu &menu) { }); menu += new ctrpf::MenuEntry("Tickflow contents", nullptr, [](ctrpf::MenuEntry*) { - ctrpf::MessageBox("Map shit", Stuff::FileMapToString(config->tickflows))(); + ctrpf::MessageBox("Map shit", Stuff::FileMapToString(config.tickflows))(); }); menu += new ctrpf::MenuEntry("Tempo contents (do this w a loaded btks)", nullptr, [](ctrpf::MenuEntry*) { - ctrpf::MessageBox("Map shit", Stuff::TempoMapToString(btks.tempos))(); + ctrpf::MessageBox("Map shit", Stuff::TempoMapToString(Megamix::btks.tempos))(); }); menu += new ctrpf::MenuEntry("Force a crash (prefetch)", nullptr, [](ctrpf::MenuEntry*) { From 9fb52912aaf8f447c1642ff6d9602602124182ee Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Fri, 5 Sep 2025 23:00:03 +0200 Subject: [PATCH 07/13] [UNTESTED] second round of changes --- include/CTRPF.hpp | 15 ++++++++ include/Config.hpp | 2 +- include/Megamix/Error.hpp | 5 +-- include/Megamix/Region.hpp | 2 +- include/Saltwater.hpp | 23 ++++++----- include/Stuff.hpp | 7 ++-- src/Config.cpp | 2 +- src/Megamix/BTKS.cpp | 2 +- src/Megamix/Commands.cpp | 2 +- src/Megamix/Error.cpp | 79 ++++++++++++++++++-------------------- src/Megamix/Hooks.cpp | 8 ++-- src/Megamix/Patches.cpp | 2 +- src/Megamix/Region.cpp | 4 +- src/main.cpp | 41 +++++++++----------- 14 files changed, 98 insertions(+), 96 deletions(-) create mode 100644 include/CTRPF.hpp diff --git a/include/CTRPF.hpp b/include/CTRPF.hpp new file mode 100644 index 0000000..383de15 --- /dev/null +++ b/include/CTRPF.hpp @@ -0,0 +1,15 @@ +#ifndef CTRPF_WRAPPER_H +#define CTRPF_WRAPPER_H + +#include + +namespace CTRPF = CTRPluginFramework; + +using CTRPF::Process; +using CTRPF::OSD; + +constexpr auto Format = CTRPF::Utils::Format; + +// TODO: Format but with char*? + +#endif diff --git a/include/Config.hpp b/include/Config.hpp index 1d218ba..dac1a1b 100644 --- a/include/Config.hpp +++ b/include/Config.hpp @@ -2,7 +2,7 @@ #define CONFIG_H #include <3ds.h> -#include +#include "CTRPF.hpp" #include #include diff --git a/include/Megamix/Error.hpp b/include/Megamix/Error.hpp index 1fd3102..1bed31c 100644 --- a/include/Megamix/Error.hpp +++ b/include/Megamix/Error.hpp @@ -1,14 +1,11 @@ #ifndef MEGAMIX_ERROR_HPP #define MEGAMIX_ERROR_HPP -#include #include -#include +#include "CTRPF.hpp" #define CALL_STACK_SIZE 5 -using CTRPluginFramework::Process; - struct Void{}; namespace Megamix { diff --git a/include/Megamix/Region.hpp b/include/Megamix/Region.hpp index 4b6915a..82f061d 100644 --- a/include/Megamix/Region.hpp +++ b/include/Megamix/Region.hpp @@ -1,7 +1,7 @@ #ifndef RHMREGION_H #define RHMREGION_H -#include +#include "CTRPF.hpp" #include #include #include diff --git a/include/Saltwater.hpp b/include/Saltwater.hpp index 2295a5f..e2ccf47 100644 --- a/include/Saltwater.hpp +++ b/include/Saltwater.hpp @@ -1,7 +1,9 @@ #ifndef SALTWATER_H #define SALTWATER_H +#include "external/plgldr.h" #include <3ds.h> +#include // Saltwater version @@ -19,7 +21,6 @@ #ifdef RELEASE - #define VERSION "v" STRC(VERSION_MAJOR) "." STRC(VERSION_MINOR) "." STRC(VERSION_PATCH) #else @@ -29,17 +30,15 @@ #endif -// Params struct -extern "C" { - struct SaltwaterParams { - u16 barista; - bool rhmpatch; - bool plgldr; - bool mod_loaded_msg; - bool extra_rows; - u32 null[30]; - }; -} +struct SaltwaterParams { + u16 barista; + bool rhmpatch; + bool plgldr; + bool mod_loaded_msg; + bool extra_rows; + u32 null[30]; +}; +static_assert(sizeof(SaltwaterParams) == sizeof(PluginLoadParameters::config)); extern SaltwaterParams params; diff --git a/include/Stuff.hpp b/include/Stuff.hpp index 94c7b43..b3078d3 100644 --- a/include/Stuff.hpp +++ b/include/Stuff.hpp @@ -1,17 +1,16 @@ -#include +#include "CTRPF.hpp" #include "Config.hpp" #include "Megamix.hpp" -using CTRPluginFramework::Utils; using Megamix::BTKS; namespace Stuff { static inline std::string FileMapToString(Config::TickflowMap in) { std::string out = "{"; for (auto item = in.cbegin(); item != in.cend(); ++item) { - out += Utils::Format("%X: %s, ", item->first, item->second.c_str()); + out += Format("%X: %s, ", item->first, item->second.c_str()); } out += "}"; return out; @@ -21,7 +20,7 @@ namespace Stuff { std::string out = "{"; for (auto item = in.cbegin(); item != in.cend(); ++item) { - out += Utils::Format("%X: Tempo of size %d, ", item->first, ((int*)item->second->pos)[-1]); + out += Format("%X: Tempo of size %d, ", item->first, ((int*)item->second->pos)[-1]); } out += "}"; return out; diff --git a/src/Config.cpp b/src/Config.cpp index 5c44a81..b0e3257 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -1,5 +1,5 @@ #include <3ds.h> -#include +#include "CTRPF.hpp" #include "Megamix.hpp" #include "Config.hpp" diff --git a/src/Megamix/BTKS.cpp b/src/Megamix/BTKS.cpp index 4028d75..6e49b58 100644 --- a/src/Megamix/BTKS.cpp +++ b/src/Megamix/BTKS.cpp @@ -1,5 +1,5 @@ #include <3ds.h> -#include +#include "CTRPF.hpp" #include #include "Megamix.hpp" diff --git a/src/Megamix/Commands.cpp b/src/Megamix/Commands.cpp index 3f94450..5268af2 100644 --- a/src/Megamix/Commands.cpp +++ b/src/Megamix/Commands.cpp @@ -1,5 +1,5 @@ #include <3ds.h> -#include +#include "CTRPF.hpp" #include diff --git a/src/Megamix/Error.cpp b/src/Megamix/Error.cpp index 6574885..868b56d 100644 --- a/src/Megamix/Error.cpp +++ b/src/Megamix/Error.cpp @@ -1,20 +1,15 @@ #include <3ds.h> -#include +#include "CTRPF.hpp" #include #include "Megamix.hpp" #include "Saltwater.hpp" -using CTRPluginFramework::Process; -using CTRPluginFramework::Utils; -using CTRPluginFramework::Screen; -using CTRPluginFramework::Color; -using CTRPluginFramework::Controller; -using CTRPluginFramework::Key; -using CTRPluginFramework::OSD; -using CTRPluginFramework::File; -using CTRPluginFramework::Directory; +using CTRPF::Directory; +using CTRPF::File; +using CTRPF::Controller; +using CTRPF::Key; extern const u32 _TEXT_END; extern const u32 _start; @@ -59,7 +54,7 @@ namespace Megamix { return "Unsupported Tickflow format"; default: - return Utils::Format("Unknown error code %08x", code); + return Format("Unknown error code %08x", code); } } @@ -96,14 +91,14 @@ namespace Megamix { u32 fsr_status = (info->fsr & 0xf); switch (info->type) { case ERRF_EXCEPTION_PREFETCH_ABORT: - return Utils::Format("0-%02d-%s-%08X", fsr_status, MemSection(regs->pc), regs->pc); + return Format("0-%02d-%s-%08X", fsr_status, MemSection(regs->pc), regs->pc); case ERRF_EXCEPTION_DATA_ABORT: fsr_status += ((info->fsr >> 10) & 1) * 0x10; - return Utils::Format("1-%02d-%s-%07X-%08X", fsr_status, MemSection(info->far), regs->pc, info->far); + return Format("1-%02d-%s-%07X-%08X", fsr_status, MemSection(info->far), regs->pc, info->far); case ERRF_EXCEPTION_VFP: - return Utils::Format("2-%07X", regs->pc); + return Format("2-%07X", regs->pc); case ERRF_EXCEPTION_UNDEFINED: - return Utils::Format("3-%07X", regs->pc); + return Format("3-%07X", regs->pc); default: return "INVALID"; } @@ -180,23 +175,23 @@ namespace Megamix { } static void InfoScreen(ERRF_ExceptionInfo* info, CpuRegisters* regs) { - Screen screen = OSD::GetTopScreen(); + CTRPF::Screen screen = OSD::GetTopScreen(); if (!faded) { screen.Fade(0.3); faded = true; } - screen.DrawRect(16, 16, 368, 208, Color(0, 0, 0)); + screen.DrawRect(16, 16, 368, 208, CTRPF::Color(0, 0, 0)); u32 posY = 20; - posY = screen.Draw("Oh, no!", 20, posY, Color(255, 0, 0)); - posY = screen.Draw(Utils::Format("Error %s", CrashCode(info, regs).c_str()), 20, posY, Color(255, 127, 127)); + posY = screen.Draw("Oh, no!", 20, posY, CTRPF::Color(255, 0, 0)); + posY = screen.Draw(Format("Error %s", CrashCode(info, regs).c_str()), 20, posY, CTRPF::Color(255, 127, 127)); posY += 10; posY = screen.Draw("Something went wrong!", 20, posY); posY = screen.Draw("But don't panic, report the error to the SpiceRack", 20, posY); posY = screen.Draw("Discord server (discord.gg/xAKFPaERRG)", 20, posY); posY += 10; - if (dump_location != "") { + if (dump_location.empty()) { if (dump_error) posY = screen.Draw(std::string("Error while saving dump: ").append(dump_location), 20, posY); else @@ -212,26 +207,26 @@ namespace Megamix { } static void DevScreen(ERRF_ExceptionInfo* info, CpuRegisters* regs) { - Screen screen = OSD::GetTopScreen(); - screen.DrawRect(16, 16, 368, 208, Color(0, 0, 0)); + CTRPF::Screen screen = OSD::GetTopScreen(); + screen.DrawRect(16, 16, 368, 208, CTRPF::Color(0, 0, 0)); u32 posY = 20; - posY = screen.Draw(Utils::Format("Debug info (%s)", Game::_regionName()), 20, posY); - posY = screen.Draw(Utils::Format("@ %08x -> %08x (@ PC -> LR)", regs->pc, regs->lr), 20, posY); - posY = screen.Draw(Utils::Format("Exception type %d", info->type), 20, posY); + posY = screen.Draw(Format("Debug info (%s)", Game::_regionName()), 20, posY); + posY = screen.Draw(Format("@ %08x -> %08x (@ PC -> LR)", regs->pc, regs->lr), 20, posY); + posY = screen.Draw(Format("Exception type %d", info->type), 20, posY); posY += 10; posY = screen.Draw("Call stack:", 20, posY); for (int i = 0; i < CALL_STACK_SIZE; i++) { - posY = screen.Draw(Utils::Format(" - %08x", crash.info.callStack[i]), 20, posY); + posY = screen.Draw(Format(" - %08x", crash.info.callStack[i]), 20, posY); } posY += 10; - posY = screen.Draw(Utils::Format("r0 = %08x r1 = %08x", crash.registers[0], crash.registers[1]), 20, posY); - posY = screen.Draw(Utils::Format("r2 = %08x r3 = %08x", crash.registers[2], crash.registers[3]), 20, posY); + posY = screen.Draw(Format("r0 = %08x r1 = %08x", crash.registers[0], crash.registers[1]), 20, posY); + posY = screen.Draw(Format("r2 = %08x r3 = %08x", crash.registers[2], crash.registers[3]), 20, posY); if (dump_location != "") { if (dump_error) @@ -261,7 +256,7 @@ namespace Megamix { int num = 0; std::string path; for (;; num++) { - path = Utils::Format(MEGAMIX_CRASH_PATH "swcrash_%05d.swd", num); + path = Format(MEGAMIX_CRASH_PATH "swcrash_%05d.swd", num); if (!File::Exists(path)) break; } @@ -302,21 +297,23 @@ namespace Megamix { } Controller::Update(); + if (Controller::IsKeyPressed(Key::B)) { return Process::ExceptionCallbackState::EXCB_RETURN_HOME; - } else { - if (Controller::IsKeyPressed(Key::A) && dump_location == "") { - int result = SaveCrashDump(); - if (result != 0) { - dump_error = true; - dump_location = ErrorMessage(result); - } - render = true; - } else if (Controller::IsKeyPressed(Key::Y)) { - render = true; - full_info = !full_info; + } + + if (Controller::IsKeyPressed(Key::A) && dump_location == "") { + int result = SaveCrashDump(); + if (result != 0) { + dump_error = true; + dump_location = ErrorMessage(result); } - return Process::ExceptionCallbackState::EXCB_LOOP; + render = true; + } else if (Controller::IsKeyPressed(Key::Y)) { + render = true; + full_info = !full_info; } + return Process::ExceptionCallbackState::EXCB_LOOP; + } } diff --git a/src/Megamix/Hooks.cpp b/src/Megamix/Hooks.cpp index a456e71..bddb612 100644 --- a/src/Megamix/Hooks.cpp +++ b/src/Megamix/Hooks.cpp @@ -1,5 +1,5 @@ #include <3ds.h> -#include +#include "CTRPF.hpp" #include "Megamix/Region.hpp" #include "external/rt.h" @@ -31,7 +31,7 @@ namespace Megamix::Hooks { if (!result) { return (void*)(Megamix::btks.start); } else { - OSD::Notify(CTRPluginFramework::Utils::Format("Error: %s", Megamix::ErrorMessage(result).c_str())); + OSD::Notify(Format("Error: %s", Megamix::ErrorMessage(result).c_str())); } } return Game::gGameTable()[index].tfStart; // og code @@ -43,7 +43,7 @@ namespace Megamix::Hooks { if (!result) { return (void*)(Megamix::btks.start); } else { - OSD::Notify(CTRPluginFramework::Utils::Format("Error: %s", Megamix::ErrorMessage(result).c_str())); + OSD::Notify(Format("Error: %s", Megamix::ErrorMessage(result).c_str())); } } return Game::gGateTable()[index].tfStart; // og code @@ -55,7 +55,7 @@ namespace Megamix::Hooks { if (!result) { return (void*)(Megamix::btks.start); } else { - OSD::Notify(CTRPluginFramework::Utils::Format("Error: %s", Megamix::ErrorMessage(result).c_str())); + OSD::Notify(Format("Error: %s", Megamix::ErrorMessage(result).c_str())); } } return Game::gGateTable()[index].tfGatePractice; // og code diff --git a/src/Megamix/Patches.cpp b/src/Megamix/Patches.cpp index ab48e24..ad63b2e 100644 --- a/src/Megamix/Patches.cpp +++ b/src/Megamix/Patches.cpp @@ -4,7 +4,7 @@ #include #include <3ds.h> -#include +#include "CTRPF.hpp" #include "Megamix.hpp" #include "Config.hpp" diff --git a/src/Megamix/Region.cpp b/src/Megamix/Region.cpp index 319eb97..6b5412f 100644 --- a/src/Megamix/Region.cpp +++ b/src/Megamix/Region.cpp @@ -1,9 +1,7 @@ -#include "Megamix/Region.hpp" #include <3ds.h> -#include +#include "CTRPF.hpp" #include "Megamix.hpp" -#include "Megamix/Types.hpp" #include diff --git a/src/main.cpp b/src/main.cpp index a9d9703..a01b073 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,5 @@ #include <3ds.h> -#include -#include +#include "CTRPF.hpp" #include "csvc.h" #include "external/plgldr.h" @@ -14,8 +13,6 @@ const char* version = VERSION; SaltwaterParams params; -// tired of typing these names -namespace ctrpf = CTRPluginFramework; namespace CTRPluginFramework { void PatchProcess(FwkSettings &settings); void OnProcessExit(void); @@ -55,7 +52,7 @@ static void ToggleTouchscreenForceOn(void) { if(R_FAILED(svcMapProcessMemoryEx(CUR_PROCESS_HANDLE, 0x14000000, processHandle, (u32)startAddress, textTotalSize))) goto exit; - found = (u32 *)Utils::Search(0x14000000, (u32)textTotalSize, pattern); + found = (u32 *)CTRPF::Utils::Search(0x14000000, (u32)textTotalSize, pattern); if (found != nullptr) { @@ -71,15 +68,15 @@ static void ToggleTouchscreenForceOn(void) { // This function is called before main and before the game starts // Useful to do code edits safely -void ctrpf::PatchProcess(ctrpf::FwkSettings&) { +void CTRPF::PatchProcess(CTRPF::FwkSettings&) { ToggleTouchscreenForceOn(); // plugin params - these are used for shorter types (bools usually) - params = *reinterpret_cast(ctrpf::FwkSettings::Header->config); + params = *reinterpret_cast(CTRPF::FwkSettings::Header->config); if (params.rhmpatch) { // barista moved RHMPatch's code.ips, move it back to where it was - ctrpf::File::Rename( + CTRPF::File::Rename( "/luma/titles/000400000018a400/code.old.ips", "/luma/titles/000400000018a400/code.ips" ); @@ -93,11 +90,11 @@ void ctrpf::PatchProcess(ctrpf::FwkSettings&) { } // Init region and config - auto region_res = Megamix::initGameInterface(ctrpf::Process::GetTitleID()); + auto region_res = Megamix::initGameInterface(CTRPF::Process::GetTitleID()); if (!region_res.has_value()) { Megamix::panic("what the hell how did you get this\nyou're running saltwater on something that isn't megamix"); } - region = Region::FromCode(ctrpf::Process::GetTitleID()); //TODO: remove + region = Region::FromCode(CTRPF::Process::GetTitleID()); //TODO: remove config = Config::FromFile(MEGAMIX_CONFIG_PATH); // Start hooks, apply patches @@ -118,39 +115,39 @@ void ctrpf::PatchProcess(ctrpf::FwkSettings&) { // This function is called when the process exits // Useful to save settings, undo patchs or clean up things -void ctrpf::OnProcessExit(void) { +void CTRPF::OnProcessExit(void) { Megamix::Hooks::DisableAllHooks(); // Probably not needed, but still ToggleTouchscreenForceOn(); } #ifndef RELEASE -void InitMenu(ctrpf::PluginMenu &menu) { - menu += new ctrpf::MenuEntry("Config values", nullptr, [](ctrpf::MenuEntry*) { - ctrpf::MessageBox("Settings", Utils::Format( +void InitMenu(CTRPF::PluginMenu &menu) { + menu += new CTRPF::MenuEntry("Config values", nullptr, [](CTRPF::MenuEntry*) { + CTRPF::MessageBox("Settings", Format( "Result: %d", configResult ))(); }); - menu += new ctrpf::MenuEntry("Tickflow contents", nullptr, [](ctrpf::MenuEntry*) { - ctrpf::MessageBox("Map shit", Stuff::FileMapToString(config.tickflows))(); + menu += new CTRPF::MenuEntry("Tickflow contents", nullptr, [](CTRPF::MenuEntry*) { + CTRPF::MessageBox("Map shit", Stuff::FileMapToString(config.tickflows))(); }); - menu += new ctrpf::MenuEntry("Tempo contents (do this w a loaded btks)", nullptr, [](ctrpf::MenuEntry*) { - ctrpf::MessageBox("Map shit", Stuff::TempoMapToString(Megamix::btks.tempos))(); + menu += new CTRPF::MenuEntry("Tempo contents (do this w a loaded btks)", nullptr, [](CTRPF::MenuEntry*) { + CTRPF::MessageBox("Map shit", Stuff::TempoMapToString(Megamix::btks.tempos))(); }); - menu += new ctrpf::MenuEntry("Force a crash (prefetch)", nullptr, [](ctrpf::MenuEntry*) { + menu += new CTRPF::MenuEntry("Force a crash (prefetch)", nullptr, [](CTRPF::MenuEntry*) { ((void(*)(void))nullptr)(); }); - menu += new ctrpf::MenuEntry("Force a crash (data)", nullptr, [](ctrpf::MenuEntry*) { + menu += new CTRPF::MenuEntry("Force a crash (data)", nullptr, [](CTRPF::MenuEntry*) { *(volatile int*)nullptr = 100; }); } #endif -int ctrpf::main(void) { +int CTRPF::main(void) { // Crash handler Process::exceptionCallback = Megamix::CrashHandler; @@ -161,7 +158,7 @@ int ctrpf::main(void) { #ifdef RELEASE Process::WaitForExit(); #else - PluginMenu *menu = new PluginMenu(Utils::Format("Saltwater %s debug", VERSION), "", 1); + PluginMenu *menu = new PluginMenu(Format("Saltwater %s debug", VERSION), "", 1); // Synnchronize the menu with frame event menu->SynchronizeWithFrame(true); From ef16bc55e2fea3271f280c26b63a250d0f2cf623 Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Sat, 6 Sep 2025 01:01:06 +0200 Subject: [PATCH 08/13] [UNTESTED] third pass, begin unfucking BTKS --- include/Helpers.hpp | 1 - include/Megamix/BTKS.hpp | 4 +- include/Megamix/Commands.hpp | 10 +-- include/Megamix/Hooks.hpp | 21 +++-- include/Megamix/Region.hpp | 2 +- include/Megamix/Types.hpp | 59 ++++++------ include/Stuff.hpp | 5 ++ src/Config.cpp | 6 +- src/Megamix/BTKS.cpp | 169 ++++++++++++++++++----------------- src/Megamix/Commands.cpp | 22 ++--- src/Megamix/Hooks.cpp | 24 +++-- src/Megamix/Patches.cpp | 31 ++++--- src/main.cpp | 10 +-- 13 files changed, 195 insertions(+), 169 deletions(-) diff --git a/include/Helpers.hpp b/include/Helpers.hpp index 4c46fe8..d4b16a7 100644 --- a/include/Helpers.hpp +++ b/include/Helpers.hpp @@ -1,7 +1,6 @@ #ifndef HELPERS_HPP #define HELPERS_HPP -#include "Helpers/AutoRegion.hpp" #include "Helpers/HoldKey.hpp" #include "Helpers/KeySequence.hpp" #include "Helpers/MenuEntryHelpers.hpp" diff --git a/include/Megamix/BTKS.hpp b/include/Megamix/BTKS.hpp index 17b2af2..ba5e07e 100644 --- a/include/Megamix/BTKS.hpp +++ b/include/Megamix/BTKS.hpp @@ -20,8 +20,8 @@ namespace Megamix { char* tickflow = nullptr; char* strings = nullptr; std::map tempos = std::map(); - u32 tickflowSize; - u32 stringSize; + u32 tickflowSize = 0; + u32 stringSize = 0; int LoadFile(std::string filename); void Unload(); diff --git a/include/Megamix/Commands.hpp b/include/Megamix/Commands.hpp index 50a1ae3..58d5584 100644 --- a/include/Megamix/Commands.hpp +++ b/include/Megamix/Commands.hpp @@ -13,15 +13,7 @@ namespace Megamix{ DisplayCondvar = 0x300 }; - void tickflowCommandsHookWrapper(); - - extern "C" int tickflowCommandsHook(CTickflow* self, u32 cmd_num, u32 arg0, u32* args); - - void input_cmd(CTickflow* self, u32 arg0, u32* args); - void versionCheck(CTickflow* self, u32 arg0, u32* args); - void languageCheck(CTickflow* self, u32 arg0, u32* args); - void displayCondvar(CTickflow* self, u32 arg0, u32* args); - void msbtWithNum(CTickflow* self, u32 arg0, u32* args); + void tickflowCommandsHook(); } #endif diff --git a/include/Megamix/Hooks.hpp b/include/Megamix/Hooks.hpp index 1012426..9a40ced 100644 --- a/include/Megamix/Hooks.hpp +++ b/include/Megamix/Hooks.hpp @@ -4,13 +4,22 @@ #include <3ds/types.h> namespace Megamix::Hooks { - void TickflowHooks(); - void TempoHooks(); - void RegionHooks(); - void CommandHook(); - void DisableAllHooks(); + void initTickflowHooks(); + void initTempoHooks(); + void initRegionHooks(); + void initCommandHooks(); + + void disableTickflowHooks(); + void disableTempoHooks(); + void disableRegionHooks(); + void disableCommandHooks(); + inline void disableAllHooks() { + disableTickflowHooks(); + disableTempoHooks(); + disableRegionHooks(); + disableCommandHooks(); + } - template T StubbedFunction(); template void StubFunction(u32 address); } diff --git a/include/Megamix/Region.hpp b/include/Megamix/Region.hpp index 82f061d..7eb215a 100644 --- a/include/Megamix/Region.hpp +++ b/include/Megamix/Region.hpp @@ -127,7 +127,7 @@ namespace Megamix { namespace Region { - + // TODO: split into two enum classes enum { JP = 0, US = 1, diff --git a/include/Megamix/Types.hpp b/include/Megamix/Types.hpp index 00ad90c..9bc41e5 100644 --- a/include/Megamix/Types.hpp +++ b/include/Megamix/Types.hpp @@ -4,6 +4,9 @@ #include #include <3ds/types.h> +#include "CTRPF.hpp" + +// TODO: split this file - potentially move into Game? namespace Megamix { struct GameDef { @@ -46,7 +49,7 @@ namespace Megamix { struct TempoTable { u32 id1; u32 id2; - u8 unk8; + u8 isStreamed; u16 unkA; Tempo* pos; }; @@ -216,17 +219,17 @@ namespace Megamix { u32 lowIndex; MuseumRow(std::array gameIndices, const char* titleId, u32 highIndex, u32 lowIndex) { - if (gameIndices[0] == 0x101) this->columnCount = 0; - else if (gameIndices[1] == 0x101) this->columnCount = 1; - else if (gameIndices[2] == 0x101) this->columnCount = 2; - else if (gameIndices[3] == 0x101) this->columnCount = 3; - else if (gameIndices[4] == 0x101) this->columnCount = 4; - else this->columnCount = 5; - - this->gameIndices = gameIndices; - this->titleId = titleId; - this->highIndex = highIndex; - this->lowIndex = lowIndex; + if (gameIndices[0] == 0x101) columnCount = 0; + else if (gameIndices[1] == 0x101) columnCount = 1; + else if (gameIndices[2] == 0x101) columnCount = 2; // broken in vanilla + else if (gameIndices[3] == 0x101) columnCount = 3; + else if (gameIndices[4] == 0x101) columnCount = 4; + else columnCount = 5; + + gameIndices = gameIndices; + titleId = titleId; + highIndex = highIndex; + lowIndex = lowIndex; } }; @@ -237,24 +240,24 @@ namespace Megamix { u8 a; Color8() { - this->r = 0; - this->g = 0; - this->b = 0; - this->a = 0; + r = 0; + g = 0; + b = 0; + a = 0; } Color8(u8 r, u8 g, u8 b, u8 a) { - this->r = r; - this->g = g; - this->b = b; - this->a = a; + r = r; + g = g; + b = b; + a = a; } Color8(u32 color) { - this->r = color >> 24; - this->g = color >> 16; - this->b = color >> 8; - this->a = color; + r = color >> 24; + g = color >> 16; + b = color >> 8; + a = color; } }; @@ -265,11 +268,11 @@ namespace Megamix { Color8 unk3; MuseumRowColor(Color8 background, Color8 edgeFade) { - this->unk1 = {0x00, 0x00, 0x00, 0x00}; - this->unk3 = {0xFF, 0xFF, 0xFF, 0x6E}; + unk1 = {0x00, 0x00, 0x00, 0x00}; + unk3 = {0xFF, 0xFF, 0xFF, 0x6E}; - this->background = background; - this->edgeFade = edgeFade; + background = background; + edgeFade = edgeFade; } }; diff --git a/include/Stuff.hpp b/include/Stuff.hpp index b3078d3..966ed1f 100644 --- a/include/Stuff.hpp +++ b/include/Stuff.hpp @@ -6,6 +6,11 @@ using Megamix::BTKS; +#define CHAR4_LE(char1, char2, char3, char4) ( \ + ((u32)(char4) << 24) | ((u32)(char3) << 16) | \ + ((u32)(char2) << 8) | ((u32)(char1) << 0) \ +) + namespace Stuff { static inline std::string FileMapToString(Config::TickflowMap in) { std::string out = "{"; diff --git a/src/Config.cpp b/src/Config.cpp index b0e3257..4c2e6b5 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -3,6 +3,7 @@ #include "Megamix.hpp" #include "Config.hpp" +#include "Stuff.hpp" #include @@ -12,11 +13,6 @@ int configResult; Config config; -#define CHAR4_LE(char1, char2, char3, char4) ( \ - ((u32)(char4) << 24) | ((u32)(char3) << 16) | \ - ((u32)(char2) << 8) | ((u32)(char1) << 0) \ -) - Config::Config() { tickflows = {}; } diff --git a/src/Megamix/BTKS.cpp b/src/Megamix/BTKS.cpp index 6e49b58..aeac121 100644 --- a/src/Megamix/BTKS.cpp +++ b/src/Megamix/BTKS.cpp @@ -4,6 +4,7 @@ #include "Megamix.hpp" #include "Saltwater.hpp" +#include "Stuff.hpp" using CTRPluginFramework::File; using CTRPluginFramework::OSD; @@ -11,86 +12,85 @@ using CTRPluginFramework::OSD; namespace Megamix { BTKS btks; + struct SectionHeader { + u32 magic; + u32 size; + }; + + struct BTKSHeader : SectionHeader { + u32 revision; + u32 headerSize; + u32 numSections; + u32 tickflowVariant; + }; + int BTKS::LoadFile(const std::string filename) { - this->Unload(); + Unload(); if (params.mod_loaded_msg) OSD::Notify("Loading BTKS file..."); File file(MEGAMIX_MODS_PATH + filename + ".btk", File::Mode::READ); u32 result; - char* magicBuf = new char[8]; - magicBuf[4] = 0; // terminator - u32* intBuf = new u32[1]; - //Header - result = file.Read(magicBuf, 4); // Magic + // Read header + // ------------------- + + BTKSHeader header; + result = file.Read(&header, sizeof(header)); // Magic if (result) return result; - if (strcmp(magicBuf,"BTKS")) { + + // sanity checks on the header + + if (header.magic != CHAR4_LE('B', 'T', 'K', 'S')) { return -6; // Not a BTKS file } - result = file.Read(intBuf, 4); // Filesize - not that useful rn - if (result) return result; - int filesize = *intBuf; - - result = file.Read(intBuf, 4); // Format version - supported: rev2 - if (result) return result; - int version = *intBuf; - if (version != 2) + // rev2 is the only supported revision + if (header.revision != 2) return -7; // Unsupported version - // Since the only supported version is rev2 we can expect it to always be that - - result = file.Read(intBuf, 4); // Header size - expected: 0x18 - if (result) return result; - int headerEnd = *intBuf; - - result = file.Read(intBuf, 4); // Number of sections - expected: 3 or 4 - if (result) return result; - int numSections = *intBuf; - - result = file.Read(intBuf, 4); // Tickflow format - supported: 0 (US/EU/KR) - if (result) return result; - if (!(*intBuf == 0 || (region == Region::JP && *intBuf == 1))) + if (!(header.tickflowVariant == 0 || (region == Region::JP && header.tickflowVariant == 1))) return -13; // Unsupported Tickflow format - // Seek to end of header - result = file.Seek(headerEnd, File::SeekPos::SET); + + + // Seek to end of header (in case there's some non-standard stuff idfk) + result = file.Seek(header.headerSize, File::SeekPos::SET); if (result) return result; - // Section code! This is where things get more complicated + // Read sections + // ------------------- Pointer* pointers = nullptr; int numPointers = 0; - for (int i = 0; i < numSections; i++) { - result = file.Read(magicBuf, 4); // Section magic + for (u32 i = 0; i < header.numSections; i++) { + SectionHeader secHeader; + result = file.Read(&secHeader, sizeof(secHeader)); // Section magic if (result) return result; + // TODO: separate each individual section into a function for readability + // Tickflow section - if (!strcmp(magicBuf, "FLOW")) { - result = file.Read(intBuf, 4); // Section size - if (result) return result; - tickflowSize = *intBuf - 0xC; - tickflow = new char[tickflowSize]; - result = file.Read(intBuf, 4); // Position of start sub + if (secHeader.magic == CHAR4_LE('F', 'L', 'O', 'W')) { + if (tickflowSize != 0) { + // TODO: error - repeated FLOW section + } + tickflowSize = secHeader.size - 0xC; + result = file.Read(&start, 4); // Position of start sub if (result) return result; - start = *intBuf; - if (tickflowSize > 0x100000) + if (tickflowSize > 0x100000) // 1MiB return -12; //file too big - tickflow = (char*) malloc(tickflowSize); + tickflow = new char[tickflowSize]; result = file.Read(tickflow, tickflowSize); // Tickflow data if (result) return result; } // Pointer section - else if (!strcmp(magicBuf, "PTRO")) { - result = file.Read(intBuf, 4); // Section size + else if (secHeader.magic == CHAR4_LE('P', 'T', 'R', 'O')) { + int extraBytes = secHeader.size - 0x08; + result = file.Read(&numPointers, 4); // Number of pointers if (result) return result; - int extraBytes = *intBuf - 0x08; - result = file.Read(intBuf, 4); // Number of pointers - if (result) return result; - numPointers = *intBuf; extraBytes -= 5 * numPointers; pointers = new Pointer[numPointers]; for (int i = 0; i < numPointers; i++) { @@ -99,49 +99,50 @@ namespace Megamix { } file.Read(nullptr, extraBytes); // Extra bytes that may be there for whatever reason } - else if (!strcmp(magicBuf, "STRD")) { - result = file.Read(intBuf, 4); // Section size - if (result) return result; - stringSize = *intBuf - 0x08; - if (stringSize > 0x80000) + else if (secHeader.magic == CHAR4_LE('S', 'T', 'R', 'D')) { + stringSize = secHeader.size - 0x08; + if (stringSize > 0x80000) // 500KiB return -12; //file too big strings = (char*) malloc(stringSize); result = file.Read(strings, stringSize); } - else if (!strcmp(magicBuf, "TMPO")) { - result = file.Read(intBuf, 4); // Section size + else if (secHeader.magic == CHAR4_LE('T', 'M', 'P', 'O')) { + int extraBytes = secHeader.size - 0xC; + u32 tempoAmount; + result = file.Read(&tempoAmount, 4); if (result) return result; - int extraBytes = *intBuf - 0xC; - result = file.Read(intBuf, 4); - if (result) return result; - u32 tempoAmount = *intBuf; + + // TODO: restructure this entire code for (u32 i = 0; i < tempoAmount; i++) { - result = file.Read(intBuf, 4); + u32 id; + result = file.Read(&id, 4); if (result) return result; - u32 id = *intBuf; - result = file.Read(intBuf, 4); + + u32 dataSize; + result = file.Read(&dataSize, 4); if (result) return result; - u32 dataSize = *intBuf; - result = file.Read(intBuf, 4); + + // TODO: this stinks + u32 isStreamed_; + result = file.Read(&isStreamed_, 4); if (result) return result; - bool is_streamed = *(bool*)intBuf; + bool isStreamed = isStreamed_ != 0; + Tempo* data = new Tempo[dataSize]; for (u32 i = 0; i < dataSize; i++) { - float* floatBuf = new float; - result = file.Read(floatBuf, 4); + result = file.Read(&data[i].beats, 4); + if (result) return result; + result = file.Read(&data[i].time, 4); if (result) return result; - data[i].beats = *floatBuf; - result = file.Read(intBuf, 4); + result = file.Read(&data[i].flag8, 2); if (result) return result; - data[i].time = *intBuf; - result = file.Read(intBuf, 4); + result = file.Read(&data[i].flagA, 2); if (result) return result; - data[i].flag8 = ((u16*)intBuf)[0]; - data[i].flagA = ((u16*)intBuf)[1]; } - if (is_streamed && data->flag8 != 1) { + // recalculate tempo data for streamed tempos; this is done on game load for vanilla tempos + if (isStreamed && data->flag8 != 1) { u32 time = data->time; float beats = data->beats; u32 time_added = time + 2000; @@ -152,8 +153,9 @@ namespace Megamix { TempoTable* tempo = new TempoTable; tempo->id1 = tempo->id2 = id; - tempo->unk8 = (u8)is_streamed; - tempo->unkA = 0x7D00; //seems to always be this? + tempo->isStreamed = (u8)isStreamed; + tempo->unkA = 32000; // sample rate? + tempo->pos = data; tempos[id] = tempo; @@ -168,14 +170,17 @@ namespace Megamix { if (tickflow == nullptr || strings == nullptr) { return -11; //missing required section } + + // Handle relocations + // ------------------- for (int i = 0; i < numPointers; i++) { Pointer pointer = pointers[i]; - if (pointer.pointerType == 0) - *(u32*)((u32)tickflow + pointer.pointerPos) += (u32)(strings); - else if (pointer.pointerType == 1) - *(u32*)((u32)tickflow + pointer.pointerPos) += (u32)(tickflow); - else + if (pointer.pointerType == 0) { + *(u32*)&tickflow[pointer.pointerPos] += (u32)(strings); + } else if (pointer.pointerType == 1) { + *(u32*)&tickflow[pointer.pointerPos] += (u32)(tickflow); + } else return -10; //unknown pointer type } start = (u32)tickflow + start; @@ -187,12 +192,12 @@ namespace Megamix { return 0; } + // TODO: more descriptive name void BTKS::Unload() { loaded = false; delete[] tickflow; delete[] strings; - //this may not be required? but just in case for (auto tempo: tempos) { delete[] tempo.second->pos; delete tempo.second; diff --git a/src/Megamix/Commands.cpp b/src/Megamix/Commands.cpp index 5268af2..e4fb448 100644 --- a/src/Megamix/Commands.cpp +++ b/src/Megamix/Commands.cpp @@ -6,21 +6,23 @@ #include "Megamix.hpp" #include "Saltwater.hpp" -using CTRPluginFramework::OSD; -using CTRPluginFramework::Utils; - namespace Megamix { + void input_cmd(CTickflow* self, u32 arg0, u32* args); + void versionCheck(CTickflow* self, u32 arg0, u32* args); + void languageCheck(CTickflow* self, u32 arg0, u32* args); + void displayCondvar(CTickflow* self, u32 arg0, u32* args); + void msbtWithNum(CTickflow* self, u32 arg0, u32* args); - void tickflowCommandsHookWrapper() { + void tickflowCommandsHook() { asm( "mov r0, r6\n" - "bl tickflowCommandsHook\n" + "bl tickflowCommandsHookImpl\n" "bx r0\n" ); } - extern "C" __attribute__((used)) - int tickflowCommandsHook(CTickflow* self, u32 cmd_num, u32 arg0, u32* args){ + extern "C" __attribute__((used)) + int tickflowCommandsHookImpl(CTickflow* self, u32 cmd_num, u32 arg0, u32* args){ switch(cmd_num){ // Necessary, as our hook overrides case 0. case 0: @@ -124,11 +126,11 @@ namespace Megamix { void displayCondvar(CTickflow* self, u32 arg0, u32* args) { // Keeping that just in case // Screen bottomScreen = OSD::GetBottomScreen(); - // bottomScreen.Draw("Condvar:"+Utils::Format("0x%08x",self->condvar), 0, 0, Color::White, Color::Black); + // bottomScreen.Draw("Condvar:"+Format("0x%08x",self->condvar), 0, 0, Color::White, Color::Black); if(arg0 == 0) { - OSD::Notify("Condvar:"+Utils::Format("0x%08x",self->condvar)); + OSD::Notify("Condvar:"+Format("0x%08x",self->condvar)); } else if (arg0 == 1){ - OSD::Notify("Condvar:"+Utils::Format("%08d",self->condvar)); + OSD::Notify("Condvar:"+Format("%08d",self->condvar)); } } diff --git a/src/Megamix/Hooks.cpp b/src/Megamix/Hooks.cpp index bddb612..efa65e3 100644 --- a/src/Megamix/Hooks.cpp +++ b/src/Megamix/Hooks.cpp @@ -1,7 +1,6 @@ #include <3ds.h> #include "CTRPF.hpp" -#include "Megamix/Region.hpp" #include "external/rt.h" #include "Megamix.hpp" @@ -116,8 +115,9 @@ namespace Megamix::Hooks { return region; } + // --- - void TickflowHooks() { + void initTickflowHooks() { rtInitHook(&tickflowHook, Game::Hooks::tickflow(), (u32)getTickflowOffset); rtEnableHook(&tickflowHook); rtInitHook(&gateHook, Game::Hooks::gate(), (u32)getGateTickflowOffset); @@ -126,7 +126,7 @@ namespace Megamix::Hooks { rtEnableHook(&gatePracHook); } - void TempoHooks() { + void initTempoHooks() { rtInitHook(&tempoStrmHook, Game::Hooks::strmTempo(), (u32)getTempoStrm); rtEnableHook(&tempoStrmHook); rtInitHook(&tempoSeqHook, Game::Hooks::seqTempo(), (u32)getTempoSeq); @@ -135,8 +135,9 @@ namespace Megamix::Hooks { rtEnableHook(&tempoAllHook); } - void RegionHooks() { + void initRegionHooks() { if (region != Region::JP){ + // TODO: remove when the FS stuff is replaced rtInitHook(®ionFSHook, Region::RegionFSHookFunc(), (u32)getRegionMegamix); rtEnableHook(®ionFSHook); } @@ -144,19 +145,28 @@ namespace Megamix::Hooks { rtEnableHook(®ionOtherHook); } - void CommandHook() { - rtInitHook(&tickflowCommandsHook, Region::TickflowCommandsSwitch(), (u32)tickflowCommandsHookWrapper); + void initCommandHooks() { + rtInitHook(&tickflowCommandsHook, Region::TickflowCommandsSwitch(), (u32)Megamix::tickflowCommandsHook); rtEnableHook(&tickflowCommandsHook); } - void DisableAllHooks() { + void disableTickflowHooks() { rtDisableHook(&tickflowHook); rtDisableHook(&gateHook); + } + + void disableTempoHooks() { rtDisableHook(&tempoStrmHook); rtDisableHook(&tempoSeqHook); rtDisableHook(&tempoAllHook); + } + + void disableRegionHooks() { rtDisableHook(®ionFSHook); rtDisableHook(®ionOtherHook); + } + + void disableCommandHooks() { rtDisableHook(&tickflowCommandsHook); } diff --git a/src/Megamix/Patches.cpp b/src/Megamix/Patches.cpp index ad63b2e..23a8098 100644 --- a/src/Megamix/Patches.cpp +++ b/src/Megamix/Patches.cpp @@ -10,7 +10,23 @@ #include "Config.hpp" #include "Megamix/Region.hpp" +// TODO: these currently have no way to unpatch. should we not do that? + namespace Megamix::Patches { + // see specified sections in the arm A-profile reference manual + namespace BuildInstr { + // register is 4 bits, value is 12 bits + // see F5.1.35 + static constexpr u32 cmp_immediate(u32 reg, u32 value) { + // 1110 == no condition + const u32 cond = 0b1110 << 28; + const u32 cmp_imm_base = 0b0000'00110'10'1 << 20; + reg <<= 16; + + return cond | cmp_imm_base | reg | value; + } + } + std::vector museumRows { /* 0 */ MuseumRow({ RvlKarate0, NtrRobotS, RvlBadmintonS, CtrStepS, None }, "stage_gr00", 0, 0), /* 1 */ MuseumRow({ AgbHairS, NtrChorusS, RvlMuscleS, CtrFruitbasketS, None }, "stage_gr01", 0, 1), @@ -75,17 +91,6 @@ namespace Megamix::Patches { /* 28 */ MuseumRowColor(0x78500AFF, 0x643C3200), }; - // see section F5.1.35 in the arm A-profile reference manual - // register is 4 bits, value is 12 bits - constexpr u32 make_cmp_immediate_instruction(u32 reg, u32 value) { - // 1110 == no condition - const u32 cond = 0b1110 << 28; - const u32 cmp_imm_base = 0b0000'00110'10'1 << 20; - reg <<= 16; - - return cond | cmp_imm_base | reg | value; - } - std::optional SlotIdToMuseumGameId(u16 gameId) { if (gameId < 0x100) { return gameId; @@ -196,14 +201,14 @@ namespace Megamix::Patches { } u32 compare_r1_instruction = // cmp r1, MUSEUM_ROW_COUNT - make_cmp_immediate_instruction(1, museumRows.size()); + BuildInstr::cmp_immediate(1, museumRows.size()); for (auto address : Game::pMuseumRows::r1Cmps()) { Process::Patch(address, compare_r1_instruction); } u32 compare_r8_instruction = // cmp r8, MUSEUM_ROW_COUNT - make_cmp_immediate_instruction(8, museumRows.size()); + BuildInstr::cmp_immediate(8, museumRows.size()); for (auto address : Game::pMuseumRows::r8Cmps()) { Process::Patch(address, compare_r8_instruction); diff --git a/src/main.cpp b/src/main.cpp index a01b073..5143ba2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -98,14 +98,14 @@ void CTRPF::PatchProcess(CTRPF::FwkSettings&) { config = Config::FromFile(MEGAMIX_CONFIG_PATH); // Start hooks, apply patches - Megamix::Hooks::TickflowHooks(); - Megamix::Hooks::RegionHooks(); + Megamix::Hooks::initTickflowHooks(); + Megamix::Hooks::initRegionHooks(); Megamix::Patches::PatchRetryRemix(); if (region != Region::JP) { //TODO: find out how to make the tempo hooks JP-compatible - Megamix::Hooks::TempoHooks(); + Megamix::Hooks::initTempoHooks(); //TODO: find out how to make the tickflow commands hook JP-compatible - Megamix::Hooks::CommandHook(); + Megamix::Hooks::initCommandHooks(); } if (region != Region::JP && params.extra_rows) { @@ -116,7 +116,7 @@ void CTRPF::PatchProcess(CTRPF::FwkSettings&) { // This function is called when the process exits // Useful to save settings, undo patchs or clean up things void CTRPF::OnProcessExit(void) { - Megamix::Hooks::DisableAllHooks(); // Probably not needed, but still + Megamix::Hooks::disableAllHooks(); // Probably not needed, but still ToggleTouchscreenForceOn(); } From c3487f46da9473e76ce55877c8b552b05f745cd6 Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Sat, 6 Sep 2025 01:05:20 +0200 Subject: [PATCH 09/13] [BOOTS ON US] forgot to move this one oops --- src/Megamix/Patches.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Megamix/Patches.cpp b/src/Megamix/Patches.cpp index 23a8098..715f647 100644 --- a/src/Megamix/Patches.cpp +++ b/src/Megamix/Patches.cpp @@ -25,6 +25,17 @@ namespace Megamix::Patches { return cond | cmp_imm_base | reg | value; } + + // register is 4 bits, value is 12 bits + // see F5.1.122 + static constexpr u32 mov_immediate(u32 reg, u32 value) { + // 1110 == no condition + const u32 cond = 0b1110 << 28; + const u32 cmp_imm_base = 0b0000'00111'01'0 << 20; + reg <<= 12; + + return cond | cmp_imm_base | reg | value; + } } std::vector museumRows { @@ -221,20 +232,9 @@ namespace Megamix::Patches { } } - // see section F5.1.122 in the arm A-profile reference manual - // register is 4 bits, value is 12 bits - constexpr u32 make_mov_immediate_instruction(u32 reg, u32 value) { - // 1110 == no condition - const u32 cond = 0b1110 << 28; - const u32 cmp_imm_base = 0b0000'00111'01'0 << 20; - reg <<= 12; - - return cond | cmp_imm_base | reg | value; - } - void PatchRetryRemix() { u32 instr = // mov r2, #0xE - make_mov_immediate_instruction(2, 0xE); + BuildInstr::mov_immediate(2, 0xE); for (auto loc: Game::Patches::ptrsToRetryRemix()){ Process::Patch(loc, instr); From ed5bd95cc7d23d2f5c2ab7c5cf54e6e7c62aab26 Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Sun, 21 Dec 2025 19:40:13 +0100 Subject: [PATCH 10/13] unfuck the merge --- src/Megamix/BTKS.cpp | 2 +- src/Megamix/Hooks.cpp | 8 ++++---- src/Megamix/Patches.cpp | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Megamix/BTKS.cpp b/src/Megamix/BTKS.cpp index 580648f..a436d61 100644 --- a/src/Megamix/BTKS.cpp +++ b/src/Megamix/BTKS.cpp @@ -50,7 +50,7 @@ namespace Megamix { if (header.revision != 2) return -7; // Unsupported version - if (!((isJP() && header.tickflowVariant == 0) || (isJP() && header.tickflowVariant == 1))) + if (!((!isJP() && header.tickflowVariant == 0) || (isJP() && header.tickflowVariant == 1))) return -13; // Unsupported Tickflow format // Seek to end of header (in case there's some non-standard stuff idfk) diff --git a/src/Megamix/Hooks.cpp b/src/Megamix/Hooks.cpp index 534c5ec..0a215b8 100644 --- a/src/Megamix/Hooks.cpp +++ b/src/Megamix/Hooks.cpp @@ -140,7 +140,7 @@ namespace Megamix::Hooks { // --- - void TickflowHooks() { + void initTickflowHooks() { rtInitHook(&tickflowHook, GHooks::tickflow(), (u32)getTickflowOffset); rtEnableHook(&tickflowHook); rtInitHook(&gateHook, GHooks::gate(), (u32)getGateTickflowOffset); @@ -149,7 +149,7 @@ namespace Megamix::Hooks { rtEnableHook(&gatePracHook); } - void TempoHooks() { + void initTempoHooks() { rtInitHook(&tempoStrmHook, GHooks::strmTempo(), (u32)getTempoStrm); rtEnableHook(&tempoStrmHook); rtInitHook(&tempoSeqHook, GHooks::seqTempo(), (u32)getTempoSeq); @@ -158,7 +158,7 @@ namespace Megamix::Hooks { rtEnableHook(&tempoAllHook); } - void RegionHooks() { + void initRegionHooks() { if (!Megamix::isJP()){ rtInitHook(®ionFSHook, GHooks::megamixRegionCode(), (u32)getRegionMegamix); rtEnableHook(®ionFSHook); @@ -167,7 +167,7 @@ namespace Megamix::Hooks { rtEnableHook(®ionOtherHook); } - void CommandHook() { + void initCommandHooks() { rtInitHook(&tickflowCommandsHook, Game::hTickflowCmds::hook(), (u32)tickflowCommands); rtEnableHook(&tickflowCommandsHook); } diff --git a/src/Megamix/Patches.cpp b/src/Megamix/Patches.cpp index 715f647..c52e264 100644 --- a/src/Megamix/Patches.cpp +++ b/src/Megamix/Patches.cpp @@ -8,7 +8,6 @@ #include "Megamix.hpp" #include "Config.hpp" -#include "Megamix/Region.hpp" // TODO: these currently have no way to unpatch. should we not do that? From 91ebb4b73b10a6885ca1c50dd88894707d4aeb4d Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Sun, 21 Dec 2025 20:02:59 +0100 Subject: [PATCH 11/13] unfuck museum row patch --- include/Megamix/Types.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/Megamix/Types.hpp b/include/Megamix/Types.hpp index 9bc41e5..5fec1f3 100644 --- a/include/Megamix/Types.hpp +++ b/include/Megamix/Types.hpp @@ -226,10 +226,10 @@ namespace Megamix { else if (gameIndices[4] == 0x101) columnCount = 4; else columnCount = 5; - gameIndices = gameIndices; - titleId = titleId; - highIndex = highIndex; - lowIndex = lowIndex; + this->gameIndices = gameIndices; + this->titleId = titleId; + this->highIndex = highIndex; + this->lowIndex = lowIndex; } }; @@ -271,8 +271,8 @@ namespace Megamix { unk1 = {0x00, 0x00, 0x00, 0x00}; unk3 = {0xFF, 0xFF, 0xFF, 0x6E}; - background = background; - edgeFade = edgeFade; + this->background = background; + this->edgeFade = edgeFade; } }; From 985eef5dba593ba753b37074d1e87da7841db2dd Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Fri, 23 Jan 2026 20:41:36 +0100 Subject: [PATCH 12/13] FINISH conhlee review. now to make sure there's no bugs --- Makefile | 1 - include/CTRPF.hpp | 2 - include/Megamix/Error.hpp | 3 +- include/Megamix/Region.hpp | 23 ++++++++- include/Stuff.hpp | 1 - src/Megamix/BTKS.cpp | 4 +- src/Megamix/Error.cpp | 101 +++++++++++++++++++++---------------- src/Megamix/Region.cpp | 2 - 8 files changed, 81 insertions(+), 56 deletions(-) diff --git a/Makefile b/Makefile index d6a1498..7230ebc 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,6 @@ else else ifeq ($(shell uname -s),macOS) ifeq ($(AZAHAR),1) -# TODO: triple check with a mac user CITRA_DIR := ~/Library/Application Support/Azahar/sdmc else CITRA_DIR := ~/Library/Application Support/Citra/sdmc diff --git a/include/CTRPF.hpp b/include/CTRPF.hpp index 383de15..547c381 100644 --- a/include/CTRPF.hpp +++ b/include/CTRPF.hpp @@ -10,6 +10,4 @@ using CTRPF::OSD; constexpr auto Format = CTRPF::Utils::Format; -// TODO: Format but with char*? - #endif diff --git a/include/Megamix/Error.hpp b/include/Megamix/Error.hpp index 65f3559..820cb71 100644 --- a/include/Megamix/Error.hpp +++ b/include/Megamix/Error.hpp @@ -4,8 +4,6 @@ #include #include "CTRPF.hpp" -#define CALL_STACK_SIZE 5 - namespace Megamix { std::string ErrorMessage(int code); @@ -49,6 +47,7 @@ namespace Megamix { u32 statusRegisterA; // IFSR, DFSR, FPEXC u32 statusRegisterB; // FAR, FPINST + static constexpr int CALL_STACK_SIZE = 5; u32 callStack[CALL_STACK_SIZE]; }; diff --git a/include/Megamix/Region.hpp b/include/Megamix/Region.hpp index 053303e..faa7fbc 100644 --- a/include/Megamix/Region.hpp +++ b/include/Megamix/Region.hpp @@ -10,8 +10,6 @@ #include "Megamix/Error.hpp" #include "Megamix/Types.hpp" -extern u8 region; - namespace Megamix { struct GameInterface { u32 gameCode; @@ -115,6 +113,27 @@ namespace Megamix { namespace Game { + enum class GameVariant: u8 { + JPRev0, + USRev0, + EURev0, + KRRev0, + UNK = (u8)-1, + }; + + inline GameVariant getRegion() { + if (isJP()) + return GameVariant::JPRev0; + else if (isUS()) + return GameVariant::USRev0; + else if (isEU()) + return GameVariant::EURev0; + else if (isKR()) + return GameVariant::KRRev0; + else + return GameVariant::UNK; + } + inline u32 _textEnd () { return pointers->textEnd; } inline u32 _rodataEnd() { return pointers->rodataEnd; } inline u32 _dataEnd() { return pointers->dataEnd; } diff --git a/include/Stuff.hpp b/include/Stuff.hpp index 966ed1f..058f8c1 100644 --- a/include/Stuff.hpp +++ b/include/Stuff.hpp @@ -22,7 +22,6 @@ namespace Stuff { } static inline std::string TempoMapToString(std::map in) { - std::string out = "{"; for (auto item = in.cbegin(); item != in.cend(); ++item) { out += Format("%X: Tempo of size %d, ", item->first, ((int*)item->second->pos)[-1]); diff --git a/src/Megamix/BTKS.cpp b/src/Megamix/BTKS.cpp index a436d61..b8c226a 100644 --- a/src/Megamix/BTKS.cpp +++ b/src/Megamix/BTKS.cpp @@ -37,7 +37,7 @@ namespace Megamix { // ------------------- BTKSHeader header; - result = file.Read(&header, sizeof(header)); // Magic + result = file.Read(&header, sizeof(header)); if (result) return result; // sanity checks on the header @@ -69,7 +69,7 @@ namespace Megamix { if (result) return result; // TODO: separate each individual section into a function for readability - + // Tickflow section if (secHeader.magic == CHAR4_LE('F', 'L', 'O', 'W')) { if (tickflowSize != 0) { diff --git a/src/Megamix/Error.cpp b/src/Megamix/Error.cpp index 868b56d..f55193d 100644 --- a/src/Megamix/Error.cpp +++ b/src/Megamix/Error.cpp @@ -1,3 +1,4 @@ +#include "Megamix/Error.hpp" #include <3ds.h> #include "CTRPF.hpp" @@ -14,11 +15,9 @@ using CTRPF::Key; extern const u32 _TEXT_END; extern const u32 _start; -//TODO: add enum -//TODO: exceptions? +//TODO: switch error system to something that does not suck ass namespace Megamix { - u8 errorImg[] = {}; - + // TODO: move all the strings to headers for translation reasons std::string ErrorMessage(int code) { switch (code) { // CTRPF errors @@ -104,20 +103,32 @@ namespace Megamix { } } - static bool render = true; - static bool dumped = false; - static bool full_info = false; - static bool faded = false; - - static std::string dump_location = ""; // also serves as error message if dump_error is true - static CrashInfo crash; - static bool dump_error = false; + // this has to be static because GetCrashData runs every frame for some reason + static struct { + bool render; + bool dumped; + bool full_info; + bool faded; + + std::string dump_location; + CrashInfo crash; + bool dump_error; + } s_crash_data = { + .render = true, + .dumped = false, + .full_info = false, + .faded = false, + + .dump_location = "", + .crash = {}, + .dump_error = false, + }; namespace ErrorScreen { static CrashInfo GetCrashData(ERRF_ExceptionInfo* info, CpuRegisters* regs) { CrashInfo crash; crash.info.type = CrashType::Extended; - crash.info.region = region; + crash.info.region = (u8)Game::getRegion(); crash.info.excType = info->type; #ifdef RELEASE @@ -159,7 +170,7 @@ namespace Megamix { // TODO: get call stack u32 stack_offset = 0; - for (int i = 0; i < CALL_STACK_SIZE; i++) { + for (int i = 0; i < ShortCrashInfo::CALL_STACK_SIZE; i++) { while ((u32)stack >= 0x06000000 || (u32)(stack + stack_offset) < 0x01000000) { u32 val = *(u32*)(regs->sp + stack_offset); if ((val >= 0x0010000 && val < Game::_textEnd() || (val >= (u32)_start && val < _TEXT_END))) { @@ -174,11 +185,12 @@ namespace Megamix { return crash; } + // TODO: move all the strings to headers for translation reasons static void InfoScreen(ERRF_ExceptionInfo* info, CpuRegisters* regs) { CTRPF::Screen screen = OSD::GetTopScreen(); - if (!faded) { + if (!s_crash_data.faded) { screen.Fade(0.3); - faded = true; + s_crash_data.faded = true; } screen.DrawRect(16, 16, 368, 208, CTRPF::Color(0, 0, 0)); @@ -191,11 +203,12 @@ namespace Megamix { posY = screen.Draw("Discord server (discord.gg/xAKFPaERRG)", 20, posY); posY += 10; - if (dump_location.empty()) { - if (dump_error) - posY = screen.Draw(std::string("Error while saving dump: ").append(dump_location), 20, posY); - else - posY = screen.Draw(std::string("Crash dump saved to ").append(dump_location), 20, posY); + if (s_crash_data.dump_location.empty()) { + if (s_crash_data.dump_error) { + posY = screen.Draw(std::string("Error while saving dump: ").append(s_crash_data.dump_location), 20, posY); + } else { + posY = screen.Draw(std::string("Crash dump saved to ").append(s_crash_data.dump_location), 20, posY); + } posY += 10; } else { posY = screen.Draw("> Press A to dump crash (WIP)", 20, posY); @@ -219,20 +232,20 @@ namespace Megamix { posY += 10; posY = screen.Draw("Call stack:", 20, posY); - for (int i = 0; i < CALL_STACK_SIZE; i++) { - posY = screen.Draw(Format(" - %08x", crash.info.callStack[i]), 20, posY); + for (int i = 0; i < ShortCrashInfo::CALL_STACK_SIZE; i++) { + posY = screen.Draw(Format(" - %08x", s_crash_data.crash.info.callStack[i]), 20, posY); } posY += 10; - posY = screen.Draw(Format("r0 = %08x r1 = %08x", crash.registers[0], crash.registers[1]), 20, posY); - posY = screen.Draw(Format("r2 = %08x r3 = %08x", crash.registers[2], crash.registers[3]), 20, posY); - - if (dump_location != "") { - if (dump_error) - posY = screen.Draw(std::string("Error while saving dump: ").append(dump_location), 20, posY); + posY = screen.Draw(Format("r0 = %08x r1 = %08x", s_crash_data.crash.registers[0], s_crash_data.crash.registers[1]), 20, posY); + posY = screen.Draw(Format("r2 = %08x r3 = %08x", s_crash_data.crash.registers[2], s_crash_data.crash.registers[3]), 20, posY); + + if (s_crash_data.dump_location != "") { + if (s_crash_data.dump_error) + posY = screen.Draw(std::string("Error while saving dump: ").append(s_crash_data.dump_location), 20, posY); else - posY = screen.Draw(std::string("Crash dump saved to ").append(dump_location), 20, posY); + posY = screen.Draw(std::string("Crash dump saved to ").append(s_crash_data.dump_location), 20, posY); posY += 10; } else { posY = screen.Draw("> Press A to dump crash", 20, posY); @@ -270,26 +283,26 @@ namespace Megamix { res = file.Write("SELCRAH\0", 8); if (res) return res; - res = file.Write(&crash, sizeof(crash)); + res = file.Write(&s_crash_data.crash, sizeof(s_crash_data.crash)); if (res) return res; res = file.Close(); if (res) return res; - dump_location = path; + s_crash_data.dump_location = path; return 0; } Process::ExceptionCallbackState CrashHandler(ERRF_ExceptionInfo* info, CpuRegisters* regs) { - if (!dumped) { - crash = ErrorScreen::GetCrashData(info, regs); - dumped = true; + if (!s_crash_data.dumped) { + s_crash_data.crash = ErrorScreen::GetCrashData(info, regs); + s_crash_data.dumped = true; } - if (render) { - render = false; - if (full_info) { + if (s_crash_data.render) { + s_crash_data.render = false; + if (s_crash_data.full_info) { ErrorScreen::DevScreen(info, regs); } else { ErrorScreen::InfoScreen(info, regs); @@ -302,16 +315,16 @@ namespace Megamix { return Process::ExceptionCallbackState::EXCB_RETURN_HOME; } - if (Controller::IsKeyPressed(Key::A) && dump_location == "") { + if (Controller::IsKeyPressed(Key::A) && s_crash_data.dump_location == "") { int result = SaveCrashDump(); if (result != 0) { - dump_error = true; - dump_location = ErrorMessage(result); + s_crash_data.dump_error = true; + s_crash_data.dump_location = ErrorMessage(result); } - render = true; + s_crash_data.render = true; } else if (Controller::IsKeyPressed(Key::Y)) { - render = true; - full_info = !full_info; + s_crash_data.render = true; + s_crash_data.full_info = !s_crash_data.full_info; } return Process::ExceptionCallbackState::EXCB_LOOP; diff --git a/src/Megamix/Region.cpp b/src/Megamix/Region.cpp index 79845ea..9b97c01 100644 --- a/src/Megamix/Region.cpp +++ b/src/Megamix/Region.cpp @@ -5,8 +5,6 @@ #include -u8 region; - #define THUMB_CALL_ADDR(pos) ((pos) | 1) namespace Megamix { From 0a9b98f0315497439b297ce1dec2bc6ec8f7df55 Mon Sep 17 00:00:00 2001 From: patataofcourse Date: Sun, 31 May 2026 14:59:32 +0200 Subject: [PATCH 13/13] btks: use a ternary rather than a weird and/or combo --- src/Megamix/BTKS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Megamix/BTKS.cpp b/src/Megamix/BTKS.cpp index b8c226a..65c84c5 100644 --- a/src/Megamix/BTKS.cpp +++ b/src/Megamix/BTKS.cpp @@ -50,7 +50,7 @@ namespace Megamix { if (header.revision != 2) return -7; // Unsupported version - if (!((!isJP() && header.tickflowVariant == 0) || (isJP() && header.tickflowVariant == 1))) + if (isJP() ? (header.tickflowVariant != 1) : (header.tickflowVariant != 0)) return -13; // Unsupported Tickflow format // Seek to end of header (in case there's some non-standard stuff idfk)