From b2cf295010056aacf275202db581916839187ada Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 13 Feb 2024 08:28:36 +0000 Subject: [PATCH 001/123] try to fix bug when missing song data specifies song id --- source/SongHooks.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/source/SongHooks.c b/source/SongHooks.c index 62e0353..a8a7ced 100644 --- a/source/SongHooks.c +++ b/source/SongHooks.c @@ -51,21 +51,21 @@ int MetadataSongIDHook(DataNode *song_id) int GetSongIDHook(DataArray *song, DataArray *missing_data_maybe) { Symbol song_id; - DataNode *found; - DataArray *array; + DataNode *found = NULL; + DataArray *array = NULL; SymbolConstruct(&song_id, "song_id"); if (song == NULL) return 0; - array = DataFindArray(song, song_id); + // check missing song data first if we have it + if (missing_data_maybe != NULL) + array = DataFindArray(missing_data_maybe, song_id); + // if there's nothing in missing song data, check song array if (array == NULL) - { - if (missing_data_maybe == NULL) - return 0; - else - array = DataFindArray(missing_data_maybe, song_id); - } + array = DataFindArray(song, song_id); + // no song_id? idk man if (array == NULL) return 0; + // get the value from song_id found = DataNodeEvaluate(&array->mNodes->n[1]); if (found == NULL) return 0; From 700977ac811673319d52ed5c7e988bd0925adcf9 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 16 Mar 2024 22:16:14 +0000 Subject: [PATCH 002/123] temporarily disable wii usb hooks until i get it working --- source/rb3enhanced.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 4415c8d..2df2da8 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -357,7 +357,7 @@ void ApplyHooks() HookFunction(PORT_UPDATEPRESENCE, &UpdatePresence, &UpdatePresenceHook); #ifdef RB3E_WII // wii exclusive hooks - HookFunction(PORT_USBWIIGETTYPE, &UsbWiiGetType, &UsbWiiGetTypeHook); + // HookFunction(PORT_USBWIIGETTYPE, &UsbWiiGetType, &UsbWiiGetTypeHook); HookFunction(PORT_WIINETINIT_DNSLOOKUP, &StartDNSLookup, &StartDNSLookupHook); #elif RB3E_XBOX // 360 exclusive hooks HookFunction(PORT_STAGEKIT_SET_STATE, &StagekitSetState, &StagekitSetStateHook); From 2c5a38ea366700a61684b744cd04898c81d38064 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Wed, 3 Apr 2024 19:53:21 +0100 Subject: [PATCH 003/123] File vtable --- include/rb3/File.h | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/include/rb3/File.h b/include/rb3/File.h index 376e0df..ac251e2 100644 --- a/include/rb3/File.h +++ b/include/rb3/File.h @@ -1,7 +1,51 @@ #ifndef _FILE_H #define _FILE_H +#include "String.h" + +typedef struct _File File; + +typedef void *(*FileDestructor_t)(File *thisFile); +typedef String *(*FileFilename_t)(String *str, File *thisFile); +typedef int (*FileRead_t)(File *thisFile, void *iData, int iBytes); +typedef char (*FileReadAsync_t)(File *thisFile, void *iData, int iBytes); +typedef int (*FileWrite_t)(File *thisFile, void *iData, int iBytes); +typedef char (*FileWriteAsync_t)(File *thisFile, void *iData, int iBytes); +typedef int (*FileSeek_t)(File *thisFile, int iOffset, int iSeekType); +typedef int (*FileTell_t)(File *thisFile); +typedef void (*FileFlush_t)(File *thisFile); +typedef char (*FileEof_t)(File *thisFile); +typedef char (*FileFail_t)(File *thisFile); +typedef int (*FileSize_t)(File *thisFile); +typedef int (*FileUncompressedSize_t)(File *thisFile); +typedef char (*FileReadDone_t)(File *thisFile, int *oBytes); +typedef char (*FileWriteDone_t)(File *thisFile, int *oBytes); + +typedef struct _File_vtable +{ + FileDestructor_t destructor; + FileFilename_t Filename; + FileRead_t Read; + FileReadAsync_t ReadAsync; + FileWrite_t Write; + FileWriteAsync_t WriteAsync; + FileSeek_t Seek; + FileTell_t Tell; + FileFlush_t Flush; + FileEof_t Eof; + FileFail_t Fail; + FileSize_t Size; + FileUncompressedSize_t UncompressedSize; + FileReadDone_t ReadDone; + FileWriteDone_t WriteDone; +} File_vtable; + +struct _File +{ + File_vtable *vtable; +}; + // flags can be used to check the existence of files both inside and outside the ARK, similar to how the NewFile flags work char FileExists(char *path, int flags); -#endif // _FILE_H \ No newline at end of file +#endif // _FILE_H From bfcd5deee8d9308d3f5ae5cda89a1c21671466c7 Mon Sep 17 00:00:00 2001 From: ihatecompvir Date: Tue, 16 Apr 2024 13:50:52 -0700 Subject: [PATCH 004/123] Patch to fix reading version 34 meshes --- include/MiloSceneHooks.h | 4 +++- include/ports.h | 7 +++++++ include/rb3/BinStream.h | 5 +++++ include/rb3/Vector.h | 25 +++++++++++++++++++++++++ include/rb3_include.h | 1 + source/MiloSceneHooks.c | 22 ++++++++++++++++++++++ source/_functions.c | 4 ++++ source/rb3enhanced.c | 6 ++++++ 8 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 include/rb3/Vector.h diff --git a/include/MiloSceneHooks.h b/include/MiloSceneHooks.h index 67b889e..1d824a0 100644 --- a/include/MiloSceneHooks.h +++ b/include/MiloSceneHooks.h @@ -6,6 +6,8 @@ #include "rb3/BinStream.h" #include "rb3/DirLoader.h" #include "rb3/Object.h" +#include "rb3/Vector.h" // void DirLoaderOpenFileHook(DirLoader *thisDirLoader); -void LoadObj(Object *object, BinStream *stream); \ No newline at end of file +void LoadObj(Object *object, BinStream *stream); +void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3); \ No newline at end of file diff --git a/include/ports.h b/include/ports.h index e74abcf..70a3aef 100644 --- a/include/ports.h +++ b/include/ports.h @@ -46,6 +46,8 @@ #define PORT_LOADOBJS_BCTRL 0x827562e4 // bctrl to Object::PreLoad insie of DirLoader::LoadObjs #define PORT_SONGMGR_ISDEMO_CHECK 0x82575f9c // "bne" after IsUGC check inside SongMgr::IsDemo #define PORT_STAGEKIT_EXISTS 0x8228d03c // StageKit check. nop over to allow for fog command without a physical StageKit connected. +#define PORT_VERTEX_READ_1 0x82418704 // call to Vector3::operator>> to read vertex position +#define PORT_VERTEX_READ_2 0x82418748 // call to Vector3::operator>> to read vertex normals // function patch addresses #define PORT_SETDISKERROR 0x82516320 // PlatformMgr::SetDiskError #define PORT_APP_RUN 0x82272e90 // App::Run @@ -127,6 +129,10 @@ #define PORT_INITSONGMETADATA 0x827aa450 // InitSongMetadata #define PORT_UPDATEPRESENCE 0x82680430 // PresenceMgr::UpdatePresence #define PORT_STEPSEQUENCEJOBSETSTEP 0x82af92b8 // Quazal::StepSequenceJob::SetStep +#define PORT_BINSTREAMWRITE 0x827c4f58 // BinStream::Write +#define PORT_BINSTREAMREAD 0x827c4ea8 // BinStream::Read +#define PORT_BINSTREAMREADENDIAN 0x827c5058 // BinStream::ReadEndian +#define PORT_BINSTREAMWRITEENDIAN 0x827c5098 // BinStream::WriteEndian // instance addresses #define PORT_MODIFIERMGR_POINTER 0x82dfec08 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x82cc8f60 // address of RockCentralGateway @@ -142,6 +148,7 @@ #define PORT_THEMUSICLIBRARY 0x82dfd3a8 // pointer to TheMusicLibrary #define PORT_THEGAME 0x82e02128 // pointer to TheGame (you lost) #define PORT_OBJECTDIRMAINDIR 0x82e054b8 // ObjectDir::sMainDir +#define PORT_MESH_GREV 0x82cc2638 // address of RndMesh::gRev // import function stubs #define PORT_XEKEYSSETKEY_STUB 0x82c4c47c #define PORT_XEKEYSAESCBC_STUB 0x82c4c48c diff --git a/include/rb3/BinStream.h b/include/rb3/BinStream.h index 49b12a6..bef967a 100644 --- a/include/rb3/BinStream.h +++ b/include/rb3/BinStream.h @@ -28,4 +28,9 @@ struct _BinStream BinStream_vtable *vtable; }; +extern void BinstreamWrite(void *thisBinStream, void *data, int size); +extern void BinstreamRead(void *thisBinStream, void *data, int size); +extern void BinstreamReadEndian(void *thisBinStream, void *data, int size); +extern void BinstreamWriteEndian(void *thisBinStream, void *data, int size); + #endif // _BINSTREAM_H \ No newline at end of file diff --git a/include/rb3/Vector.h b/include/rb3/Vector.h new file mode 100644 index 0000000..4d778fd --- /dev/null +++ b/include/rb3/Vector.h @@ -0,0 +1,25 @@ +#ifndef _VECTOR_H +#define _VECTOR_H + +typedef struct _Vector2 +{ + float x; + float y; +} Vector2; + +typedef struct _Vector3 +{ + float x; + float y; + float z; +} Vector3; + +typedef struct _Vector4 +{ + float x; + float y; + float z; + float w; +} Vector4; + +#endif // _VECTOR_H \ No newline at end of file diff --git a/include/rb3_include.h b/include/rb3_include.h index d786164..f03e893 100644 --- a/include/rb3_include.h +++ b/include/rb3_include.h @@ -36,6 +36,7 @@ #include "rb3/TextStream.h" #include "rb3/TrackPanelDirBase.h" #include "rb3/UsbWii.h" +#include "rb3/Vector.h" #include "quazal/InetAddress.h" #include "quazal/QuazalSocket.h" diff --git a/source/MiloSceneHooks.c b/source/MiloSceneHooks.c index f00666b..389af3a 100644 --- a/source/MiloSceneHooks.c +++ b/source/MiloSceneHooks.c @@ -65,5 +65,27 @@ void LoadObj(Object *object, BinStream *stream) object_pre_load: object->table->preLoad(object, stream); + return; +} + +// This hook allows for GH2-360/RB1/RB2 meshes to load correctly in RB3 +void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3) +{ + // the gRev of the current mesh + int gRev = *(int *)PORT_MESH_GREV; + char empty[4] = {0}; + + BinstreamReadEndian(thisBinStream, (void *)&vec3->x, 4); + BinstreamReadEndian(thisBinStream, (void *)&vec3->y, 4); + BinstreamReadEndian(thisBinStream, (void *)&vec3->z, 4); + + // if the current RndMesh being read is the GH2-360/RB1/RB2 format, we need to skip over the W component + // RB3 (for whatever reason) expects vertices in these versions of meshes to not include W + // even though they *do* in actual GH2-360/RB1/RB2 meshes + if (gRev == 34) + { + BinstreamReadEndian(thisBinStream, (void *)&empty, 4); + } + return; } \ No newline at end of file diff --git a/source/_functions.c b/source/_functions.c index c801ab7..e88a127 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -92,3 +92,7 @@ RB3E_STUB(QueuingSocketBind) RB3E_STUB(OperatorEqualsFmt) RB3E_STUB(UpdatePresence) RB3E_STUB(StepSequenceJobSetStep) +RB3E_STUB(BinstreamWrite) +RB3E_STUB(BinstreamRead) +RB3E_STUB(BinstreamReadEndian) +RB3E_STUB(BinstreamWriteEndian) \ No newline at end of file diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 2df2da8..199a460 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -323,6 +323,10 @@ void InitialiseFunctions() POKE_B(&JoypadGetPadData, PORT_JOYPADGETPADDATA); POKE_B(&MemAlloc, PORT_MEMALLOC); POKE_B(&MemFree, PORT_MEMFREE); + POKE_B(&BinstreamWrite, PORT_BINSTREAMWRITE); + POKE_B(&BinstreamRead, PORT_BINSTREAMREAD); + POKE_B(&BinstreamWriteEndian, PORT_BINSTREAMWRITEENDIAN); + POKE_B(&BinstreamReadEndian, PORT_BINSTREAMREADENDIAN); RB3E_MSG("Functions initialized!", NULL); } @@ -333,6 +337,8 @@ void ApplyHooks() POKE_B(PORT_BUILDINSTRUMENTSELECTION, &BuildInstrumentSelectionList); POKE_BL(PORT_OPTIONSTR_DEFINE, &DefinesHook); POKE_BL(PORT_RUNLOOP_SPARE, &RB3E_RunLoop); + POKE_BL(PORT_VERTEX_READ_1, &VertexReadHook); + POKE_BL(PORT_VERTEX_READ_2, &VertexReadHook); HookFunction(PORT_LOCALIZE, &Localize, &LocalizeHook); HookFunction(PORT_WILLBENOSTRUM, &WillBeNoStrum, &WillBeNoStrumHook); HookFunction(PORT_ADDGAMEGEM, &AddGameGem, &AddGameGemHook); From 544173351343a5349756e476ed07f456ac3c43ef Mon Sep 17 00:00:00 2001 From: ihatecompvir Date: Wed, 17 Apr 2024 16:16:14 -0700 Subject: [PATCH 005/123] Fix Wii build + note shuffle --- include/GlobalSymbols.h | 1 + include/ports.h | 5 +++++ source/GemHooks.c | 43 +++++++++++++++++++++++++++++++---------- source/GlobalSymbols.c | 1 + source/LocaleHooks.c | 4 ++-- source/MiloSceneHooks.c | 2 ++ source/rb3enhanced.c | 5 +++-- 7 files changed, 47 insertions(+), 14 deletions(-) diff --git a/include/GlobalSymbols.h b/include/GlobalSymbols.h index 35677dc..787c727 100644 --- a/include/GlobalSymbols.h +++ b/include/GlobalSymbols.h @@ -26,6 +26,7 @@ typedef struct _GlobalSymbols Symbol colorShuffle; Symbol mirrorMode; Symbol blackBackground; + Symbol gemShuffle; // gem widgets Symbol greenGem; diff --git a/include/ports.h b/include/ports.h index 70a3aef..baff9cc 100644 --- a/include/ports.h +++ b/include/ports.h @@ -61,6 +61,7 @@ #define PORT_SYMBOL_CT 0x827c0728 // Symbol::Symbol #define PORT_LOCALIZE 0x827c96d8 // Locale::Localize #define PORT_ADDGAMEGEM 0x8278e530 // GameGemList::AddGameGem +#define PORT_SONGDATAADDMULTIGEM 0x827719b0 // SongData::AddMultiGem #define PORT_WILLBENOSTRUM 0x8278cbb0 // GameGemList::WillBeNoStrum #define PORT_SETVENUE 0x8257d1c0 // MetaPerformer::SetVenue(?) (actual func name is not known) #define PORT_ISUGCPLUS 0x8259e890 // function that checks song source(?) @@ -255,6 +256,10 @@ void DbgPrint(const char *s, ...); #define PORT_INITSONGMETADATA 0x805147a4 // InitSongMetadata #define PORT_UPDATEPRESENCE 0x801879d4 // PresenceMgr::UpdatePresence #define PORT_STEPSEQUENCEJOBSETSTEP 0x80025364 // Quazal::StepSequenceJob::SetStep +#define PORT_BINSTREAMWRITE 0x80342f48 // BinStream::Write +#define PORT_BINSTREAMREAD 0x80342e7c // BinStream::Read +#define PORT_BINSTREAMREADENDIAN 0x803430bc // BinStream::ReadEndian +#define PORT_BINSTREAMWRITEENDIAN 0x80343190 // BinStream::WriteEndian // instance addresses #define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway diff --git a/source/GemHooks.c b/source/GemHooks.c index 87340f7..d2cf90e 100644 --- a/source/GemHooks.c +++ b/source/GemHooks.c @@ -99,27 +99,50 @@ Symbol GetSlotColorHook(int *bandUser) return slotColor; } -int AddGameGemHook(int *gameGemList, GameGem *gem, NoStrumState gemType) +void shuffleColors(GameGem *gem) +{ + char colors[5] = {gem->green, gem->red, gem->yellow, gem->blue, gem->orange}; + int i; + + for (i = 4; i > 0; i--) + { + int j = RandomInt(0, 0x7FFFFFFF) % (i + 1); + char temp = colors[i]; + colors[i] = colors[j]; + colors[j] = temp; + } + + // Assign the shuffled colors back + gem->green = colors[0]; + gem->red = colors[1]; + gem->yellow = colors[2]; + gem->blue = colors[3]; + gem->orange = colors[4]; +} + +int AddGameGemHook(void *gameGemList, GameGem *gem, NoStrumState gemType) { Modifier *mirrorModeModifier; + Modifier *gemShuffleModifier; char origGreen = gem->green; char origRed = gem->red; - // char origYellow = gem->yellow; char origBlue = gem->blue; char origOrange = gem->orange; mirrorModeModifier = ModifierActive(*(int *)PORT_MODIFIERMGR_POINTER, globalSymbols.mirrorMode, 0); + gemShuffleModifier = ModifierActive(*(int *)PORT_MODIFIERMGR_POINTER, globalSymbols.gemShuffle, 0); + if (gemShuffleModifier->enabled) + { + shuffleColors(gem); + } if (mirrorModeModifier->enabled) { - if (gemType == kStrumDefault) - { - gem->green = origOrange; - gem->red = origBlue; - // yellow doesn't need to be swapped - gem->blue = origRed; - gem->orange = origGreen; - } + gem->green = origOrange; + gem->red = origBlue; + // yellow doesn't need to be swapped + gem->blue = origRed; + gem->orange = origGreen; return AddGameGem(gameGemList, gem, gemType); } else diff --git a/source/GlobalSymbols.c b/source/GlobalSymbols.c index 78e564d..ee01767 100644 --- a/source/GlobalSymbols.c +++ b/source/GlobalSymbols.c @@ -41,6 +41,7 @@ void InitGlobalSymbols() SymbolConstruct(&globalSymbols.colorShuffle, "mod_color_shuffle"); SymbolConstruct(&globalSymbols.forceHopos, "mod_force_hopos"); SymbolConstruct(&globalSymbols.mirrorMode, "mod_mirror_mode"); + SymbolConstruct(&globalSymbols.gemShuffle, "mod_gem_shuffle"); SymbolConstruct(&globalSymbols.greenGem, "gem_green.wid"); SymbolConstruct(&globalSymbols.redGem, "gem_red.wid"); diff --git a/source/LocaleHooks.c b/source/LocaleHooks.c index 89419ef..1dfbb1b 100644 --- a/source/LocaleHooks.c +++ b/source/LocaleHooks.c @@ -39,9 +39,9 @@ static int numOverrideLocales = sizeof(overrideLocales) / sizeof(overrideLocales static char *newLocales[][2] = { {"mod_black_background", "Black Background"}, {"mod_force_hopos", "Force HOPOs"}, - {"mod_mirror_mode", "Mirror Mode (Guitar Only)"}, + {"mod_mirror_mode", "Mirror Mode"}, {"mod_color_shuffle", "Gem Color Shuffle"}, -}; + {"mod_gem_shuffle", "Note Shuffle"}}; static int numNewLocales = sizeof(newLocales) / sizeof(newLocales[0]); char RB3E_ActiveLocale[RB3E_LANG_LEN + 1] = {0}; diff --git a/source/MiloSceneHooks.c b/source/MiloSceneHooks.c index 389af3a..aea1bce 100644 --- a/source/MiloSceneHooks.c +++ b/source/MiloSceneHooks.c @@ -71,6 +71,7 @@ void LoadObj(Object *object, BinStream *stream) // This hook allows for GH2-360/RB1/RB2 meshes to load correctly in RB3 void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3) { +#ifdef RB3E_XBOX // the gRev of the current mesh int gRev = *(int *)PORT_MESH_GREV; char empty[4] = {0}; @@ -88,4 +89,5 @@ void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3) } return; +#endif } \ No newline at end of file diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 199a460..5596f75 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -111,6 +111,7 @@ void *ModifierManagerConstructorHook(int thisModifierManager, int unk) ExecuteDTA(PORT_ROCKCENTRALGATEWAY, "{do{push_back {find $syscfg modifiers modifiers} (mod_force_hopos)}}"); ExecuteDTA(PORT_ROCKCENTRALGATEWAY, "{do{push_back {find $syscfg modifiers modifiers} (mod_mirror_mode)}}"); ExecuteDTA(PORT_ROCKCENTRALGATEWAY, "{do{push_back {find $syscfg modifiers modifiers} (mod_color_shuffle)}}"); + ExecuteDTA(PORT_ROCKCENTRALGATEWAY, "{do{push_back {find $syscfg modifiers modifiers} (mod_gem_shuffle)}}"); return ModifierManagerConstructor(thisModifierManager, unk); } @@ -337,8 +338,6 @@ void ApplyHooks() POKE_B(PORT_BUILDINSTRUMENTSELECTION, &BuildInstrumentSelectionList); POKE_BL(PORT_OPTIONSTR_DEFINE, &DefinesHook); POKE_BL(PORT_RUNLOOP_SPARE, &RB3E_RunLoop); - POKE_BL(PORT_VERTEX_READ_1, &VertexReadHook); - POKE_BL(PORT_VERTEX_READ_2, &VertexReadHook); HookFunction(PORT_LOCALIZE, &Localize, &LocalizeHook); HookFunction(PORT_WILLBENOSTRUM, &WillBeNoStrum, &WillBeNoStrumHook); HookFunction(PORT_ADDGAMEGEM, &AddGameGem, &AddGameGemHook); @@ -372,6 +371,8 @@ void ApplyHooks() POKE_B(PORT_GETSONGID, &GetSongIDHook); POKE_BL(PORT_SONG_ID_EVALUATE, &MetadataSongIDHook); POKE_BL(PORT_LOADOBJS_BCTRL, &LoadObj); + POKE_BL(PORT_VERTEX_READ_1, &VertexReadHook); + POKE_BL(PORT_VERTEX_READ_2, &VertexReadHook); #endif RB3E_MSG("Hooks applied!", NULL); } From 3d67774d4a0826ac438656f0710da29be461893f Mon Sep 17 00:00:00 2001 From: ihatecompvir Date: Mon, 24 Jun 2024 21:10:51 -0700 Subject: [PATCH 006/123] Support 2x bass pedal on MIDI note 95 allows 2x bass pedal versions of songs to exist in one MIDI. plus there is a new modifier to toggle this --- include/GlobalSymbols.h | 1 + include/SongParserHooks.h | 9 +++++++++ include/ports.h | 2 ++ include/rb3/SongParser.h | 20 ++++++++++++++++++++ include/rb3_include.h | 1 + include/rb3e_include.h | 1 + source/GlobalSymbols.c | 1 + source/LocaleHooks.c | 3 ++- source/SongParserHooks.c | 39 +++++++++++++++++++++++++++++++++++++++ source/_functions.c | 5 +++-- source/rb3enhanced.c | 2 ++ 11 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 include/SongParserHooks.h create mode 100644 include/rb3/SongParser.h create mode 100644 source/SongParserHooks.c diff --git a/include/GlobalSymbols.h b/include/GlobalSymbols.h index 787c727..f006498 100644 --- a/include/GlobalSymbols.h +++ b/include/GlobalSymbols.h @@ -27,6 +27,7 @@ typedef struct _GlobalSymbols Symbol mirrorMode; Symbol blackBackground; Symbol gemShuffle; + Symbol doubleBass; // gem widgets Symbol greenGem; diff --git a/include/SongParserHooks.h b/include/SongParserHooks.h new file mode 100644 index 0000000..567021d --- /dev/null +++ b/include/SongParserHooks.h @@ -0,0 +1,9 @@ +/* + RB3Enhanced - SongParserHooks.h + Hooks related to the parsing of song MIDIs. +*/ + +#include "rb3/SongParser.h" +#include "ports.h" + +int SongParserPitchToSlotHook(SongParser *thisSongParser, int pitch, int *difficulty, int tick); \ No newline at end of file diff --git a/include/ports.h b/include/ports.h index baff9cc..e09d4e8 100644 --- a/include/ports.h +++ b/include/ports.h @@ -134,6 +134,7 @@ #define PORT_BINSTREAMREAD 0x827c4ea8 // BinStream::Read #define PORT_BINSTREAMREADENDIAN 0x827c5058 // BinStream::ReadEndian #define PORT_BINSTREAMWRITEENDIAN 0x827c5098 // BinStream::WriteEndian +#define PORT_SONGPARSERPITCHTOSLOT 0x82783c20 // SongParser::PitchToSlot // instance addresses #define PORT_MODIFIERMGR_POINTER 0x82dfec08 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x82cc8f60 // address of RockCentralGateway @@ -260,6 +261,7 @@ void DbgPrint(const char *s, ...); #define PORT_BINSTREAMREAD 0x80342e7c // BinStream::Read #define PORT_BINSTREAMREADENDIAN 0x803430bc // BinStream::ReadEndian #define PORT_BINSTREAMWRITEENDIAN 0x80343190 // BinStream::WriteEndian +#define PORT_SONGPARSERPITCHTOSLOT 0x8048e298 // SongParser::PitchToSlot // instance addresses #define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway diff --git a/include/rb3/SongParser.h b/include/rb3/SongParser.h new file mode 100644 index 0000000..e78cb9b --- /dev/null +++ b/include/rb3/SongParser.h @@ -0,0 +1,20 @@ +#ifndef _SONGPARSER_H +#define _SONGPARSER_H + +#include "Symbol.h" +#include "BandUser.h" + +// TODO: fill out the rest of this, there is a lot of useful stuff in this class that could be useful for controlling how songs load +typedef struct _SongParser +{ +#ifdef RB3E_WII + char pad[0xdc]; +#else + char pad[0xf8]; +#endif + TrackType mTrackType; +} SongParser; + +extern int SongParserPitchToSlot(SongParser *thisSongParser, int pitch, int *difficulty, int tick); + +#endif // _SONGPARSER_H diff --git a/include/rb3_include.h b/include/rb3_include.h index f03e893..5100c0d 100644 --- a/include/rb3_include.h +++ b/include/rb3_include.h @@ -29,6 +29,7 @@ #include "rb3/Random.h" #include "rb3/RockCentralGateway.h" #include "rb3/SongMetadata.h" +#include "rb3/SongParser.h" #include "rb3/SongSortByRecentEntry.h" #include "rb3/SortNode.h" #include "rb3/String.h" diff --git a/include/rb3e_include.h b/include/rb3e_include.h index 2f63dd2..20e56da 100644 --- a/include/rb3e_include.h +++ b/include/rb3e_include.h @@ -25,6 +25,7 @@ #include "RndPropAnimHooks.h" #include "SetlistHooks.h" #include "SongHooks.h" +#include "SongParserHooks.h" #include "SongSort.h" #include "SpeedHooks.h" #include "utilities.h" diff --git a/source/GlobalSymbols.c b/source/GlobalSymbols.c index ee01767..ec759e2 100644 --- a/source/GlobalSymbols.c +++ b/source/GlobalSymbols.c @@ -42,6 +42,7 @@ void InitGlobalSymbols() SymbolConstruct(&globalSymbols.forceHopos, "mod_force_hopos"); SymbolConstruct(&globalSymbols.mirrorMode, "mod_mirror_mode"); SymbolConstruct(&globalSymbols.gemShuffle, "mod_gem_shuffle"); + SymbolConstruct(&globalSymbols.doubleBass, "mod_double_bass"); SymbolConstruct(&globalSymbols.greenGem, "gem_green.wid"); SymbolConstruct(&globalSymbols.redGem, "gem_red.wid"); diff --git a/source/LocaleHooks.c b/source/LocaleHooks.c index 1dfbb1b..8b3c3ca 100644 --- a/source/LocaleHooks.c +++ b/source/LocaleHooks.c @@ -41,7 +41,8 @@ static char *newLocales[][2] = { {"mod_force_hopos", "Force HOPOs"}, {"mod_mirror_mode", "Mirror Mode"}, {"mod_color_shuffle", "Gem Color Shuffle"}, - {"mod_gem_shuffle", "Note Shuffle"}}; + {"mod_gem_shuffle", "Note Shuffle"}, + {"mod_double_bass", "Double Bass Pedal"}}; static int numNewLocales = sizeof(newLocales) / sizeof(newLocales[0]); char RB3E_ActiveLocale[RB3E_LANG_LEN + 1] = {0}; diff --git a/source/SongParserHooks.c b/source/SongParserHooks.c new file mode 100644 index 0000000..7be6749 --- /dev/null +++ b/source/SongParserHooks.c @@ -0,0 +1,39 @@ +/* + RB3Enhanced - SongHooks.c + Hooks related to the parsing of song MIDIs. +*/ + +#include +#include +#include "SongParserHooks.h" +#include "rb3/ModifierManager.h" +#include "GlobalSymbols.h" + +int SongParserPitchToSlotHook(SongParser *thisSongParser, int pitch, int *difficulty, int tick) +{ + Modifier *doubleBassModifier; + + doubleBassModifier = ModifierActive(*(int *)PORT_MODIFIERMGR_POINTER, globalSymbols.doubleBass, 0); + + // only read the notes when double bass pedal modifier is on + if (doubleBassModifier->enabled) + { + // make sure the current track type is drums + if (thisSongParser->mTrackType == DRUMS) + { + // check for MIDI note pitch 95 (which is the 2x bass pedal note) + if (pitch == 95) + { + // the original function is expected to modify the difficulty value depending on the midi note pitch (which is why it is a ptr), so do that here + // we don't need to worry about the other difficulties since this is purely expert only. although adding support for those in the future would be easy + if (*difficulty == 4) + { + *difficulty = 3; + return 0; + } + } + } + } + + return SongParserPitchToSlot(thisSongParser, pitch, difficulty, tick); +} \ No newline at end of file diff --git a/source/_functions.c b/source/_functions.c index e88a127..7643455 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -15,7 +15,7 @@ #define RB3E_STUB(x) \ __declspec(naked) void x() \ { \ - __asm { li r3, __LINE__ } \ + __asm { li r3, __LINE__ } \ } #else // other platforms should use GCC notation #define RB3E_STUB(x) \ @@ -95,4 +95,5 @@ RB3E_STUB(StepSequenceJobSetStep) RB3E_STUB(BinstreamWrite) RB3E_STUB(BinstreamRead) RB3E_STUB(BinstreamReadEndian) -RB3E_STUB(BinstreamWriteEndian) \ No newline at end of file +RB3E_STUB(BinstreamWriteEndian) +RB3E_STUB(SongParserPitchToSlot) \ No newline at end of file diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 5596f75..2d7e979 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -112,6 +112,7 @@ void *ModifierManagerConstructorHook(int thisModifierManager, int unk) ExecuteDTA(PORT_ROCKCENTRALGATEWAY, "{do{push_back {find $syscfg modifiers modifiers} (mod_mirror_mode)}}"); ExecuteDTA(PORT_ROCKCENTRALGATEWAY, "{do{push_back {find $syscfg modifiers modifiers} (mod_color_shuffle)}}"); ExecuteDTA(PORT_ROCKCENTRALGATEWAY, "{do{push_back {find $syscfg modifiers modifiers} (mod_gem_shuffle)}}"); + ExecuteDTA(PORT_ROCKCENTRALGATEWAY, "{do{push_back {find $syscfg modifiers modifiers} (mod_double_bass)}}"); return ModifierManagerConstructor(thisModifierManager, unk); } @@ -360,6 +361,7 @@ void ApplyHooks() HookFunction(PORT_SYMBOLPREINIT, &SymbolPreInit, &SymbolPreInitHook); HookFunction(PORT_INITSONGMETADATA, &InitSongMetadata, &InitSongMetadataHook); HookFunction(PORT_UPDATEPRESENCE, &UpdatePresence, &UpdatePresenceHook); + HookFunction(PORT_SONGPARSERPITCHTOSLOT, &SongParserPitchToSlot, &SongParserPitchToSlotHook); #ifdef RB3E_WII // wii exclusive hooks // HookFunction(PORT_USBWIIGETTYPE, &UsbWiiGetType, &UsbWiiGetTypeHook); From 98e734db935a1a93b067c63cf77b4998bd1815a9 Mon Sep 17 00:00:00 2001 From: ihatecompvir Date: Tue, 25 Jun 2024 07:14:44 -0700 Subject: [PATCH 007/123] Unlock all clothing no longer shows lock icon --- include/ports.h | 120 ++++++++++++++++++++++--------------------- source/rb3enhanced.c | 4 +- 2 files changed, 63 insertions(+), 61 deletions(-) diff --git a/include/ports.h b/include/ports.h index e09d4e8..cb64f8e 100644 --- a/include/ports.h +++ b/include/ports.h @@ -5,49 +5,50 @@ #ifdef RB3E_XBOX // Rock Band 3 Xbox 360 Title Update 5 // instruction patch addresses -#define PORT_SONGLIMIT 0x82579880 // call to "max_song_count" DataNode::_value -#define PORT_APP_CALL 0x82272e88 // call to App::_ct from main() -#define PORT_SONGBLACKLIST 0x82579098 // call to a function that checks song blacklist -#define PORT_DATAINITFUNCS_TAIL 0x82765980 // blr of DataInitFuncs -#define PORT_FASTSTART_CHECK 0x82270f40 // beq after OptionBool("fast",0) in App::_ct -#define PORT_SYSTEMINIT_BLANK 0x825113a4 // call to a stub function in SystemInit -#define PORT_XL_BYPASS_SG 0x82a88268 // lbz r11, bypassSecureGateway in Quazal::XboxClient::Login2 -#define PORT_RCG_POLL_LOGGEDIN 0x824f7178 // check for login status in RockCentralGateway::Poll -#define PORT_AUD_PATCH_CRASH 0x82b8c928 // patch somewhere to prevent Audition Mode crashes - blr -#define PORT_AUD_PATCH_NET1 0x82b8f7fc // patch somewhere to force Audition Mode to be connected (1) - nop -#define PORT_AUD_PATCH_NET2 0x82b8f80c // patch somewhere to force Audition Mode to be connected (2) - nop -#define PORT_AUD_PATCH_NET3 0x82b8f814 // patch somewhere to force Audition Mode to be connected (3) - nop -#define PORT_AUD_PATCH_NET4 0x82b8f81c // patch somewhere to force Audition Mode to be connected (4) - li r3, 0 -#define PORT_AUD_PATCH_NET5 0x82562638 // patch somewhere to force Audition Mode to be connected (5) - li r3, 1 -#define PORT_AUD_PATCH_REPL 0x82b8e978 // patch to allow Audition to recieve insecure packets from Magma -#define PORT_AUD_PATCH_HDD 0x82515dd4 // patch to make has_hard_drive always return true, probably broken -#define PORT_AUD_PATCH_UNK 0x823f6074 // idk -#define PORT_AUD_HANDLE_MESSAGES 0x82563edc // part of AuditionMgr::Handle that handles disconnected from Rock Central/XBL/etc. messages -#define PORT_AUD_HANDLE_MSG_JUMP 0x82564048 // address to jump PORT_AUD_HANDLE_MESSAGES to, to allow audition mode without GoCentral -#define PORT_AUD_INVALID_DATA_CHECK 0x825629fc // check for invalid data in a song submitted to audition mode -#define PORT_XNQOS_PROBE1 0x823ee7f8 // instruction that checks xnqos probe results -#define PORT_XNQOS_PROBE2 0x823ee800 // instruction that checks xnqos probe results -#define PORT_XNQOS_PROBE3 0x823ee80c // instruction that checks xnqos probe results -#define PORT_VDP_DISABLE1 0x82b39ba0 // nop over VDP packet sending -#define PORT_VDP_DISABLE2 0x82b39e60 // nop over VDP packet sending -#define PORT_VDP_DISABLE3 0x82b3a5e4 // nop over VDP packet sending -#define PORT_VDP_DISABLE4 0x82b3a5f0 // nop over VDP packet sending -#define PORT_SESSION_MASK_CHECK 0x82652acc // beq in while loop for instrument mask check -#define PORT_CHARACTER_CLOTHES_CHECK 0x82618120 // check to see if the goal required to select a piece of clothing has been achieved or not -#define PORT_FACE_PAINT_CHECK 0x82614a60 // check to see if face paint is unlocked -#define PORT_TATTOO_CHECK 0x82614a9c // check to see if tattoos are unlocked -#define PORT_VIDEO_VENUE_CHECK 0x82581634 // check to see if video venues are unlocked -#define PORT_OPTIONSTR_DEFINE 0x82510cc8 // bl to OptionStr("define", NULL) in PreInitSystem -#define PORT_RENDER_RES_X_PATCH1 0x8273bf20 // instruction patch to force render width -#define PORT_RENDER_RES_X_PATCH2 0x8273bf24 // instruction patch to force render width -#define PORT_RENDER_RES_Y_PATCH1 0x8273bedc // instruction patch to force render height -#define PORT_RUNLOOP_SPARE 0x822700e0 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) -#define PORT_SONG_ID_EVALUATE 0x827aa7d4 // branch to DataNode::Evaluate in SongMetadata::__ct -#define PORT_LOADOBJS_BCTRL 0x827562e4 // bctrl to Object::PreLoad insie of DirLoader::LoadObjs -#define PORT_SONGMGR_ISDEMO_CHECK 0x82575f9c // "bne" after IsUGC check inside SongMgr::IsDemo -#define PORT_STAGEKIT_EXISTS 0x8228d03c // StageKit check. nop over to allow for fog command without a physical StageKit connected. -#define PORT_VERTEX_READ_1 0x82418704 // call to Vector3::operator>> to read vertex position -#define PORT_VERTEX_READ_2 0x82418748 // call to Vector3::operator>> to read vertex normals +#define PORT_SONGLIMIT 0x82579880 // call to "max_song_count" DataNode::_value +#define PORT_APP_CALL 0x82272e88 // call to App::_ct from main() +#define PORT_SONGBLACKLIST 0x82579098 // call to a function that checks song blacklist +#define PORT_DATAINITFUNCS_TAIL 0x82765980 // blr of DataInitFuncs +#define PORT_FASTSTART_CHECK 0x82270f40 // beq after OptionBool("fast",0) in App::_ct +#define PORT_SYSTEMINIT_BLANK 0x825113a4 // call to a stub function in SystemInit +#define PORT_XL_BYPASS_SG 0x82a88268 // lbz r11, bypassSecureGateway in Quazal::XboxClient::Login2 +#define PORT_RCG_POLL_LOGGEDIN 0x824f7178 // check for login status in RockCentralGateway::Poll +#define PORT_AUD_PATCH_CRASH 0x82b8c928 // patch somewhere to prevent Audition Mode crashes - blr +#define PORT_AUD_PATCH_NET1 0x82b8f7fc // patch somewhere to force Audition Mode to be connected (1) - nop +#define PORT_AUD_PATCH_NET2 0x82b8f80c // patch somewhere to force Audition Mode to be connected (2) - nop +#define PORT_AUD_PATCH_NET3 0x82b8f814 // patch somewhere to force Audition Mode to be connected (3) - nop +#define PORT_AUD_PATCH_NET4 0x82b8f81c // patch somewhere to force Audition Mode to be connected (4) - li r3, 0 +#define PORT_AUD_PATCH_NET5 0x82562638 // patch somewhere to force Audition Mode to be connected (5) - li r3, 1 +#define PORT_AUD_PATCH_REPL 0x82b8e978 // patch to allow Audition to recieve insecure packets from Magma +#define PORT_AUD_PATCH_HDD 0x82515dd4 // patch to make has_hard_drive always return true, probably broken +#define PORT_AUD_PATCH_UNK 0x823f6074 // idk +#define PORT_AUD_HANDLE_MESSAGES 0x82563edc // part of AuditionMgr::Handle that handles disconnected from Rock Central/XBL/etc. messages +#define PORT_AUD_HANDLE_MSG_JUMP 0x82564048 // address to jump PORT_AUD_HANDLE_MESSAGES to, to allow audition mode without GoCentral +#define PORT_AUD_INVALID_DATA_CHECK 0x825629fc // check for invalid data in a song submitted to audition mode +#define PORT_XNQOS_PROBE1 0x823ee7f8 // instruction that checks xnqos probe results +#define PORT_XNQOS_PROBE2 0x823ee800 // instruction that checks xnqos probe results +#define PORT_XNQOS_PROBE3 0x823ee80c // instruction that checks xnqos probe results +#define PORT_VDP_DISABLE1 0x82b39ba0 // nop over VDP packet sending +#define PORT_VDP_DISABLE2 0x82b39e60 // nop over VDP packet sending +#define PORT_VDP_DISABLE3 0x82b3a5e4 // nop over VDP packet sending +#define PORT_VDP_DISABLE4 0x82b3a5f0 // nop over VDP packet sending +#define PORT_SESSION_MASK_CHECK 0x82652acc // beq in while loop for instrument mask check +#define PORT_CHARACTER_CLOTHES_CHECK 0x82655148 // check to see if the goal required to select a piece of clothing has been achieved or not +#define PORT_CHARACTER_CLOTHES_CHECK2 0x8265514c // check to see if the goal required to select a piece of clothing has been achieved or not 2 +#define PORT_FACE_PAINT_CHECK 0x82614a60 // check to see if face paint is unlocked +#define PORT_TATTOO_CHECK 0x82614a9c // check to see if tattoos are unlocked +#define PORT_VIDEO_VENUE_CHECK 0x82581634 // check to see if video venues are unlocked +#define PORT_OPTIONSTR_DEFINE 0x82510cc8 // bl to OptionStr("define", NULL) in PreInitSystem +#define PORT_RENDER_RES_X_PATCH1 0x8273bf20 // instruction patch to force render width +#define PORT_RENDER_RES_X_PATCH2 0x8273bf24 // instruction patch to force render width +#define PORT_RENDER_RES_Y_PATCH1 0x8273bedc // instruction patch to force render height +#define PORT_RUNLOOP_SPARE 0x822700e0 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) +#define PORT_SONG_ID_EVALUATE 0x827aa7d4 // branch to DataNode::Evaluate in SongMetadata::__ct +#define PORT_LOADOBJS_BCTRL 0x827562e4 // bctrl to Object::PreLoad insie of DirLoader::LoadObjs +#define PORT_SONGMGR_ISDEMO_CHECK 0x82575f9c // "bne" after IsUGC check inside SongMgr::IsDemo +#define PORT_STAGEKIT_EXISTS 0x8228d03c // StageKit check. nop over to allow for fog command without a physical StageKit connected. +#define PORT_VERTEX_READ_1 0x82418704 // call to Vector3::operator>> to read vertex position +#define PORT_VERTEX_READ_2 0x82418748 // call to Vector3::operator>> to read vertex normals // function patch addresses #define PORT_SETDISKERROR 0x82516320 // PlatformMgr::SetDiskError #define PORT_APP_RUN 0x82272e90 // App::Run @@ -179,22 +180,23 @@ void DbgPrint(const char *s, ...); #include // instruction patch addresses -#define PORT_SONGLIMIT 0x801cedac // call to "max_song_count" DataNode::_value -#define PORT_SONGBLACKLIST 0x801d2148 // call to a function that checks song blacklist -#define PORT_DATAINITFUNCS_TAIL 0x80321b7c // blr of DataInitFuncs -#define PORT_FASTSTART_CHECK 0x8000e2f0 // beq after OptionBool("fast",0) in App::_ct -#define PORT_STRAPSCREEN_1 0x8000e40c // branch to CustomSplash::Show in App::_ct -#define PORT_STRAPSCREEN_2 0x8000e41c // branch to CustomSplash::EndShow in App::_ct -#define PORT_NASWII_HOST 0x807e94a0 // branch to the add header function in the DWCDL login function -#define PORT_CHARACTER_CLOTHES_CHECK 0x801fec58 // check to see if the goal required to select a piece of clothing has been unlocked -#define PORT_FACE_PAINT_CHECK 0x801fd9a8 // check to see if face paint is unlocked -#define PORT_TATTOO_CHECK 0x801fd9c4 // check to see if tattoos are unlocked -#define PORT_VIDEO_VENUE_CHECK 0x80227e34 // check to see if video venues are unlocked -#define PORT_OPTIONSTR_DEFINE 0x8030e418 // bl to OptionStr("define", NULL) in PreInitSystem -#define PORT_RUNLOOP_SPARE 0x8000f740 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) -#define PORT_MICCHECK 0x8024a4e8 // a bne that throws an error on the song select screen if the mic is not connected -#define PORT_BIGSYMBOLFUNC_TAIL 0x8037a3d4 // blr after a function that initialises a bunch of symbols -#define PORT_UPDATEPRESENCEBLOCK_B 0x80188194 // branch after the failure case in a function that calls UpdatePresence +#define PORT_SONGLIMIT 0x801cedac // call to "max_song_count" DataNode::_value +#define PORT_SONGBLACKLIST 0x801d2148 // call to a function that checks song blacklist +#define PORT_DATAINITFUNCS_TAIL 0x80321b7c // blr of DataInitFuncs +#define PORT_FASTSTART_CHECK 0x8000e2f0 // beq after OptionBool("fast",0) in App::_ct +#define PORT_STRAPSCREEN_1 0x8000e40c // branch to CustomSplash::Show in App::_ct +#define PORT_STRAPSCREEN_2 0x8000e41c // branch to CustomSplash::EndShow in App::_ct +#define PORT_NASWII_HOST 0x807e94a0 // branch to the add header function in the DWCDL login function +#define PORT_CHARACTER_CLOTHES_CHECK 0x802607bc // check to see if the goal required to select a piece of clothing has been unlocked +#define PORT_CHARACTER_CLOTHES_CHECK2 0x802607c0 // check to see if the goal required to select a piece of clothing has been unlocked 2 +#define PORT_FACE_PAINT_CHECK 0x801fd9a8 // check to see if face paint is unlocked +#define PORT_TATTOO_CHECK 0x801fd9c4 // check to see if tattoos are unlocked +#define PORT_VIDEO_VENUE_CHECK 0x80227e34 // check to see if video venues are unlocked +#define PORT_OPTIONSTR_DEFINE 0x8030e418 // bl to OptionStr("define", NULL) in PreInitSystem +#define PORT_RUNLOOP_SPARE 0x8000f740 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) +#define PORT_MICCHECK 0x8024a4e8 // a bne that throws an error on the song select screen if the mic is not connected +#define PORT_BIGSYMBOLFUNC_TAIL 0x8037a3d4 // blr after a function that initialises a bunch of symbols +#define PORT_UPDATEPRESENCEBLOCK_B 0x80188194 // branch after the failure case in a function that calls UpdatePresence // #define PORT_LOADOBJS_BCTRL 0x827562e4 // function patch addresses #define PORT_SETDISKERROR 0x8030ce7c // PlatformMgr::SetDiskError diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 2d7e979..2a8a894 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -240,8 +240,8 @@ void ApplyConfigurablePatches() if (config.UnlockClothing == 1) { // Unlocks all clothing, tattoos, face paint, and video venues - // TODO: Figure out what marks items as locked in the UI and patch that as well, right now it still shows them as locked - POKE_32(PORT_CHARACTER_CLOTHES_CHECK, NOP); + POKE_32(PORT_CHARACTER_CLOTHES_CHECK, LI(3, 1)); + POKE_32(PORT_CHARACTER_CLOTHES_CHECK2, BLR); POKE_32(PORT_TATTOO_CHECK, LI(3, 1)); POKE_32(PORT_FACE_PAINT_CHECK, LI(3, 1)); POKE_32(PORT_VIDEO_VENUE_CHECK, LI(3, 1)); From 168777c6259add7085c44e037157f3c9a57f24cb Mon Sep 17 00:00:00 2001 From: ihatecompvir Date: Tue, 25 Jun 2024 15:36:44 -0700 Subject: [PATCH 008/123] Fix double bass Forgot that I need to actually call the original function, lol --- source/SongParserHooks.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/SongParserHooks.c b/source/SongParserHooks.c index 7be6749..2069ebb 100644 --- a/source/SongParserHooks.c +++ b/source/SongParserHooks.c @@ -1,5 +1,5 @@ /* - RB3Enhanced - SongHooks.c + RB3Enhanced - SongParserHooks.c Hooks related to the parsing of song MIDIs. */ @@ -12,7 +12,9 @@ int SongParserPitchToSlotHook(SongParser *thisSongParser, int pitch, int *difficulty, int tick) { Modifier *doubleBassModifier; + int ret; + ret = SongParserPitchToSlot(thisSongParser, pitch, difficulty, tick); doubleBassModifier = ModifierActive(*(int *)PORT_MODIFIERMGR_POINTER, globalSymbols.doubleBass, 0); // only read the notes when double bass pedal modifier is on @@ -35,5 +37,5 @@ int SongParserPitchToSlotHook(SongParser *thisSongParser, int pitch, int *diffic } } - return SongParserPitchToSlot(thisSongParser, pitch, difficulty, tick); + return ret; } \ No newline at end of file From efa53a4d7e3de0bc728326493c8f79b102ba5ccb Mon Sep 17 00:00:00 2001 From: Blasteroids <127441225+Blasteroids@users.noreply.github.com> Date: Sat, 29 Jun 2024 13:43:49 +0100 Subject: [PATCH 009/123] SK Led fix Fixes the RB3 (bug?) where 2 red leds stay on after exiting the score screen. This fix turns them off. --- include/rb3enhanced.h | 5 +++++ source/GameHooks.c | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/include/rb3enhanced.h b/include/rb3enhanced.h index f68c383..10b30af 100644 --- a/include/rb3enhanced.h +++ b/include/rb3enhanced.h @@ -33,3 +33,8 @@ extern int RB3E_LoadedSongCount; // Emulator detection int RB3E_IsEmulator(); + +// StageKit set state, needed for fixing LED bug. +#ifdef RB3E_XBOX + void StagekitSetStateHook(int state1, int state2); +#endif diff --git a/source/GameHooks.c b/source/GameHooks.c index 27387c3..798e94f 100644 --- a/source/GameHooks.c +++ b/source/GameHooks.c @@ -68,5 +68,12 @@ void *GameDestructHook(void *theGame, int r4) { char in_game = 0x00; RB3E_SendEvent(RB3E_EVENT_STATE, &in_game, sizeof(in_game)); + +#ifdef RB3E_XBOX + // When not in-game, turn off the stage-kit lights. + // Fixes the RB3 bug(?) that leaves 2 red leds on when exiting the score view screen. + StagekitSetStateHook(0x00, 0xFF); +#endif + return GameDestruct(theGame, r4); } From 07d4e432d0735d478c0701c6732887a623929f2c Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sun, 30 Jun 2024 21:14:01 +0100 Subject: [PATCH 010/123] fixes a crash when playing online --- include/ports.h | 4 ++++ source/rb3enhanced.c | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/include/ports.h b/include/ports.h index cb64f8e..404a75b 100644 --- a/include/ports.h +++ b/include/ports.h @@ -49,6 +49,8 @@ #define PORT_STAGEKIT_EXISTS 0x8228d03c // StageKit check. nop over to allow for fog command without a physical StageKit connected. #define PORT_VERTEX_READ_1 0x82418704 // call to Vector3::operator>> to read vertex position #define PORT_VERTEX_READ_2 0x82418748 // call to Vector3::operator>> to read vertex normals +#define PORT_MULTIPLAYER_CRASH 0x82ae6880 // branch to a function that can crash in online multiplayer +#define PORT_MULTIPLAYER_FIX 0x8282b238 // the function that doesn't crash // function patch addresses #define PORT_SETDISKERROR 0x82516320 // PlatformMgr::SetDiskError #define PORT_APP_RUN 0x82272e90 // App::Run @@ -197,6 +199,8 @@ void DbgPrint(const char *s, ...); #define PORT_MICCHECK 0x8024a4e8 // a bne that throws an error on the song select screen if the mic is not connected #define PORT_BIGSYMBOLFUNC_TAIL 0x8037a3d4 // blr after a function that initialises a bunch of symbols #define PORT_UPDATEPRESENCEBLOCK_B 0x80188194 // branch after the failure case in a function that calls UpdatePresence +#define PORT_MULTIPLAYER_CRASH 0x80018a78 // branch to a function that can crash in online multiplayer +#define PORT_MULTIPLAYER_FIX 0x806ec0e8 // the function that doesn't crash // #define PORT_LOADOBJS_BCTRL 0x827562e4 // function patch addresses #define PORT_SETDISKERROR 0x8030ce7c // PlatformMgr::SetDiskError diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 2a8a894..9850e04 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -217,6 +217,12 @@ void ApplyPatches() // skips check for stagekit to allow for fog commands to be issued without a stagekit plugged in POKE_32(PORT_STAGEKIT_EXISTS, NOP); +#endif + // fixes a crash in online multiplayer +#ifdef RB3E_WII + POKE_B(PORT_MULTIPLAYER_CRASH, PORT_MULTIPLAYER_FIX); +#else + POKE_BL(PORT_MULTIPLAYER_CRASH, PORT_MULTIPLAYER_FIX); #endif RB3E_MSG("Patches applied!", NULL); } From 758667b1fb04fa8a66583f154815db01884f54b0 Mon Sep 17 00:00:00 2001 From: David Ross Smith <5095074+DragRedSim@users.noreply.github.com> Date: Sun, 28 Jul 2024 03:51:59 +1000 Subject: [PATCH 011/123] Extend strncpy(config.GoCentralAddress) to full field length config.GoCentralAddress is defined in the ini spec in config.h as being of up to RB3E_MAX_DOMAIN chars, but the strncpy routine for that item was only copying RB3E_MAX_CONFIG_LEN; this was 30 chars. The announced Xbox URI had precisely 31 characters. This caused all DNS lookups for this address to fail, and no connection to be made to the GoCentral server. --- source/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/config.c b/source/config.c index 8628622..24678b8 100644 --- a/source/config.c +++ b/source/config.c @@ -80,7 +80,7 @@ static int INIHandler(void *user, const char *section, const char *name, const c if (strcmp(name, "EnableGoCentral") == 0) config.EnableGoCentral = RB3E_CONFIG_BOOL(value); if (strcmp(name, "GoCentralAddress") == 0) - strncpy(config.GoCentralAddress, value, RB3E_MAX_CONFIG_LEN); + strncpy(config.GoCentralAddress, value, RB3E_MAX_DOMAIN); } if (strcmp(section, "HTTP") == 0) { From b2d6f0657cd6b3160c1c8f8abfcc15057c9f730a Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:48:37 -0500 Subject: [PATCH 012/123] add index created by @InvoxiPlayGames adding the original version created by emma for tracking purposes before committing changes --- assets/rb3e_index.html | 220 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 assets/rb3e_index.html diff --git a/assets/rb3e_index.html b/assets/rb3e_index.html new file mode 100644 index 0000000..db21be5 --- /dev/null +++ b/assets/rb3e_index.html @@ -0,0 +1,220 @@ + + + + RB3Enhanced + + + + + +
+

RB3Enhanced

+ +
+ + + + + + + +
+
+
+ (Don't press this until you're in the Music Library!) +
+
+ + \ No newline at end of file From 67f823ea9c4fb8041fe61a818f5e5ba7cb7b7c54 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 11 Aug 2024 12:38:49 -0500 Subject: [PATCH 013/123] massively overhaul index.html This updates the styling of the page to be a bit more modern. The page also has a theme per the running device state of light mode vs dark mode. Add's the ability to use (or hide) a lastfm api key for album art fetching. This isnt a perfect mechanism so you are also able to hide the api key field to not attempt getting album images. The album images are tagged and cached based on the corresponding song tag, so any arbitrary filtering of the song list will continue to match the cached image. filtered searches trigger faster api calls so the user isnt sat looking at a blank image. once search is unfiltered, the task for populating art for the entire library continues running. No longer need to click a search button to trigger a search, can live type and live filter. --- assets/rb3e_index.html | 854 ++++++++++++++++++++++++++++++----------- 1 file changed, 640 insertions(+), 214 deletions(-) diff --git a/assets/rb3e_index.html b/assets/rb3e_index.html index db21be5..bb50319 100644 --- a/assets/rb3e_index.html +++ b/assets/rb3e_index.html @@ -1,220 +1,646 @@ - RB3Enhanced - - - + + + RB3Enhanced + + -
-

RB3Enhanced

- -
- - - - - - - -
-
-
- (Don't press this until you're in the Music Library!) -
-
+
+ + +
+ +
+
+ + + + + + + +
+
+ +
+ (Don't press this until you're in the Music Library!) +
+
- \ No newline at end of file + From 4ab7a11ced3a4afe95e9804324b64bdef49a846b Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 24 Sep 2024 04:53:51 +0100 Subject: [PATCH 014/123] rb3.ini in assets folder, fix actions..? --- .github/workflows/build.yml | 8 +++---- assets/rb3.ini | 47 +++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 assets/rb3.ini diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cc28b45..54c8318 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: make wii -j2 DEBUG=1 - name: Upload binaries (debug) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: RB3Enhanced-Wii-Debug path: out/RB3Enhanced.mod @@ -41,7 +41,7 @@ jobs: make wii -j2 - name: Upload binaries (no-debug) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: RB3Enhanced-Wii-NoDebug path: out/RB3Enhanced.mod @@ -72,7 +72,7 @@ jobs: make xbox -j2 DEBUG=1 - name: Upload binaries (debug) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: RB3Enhanced-Xbox-Debug path: out/RB3Enhanced.dll @@ -85,7 +85,7 @@ jobs: make xbox -j2 - name: Upload binaries (no-debug) - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: RB3Enhanced-Xbox-NoDebug path: out/RB3Enhanced.dll diff --git a/assets/rb3.ini b/assets/rb3.ini new file mode 100644 index 0000000..519e148 --- /dev/null +++ b/assets/rb3.ini @@ -0,0 +1,47 @@ +[General] +# The multiplier to apply to the speed of the note highway. +# 1.0 = 100%, 1.2 = 120%, 1.5 = 150% +TrackSpeedMultiplier = 1.0 +# The multiplier to apply to the speed of the song audio. +# 1.0 = 100%, 1.2 = 120%, 1.5 = 150% +SongSpeedMultiplier = 1.0 +# The name of the venue to force. (e.g. "none" for black background) +# To disable this and use default venue selection, set this to "false" +ForcedVenue = false +# Whether to enable game origin icons in the setlist menu. +# This requires the correct font files in the rawfiles directory. +GameOriginIcons = true +# Whether to unlock all clothing and other unlockables by default. +UnlockClothing = false +# The folder to check on the Hard Drive and USB drives for raw files. +# The default is "rb3", uncomment this line to change it +#RawfilesDir = rb3 + +[Events] +# Enables sending events (song start, etc) to a computer or other networked device. +EnableEvents = false +# The IP address to send these events to (set to 255.255.255.255 to broadcast to the entire local network.) +BroadcastTarget = 255.255.255.255 + +[GoCentral] +# Enables redirecting Rock Central servers to a custom server. +# This also re-enables Audition Mode if enabled. +# (This setting is ignored if using an Xbox 360 and connected to official services.) +EnableGoCentral = true +# The IP address of the GoCentral server. +#GoCentralAddress = 0.0.0.0 + +[Xbox360] +# Enables playing online multiplayer without the need for Xbox Live. +# (This setting is ignored if using an Xbox 360 and connected to official services.) +EnableLiveless = true +# The IP to direct connect to. If hosting a game, set this to "127.0.0.1" +DirectConnectIP = 127.0.0.1 +# The STUN server to contact to request your public IP address, for liveless multiplayer. (TCP required.) +# Uncomment these lines to enable. +#STUNServer = stun.l.google.com +#STUNServerPort = 19302 +# Your external IPv4 address, for liveless multiplayer. Overrode by STUN, if enabled. +ExternalIP = 0.0.0.0 + +# End of rb3.ini \ No newline at end of file From 883862a5f4565827cef9c4f14cd6a90eb864c1ef Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Thu, 9 Jan 2025 21:39:44 +0000 Subject: [PATCH 015/123] use my fork of brainslug in actions --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 54c8318..f02b03b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: - name: Install BrainSlug run: | - git clone https://github.com/Chadderz121/brainslug-wii + git clone -b atof https://github.com/InvoxiPlayGames/brainslug-wii.git cd brainslug-wii make install cd .. From 88321b733bbe944a1db8c1b5f67c2a4be34d8caf Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Thu, 9 Jan 2025 21:44:46 +0000 Subject: [PATCH 016/123] remove compiler "errors" that should be warnings if not for the lovely toolchain update --- source/MiloSceneHooks.c | 7 ++++--- source/MusicLibrary.c | 2 +- source/PassiveMessages.c | 2 +- source/rb3enhanced.c | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/source/MiloSceneHooks.c b/source/MiloSceneHooks.c index aea1bce..518f686 100644 --- a/source/MiloSceneHooks.c +++ b/source/MiloSceneHooks.c @@ -34,8 +34,9 @@ void LoadObj(Object *object, BinStream *stream) if (strlen(origPath) + strlen(object->name) - 20 > sizeof(newPath)) goto object_pre_load; strncpy(newPath, origPath, strlen(origPath) - 10); - strncat(newPath, "/", 1); - strncat(newPath, object->name, strlen(object->name)); + // HACK(Emma): we don't have strncat on Wii so just strcat. THIS IS NOT SAFE! + strcat(newPath, "/"); //, 1); + strcat(newPath, object->name); //, strlen(object->name)); replacementPath = RB3E_GetRawfilePath(newPath, 0); if (replacementPath != NULL) @@ -56,7 +57,7 @@ void LoadObj(Object *object, BinStream *stream) // not sure if this is 100% necessary, but cannot hurt fileStream.vtable->seekImpl(&fileStream, 0, 0); - object->table->preLoad(object, &fileStream); + object->table->preLoad(object, (BinStream *)&fileStream); // destroy the FileStream so we do not leak memory fileStream.vtable->destructor(&fileStream, 0); diff --git a/source/MusicLibrary.c b/source/MusicLibrary.c index fce3e47..69c3633 100644 --- a/source/MusicLibrary.c +++ b/source/MusicLibrary.c @@ -8,7 +8,7 @@ void CheckForPanelAndJump(Symbol entryName, int sortType) { // check if the song select panel is "up" (displayed on screen) before attempting a jump - UIPanel *songSelectPanel = ObjectFindUIPanel(*(int *)PORT_OBJECTDIRMAINDIR, "song_select_panel", 1); + UIPanel *songSelectPanel = ObjectFindUIPanel(*(int **)PORT_OBJECTDIRMAINDIR, "song_select_panel", 1); if (songSelectPanel != NULL && songSelectPanel->is_up == 1) { MusicLibrarySelect(*(int *)PORT_THEMUSICLIBRARY, entryName, sortType, 1); diff --git a/source/PassiveMessages.c b/source/PassiveMessages.c index 4da7a06..005de59 100644 --- a/source/PassiveMessages.c +++ b/source/PassiveMessages.c @@ -18,7 +18,7 @@ void DisplayMessage(char *message) messageNode.type = SYMBOL; messageNode.value.string = message; messageArray.mNodeCount = 1; - messageArray.mNodes = &messageNode; // set this right for what it actually is + messageArray.mNodes = (DataNodes *)&messageNode; // set this right for what it actually is messageArray.mRefCount = 1; messageArray.mFile = *(Symbol *)PORT_NULLSYMBOL; QueueMessage((*(BandUI *)PORT_THEBANDUI).passiveMessagesPanel, &messageArray, 0, (*(Symbol *)PORT_NULLSYMBOL).sym, 0xFFFFFFFF); diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 9850e04..6a5d8b6 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -117,11 +117,11 @@ void *ModifierManagerConstructorHook(int thisModifierManager, int unk) } // a TextStream object that prints straight to RB3E_DEBUG -void DebugTextStreamDestructor(void *thisTextStream) +void DebugTextStreamDestructor(TextStream *thisTextStream) { // not dynamically allocating anything, can't be a destructor } -void DebugTextStreamPrint(void *thisTextStream, char *text) +void DebugTextStreamPrint(TextStream *thisTextStream, char *text) { #ifdef RB3E_XBOX // HACK(Emma): retail xbdm doesn't like newlines. From 99e1394c7d8dcbca781a216d181eacfff7c942d4 Mon Sep 17 00:00:00 2001 From: ihatecompvir Date: Thu, 30 Jan 2025 01:06:48 -0800 Subject: [PATCH 017/123] Don't trigger breakpoints on server errors --- include/ports.h | 1 + source/rb3enhanced.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/include/ports.h b/include/ports.h index 404a75b..4b25e49 100644 --- a/include/ports.h +++ b/include/ports.h @@ -51,6 +51,7 @@ #define PORT_VERTEX_READ_2 0x82418748 // call to Vector3::operator>> to read vertex normals #define PORT_MULTIPLAYER_CRASH 0x82ae6880 // branch to a function that can crash in online multiplayer #define PORT_MULTIPLAYER_FIX 0x8282b238 // the function that doesn't crash +#define PORT_QUAZAL_BREAKPOINT 0x828410c0 // address to DbgBreakPoint in Quazal::Platform::Breakpoint // function patch addresses #define PORT_SETDISKERROR 0x82516320 // PlatformMgr::SetDiskError #define PORT_APP_RUN 0x82272e90 // App::Run diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 6a5d8b6..92b3779 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -217,6 +217,9 @@ void ApplyPatches() // skips check for stagekit to allow for fog commands to be issued without a stagekit plugged in POKE_32(PORT_STAGEKIT_EXISTS, NOP); + + // just blr instead of actually triggering the breakpoint + POKE_32(PORT_QUAZAL_BREAKPOINT, BLR); #endif // fixes a crash in online multiplayer #ifdef RB3E_WII From 3e559e608def085323cbf383bd2b9509e74aad34 Mon Sep 17 00:00:00 2001 From: LinosM Date: Fri, 14 Feb 2025 16:47:18 -0500 Subject: [PATCH 018/123] Support for scripts over the network --- include/config.h | 1 + source/config.c | 2 ++ source/net_http_server.c | 64 +++++++++++++++++++++++++++++++++++----- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/include/config.h b/include/config.h index 0ec5443..82f121d 100644 --- a/include/config.h +++ b/include/config.h @@ -44,6 +44,7 @@ typedef struct _RB3E_Config // [HTTP] char EnableHTTPServer; char AllowCORS; + char AllowScripts; #ifdef RB3E_XBOX // [Xbox360] char EnableLiveless; diff --git a/source/config.c b/source/config.c index 24678b8..166e6b2 100644 --- a/source/config.c +++ b/source/config.c @@ -88,6 +88,8 @@ static int INIHandler(void *user, const char *section, const char *name, const c config.EnableHTTPServer = RB3E_CONFIG_BOOL(value); if (strcmp(name, "AllowCORS") == 0) config.AllowCORS = RB3E_CONFIG_BOOL(value); + if (strcmp(name, "AllowScripts") == 0) + config.AllowScripts = RB3E_CONFIG_BOOL(value); } if (strcmp(section, "Network") == 0) { diff --git a/source/net_http_server.c b/source/net_http_server.c index 18b1f78..1752683 100644 --- a/source/net_http_server.c +++ b/source/net_http_server.c @@ -8,6 +8,7 @@ #include #include #include +#include #include "quazal/InetAddress.h" #include "rb3/SongMetadata.h" #include "rb3/BandSongMgr.h" @@ -24,6 +25,7 @@ int RB3E_HTTPSocket = -1; static int InitFailed = 0; static char *PendingShortname = NULL; +static char *PendingScript = NULL; typedef struct _song_list_vector { @@ -59,6 +61,35 @@ typedef enum _HTTP_Request_Status HTTP_REQUEST_DONE } HTTP_Request_Status; +// function from https://stackoverflow.com/a/14530993 +void urldecode2(char *dst, const char *src) +{ + char a, b; + while (*src) { + if ((*src == '%') && + ((a = src[1]) && (b = src[2])) && + (isxdigit(a) && isxdigit(b))) { + if (a >= 'a') + a -= 'a'-'A'; + if (a >= 'A') + a -= ('A' - 10); + else + a -= '0'; + if (b >= 'a') + b -= 'a'-'A'; + if (b >= 'A') + b -= ('A' - 10); + else + b -= '0'; + *dst++ = 16*a+b; + src+=3; + } else { + *dst++ = *src++; + } + } + *dst++ = '\0'; +} + void HTTP_Server_Accept(void *connection) { int s = (int)connection; @@ -69,6 +100,7 @@ void HTTP_Server_Accept(void *connection) int state_read_bytes = 0; int read_bytes = 0; char request_path[250] = {0}; + char decoded_path[0x400]; int request_valid = 0; char last_byte = '\0'; const char ok_response[] = "HTTP/1.1 200 OK\r\nServer: RB3Enhanced " RB3E_BUILDTAG "\r\nContent-Type: text/html\r\nX-Clacks-Overhead: GNU maxton\r\nContent-Length: 2\r\n\r\nOK"; @@ -76,6 +108,7 @@ void HTTP_Server_Accept(void *connection) char response_buffer[2000] = {0}; int song_id = 0; char *shortname = NULL; + char *script = NULL; SongMetadata *song_metadata = NULL; int list_count = 0; int i = 0; @@ -159,10 +192,11 @@ void HTTP_Server_Accept(void *connection) state_read_bytes = 0; } } - RB3E_DEBUG("Got a request for '%s' over HTTP", request_path); - if (strstr(request_path, "/song_") == request_path) + urldecode2(decoded_path, request_path); + RB3E_DEBUG("Got a request for '%s' over HTTP", decoded_path); + if (strstr(decoded_path, "/song_") == decoded_path) { - if (sscanf(request_path + sizeof("/song"), "%i", &song_id) == 1) + if (sscanf(decoded_path + sizeof("/song"), "%i", &song_id) == 1) { RB3E_DEBUG("Fetching song metadata for %i over HTTP", song_id); song_metadata = GetMetadata((BandSongMgr *)PORT_THESONGMGR, song_id); @@ -198,10 +232,10 @@ void HTTP_Server_Accept(void *connection) goto close_connection; } } - else if (strstr(request_path, "/jump?shortname=") == request_path) + else if (strstr(decoded_path, "/jump?shortname=") == decoded_path) { // TODO(Emma): This will crash if you're not in the music library. Figure out if we can detect what screen we're on - shortname = request_path + 16; + shortname = decoded_path + 16; RB3E_DEBUG("Jumping to song %s in Music Library", shortname); // hacky solution to send the shortname to the main thread PendingShortname = shortname; @@ -210,7 +244,16 @@ void HTTP_Server_Accept(void *connection) RB3E_TCP_Send(s, (void *)ok_response, strlen(ok_response)); goto close_connection; } - else if (strcmp(request_path, "/list_songs") == 0) + else if (strstr(decoded_path, "/execute?script=") == decoded_path && config.AllowScripts) + { + script = decoded_path + 16; + PendingScript = script; + while (PendingScript != NULL) + RB3E_Sleep(1); + RB3E_TCP_Send(s, (void *)ok_response, strlen(ok_response)); + goto close_connection; + } + else if (strcmp(decoded_path, "/list_songs") == 0) { if (song_list.start == NULL) SongMgrGetRankedSongs((BandSongMgr *)PORT_THESONGMGR, &song_list, 0, 0); @@ -265,7 +308,7 @@ void HTTP_Server_Accept(void *connection) } goto close_connection; } - else if (strcmp(request_path, "/") == 0) + else if (strcmp(decoded_path, "/") == 0) { file_path = RB3E_GetRawfilePath("rb3e_index.html", 1); if (file_path == NULL) @@ -298,7 +341,7 @@ void HTTP_Server_Accept(void *connection) } goto close_connection; } - else if (strcmp(request_path, "/jsonrpc") == 0) + else if (strcmp(decoded_path, "/jsonrpc") == 0) { file_path = RB3E_GetRawfilePath("discordrp.json", 1); if (file_path == NULL) @@ -359,6 +402,11 @@ void HTTP_Server_RunLoop() MusicLibrarySelectSong(PendingShortname); PendingShortname = NULL; } + if (PendingScript != NULL) + { + ExecuteDTA(PORT_ROCKCENTRALGATEWAY, PendingScript); + PendingScript = NULL; + } // accept incoming connections // TODO(Emma): add some sort of subnet restriction and ratelimit r = RB3E_TCP_Accept(RB3E_HTTPSocket, &connected_ip, &connected_port); From 106c4290ee4fc2a91da65f4092a2b881971dc59c Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 8 Mar 2025 15:50:19 +0000 Subject: [PATCH 019/123] add some scripting error debugging similar to debug --- include/DataDebug.h | 11 +++ include/ports.h | 7 ++ include/rb3/Data.h | 8 +- include/rb3e_include.h | 1 + source/DataDebug.c | 200 +++++++++++++++++++++++++++++++++++++++++ source/_functions.c | 6 +- source/rb3enhanced.c | 6 ++ 7 files changed, 237 insertions(+), 2 deletions(-) create mode 100644 include/DataDebug.h create mode 100644 source/DataDebug.c diff --git a/include/DataDebug.h b/include/DataDebug.h new file mode 100644 index 0000000..48dd96b --- /dev/null +++ b/include/DataDebug.h @@ -0,0 +1,11 @@ +/* + RB3Enhanced - DataDebug.h + Hooks for debugging scripting errors, similar to debug. +*/ + +#include "rb3/Data.h" + +DataNode *DataSetHook(DataNode *ret, DataArray *array); +DataNode *DataSetElemHook(DataNode *ret, DataArray *array); +DataNode *DataOnElemHook(DataNode *ret, DataArray *array); +void *DataNodeGetObjHook(DataNode *node); diff --git a/include/ports.h b/include/ports.h index 4b25e49..6e001f0 100644 --- a/include/ports.h +++ b/include/ports.h @@ -139,6 +139,10 @@ #define PORT_BINSTREAMREADENDIAN 0x827c5058 // BinStream::ReadEndian #define PORT_BINSTREAMWRITEENDIAN 0x827c5098 // BinStream::WriteEndian #define PORT_SONGPARSERPITCHTOSLOT 0x82783c20 // SongParser::PitchToSlot +#define PORT_DATASET 0x8275d670 // DataSet +#define PORT_DATASETELEM 0x82760b38 // DataSetElem +#define PORT_DATAONELEM 0x8275ff50 // DataOnElem +#define PORT_DATANODEGETOBJ 0x8274b088 // DataNode::GetObj // instance addresses #define PORT_MODIFIERMGR_POINTER 0x82dfec08 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x82cc8f60 // address of RockCentralGateway @@ -269,6 +273,9 @@ void DbgPrint(const char *s, ...); #define PORT_BINSTREAMREADENDIAN 0x803430bc // BinStream::ReadEndian #define PORT_BINSTREAMWRITEENDIAN 0x80343190 // BinStream::WriteEndian #define PORT_SONGPARSERPITCHTOSLOT 0x8048e298 // SongParser::PitchToSlot +#define PORT_DATASET 0x8031b86c // DataSetSet +#define PORT_DATASETELEM 0x8031e9a0 // DataSetElem +#define PORT_DATAONELEM 0x8031dc40 // DataOnElem // instance addresses #define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway diff --git a/include/rb3/Data.h b/include/rb3/Data.h index ed30c5e..710cb85 100644 --- a/include/rb3/Data.h +++ b/include/rb3/Data.h @@ -7,7 +7,7 @@ typedef union _DataNode_Value { int intVal; float floatVal; - int *dataArray; + void *dataArray; int *object; char *string; } DataNode_Value; @@ -70,4 +70,10 @@ extern DataArray *DataReadFile(char *file, int dtb); extern DataArray *DataFindArray(DataArray *data, Symbol name); extern int DataFindData(DataArray *data, Symbol name, DataNode *out); +extern DataNode *DataSet(DataNode *ret, DataArray *array); +extern DataNode *DataSetElem(DataNode *ret, DataArray *array); +extern DataNode *DataOnElem(DataNode *ret, DataArray *array); + +extern void *DataNodeGetObj(DataNode *node); + #endif // _DATA_H diff --git a/include/rb3e_include.h b/include/rb3e_include.h index 20e56da..0698911 100644 --- a/include/rb3e_include.h +++ b/include/rb3e_include.h @@ -4,6 +4,7 @@ #include "rb3_include.h" #include "config.h" #include "crc32.h" +#include "DataDebug.h" #include "DTAFunctions.h" #include "GameHooks.h" #include "GemHooks.h" diff --git a/source/DataDebug.c b/source/DataDebug.c new file mode 100644 index 0000000..aa74d4c --- /dev/null +++ b/source/DataDebug.c @@ -0,0 +1,200 @@ +/* + RB3Enhanced - DataDebug.h + Hooks for debugging scripting errors, similar to debug. +*/ + +#include +#include +#include +#include +#include "rb3/Data.h" +#include "ports.h" + +DataNode *DataSetHook(DataNode *ret, DataArray *array) +{ + if (array != NULL) + { + if (array->mNodes->n[1].type != VAR && array->mNodes->n[1].type != OBJECT_PROP_REF) + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Invalid set called of type %i in %s (line %d)", array->mNodes->n[1].type, array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Invalid set called of type %i", array->mNodes->n[1].type); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + } + return DataSet(ret, array); +} + +DataNode *DataSetElemHook(DataNode *ret, DataArray *array) +{ + if (array != NULL) + { + DataArray *setArray = array->mNodes->n[1].value.dataArray; + DataNode *evaluated = DataNodeEvaluate(&array->mNodes->n[1]); + if (evaluated->type == ARRAY) + { + setArray = evaluated->value.dataArray; + } + else + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Tried to set_elem with non-array in %s (line %d)", array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Tried to set_elem with non-array", NULL); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + if (setArray != NULL) + { + DataNode *idxNode = DataNodeEvaluate(&array->mNodes->n[2]); + if (idxNode != NULL) + { + int idx = idxNode->value.intVal; + if (idx < 0 || idx > setArray->mNodeCount) + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Tried to set_elem with an invalid index %d in %s (line %d)", idx, array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Tried to set_elem with an invalid index", NULL); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + } + else + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Tried to set_elem with a null index in %s (line %d)", array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Tried to set_elem with a null index", NULL); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + } + else + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Tried to set_elem with a null array in %s (line %d)", array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Tried to set_elem with a null array", NULL); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + } + return DataSetElem(ret, array); +} + +DataNode *DataOnElemHook(DataNode *ret, DataArray *array) +{ + if (array != NULL) + { + DataArray *elemArray = array->mNodes->n[1].value.dataArray; + DataNode *evaluated = DataNodeEvaluate(&array->mNodes->n[1]); + if (evaluated->type == ARRAY) + { + elemArray = evaluated->value.dataArray; + } + else + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Tried to elem with non-array in %s (line %d)", array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Tried to elem with non-array", NULL); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + if (elemArray != NULL) + { + DataNode *idxNode = DataNodeEvaluate(&array->mNodes->n[2]); + if (idxNode != NULL) + { + int idx = idxNode->value.intVal; + if (idx < 0 || idx >= elemArray->mNodeCount) + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Tried to elem with an invalid index %d in %s (line %d)", idx, array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Tried to elem with an invalid index %d", idx); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + } + else + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Tried to elem with a null index in %s (line %d)", array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Tried to elem with a null index", NULL); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + } + else + { + if (array->mFile.sym != NULL) + { + RB3E_DEBUG("Tried to elem with a null array in %s (line %d)", array->mFile.sym, array->mLine); + } + else + { + RB3E_DEBUG("Tried to elem with a null array", NULL); + } + ret->type = INT_VALUE; + ret->value.intVal = 0; + return ret; + } + } + return DataOnElem(ret, array); +} + +void *DataNodeGetObjHook(DataNode *node) +{ + DataNode *eval = DataNodeEvaluate(node); + if (eval->type != OBJECT && eval->type != STRING_VALUE && eval->type != SYMBOL) + { + RB3E_DEBUG("Tried to get object of non-object node type %d", node->type); + return NULL; + } + return DataNodeGetObj(node); +} diff --git a/source/_functions.c b/source/_functions.c index 7643455..66372dc 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -96,4 +96,8 @@ RB3E_STUB(BinstreamWrite) RB3E_STUB(BinstreamRead) RB3E_STUB(BinstreamReadEndian) RB3E_STUB(BinstreamWriteEndian) -RB3E_STUB(SongParserPitchToSlot) \ No newline at end of file +RB3E_STUB(SongParserPitchToSlot) +RB3E_STUB(DataSet) +RB3E_STUB(DataSetElem) +RB3E_STUB(DataOnElem) +RB3E_STUB(DataNodeGetObj) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 92b3779..9d0750f 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -60,6 +60,8 @@ void UpdatePresenceHook(void *thisPresenceMgr) void *NewFileHook(char *fileName, int flags) { char *new_path = NULL; + if (strlen(fileName) > 250) + return NULL; if (config.DisableRawfiles) goto LOAD_ORIGINAL; // checks the platform-specific APIs for the file @@ -371,6 +373,9 @@ void ApplyHooks() HookFunction(PORT_INITSONGMETADATA, &InitSongMetadata, &InitSongMetadataHook); HookFunction(PORT_UPDATEPRESENCE, &UpdatePresence, &UpdatePresenceHook); HookFunction(PORT_SONGPARSERPITCHTOSLOT, &SongParserPitchToSlot, &SongParserPitchToSlotHook); + HookFunction(PORT_DATASET, &DataSet, &DataSetHook); + HookFunction(PORT_DATASETELEM, &DataSetElem, &DataSetElemHook); + HookFunction(PORT_DATAONELEM, &DataOnElem, &DataOnElemHook); #ifdef RB3E_WII // wii exclusive hooks // HookFunction(PORT_USBWIIGETTYPE, &UsbWiiGetType, &UsbWiiGetTypeHook); @@ -379,6 +384,7 @@ void ApplyHooks() HookFunction(PORT_STAGEKIT_SET_STATE, &StagekitSetState, &StagekitSetStateHook); HookFunction(PORT_SETSONGNAMEFROMNODE, &SetSongNameFromNode, &SetSongNameFromNodeHook); // TODO: port these to Wii + HookFunction(PORT_DATANODEGETOBJ, &DataNodeGetObj, &DataNodeGetObjHook); POKE_B(PORT_GETSONGID, &GetSongIDHook); POKE_BL(PORT_SONG_ID_EVALUATE, &MetadataSongIDHook); POKE_BL(PORT_LOADOBJS_BCTRL, &LoadObj); From d1a3a75915d8d48b05be561e4ddd6058d51e434d Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 3 May 2025 16:08:45 +0100 Subject: [PATCH 020/123] enable 192MB heap on Dolphin, groundwork for launcher to set it on vWii --- include/ports.h | 8 +++ include/rb3/WiiMemMgr.h | 32 +++++++++++ include/wii_ipc.h | 117 ++++++++++++++++++++++++++++++++++++++++ source/_functions.c | 1 + source/wii.c | 73 +++++++++++++++++++++++++ 5 files changed, 231 insertions(+) create mode 100644 include/rb3/WiiMemMgr.h create mode 100644 include/wii_ipc.h diff --git a/include/ports.h b/include/ports.h index 6e001f0..7d9d1be 100644 --- a/include/ports.h +++ b/include/ports.h @@ -276,6 +276,7 @@ void DbgPrint(const char *s, ...); #define PORT_DATASET 0x8031b86c // DataSetSet #define PORT_DATASETELEM 0x8031e9a0 // DataSetElem #define PORT_DATAONELEM 0x8031dc40 // DataOnElem +#define PORT_HEAPINIT 0x80352cbc // Heap::Init // instance addresses #define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway @@ -294,6 +295,13 @@ void DbgPrint(const char *s, ...); // string pointers #define PORT_NASWII_AC_URL 0x808e2310 #define PORT_NASWII_PR_URL 0x808e2390 +// wii stuff +#define PORT_WII_OS0_GLOBALS 0x80000000 // "os0"/"early" globals +#define PORT_WII_OS1_GLOBALS 0x80003000 // "os1"/"late" globals +#define PORT_CONFIGMEM2_52MB_INST 0x80769340 // ConfigMEM2_52MB 0x90000000 length param +#define PORT_CONFIGMEM2_56MB_INST 0x80769420 // ConfigMEM2_56MB 0x90000000 length param +#define PORT_CONFIGMEM2_64MB_INST 0x80769500 // ConfigMEM2_64MB 0x90000000 length param +#define PORT_ENABLEINSTSONMEM2LO16MB_INST 0x80769740 // EnableInstsOnMEM2Lo16MB 0x90000000 length param // define logging functions #define RB3E_PRINT printf diff --git a/include/rb3/WiiMemMgr.h b/include/rb3/WiiMemMgr.h new file mode 100644 index 0000000..3a0d6dc --- /dev/null +++ b/include/rb3/WiiMemMgr.h @@ -0,0 +1,32 @@ +#ifndef _WIIMEMMGR_H +#define _WIIMEMMGR_H +#ifdef RB3E_WII + +typedef struct _FreeBlock FreeBlock; + +typedef struct _FreeBlock +{ + unsigned int mSizeWords; + unsigned int mTimeStamp; + FreeBlock *mNextBlock; +} FreeBlock; + +typedef struct _Heap +{ + FreeBlock *freeBlockChain; + int *mStart; + const char *mName; + int mSizeWords; + int mNum; + int mIsHandleHeap; + int mDebugLevel; + int mStrategy; + int mAllowTemp; + int unk_1; + int unk_2; + int unk_3; + int unk_4; +} Heap; + +#endif // RB3E_WII +#endif // _WIIMEMMGR_H diff --git a/include/wii_ipc.h b/include/wii_ipc.h new file mode 100644 index 0000000..9c8d934 --- /dev/null +++ b/include/wii_ipc.h @@ -0,0 +1,117 @@ +#ifdef RB3E_WII +#ifndef _WII_IPC_H +#define _WII_IPC_H +// For IPCCommandType, IOSOpenMode and SeekMode: +// Copyright 2008 Dolphin Emulator Project +// Licensed under GPLv2+ + +#include + +typedef int32_t s32; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; + +typedef enum _IPCCommandType +{ + IPC_CMD_OPEN = 1, + IPC_CMD_CLOSE = 2, + IPC_CMD_READ = 3, + IPC_CMD_WRITE = 4, + IPC_CMD_SEEK = 5, + IPC_CMD_IOCTL = 6, + IPC_CMD_IOCTLV = 7, + IPC_REPLY = 8, + IPC_INTERRUPT = 9, +} IPCCommandType; + +typedef enum _IOSOpenMode +{ + IOS_OPEN_READ = 1, + IOS_OPEN_WRITE = 2, + IOS_OPEN_RW = (IOS_OPEN_READ | IOS_OPEN_WRITE) +} IOSOpenMode; + +typedef struct _IOSResourceOpenRequest +{ + const u8 *path; + IOSOpenMode flags; + u32 uid; // some sort of ID (UID/PID?) + u16 gid; // probably gid based on the SetGid syscall +} IOSResourceOpenRequest; + +typedef struct _IOSResourceCloseRequest +{ +} IOSResourceCloseRequest; + +typedef struct _IOSResourceReadWriteRequest +{ + u8 *data; + u32 length; +} IOSResourceReadWriteRequest; + +typedef enum _SeekMode +{ + IOS_SEEK_SET = 0, + IOS_SEEK_CUR = 1, + IOS_SEEK_END = 2, +} SeekMode; + +typedef struct _IOSResourceSeekRequest +{ + u32 offset; + SeekMode mode; +} IOSResourceSeekRequest; + +typedef struct _IOSResourceIOCtlRequest +{ + u32 request; + u8 *in; + u32 in_size; + u8 *out; + u32 out_size; +} IOSResourceIOCtlRequest; + +typedef struct _IOVector +{ + u8 *base; + u32 length; +} IOVector; + +typedef struct _IOSResourceIOCtlVRequest +{ + u32 request; + u32 in_count; + u32 io_count; + IOVector *vectors; +} IOSResourceIOCtlVRequest; + +typedef union _IOSResourceArgs +{ + IOSResourceOpenRequest open; + IOSResourceCloseRequest close; + IOSResourceReadWriteRequest read; + IOSResourceReadWriteRequest write; + IOSResourceSeekRequest seek; + IOSResourceIOCtlRequest ioctl; + IOSResourceIOCtlVRequest ioctlv; +} IOSResourceArgs; + +typedef struct _IOSRequest +{ + IPCCommandType cmd; + s32 ret; + u32 fd; + IOSResourceArgs args; +} IOSRequest; + +typedef struct _IOSMessage +{ + IOSRequest request; + void *callback; + u32 caller_data; + u32 relaunch; +} IOSMessage; + +#endif // _WII_IPC_H +#endif // RB3E_WII diff --git a/source/_functions.c b/source/_functions.c index 66372dc..b451527 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -101,3 +101,4 @@ RB3E_STUB(DataSet) RB3E_STUB(DataSetElem) RB3E_STUB(DataOnElem) RB3E_STUB(DataNodeGetObj) +RB3E_STUB(HeapInit) diff --git a/source/wii.c b/source/wii.c index e761122..76bb0f6 100644 --- a/source/wii.c +++ b/source/wii.c @@ -20,6 +20,12 @@ #include "rb3enhanced.h" #include "GlobalSymbols.h" #include "config.h" +#include "rb3/WiiMemMgr.h" +#include "wii_ipc.h" +#include "utilities.h" + +char _has256MBMem2 = 0; +int RB3E_Launcher_ExpandedRAM = 0; BSLUG_MODULE_GAME("SZB?"); BSLUG_MODULE_NAME("RB3Enhanced"); @@ -97,8 +103,26 @@ int RB3E_RelaunchGame() return -1; } +void HeapInit(Heap *heap, const char *name, int num, int *mem, int sizeWords, int isHandle, int strategy, int debug_level, int allow_temp); +void HeapInitHook(Heap *heap, const char *name, int num, int *mem, int sizeWords, int isHandle, int strategy, int debug_level, int allow_temp) +{ + if (strcmp(name, "main") == 0) + { + // TODO(Emma): somehow reclaim original "mem" buffer here, as right now it's a very large (~38MiB) memory leak + // ideally we would add it as a free block in the resulting heap + mem = (int *)(0x94000000); + sizeWords = 0x3000000; + } + RB3E_DEBUG("Heap %i (\"%s\") = %p, words: 0x%x (size: 0x%x)", num, name, mem, sizeWords, sizeWords * 4); + HeapInit(heap, name, num, mem, sizeWords, isHandle, strategy, debug_level, allow_temp); +} + static void CTHook(void *ThisApp, int argc, char **argv) { + if (_has256MBMem2) + { + RB3E_MSG("Running with 256MB MEM2!", NULL); + } // launch game StartupHook(ThisApp, argc, argv); // apply wfc url patches if gocentral is enabled and configured server isn't Ninty @@ -137,6 +161,55 @@ static void _startHook() { POKE_B(&SymbolConstruct, PORT_SYMBOL_CT); POKE_B(PORT_BIGSYMBOLFUNC_TAIL, InitGlobalSymbols); +#ifdef RB3EDEBUG + // for now we limit the ability to use more than intentional RAM to debug builds + // just in case shit hits the fan + // Cool Facts: on a devkit Wii, the retail build of RB3 will have a 128MB heap by default. so we don't want to handle anything extra! + + // detect 256MB being enabled in Dolphin + if (*(uint32_t *)0x80003118 >= 0x10000000) + { + _has256MBMem2 = 1; + // change OS1 globals to act like MEM2 is 64MB + // this is because our Heap expansion code relies on the game thinking it's running on 64MB + POKE_32(0x80003118, 0x04000000); // mem2 size physical + POKE_32(0x8000311C, 0x04000000); // mem2 size simulated + POKE_32(0x80003120, 0x93400000); // end of mem2 + POKE_32(0x80003128, 0x933E0000); // usable mem2 end + POKE_32(0x80003130, 0x933E0000); // ipc buffer start + POKE_32(0x80003134, 0x93400000); // ipc buffer end + } + // otherwise, detect the launcher enabling 256MB on vWii + else if (RB3E_Launcher_ExpandedRAM == 0xCAFE1337) + { + _has256MBMem2 = 1; + } + + if (_has256MBMem2) + { + // change the IBAT and DBAT initialisation functions + + // change ConfigMEM2_52MB to allocate 256MB DBAT + POKE_32(PORT_CONFIGMEM2_52MB_INST + 0x0, ADDI(3, 3, 0x1FFF)); // 0x90000000 cached DBAT + POKE_32(PORT_CONFIGMEM2_52MB_INST + 0x10, ADDI(5, 5, 0x1FFF)); // 0xd0000000 uncached DBAT + // change ConfigMEM2_56MB to allocate 256MB DBAT + POKE_32(PORT_CONFIGMEM2_56MB_INST + 0x0, ADDI(3, 3, 0x1FFF)); // 0x90000000 cached DBAT + POKE_32(PORT_CONFIGMEM2_56MB_INST + 0x10, ADDI(5, 5, 0x1FFF)); // 0xd0000000 uncached DBAT + // change ConfigMEM2_64MB to allocate 256MB DBAT + POKE_32(PORT_CONFIGMEM2_64MB_INST + 0x0, ADDI(3, 3, 0x1FFF)); // 0x90000000 cached DBAT + POKE_32(PORT_CONFIGMEM2_64MB_INST + 0x10, ADDI(5, 5, 0x1FFF)); // 0xd0000000 uncached DBAT + // change EnableInstsOnMEM2Lo16MB to enable insts on all 256MB + // RsoInit calls this in RealMode to allow RSOs to run from MEM2 + POKE_32(PORT_ENABLEINSTSONMEM2LO16MB_INST + 0x0, ADDI(6, 6, 0x1FFF)); // 0x90000000 cached IBAT + // clear icache just in case + ICInvalidateRange((void *)PORT_CONFIGMEM2_52MB_INST, 0x800); + + // hook Heap::Init to change size and location of main heap + HookFunction(PORT_HEAPINIT, HeapInit, HeapInitHook); + } +#endif + + // launch the game _start(); } From 53dab517935c7358e58deefc22f01a8025f1de42 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 3 May 2025 16:10:53 +0100 Subject: [PATCH 021/123] use ubuntu-latest in Wii actions --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f02b03b..fac69e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,7 @@ on: [push] jobs: Wii: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest timeout-minutes: 10 container: From e9863ad33c979a71910cbcccab8b410e08597847 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sun, 4 May 2025 12:27:37 +0100 Subject: [PATCH 022/123] bank 8 support, ports header restructure --- .github/workflows/build.yml | 11 ++ .vscode/c_cpp_properties.json | 20 +++ Makefile | 5 + include/ports.h | 308 +--------------------------------- include/ports_ps3.h | 136 +++++++++++++++ include/ports_wii.h | 132 +++++++++++++++ include/ports_wii_bank8.h | 140 ++++++++++++++++ include/ports_xbox360.h | 184 ++++++++++++++++++++ include/rb3/Data.h | 5 + include/wii_ipc.h | 4 + source/DTAFunctions.c | 15 +- source/MusicLibrary.c | 4 + source/_functions.c | 1 + source/rb3enhanced.c | 22 ++- source/wii.c | 12 ++ 15 files changed, 692 insertions(+), 307 deletions(-) create mode 100644 include/ports_ps3.h create mode 100644 include/ports_wii.h create mode 100644 include/ports_wii_bank8.h create mode 100644 include/ports_xbox360.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fac69e0..dcf7df9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -46,6 +46,17 @@ jobs: name: RB3Enhanced-Wii-NoDebug path: out/RB3Enhanced.mod + - name: Compile (bank8-debug) + run: | + make clean + make wii -j2 DEBUG=1 + + - name: Upload binaries (bank8-debug) + uses: actions/upload-artifact@v4 + with: + name: RB3Enhanced-Wii-Bank8-Debug + path: out/RB3Enhanced.mod + Xbox: if: github.repository == 'RBEnhanced/RB3Enhanced' runs-on: windows-2019 diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5ce7404..89fdfd5 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -35,6 +35,26 @@ "compilerPath": "C:/devkitPro/devkitPPC/bin/powerpc-eabi-gcc.exe", "cStandard": "c89", "intelliSenseMode": "gcc-x86" + }, + { + "name": "Wii (Bank 8)", + "includePath": [ + "${workspaceFolder}/include", + "C:/devkitPro/bslug/include" + ], + "defines": [ + "_DEBUG", + "GEKKO", + "HW_RVL", + "__wii__", + "RB3E", + "RB3E_WII", + "RB3E_WII_BANK8", + "RB3EDEBUG" + ], + "compilerPath": "C:/devkitPro/devkitPPC/bin/powerpc-eabi-gcc.exe", + "cStandard": "c89", + "intelliSenseMode": "gcc-x86" } ], "version": 4 diff --git a/Makefile b/Makefile index 5ec9070..d9d317b 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,11 @@ ifeq ($(strip $(DEBUG)),1) DEFINES += RB3EDEBUG endif +# if BANK8=1 then compile builds for bank 8 dev build +ifeq ($(strip $(BANK8)),1) + DEFINES += RB3E_WII_BANK8 +endif + # Wii variables # ================= # build directory for Wii compilation diff --git a/include/ports.h b/include/ports.h index 7d9d1be..7108fa1 100644 --- a/include/ports.h +++ b/include/ports.h @@ -2,311 +2,11 @@ RB3Enhanced - ports.h Defines port addresses and platform-specific function definitions. */ -#ifdef RB3E_XBOX // Rock Band 3 Xbox 360 Title Update 5 -// instruction patch addresses -#define PORT_SONGLIMIT 0x82579880 // call to "max_song_count" DataNode::_value -#define PORT_APP_CALL 0x82272e88 // call to App::_ct from main() -#define PORT_SONGBLACKLIST 0x82579098 // call to a function that checks song blacklist -#define PORT_DATAINITFUNCS_TAIL 0x82765980 // blr of DataInitFuncs -#define PORT_FASTSTART_CHECK 0x82270f40 // beq after OptionBool("fast",0) in App::_ct -#define PORT_SYSTEMINIT_BLANK 0x825113a4 // call to a stub function in SystemInit -#define PORT_XL_BYPASS_SG 0x82a88268 // lbz r11, bypassSecureGateway in Quazal::XboxClient::Login2 -#define PORT_RCG_POLL_LOGGEDIN 0x824f7178 // check for login status in RockCentralGateway::Poll -#define PORT_AUD_PATCH_CRASH 0x82b8c928 // patch somewhere to prevent Audition Mode crashes - blr -#define PORT_AUD_PATCH_NET1 0x82b8f7fc // patch somewhere to force Audition Mode to be connected (1) - nop -#define PORT_AUD_PATCH_NET2 0x82b8f80c // patch somewhere to force Audition Mode to be connected (2) - nop -#define PORT_AUD_PATCH_NET3 0x82b8f814 // patch somewhere to force Audition Mode to be connected (3) - nop -#define PORT_AUD_PATCH_NET4 0x82b8f81c // patch somewhere to force Audition Mode to be connected (4) - li r3, 0 -#define PORT_AUD_PATCH_NET5 0x82562638 // patch somewhere to force Audition Mode to be connected (5) - li r3, 1 -#define PORT_AUD_PATCH_REPL 0x82b8e978 // patch to allow Audition to recieve insecure packets from Magma -#define PORT_AUD_PATCH_HDD 0x82515dd4 // patch to make has_hard_drive always return true, probably broken -#define PORT_AUD_PATCH_UNK 0x823f6074 // idk -#define PORT_AUD_HANDLE_MESSAGES 0x82563edc // part of AuditionMgr::Handle that handles disconnected from Rock Central/XBL/etc. messages -#define PORT_AUD_HANDLE_MSG_JUMP 0x82564048 // address to jump PORT_AUD_HANDLE_MESSAGES to, to allow audition mode without GoCentral -#define PORT_AUD_INVALID_DATA_CHECK 0x825629fc // check for invalid data in a song submitted to audition mode -#define PORT_XNQOS_PROBE1 0x823ee7f8 // instruction that checks xnqos probe results -#define PORT_XNQOS_PROBE2 0x823ee800 // instruction that checks xnqos probe results -#define PORT_XNQOS_PROBE3 0x823ee80c // instruction that checks xnqos probe results -#define PORT_VDP_DISABLE1 0x82b39ba0 // nop over VDP packet sending -#define PORT_VDP_DISABLE2 0x82b39e60 // nop over VDP packet sending -#define PORT_VDP_DISABLE3 0x82b3a5e4 // nop over VDP packet sending -#define PORT_VDP_DISABLE4 0x82b3a5f0 // nop over VDP packet sending -#define PORT_SESSION_MASK_CHECK 0x82652acc // beq in while loop for instrument mask check -#define PORT_CHARACTER_CLOTHES_CHECK 0x82655148 // check to see if the goal required to select a piece of clothing has been achieved or not -#define PORT_CHARACTER_CLOTHES_CHECK2 0x8265514c // check to see if the goal required to select a piece of clothing has been achieved or not 2 -#define PORT_FACE_PAINT_CHECK 0x82614a60 // check to see if face paint is unlocked -#define PORT_TATTOO_CHECK 0x82614a9c // check to see if tattoos are unlocked -#define PORT_VIDEO_VENUE_CHECK 0x82581634 // check to see if video venues are unlocked -#define PORT_OPTIONSTR_DEFINE 0x82510cc8 // bl to OptionStr("define", NULL) in PreInitSystem -#define PORT_RENDER_RES_X_PATCH1 0x8273bf20 // instruction patch to force render width -#define PORT_RENDER_RES_X_PATCH2 0x8273bf24 // instruction patch to force render width -#define PORT_RENDER_RES_Y_PATCH1 0x8273bedc // instruction patch to force render height -#define PORT_RUNLOOP_SPARE 0x822700e0 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) -#define PORT_SONG_ID_EVALUATE 0x827aa7d4 // branch to DataNode::Evaluate in SongMetadata::__ct -#define PORT_LOADOBJS_BCTRL 0x827562e4 // bctrl to Object::PreLoad insie of DirLoader::LoadObjs -#define PORT_SONGMGR_ISDEMO_CHECK 0x82575f9c // "bne" after IsUGC check inside SongMgr::IsDemo -#define PORT_STAGEKIT_EXISTS 0x8228d03c // StageKit check. nop over to allow for fog command without a physical StageKit connected. -#define PORT_VERTEX_READ_1 0x82418704 // call to Vector3::operator>> to read vertex position -#define PORT_VERTEX_READ_2 0x82418748 // call to Vector3::operator>> to read vertex normals -#define PORT_MULTIPLAYER_CRASH 0x82ae6880 // branch to a function that can crash in online multiplayer -#define PORT_MULTIPLAYER_FIX 0x8282b238 // the function that doesn't crash -#define PORT_QUAZAL_BREAKPOINT 0x828410c0 // address to DbgBreakPoint in Quazal::Platform::Breakpoint -// function patch addresses -#define PORT_SETDISKERROR 0x82516320 // PlatformMgr::SetDiskError -#define PORT_APP_RUN 0x82272e90 // App::Run -#define PORT_APP_RUNNODEBUG 0x82270080 // App::RunWithoutDebugging -#define PORT_APP_CT 0x82270e68 // App::_ct -#define PORT_NEWFILE 0x825173e0 // NewFile -#define PORT_SETTRACKSPEED 0x827dd118 // TrackPanelDirBase::UpdateTrackSpeed -#define PORT_SETSONGSPEED 0x82678C88 // Game::SetMusicSpeed -#define PORT_MODIFIERMGR_CT 0x82589c48 // ModifierManager::__ct -#define PORT_MODIFIERMGR_ACTIVE 0x82588d80 // ModifierManager::ModifierActive -#define PORT_SYMBOL_CT 0x827c0728 // Symbol::Symbol -#define PORT_LOCALIZE 0x827c96d8 // Locale::Localize -#define PORT_ADDGAMEGEM 0x8278e530 // GameGemList::AddGameGem -#define PORT_SONGDATAADDMULTIGEM 0x827719b0 // SongData::AddMultiGem -#define PORT_WILLBENOSTRUM 0x8278cbb0 // GameGemList::WillBeNoStrum -#define PORT_SETVENUE 0x8257d1c0 // MetaPerformer::SetVenue(?) (actual func name is not known) -#define PORT_ISUGCPLUS 0x8259e890 // function that checks song source(?) -#define PORT_KEYSONGUITAR 0x825b50f8 // function that checks "key_keys_on_guitar" -#define PORT_EXECUTEDTA 0x824f7e50 // RockCentralGateway::ExecuteConfig -#define PORT_BANDLABELSETDISPLAYTEXT 0x823406f8 // BandLabel::SetDisplayText -#define PORT_SETSONGANDARTISTNAME 0x825c66f8 // BandLabel::SetSongAndArtistName -#define PORT_SETSONGNAMEFROMNODE 0x825c56a0 // BandLabel::SetSongNameFromNode -#define PORT_DATANODEEVALUATE 0x8274ae98 // DataNode::Evaluate -#define PORT_DATAARRAYFINDARRAY 0x8274c5a0 // DataArray::FindArray -#define PORT_DATAARRAYFINDDATA 0x8274c7f0 // DataArray::FindData -#define PORT_HMXFACTORYFUNCAT 0x82359f28 // HmxObjectFactoryFunc::_at -#define PORT_SETADDRESS 0x82aeb888 // Quazal::InetAddress::SetAddress -#define PORT_XL_USESECURESOCKETS 0x82a8eca8 // Inet::UseSecureSockets -#define PORT_XL_XSESSIONCREATE 0x82a69c90 // XSessionCreate -#define PORT_XL_XSESSIONJOINREMOTE 0x82a69fb0 // XSessionJoinRemote -#define PORT_XL_XSESSIONMODIFY 0x82a69e40 // XSessionModify -#define PORT_XL_XSESSIONSEARCHEX 0x82a6a490 // XSessionSearchEx -#define PORT_XL_XINVITEGETACCEPTEDINFO 0x82a6a7c8 // XInviteGetAcceptedInfo -#define PORT_RANDOMINT 0x824f2f90 // RandomInt(min, max) -#define PORT_GETWIDGETBYNAME 0x82b9b880 // GemManager::GetWidgetByName -#define PORT_GETSLOTCOLOR 0x82baa308 // TrackConfig::GetSlotColor -#define PORT_ARCHIVE_CT 0x82514408 // Archive::_ct -#define PORT_ARCHIVE_SETLOCATIONHARDDRIVE 0x82512b00 // Archive::SetLocationHardDrive -#define PORT_ARCHIVE_MERGE 0x82513ee8 // Archive::Merge -#define PORT_ARCHIVE_DT 0x82513af8 // Archive::_dt -#define PORT_FILE_EXISTS 0x825175b0 // FileExists -#define PORT_QUEUEMESSAGE 0x82628e50 // PassiveMessagesPanel::QueueMessage -#define PORT_SETSYSTEMLANGUAGE 0x82510590 // SetSystemLanguage -#define PORT_ISSUPPORTEDLANGUAGE 0x82510510 // IsSupportedLanguage -#define PORT_DATAREADFILE 0x8276c700 // DataReadFile -#define PORT_STAGEKIT_SET_STATE 0x82524DE0 // StageKit::SetState(?) - actual name not known -#define PORT_GETSONGIDFROMSHORTNAME 0x82577140 // BandSongMgr::GetSongIDFromShortname -#define PORT_GETMETADATA 0x827a8e30 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) -#define PORT_GETSONGSHORTNAME 0x8257c498 // MetaPerformer::GetSongShortname(?) - actual name not known -#define PORT_GAME_CT 0x8267bf30 // Game::__ct -#define PORT_GAME_DT 0x8267b1f0 // Game::__dt -#define PORT_GAMEGETACTIVEPLAYER 0x82678e88 // Game::GetActivePlayer -#define PORT_GETBANDUSERS 0x82683b78 // BandUserMgr::GetBandUsers -#define PORT_GETBANDUSERFROMSLOT 0x82682b60 // BandUserMgr::GetBandUserFromSlot -#define PORT_GETSONGID 0x827a87f0 // GetSongID, function used when adding songs to BandSongMgr -#define PORT_SONGMGRGETRANKEDSONGS 0x82577340 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function -#define PORT_BUILDINSTRUMENTSELECTION 0x82668c70 // BuildInstrumentSelectionList(?) - actual name not known -#define PORT_PREPARESOMEVECTORMAYBE 0x82796d90 // Prepares some vector, used by BuildInstrumentSelectionList -#define PORT_SOMEVECTORPUSHBACKMAYBE 0x82b6aa10 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back -#define PORT_POSTPROC_DOPOST 0x82b89a08 // NgPostProc::DoPost -#define PORT_MUSICLIBRARYSELECTMAYBE 0x8253EB00 // Selects an entry in the Music Library screen - actual name not known -#define PORT_GETSYMBOLBYGAMEORIGIN 0x8265bb78 // SongSortByRecent::GetSymbolByGameOrigin -#define PORT_GETGAMEORIGINBYSYMBOL 0x8265b910 // SongSortByRecent::GetGameOriginBySymbol -#define PORT_SONGSORTBYRECENT 0x8265bde8 // RecentCmp::__ct -#define PORT_FILESTREAM_CT 0x827c3340 // FileStream::__ct (the one that takes a char * path instead of a File object) -#define PORT_CHUNKSTREAM_CT 0x827ca488 // ChunkStream::__ct -#define PORT_RNDPROPANIMSETFRAME 0x82426dd0 // RndPropAnim::SetFrame -#define PORT_DYNAMICCAST 0x8282a0c8 // dynamic_cast -#define PORT_OBJECTFINDUIPANEL 0x82537430 // Object::Find -#define PORT_JOYPADGETCACHEDXINPUTCAPS 0x82531F08 // JoypadGetCachedXInputCaps -#define PORT_JOYPADGETPADDATA 0x82524998 // JoypadGetPadData -#define PORT_MEMFREE 0x827bc430 // MemFree -#define PORT_MEMALLOC 0x827bcd38 // MemAlloc -#define PORT_SYMBOLPREINIT 0x827c04f8 // Symbol::PreInit -#define PORT_QUEUINGSOCKET_BIND 0x82b397b0 // Quazal::QueuingSocket::Bind -#define PORT_QUAZALSOCKET_BIND 0x82b1a830 // Quazal::Socket::Bind -#define PORT_MEMPRINTOVERVIEW 0x827bc838 // MemPrintOverview -#define PORT_MEMPRINT 0x827bc970 // MemPrint -#define PORT_MEMNUMHEAPS 0x827bb628 // MemNumHeaps -#define PORT_INITSONGMETADATA 0x827aa450 // InitSongMetadata -#define PORT_UPDATEPRESENCE 0x82680430 // PresenceMgr::UpdatePresence -#define PORT_STEPSEQUENCEJOBSETSTEP 0x82af92b8 // Quazal::StepSequenceJob::SetStep -#define PORT_BINSTREAMWRITE 0x827c4f58 // BinStream::Write -#define PORT_BINSTREAMREAD 0x827c4ea8 // BinStream::Read -#define PORT_BINSTREAMREADENDIAN 0x827c5058 // BinStream::ReadEndian -#define PORT_BINSTREAMWRITEENDIAN 0x827c5098 // BinStream::WriteEndian -#define PORT_SONGPARSERPITCHTOSLOT 0x82783c20 // SongParser::PitchToSlot -#define PORT_DATASET 0x8275d670 // DataSet -#define PORT_DATASETELEM 0x82760b38 // DataSetElem -#define PORT_DATAONELEM 0x8275ff50 // DataOnElem -#define PORT_DATANODEGETOBJ 0x8274b088 // DataNode::GetObj -// instance addresses -#define PORT_MODIFIERMGR_POINTER 0x82dfec08 // pointer to ModifierManager -#define PORT_ROCKCENTRALGATEWAY 0x82cc8f60 // address of RockCentralGateway -#define PORT_GDATAFUNCS 0x82e05d30 // address of gDataFuncs -#define PORT_THEARCHIVE 0x82cc9c60 // address of TheArchive (main ARK) -#define PORT_THEBANDUI 0x82dfd2b0 // address of TheBandUI -#define PORT_NULLSYMBOL 0x82c71838 // address of gNullSymbol -#define PORT_THESONGDB 0x82e023f8 // address of TheSongDB -#define PORT_THESONGMGR 0x82dfe7b4 // address of TheSongMgr -#define PORT_THEMETAPERFORMER 0x82dfe954 // address of TheMetaPerformer -#define PORT_THEBANDUSERMGR 0x82e023b8 // address of TheBandUserMgr -#define PORT_THESONGSORTMGR 0x82dfee5c // pointer to TheSongSortMgr -#define PORT_THEMUSICLIBRARY 0x82dfd3a8 // pointer to TheMusicLibrary -#define PORT_THEGAME 0x82e02128 // pointer to TheGame (you lost) -#define PORT_OBJECTDIRMAINDIR 0x82e054b8 // ObjectDir::sMainDir -#define PORT_MESH_GREV 0x82cc2638 // address of RndMesh::gRev -// import function stubs -#define PORT_XEKEYSSETKEY_STUB 0x82c4c47c -#define PORT_XEKEYSAESCBC_STUB 0x82c4c48c -#define PORT_SOCKET_STUB 0x8284da48 -#define PORT_XNETSTARTUP 0x8284d7e8 -#define PORT_XNETXNADDRTOINADDR 0x8284d840 -#define PORT_XNETCONNECT 0x8284d890 -#define PORT_XNETUNREGISTERKEY 0x8284d830 -#define PORT_XNETUNREGISTERINADDR 0x8284d870 -#define PORT_XNETREGISTERKEY 0x8284d820 -#define PORT_XNETGETTITLEXNADDR 0x8284d968 -#define PORT_XNETQOSLOOKUP 0x8284d8f8 -#define PORT_XAMUSERGETSIGNININFO 0x82c4be1c -#define PORT_XAMUSERGETSIGNINSTATE 0x82c4bcfc -#define PORT_XAMUSERCHECKPRIVILEGE 0x82c4bd1c -#define PORT_XAMSHOWFRIENDSUI 0x8283d710 - -// define logging functions -void DbgPrint(const char *s, ...); -#define RB3E_PRINT DbgPrint - -#endif // RB3E_XBOX - -#ifdef RB3E_WII // Rock Band 3 Wii - -#include - -// instruction patch addresses -#define PORT_SONGLIMIT 0x801cedac // call to "max_song_count" DataNode::_value -#define PORT_SONGBLACKLIST 0x801d2148 // call to a function that checks song blacklist -#define PORT_DATAINITFUNCS_TAIL 0x80321b7c // blr of DataInitFuncs -#define PORT_FASTSTART_CHECK 0x8000e2f0 // beq after OptionBool("fast",0) in App::_ct -#define PORT_STRAPSCREEN_1 0x8000e40c // branch to CustomSplash::Show in App::_ct -#define PORT_STRAPSCREEN_2 0x8000e41c // branch to CustomSplash::EndShow in App::_ct -#define PORT_NASWII_HOST 0x807e94a0 // branch to the add header function in the DWCDL login function -#define PORT_CHARACTER_CLOTHES_CHECK 0x802607bc // check to see if the goal required to select a piece of clothing has been unlocked -#define PORT_CHARACTER_CLOTHES_CHECK2 0x802607c0 // check to see if the goal required to select a piece of clothing has been unlocked 2 -#define PORT_FACE_PAINT_CHECK 0x801fd9a8 // check to see if face paint is unlocked -#define PORT_TATTOO_CHECK 0x801fd9c4 // check to see if tattoos are unlocked -#define PORT_VIDEO_VENUE_CHECK 0x80227e34 // check to see if video venues are unlocked -#define PORT_OPTIONSTR_DEFINE 0x8030e418 // bl to OptionStr("define", NULL) in PreInitSystem -#define PORT_RUNLOOP_SPARE 0x8000f740 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) -#define PORT_MICCHECK 0x8024a4e8 // a bne that throws an error on the song select screen if the mic is not connected -#define PORT_BIGSYMBOLFUNC_TAIL 0x8037a3d4 // blr after a function that initialises a bunch of symbols -#define PORT_UPDATEPRESENCEBLOCK_B 0x80188194 // branch after the failure case in a function that calls UpdatePresence -#define PORT_MULTIPLAYER_CRASH 0x80018a78 // branch to a function that can crash in online multiplayer -#define PORT_MULTIPLAYER_FIX 0x806ec0e8 // the function that doesn't crash -// #define PORT_LOADOBJS_BCTRL 0x827562e4 -// function patch addresses -#define PORT_SETDISKERROR 0x8030ce7c // PlatformMgr::SetDiskError -#define PORT_NEWFILE 0x802f9ed0 // NewFile -#define PORT_SETTRACKSPEED 0x80441ee0 // TrackPanelDirBase::UpdateTrackSpeed -#define PORT_SETSONGSPEED 0x801130d0 // Game::SetMusicSpeed -#define PORT_MODIFIERMGR_CT 0x8022c1b4 // ModifierManager::__ct -#define PORT_MODIFIERMGR_ACTIVE 0x8022c830 // ModifierManager::ModifierActive -#define PORT_SYMBOL_CT 0x80363f60 // Symbol::Symbol -#define PORT_LOCALIZE 0x803506f4 // Locale::Localize -#define PORT_SETVENUE 0x802282dc // MetaPerformer::SetVenue(?) (actual func name is not known) -#define PORT_EXECUTEDTA 0x802cf7e0 // RockCentralGateway::ExecuteConfig -#define PORT_BANDLABELSETDISPLAYTEXT 0x803b1858 // BandLabel::SetDisplayText -#define PORT_SETSONGANDARTISTNAME 0x801b68a8 // BandLabel::SetSongAndArtistName -#define PORT_KEYSONGUITAR 0x80242ab4 // function that checks "key_keys_on_guitar" -#define PORT_HMXFACTORYFUNCAT 0x8031b2f8 // HmxObjectFactoryFunc::_at -#define PORT_WILLBENOSTRUM 0x80463010 // GameGemList::WillBeNoStrum -#define PORT_ADDGAMEGEM 0x80463198 // GameGemList::AddGameGem -#define PORT_SETADDRESS 0x8001bf74 // Quazal::InetAddress::SetAddress -#define PORT_RANDOMINT 0x802ddd60 // RandomInt(min, max) -#define PORT_GETWIDGETBYNAME 0x800d59b0 // GemManager::GetWidgetByName -#define PORT_DATANODEEVALUATE 0x80322e9c // DataNode::Evaluate -#define PORT_GETSLOTCOLOR 0x800e42a4 // TrackConfig::GetSlotColor -#define PORT_USBWIIGETTYPE 0x806c1a3c // UsbWii::GetType -#define PORT_FILE_EXISTS 0x802fa134 // FileExists -#define PORT_QUEUEMESSAGE 0x80253c50 // PassiveMessagesPanel::QueueMessage -#define PORT_SETSYSTEMLANGUAGE 0x8030f308 // SetSystemLanguage -#define PORT_ISSUPPORTEDLANGUAGE 0x8030f280 // IsSupportedLanguage -#define PORT_DATAREADFILE 0x80319bdc // DataReadFile -#define PORT_GAME_CT 0x80110f20 // Game::__ct -#define PORT_GAME_DT 0x80111614 // Game::__dt -#define PORT_GAMEGETACTIVEPLAYER 0x8011346c // Game::GetActivePlayer -#define PORT_WIINETINIT_DNSLOOKUP 0x8030c3a0 // WiiNetInit::StartDNSLookup -#define PORT_BUILDINSTRUMENTSELECTION 0x802478a8 // BuildInstrumentSelectionList(?) - actual name not known -#define PORT_PREPARESOMEVECTORMAYBE 0x80247c58 // Prepares some vector, used by BuildInstrumentSelectionList -#define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back -#define PORT_POSTPROC_DOPOST 0x806b52b4 // WiiPostProc::DoPost -#define PORT_MUSICLIBRARYSELECTMAYBE 0x80230d64 // Selects an entry in the Music Library screen - actual name not known -#define PORT_GETSYMBOLBYGAMEORIGIN 0x8027dd3c // SongSortByRecent::GetSymbolByGameOrigin -#define PORT_GETGAMEORIGINBYSYMBOL 0x8027dc58 // SongSortByRecent::GetGameOriginBySymbol -#define PORT_SONGSORTBYRECENT 0x8027dba8 // RecentCmp::__ct -#define PORT_FILESTREAM_CT 0x8034c9f8 // FileStream::__ct (the one that takes a char * path instead of a File object) -#define PORT_CHUNKSTREAM_CT 0x8034aa90 // ChunkStream::__ct -#define PORT_GETBANDUSERFROMSLOT 0x8010021c // BandUserMgr::GetBandUserFromSlot -#define PORT_GETBANDUSERS 0x80100558 // BandUserMgr::GetBandUsers -#define PORT_GETSONGSHORTNAME 0x80224edc // MetaPerformer::GetSongShortname(?) - actual name not known -#define PORT_GETMETADATA 0x80515510 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) -#define PORT_GETSONGID 0x8051513c // GetSongID, function used when adding songs to BandSongMgr -#define PORT_SONGMGRGETRANKEDSONGS 0x801d1590 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function -#define PORT_GETSONGIDFROMSHORTNAME 0x801d0b44 // BandSongMgr::GetSongIDFromShortname -#define PORT_RNDPROPANIMSETFRAME 0x80632790 // RndPropAnim::SetFrame -#define PORT_DYNAMICCAST 0x806f5e78 // dynamic_cast -#define PORT_OBJECTFINDUIPANEL 0x80101d74 // Object::Find -#define PORT_JOYPADGETPADDATA 0x80302eec // JoypadGetPadData -#define PORT_MEMALLOC 0x80353e18 // MemAlloc -#define PORT_MEMFREE 0x80354238 // MemFree -#define PORT_SYMBOLPREINIT 0x80364c74 // Symbol::PreInit -#define PORT_QUEUINGSOCKET_BIND 0x800478d4 // Quazal::QueuingSocket::Bind -#define PORT_QUAZALSOCKET_BIND 0x8001cd10 // Quazal::Socket::Bind -#define PORT_INITSONGMETADATA 0x805147a4 // InitSongMetadata -#define PORT_UPDATEPRESENCE 0x801879d4 // PresenceMgr::UpdatePresence -#define PORT_STEPSEQUENCEJOBSETSTEP 0x80025364 // Quazal::StepSequenceJob::SetStep -#define PORT_BINSTREAMWRITE 0x80342f48 // BinStream::Write -#define PORT_BINSTREAMREAD 0x80342e7c // BinStream::Read -#define PORT_BINSTREAMREADENDIAN 0x803430bc // BinStream::ReadEndian -#define PORT_BINSTREAMWRITEENDIAN 0x80343190 // BinStream::WriteEndian -#define PORT_SONGPARSERPITCHTOSLOT 0x8048e298 // SongParser::PitchToSlot -#define PORT_DATASET 0x8031b86c // DataSetSet -#define PORT_DATASETELEM 0x8031e9a0 // DataSetElem -#define PORT_DATAONELEM 0x8031dc40 // DataOnElem -#define PORT_HEAPINIT 0x80352cbc // Heap::Init -// instance addresses -#define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager -#define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway -#define PORT_GDATAFUNCS 0x8091a528 // address of gDataFuncs -#define PORT_THEARCHIVE 0x80902234 // address of TheArchive (main ARK) -#define PORT_THEBANDUI 0x808fc2b0 // address of TheBandUI -#define PORT_NULLSYMBOL 0x808540e0 // address of gNullSymbol -#define PORT_THESONGDB 0x808fb170 // address of TheSongDB - TODO: check -#define PORT_THEMUSICLIBRARY 0x808fda84 // pointer to TheMusicLibrary -#define PORT_THESONGSORTMGR 0x808ff988 // pointer to TheSongSortMgr -#define PORT_THESONGMGR 0x808fbda4 // address of TheSongMgr -#define PORT_THEMETAPERFORMER 0x808fd6f8 // address of TheMetaPerformer -#define PORT_THEBANDUSERMGR 0x808f9350 // pointer to TheBandUserMgr -#define PORT_THEGAME 0x808f9758 // pointer to TheGame (you lost) -#define PORT_OBJECTDIRMAINDIR 0x8091b1e8 // ObjectDir::sMainDir -// string pointers -#define PORT_NASWII_AC_URL 0x808e2310 -#define PORT_NASWII_PR_URL 0x808e2390 -// wii stuff -#define PORT_WII_OS0_GLOBALS 0x80000000 // "os0"/"early" globals -#define PORT_WII_OS1_GLOBALS 0x80003000 // "os1"/"late" globals -#define PORT_CONFIGMEM2_52MB_INST 0x80769340 // ConfigMEM2_52MB 0x90000000 length param -#define PORT_CONFIGMEM2_56MB_INST 0x80769420 // ConfigMEM2_56MB 0x90000000 length param -#define PORT_CONFIGMEM2_64MB_INST 0x80769500 // ConfigMEM2_64MB 0x90000000 length param -#define PORT_ENABLEINSTSONMEM2LO16MB_INST 0x80769740 // EnableInstsOnMEM2Lo16MB 0x90000000 length param - -// define logging functions -#define RB3E_PRINT printf - -#endif // RB3E_WII +#include "ports_xbox360.h" +#include "ports_wii.h" +#include "ports_ps3.h" +#include "ports_wii_bank8.h" #define RB3E_MSG(x, ...) RB3E_PRINT("[RB3E:MSG] " x "\n", __VA_ARGS__) #ifdef RB3EDEBUG diff --git a/include/ports_ps3.h b/include/ports_ps3.h new file mode 100644 index 0000000..50d035f --- /dev/null +++ b/include/ports_ps3.h @@ -0,0 +1,136 @@ +/* + RB3Enhanced - ports_ps3.h + Defines port addresses and platform-specific function definitions (for PS3 1.06 EUR) +*/ + +#ifdef RB3E_PS3 // Rock Band 3 PS3 BLES00986 1.06 + +// instruction patch addresses +#define PORT_SONGLIMIT 0x00281580 // call to "max_song_count" DataNode::_value +#define PORT_APP_CALL 0x00017384 // call to App::_ct from main() +#define PORT_SONGBLACKLIST 0x00280BE4 // call to BandSongMgr::IsInExclusionList +#define PORT_DATAINITFUNCS_TAIL 0x008F1D48 // blr of DataInitFuncs +#define PORT_FASTSTART_CHECK 0x00012744 // beq after OptionBool("fast",0) in App::_ct +#define PORT_SYSTEMINIT_BLANK 0x008BFBD0 // call to a stub function in SystemInit +#define PORT_CHARACTER_CLOTHES_CHECK 0x002A92DC // check to see if the goal required to select a piece of clothing has been achieved or not +#define PORT_FACE_PAINT_CHECK 0x002A4C60 // check to see if face paint is unlocked +#define PORT_TATTOO_CHECK 0x002A4CA0 // check to see if tattoos are unlocked +#define PORT_VIDEO_VENUE_CHECK 0x002D06A8 // check to see if video venues are unlocked +#define PORT_OPTIONSTR_DEFINE 0x008C005C // bl to OptionStr("define", NULL) in PreInitSystem +#define PORT_RUNLOOP_SPARE 0x00010960 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) +#define PORT_SONG_ID_EVALUATE 0x0063E6B8 // branch to DataNode::Evaluate in SongMetadata::__ct +// #define PORT_LOADOBJS_BCTRL 0 // bctrl to Object::PreLoad insie of DirLoader::LoadObjs +#define PORT_STAGEKIT_EXISTS 0x00442FAC // StageKit check. nop over to allow for fog command without a physical StageKit connected. +#define PORT_PS3_VORBISREADER_CTR_CHECK 0x00883F40 // check in the SPU VorbisReader decoder that checks if there is a CTR state +#define PORT_NPDRM_UNAVAILABLE_FALLTHROUGH 0x008C8BD0 // fallthrough after sceNpDrmIsAvailable (r == CELL_OK) beq instruction +#define PORT_NPDRM_FSOPEN_JUMP 0x008C8C6C // statement that runs after sceNpDrmIsAvailable returns CELL_OK (starts "li r0, 2") +#define PORT_NPDRM_FSOPEN_RETURN 0x008C8C80 // branch to a "extsw r4, r9" after the npdrm cellFsOpen completes +#define PORT_NPDRM_SETERRORCODE_JUMP 0x008C8C5C // "extsw r4, r3" after the call to cellFsOpen for unencrypted midi files +// function patch addresses +#define PORT_SETDISKERROR 0x008CB888 // PlatformMgr::SetDiskError +#define PORT_APP_RUN 0x000108C8 // App::Run +#define PORT_APP_CT 0x000125CC // App::_ct +#define PORT_NEWFILE 0x008AB024 // NewFile +#define PORT_SETTRACKSPEED 0x0051A3D8 // TrackPanelDirBase::UpdateTrackSpeed +#define PORT_SETSONGSPEED 0x0017BBE0 // Game::SetMusicSpeed +#define PORT_MODIFIERMGR_CT 0x002D7718 // ModifierManager::__ct +#define PORT_MODIFIERMGR_ACTIVE 0x002D5B64 // ModifierManager::ModifierActive +#define PORT_SYMBOL_CT 0x00956C00 // Symbol::Symbol +#define PORT_LOCALIZE 0x00937554 // Locale::Localize +// #define PORT_ADDGAMEGEM 0 // GameGemList::AddGameGem +// #define PORT_WILLBENOSTRUM 0 // GameGemList::WillBeNoStrum +#define PORT_SETVENUE 0x002CC1E0 // MetaPerformer::SetVenue(?) (actual func name is not known) +#define PORT_ISUGCPLUS 0x00276470 // function that checks song source(?) +#define PORT_KEYSONGUITAR 0x0030028C // function that checks "key_keys_on_guitar" +#define PORT_EXECUTEDTA 0x00416A58 // RockCentralGateway::ExecuteConfig +#define PORT_BANDLABELSETDISPLAYTEXT 0x00465D78 // BandLabel::SetDisplayText +#define PORT_SETSONGANDARTISTNAME 0x00265A44 // BandLabel::SetSongAndArtistName +#define PORT_SETSONGNAMEFROMNODE 0x00263C28 // BandLabel::SetSongNameFromNode +#define PORT_DATANODEEVALUATE 0x008FBE24 // DataNode::Evaluate +#define PORT_DATAARRAYFINDARRAY 0x008E95B4 // DataArray::FindArray +#define PORT_DATAARRAYFINDDATA 0x008E9D14 // DataArray::FindData +#define PORT_ADDDTAFUNCTIONPS3 0x008F0AC0 // unknown name function, adds DTA function to gDataFuncs, PS3 only +#define PORT_SETADDRESS 0x000D12B4 // Quazal::InetAddress::SetAddress +#define PORT_RANDOMINT 0x0096F5D4 // RandomInt(min, max) +#define PORT_GETWIDGETBYNAME 0x009CC848 // GemManager::GetWidgetByName +#define PORT_GETSLOTCOLOR 0x009DC30C // TrackConfig::GetSlotColor +#define PORT_ARCHIVE_CT 0x008A0934 // Archive::_ct +#define PORT_ARCHIVE_SETLOCATIONHARDDRIVE 0x0089E46C // Archive::SetLocationHardDrive +#define PORT_ARCHIVE_MERGE 0x008A0CB4 // Archive::Merge +// #define PORT_ARCHIVE_DT 0 // Archive::_dt +#define PORT_FILE_EXISTS 0x008AB2AC // FileExists +#define PORT_QUEUEMESSAGE 0x00323570 // PassiveMessagesPanel::QueueMessage +#define PORT_SETSYSTEMLANGUAGE 0x008BEDB0 // SetSystemLanguage +#define PORT_ISSUPPORTEDLANGUAGE 0x008BE934 // IsSupportedLanguage +#define PORT_DATAREADFILE 0x008EF84C // DataReadFile +#define PORT_STAGEKIT_SET_STATE 0x008B2FBC // StageKit::SetState(?) - actual name not known +// #define PORT_GETSONGIDFROMSHORTNAME 0 // BandSongMgr::GetSongIDFromShortname +// #define PORT_GETMETADATA 0 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) +#define PORT_GETSONGSHORTNAME 0x002C9960 // MetaPerformer::GetSongShortname(?) - actual name not known +#define PORT_GAME_CT 0x00181CE4 // Game::__ct +#define PORT_GAME_DT 0x0018337C // Game::__dt +#define PORT_GAMEGETACTIVEPLAYER 0x0017BB48 // Game::GetActivePlayer +#define PORT_GETBANDUSERS 0x00178E00 // BandUserMgr::GetBandUsers +#define PORT_GETBANDUSERFROMSLOT 0x00176FA0 // BandUserMgr::GetBandUserFromSlot +#define PORT_GETSONGID 0x0063F7A8 // GetSongID, function used when adding songs to BandSongMgr +#define PORT_SONGMGRGETRANKEDSONGS 0x002802B8 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function +#define PORT_BUILDINSTRUMENTSELECTION 0x003E4EC4 // BuildInstrumentSelectionList(?) - actual name not known +#define PORT_PREPARESOMEVECTORMAYBE 0x003E4EAC // Prepares some vector, used by BuildInstrumentSelectionList +#define PORT_SOMEVECTORPUSHBACKMAYBE 0x003E4E1C // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back +#define PORT_POSTPROC_DOPOST 0x00A04218 // NgPostProc::DoPost +#define PORT_MUSICLIBRARYSELECTMAYBE 0x002E09A0 // Selects an entry in the Music Library screen - actual name not known +#define PORT_GETSYMBOLBYGAMEORIGIN 0x003EA7F8 // SongSortByRecent::GetSymbolByGameOrigin +#define PORT_GETGAMEORIGINBYSYMBOL 0x003EA5E8 // SongSortByRecent::GetGameOriginBySymbol +#define PORT_SONGSORTBYRECENT 0x003EAA9C // RecentCmp::__ct +#define PORT_FILESTREAM_CT 0x00935764 // FileStream::__ct (the one that takes a char * path instead of a File object) +#define PORT_CHUNKSTREAM_CT 0x0093100C // ChunkStream::__ct +// #define PORT_RNDPROPANIMSETFRAME 0 // RndPropAnim::SetFrame +#define PORT_DYNAMICCAST 0x00A3BA7C // dynamic_cast +#define PORT_OBJECTFINDUIPANEL 0x008FFF64 // Object::Find +#define PORT_JOYPADGETPADDATA 0x008B18C0 // JoypadGetPadData +#define PORT_MEMFREE 0x0093EEC0 // MemFree +#define PORT_MEMALLOC 0x0093FAE0 // MemAlloc +#define PORT_SYMBOLPREINIT 0x00956F68 // Symbol::PreInit +// #define PORT_QUEUINGSOCKET_BIND 0 // Quazal::QueuingSocket::Bind +// #define PORT_QUAZALSOCKET_BIND 0 // Quazal::Socket::Bind +// #define PORT_MEMPRINTOVERVIEW 0 // MemPrintOverview +// #define PORT_MEMPRINT 0 // MemPrint +// #define PORT_MEMNUMHEAPS 0 // MemNumHeaps +#define PORT_INITSONGMETADATA 0x0063DAB4 // InitSongMetadata +// #define PORT_UPDATEPRESENCE 0 // PresenceMgr::UpdatePresence +#define PORT_STEPSEQUENCEJOBSETSTEP 0x000DD0D8 // Quazal::StepSequenceJob::SetStep +#define PORT_CTR_DECRYPT 0x00A1FBF8 // ctr_decrypt +#define PORT_TITLEIDREGISTER 0x008A6224 // TitleIDRegister(?) - function that gets passed title IDs at startup / config parsing +#define PORT_CANPLAYSONG_PARENTAL 0x00391648 // broken function that checks against parental controls whether a song is playable +// instance addresses +#define PORT_MODIFIERMGR_POINTER 0x00F2B870 // pointer to ModifierManager +#define PORT_ROCKCENTRALGATEWAY 0x00F33948 // address of RockCentralGateway +// #define PORT_GDATAFUNCS 0 // address of gDataFuncs +// #define PORT_THEARCHIVE 0 // address of TheArchive (main ARK) +// #define PORT_THEBANDUI 0 // address of TheBandUI +#define PORT_NULLSYMBOL 0x00F1BEF0 // address of gNullSymbol +// #define PORT_THESONGDB 0 // address of TheSongDB +// #define PORT_THESONGMGR 0 // address of TheSongMgr +// #define PORT_THEMETAPERFORMER 0 // address of TheMetaPerformer +#define PORT_THEBANDUSERMGR 0x00F23460 // address of TheBandUserMgr +// #define PORT_THESONGSORTMGR 0 // pointer to TheSongSortMgr +// #define PORT_THEMUSICLIBRARY 0 // pointer to TheMusicLibrary +// #define PORT_THEGAME 0 // pointer to TheGame (you lost) +// #define PORT_OBJECTDIRMAINDIR 0 // ObjectDir::sMainDir +// ps3 usb specific hooks +#define PORT_LDDREGISTERTAIL 0x008e4240 // the "ld r2" instruction after the final call to cellUsbdRegisterExtraLdd2 +#define PORT_LDDADDRESS 0x00f1bf1c // just one of the LDDs. doesn't matter which. this one is RockBandKeyboard +#define PORT_ISUSBDEVICEVALID 0x008e1074 // function called by the Probe LDD function that checks if a device is valid + +// define logging functions +extern int _sys_printf(char *fmt, ...); +#define RB3E_PRINT _sys_printf + +// do some definitions of stuff +extern int _sys_sprintf(char *buf, char *fmt, ...); +#define printf _sys_printf +#define sprintf _sys_sprintf + +#define PLUGIN_PTR(x) (uint32_t)(*(uint32_t *)&x) + +#endif // RB3E_PS3 diff --git a/include/ports_wii.h b/include/ports_wii.h new file mode 100644 index 0000000..7af6c15 --- /dev/null +++ b/include/ports_wii.h @@ -0,0 +1,132 @@ +/* + RB3Enhanced - ports_wii.h + Defines port addresses and platform-specific function definitions (for Wii retail) +*/ + +#ifdef RB3E_WII // Rock Band 3 Wii +#ifndef RB3E_WII_BANK8 + +#include + +// instruction patch addresses +#define PORT_SONGLIMIT 0x801cedac // call to "max_song_count" DataNode::_value +#define PORT_SONGBLACKLIST 0x801d2148 // call to BandSongMgr::IsInExclusionList +#define PORT_DATAINITFUNCS_TAIL 0x80321b7c // blr of DataInitFuncs +#define PORT_FASTSTART_CHECK 0x8000e2f0 // beq after OptionBool("fast",0) in App::_ct +#define PORT_STRAPSCREEN_1 0x8000e40c // branch to CustomSplash::Show in App::_ct +#define PORT_STRAPSCREEN_2 0x8000e41c // branch to CustomSplash::EndShow in App::_ct +#define PORT_NASWII_HOST 0x807e94a0 // branch to the add header function in the DWCDL login function +#define PORT_CHARACTER_CLOTHES_CHECK 0x802607bc // check to see if the goal required to select a piece of clothing has been unlocked +#define PORT_CHARACTER_CLOTHES_CHECK2 0x802607c0 // check to see if the goal required to select a piece of clothing has been unlocked 2 +#define PORT_FACE_PAINT_CHECK 0x801fd9a8 // check to see if face paint is unlocked +#define PORT_TATTOO_CHECK 0x801fd9c4 // check to see if tattoos are unlocked +#define PORT_VIDEO_VENUE_CHECK 0x80227e34 // check to see if video venues are unlocked +#define PORT_OPTIONSTR_DEFINE 0x8030e418 // bl to OptionStr("define", NULL) in PreInitSystem +#define PORT_RUNLOOP_SPARE 0x8000f740 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) +#define PORT_MICCHECK 0x8024a4e8 // a bne that throws an error on the song select screen if the mic is not connected +#define PORT_BIGSYMBOLFUNC_TAIL 0x8037a3d4 // blr after a function that initialises a bunch of symbols +#define PORT_UPDATEPRESENCEBLOCK_B 0x80188194 // branch after the failure case in a function that calls UpdatePresence +#define PORT_MULTIPLAYER_CRASH 0x80018a78 // branch to a function that can crash in online multiplayer +#define PORT_MULTIPLAYER_FIX 0x806ec0e8 // the function that doesn't crash +// #define PORT_LOADOBJS_BCTRL 0x827562e4 +// function patch addresses +#define PORT_SETDISKERROR 0x8030ce7c // PlatformMgr::SetDiskError +#define PORT_NEWFILE 0x802f9ed0 // NewFile +#define PORT_SETTRACKSPEED 0x80441ee0 // TrackPanelDirBase::UpdateTrackSpeed +#define PORT_SETSONGSPEED 0x801130d0 // Game::SetMusicSpeed +#define PORT_MODIFIERMGR_CT 0x8022c1b4 // ModifierManager::__ct +#define PORT_MODIFIERMGR_ACTIVE 0x8022c830 // ModifierManager::ModifierActive +#define PORT_SYMBOL_CT 0x80363f60 // Symbol::Symbol +#define PORT_LOCALIZE 0x803506f4 // Locale::Localize +#define PORT_SETVENUE 0x802282dc // MetaPerformer::SetVenue(?) (actual func name is not known) +#define PORT_EXECUTEDTA 0x802cf7e0 // RockCentralGateway::ExecuteConfig +#define PORT_BANDLABELSETDISPLAYTEXT 0x803b1858 // BandLabel::SetDisplayText +#define PORT_SETSONGANDARTISTNAME 0x801b68a8 // BandLabel::SetSongAndArtistName +#define PORT_KEYSONGUITAR 0x80242ab4 // function that checks "key_keys_on_guitar" +#define PORT_HMXFACTORYFUNCAT 0x8031b2f8 // HmxObjectFactoryFunc::_at +#define PORT_WILLBENOSTRUM 0x80463010 // GameGemList::WillBeNoStrum +#define PORT_ADDGAMEGEM 0x80463198 // GameGemList::AddGameGem +#define PORT_SETADDRESS 0x8001bf74 // Quazal::InetAddress::SetAddress +#define PORT_RANDOMINT 0x802ddd60 // RandomInt(min, max) +#define PORT_GETWIDGETBYNAME 0x800d59b0 // GemManager::GetWidgetByName +#define PORT_DATANODEEVALUATE 0x80322e9c // DataNode::Evaluate +#define PORT_GETSLOTCOLOR 0x800e42a4 // TrackConfig::GetSlotColor +#define PORT_USBWIIGETTYPE 0x806c1a3c // UsbWii::GetType +#define PORT_FILE_EXISTS 0x802fa134 // FileExists +#define PORT_QUEUEMESSAGE 0x80253c50 // PassiveMessagesPanel::QueueMessage +#define PORT_SETSYSTEMLANGUAGE 0x8030f308 // SetSystemLanguage +#define PORT_ISSUPPORTEDLANGUAGE 0x8030f280 // IsSupportedLanguage +#define PORT_DATAREADFILE 0x80319bdc // DataReadFile +#define PORT_GAME_CT 0x80110f20 // Game::__ct +#define PORT_GAME_DT 0x80111614 // Game::__dt +#define PORT_GAMEGETACTIVEPLAYER 0x8011346c // Game::GetActivePlayer +#define PORT_WIINETINIT_DNSLOOKUP 0x8030c3a0 // WiiNetInit::StartDNSLookup +#define PORT_BUILDINSTRUMENTSELECTION 0x802478a8 // BuildInstrumentSelectionList(?) - actual name not known +#define PORT_PREPARESOMEVECTORMAYBE 0x80247c58 // Prepares some vector, used by BuildInstrumentSelectionList +#define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back +#define PORT_POSTPROC_DOPOST 0x806b52b4 // WiiPostProc::DoPost +#define PORT_MUSICLIBRARYSELECTMAYBE 0x80230d64 // Selects an entry in the Music Library screen - actual name not known +#define PORT_GETSYMBOLBYGAMEORIGIN 0x8027dd3c // SongSortByRecent::GetSymbolByGameOrigin +#define PORT_GETGAMEORIGINBYSYMBOL 0x8027dc58 // SongSortByRecent::GetGameOriginBySymbol +#define PORT_SONGSORTBYRECENT 0x8027dba8 // RecentCmp::__ct +#define PORT_FILESTREAM_CT 0x8034c9f8 // FileStream::__ct (the one that takes a char * path instead of a File object) +#define PORT_CHUNKSTREAM_CT 0x8034aa90 // ChunkStream::__ct +#define PORT_GETBANDUSERFROMSLOT 0x8010021c // BandUserMgr::GetBandUserFromSlot +#define PORT_GETBANDUSERS 0x80100558 // BandUserMgr::GetBandUsers +#define PORT_GETSONGSHORTNAME 0x80224edc // MetaPerformer::GetSongShortname(?) - actual name not known +#define PORT_GETMETADATA 0x80515510 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) +#define PORT_GETSONGID 0x8051513c // GetSongID, function used when adding songs to BandSongMgr +#define PORT_SONGMGRGETRANKEDSONGS 0x801d1590 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function +#define PORT_GETSONGIDFROMSHORTNAME 0x801d0b44 // BandSongMgr::GetSongIDFromShortname +#define PORT_RNDPROPANIMSETFRAME 0x80632790 // RndPropAnim::SetFrame +#define PORT_DYNAMICCAST 0x806f5e78 // dynamic_cast +#define PORT_OBJECTFINDUIPANEL 0x80101d74 // Object::Find +#define PORT_JOYPADGETPADDATA 0x80302eec // JoypadGetPadData +#define PORT_MEMALLOC 0x80353e18 // MemAlloc +#define PORT_MEMFREE 0x80354238 // MemFree +#define PORT_SYMBOLPREINIT 0x80364c74 // Symbol::PreInit +#define PORT_QUEUINGSOCKET_BIND 0x800478d4 // Quazal::QueuingSocket::Bind +#define PORT_QUAZALSOCKET_BIND 0x8001cd10 // Quazal::Socket::Bind +#define PORT_INITSONGMETADATA 0x805147a4 // InitSongMetadata +#define PORT_UPDATEPRESENCE 0x801879d4 // PresenceMgr::UpdatePresence +#define PORT_STEPSEQUENCEJOBSETSTEP 0x80025364 // Quazal::StepSequenceJob::SetStep +#define PORT_BINSTREAMWRITE 0x80342f48 // BinStream::Write +#define PORT_BINSTREAMREAD 0x80342e7c // BinStream::Read +#define PORT_BINSTREAMREADENDIAN 0x803430bc // BinStream::ReadEndian +#define PORT_BINSTREAMWRITEENDIAN 0x80343190 // BinStream::WriteEndian +#define PORT_SONGPARSERPITCHTOSLOT 0x8048e298 // SongParser::PitchToSlot +#define PORT_DATASET 0x8031b86c // DataSetSet +#define PORT_DATASETELEM 0x8031e9a0 // DataSetElem +#define PORT_DATAONELEM 0x8031dc40 // DataOnElem +#define PORT_HEAPINIT 0x80352cbc // Heap::Init +// instance addresses +#define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager +#define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway +#define PORT_GDATAFUNCS 0x8091a528 // address of gDataFuncs +#define PORT_THEARCHIVE 0x80902234 // address of TheArchive (main ARK) +#define PORT_THEBANDUI 0x808fc2b0 // address of TheBandUI +#define PORT_NULLSYMBOL 0x808540e0 // address of gNullSymbol +#define PORT_THESONGDB 0x808fb170 // address of TheSongDB - TODO: check +#define PORT_THEMUSICLIBRARY 0x808fda84 // pointer to TheMusicLibrary +#define PORT_THESONGSORTMGR 0x808ff988 // pointer to TheSongSortMgr +#define PORT_THESONGMGR 0x808fbda4 // address of TheSongMgr +#define PORT_THEMETAPERFORMER 0x808fd6f8 // address of TheMetaPerformer +#define PORT_THEBANDUSERMGR 0x808f9350 // pointer to TheBandUserMgr +#define PORT_THEGAME 0x808f9758 // pointer to TheGame (you lost) +#define PORT_OBJECTDIRMAINDIR 0x8091b1e8 // ObjectDir::sMainDir +// string pointers +#define PORT_NASWII_AC_URL 0x808e2310 +#define PORT_NASWII_PR_URL 0x808e2390 +// wii stuff +#define PORT_WII_OS0_GLOBALS 0x80000000 // "os0"/"early" globals +#define PORT_WII_OS1_GLOBALS 0x80003000 // "os1"/"late" globals +#define PORT_CONFIGMEM2_52MB_INST 0x80769340 // ConfigMEM2_52MB 0x90000000 length param +#define PORT_CONFIGMEM2_56MB_INST 0x80769420 // ConfigMEM2_56MB 0x90000000 length param +#define PORT_CONFIGMEM2_64MB_INST 0x80769500 // ConfigMEM2_64MB 0x90000000 length param +#define PORT_ENABLEINSTSONMEM2LO16MB_INST 0x80769740 // EnableInstsOnMEM2Lo16MB 0x90000000 length param + +// define logging functions +#define RB3E_PRINT printf + +#endif // RB3E_WII_BANK8 +#endif // RB3E_WII diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h new file mode 100644 index 0000000..db37dec --- /dev/null +++ b/include/ports_wii_bank8.h @@ -0,0 +1,140 @@ +/* + RB3Enhanced - ports_wii.h + Defines port addresses and platform-specific function definitions (for Wii "bank 8"/100901_A) + + Commented out defines are either not needed or not +*/ + +#ifdef RB3E_WII // Rock Band 3 Wii 2010-09-01 "bank 8" build +#ifdef RB3E_WII_BANK8 + +#include + +// instruction patch addresses +#define PORT_SONGLIMIT 0x8026fbf0 // call to "max_song_count" DataNode::_value +#define PORT_SONGBLACKLIST 0x80273298 // call to BandSongMgr::IsInExclusionList +#define PORT_DATAINITFUNCS_TAIL 0x8045d080 // blr of DataInitFuncs +#define PORT_FASTSTART_CHECK 0x8000e464 // beq after OptionBool("fast",0) in App::_ct +#define PORT_STRAPSCREEN_1 0x8000e5a4 // branch to CustomSplash::Show in App::_ct +#define PORT_STRAPSCREEN_2 0x8000e5b4 // branch to CustomSplash::EndShow in App::_ct +#define PORT_NASWII_HOST 0x80b2df90 // branch to the add header function in the DWCDL login function +// #define PORT_CHARACTER_CLOTHES_CHECK 0x802607bc // FIXME check to see if the goal required to select a piece of clothing has been unlocked +// #define PORT_CHARACTER_CLOTHES_CHECK2 0x802607c0 // FIXME check to see if the goal required to select a piece of clothing has been unlocked 2 +// #define PORT_FACE_PAINT_CHECK 0x801fd9a8 // FIXME check to see if face paint is unlocked +// #define PORT_TATTOO_CHECK 0x801fd9c4 // FIXME check to see if tattoos are unlocked +// #define PORT_VIDEO_VENUE_CHECK 0x80227e34 // FIXME check to see if video venues are unlocked +#define PORT_OPTIONSTR_DEFINE 0x8044219c // bl to OptionStr("define", NULL) in PreInitSystem +// #define PORT_RUNLOOP_SPARE 0x8000f740 // FIXME branch to a function that only has a "blr" in App::Run(WithoutDebugging) +// #define PORT_MICCHECK 0x8024a4e8 // NOT NEEDED? a bne that throws an error on the song select screen if the mic is not connected +#define PORT_BIGSYMBOLFUNC_TAIL 0x804d3bac // blr after a function that initialises a bunch of symbols +#define PORT_UPDATEPRESENCEBLOCK_B 0x8021cbd0 // branch after the failure case in a function that calls UpdatePresence +#define PORT_MULTIPLAYER_CRASH 0x80023fec // branch to a function that can crash in online multiplayer +#define PORT_MULTIPLAYER_FIX 0x80a2f0c4 // the function that doesn't crash +// #define PORT_LOADOBJS_BCTRL 0x827562e4 +// function patch addresses +#define PORT_SETDISKERROR 0x804400a0 // PlatformMgr::SetDiskError +#define PORT_NEWFILE 0x804204a0 // NewFile +#define PORT_SETTRACKSPEED 0x805f9060 // TrackPanelDirBase::UpdateTrackSpeed +#define PORT_SETSONGSPEED 0x8017fa80 // Game::SetMusicSpeed +#define PORT_MODIFIERMGR_CT 0x802f7130 // ModifierManager::__ct +#define PORT_MODIFIERMGR_ACTIVE 0x802f7990 // ModifierManager::ModifierActive +#define PORT_SYMBOL_CT 0x804bd1c0 // Symbol::Symbol +#define PORT_LOCALIZE 0x8049b7f0 // Locale::Localize +#define PORT_SETVENUE 0x802f12f0 // MetaPerformer::SetVenue(?) (actual func name is not known) +#define PORT_EXECUTEDTA 0x803edbd0 // RockCentralGateway::ExecuteConfig +#define PORT_BANDLABELSETDISPLAYTEXT 0x80518360 // BandLabel::SetDisplayText +#define PORT_SETSONGANDARTISTNAME 0x80254650 // BandLabel::SetSongAndArtistName +#define PORT_KEYSONGUITAR 0x8031c1b0 // OvershellPanel::CanGuitarPlayKeys +// #define PORT_HMXFACTORYFUNCAT 0x8031b2f8 // FIXME HmxObjectFactoryFunc::_at - inlined in bank8? +#define PORT_WILLBENOSTRUM 0x8062b2d0 // GameGemList::WillBeNoStrum +#define PORT_ADDGAMEGEM 0x8062b460 // GameGemList::AddGameGem +#define PORT_SETADDRESS 0x80028d00 // Quazal::InetAddress::SetAddress +#define PORT_RANDOMINT 0x803fef30 // RandomInt(min, max) +#define PORT_GETWIDGETBYNAME 0x80135f30 // GemManager::GetWidgetByName +#define PORT_DATANODEEVALUATE 0x8045e030 // DataNode::Evaluate +#define PORT_GETSLOTCOLOR 0x801484d0 // TrackConfig::GetSlotColor +#define PORT_USBWIIGETTYPE 0x809fd840 // UsbWii::GetType +#define PORT_FILE_EXISTS 0x804207c0 // FileExists +#define PORT_QUEUEMESSAGE 0x80333140 // PassiveMessagesPanel::QueueMessage +#define PORT_SETSYSTEMLANGUAGE 0x80443570 // SetSystemLanguage +#define PORT_ISSUPPORTEDLANGUAGE 0x804434e0 // IsSupportedLanguage +#define PORT_DATAREADFILE 0x80452ef0 // DataReadFile +#define PORT_GAME_CT 0x8017d250 // Game::__ct +#define PORT_GAME_DT 0x8017dbd0 // Game::__dt +#define PORT_GAMEGETACTIVEPLAYER 0x8017fe40 // Game::GetActivePlayer +#define PORT_WIINETINIT_DNSLOOKUP 0x8043f1c0 // WiiNetInit::StartDNSLookup +#define PORT_BUILDINSTRUMENTSELECTION 0x802478a8 // BuildInstrumentSelectionList(?) - actual name not known +#define PORT_PREPARESOMEVECTORMAYBE 0x80247c58 // Prepares some vector, used by BuildInstrumentSelectionList +#define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back +#define PORT_POSTPROC_DOPOST 0x809ef2d0 // WiiPostProc::DoPost +#define PORT_MUSICLIBRARYSELECTMAYBE 0x802ff140 // UNSURE MusicLibrary::TryToSetHighlight, Selects an entry in the Music Library screen - actual name not known +// #define PORT_GETSYMBOLBYGAMEORIGIN 0x8027dd3c // FIXME SongSortByRecent::GetSymbolByGameOrigin +// #define PORT_GETGAMEORIGINBYSYMBOL 0x8027dc58 // FIXME SongSortByRecent::GetGameOriginBySymbol +#define PORT_SONGSORTBYRECENT 0x803747d0 // FIXME RecentCmp::__ct +#define PORT_FILESTREAM_CT 0x80494fd0 // FileStream::__ct (the one that takes a char * path instead of a File object) +#define PORT_CHUNKSTREAM_CT 0x804929a0 // ChunkStream::__ct +// #define PORT_GETBANDUSERFROMSLOT 0x8010021c // FIXME BandUserMgr::GetBandUserFromSlot +#define PORT_GETBANDUSERS 0x801683e0 // BandUserMgr::GetBandUsers +#define PORT_GETSONGSHORTNAME 0x802f5dd0 // MetaPerformer::GetSongSymbol +#define PORT_GETMETADATA 0x80271de0 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) +// #define PORT_GETSONGID 0x8051513c // FIXME GetSongID, function used when adding songs to BandSongMgr +#define PORT_SONGMGRGETRANKEDSONGS 0x802726a0 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function +#define PORT_GETSONGIDFROMSHORTNAME 0x801d0b44 // BandSongMgr::GetSongIDFromShortname +#define PORT_RNDPROPANIMSETFRAME 0x80928020 // RndPropAnim::SetFrame +#define PORT_DYNAMICCAST 0x80a38f54 // dynamic_cast +// #define PORT_OBJECTFINDUIPANEL 0x80101d74 // FIXME Object::Find +#define PORT_JOYPADGETPADDATA 0x8042f6f0 // JoypadGetPadData +#define PORT_MEMALLOC 0x804a0a70 // MemAlloc +#define PORT_MEMFREE 0x804a12c0 // MemFree +#define PORT_SYMBOLPREINIT 0x804be150 // Symbol::PreInit +#define PORT_QUEUINGSOCKET_BIND 0x80068820 // Quazal::QueuingSocket::Bind +#define PORT_QUAZALSOCKET_BIND 0x80029dc0 // Quazal::Socket::Bind +#define PORT_INITSONGMETADATA 0x8075a0c0 // InitSongMetadata +#define PORT_UPDATEPRESENCE 0x8021c3d0 // PresenceMgr::UpdatePresence +#define PORT_STEPSEQUENCEJOBSETSTEP 0x80035cf0 // Quazal::StepSequenceJob::SetStep +#define PORT_BINSTREAMWRITE 0x804892b0 // BinStream::Write +#define PORT_BINSTREAMREAD 0x80489140 // BinStream::Read +#define PORT_BINSTREAMREADENDIAN 0x804894f0 // BinStream::ReadEndian +#define PORT_BINSTREAMWRITEENDIAN 0x80489640 // BinStream::WriteEndian +#define PORT_SONGPARSERPITCHTOSLOT 0x8065d3d0 // SongParser::PitchToSlot +#define PORT_DATASET 0x80454b00 // DataSet +#define PORT_DATASETELEM 0x80459240 // DataSetElem +#define PORT_DATAONELEM 0x80457bf0 // DataOnElem +#define PORT_HEAPINIT 0x8049f030 // Heap::Init +#define PORT_DATAREGISTERFUNC 0x804545e0 // DataRegisterFunc +// instance addresses +#define PORT_MODIFIERMGR_POINTER 0x80c904a8 // pointer to ModifierManager +#define PORT_ROCKCENTRALGATEWAY 0x80c91818 // address of RockCentralGateway +#define PORT_GDATAFUNCS 0x80cab890 // address of gDataFuncs +#define PORT_THEARCHIVE 0x80c92dd0 // address of TheArchive (main ARK) +#define PORT_THEBANDUI 0x80c8f800 // address of TheBandUI +#define PORT_NULLSYMBOL 0x80bb5d30 // FIXME address of gNullSymbol +#define PORT_THESONGDB 0x80c8f048 // address of TheSongDB - TODO: check +#define PORT_THEMUSICLIBRARY 0x80c904b8 // pointer to TheMusicLibrary +#define PORT_THESONGSORTMGR 0x80c91248 // pointer to TheSongSortMgr +#define PORT_THESONGMGR 0x80c8f780 // address of TheSongMgr +#define PORT_THEMETAPERFORMER 0x80c90470 // address of TheMetaPerformer / MetaPerformer::sMetaPerformer +#define PORT_THEBANDUSERMGR 0x80c8e9b8 // pointer to TheBandUserMgr +#define PORT_THEGAME 0x80c8eb18 // pointer to TheGame (you lost) +#define PORT_OBJECTDIRMAINDIR 0x80cac538 // ObjectDir::sMainDir +// string pointers +#define PORT_NASWII_AC_URL 0x80c77fdc +#define PORT_NASWII_PR_URL 0x80c7805c +// wii stuff +#define PORT_WII_OS0_GLOBALS 0x80000000 // "os0"/"early" globals +#define PORT_WII_OS1_GLOBALS 0x80003000 // "os1"/"late" globals +#define PORT_CONFIGMEM2_52MB_INST 0x80aac4e0 // ConfigMEM2_52MB 0x90000000 length param +#define PORT_CONFIGMEM2_56MB_INST 0x80aac5c0 // ConfigMEM2_56MB 0x90000000 length param +#define PORT_CONFIGMEM2_64MB_INST 0x80aac6a0 // ConfigMEM2_64MB 0x90000000 length param +#define PORT_ENABLEINSTSONMEM2LO16MB_INST 0x80aac8e0 // EnableInstsOnMEM2Lo16MB 0x90000000 length param +// bank8 specific stuff +#define PORT_BANK8_MEM2_RSO_ASSERT1 0x804428e8 +#define PORT_BANK8_MEM2_RSO_ASSERT2 0x80442940 +#define PORT_BANK8_MEM2_RSO_ASSERT3 0x804b42a8 +#define PORT_BANK8_MEM2_RSO_ASSERT4 0x804b4198 + +// define logging functions +#define RB3E_PRINT printf + +#endif // RB3E_WII_BANK8 +#endif // RB3E_WII diff --git a/include/ports_xbox360.h b/include/ports_xbox360.h new file mode 100644 index 0000000..9b73cbd --- /dev/null +++ b/include/ports_xbox360.h @@ -0,0 +1,184 @@ +/* + RB3Enhanced - ports_xbox360.h + Defines port addresses and platform-specific function definitions (for 360 TU5) +*/ + +#ifdef RB3E_XBOX // Rock Band 3 Xbox 360 Title Update 5 + +// instruction patch addresses +#define PORT_SONGLIMIT 0x82579880 // call to "max_song_count" DataNode::_value +#define PORT_APP_CALL 0x82272e88 // call to App::_ct from main() +#define PORT_SONGBLACKLIST 0x82579098 // call to BandSongMgr::IsInExclusionList +#define PORT_DATAINITFUNCS_TAIL 0x82765980 // blr of DataInitFuncs +#define PORT_FASTSTART_CHECK 0x82270f40 // beq after OptionBool("fast",0) in App::_ct +#define PORT_SYSTEMINIT_BLANK 0x825113a4 // call to a stub function in SystemInit +#define PORT_XL_BYPASS_SG 0x82a88268 // lbz r11, bypassSecureGateway in Quazal::XboxClient::Login2 +#define PORT_RCG_POLL_LOGGEDIN 0x824f7178 // check for login status in RockCentralGateway::Poll +#define PORT_AUD_PATCH_CRASH 0x82b8c928 // patch somewhere to prevent Audition Mode crashes - blr +#define PORT_AUD_PATCH_NET1 0x82b8f7fc // patch somewhere to force Audition Mode to be connected (1) - nop +#define PORT_AUD_PATCH_NET2 0x82b8f80c // patch somewhere to force Audition Mode to be connected (2) - nop +#define PORT_AUD_PATCH_NET3 0x82b8f814 // patch somewhere to force Audition Mode to be connected (3) - nop +#define PORT_AUD_PATCH_NET4 0x82b8f81c // patch somewhere to force Audition Mode to be connected (4) - li r3, 0 +#define PORT_AUD_PATCH_NET5 0x82562638 // patch somewhere to force Audition Mode to be connected (5) - li r3, 1 +#define PORT_AUD_PATCH_REPL 0x82b8e978 // patch to allow Audition to recieve insecure packets from Magma +#define PORT_AUD_PATCH_HDD 0x82515dd4 // patch to make has_hard_drive always return true, probably broken +#define PORT_AUD_PATCH_UNK 0x823f6074 // idk +#define PORT_AUD_HANDLE_MESSAGES 0x82563edc // part of AuditionMgr::Handle that handles disconnected from Rock Central/XBL/etc. messages +#define PORT_AUD_HANDLE_MSG_JUMP 0x82564048 // address to jump PORT_AUD_HANDLE_MESSAGES to, to allow audition mode without GoCentral +#define PORT_AUD_INVALID_DATA_CHECK 0x825629fc // check for invalid data in a song submitted to audition mode +#define PORT_XNQOS_PROBE1 0x823ee7f8 // instruction that checks xnqos probe results +#define PORT_XNQOS_PROBE2 0x823ee800 // instruction that checks xnqos probe results +#define PORT_XNQOS_PROBE3 0x823ee80c // instruction that checks xnqos probe results +#define PORT_VDP_DISABLE1 0x82b39ba0 // nop over VDP packet sending +#define PORT_VDP_DISABLE2 0x82b39e60 // nop over VDP packet sending +#define PORT_VDP_DISABLE3 0x82b3a5e4 // nop over VDP packet sending +#define PORT_VDP_DISABLE4 0x82b3a5f0 // nop over VDP packet sending +#define PORT_SESSION_MASK_CHECK 0x82652acc // beq in while loop for instrument mask check +#define PORT_CHARACTER_CLOTHES_CHECK 0x82655148 // check to see if the goal required to select a piece of clothing has been achieved or not +#define PORT_CHARACTER_CLOTHES_CHECK2 0x8265514c // check to see if the goal required to select a piece of clothing has been achieved or not 2 +#define PORT_FACE_PAINT_CHECK 0x82614a60 // check to see if face paint is unlocked +#define PORT_TATTOO_CHECK 0x82614a9c // check to see if tattoos are unlocked +#define PORT_VIDEO_VENUE_CHECK 0x82581634 // check to see if video venues are unlocked +#define PORT_OPTIONSTR_DEFINE 0x82510cc8 // bl to OptionStr("define", NULL) in PreInitSystem +#define PORT_RENDER_RES_X_PATCH1 0x8273bf20 // instruction patch to force render width +#define PORT_RENDER_RES_X_PATCH2 0x8273bf24 // instruction patch to force render width +#define PORT_RENDER_RES_Y_PATCH1 0x8273bedc // instruction patch to force render height +#define PORT_RUNLOOP_SPARE 0x822700e0 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) +#define PORT_SONG_ID_EVALUATE 0x827aa7d4 // branch to DataNode::Evaluate in SongMetadata::__ct +#define PORT_LOADOBJS_BCTRL 0x827562e4 // bctrl to Object::PreLoad insie of DirLoader::LoadObjs +#define PORT_SONGMGR_ISDEMO_CHECK 0x82575f9c // "bne" after IsUGC check inside SongMgr::IsDemo +#define PORT_STAGEKIT_EXISTS 0x8228d03c // StageKit check. nop over to allow for fog command without a physical StageKit connected. +#define PORT_VERTEX_READ_1 0x82418704 // call to Vector3::operator>> to read vertex position +#define PORT_VERTEX_READ_2 0x82418748 // call to Vector3::operator>> to read vertex normals +#define PORT_MULTIPLAYER_CRASH 0x82ae6880 // branch to a function that can crash in online multiplayer +#define PORT_MULTIPLAYER_FIX 0x8282b238 // the function that doesn't crash +#define PORT_QUAZAL_BREAKPOINT 0x828410c0 // address to DbgBreakPoint in Quazal::Platform::Breakpoint +// function patch addresses +#define PORT_SETDISKERROR 0x82516320 // PlatformMgr::SetDiskError +#define PORT_APP_RUN 0x82272e90 // App::Run +#define PORT_APP_RUNNODEBUG 0x82270080 // App::RunWithoutDebugging +#define PORT_APP_CT 0x82270e68 // App::_ct +#define PORT_NEWFILE 0x825173e0 // NewFile +#define PORT_SETTRACKSPEED 0x827dd118 // TrackPanelDirBase::UpdateTrackSpeed +#define PORT_SETSONGSPEED 0x82678C88 // Game::SetMusicSpeed +#define PORT_MODIFIERMGR_CT 0x82589c48 // ModifierManager::__ct +#define PORT_MODIFIERMGR_ACTIVE 0x82588d80 // ModifierManager::ModifierActive +#define PORT_SYMBOL_CT 0x827c0728 // Symbol::Symbol +#define PORT_LOCALIZE 0x827c96d8 // Locale::Localize +#define PORT_ADDGAMEGEM 0x8278e530 // GameGemList::AddGameGem +#define PORT_SONGDATAADDMULTIGEM 0x827719b0 // SongData::AddMultiGem +#define PORT_WILLBENOSTRUM 0x8278cbb0 // GameGemList::WillBeNoStrum +#define PORT_SETVENUE 0x8257d1c0 // MetaPerformer::SetVenue(?) (actual func name is not known) +#define PORT_ISUGCPLUS 0x8259e890 // function that checks song source(?) +#define PORT_KEYSONGUITAR 0x825b50f8 // function that checks "key_keys_on_guitar" +#define PORT_EXECUTEDTA 0x824f7e50 // RockCentralGateway::ExecuteConfig +#define PORT_BANDLABELSETDISPLAYTEXT 0x823406f8 // BandLabel::SetDisplayText +#define PORT_SETSONGANDARTISTNAME 0x825c66f8 // BandLabel::SetSongAndArtistName +#define PORT_SETSONGNAMEFROMNODE 0x825c56a0 // BandLabel::SetSongNameFromNode +#define PORT_DATANODEEVALUATE 0x8274ae98 // DataNode::Evaluate +#define PORT_DATAARRAYFINDARRAY 0x8274c5a0 // DataArray::FindArray +#define PORT_DATAARRAYFINDDATA 0x8274c7f0 // DataArray::FindData +#define PORT_HMXFACTORYFUNCAT 0x82359f28 // HmxObjectFactoryFunc::_at +#define PORT_SETADDRESS 0x82aeb888 // Quazal::InetAddress::SetAddress +#define PORT_XL_USESECURESOCKETS 0x82a8eca8 // Inet::UseSecureSockets +#define PORT_XL_XSESSIONCREATE 0x82a69c90 // XSessionCreate +#define PORT_XL_XSESSIONJOINREMOTE 0x82a69fb0 // XSessionJoinRemote +#define PORT_XL_XSESSIONMODIFY 0x82a69e40 // XSessionModify +#define PORT_XL_XSESSIONSEARCHEX 0x82a6a490 // XSessionSearchEx +#define PORT_XL_XINVITEGETACCEPTEDINFO 0x82a6a7c8 // XInviteGetAcceptedInfo +#define PORT_RANDOMINT 0x824f2f90 // RandomInt(min, max) +#define PORT_GETWIDGETBYNAME 0x82b9b880 // GemManager::GetWidgetByName +#define PORT_GETSLOTCOLOR 0x82baa308 // TrackConfig::GetSlotColor +#define PORT_ARCHIVE_CT 0x82514408 // Archive::_ct +#define PORT_ARCHIVE_SETLOCATIONHARDDRIVE 0x82512b00 // Archive::SetLocationHardDrive +#define PORT_ARCHIVE_MERGE 0x82513ee8 // Archive::Merge +#define PORT_ARCHIVE_DT 0x82513af8 // Archive::_dt +#define PORT_FILE_EXISTS 0x825175b0 // FileExists +#define PORT_QUEUEMESSAGE 0x82628e50 // PassiveMessagesPanel::QueueMessage +#define PORT_SETSYSTEMLANGUAGE 0x82510590 // SetSystemLanguage +#define PORT_ISSUPPORTEDLANGUAGE 0x82510510 // IsSupportedLanguage +#define PORT_DATAREADFILE 0x8276c700 // DataReadFile +#define PORT_STAGEKIT_SET_STATE 0x82524DE0 // StageKit::SetState(?) - actual name not known +#define PORT_GETSONGIDFROMSHORTNAME 0x82577140 // BandSongMgr::GetSongIDFromShortname +#define PORT_GETMETADATA 0x827a8e30 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) +#define PORT_GETSONGSHORTNAME 0x8257c498 // MetaPerformer::GetSongShortname(?) - actual name not known +#define PORT_GAME_CT 0x8267bf30 // Game::__ct +#define PORT_GAME_DT 0x8267b1f0 // Game::__dt +#define PORT_GAMEGETACTIVEPLAYER 0x82678e88 // Game::GetActivePlayer +#define PORT_GETBANDUSERS 0x82683b78 // BandUserMgr::GetBandUsers +#define PORT_GETBANDUSERFROMSLOT 0x82682b60 // BandUserMgr::GetBandUserFromSlot +#define PORT_GETSONGID 0x827a87f0 // GetSongID, function used when adding songs to BandSongMgr +#define PORT_SONGMGRGETRANKEDSONGS 0x82577340 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function +#define PORT_BUILDINSTRUMENTSELECTION 0x82668c70 // BuildInstrumentSelectionList(?) - actual name not known +#define PORT_PREPARESOMEVECTORMAYBE 0x82796d90 // Prepares some vector, used by BuildInstrumentSelectionList +#define PORT_SOMEVECTORPUSHBACKMAYBE 0x82b6aa10 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back +#define PORT_POSTPROC_DOPOST 0x82b89a08 // NgPostProc::DoPost +#define PORT_MUSICLIBRARYSELECTMAYBE 0x8253EB00 // Selects an entry in the Music Library screen - actual name not known +#define PORT_GETSYMBOLBYGAMEORIGIN 0x8265bb78 // SongSortByRecent::GetSymbolByGameOrigin +#define PORT_GETGAMEORIGINBYSYMBOL 0x8265b910 // SongSortByRecent::GetGameOriginBySymbol +#define PORT_SONGSORTBYRECENT 0x8265bde8 // RecentCmp::__ct +#define PORT_FILESTREAM_CT 0x827c3340 // FileStream::__ct (the one that takes a char * path instead of a File object) +#define PORT_CHUNKSTREAM_CT 0x827ca488 // ChunkStream::__ct +#define PORT_RNDPROPANIMSETFRAME 0x82426dd0 // RndPropAnim::SetFrame +#define PORT_DYNAMICCAST 0x8282a0c8 // dynamic_cast +#define PORT_OBJECTFINDUIPANEL 0x82537430 // Object::Find +#define PORT_JOYPADGETCACHEDXINPUTCAPS 0x82531F08 // JoypadGetCachedXInputCaps +#define PORT_JOYPADGETPADDATA 0x82524998 // JoypadGetPadData +#define PORT_MEMFREE 0x827bc430 // MemFree +#define PORT_MEMALLOC 0x827bcd38 // MemAlloc +#define PORT_SYMBOLPREINIT 0x827c04f8 // Symbol::PreInit +#define PORT_QUEUINGSOCKET_BIND 0x82b397b0 // Quazal::QueuingSocket::Bind +#define PORT_QUAZALSOCKET_BIND 0x82b1a830 // Quazal::Socket::Bind +#define PORT_MEMPRINTOVERVIEW 0x827bc838 // MemPrintOverview +#define PORT_MEMPRINT 0x827bc970 // MemPrint +#define PORT_MEMNUMHEAPS 0x827bb628 // MemNumHeaps +#define PORT_INITSONGMETADATA 0x827aa450 // InitSongMetadata +#define PORT_UPDATEPRESENCE 0x82680430 // PresenceMgr::UpdatePresence +#define PORT_STEPSEQUENCEJOBSETSTEP 0x82af92b8 // Quazal::StepSequenceJob::SetStep +#define PORT_BINSTREAMWRITE 0x827c4f58 // BinStream::Write +#define PORT_BINSTREAMREAD 0x827c4ea8 // BinStream::Read +#define PORT_BINSTREAMREADENDIAN 0x827c5058 // BinStream::ReadEndian +#define PORT_BINSTREAMWRITEENDIAN 0x827c5098 // BinStream::WriteEndian +#define PORT_SONGPARSERPITCHTOSLOT 0x82783c20 // SongParser::PitchToSlot +#define PORT_DATASET 0x8275d670 // DataSet +#define PORT_DATASETELEM 0x82760b38 // DataSetElem +#define PORT_DATAONELEM 0x8275ff50 // DataOnElem +#define PORT_DATANODEGETOBJ 0x8274b088 // DataNode::GetObj +// instance addresses +#define PORT_MODIFIERMGR_POINTER 0x82dfec08 // pointer to ModifierManager +#define PORT_ROCKCENTRALGATEWAY 0x82cc8f60 // address of RockCentralGateway +#define PORT_GDATAFUNCS 0x82e05d30 // address of gDataFuncs +#define PORT_THEARCHIVE 0x82cc9c60 // address of TheArchive (main ARK) +#define PORT_THEBANDUI 0x82dfd2b0 // address of TheBandUI +#define PORT_NULLSYMBOL 0x82c71838 // address of gNullSymbol +#define PORT_THESONGDB 0x82e023f8 // address of TheSongDB +#define PORT_THESONGMGR 0x82dfe7b4 // address of TheSongMgr +#define PORT_THEMETAPERFORMER 0x82dfe954 // address of TheMetaPerformer +#define PORT_THEBANDUSERMGR 0x82e023b8 // address of TheBandUserMgr +#define PORT_THESONGSORTMGR 0x82dfee5c // pointer to TheSongSortMgr +#define PORT_THEMUSICLIBRARY 0x82dfd3a8 // pointer to TheMusicLibrary +#define PORT_THEGAME 0x82e02128 // pointer to TheGame (you lost) +#define PORT_OBJECTDIRMAINDIR 0x82e054b8 // ObjectDir::sMainDir +#define PORT_MESH_GREV 0x82cc2638 // address of RndMesh::gRev +// import function stubs +#define PORT_XEKEYSSETKEY_STUB 0x82c4c47c +#define PORT_XEKEYSAESCBC_STUB 0x82c4c48c +#define PORT_SOCKET_STUB 0x8284da48 +#define PORT_XNETSTARTUP 0x8284d7e8 +#define PORT_XNETXNADDRTOINADDR 0x8284d840 +#define PORT_XNETCONNECT 0x8284d890 +#define PORT_XNETUNREGISTERKEY 0x8284d830 +#define PORT_XNETUNREGISTERINADDR 0x8284d870 +#define PORT_XNETREGISTERKEY 0x8284d820 +#define PORT_XNETGETTITLEXNADDR 0x8284d968 +#define PORT_XNETQOSLOOKUP 0x8284d8f8 +#define PORT_XAMUSERGETSIGNININFO 0x82c4be1c +#define PORT_XAMUSERGETSIGNINSTATE 0x82c4bcfc +#define PORT_XAMUSERCHECKPRIVILEGE 0x82c4bd1c +#define PORT_XAMSHOWFRIENDSUI 0x8283d710 + +// define logging functions +void DbgPrint(const char *s, ...); +#define RB3E_PRINT DbgPrint + +#endif // RB3E_XBOX diff --git a/include/rb3/Data.h b/include/rb3/Data.h index 710cb85..adee3d0 100644 --- a/include/rb3/Data.h +++ b/include/rb3/Data.h @@ -76,4 +76,9 @@ extern DataNode *DataOnElem(DataNode *ret, DataArray *array); extern void *DataNodeGetObj(DataNode *node); +#ifdef RB3E_WII_BANK8 +// used on bank 8 +extern void DataRegisterFunc(Symbol name, void *func); +#endif + #endif // _DATA_H diff --git a/include/wii_ipc.h b/include/wii_ipc.h index 9c8d934..371fb09 100644 --- a/include/wii_ipc.h +++ b/include/wii_ipc.h @@ -1,3 +1,7 @@ +/* + RB3Enhanced - wii_ipc.h + IPC structures for interacting with the IOS processor. +*/ #ifdef RB3E_WII #ifndef _WII_IPC_H #define _WII_IPC_H diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index 78ebfd1..7aef4be 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -170,6 +170,7 @@ DataNode *DTASendModData(DataNode *node, DataArray *args) void AddDTAFunctions() { +#ifndef RB3E_WII_BANK8 *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.print_debug) = (int)PrintToDebugger; *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_change_music_speed) = (int)ChangeMusicSpeed; *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_change_track_speed) = (int)ChangeTrackSpeed; @@ -180,5 +181,17 @@ void AddDTAFunctions() *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_relaunch_game) = (int)DTARelaunchGame; *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_get_song_count) = (int)DTAGetSongCount; *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_send_event_string) = (int)DTASendModData; +#else + DataRegisterFunc(globalSymbols.print_debug, PrintToDebugger); + DataRegisterFunc(globalSymbols.rb3e_change_music_speed, ChangeMusicSpeed); + DataRegisterFunc(globalSymbols.rb3e_change_track_speed, ChangeTrackSpeed); + DataRegisterFunc(globalSymbols.rb3e_get_music_speed, GetMusicSpeed); + DataRegisterFunc(globalSymbols.rb3e_get_track_speed, GetTrackSpeed); + DataRegisterFunc(globalSymbols.rb3e_set_venue, DTASetVenue); + DataRegisterFunc(globalSymbols.rb3e_is_emulator, DTAIsEmulator); + DataRegisterFunc(globalSymbols.rb3e_relaunch_game, DTARelaunchGame); + DataRegisterFunc(globalSymbols.rb3e_get_song_count, DTAGetSongCount); + DataRegisterFunc(globalSymbols.rb3e_send_event_string, DTASendModData); +#endif RB3E_MSG("Added DTA functions!", NULL); -} \ No newline at end of file +} diff --git a/source/MusicLibrary.c b/source/MusicLibrary.c index 69c3633..d595943 100644 --- a/source/MusicLibrary.c +++ b/source/MusicLibrary.c @@ -7,12 +7,16 @@ void CheckForPanelAndJump(Symbol entryName, int sortType) { +#ifndef RB3E_WII_BANK8 // check if the song select panel is "up" (displayed on screen) before attempting a jump UIPanel *songSelectPanel = ObjectFindUIPanel(*(int **)PORT_OBJECTDIRMAINDIR, "song_select_panel", 1); if (songSelectPanel != NULL && songSelectPanel->is_up == 1) { MusicLibrarySelect(*(int *)PORT_THEMUSICLIBRARY, entryName, sortType, 1); } +#else + RB3E_MSG("CheckForPanelAndJump not implemented on Bank 8!", NULL); +#endif } // selects any entry based on the full artist name or the game origin diff --git a/source/_functions.c b/source/_functions.c index b451527..93e56e2 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -102,3 +102,4 @@ RB3E_STUB(DataSetElem) RB3E_STUB(DataOnElem) RB3E_STUB(DataNodeGetObj) RB3E_STUB(HeapInit) +RB3E_STUB(DataRegisterFunc) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 9d0750f..a260f69 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -209,8 +209,11 @@ void ApplyPatches() POKE_32(PORT_STRAPSCREEN_2, NOP); // Patch out erroneous second host header POKE_32(PORT_NASWII_HOST, NOP); +#ifndef RB3E_WII_BANK8 // always take the branch to 0x8024a628 so vocals can be selected without a mic plugged in + // bank 8 does not have the mic check POKE_32(PORT_MICCHECK, 0x42800140); +#endif // always fire the UpdatePresence function. TODO(Emma): look into it, still not firing when screen is changed :/ POKE_32(PORT_UPDATEPRESENCEBLOCK_B, NOP); #elif RB3E_XBOX @@ -248,6 +251,9 @@ void ApplyConfigurablePatches() POKE_32(PORT_RENDER_RES_Y_PATCH1, LI(11, config.RenderResY)); } #endif + +#ifndef RB3E_WII_BANK8 + // TODO(Emma): port offsets to bank8 if (config.UnlockClothing == 1) { // Unlocks all clothing, tattoos, face paint, and video venues @@ -257,6 +263,7 @@ void ApplyConfigurablePatches() POKE_32(PORT_FACE_PAINT_CHECK, LI(3, 1)); POKE_32(PORT_VIDEO_VENUE_CHECK, LI(3, 1)); } +#endif #ifdef RB3EDEBUG if (config.QuazalLogging == 1) @@ -316,7 +323,13 @@ void InitialiseFunctions() POKE_B(&BandLabelSetDisplayText, PORT_BANDLABELSETDISPLAYTEXT); POKE_B(&SymbolConstruct, PORT_SYMBOL_CT); POKE_B(&ModifierActive, PORT_MODIFIERMGR_ACTIVE); +#ifndef RB3E_WII_BANK8 POKE_B(&HmxFactoryFuncAt, PORT_HMXFACTORYFUNCAT); + POKE_B(&GetBandUserFromSlot, PORT_GETBANDUSERFROMSLOT); + POKE_B(&ObjectFindUIPanel, PORT_OBJECTFINDUIPANEL); +#else + POKE_B(&DataRegisterFunc, PORT_DATAREGISTERFUNC); +#endif POKE_B(&RandomInt, PORT_RANDOMINT); POKE_B(&DataNodeEvaluate, PORT_DATANODEEVALUATE); POKE_B(&FileExists, PORT_FILE_EXISTS); @@ -327,12 +340,10 @@ void InitialiseFunctions() POKE_B(&GetMetadata, PORT_GETMETADATA); POKE_B(&GetSongIDFromShortname, PORT_GETSONGIDFROMSHORTNAME); POKE_B(&GetBandUsers, PORT_GETBANDUSERS); - POKE_B(&GetBandUserFromSlot, PORT_GETBANDUSERFROMSLOT); POKE_B(&FileStreamConstructor, PORT_FILESTREAM_CT); POKE_B(&ChunkStreamConstructor, PORT_CHUNKSTREAM_CT); POKE_B(&Dynamic_Cast, PORT_DYNAMICCAST); POKE_B(&GameGetActivePlayer, PORT_GAMEGETACTIVEPLAYER); - POKE_B(&ObjectFindUIPanel, PORT_OBJECTFINDUIPANEL); POKE_B(&JoypadGetPadData, PORT_JOYPADGETPADDATA); POKE_B(&MemAlloc, PORT_MEMALLOC); POKE_B(&MemFree, PORT_MEMFREE); @@ -349,7 +360,10 @@ void ApplyHooks() POKE_B(PORT_ISSUPPORTEDLANGUAGE, &IsSupportedLanguageHook); POKE_B(PORT_BUILDINSTRUMENTSELECTION, &BuildInstrumentSelectionList); POKE_BL(PORT_OPTIONSTR_DEFINE, &DefinesHook); +#ifndef RB3E_WII_BANK8 + // TODO(Emma): find suitable alternative for bank8 POKE_BL(PORT_RUNLOOP_SPARE, &RB3E_RunLoop); +#endif HookFunction(PORT_LOCALIZE, &Localize, &LocalizeHook); HookFunction(PORT_WILLBENOSTRUM, &WillBeNoStrum, &WillBeNoStrumHook); HookFunction(PORT_ADDGAMEGEM, &AddGameGem, &AddGameGemHook); @@ -364,10 +378,14 @@ void ApplyHooks() HookFunction(PORT_GETSLOTCOLOR, &GetSlotColor, &GetSlotColorHook); HookFunction(PORT_SETSYSTEMLANGUAGE, &SetSystemLanguage, &SetSystemLanguageHook); HookFunction(PORT_DATAREADFILE, &DataReadFile, &DataReadFileHook); +#ifndef RB3E_WII_BANK8 + // TODO(Emma): port GetBandUserFromSlot to bank8 HookFunction(PORT_GAME_CT, &GameConstruct, &GameConstructHook); HookFunction(PORT_GAME_DT, &GameDestruct, &GameDestructHook); + // TODO(Emma): port offsets to bank 8 HookFunction(PORT_GETSYMBOLBYGAMEORIGIN, &GetSymbolByGameOrigin, &GetSymbolByGameOriginHook); HookFunction(PORT_GETGAMEORIGINBYSYMBOL, &GetGameOriginBySymbol, &GetGameOriginBySymbolHook); +#endif HookFunction(PORT_RNDPROPANIMSETFRAME, &PropAnimSetFrame, &PropAnimSetFrameHook); HookFunction(PORT_SYMBOLPREINIT, &SymbolPreInit, &SymbolPreInitHook); HookFunction(PORT_INITSONGMETADATA, &InitSongMetadata, &InitSongMetadataHook); diff --git a/source/wii.c b/source/wii.c index 76bb0f6..4baf41b 100644 --- a/source/wii.c +++ b/source/wii.c @@ -29,7 +29,11 @@ int RB3E_Launcher_ExpandedRAM = 0; BSLUG_MODULE_GAME("SZB?"); BSLUG_MODULE_NAME("RB3Enhanced"); +#ifndef RB3E_WII_BANK8 BSLUG_MODULE_VERSION(RB3E_BUILDTAG); +#else +BSLUG_MODULE_VERSION(RB3E_BUILDTAG "-BANK8"); +#endif BSLUG_MODULE_AUTHOR("github.com/RBEnhanced"); BSLUG_MODULE_LICENSE("GPLv2"); @@ -206,6 +210,14 @@ static void _startHook() // hook Heap::Init to change size and location of main heap HookFunction(PORT_HEAPINIT, HeapInit, HeapInitHook); + +#ifdef RB3E_WII_BANK8 + // RSO loader code gets real mad at you + POKE_32(PORT_BANK8_MEM2_RSO_ASSERT1, LIS(0, 0xa000)); + POKE_32(PORT_BANK8_MEM2_RSO_ASSERT2, LIS(0, 0xa000)); + POKE_32(PORT_BANK8_MEM2_RSO_ASSERT3, LIS(0, 0xa000)); + POKE_32(PORT_BANK8_MEM2_RSO_ASSERT4, LIS(0, 0xa000)); +#endif } #endif From 94a5d64688dc83c39de78a0b013ac2bb0cc0e16b Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sun, 4 May 2025 13:31:02 +0100 Subject: [PATCH 023/123] more bank8 addy ports --- include/ports_wii_bank8.h | 64 +++++++++++++++++++-------------------- source/rb3enhanced.c | 17 +++-------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index db37dec..abba151 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -11,20 +11,20 @@ #include // instruction patch addresses -#define PORT_SONGLIMIT 0x8026fbf0 // call to "max_song_count" DataNode::_value -#define PORT_SONGBLACKLIST 0x80273298 // call to BandSongMgr::IsInExclusionList -#define PORT_DATAINITFUNCS_TAIL 0x8045d080 // blr of DataInitFuncs -#define PORT_FASTSTART_CHECK 0x8000e464 // beq after OptionBool("fast",0) in App::_ct -#define PORT_STRAPSCREEN_1 0x8000e5a4 // branch to CustomSplash::Show in App::_ct -#define PORT_STRAPSCREEN_2 0x8000e5b4 // branch to CustomSplash::EndShow in App::_ct -#define PORT_NASWII_HOST 0x80b2df90 // branch to the add header function in the DWCDL login function -// #define PORT_CHARACTER_CLOTHES_CHECK 0x802607bc // FIXME check to see if the goal required to select a piece of clothing has been unlocked -// #define PORT_CHARACTER_CLOTHES_CHECK2 0x802607c0 // FIXME check to see if the goal required to select a piece of clothing has been unlocked 2 -// #define PORT_FACE_PAINT_CHECK 0x801fd9a8 // FIXME check to see if face paint is unlocked -// #define PORT_TATTOO_CHECK 0x801fd9c4 // FIXME check to see if tattoos are unlocked -// #define PORT_VIDEO_VENUE_CHECK 0x80227e34 // FIXME check to see if video venues are unlocked -#define PORT_OPTIONSTR_DEFINE 0x8044219c // bl to OptionStr("define", NULL) in PreInitSystem -// #define PORT_RUNLOOP_SPARE 0x8000f740 // FIXME branch to a function that only has a "blr" in App::Run(WithoutDebugging) +#define PORT_SONGLIMIT 0x8026fbf0 // call to "max_song_count" DataNode::_value +#define PORT_SONGBLACKLIST 0x80273298 // call to BandSongMgr::IsInExclusionList +#define PORT_DATAINITFUNCS_TAIL 0x8045d080 // blr of DataInitFuncs +#define PORT_FASTSTART_CHECK 0x8000e464 // beq after OptionBool("fast",0) in App::_ct +#define PORT_STRAPSCREEN_1 0x8000e5a4 // branch to CustomSplash::Show in App::_ct +#define PORT_STRAPSCREEN_2 0x8000e5b4 // branch to CustomSplash::EndShow in App::_ct +#define PORT_NASWII_HOST 0x80b2df90 // branch to the add header function in the DWCDL login function +#define PORT_CHARACTER_CLOTHES_CHECK 0x80348410 // (ProfileAssets::HasAsset) check to see if the goal required to select a piece of clothing has been unlocked +#define PORT_CHARACTER_CLOTHES_CHECK2 0x80348414 // check to see if the goal required to select a piece of clothing has been unlocked 2 +#define PORT_FACE_PAINT_CHECK 0x802b58b0 // check to see if face paint is unlocked (key_unlocked_face_paint BandProfile::HasCampaignKey) +#define PORT_TATTOO_CHECK 0x802b58cc // check to see if tattoos are unlocked (key_unlocked_tattoos BandProfile::HasCampaignKey) +#define PORT_VIDEO_VENUE_CHECK 0x802f0ce8 // check to see if video venues are unlocked (key_video_venues BandProfile::HasCampaignKey) +#define PORT_OPTIONSTR_DEFINE 0x8044219c // bl to OptionStr("define", NULL) in PreInitSystem +#define PORT_RUNLOOP_SPARE 0x80010788 // branch to a function that only has a "blr" in App::Run(WithoutDebugging) (branch to UIStats::Poll) // #define PORT_MICCHECK 0x8024a4e8 // NOT NEEDED? a bne that throws an error on the song select screen if the mic is not connected #define PORT_BIGSYMBOLFUNC_TAIL 0x804d3bac // blr after a function that initialises a bunch of symbols #define PORT_UPDATEPRESENCEBLOCK_B 0x8021cbd0 // branch after the failure case in a function that calls UpdatePresence @@ -36,11 +36,11 @@ #define PORT_NEWFILE 0x804204a0 // NewFile #define PORT_SETTRACKSPEED 0x805f9060 // TrackPanelDirBase::UpdateTrackSpeed #define PORT_SETSONGSPEED 0x8017fa80 // Game::SetMusicSpeed -#define PORT_MODIFIERMGR_CT 0x802f7130 // ModifierManager::__ct -#define PORT_MODIFIERMGR_ACTIVE 0x802f7990 // ModifierManager::ModifierActive +#define PORT_MODIFIERMGR_CT 0x802f7130 // ModifierMgr::__ct +#define PORT_MODIFIERMGR_ACTIVE 0x802f7ab0 // ModifierMgr::GetModifier #define PORT_SYMBOL_CT 0x804bd1c0 // Symbol::Symbol #define PORT_LOCALIZE 0x8049b7f0 // Locale::Localize -#define PORT_SETVENUE 0x802f12f0 // MetaPerformer::SetVenue(?) (actual func name is not known) +#define PORT_SETVENUE 0x802f12f0 // MetaPerformer::SetVenue #define PORT_EXECUTEDTA 0x803edbd0 // RockCentralGateway::ExecuteConfig #define PORT_BANDLABELSETDISPLAYTEXT 0x80518360 // BandLabel::SetDisplayText #define PORT_SETSONGANDARTISTNAME 0x80254650 // BandLabel::SetSongAndArtistName @@ -68,21 +68,21 @@ #define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back #define PORT_POSTPROC_DOPOST 0x809ef2d0 // WiiPostProc::DoPost #define PORT_MUSICLIBRARYSELECTMAYBE 0x802ff140 // UNSURE MusicLibrary::TryToSetHighlight, Selects an entry in the Music Library screen - actual name not known -// #define PORT_GETSYMBOLBYGAMEORIGIN 0x8027dd3c // FIXME SongSortByRecent::GetSymbolByGameOrigin -// #define PORT_GETGAMEORIGINBYSYMBOL 0x8027dc58 // FIXME SongSortByRecent::GetGameOriginBySymbol -#define PORT_SONGSORTBYRECENT 0x803747d0 // FIXME RecentCmp::__ct -#define PORT_FILESTREAM_CT 0x80494fd0 // FileStream::__ct (the one that takes a char * path instead of a File object) -#define PORT_CHUNKSTREAM_CT 0x804929a0 // ChunkStream::__ct -// #define PORT_GETBANDUSERFROMSLOT 0x8010021c // FIXME BandUserMgr::GetBandUserFromSlot -#define PORT_GETBANDUSERS 0x801683e0 // BandUserMgr::GetBandUsers -#define PORT_GETSONGSHORTNAME 0x802f5dd0 // MetaPerformer::GetSongSymbol -#define PORT_GETMETADATA 0x80271de0 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) -// #define PORT_GETSONGID 0x8051513c // FIXME GetSongID, function used when adding songs to BandSongMgr -#define PORT_SONGMGRGETRANKEDSONGS 0x802726a0 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function -#define PORT_GETSONGIDFROMSHORTNAME 0x801d0b44 // BandSongMgr::GetSongIDFromShortname -#define PORT_RNDPROPANIMSETFRAME 0x80928020 // RndPropAnim::SetFrame -#define PORT_DYNAMICCAST 0x80a38f54 // dynamic_cast -// #define PORT_OBJECTFINDUIPANEL 0x80101d74 // FIXME Object::Find +#define PORT_GETSYMBOLBYGAMEORIGIN 0x80374910 // RecentCmp::RecentTypeToOrigin FIXME SongSortByRecent::GetSymbolByGameOrigin +#define PORT_GETGAMEORIGINBYSYMBOL 0x80374870 // RecentCmp::OriginToRecentType FIXME SongSortByRecent::GetGameOriginBySymbol +#define PORT_SONGSORTBYRECENT 0x803747d0 // FIXME RecentCmp::__ct +#define PORT_FILESTREAM_CT 0x80494fd0 // FileStream::__ct (the one that takes a char * path instead of a File object) +#define PORT_CHUNKSTREAM_CT 0x804929a0 // ChunkStream::__ct +#define PORT_GETBANDUSERFROMSLOT 0x80168010 // BandUserMgr::GetUserFromSlot +#define PORT_GETBANDUSERS 0x801683e0 // BandUserMgr::GetBandUsers +#define PORT_GETSONGSHORTNAME 0x802f5dd0 // MetaPerformer::GetSongSymbol +#define PORT_GETMETADATA 0x80271de0 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) +#define PORT_GETSONGID 0x8075af40 // DataArray::GetSongID ???, function used when adding songs to BandSongMgr +#define PORT_SONGMGRGETRANKEDSONGS 0x802726a0 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function +#define PORT_GETSONGIDFROMSHORTNAME 0x801d0b44 // BandSongMgr::GetSongIDFromShortname +#define PORT_RNDPROPANIMSETFRAME 0x80928020 // RndPropAnim::SetFrame +#define PORT_DYNAMICCAST 0x80a38f54 // dynamic_cast +// #define PORT_OBJECTFINDUIPANEL 0x80101d74 // FIXME Object::Find - inlined on bank8? #define PORT_JOYPADGETPADDATA 0x8042f6f0 // JoypadGetPadData #define PORT_MEMALLOC 0x804a0a70 // MemAlloc #define PORT_MEMFREE 0x804a12c0 // MemFree diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index a260f69..6196240 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -209,13 +209,13 @@ void ApplyPatches() POKE_32(PORT_STRAPSCREEN_2, NOP); // Patch out erroneous second host header POKE_32(PORT_NASWII_HOST, NOP); + // always fire the UpdatePresence function. TODO(Emma): look into it, still not firing when screen is changed :/ + POKE_32(PORT_UPDATEPRESENCEBLOCK_B, NOP); #ifndef RB3E_WII_BANK8 // always take the branch to 0x8024a628 so vocals can be selected without a mic plugged in // bank 8 does not have the mic check POKE_32(PORT_MICCHECK, 0x42800140); #endif - // always fire the UpdatePresence function. TODO(Emma): look into it, still not firing when screen is changed :/ - POKE_32(PORT_UPDATEPRESENCEBLOCK_B, NOP); #elif RB3E_XBOX if (RB3E_IsEmulator()) POKE_32(PORT_SONGMGR_ISDEMO_CHECK, NOP); @@ -252,8 +252,6 @@ void ApplyConfigurablePatches() } #endif -#ifndef RB3E_WII_BANK8 - // TODO(Emma): port offsets to bank8 if (config.UnlockClothing == 1) { // Unlocks all clothing, tattoos, face paint, and video venues @@ -263,7 +261,6 @@ void ApplyConfigurablePatches() POKE_32(PORT_FACE_PAINT_CHECK, LI(3, 1)); POKE_32(PORT_VIDEO_VENUE_CHECK, LI(3, 1)); } -#endif #ifdef RB3EDEBUG if (config.QuazalLogging == 1) @@ -323,9 +320,10 @@ void InitialiseFunctions() POKE_B(&BandLabelSetDisplayText, PORT_BANDLABELSETDISPLAYTEXT); POKE_B(&SymbolConstruct, PORT_SYMBOL_CT); POKE_B(&ModifierActive, PORT_MODIFIERMGR_ACTIVE); + POKE_B(&GetBandUserFromSlot, PORT_GETBANDUSERFROMSLOT); #ifndef RB3E_WII_BANK8 POKE_B(&HmxFactoryFuncAt, PORT_HMXFACTORYFUNCAT); - POKE_B(&GetBandUserFromSlot, PORT_GETBANDUSERFROMSLOT); + // TODO(Emma): port to bank8 POKE_B(&ObjectFindUIPanel, PORT_OBJECTFINDUIPANEL); #else POKE_B(&DataRegisterFunc, PORT_DATAREGISTERFUNC); @@ -360,10 +358,7 @@ void ApplyHooks() POKE_B(PORT_ISSUPPORTEDLANGUAGE, &IsSupportedLanguageHook); POKE_B(PORT_BUILDINSTRUMENTSELECTION, &BuildInstrumentSelectionList); POKE_BL(PORT_OPTIONSTR_DEFINE, &DefinesHook); -#ifndef RB3E_WII_BANK8 - // TODO(Emma): find suitable alternative for bank8 POKE_BL(PORT_RUNLOOP_SPARE, &RB3E_RunLoop); -#endif HookFunction(PORT_LOCALIZE, &Localize, &LocalizeHook); HookFunction(PORT_WILLBENOSTRUM, &WillBeNoStrum, &WillBeNoStrumHook); HookFunction(PORT_ADDGAMEGEM, &AddGameGem, &AddGameGemHook); @@ -378,14 +373,10 @@ void ApplyHooks() HookFunction(PORT_GETSLOTCOLOR, &GetSlotColor, &GetSlotColorHook); HookFunction(PORT_SETSYSTEMLANGUAGE, &SetSystemLanguage, &SetSystemLanguageHook); HookFunction(PORT_DATAREADFILE, &DataReadFile, &DataReadFileHook); -#ifndef RB3E_WII_BANK8 - // TODO(Emma): port GetBandUserFromSlot to bank8 HookFunction(PORT_GAME_CT, &GameConstruct, &GameConstructHook); HookFunction(PORT_GAME_DT, &GameDestruct, &GameDestructHook); - // TODO(Emma): port offsets to bank 8 HookFunction(PORT_GETSYMBOLBYGAMEORIGIN, &GetSymbolByGameOrigin, &GetSymbolByGameOriginHook); HookFunction(PORT_GETGAMEORIGINBYSYMBOL, &GetGameOriginBySymbol, &GetGameOriginBySymbolHook); -#endif HookFunction(PORT_RNDPROPANIMSETFRAME, &PropAnimSetFrame, &PropAnimSetFrameHook); HookFunction(PORT_SYMBOLPREINIT, &SymbolPreInit, &SymbolPreInitHook); HookFunction(PORT_INITSONGMETADATA, &InitSongMetadata, &InitSongMetadataHook); From 701e5c0b10e64a75ff4e3c10896f751a4c5922e2 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sun, 4 May 2025 23:10:01 +0100 Subject: [PATCH 024/123] rebuild DataRegisterFunc, fix keyboard on 256mb, build bank8 in actions --- .github/workflows/build.yml | 2 +- include/ports_ps3.h | 2 +- include/ports_wii.h | 1 + include/ports_wii_bank8.h | 1 + include/rb3/Data.h | 7 +++---- source/DTAFunctions.c | 31 +++++++++++++------------------ source/_functions.c | 5 ++++- source/wii.c | 33 +++++++++++++++++++++++++++++++++ 8 files changed, 57 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dcf7df9..fdf2764 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,7 +49,7 @@ jobs: - name: Compile (bank8-debug) run: | make clean - make wii -j2 DEBUG=1 + make wii -j2 DEBUG=1 BANK8=1 - name: Upload binaries (bank8-debug) uses: actions/upload-artifact@v4 diff --git a/include/ports_ps3.h b/include/ports_ps3.h index 50d035f..a9f808b 100644 --- a/include/ports_ps3.h +++ b/include/ports_ps3.h @@ -49,7 +49,7 @@ #define PORT_DATANODEEVALUATE 0x008FBE24 // DataNode::Evaluate #define PORT_DATAARRAYFINDARRAY 0x008E95B4 // DataArray::FindArray #define PORT_DATAARRAYFINDDATA 0x008E9D14 // DataArray::FindData -#define PORT_ADDDTAFUNCTIONPS3 0x008F0AC0 // unknown name function, adds DTA function to gDataFuncs, PS3 only +#define PORT_DATAREGISTERFUNC 0x008F0AC0 // DataRegisterFunc #define PORT_SETADDRESS 0x000D12B4 // Quazal::InetAddress::SetAddress #define PORT_RANDOMINT 0x0096F5D4 // RandomInt(min, max) #define PORT_GETWIDGETBYNAME 0x009CC848 // GemManager::GetWidgetByName diff --git a/include/ports_wii.h b/include/ports_wii.h index 7af6c15..ff1dc17 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -99,6 +99,7 @@ #define PORT_DATASETELEM 0x8031e9a0 // DataSetElem #define PORT_DATAONELEM 0x8031dc40 // DataOnElem #define PORT_HEAPINIT 0x80352cbc // Heap::Init +#define PORT_DATAREGISTERFUNC 0x8031b2b8 // DataRegisterFunc // instance addresses #define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index abba151..3724bcb 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -132,6 +132,7 @@ #define PORT_BANK8_MEM2_RSO_ASSERT2 0x80442940 #define PORT_BANK8_MEM2_RSO_ASSERT3 0x804b42a8 #define PORT_BANK8_MEM2_RSO_ASSERT4 0x804b4198 +#define PORT_BANK8_KEYBOARD_RESOLVED 0x80448750 // ResolvedModule? // define logging functions #define RB3E_PRINT printf diff --git a/include/rb3/Data.h b/include/rb3/Data.h index adee3d0..80c8f00 100644 --- a/include/rb3/Data.h +++ b/include/rb3/Data.h @@ -76,9 +76,8 @@ extern DataNode *DataOnElem(DataNode *ret, DataArray *array); extern void *DataNodeGetObj(DataNode *node); -#ifdef RB3E_WII_BANK8 -// used on bank 8 -extern void DataRegisterFunc(Symbol name, void *func); -#endif +// inlined on 360 +typedef DataNode *(*DTAFunction_t)(DataNode *node, DataArray *args); +extern void DataRegisterFunc(Symbol name, DTAFunction_t func); #endif // _DATA_H diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index 7aef4be..fc83776 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -37,13 +37,13 @@ DataNode *PrintToDebugger(DataNode *node, DataArray *args) } // Get configuration values -DataNode *GetMusicSpeed(DataNode *node, int *args) +DataNode *GetMusicSpeed(DataNode *node, DataArray *args) { node->type = FLOAT_VALUE; node->value.floatVal = config.SongSpeedMultiplier; return node; } -DataNode *GetTrackSpeed(DataNode *node, int *args) +DataNode *GetTrackSpeed(DataNode *node, DataArray *args) { node->type = FLOAT_VALUE; node->value.floatVal = config.TrackSpeedMultiplier; @@ -118,21 +118,21 @@ DataNode *DTASetVenue(DataNode *node, DataArray *args) return node; } -DataNode *DTAIsEmulator(DataNode *node, int *args) +DataNode *DTAIsEmulator(DataNode *node, DataArray *args) { node->type = INT_VALUE; node->value.intVal = RB3E_IsEmulator(); return node; } -DataNode *DTARelaunchGame(DataNode *node, int *args) +DataNode *DTARelaunchGame(DataNode *node, DataArray *args) { node->type = INT_VALUE; node->value.intVal = RB3E_RelaunchGame(); return node; } -DataNode *DTAGetSongCount(DataNode *node, int *args) +DataNode *DTAGetSongCount(DataNode *node, DataArray *args) { node->type = INT_VALUE; node->value.intVal = RB3E_LoadedSongCount; @@ -168,20 +168,16 @@ DataNode *DTASendModData(DataNode *node, DataArray *args) return node; } +#ifdef RB3E_XBOX +// this function is inlined on the Xbox version, so we re-create it +void DataRegisterFunc(Symbol name, DTAFunction_t func) +{ + *(DTAFunction_t *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &name) = &func; +} +#endif + void AddDTAFunctions() { -#ifndef RB3E_WII_BANK8 - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.print_debug) = (int)PrintToDebugger; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_change_music_speed) = (int)ChangeMusicSpeed; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_change_track_speed) = (int)ChangeTrackSpeed; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_get_music_speed) = (int)GetMusicSpeed; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_get_track_speed) = (int)GetTrackSpeed; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_set_venue) = (int)DTASetVenue; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_is_emulator) = (int)DTAIsEmulator; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_relaunch_game) = (int)DTARelaunchGame; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_get_song_count) = (int)DTAGetSongCount; - *(int *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &globalSymbols.rb3e_send_event_string) = (int)DTASendModData; -#else DataRegisterFunc(globalSymbols.print_debug, PrintToDebugger); DataRegisterFunc(globalSymbols.rb3e_change_music_speed, ChangeMusicSpeed); DataRegisterFunc(globalSymbols.rb3e_change_track_speed, ChangeTrackSpeed); @@ -192,6 +188,5 @@ void AddDTAFunctions() DataRegisterFunc(globalSymbols.rb3e_relaunch_game, DTARelaunchGame); DataRegisterFunc(globalSymbols.rb3e_get_song_count, DTAGetSongCount); DataRegisterFunc(globalSymbols.rb3e_send_event_string, DTASendModData); -#endif RB3E_MSG("Added DTA functions!", NULL); } diff --git a/source/_functions.c b/source/_functions.c index 93e56e2..a5db8c4 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -59,6 +59,9 @@ RB3E_STUB(MemPrint) RB3E_STUB(MemNumHeaps) RB3E_STUB(MemAlloc) RB3E_STUB(MemFree) +#ifndef RB3E_XBOX +RB3E_STUB(DataRegisterFunc) // DataRegisterFunc is inlined on 360 +#endif // hooked function stubs RB3E_STUB(Localize) RB3E_STUB(SetVenue) @@ -102,4 +105,4 @@ RB3E_STUB(DataSetElem) RB3E_STUB(DataOnElem) RB3E_STUB(DataNodeGetObj) RB3E_STUB(HeapInit) -RB3E_STUB(DataRegisterFunc) +RB3E_STUB(ResolvedModuleKeyboard) diff --git a/source/wii.c b/source/wii.c index 4baf41b..0c040b4 100644 --- a/source/wii.c +++ b/source/wii.c @@ -121,6 +121,37 @@ void HeapInitHook(Heap *heap, const char *name, int num, int *mem, int sizeWords HeapInit(heap, name, num, mem, sizeWords, isHandle, strategy, debug_level, allow_temp); } +#ifdef RB3E_WII_BANK8 +void ResolvedModuleKeyboard(void *keyboardRso); +void ResolvedModuleKeyboardHook(void *keyboardRso) +{ + // The keyboard RSO in bank 8 has asserts that detect if the address is outside of 128MB worth of MEM2 + // so we try to patch out the asserts that crash as well as all the address checks. + // Does this suck? Yes. Does it work? Debatable. + uint32_t *rsoAsUint32 = (uint32_t *)keyboardRso; + for (int i = 0; i < (0x100000 / 4); i++) + { + // check for (address & 0xF8000000) == 0x90000000 + if ((rsoAsUint32[i] & 0xF00FFFFF) == 0x50030008 && // rlwinm rD, rA, 0, 0, 0x4 + (rsoAsUint32[i + 1] & 0xFF00FFFF) == 0x3c007000 && // addis r0, rA, 0x7000 + rsoAsUint32[i + 2] == 0x28000000) // cmplwi r0, 0 + { + RB3E_DEBUG("Patching keyboard address check at %p", &rsoAsUint32[i]); + rsoAsUint32[i + 1] = LI(0, 0); // make the check always 0 (always succeed) + } + // check for assert function start + if (rsoAsUint32[i] == 0x90010070 && + rsoAsUint32[i + 1] == 0x41820158 && + rsoAsUint32[i + 2] == 0x7f83e378) + { + RB3E_DEBUG("Patching assert function at %p", &rsoAsUint32[i]); + rsoAsUint32[i] = BLR; + } + } + ResolvedModuleKeyboard(keyboardRso); +} +#endif + static void CTHook(void *ThisApp, int argc, char **argv) { if (_has256MBMem2) @@ -217,6 +248,8 @@ static void _startHook() POKE_32(PORT_BANK8_MEM2_RSO_ASSERT2, LIS(0, 0xa000)); POKE_32(PORT_BANK8_MEM2_RSO_ASSERT3, LIS(0, 0xa000)); POKE_32(PORT_BANK8_MEM2_RSO_ASSERT4, LIS(0, 0xa000)); + // hook the keyboard RSO function to accept pointers past 0x98000000 + HookFunction(PORT_BANK8_KEYBOARD_RESOLVED, ResolvedModuleKeyboard, ResolvedModuleKeyboardHook); #endif } #endif From 5734ccb91f233010eea3e2dea8d27c7e70918fe4 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 6 May 2025 20:23:25 +0100 Subject: [PATCH 025/123] 480p fix, deflicker fix (disabled by default, needs safety research) --- include/ports_wii.h | 4 ++++ include/ports_wii_bank8.h | 4 ++++ source/wii.c | 14 ++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/include/ports_wii.h b/include/ports_wii.h index ff1dc17..87555fd 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -125,6 +125,10 @@ #define PORT_CONFIGMEM2_56MB_INST 0x80769420 // ConfigMEM2_56MB 0x90000000 length param #define PORT_CONFIGMEM2_64MB_INST 0x80769500 // ConfigMEM2_64MB 0x90000000 length param #define PORT_ENABLEINSTSONMEM2LO16MB_INST 0x80769740 // EnableInstsOnMEM2Lo16MB 0x90000000 length param +#define PORT_VISETMODE_LI_28 0x8077b2ac // "li r28, 0x1" in VISetRevolutionModeSimple +#define PORT_VISETMODE_STB_28 0x8077b2b4 // "stb r28, 0x21(r1)" in VISetRevolutionModeSimple +#define PORT_VISETMODE_PATCH_CODE 0x800027a0 // stub area to insert the 480p fix code for VISetRevolutionModeSimple +#define PORT_GXSETCOPYFILTER_BEQ 0x807464b0 // "beq 0x40" in GXSetCopyFilter // define logging functions #define RB3E_PRINT printf diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index 3724bcb..abdb82c 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -127,6 +127,10 @@ #define PORT_CONFIGMEM2_56MB_INST 0x80aac5c0 // ConfigMEM2_56MB 0x90000000 length param #define PORT_CONFIGMEM2_64MB_INST 0x80aac6a0 // ConfigMEM2_64MB 0x90000000 length param #define PORT_ENABLEINSTSONMEM2LO16MB_INST 0x80aac8e0 // EnableInstsOnMEM2Lo16MB 0x90000000 length param +#define PORT_VISETMODE_LI_28 0x80abee6c // "li r28, 0x1" in VISetRevolutionModeSimple +#define PORT_VISETMODE_STB_28 0x80abee74 // "stb r28, 0x21(r1)" in VISetRevolutionModeSimple +#define PORT_VISETMODE_PATCH_CODE 0x800027a0 // stub area to insert the 480p fix code for VISetRevolutionModeSimple +#define PORT_GXSETCOPYFILTER_BEQ 0x80a89650 // "beq 0x40" in GXSetCopyFilter // bank8 specific stuff #define PORT_BANK8_MEM2_RSO_ASSERT1 0x804428e8 #define PORT_BANK8_MEM2_RSO_ASSERT2 0x80442940 diff --git a/source/wii.c b/source/wii.c index 0c040b4..aa4e770 100644 --- a/source/wii.c +++ b/source/wii.c @@ -196,6 +196,20 @@ static void _startHook() { POKE_B(&SymbolConstruct, PORT_SYMBOL_CT); POKE_B(PORT_BIGSYMBOLFUNC_TAIL, InitGlobalSymbols); + + // Extrems's 480p fix + POKE_B(PORT_VISETMODE_LI_28, PORT_VISETMODE_PATCH_CODE); + POKE_32(PORT_VISETMODE_PATCH_CODE + 0, LI(3, 3)); + POKE_32(PORT_VISETMODE_PATCH_CODE + 4, LI(28, 1)); + POKE_B(PORT_VISETMODE_PATCH_CODE + 8, PORT_VISETMODE_LI_28 + 4); + POKE_32(PORT_VISETMODE_STB_28, 0x98610021); // replace stb r28,0x21(r1) with stb r3,0x21(r1) + +#if 0 + // TODO(Emma): can we somehow make this an optional toggle in the launcher? + // Remove the deflicker filter + POKE_32(PORT_GXSETCOPYFILTER_BEQ, 0x48000040); // replace beq with b +#endif + #ifdef RB3EDEBUG // for now we limit the ability to use more than intentional RAM to debug builds // just in case shit hits the fan From 2ff6291fe6a16ca4c6d93a4ae6958643892f1e51 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 6 May 2025 21:48:47 +0100 Subject: [PATCH 026/123] use correct packet size for PCP (fix #32) --- source/net_natpmp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/net_natpmp.c b/source/net_natpmp.c index d277747..cf74fba 100644 --- a/source/net_natpmp.c +++ b/source/net_natpmp.c @@ -91,7 +91,7 @@ void PCP_RequestOpenPort(unsigned short port) memcpy(req.nonce, mapping_nonce, sizeof(mapping_nonce)); fill_ipv4_mapped_ipv6(&req.suggested_address, 0); // fire it off to the gateway - RB3E_UDP_SendTo(natpmp_socket, gateway_ipv4, PCP_COMMAND_PORT, &req, sizeof(NATPMP_MappingRequest)); + RB3E_UDP_SendTo(natpmp_socket, gateway_ipv4, PCP_COMMAND_PORT, &req, sizeof(PCP_MAPRequest)); RB3E_DEBUG("Requested port %i from PCP", port); } From 7d608c27a9bbc25896c95ea40f7233e3df9fbc16 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 6 May 2025 23:25:52 +0100 Subject: [PATCH 027/123] fix 360 DTA functions --- source/DTAFunctions.c | 2 +- source/net_http_server.c | 47 ++++++++++++++++++++++------------------ source/xbox360_net.c | 2 +- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index fc83776..6c10a10 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -172,7 +172,7 @@ DataNode *DTASendModData(DataNode *node, DataArray *args) // this function is inlined on the Xbox version, so we re-create it void DataRegisterFunc(Symbol name, DTAFunction_t func) { - *(DTAFunction_t *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &name) = &func; + *(DTAFunction_t *)HmxFactoryFuncAt((int *)PORT_GDATAFUNCS, &name) = func; } #endif diff --git a/source/net_http_server.c b/source/net_http_server.c index 1752683..1245e9f 100644 --- a/source/net_http_server.c +++ b/source/net_http_server.c @@ -12,6 +12,7 @@ #include "quazal/InetAddress.h" #include "rb3/SongMetadata.h" #include "rb3/BandSongMgr.h" +#include "rb3/RockCentralGateway.h" #include "rb3enhanced.h" #include "version.h" #include "ports.h" @@ -65,27 +66,31 @@ typedef enum _HTTP_Request_Status void urldecode2(char *dst, const char *src) { char a, b; - while (*src) { - if ((*src == '%') && - ((a = src[1]) && (b = src[2])) && - (isxdigit(a) && isxdigit(b))) { - if (a >= 'a') - a -= 'a'-'A'; - if (a >= 'A') - a -= ('A' - 10); - else - a -= '0'; - if (b >= 'a') - b -= 'a'-'A'; - if (b >= 'A') - b -= ('A' - 10); - else - b -= '0'; - *dst++ = 16*a+b; - src+=3; - } else { - *dst++ = *src++; - } + while (*src) + { + if ((*src == '%') && + ((a = src[1]) && (b = src[2])) && + (isxdigit(a) && isxdigit(b))) + { + if (a >= 'a') + a -= 'a' - 'A'; + if (a >= 'A') + a -= ('A' - 10); + else + a -= '0'; + if (b >= 'a') + b -= 'a' - 'A'; + if (b >= 'A') + b -= ('A' - 10); + else + b -= '0'; + *dst++ = 16 * a + b; + src += 3; + } + else + { + *dst++ = *src++; + } } *dst++ = '\0'; } diff --git a/source/xbox360_net.c b/source/xbox360_net.c index 05ad229..299b61d 100644 --- a/source/xbox360_net.c +++ b/source/xbox360_net.c @@ -67,7 +67,7 @@ unsigned int RB3E_GetGatewayIP() XnpRouteEntry routes[2] = {0}; int size = sizeof(routes); // XNET_OPTID_ROUTE_ENTRY - int ret = XNetGetOpt(0x1392, routes, &size); + int ret = XNetGetOpt(0x1392, (BYTE *)routes, &size); if (ret == 0) { return routes[1].interface_addr; From 0745b660bd766559b293201c01906dc7c32718f5 Mon Sep 17 00:00:00 2001 From: ihatecompvir Date: Thu, 8 May 2025 20:31:43 -0700 Subject: [PATCH 028/123] Xbox 360 socket timeout typo fix Don't really think this was really causing any specific problems, but fixing anyway --- source/xbox360_net.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/xbox360_net.c b/source/xbox360_net.c index 299b61d..0614087 100644 --- a/source/xbox360_net.c +++ b/source/xbox360_net.c @@ -106,7 +106,7 @@ int RB3E_SetSendTimeout(int socket, int timeout_ms) int RB3E_SetTimeout(int socket, int timeout_ms) { int r = RB3E_SetRecvTimeout(socket, timeout_ms); - r = RB3E_SetRecvTimeout(socket, timeout_ms); + r = RB3E_SetSendTimeout(socket, timeout_ms); return r; } From 13874e8d6e440544f6b0970201f7ff1aa1f62163 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 10 May 2025 00:32:51 +0100 Subject: [PATCH 029/123] [bank8] fix GetSongIDFromShortName to stop crash initialising TheGame --- include/ports_wii_bank8.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index abdb82c..33ffc1f 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -79,7 +79,7 @@ #define PORT_GETMETADATA 0x80271de0 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) #define PORT_GETSONGID 0x8075af40 // DataArray::GetSongID ???, function used when adding songs to BandSongMgr #define PORT_SONGMGRGETRANKEDSONGS 0x802726a0 // BandSongMgr::GetRankedSongs(?) - not sure on the real name of the function -#define PORT_GETSONGIDFROMSHORTNAME 0x801d0b44 // BandSongMgr::GetSongIDFromShortname +#define PORT_GETSONGIDFROMSHORTNAME 0x80271ad0 // BandSongMgr::GetSongIDFromShortname #define PORT_RNDPROPANIMSETFRAME 0x80928020 // RndPropAnim::SetFrame #define PORT_DYNAMICCAST 0x80a38f54 // dynamic_cast // #define PORT_OBJECTFINDUIPANEL 0x80101d74 // FIXME Object::Find - inlined on bank8? From e2fb3f640517e7dfba7c9cccf8ad2b03c8f6c79c Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 10 May 2025 00:49:23 +0100 Subject: [PATCH 030/123] [bank8] use gSongMgr instead of TheSongMgr address --- include/ports_wii_bank8.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index 33ffc1f..6dec2c7 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -112,7 +112,7 @@ #define PORT_THESONGDB 0x80c8f048 // address of TheSongDB - TODO: check #define PORT_THEMUSICLIBRARY 0x80c904b8 // pointer to TheMusicLibrary #define PORT_THESONGSORTMGR 0x80c91248 // pointer to TheSongSortMgr -#define PORT_THESONGMGR 0x80c8f780 // address of TheSongMgr +#define PORT_THESONGMGR 0x80c8f61c // address of gSongMgr #define PORT_THEMETAPERFORMER 0x80c90470 // address of TheMetaPerformer / MetaPerformer::sMetaPerformer #define PORT_THEBANDUSERMGR 0x80c8e9b8 // pointer to TheBandUserMgr #define PORT_THEGAME 0x80c8eb18 // pointer to TheGame (you lost) From 440bb7bf96d06c6c0f390bb36c55c25b471751c2 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 10 May 2025 08:23:43 +0100 Subject: [PATCH 031/123] [wii] graphical exception handler for diagnosing crashes --- include/ports_wii.h | 10 + include/ports_wii_bank8.h | 24 +- source/_functions.c | 14 + source/rb3enhanced.c | 4 + source/wii.c | 4 + source/wii_exceptions.c | 625 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 674 insertions(+), 7 deletions(-) create mode 100644 source/wii_exceptions.c diff --git a/include/ports_wii.h b/include/ports_wii.h index 87555fd..dfe6b0e 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -129,6 +129,16 @@ #define PORT_VISETMODE_STB_28 0x8077b2b4 // "stb r28, 0x21(r1)" in VISetRevolutionModeSimple #define PORT_VISETMODE_PATCH_CODE 0x800027a0 // stub area to insert the 480p fix code for VISetRevolutionModeSimple #define PORT_GXSETCOPYFILTER_BEQ 0x807464b0 // "beq 0x40" in GXSetCopyFilter +#define PORT_OSFATAL_HALT_X_OFFSET 0x807673fc // 'li r7, 48' for the X offset in Halt (OSFatal) +#define PORT_OSFATAL_HALT_Y_OFFSET 0x80767404 // 'li r8, 100' for the Y offset in Halt (OSFatal) +#define PORT_SCREENREPORT_X_NEWLINE 0x80766894 // 'subi rX, rX, 48' for the X newline cutoff in ScreenReport +#define PORT_OSFATAL_HALT_OSREPORT 0x80767460 // call to OSReport in Halt (OSFatal) +#define PORT_OSFATAL_HALT_PPCHALT 0x80767464 // call to PPCHalt in Halt (OSFatal) +#define PORT_OSFATAL 0x80766cb0 // OSFatal +#define PORT_OSSETERRORHANDLER 0x80764b80 // OSSetErrorHandler +#define PORT_PPCHALT 0x80706390 // PPCHalt +#define PORT_OSRETURNTOMENU 0x8076a610 // OSReturnToMenu +#define PORT_OSREADROM 0x8076ae20 // OSReadROM // define logging functions #define RB3E_PRINT printf diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index 6dec2c7..5d13a0c 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -63,14 +63,14 @@ #define PORT_GAME_DT 0x8017dbd0 // Game::__dt #define PORT_GAMEGETACTIVEPLAYER 0x8017fe40 // Game::GetActivePlayer #define PORT_WIINETINIT_DNSLOOKUP 0x8043f1c0 // WiiNetInit::StartDNSLookup -#define PORT_BUILDINSTRUMENTSELECTION 0x802478a8 // BuildInstrumentSelectionList(?) - actual name not known -#define PORT_PREPARESOMEVECTORMAYBE 0x80247c58 // Prepares some vector, used by BuildInstrumentSelectionList -#define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back +// #define PORT_BUILDINSTRUMENTSELECTION 0x802478a8 // FIXME BuildInstrumentSelectionList(?) - actual name not known +// #define PORT_PREPARESOMEVECTORMAYBE 0x80247c58 // FIXME Prepares some vector, used by BuildInstrumentSelectionList +// #define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // FIXME vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back #define PORT_POSTPROC_DOPOST 0x809ef2d0 // WiiPostProc::DoPost #define PORT_MUSICLIBRARYSELECTMAYBE 0x802ff140 // UNSURE MusicLibrary::TryToSetHighlight, Selects an entry in the Music Library screen - actual name not known -#define PORT_GETSYMBOLBYGAMEORIGIN 0x80374910 // RecentCmp::RecentTypeToOrigin FIXME SongSortByRecent::GetSymbolByGameOrigin -#define PORT_GETGAMEORIGINBYSYMBOL 0x80374870 // RecentCmp::OriginToRecentType FIXME SongSortByRecent::GetGameOriginBySymbol -#define PORT_SONGSORTBYRECENT 0x803747d0 // FIXME RecentCmp::__ct +#define PORT_GETSYMBOLBYGAMEORIGIN 0x80374910 // RecentCmp::RecentTypeToOrigin +#define PORT_GETGAMEORIGINBYSYMBOL 0x80374870 // RecentCmp::OriginToRecentType +#define PORT_SONGSORTBYRECENT 0x803747d0 // RecentCmp::__ct #define PORT_FILESTREAM_CT 0x80494fd0 // FileStream::__ct (the one that takes a char * path instead of a File object) #define PORT_CHUNKSTREAM_CT 0x804929a0 // ChunkStream::__ct #define PORT_GETBANDUSERFROMSLOT 0x80168010 // BandUserMgr::GetUserFromSlot @@ -108,7 +108,7 @@ #define PORT_GDATAFUNCS 0x80cab890 // address of gDataFuncs #define PORT_THEARCHIVE 0x80c92dd0 // address of TheArchive (main ARK) #define PORT_THEBANDUI 0x80c8f800 // address of TheBandUI -#define PORT_NULLSYMBOL 0x80bb5d30 // FIXME address of gNullSymbol +#define PORT_NULLSYMBOL 0x80bb5d30 // address of gNullSymbol #define PORT_THESONGDB 0x80c8f048 // address of TheSongDB - TODO: check #define PORT_THEMUSICLIBRARY 0x80c904b8 // pointer to TheMusicLibrary #define PORT_THESONGSORTMGR 0x80c91248 // pointer to TheSongSortMgr @@ -131,6 +131,16 @@ #define PORT_VISETMODE_STB_28 0x80abee74 // "stb r28, 0x21(r1)" in VISetRevolutionModeSimple #define PORT_VISETMODE_PATCH_CODE 0x800027a0 // stub area to insert the 480p fix code for VISetRevolutionModeSimple #define PORT_GXSETCOPYFILTER_BEQ 0x80a89650 // "beq 0x40" in GXSetCopyFilter +#define PORT_OSFATAL_HALT_X_OFFSET 0x80aaa59c // 'li r7, 48' for the X offset in Halt (OSFatal) +#define PORT_OSFATAL_HALT_Y_OFFSET 0x80aaa59c // 'li r8, 100' for the Y offset in Halt (OSFatal) +#define PORT_SCREENREPORT_X_NEWLINE 0x80aaa5a4 // 'subi rX, rX, 48' for the X newline cutoff in ScreenReport +#define PORT_OSFATAL_HALT_OSREPORT 0x80aaa600 // call to OSReport in Halt (OSFatal) +#define PORT_OSFATAL_HALT_PPCHALT 0x80aaa604 // call to PPCHalt in Halt (OSFatal) +#define PORT_OSFATAL 0x80aa9e50 // OSFatal +#define PORT_OSSETERRORHANDLER 0x80aa7d20 // OSSetErrorHandler +#define PORT_PPCHALT 0x80a49600 // PPCHalt +#define PORT_OSRETURNTOMENU 0x80aad970 // OSReturnToMenu +#define PORT_OSREADROM 0x80aae180 // OSReadROM // bank8 specific stuff #define PORT_BANK8_MEM2_RSO_ASSERT1 0x804428e8 #define PORT_BANK8_MEM2_RSO_ASSERT2 0x80442940 diff --git a/source/_functions.c b/source/_functions.c index a5db8c4..17f1697 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -25,6 +25,11 @@ } #endif +#ifndef RB3E_XBOX +RB3E_STUB(RB3EBase); // this will always be the start of .text +// Xbox is loaded at a fixed address unlike Wii/PS3/PC +#endif + // function stub definitions #ifndef RB3E_WII RB3E_STUB(AppConstructor) // AppConstructor is handled by the BrainSlug engine @@ -106,3 +111,12 @@ RB3E_STUB(DataOnElem) RB3E_STUB(DataNodeGetObj) RB3E_STUB(HeapInit) RB3E_STUB(ResolvedModuleKeyboard) + +#ifdef RB3E_WII +// Wii-specific functions +// FUTURE(Emma): these really ought to be provided by the BrainSlug loader +RB3E_STUB(OSFatal) +RB3E_STUB(OSSetErrorHandler) +RB3E_STUB(PPCHalt) +RB3E_STUB(OSReturnToMenu) +#endif diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 6196240..2ce444c 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -314,8 +314,10 @@ void InitialiseFunctions() POKE_B(&DataFindData, PORT_DATAARRAYFINDDATA); #endif POKE_B(&SongMgrGetRankedSongs, PORT_SONGMGRGETRANKEDSONGS); +#ifndef RB3E_WII_BANK8 POKE_B(&PrepareSomeVectorMaybe, PORT_PREPARESOMEVECTORMAYBE); POKE_B(&SomeVectorPushBackMaybe, PORT_SOMEVECTORPUSHBACKMAYBE); +#endif POKE_B(&ExecuteDTA, PORT_EXECUTEDTA); POKE_B(&BandLabelSetDisplayText, PORT_BANDLABELSETDISPLAYTEXT); POKE_B(&SymbolConstruct, PORT_SYMBOL_CT); @@ -356,7 +358,9 @@ void ApplyHooks() { POKE_B(PORT_DATAINITFUNCS_TAIL, &AddDTAFunctions); POKE_B(PORT_ISSUPPORTEDLANGUAGE, &IsSupportedLanguageHook); +#ifndef RB3E_WII_BANK8 POKE_B(PORT_BUILDINSTRUMENTSELECTION, &BuildInstrumentSelectionList); +#endif POKE_BL(PORT_OPTIONSTR_DEFINE, &DefinesHook); POKE_BL(PORT_RUNLOOP_SPARE, &RB3E_RunLoop); HookFunction(PORT_LOCALIZE, &Localize, &LocalizeHook); diff --git a/source/wii.c b/source/wii.c index aa4e770..78a88b3 100644 --- a/source/wii.c +++ b/source/wii.c @@ -152,8 +152,12 @@ void ResolvedModuleKeyboardHook(void *keyboardRso) } #endif +extern void RB3E_InstallWiiExceptionHandler(); // wii_exceptions.c static void CTHook(void *ThisApp, int argc, char **argv) { + // install an exception handler to show a graphical error when things go wrong + RB3E_InstallWiiExceptionHandler(); + // print out if we're using a 256MB MEM2 if (_has256MBMem2) { RB3E_MSG("Running with 256MB MEM2!", NULL); diff --git a/source/wii_exceptions.c b/source/wii_exceptions.c new file mode 100644 index 0000000..9e6fd68 --- /dev/null +++ b/source/wii_exceptions.c @@ -0,0 +1,625 @@ +/* + RB3Enhanced - wii_exceptions.c + Low-level exception handling for RB3Enhanced on Wii. +*/ +#ifdef RB3E_WII + +#include +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "ppcasm.h" +#include "ports.h" + +// Font for OSFatal as used by Dolphin Emulator +static unsigned char dolphin_osfatal_font[] = { + 0x59, 0x61, 0x79, 0x30, 0x00, 0x01, 0x01, 0x10, 0x00, 0x00, 0x01, 0xF3, 0x00, 0x00, 0x0D, 0x71, + 0xFD, 0x77, 0x7E, 0xFB, 0xFF, 0xFF, 0xDB, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xBF, 0xFF, 0xFD, 0xF7, + 0xFF, 0x4F, 0xFF, 0xFF, 0xFA, 0xBE, 0xC7, 0x53, 0xE4, 0x9F, 0x73, 0xEE, 0xF7, 0xDF, 0xA8, 0x7D, + 0x4C, 0x23, 0xF4, 0xFB, 0xE7, 0x79, 0xFA, 0x4F, 0x7F, 0xFF, 0xFE, 0x9F, 0xFE, 0x8B, 0xFF, 0xBF, + 0xE7, 0xFF, 0xF3, 0xF0, 0xFB, 0xA7, 0xFC, 0x89, 0xDC, 0xFF, 0x9E, 0xF1, 0x2A, 0xE3, 0xE5, 0xFF, + 0xFC, 0xEE, 0x7C, 0x7C, 0x97, 0xEF, 0xBE, 0x5F, 0x5C, 0x51, 0x4F, 0x8F, 0xAE, 0x07, 0xA7, 0x3F, + 0x3E, 0x30, 0xBF, 0x77, 0xE9, 0xE3, 0xDF, 0x24, 0xC8, 0x73, 0xF0, 0xBF, 0x22, 0x4D, 0xD8, 0x1F, + 0xA0, 0xFF, 0xFC, 0x68, 0x8F, 0x0F, 0x3F, 0xEF, 0xFE, 0xF7, 0xF6, 0x91, 0xD0, 0xA3, 0xA0, 0x1D, + 0xC4, 0x1D, 0x60, 0x8F, 0x08, 0x6E, 0x9A, 0x4F, 0x3F, 0x5A, 0x07, 0xA1, 0x81, 0xF7, 0xA0, 0x8E, + 0x07, 0x8F, 0x09, 0xF7, 0xEF, 0x8C, 0x23, 0xDC, 0x29, 0xFF, 0xE8, 0x7F, 0xFF, 0xFF, 0xA3, 0xFF, + 0xBE, 0x8D, 0x0F, 0x07, 0x07, 0xD0, 0x17, 0xFF, 0x8F, 0xFF, 0xFB, 0xED, 0xFF, 0xFE, 0x6F, 0x85, + 0x8B, 0xF3, 0xFE, 0x27, 0x28, 0x87, 0x95, 0xBC, 0x74, 0x34, 0x23, 0x12, 0x34, 0x02, 0x4A, 0x0E, + 0x01, 0xEF, 0xE1, 0xF5, 0x5F, 0x1F, 0xE3, 0xFF, 0xE3, 0xDF, 0xFF, 0xC5, 0x0B, 0xFF, 0x95, 0xFC, + 0x1F, 0x90, 0x6B, 0xF3, 0xBF, 0x7B, 0xF3, 0xD7, 0xEF, 0x90, 0x7D, 0xE0, 0x13, 0x00, 0xC3, 0x07, + 0x39, 0xE0, 0x75, 0x29, 0x7C, 0xF0, 0xDF, 0xCF, 0xFF, 0xFF, 0xFF, 0xEF, 0xF7, 0x97, 0x1D, 0xFD, + 0xBF, 0x87, 0xF9, 0xF4, 0xFF, 0xFC, 0x7A, 0x70, 0x5C, 0x9E, 0x7D, 0x9F, 0x67, 0xD7, 0xFF, 0xE7, + 0x00, 0xFC, 0x4E, 0x7F, 0xBC, 0x91, 0xEF, 0xBE, 0xF4, 0xF7, 0x8F, 0x1D, 0xDE, 0x67, 0xD7, 0xDF, + 0x7C, 0xDF, 0xFD, 0xF9, 0xD5, 0xFF, 0xE7, 0xAE, 0xA0, 0xA4, 0xAE, 0xEF, 0x58, 0x49, 0x3F, 0xBD, + 0xF9, 0x67, 0xFB, 0xFF, 0x71, 0xEF, 0xBE, 0xF4, 0xDF, 0x79, 0x83, 0xDD, 0xFB, 0xE3, 0xBE, 0x81, + 0xEA, 0x05, 0x79, 0xDF, 0x78, 0xF5, 0xF2, 0xE7, 0x6D, 0xBE, 0x29, 0xD3, 0x9B, 0xCF, 0xFF, 0xF8, + 0x5F, 0xFF, 0x0F, 0xFF, 0xCF, 0xF7, 0xFE, 0xFE, 0xFD, 0xFF, 0xD7, 0x0B, 0xFF, 0xFF, 0xFF, 0xFB, + 0xF9, 0xEE, 0xFF, 0xCC, 0x04, 0xE8, 0x03, 0x9F, 0xBB, 0xD5, 0x98, 0xE8, 0xEB, 0xBE, 0xF9, 0xC8, + 0xFB, 0x58, 0x1F, 0x2A, 0x83, 0xE0, 0xF7, 0xBE, 0xF7, 0x10, 0xFF, 0x84, 0x9F, 0xBF, 0xF8, 0xFE, + 0xDF, 0xFE, 0x7F, 0xF7, 0x27, 0xD7, 0xEF, 0x2F, 0xEF, 0x2E, 0x05, 0xFF, 0x1C, 0x9F, 0x90, 0xAB, + 0x93, 0x9C, 0xF2, 0x4F, 0xA4, 0xBF, 0x7D, 0x62, 0x72, 0x3C, 0xE4, 0xE4, 0xDF, 0x38, 0xD1, 0xC2, + 0x60, 0x24, 0x43, 0x4A, 0x00, 0x3E, 0x80, 0x43, 0xC0, 0x01, 0xE9, 0x1F, 0xFF, 0xDF, 0x17, 0x50, + 0x7F, 0xFF, 0x23, 0x7F, 0xFF, 0x80, 0xF0, 0xCF, 0x47, 0x38, 0x9F, 0xCD, 0x0B, 0xE4, 0xDE, 0x4F, + 0x8E, 0x7C, 0x1E, 0x00, 0x27, 0x7F, 0xD0, 0xFF, 0x8C, 0x7C, 0x6A, 0x27, 0xBF, 0xC3, 0x27, 0xAF, + 0xFF, 0xBE, 0x3F, 0xFE, 0x9A, 0x07, 0xEE, 0x3F, 0x40, 0xF5, 0x7C, 0x7B, 0xF7, 0xFB, 0xD0, 0x3C, + 0x85, 0x06, 0x33, 0x80, 0xF8, 0x01, 0xBC, 0xAF, 0xFE, 0x0F, 0xFF, 0xF2, 0x20, 0x8E, 0x8B, 0xBE, + 0x3F, 0xFF, 0x3F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x10, 0x03, 0x10, 0x09, 0x10, 0x03, 0x10, 0x0B, 0x20, 0x0A, 0x30, 0x13, 0x70, + 0x00, 0x10, 0x04, 0x10, 0x1B, 0x10, 0x12, 0x10, 0x0D, 0x10, 0x0D, 0x10, 0x0D, 0x10, 0x0F, 0x40, + 0x65, 0x10, 0x67, 0x30, 0x00, 0x10, 0x74, 0x10, 0x19, 0x20, 0x00, 0x20, 0x16, 0x10, 0x6D, 0x40, + 0xAB, 0x10, 0x00, 0x10, 0x00, 0x50, 0x11, 0x10, 0x1A, 0x30, 0xF9, 0x00, 0x00, 0x30, 0x01, 0x00, + 0x00, 0x30, 0x00, 0x00, 0x30, 0x80, 0x0E, 0x10, 0x01, 0x00, 0x2D, 0xA0, 0x12, 0x00, 0x5E, 0xA0, + 0x8F, 0x00, 0x2D, 0x80, 0x1F, 0x10, 0x5B, 0x10, 0x01, 0x00, 0xEF, 0x30, 0x01, 0x00, 0x00, 0x00, + 0x2E, 0x10, 0x7D, 0x00, 0x5D, 0x20, 0x01, 0xC0, 0x14, 0x01, 0x1F, 0x51, 0x55, 0x00, 0x00, 0x11, + 0x1E, 0x01, 0xA0, 0xD0, 0x00, 0xA0, 0x11, 0x11, 0x63, 0x00, 0x00, 0x00, 0x5F, 0x13, 0xE0, 0x80, + 0x00, 0x00, 0x2F, 0xC0, 0x8F, 0x00, 0x2F, 0x80, 0x0E, 0x30, 0x01, 0x00, 0x00, 0x52, 0xD7, 0x02, + 0xDB, 0x00, 0x60, 0x12, 0xA9, 0x15, 0xB6, 0x00, 0xCF, 0x14, 0x1E, 0x80, 0xFD, 0x32, 0xF0, 0x00, + 0x8F, 0x20, 0x01, 0x10, 0xC7, 0x10, 0x01, 0xE0, 0x2F, 0x10, 0x51, 0x40, 0x83, 0xB4, 0x6E, 0x40, + 0x5F, 0x14, 0xA7, 0x00, 0x00, 0x01, 0x53, 0x11, 0xE6, 0x70, 0x01, 0x00, 0x00, 0x55, 0xE9, 0x43, + 0xF1, 0x01, 0xB0, 0x20, 0x01, 0x11, 0x83, 0x40, 0x09, 0x40, 0x14, 0x02, 0x49, 0x02, 0x9E, 0x12, + 0x3F, 0x31, 0x57, 0x00, 0x00, 0x61, 0x7B, 0x45, 0xD5, 0x80, 0x01, 0xE0, 0x00, 0x81, 0xAA, 0x01, + 0xB0, 0x10, 0x01, 0x11, 0xA8, 0x14, 0x2F, 0x43, 0x95, 0x01, 0x7F, 0x20, 0x2D, 0x48, 0x95, 0x60, + 0x2D, 0x06, 0x6F, 0x40, 0x5F, 0x40, 0x01, 0x01, 0x5E, 0x07, 0xF5, 0x00, 0x00, 0x94, 0xF5, 0x20, + 0xFD, 0x01, 0x2D, 0x12, 0xA5, 0x76, 0xA7, 0x00, 0x2D, 0x33, 0x40, 0xA1, 0x46, 0x00, 0x2E, 0x93, + 0xE6, 0x07, 0x36, 0x05, 0x4F, 0x12, 0x1C, 0x06, 0xAB, 0x12, 0x48, 0x10, 0xC2, 0x06, 0x9B, 0x00, + 0x00, 0x07, 0x66, 0x31, 0x52, 0x07, 0x6A, 0x00, 0x00, 0x09, 0x45, 0xC1, 0xDE, 0x01, 0x4F, 0xA9, + 0x55, 0x04, 0x94, 0x09, 0xD6, 0xB6, 0x26, 0x09, 0x75, 0x12, 0xD1, 0x99, 0xB6, 0x01, 0xE7, 0x24, + 0x5F, 0x98, 0xB5, 0x07, 0xF5, 0xA0, 0xD8, 0x14, 0x73, 0x06, 0x5A, 0x90, 0x22, 0x00, 0x77, 0xCA, + 0x90, 0x01, 0x09, 0x4D, 0x99, 0x70, 0x61, 0x00, 0x2F, 0x09, 0xBF, 0x00, 0x00, 0x1E, 0x5A, 0x00, + 0x00, 0x00, 0x2B, 0x05, 0x8A, 0x91, 0x70, 0x03, 0x19, 0x19, 0xA2, 0x07, 0xFF, 0x92, 0x0F, 0x4D, + 0x51, 0x00, 0x00, 0x92, 0x62, 0x00, 0x5F, 0xBB, 0x6D, 0x07, 0x0F, 0x10, 0x2F, 0x02, 0xFF, 0x00, + 0x2F, 0x1C, 0x61, 0x90, 0xBF, 0x16, 0x4B, 0x00, 0xBF, 0xE4, 0x68, 0x04, 0x78, 0x18, 0xF3, 0x05, + 0xBD, 0x14, 0x43, 0x91, 0x8B, 0x2F, 0x39, 0x08, 0xFF, 0x18, 0x01, 0x2C, 0x09, 0x48, 0x09, 0x3E, + 0x8B, 0x00, 0x2F, 0x34, 0x9D, 0x38, 0xFF, 0x26, 0x28, 0x25, 0x5F, 0x07, 0xA1, 0x15, 0x91, 0x1D, + 0x8B, 0x19, 0x99, 0x24, 0xFF, 0x21, 0x34, 0x23, 0xCB, 0x00, 0x5F, 0x49, 0xBD, 0x10, 0xC1, 0xC9, + 0xBD, 0x03, 0xFF, 0x06, 0x4F, 0x00, 0x2F, 0x10, 0xED, 0x90, 0xED, 0x22, 0x81, 0x02, 0x47, 0xA0, + 0x07, 0x5B, 0x6B, 0x60, 0x07, 0xD0, 0x2D, 0x10, 0x52, 0x10, 0x57, 0x10, 0x01, 0x50, 0x5C, 0x20, + 0xEB, 0x02, 0x7D, 0x2C, 0x24, 0x1B, 0x15, 0x03, 0xF4, 0x20, 0x01, 0x10, 0x00, 0xF0, 0x30, 0x10, + 0x66, 0x13, 0x0F, 0x3E, 0xA5, 0x22, 0x3D, 0x14, 0x2C, 0x01, 0x39, 0x11, 0x4D, 0x33, 0x9B, 0x20, + 0x0B, 0x41, 0xA5, 0x2F, 0x09, 0x02, 0x6F, 0x5E, 0x73, 0x29, 0x8B, 0x02, 0xCF, 0xA0, 0x01, 0x5D, + 0x29, 0x10, 0x09, 0x0A, 0xB9, 0x40, 0x25, 0x43, 0xCB, 0x81, 0xE9, 0x03, 0xCB, 0x00, 0x2F, 0x70, + 0xBF, 0xA0, 0xBF, 0x0D, 0xBF, 0xE0, 0x5F, 0x40, 0x01, 0x14, 0xEB, 0x03, 0xFF, 0x8E, 0x09, 0x0E, + 0x0F, 0x02, 0xAF, 0x98, 0xFF, 0x07, 0x33, 0x19, 0x2F, 0xB8, 0x9F, 0x03, 0xCF, 0x4D, 0x8F, 0x0B, + 0x0F, 0xCB, 0x6F, 0x42, 0x1D, 0x00, 0x00, 0x9B, 0xBF, 0x00, 0x8D, 0x03, 0xCF, 0x0A, 0xDF, 0x07, + 0xF1, 0x7C, 0xE8, 0x00, 0x00, 0x11, 0x7D, 0x09, 0x06, 0x2C, 0x8D, 0x14, 0x63, 0x30, 0x00, 0x36, + 0xBA, 0x92, 0x22, 0x0B, 0x6F, 0x12, 0xE3, 0x02, 0x85, 0xAB, 0x0F, 0x01, 0xDF, 0xA0, 0x11, 0x07, + 0xC5, 0xC0, 0x5F, 0x10, 0x61, 0x00, 0x5F, 0x16, 0x3B, 0xDB, 0x9F, 0x24, 0xBF, 0x0B, 0xFE, 0xB0, + 0xBF, 0x13, 0xF7, 0x01, 0x4B, 0x07, 0xF5, 0x14, 0xD7, 0x0D, 0x7A, 0x04, 0x3F, 0x30, 0x99, 0x9B, + 0x6F, 0x0E, 0x93, 0x01, 0x39, 0xA1, 0x8A, 0x01, 0xFA, 0x17, 0x1B, 0xA0, 0x2F, 0x00, 0xCF, 0x2C, + 0xBF, 0x90, 0x2F, 0x11, 0xA6, 0xA9, 0x9F, 0xF0, 0x8F, 0xA0, 0xEF, 0x29, 0x25, 0x01, 0x78, 0x00, + 0x5F, 0x17, 0x13, 0x00, 0x5F, 0xA6, 0xCF, 0x27, 0x79, 0x00, 0x00, 0xA0, 0x50, 0x08, 0x5F, 0x02, + 0x89, 0x27, 0xF1, 0xD0, 0x2F, 0x0C, 0x2E, 0x91, 0x4F, 0x0E, 0xC9, 0x90, 0x2F, 0x6A, 0x19, 0x00, + 0x8F, 0xA1, 0xAF, 0x2E, 0x6D, 0x02, 0x9F, 0x19, 0x7F, 0x1A, 0xA9, 0xB0, 0x7F, 0x01, 0x51, 0x02, + 0xCF, 0xB1, 0x1F, 0x00, 0x00, 0x48, 0x21, 0x00, 0x00, 0x13, 0x21, 0x03, 0xF5, 0x08, 0x3F, 0x10, + 0x0B, 0xA5, 0x5B, 0x29, 0xC5, 0x09, 0x8F, 0x03, 0xF5, 0x10, 0x0E, 0x10, 0x18, 0xF3, 0xF5, 0x20, + 0x2D, 0x80, 0xC9, 0x09, 0x8F, 0x5A, 0x17, 0x15, 0xF9, 0x10, 0x0D, 0x6C, 0x32, 0x10, 0x01, 0x07, + 0x29, 0x3C, 0x5F, 0x12, 0x47, 0x20, 0x37, 0x40, 0x2C, 0x02, 0x49, 0x00, 0x5F, 0x27, 0x13, 0x21, + 0x4F, 0x40, 0x2C, 0x41, 0x47, 0x06, 0x89, 0x3A, 0xD1, 0x10, 0x5C, 0x1C, 0x6D, 0x03, 0x2F, 0x72, + 0x76, 0xC0, 0x01, 0x0B, 0x3F, 0x4B, 0x79, 0x80, 0x01, 0x00, 0x8F, 0x41, 0x1D, 0x20, 0x89, 0x07, + 0x75, 0x10, 0x0F, 0x30, 0x0F, 0xE0, 0x35, 0xC0, 0x1F, 0x00, 0x63, 0x10, 0x31, 0x40, 0xEF, 0x0F, + 0x93, 0x4B, 0xA0, 0x1E, 0x97, 0x11, 0x9B, 0xD0, 0x5E, 0x0C, 0x5F, 0x00, 0x2F, 0x22, 0x8D, 0x06, + 0x30, 0x03, 0x8F, 0x10, 0xC2, 0x62, 0x07, 0x20, 0x66, 0x07, 0x6B, 0x60, 0x67, 0x70, 0x41, 0x00, + 0xCF, 0xC0, 0xF5, 0x10, 0x35, 0x02, 0x15, 0x09, 0x5F, 0x10, 0x33, 0x04, 0x5F, 0x13, 0xC7, 0x92, + 0xBE, 0x00, 0xCA, 0xE1, 0x85, 0x1A, 0xB8, 0x09, 0x8F, 0xA9, 0x8F, 0x9C, 0x3E, 0x09, 0xEF, 0x00, + 0x5F, 0x13, 0xB3, 0x04, 0x32, 0x3F, 0x05, 0xD7, 0x45, 0x01, 0x4F, 0x14, 0x2B, 0x0C, 0xEF, 0x03, + 0xF5, 0x80, 0x00, 0x1F, 0x2D, 0xAD, 0xDF, 0x23, 0x46, 0x03, 0x43, 0x1B, 0x62, 0x0B, 0x90, 0xE2, + 0xAF, 0x10, 0x01, 0x92, 0x00, 0x10, 0x0F, 0x15, 0x21, 0x02, 0x6F, 0xA0, 0x1E, 0x12, 0x3D, 0x00, + 0xCE, 0x00, 0xEF, 0x15, 0xAF, 0x0B, 0xFF, 0x12, 0xD5, 0xCA, 0x9B, 0x00, 0x8F, 0x02, 0x40, 0x63, + 0x8F, 0x12, 0xC9, 0x00, 0x00, 0x15, 0xF8, 0x00, 0x5E, 0x05, 0x5F, 0x00, 0x00, 0x07, 0x1E, 0x0F, + 0x77, 0x0D, 0x4F, 0x01, 0x1F, 0x01, 0x4B, 0x00, 0x2F, 0x00, 0x8F, 0x01, 0xDF, 0x00, 0x00, 0x00, + 0x00, 0x2A, 0xA7, 0x1D, 0xDF, 0x25, 0x5F, 0x80, 0x01, 0xE0, 0x00, 0x7A, 0x77, 0x10, 0x2D, 0x16, + 0x83, 0x0A, 0x7F, 0x29, 0xBE, 0x30, 0x01, 0x08, 0xC1, 0x50, 0x2F, 0x50, 0x07, 0x0B, 0x1E, 0x30, + 0x5F, 0x2F, 0x67, 0x0E, 0x1B, 0x09, 0xBF, 0x5E, 0xD5, 0x00, 0x5F, 0x31, 0x1F, 0x8B, 0x97, 0x37, + 0x0F, 0x00, 0xBF, 0x0A, 0xAF, 0x00, 0x2F, 0x19, 0xAF, 0x2B, 0xFB, 0x00, 0x5F, 0x20, 0xEF, 0x80, + 0xEF, 0x38, 0x46, 0x20, 0x01, 0xE2, 0x4F, 0x30, 0x2F, 0x01, 0x1F, 0x42, 0x07, 0x11, 0xDF, 0x20, + 0x4F, 0x2B, 0x3F, 0x00, 0x2F, 0x30, 0x5F, 0x02, 0x9F, 0xC2, 0x6F, 0x02, 0x6F, 0x80, 0xBF, 0x08, + 0xBF, 0x22, 0x97, 0x07, 0x91, 0x03, 0xFF, 0x0E, 0xA5, 0x8F, 0xFF, 0xDA, 0x7F, 0x00, 0x2F, 0x00, + 0x8F, 0x9A, 0xAF, 0x07, 0xF5, 0x20, 0x5F, 0xDA, 0x1F, 0x0B, 0x71, 0x03, 0xF5, 0x93, 0xCF, 0x34, + 0xBF, 0x10, 0x69, 0x22, 0x51, 0x40, 0x01, 0x0C, 0x30, 0xE2, 0x15, 0x00, 0xBF, 0x07, 0x65, 0xA3, + 0x9B, 0x09, 0x0F, 0x1D, 0xAF, 0x13, 0x35, 0x8F, 0x91, 0x0E, 0xE4, 0x00, 0x8F, 0x90, 0xEF, 0x53, + 0xF5, 0x02, 0x4F, 0x01, 0x1F, 0x25, 0x77, 0xAE, 0xCF, 0x04, 0xBF, 0x22, 0x9F, 0x81, 0x23, 0x02, + 0x9F, 0x02, 0x6F, 0x05, 0xAF, 0x0E, 0x70, 0x0E, 0xC0, 0x00, 0x00, 0x0A, 0xAF, 0x00, 0x00, 0x0E, + 0x23, 0x35, 0xF8, 0x50, 0x01, 0x00, 0x30, 0x1F, 0xAB, 0x00, 0xEF, 0xAF, 0xD9, 0x16, 0x95, 0x0A, + 0x7A, 0x11, 0x20, 0xA3, 0x79, 0x00, 0x8D, 0x43, 0xEE, 0x00, 0x2F, 0xC0, 0x12, 0x02, 0x7C, 0x0A, + 0xAF, 0xD9, 0xBF, 0x0B, 0x3F, 0x25, 0xAD, 0x16, 0x25, 0x4A, 0xD9, 0x07, 0x41, 0xFB, 0x14, 0x2A, + 0x12, 0x00, 0xF3, 0x20, 0x8F, 0x30, 0x8F, 0x1A, 0xBA, 0x0B, 0x69, 0x51, 0x1F, 0x12, 0x45, 0x09, + 0x5E, 0x40, 0x01, 0x01, 0x82, 0x63, 0xF7, 0x03, 0xFF, 0x51, 0xA7, 0x08, 0x8A, 0xAA, 0x25, 0x80, + 0x0F, 0x08, 0x5D, 0x03, 0x64, 0x01, 0x4A, 0x00, 0x00, 0x12, 0x94, 0xAB, 0x40, 0x07, 0xA1, 0x05, + 0x14, 0x13, 0xC5, 0x20, 0x01, 0x33, 0x20, 0x2D, 0xDD, 0x00, 0x2F, 0x40, 0x29, 0x10, 0x09, 0x88, + 0xDD, 0x01, 0xB1, 0xD2, 0x4F, 0x98, 0x9F, 0x0A, 0xAF, 0x09, 0x5F, 0x23, 0x99, 0x00, 0x5F, 0x91, + 0x22, 0x19, 0x71, 0x00, 0x2F, 0xB4, 0xB6, 0x01, 0x61, 0x90, 0xC8, 0x02, 0x4E, 0xBB, 0xFF, 0x20, + 0x61, 0x03, 0xFF, 0x10, 0xF3, 0x00, 0xF5, 0x03, 0xFF, 0xC6, 0xDA, 0x00, 0x00, 0xB8, 0xF6, 0x00, + 0xF8, 0x0C, 0x01, 0x00, 0x55, 0x96, 0x9C, 0x98, 0x8F, 0x00, 0x30, 0x00, 0x2B, 0x26, 0x3B, 0x80, + 0x0F, 0x31, 0xED, 0x07, 0xC7, 0x08, 0x27, 0x00, 0x2F, 0x09, 0x2E, 0x9C, 0x7A, 0x00, 0x27, 0x10, + 0xA1, 0xA2, 0x83, 0x07, 0x47, 0x00, 0x00, 0x90, 0x52, 0x0D, 0x19, 0x03, 0x41, 0x50, 0xBF, 0x10, + 0xE4, 0xBE, 0x49, 0x06, 0xBE, 0x0D, 0xF9, 0x00, 0x2F, 0xA1, 0xDF, 0x01, 0x27, 0x00, 0x2F, 0x00, + 0x00, 0x80, 0x0E, 0x08, 0xF1, 0x93, 0xD9, 0x03, 0xD9, 0x07, 0x90, 0x00, 0x00, 0x20, 0x01, 0x15, + 0x69, 0x40, 0x19, 0x37, 0x63, 0x15, 0x1E, 0x20, 0x01, 0x16, 0x77, 0xC3, 0x3F, 0x01, 0x2E, 0x22, + 0xAD, 0x0C, 0x5F, 0x55, 0xBE, 0x10, 0x01, 0x50, 0x0B, 0x19, 0x15, 0x13, 0xFB, 0x59, 0xCE, 0x00, + 0x00, 0x10, 0x88, 0x15, 0x61, 0x1B, 0xCF, 0x1A, 0xBB, 0x01, 0x7F, 0x05, 0xEC, 0x07, 0x5D, 0x00, + 0x60, 0x07, 0xBE, 0x20, 0x01, 0x03, 0x0D, 0x1A, 0xDF, 0xBE, 0xAD, 0x0B, 0x19, 0xC0, 0x2F, 0x10, + 0x00, 0xA0, 0x0F, 0x10, 0x01, 0x00, 0x00, 0x50, 0x00, 0x60, 0x0F, 0x00, 0x8A, 0x27, 0xA0, 0x12, + 0x4D, 0x00, 0x00, 0x32, 0xF9, 0x12, 0x6F, 0x09, 0x4F, 0x1F, 0x59, 0x14, 0xCC, 0x10, 0x0F, 0x57, + 0x67, 0x06, 0xAB, 0x98, 0xF9, 0x90, 0x0F, 0xA0, 0x6A, 0x9B, 0x36, 0x0B, 0x0D, 0x10, 0x2E, 0x00, + 0xA8, 0x91, 0xD7, 0xEB, 0x7F, 0x03, 0xFE, 0xC2, 0x47, 0x21, 0x4D, 0x00, 0x00, 0x00, 0x00, 0x82, + 0xA0, 0x00, 0x2E, 0x00, 0x5E, 0x80, 0x0F, 0xD2, 0xAF, 0x06, 0xB4, 0x08, 0xCF, 0x3B, 0x35, 0x97, + 0xFF, 0x14, 0x46, 0x09, 0x10, 0x18, 0x1E, 0x18, 0x95, 0xB0, 0x00, 0x04, 0x7A, 0x90, 0xB0, 0x00, + 0xBD, 0x14, 0x6B, 0xA5, 0xEF, 0x2E, 0xC5, 0x07, 0x5A, 0x0F, 0xFF, 0x86, 0x87, 0x16, 0x87, 0x00, + 0x8D, 0x07, 0x6D, 0x29, 0xF6, 0x92, 0x6A, 0x01, 0x22, 0x1B, 0x66, 0x00, 0x00, 0x00, 0xEF, 0xCF, + 0xB2, 0x00, 0x5E, 0x08, 0x00, 0xC0, 0x00, 0x09, 0x4F, 0x1B, 0xBC, 0x0F, 0x64, 0x1B, 0x68, 0x1B, + 0x0F, 0x08, 0xCE, 0x13, 0xFE, 0x10, 0xF6, 0x10, 0x01, 0x0F, 0x31, 0x1B, 0xFF, 0x63, 0xCE, 0x0B, + 0xEF, 0x1B, 0xC5, 0x13, 0x69, 0x10, 0x05, 0xC2, 0xD5, 0x02, 0xE3, 0x20, 0x01, 0x40, 0x09, 0x00, + 0x5F, 0x14, 0xE5, 0x4C, 0xE9, 0x20, 0x01, 0x02, 0x1E, 0x13, 0x67, 0x59, 0xA9, 0x42, 0x23, 0x03, + 0xFF, 0x03, 0xF7, 0x0A, 0x7F, 0x0B, 0x0F, 0x10, 0x03, 0x10, 0x01, 0xF6, 0x0D, 0x02, 0x77, 0x33, + 0x0F, 0x0A, 0xCB, 0xD3, 0x71, 0x22, 0xEA, 0x20, 0x01, 0x03, 0x0F, 0x09, 0xD8, 0x04, 0xCF, 0x0D, + 0x83, 0x32, 0x0D, 0xA2, 0x17, 0x00, 0x2F, 0x08, 0x95, 0xA6, 0x95, 0xEB, 0x5F, 0x08, 0x5F, 0x33, + 0xF5, 0x07, 0xC4, 0x3F, 0xF9, 0x40, 0x01, 0x02, 0x4B, 0xC4, 0x26, 0x02, 0x49, 0xDB, 0xFF, 0x00, + 0xCE, 0xE9, 0xBD, 0x01, 0x87, 0x50, 0x00, 0x23, 0xF5, 0xA0, 0x8F, 0x06, 0x19, 0x63, 0xFF, 0x04, + 0x07, 0x0A, 0x7F, 0x00, 0x00, 0x91, 0x5F, 0x00, 0x6E, 0x0D, 0x7D, 0x00, 0xEF, 0xB0, 0xEF, 0x00, + 0xEF, 0x00, 0x00, 0xF2, 0x9F, 0x04, 0x3C, 0x00, 0x00, 0x08, 0x3F, 0x12, 0xE5, 0x00, 0x98, 0x17, + 0xD7, 0x81, 0x09, 0x10, 0x01, 0x01, 0x98, 0x08, 0xEE, 0x0B, 0x0F, 0xE0, 0x8F, 0x09, 0x2F, 0xE0, + 0x2F, 0x14, 0x6C, 0x04, 0xF9, 0xE2, 0x0F, 0x00, 0xB7, 0x12, 0x37, 0x35, 0xEA, 0xD6, 0x79, 0x01, + 0x48, 0x19, 0xDC, 0x26, 0x0A, 0x50, 0x2F, 0x04, 0x2F, 0xC0, 0x5F, 0x00, 0x33, 0x1E, 0x07, 0x30, + 0x37, 0x20, 0x2F, 0x11, 0x15, 0x0E, 0x09, 0x7D, 0x49, 0x70, 0x8F, 0x00, 0x91, 0x10, 0x5D, 0x00, + 0xEF, 0x75, 0xE9, 0x1B, 0x0A, 0x91, 0xD0, 0x07, 0x78, 0x90, 0x8A, 0x07, 0x3F, 0x11, 0xE7, 0x05, + 0x88, 0x07, 0xCF, 0x90, 0x01, 0x48, 0x3E, 0x80, 0x01, 0x09, 0xE9, 0x40, 0x01, 0x13, 0xF5, 0x23, + 0xF7, 0x60, 0x07, 0x00, 0x00, 0x0C, 0x29, 0x04, 0x87, 0x80, 0x01, 0x0D, 0xB0, 0x01, 0x4B, 0x9B, + 0xCF, 0x07, 0x19, 0x30, 0x89, 0x10, 0xF4, 0x11, 0x43, 0xF0, 0x2F, 0x20, 0x2B, 0x2C, 0x8F, 0x23, + 0xD9, 0x1A, 0x39, 0xF9, 0xEA, 0x52, 0x0F, 0x13, 0xEE, 0x10, 0x62, 0x00, 0x5F, 0x30, 0xA8, 0x2D, + 0x4D, 0x01, 0x23, 0x1D, 0xAF, 0x1A, 0x6B, 0x20, 0xE7, 0x10, 0xC5, 0x01, 0xC5, 0xF0, 0x2F, 0x23, + 0x6F, 0x00, 0x2F, 0x00, 0x8F, 0x00, 0x2F, 0x21, 0x97, 0x53, 0x25, 0x43, 0xFB, 0x03, 0x2F, 0x14, + 0xB8, 0x4B, 0x35, 0x0E, 0x0E, 0x00, 0x00, 0x84, 0x01, 0x0C, 0x3E, 0x90, 0x01, 0x10, 0x00, 0xA3, + 0xFF, 0x00, 0x00, 0x11, 0x2B, 0x11, 0x2D, 0x01, 0x1F, 0x0F, 0xFB, 0x4D, 0x7F, 0x12, 0x1D, 0x96, + 0x15, 0x12, 0x4E, 0x01, 0x4E, 0x3F, 0x64, 0x60, 0x6F, 0x09, 0xB7, 0x14, 0x50, 0x96, 0xFE, 0x00, + 0x5F, 0x12, 0xC5, 0x21, 0xD1, 0x01, 0x4E, 0x22, 0xE0, 0xB0, 0x8F, 0x11, 0x89, 0x00, 0x2F, 0xD0, + 0x2F, 0x08, 0x24, 0x39, 0x23, 0x13, 0x31, 0x17, 0xF5, 0x20, 0x2E, 0x24, 0x65, 0x0F, 0xC9, 0xD9, + 0x8F, 0x14, 0x9F, 0x80, 0x00, 0x0F, 0x4B, 0x5B, 0xF9, 0x00, 0x2F, 0x26, 0xD7, 0x00, 0x2F, 0x31, + 0x20, 0x00, 0x2F, 0x14, 0xF9, 0x24, 0xF7, 0xB2, 0xE9, 0x00, 0xEF, 0x16, 0xA7, 0x11, 0x18, 0x00, + 0x2F, 0x11, 0x92, 0x00, 0x2F, 0x31, 0xC0, 0x00, 0x8F, 0x88, 0xF9, 0xA0, 0xEF, 0x11, 0xFB, 0x01, + 0xF8, 0x5A, 0xAF, 0x10, 0x99, 0x8A, 0xAF, 0x16, 0x61, 0x02, 0x59, 0x32, 0x1B, 0x1B, 0xD0, 0x48, + 0x97, 0x27, 0x13, 0x10, 0x46, 0x22, 0x36, 0x08, 0xA7, 0x25, 0x7B, 0x50, 0x2F, 0x50, 0x5F, 0x00, + 0x2F, 0x10, 0x5F, 0x20, 0xB8, 0x50, 0x2F, 0x10, 0x2F, 0x2B, 0xDE, 0x00, 0x2F, 0x1B, 0x9F, 0x80, + 0x2F, 0x10, 0xE5, 0x24, 0x90, 0x00, 0x2F, 0x1C, 0xA8, 0x3C, 0x90, 0x40, 0x2F, 0x29, 0xED, 0x31, + 0x06, 0x00, 0xBF, 0x50, 0x00, 0xEA, 0xFF, 0x03, 0x0F, 0x71, 0x1F, 0x40, 0x00, 0x00, 0x5F, 0x10, + 0xC1, 0x11, 0x68, 0x46, 0xCF, 0xEA, 0xE9, 0x0C, 0xBF, 0x60, 0x1F, 0x60, 0x52, 0x00, 0x2F, 0x4C, + 0xBF, 0xA0, 0x1F, 0x20, 0x33, 0x00, 0x2F, 0xA0, 0x4F, 0x6C, 0x8F, 0x00, 0x5F, 0x0B, 0x9F, 0x43, + 0x6B, 0xA0, 0x6F, 0x09, 0xC8, 0x00, 0x2F, 0x42, 0xF5, 0x80, 0x01, 0x0D, 0x54, 0x00, 0x2F, 0x40, + 0xE5, 0x40, 0x09, 0x15, 0x5E, 0x40, 0x39, 0x10, 0x09, 0x2E, 0xF6, 0xD7, 0x6F, 0x80, 0x69, 0x0A, + 0x1F, 0x2E, 0x44, 0x20, 0x57, 0x3A, 0x1F, 0x5D, 0x7A, 0x10, 0x01, 0xFE, 0xE8, 0x00, 0x2F, 0x20, + 0x01, 0x00, 0x5F, 0x00, 0x2F, 0x18, 0xBA, 0x62, 0x39, 0x14, 0x99, 0x03, 0xFF, 0x20, 0x5F, 0x11, + 0x49, 0x00, 0x5F, 0xCE, 0xCF, 0x4B, 0x79, 0x80, 0x01, 0x24, 0xF7, 0x00, 0x2F, 0x00, 0x3F, 0xA0, + 0x11, 0x0E, 0x77, 0x00, 0x2F, 0x13, 0x27, 0x0C, 0x50, 0x00, 0x2F, 0xA0, 0xEF, 0x12, 0xDA, 0x06, + 0xA5, 0xE5, 0xD5, 0x29, 0xF4, 0x00, 0x8F, 0x17, 0x28, 0x86, 0x8C, 0x16, 0xD5, 0x00, 0x2F, 0x00, + 0x00, 0x00, 0x5F, 0x15, 0x9D, 0x91, 0xAF, 0x11, 0xAF, 0x06, 0x9B, 0x00, 0x2F, 0x00, 0x42, 0x2C, + 0x72, 0x3F, 0x73, 0x6C, 0x79, 0x39, 0xF7, 0x02, 0x89, 0x00, 0x64, 0x15, 0x67, 0x99, 0x89, 0x35, + 0xC6, 0x00, 0x00, 0x03, 0x47, 0x6C, 0x95, 0x00, 0x00, 0x2C, 0x95, 0x00, 0x00, 0x90, 0x2F, 0x0D, + 0x5D, 0x6C, 0xC5, 0x00, 0x00, 0x1D, 0x23, 0x02, 0xD8, 0x00, 0x00, 0x01, 0x7F, 0x01, 0x4F, 0x1D, + 0x85, 0x10, 0x8C, 0x0A, 0xFD, 0xA4, 0x57, 0x3B, 0xA4, 0x00, 0x00, 0x2C, 0xC4, 0x0C, 0x7D, 0xA0, + 0x00, 0x00, 0x2D, 0x71, 0x21, 0x32, 0x6D, 0x02, 0x71, 0x02, 0x9F, 0x7E, 0x1C, 0x11, 0xED, 0x10, + 0x01, 0x04, 0x3B, 0x19, 0x2B, 0x13, 0xC7, 0x10, 0x03, 0x2D, 0xED, 0x40, 0x01, 0x04, 0x2D, 0x1A, + 0xA9, 0x10, 0x01, 0x10, 0x09, 0x11, 0x91, 0x69, 0x83, 0x3E, 0xDD, 0x01, 0xA1, 0x1E, 0x51, 0x1E, + 0xB7, 0x27, 0xFF, 0x80, 0x01, 0xF1, 0xB7, 0x00, 0x2F, 0x00, 0x5F, 0x00, 0x5F, 0x00, 0x8F, 0x50, + 0x2F, 0x20, 0x2F, 0x01, 0x81, 0x13, 0x0B, 0x41, 0xA3, 0x03, 0xA0, 0x30, 0x2F, 0x50, 0x91, 0x02, + 0x19, 0x00, 0x2F, 0x00, 0x2F, 0x00, 0x8F, 0xCA, 0xFF, 0x0E, 0xFF, 0x00, 0x2F, 0x11, 0xD9, 0x33, + 0x8E, 0x11, 0xD3, 0x63, 0x61, 0x07, 0x2F, 0x8B, 0xF7, 0x57, 0x57, 0x00, 0x2F, 0xF0, 0x00, 0x00, + 0x26, 0x0A, 0x7F, 0x00, 0x2F, 0x99, 0xBF, 0x13, 0xF5, 0x06, 0x16, 0xC4, 0xC7, 0x05, 0x38, 0x00, + 0x2F, 0x93, 0xC6, 0x00, 0x2F, 0x1A, 0x18, 0x04, 0x00, 0x20, 0x2F, 0xDD, 0x7F, 0x06, 0x47, 0x00, + 0x2F, 0x02, 0x9F, 0x00, 0x2F, 0x11, 0xAF, 0x70, 0x00, 0x02, 0x0D, 0x00, 0x5F, 0x03, 0xCE, 0x70, + 0x00, 0x29, 0x30, 0x0B, 0x6F, 0x91, 0x5E, 0x00, 0x00, 0x01, 0x9B, 0x01, 0x4F, 0xBA, 0x80, 0x0A, + 0x2C, 0x1A, 0x80, 0x00, 0x8D, 0x11, 0x4F, 0xC0, 0x2B, 0x00, 0x61, 0x01, 0xDF, 0x06, 0xDF, 0x01, + 0xAF, 0x00, 0x00, 0x00, 0x00, 0x68, 0x6D, 0x18, 0x6F, 0x25, 0x5F, 0xA8, 0x71, 0x00, 0x2F, 0xC8, + 0xA1, 0x0A, 0x7F, 0x00, 0x5F, 0x00, 0x2F, 0x00, 0x8F, 0x20, 0x04, 0x80, 0x0D, 0x2E, 0x2D, 0x01, + 0x1F, 0x10, 0x5F, 0x25, 0x8F, 0x00, 0x5F, 0xD9, 0xEF, 0x4D, 0x54, 0x00, 0x2F, 0x0B, 0x69, 0x54, + 0x86, 0x1A, 0x7F, 0x14, 0x8C, 0x40, 0x5F, 0x3D, 0xD7, 0x38, 0x6A, 0x30, 0x01, 0x05, 0x00, 0x30, + 0x5F, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0xB8, 0x6F, 0x06, 0x17, 0x00, 0x2F, 0x07, 0xF1, 0x19, + 0x8F, 0x00, 0x5F, 0x9B, 0x8F, 0x09, 0x8F, 0x00, 0x2F, 0x0C, 0x58, 0x50, 0x00, 0x84, 0x61, 0x10, + 0x5D, 0x05, 0x4B, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0xFF, 0x18, 0x18, 0x00, 0x1C, 0x18, 0x00, 0x02, 0x00, 0x00, + 0x15, 0x00, 0x15, 0x02, 0x30, 0x00, 0x00, 0x01, 0x10, 0x55, 0xAA, 0xFF, 0x05, 0x05, 0x08, 0x0D, + 0x0B, 0x11, 0x0E, 0x05, 0x06, 0x06, 0x0B, 0x0B, 0x05, 0x06, 0x05, 0x07, 0x0B, 0x05, 0x05, 0x09, + 0x11, 0x0D, 0x0C, 0x0C, 0x0E, 0x0B, 0x0A, 0x0E, 0x0D, 0x07, 0x06, 0x0B, 0x0A, 0x11, 0x0E, 0x10, + 0x0C, 0x10, 0x0C, 0x0B, 0x0B, 0x0D, 0x0C, 0x12, 0x06, 0x08, 0x0C, 0x0B, 0x0C, 0x09, 0x0C, 0x0A, + 0x07, 0x0B, 0x0C, 0x05, 0x05, 0x0A, 0x05, 0x13, 0x0C, 0x08, 0x09, 0x07, 0x0C, 0x0A, 0x0F, 0x0A, + 0x0A, 0x09, 0x07, 0x0B, 0x07, 0x0B, 0x00, 0x0B, 0x00, 0x04, 0x0B, 0x08, 0x0C, 0x17, 0x0B, 0x06, + 0x12, 0x00, 0x04, 0x04, 0x08, 0x08, 0x08, 0x0A, 0x14, 0x0C, 0x0F, 0x09, 0x09, 0x0C, 0x11, 0x07, + 0x0A, 0x0B, 0x06, 0x11, 0x0A, 0x09, 0x0B, 0x07, 0x07, 0x0C, 0x0C, 0x0D, 0x05, 0x04, 0x07, 0x07, + 0x0A, 0x0F, 0x0F, 0x0F, 0x09, 0x0D, 0x11, 0x0B, 0x07, 0x07, 0x0E, 0x0E, 0x10, 0x0B, 0x10, 0x11, + 0x09, 0x0A, 0x05, 0x0B, 0x0C, 0x0C, 0x0A, 0x0C, 0x0A, 0x23, 0x3D, 0x18, 0x3C, 0x19, 0x34, 0x00, + 0x74, 0x00, 0xB0, 0x2C, 0x00, 0x38, 0x09, 0x38, 0x03, 0xFF, 0x1F, 0xFF, 0xD0, 0x00, 0xE0, 0x09, + 0x0B, 0xE0, 0x1E, 0xB4, 0x02, 0xC0, 0x03, 0x80, 0x0F, 0x07, 0x01, 0xFF, 0x07, 0xFB, 0x0F, 0x41, + 0x40, 0xF0, 0x09, 0x19, 0xF0, 0x03, 0xC0, 0x0B, 0x80, 0x18, 0xD0, 0x78, 0x19, 0xBD, 0xFB, 0x0A, + 0xA8, 0x3C, 0x0A, 0x02, 0xFE, 0x0B, 0xFF, 0x1F, 0x03, 0x80, 0x09, 0x7C, 0x01, 0xFC, 0x0B, 0xBC, + 0x18, 0x07, 0xFD, 0x2F, 0xEF, 0x18, 0x40, 0x00, 0xC0, 0x0B, 0x3F, 0xEF, 0x14, 0x07, 0x0A, 0x00, + 0x07, 0x00, 0x1F, 0x00, 0x3F, 0xC0, 0x3E, 0x3D, 0x16, 0x38, 0x3C, 0x2C, 0x28, 0x1B, 0xF0, 0x3F, + 0xFF, 0x1A, 0xE9, 0x02, 0xC0, 0x02, 0xC0, 0x57, 0xE6, 0xFF, 0xFF, 0x07, 0x43, 0x74, 0x00, 0xFF, + 0xC0, 0xFA, 0x40, 0xF0, 0xE5, 0x00, 0x3D, 0x38, 0x3C, 0x34, 0x2F, 0x78, 0x0B, 0xFD, 0x01, 0x7F, + 0x00, 0x3B, 0x35, 0x00, 0x2C, 0x3C, 0x3C, 0x1E, 0xBA, 0x0B, 0xE7, 0x00, 0x0F, 0x00, 0x1D, 0x1D, + 0xB6, 0xF8, 0xFB, 0xAE, 0xCF, 0x0F, 0x8E, 0x0B, 0x0E, 0x0F, 0x42, 0x07, 0xDF, 0x02, 0xFE, 0x07, + 0xFC, 0x0F, 0x7F, 0x3E, 0x0F, 0x3C, 0x03, 0x3D, 0x03, 0xC0, 0xC7, 0xC0, 0xFF, 0x40, 0x1A, 0x0F, + 0x00, 0x1E, 0x00, 0x2D, 0x19, 0x3C, 0x00, 0x2E, 0x0F, 0x0E, 0x7F, 0xFF, 0x02, 0xF8, 0x07, 0xBD, + 0x0F, 0x4F, 0x0B, 0x40, 0x09, 0xBE, 0xF4, 0x00, 0xA0, 0x3F, 0x3F, 0xC0, 0x3F, 0x40, 0x02, 0xD0, + 0x03, 0xC0, 0x07, 0x80, 0x0B, 0x40, 0x0F, 0x0E, 0x2E, 0x01, 0x3C, 0x01, 0x3D, 0x01, 0x2D, 0x01, + 0x09, 0x14, 0x03, 0x07, 0x2E, 0xF0, 0x03, 0xD0, 0x09, 0x1F, 0x03, 0xFD, 0x02, 0xBF, 0x80, 0x06, + 0xB7, 0x01, 0xE3, 0x03, 0xC3, 0x0F, 0x03, 0x2D, 0x03, 0x78, 0x03, 0xFA, 0xAB, 0xFF, 0xFF, 0xE8, + 0x00, 0xFC, 0x3F, 0x2D, 0x00, 0x3E, 0x1B, 0x1E, 0x0B, 0x03, 0x0F, 0x07, 0x0E, 0x09, 0x00, 0x3F, + 0xBF, 0x2F, 0xFF, 0xE0, 0x0B, 0xB4, 0x0F, 0x0F, 0x0B, 0xAE, 0x02, 0xF8, 0x08, 0x3E, 0x00, 0x1F, + 0xEF, 0x07, 0xBE, 0x00, 0xFF, 0x80, 0x43, 0x0F, 0x1A, 0x1E, 0x0B, 0x80, 0x03, 0xC0, 0x01, 0x13, + 0x1F, 0x3C, 0x00, 0x7C, 0x0B, 0x28, 0x10, 0x20, 0x7C, 0x19, 0x19, 0x2C, 0x1D, 0x78, 0x10, 0x03, + 0x0F, 0xEF, 0x02, 0xFE, 0x0D, 0x1D, 0x0F, 0x40, 0x3F, 0xAA, 0x3F, 0x90, 0x0C, 0x02, 0x3F, 0xAF, + 0x0D, 0x28, 0x1F, 0xFF, 0x2F, 0xAE, 0xC0, 0x0C, 0x03, 0xFE, 0x0F, 0x0E, 0x3F, 0xFF, 0x2B, 0xAE, + 0x00, 0x01, 0x0A, 0x02, 0xFE, 0x0F, 0xEF, 0x2E, 0x0D, 0xFF, 0x2E, 0x1A, 0x61, 0x3B, 0x24, 0x19, + 0x2F, 0xF4, 0xFF, 0xBE, 0x10, 0x1F, 0x1A, 0x00, 0xBE, 0x03, 0xFE, 0x40, 0xAB, 0xE0, 0x00, 0xB8, + 0x0B, 0x7E, 0x18, 0xFF, 0x3E, 0xAF, 0x3C, 0x09, 0x6F, 0x03, 0xFE, 0x0B, 0xF8, 0x00, 0xBC, 0x00, + 0x04, 0x0E, 0xF0, 0x0B, 0xAA, 0x1A, 0x1E, 0x00, 0x6F, 0xF9, 0x0A, 0x0E, 0xBF, 0xE0, 0x1F, 0x20, + 0x3C, 0x00, 0x3F, 0xFE, 0x2F, 0xBF, 0x04, 0x1F, 0x3D, 0xFF, 0x3F, 0xEB, 0x3E, 0x02, 0x40, 0x04, + 0x0B, 0xF4, 0x0A, 0x2D, 0x01, 0x9B, 0x03, 0xFF, 0x07, 0x3D, 0x02, 0x3E, 0x02, 0x1F, 0xAF, 0x07, + 0xFD, 0x02, 0x00, 0x1E, 0x1E, 0x3F, 0x03, 0xF4, 0x2F, 0x40, 0x3F, 0x40, 0x07, 0xF4, 0x00, 0x7F, + 0x00, 0x07, 0xF0, 0x02, 0x2A, 0xAA, 0xF0, 0x40, 0x7F, 0x07, 0xF4, 0x3F, 0x05, 0x7D, 0x01, 0xF4, + 0x03, 0x13, 0x40, 0x0E, 0x03, 0x2C, 0x1F, 0x3C, 0x2D, 0x38, 0x3C, 0x3C, 0x2E, 0x00, 0x2D, 0xFF, + 0x0E, 0x9F, 0x4F, 0x0B, 0x4B, 0x0B, 0x0F, 0x4F, 0x7F, 0xAD, 0xEB, 0x02, 0xD7, 0x03, 0xC3, 0x07, + 0x82, 0xFF, 0x2F, 0x40, 0xF4, 0x00, 0x01, 0x3C, 0x07, 0x02, 0x06, 0x2E, 0x3D, 0x13, 0x3D, 0x00, + 0x2F, 0x1F, 0x00, 0x08, 0x1E, 0x03, 0x3D, 0x02, 0xFF, 0x00, 0xAF, 0x03, 0xFC, 0x04, 0x14, 0x00, + 0x3E, 0xAF, 0x2F, 0xE0, 0x0D, 0x1F, 0x02, 0x0C, 0x19, 0x01, 0x09, 0x07, 0x06, 0xBF, 0x0F, 0x0D, + 0x20, 0x2D, 0x70, 0x3D, 0x15, 0xE0, 0x03, 0x19, 0x2C, 0x0B, 0x1E, 0xFA, 0xE2, 0xF4, 0x00, 0x0B, + 0x2D, 0x0A, 0x3C, 0x02, 0x3E, 0xAF, 0x0E, 0x0F, 0xC0, 0x07, 0xFE, 0x00, 0xBF, 0xBC, 0x0B, 0x3C, + 0x01, 0xFC, 0x0A, 0x37, 0x00, 0x0F, 0x80, 0x03, 0x0F, 0x06, 0x22, 0x40, 0xBF, 0x33, 0x1E, 0x0E, + 0x1E, 0x3F, 0x00, 0x3F, 0x80, 0x3F, 0xFC, 0x01, 0xFC, 0x03, 0x09, 0x3E, 0x0F, 0xF4, 0xFE, 0x3E, + 0xBF, 0x0B, 0x20, 0x18, 0x06, 0xFE, 0x1F, 0xEF, 0x0A, 0xFF, 0xFF, 0xAA, 0xFA, 0x04, 0x28, 0x78, + 0x0A, 0xF0, 0x02, 0xF0, 0x03, 0xB8, 0xE0, 0x03, 0xF0, 0x03, 0xF4, 0x07, 0x08, 0xF0, 0x00, 0x7C, + 0x02, 0x3D, 0x08, 0xB8, 0x01, 0x0A, 0xAF, 0x0A, 0x3F, 0xC0, 0x3E, 0x80, 0x1A, 0xF0, 0x00, 0xB0, + 0x0E, 0x19, 0x78, 0xEE, 0x24, 0x18, 0x3C, 0x0F, 0x3C, 0x7D, 0x3C, 0xF4, 0x3F, 0xF4, 0x3F, 0xFC, + 0x3D, 0x3D, 0x07, 0x0E, 0x0F, 0xD0, 0x3D, 0xE0, 0x3C, 0xF0, 0x3C, 0xB4, 0x3C, 0x7C, 0x3C, 0x3C, + 0x3C, 0x2D, 0x3C, 0x1F, 0x03, 0xBC, 0x0B, 0x7C, 0x0F, 0x3C, 0x1E, 0x3C, 0x2C, 0x74, 0x78, 0x3C, + 0x3D, 0x3C, 0x1F, 0x3C, 0x0B, 0x3C, 0x03, 0x3C, 0x02, 0x4F, 0x00, 0xCF, 0x00, 0xEF, 0x01, 0x3E, + 0x03, 0xD0, 0x01, 0x00, 0x3E, 0xBF, 0x3F, 0xFE, 0x08, 0x23, 0xFE, 0x3C, 0x2F, 0x02, 0x40, 0x0F, + 0xF8, 0x01, 0xFF, 0x07, 0x07, 0x1B, 0x7C, 0x03, 0x1E, 0x02, 0x0F, 0x03, 0x0F, 0x47, 0x07, 0x8B, + 0x03, 0xCF, 0x03, 0xDF, 0x04, 0x78, 0x0B, 0x3C, 0x0F, 0x3D, 0x0F, 0x2D, 0x1E, 0x1F, 0x2D, 0x0F, + 0x3C, 0x0F, 0x7C, 0x0B, 0xB4, 0xB8, 0x3C, 0x0F, 0x2D, 0x2E, 0x0E, 0x07, 0xB8, 0x0F, 0x0F, 0x0B, + 0xAE, 0x03, 0xFC, 0x01, 0xF4, 0x02, 0xFC, 0x03, 0xFD, 0x0B, 0x5F, 0x1F, 0x0F, 0x00, 0x2E, 0x07, + 0x5E, 0x07, 0xFD, 0x02, 0xF8, 0x0D, 0x00, 0x07, 0xC0, 0x11, 0x10, 0x2D, 0x40, 0x07, 0x80, 0x03, + 0xC0, 0x02, 0x0D, 0x22, 0x03, 0xCB, 0x0B, 0x47, 0x0F, 0x02, 0x2D, 0x00, 0x38, 0xB4, 0x15, 0x1F, + 0x00, 0xBD, 0x0B, 0x03, 0xE0, 0x00, 0x0A, 0x90, 0x0B, 0x3C, 0xD0, 0x3C, 0xC0, 0x09, 0xFF, 0x3F, + 0x0B, 0x07, 0xFA, 0x0B, 0xC0, 0xBF, 0x40, 0x31, 0x00, 0xFC, 0x00, 0x7D, 0x0B, 0x03, 0x0A, 0x00, + 0x07, 0xFD, 0x18, 0x14, 0xEB, 0x0A, 0x01, 0xFD, 0x0A, 0xF0, 0x07, 0x09, 0x78, 0x03, 0xC0, 0x0B, + 0x1D, 0x21, 0x0A, 0x78, 0x1A, 0x6F, 0x8B, 0xB8, 0x1F, 0x48, 0x16, 0x36, 0x15, 0x18, 0xFC, 0x0F, + 0xE8, 0x1C, 0x4C, 0x1E, 0x22, 0x1E, 0x5A, 0xFF, 0x55, 0x0B, 0xFE, 0x3F, 0xBF, 0x0B, 0xFF, 0x7F, + 0xEB, 0xF8, 0x03, 0xF0, 0x3D, 0xBF, 0x3F, 0xEF, 0x3F, 0x3D, 0xF0, 0x7C, 0x07, 0x07, 0xFE, 0x2F, + 0xFF, 0x7D, 0x10, 0x0B, 0xF9, 0x3F, 0xAF, 0x7C, 0x03, 0xF8, 0x01, 0x08, 0x07, 0xF8, 0x2F, 0xBF, + 0x7C, 0x0B, 0xF4, 0x03, 0xFF, 0xFF, 0xFA, 0xAA, 0xF4, 0x08, 0xBF, 0xF0, 0x5F, 0x90, 0x1A, 0x07, + 0xFF, 0x1F, 0xAF, 0x3D, 0x03, 0x3C, 0x03, 0x3D, 0x07, 0x2F, 0xAF, 0x0B, 0xFE, 0x2D, 0x08, 0x01, + 0x40, 0x08, 0x20, 0x1D, 0x02, 0x2F, 0x3C, 0xBC, 0x3E, 0xF0, 0x3F, 0xF4, 0x3E, 0x7C, 0x3C, 0x2E, + 0x3A, 0x38, 0x02, 0x43, 0xFD, 0xEF, 0xBF, 0xFC, 0x07, 0xF4, 0x19, 0x07, 0xFD, 0x2F, 0xEF, 0x7C, + 0x03, 0x01, 0x06, 0x19, 0x0B, 0xF8, 0xB0, 0x0D, 0x38, 0xBD, 0x3F, 0xFC, 0x3F, 0x40, 0x0F, 0xFD, + 0xBE, 0xBE, 0x7F, 0x40, 0x1B, 0xF8, 0x00, 0xBE, 0x2A, 0xFF, 0xFF, 0x6A, 0xA9, 0x40, 0xF4, 0x0B, + 0xBE, 0xBF, 0x2F, 0xF6, 0x0F, 0x00, 0x3F, 0xEB, 0x3C, 0x90, 0x09, 0x7C, 0x00, 0x3F, 0xEF, 0x0B, + 0xFE, 0x18, 0xBC, 0x03, 0x3F, 0xAF, 0x0B, 0x0E, 0x7D, 0x0A, 0x14, 0x00, 0x2F, 0xFF, 0x3F, 0xFF, + 0x01, 0xBE, 0x5B, 0x2F, 0x02, 0x0E, 0x1E, 0xFC, 0x11, 0x07, 0x3C, 0x0C, 0x24, 0x06, 0x1E, 0x7C, + 0x03, 0x0C, 0x3D, 0xFF, 0x0E, 0x04, 0x0E, 0x1A, 0xFA, 0xBE, 0xBF, 0x0A, 0x17, 0x01, 0x00, 0x0B, + 0x49, 0xE1, 0xB0, 0x02, 0xF0, 0x03, 0x14, 0x19, 0x7A, 0x3F, 0x01, 0xFF, 0xE0, 0x10, 0x6B, 0x3D, + 0xA0, 0x6A, 0xB0, 0x47, 0x7D, 0x00, 0xFF, 0x03, 0xC7, 0x12, 0xBF, 0xFC, 0x5F, 0x94, 0x1A, 0x0F, + 0xB8, 0x07, 0x0F, 0x2D, 0x1E, 0x0F, 0x3D, 0x0F, 0x3C, 0x08, 0xF0, 0x0F, 0xF0, 0x1F, 0xF4, 0x2F, + 0x74, 0x3E, 0x7C, 0x75, 0x3C, 0xB4, 0x3C, 0xF0, 0x1D, 0xE0, 0x40, 0xB0, 0x80, 0xF0, 0xC0, 0xF0, + 0xC1, 0xE0, 0xD2, 0xD0, 0xE3, 0xC0, 0xF3, 0xC0, 0xB7, 0xBC, 0x0B, 0x3D, 0x0F, 0x2F, 0x3E, 0x0F, + 0xBC, 0x07, 0xF8, 0x0F, 0x7C, 0x0E, 0x78, 0x1E, 0x2E, 0x0F, 0x08, 0xFF, 0x56, 0x6F, 0x78, 0x01, + 0xF0, 0x03, 0xD0, 0x0B, 0x40, 0x10, 0x03, 0xC0, 0x0F, 0x80, 0xFE, 0x00, 0xBF, 0x40, 0x07, 0x10, + 0x15, 0x40, 0x0B, 0xC0, 0x01, 0xFC, 0x03, 0xF8, 0x0F, 0x0A, 0xD0, 0x3E, 0xFE, 0x20, 0x2F, 0x10, + 0x05, 0x21, 0xC0, 0x0F, 0x40, 0x7F, 0xFF, 0x6F, 0xAA, 0x0F, 0x00, 0x7F, 0xFE, 0x6F, 0xE9, 0x0B, + 0x0A, 0x54, 0x02, 0xFF, 0x02, 0xBE, 0xD0, 0x0B, 0x4D, 0xFF, 0x3E, 0xFB, 0x0D, 0xFF, 0x2E, 0xFB, + 0x3F, 0xC0, 0x35, 0x40, 0x0F, 0xE8, 0x03, 0x0F, 0x02, 0x2F, 0xEF, 0x0B, 0xFD, 0x18, 0x0B, 0xB8, + 0x18, 0x1E, 0xD0, 0x0F, 0xC0, 0x0F, 0x7F, 0x40, 0x09, 0x2F, 0x3E, 0x3D, 0x0F, 0xFC, 0x40, 0x0B, + 0x07, 0xB4, 0x03, 0xF0, 0x02, 0xF0, 0x02, 0xD0, 0x03, 0xC0, 0x07, 0x80, 0xAF, 0x40, 0x07, 0xF9, + 0x55, 0xFF, 0x10, 0x14, 0x24, 0x69, 0x07, 0xD0, 0x02, 0xFE, 0x00, 0x7F, 0x15, 0x1A, 0xB4, 0x18, + 0x0A, 0xF8, 0x0F, 0x07, 0x3C, 0x3C, 0x7C, 0xB4, 0xB4, 0xB0, 0xF0, 0xF0, 0x11, 0x00, 0x00, 0x2C, + 0x0B, 0x02, 0x0D, 0xF4, 0x1D, 0x1A, 0x38, 0xE0, 0x1E, 0xB4, 0x3C, 0x0B, 0x00, 0x0D, 0x00, 0x2C, + 0x01, 0x0E, 0x0F, 0x03, 0xFC, 0x01, 0x07, 0xFF, 0x1F, 0xEF, 0x40, 0x0B, 0x21, 0xBF, 0x03, 0xFE, + 0x0F, 0xFF, 0xFF, 0xFE, 0xAA, 0x1B, 0x06, 0x3F, 0xFF, 0x2A, 0xAF, 0x6A, 0x0B, 0x00, 0x1D, 0x1B, + 0x0F, 0x00, 0x2E, 0x1B, 0x0B, 0x0B, 0x1D, 0x1D, 0x10, 0x2E, 0x2E, 0x1A, 0x7D, 0xF4, 0x03, 0xFF, + 0x03, 0x47, 0x70, 0x0F, 0xFF, 0xCF, 0x1E, 0x0F, 0x0D, 0x03, 0xC0, 0x47, 0xC0, 0x8A, 0x07, 0x38, + 0x2C, 0x0F, 0xF0, 0x07, 0x42, 0x5D, 0x38, 0x2C, 0x3C, 0x3E, 0x1E, 0xBB, 0x0B, 0xEB, 0x00, 0x0E, + 0x00, 0x2C, 0x38, 0xEB, 0xE0, 0xEE, 0xB8, 0xBC, 0x3C, 0x80, 0x7A, 0xE0, 0xB0, 0x3C, 0x2F, 0x40, + 0x0F, 0xF8, 0x01, 0xFF, 0x00, 0x1F, 0x00, 0x07, 0x00, 0x03, 0x04, 0x01, 0x80, 0x03, 0x80, 0x2D, + 0x11, 0x1F, 0x00, 0x3E, 0x3D, 0x1F, 0x2C, 0x1F, 0x7C, 0x07, 0x6E, 0x78, 0x1D, 0x78, 0x1D, 0x78, + 0xB8, 0xF4, 0x1B, 0x78, 0x78, 0xF0, 0x1D, 0x0F, 0xD0, 0x3F, 0xF0, 0x1F, 0x1D, 0x3F, 0xFF, 0x0E, + 0xFF, 0xFC, 0x20, 0x0D, 0xCD, 0xC0, 0xDD, 0xC0, 0xB9, 0xC0, 0x71, 0x07, 0x1F, 0xFD, 0xBF, 0xBE, + 0x7F, 0x40, 0x1B, 0xF8, 0x00, 0xBE, 0x11, 0x24, 0x07, 0x80, 0x10, 0xFD, 0x2F, 0xEF, 0x7C, 0x03, + 0xF4, 0x01, 0xF0, 0xF4, 0x00, 0xF4, 0x01, 0x07, 0xF8, 0xAF, 0xBF, 0xFC, 0x0B, 0xFF, 0xFA, 0xAA, + 0x39, 0x38, 0x00, 0x74, 0x00, 0x3C, 0x3C, 0x2E, 0xB8, 0x0B, 0xF0, 0xF0, 0xBA, 0xE0, 0x2F, 0x07, + 0x7E, 0xAF, 0x3F, 0xC0, 0x0B, 0x80, 0x02, 0x1B, 0x0F, 0x80, 0x07, 0xFA, 0x00, 0x3C, 0x00, 0xFE, + 0xAA, 0x2C, 0x3E, 0xAA, 0xFF, 0xFF, 0xFA, 0xBE, 0xBF, 0xF8, 0x18, 0x2D, 0x00, 0x28, 0x1B, 0x7C, + 0x03, 0x3F, 0xAF, 0x0B, 0xFE, 0xFD, 0x00, 0xAF, 0xEF, 0x07, 0x42, 0x18, 0x0F, 0x0F, 0x0F, 0xF0, + 0x00, 0xB8, 0x02, 0x3C, 0xF0, 0x6B, 0x0B, 0x7F, 0x02, 0xFF, 0x03, 0x90, 0x3D, 0x3C, 0x09, 0x15, + 0xBE, 0xF9, 0x17, 0x03, 0x82, 0x03, 0xC3, 0x1A, 0x01, 0xF5, 0x07, 0xF8, 0x00, 0x1F, 0x40, 0x01, + 0x09, 0x2F, 0xD0, 0x15, 0xB0, 0xA8, 0x17, 0xFF, 0xFF, 0xAA, 0xAA, 0x90, 0x11, 0x07, 0xF4, 0x1F, + 0xAD, 0x3C, 0x10, 0x34, 0x19, 0xC0, 0xB5, 0xF0, 0x1E, 0xFF, 0x56, 0x6F, 0x78, 0x01, 0x0B, 0x40, + 0x10, 0x2E, 0x07, 0x5E, 0x07, 0xFD, 0x02, 0xF8, 0x3E, 0x2D, 0x3D, 0x11, 0x03, 0xFF, 0x0F, 0x0F, + 0x80, 0x07, 0xFB, 0x00, 0x03, 0xC0, 0x3F, 0xFF, 0x2B, 0xEA, 0x0E, 0x18, 0x00, 0x2E, 0xFE, 0x0B, + 0xEF, 0x0F, 0x03, 0x0E, 0x01, 0x0F, 0x03, 0x0B, 0xEB, 0x1F, 0xFF, 0x90, 0x00, 0x07, 0x02, 0xEB, + 0x00, 0xFF, 0x0B, 0xFF, 0x06, 0xBE, 0x00, 0x3C, 0x0F, 0xFF, 0xD0, 0x01, 0x1D, 0x3F, 0xD0, 0x7E, + 0xFC, 0xF0, 0x2F, 0xF0, 0x0F, 0xBD, 0x0F, 0x2F, 0xFD, 0x02, 0xFD, 0x14, 0x1B, 0x07, 0x2C, 0x1F, + 0x34, 0x3C, 0x34, 0x3C, 0x30, 0x2C, 0x1F, 0x0D, 0x07, 0xF8, 0x70, 0xA8, 0x38, 0x00, 0x1C, 0x0C, + 0x00, 0x1C, 0x9C, 0x38, 0xFC, 0x74, 0xF0, 0x3D, 0xB0, 0x38, 0xF0, 0x2F, 0x1B, 0x01, 0x81, 0x03, + 0x87, 0x0F, 0x0F, 0x2E, 0x2E, 0x2D, 0x3D, 0x0F, 0x80, 0x0F, 0x3F, 0xFF, 0x2A, 0x0A, 0x3F, 0x11, + 0x0D, 0x3F, 0x2C, 0x3E, 0x34, 0x38, 0x34, 0x3D, 0x30, 0x3F, 0x34, 0x3E, 0x2C, 0x38, 0x0D, 0x3C, + 0xE0, 0x70, 0xF8, 0x38, 0x38, 0x1C, 0xF4, 0x0C, 0xE0, 0x1C, 0xF0, 0x1C, 0xF4, 0x38, 0x3C, 0x2F, + 0x38, 0x07, 0x3C, 0x0F, 0x1F, 0xAE, 0x07, 0x18, 0x3F, 0xFF, 0x1A, 0xBE, 0xF4, 0x00, 0xA0, 0x06, + 0xF0, 0x02, 0xD0, 0x07, 0x40, 0x2D, 0x00, 0xB5, 0x40, 0xFF, 0x19, 0xF9, 0x55, 0x0B, 0x4B, 0x11, + 0xFF, 0x0E, 0x0B, 0x80, 0x3F, 0xA0, 0x0B, 0x07, 0x0F, 0x16, 0x0F, 0xFA, 0xBF, 0x1A, 0x1E, 0x07, + 0x40, 0x01, 0xF5, 0x00, 0x6F, 0xD0, 0x1F, 0x80, 0x3A, 0x07, 0x87, 0x02, 0x82, 0x2A, 0x10, 0x40, + 0xF4, 0xE0, 0x5F, 0x3B, 0x1F, 0x10, 0x1A, 0x18, 0x2A, 0x7F, 0xC0, 0xA5, 0x1A, 0x2F, 0x49, 0xFF, + 0x2F, 0xFF, 0xFC, 0x5C, 0x00, 0x4C, 0x69, 0x07, 0x40, 0x2F, 0x80, 0x27, 0x19, 0x0B, 0xC0, 0x2D, + 0xE0, 0x38, 0x49, 0x0B, 0x00, 0x0E, 0x0B, 0x2C, 0x0A, 0x03, 0x80, 0x0B, 0x00, 0x1E, 0x31, 0x02, + 0xE0, 0x7C, 0x7E, 0x00, 0x04, 0x0F, 0x3D, 0x00, 0xFF, 0x03, 0xC7, 0x0B, 0xF4, 0x47, 0xBE, 0x30, + 0x0A, 0x0F, 0x7D, 0x00, 0xD7, 0x00, 0xE7, 0x14, 0xFF, 0xFC, 0xFA, 0xA8, 0x0B, 0x6F, 0x03, 0xFE, + 0x0B, 0xF8, 0x00, 0xBC, 0x00, 0x04, 0x10, 0xF0, 0x3F, 0xC0, 0x05, 0x95, 0xF0, 0xBF, 0x1C, 0x19, + 0x01, 0x00, 0x0F, 0xFF, 0x07, 0xFF, 0x00, 0x13, 0x00, 0x03, 0x5C, 0x02, 0x2C, 0x13, 0x25, 0x80, + 0x13, 0x70, 0x38, 0xB0, 0x2C, 0xE0, 0x0F, 0x1B, 0x24, 0x24, 0x2D, 0x2D, 0x0F, 0x0F, 0x07, 0xC7, + 0x07, 0x87, 0x0F, 0x04, 0x81, 0x03, 0x83, 0x03, 0x8B, 0x03, 0x8E, 0x74, 0x74, 0xD1, 0xE0, 0x83, + 0xE0, 0x4E, 0xE0, 0x1C, 0xE0, 0x34, 0xE0, 0xF0, 0xE0, 0x87, 0x03, 0x8F, 0x03, 0xAC, 0x03, 0xB8, + 0x00, 0xF0, 0x01, 0xD0, 0x7F, 0xC0, 0xB9, 0x0B, 0xB2, 0x95, 0xF7, 0xBF, 0xCB, 0x3C, 0x00, 0xF1, + 0xE0, 0xD3, 0xE0, 0x8E, 0x06, 0x80, 0x0B, 0xC0, 0x07, 0x0F, 0x10, 0xEB, 0x02, 0xD7, 0x03, 0xC3, + 0x07, 0x82, 0xFF, 0x2F, 0xAA, 0x3C, 0xE0, 0xF4, 0x00, 0xB8, 0x02, 0x4A, 0x1E, 0x3D, 0xB4, 0x03, + 0xC0, 0x07, 0x80, 0x0F, 0xFF, 0x1F, 0xAA, 0x2D, 0x02, 0x1F, 0x00, 0x2E, 0x3E, 0x40, 0x2F, 0x3D, + 0x02, 0x3F, 0xEF, 0x3F, 0xFD, 0x0F, 0x03, 0x38, 0x1C, 0x0F, 0xFD, 0x6F, 0x2D, 0x2D, 0x28, 0x17, + 0x01, 0xF8, 0x55, 0xE4, 0x08, 0xC0, 0x07, 0x40, 0x00, 0xB5, 0x10, 0xFF, 0x0A, 0x02, 0x0E, 0x3E, + 0xF4, 0x01, 0x7F, 0xBF, 0x1F, 0x11, 0x78, 0x1F, 0xF8, 0x3C, 0xF0, 0x00, 0xFA, 0xA8, 0xFF, 0xFC, + 0x09, 0xC0, 0x07, 0xFE, 0x00, 0xBF, 0x07, 0x3F, 0x00, 0xC0, 0x0C, 0x3F, 0xFF, 0x3E, 0xC0, 0x06, + 0x16, 0x01, 0xF4, 0x03, 0xFD, 0x16, 0x0E, 0x0B, 0x0F, 0x16, 0xB8, 0xBF, 0xE0, 0x1F, 0x07, 0xF0, + 0xBF, 0xF0, 0x14, 0x0F, 0x40, 0x3F, 0xC0, 0xF1, 0x16, 0xE0, 0xB0, 0xF0, 0x16, 0x3E, 0xAF, 0x40, + 0x03, 0x3F, 0x00, 0x3F, 0x04, 0x2E, 0x03, 0xFE, 0x06, 0xBC, 0x07, 0x07, 0xD0, 0x07, 0x00, 0xD1, + 0x0C, 0xF8, 0x06, 0x3C, 0x0A, 0x0B, 0xF7, 0xC0, 0xBF, 0x40, 0x2F, 0x02, 0xF8, 0x08, 0x07, 0x7D, + 0x06, 0x03, 0xC3, 0x03, 0x83, 0x07, 0x01, 0x06, 0x7E, 0x10, 0x7C, 0xFF, 0xF0, 0xBE, 0xA0, 0x3D, + 0x1F, 0xE0, 0x3C, 0xF0, 0x3C, 0x78, 0x3C, 0x3D, 0x3C, 0x1F, 0x3C, 0x0B, 0x3C, 0x03, 0x3C, 0x02, + 0x4F, 0x00, 0xCF, 0x00, 0xEF, 0x01, 0x3C, 0x03, 0xD0, 0x02, 0x01, 0x03, 0x28, 0x4A, 0x14, 0x1E, + 0x01, 0x0B, 0x47, 0x02, 0xEE, 0x00, 0xBC, 0x01, 0xEE, 0x07, 0x8B, 0x1E, 0x02, 0x04, 0x80, 0x00, + 0x3D, 0x01, 0x0F, 0x3D, 0x2E, 0x3E, 0x3C, 0x1F, 0xF4, 0x3F, 0xD0, 0xF6, 0xF0, 0xE1, 0xF0, 0xC0, + 0xF0, 0x40, 0x05, 0x8C, 0x00, 0x3E, 0xAA, 0x3F, 0xFF, 0x04, 0x86, 0x40, 0xBF, 0x0B, 0x8C, 0x3C, + 0x01, 0x3E, 0xAF, 0xFC, 0x0A, 0xFF, 0x00, 0x7F, 0x08, 0x80, 0x07, 0xFA, 0xF8, 0xC9, 0x1E, 0x0F, + 0xE0, 0x07, 0xFE, 0x0F, 0xBF, 0x01, 0x17, 0xEB, 0x02, 0xF8, 0x06, 0x82, 0x03, 0x3D, 0xF0, 0x00, + 0xB8, 0x02, 0x3C, 0xE0, 0x0A, 0x3F, 0xFE, 0x16, 0x06, 0xFF, 0x1F, 0xEB, 0xE0, 0x06, 0x0B, 0x80, + 0x03, 0xD0, 0x01, 0x19, 0x17, 0xF4, 0x03, 0xFC, 0x16, 0x03, 0xD0, 0x0B, 0xFF, 0x0D, 0x0E, 0x14, + 0x5C, 0x03, 0x9C, 0x01, 0x3A, 0x2D, 0x7E, 0x1C, 0x10, 0x17, 0xC7, 0x0D, 0x82, 0xC0, 0xC3, 0x19, + 0x00, 0x00, 0x0A, 0x42, 0x07, 0xFF, 0x07, 0xFD, 0x0B, 0x80, 0x08, 0x1E, 0x07, 0x0F, 0x0F, 0x0B, + 0x5E, 0x03, 0xFD, 0x02, 0xF8, 0x0E, 0x3E, 0xBF, 0x03, 0x3E, 0xBF, 0x3F, 0xFE, 0x04, 0x3C, 0x07, + 0x3D, 0x2F, 0x80, 0x00, 0x1B, 0xFE, 0x2F, 0xBF, 0x03, 0x0B, 0xFF, 0x7F, 0xEB, 0xF8, 0x03, 0xF0, + 0xFE, 0x3F, 0x1B, 0x1E, 0x1B, 0x1D, 0x4E, 0x0B, 0xFD, 0x3F, 0xAF, 0x97, 0x1F, 0xE0, 0xBE, 0xFD, + 0xF0, 0x2E, 0xD0, 0x0F, 0xFF, 0xFF, 0xEA, 0xAA, 0xD0, 0x01, 0x07, 0xFE, 0x2F, 0xFF, 0x7D, 0x10, + 0x07, 0xF8, 0x2F, 0xBF, 0x7C, 0x0B, 0xF4, 0x03, 0xFF, 0xFF, 0xFA, 0xAA, 0xF4, 0x40, 0x04, 0x07, + 0xFC, 0x1C, 0x0B, 0x1D, 0x1E, 0x16, 0x78, 0xFE, 0x3F, 0xEF, 0xBC, 0x03, 0xF4, 0xF4, 0x01, 0xC0, + 0x02, 0x3C, 0xFF, 0x3F, 0xEF, 0x3F, 0x01, 0x80, 0x06, 0x15, 0x06, 0x0F, 0x40, 0x3C, 0xFA, 0x3C, + 0x3C, 0xE0, 0x0A, 0x0B, 0xBE, 0xBF, 0x2F, 0xF6, 0x0A, 0xDF, 0x0F, 0xBE, 0xBD, 0x2F, 0xF4, 0x00, + 0xFF, 0xBE, 0x2F, 0xFD, 0x08, 0x7C, 0x00, 0x3F, 0xEF, 0x0B, 0xFE, 0x00, 0xB0, 0x7C, 0x00, 0x3C, + 0x03, 0x0F, 0x7D, 0x0A, 0x7E, 0x1F, 0x7D, 0xBC, 0x03, 0x3F, 0xAF, 0xD0, 0x0B, 0x00, 0x1D, 0x02, + 0x7C, 0xD6, 0x2B, 0x08, 0x1F, 0x0B, 0x00, 0x0F, 0x03, 0xC7, 0x16, 0xC2, 0x03, 0x83, 0x0D, 0x1D, + 0x17, 0x2C, 0xFF, 0x2E, 0x07, 0xFD, 0x2F, 0xEF, 0x7C, 0x10, 0x01, 0x1C, 0x1E, 0x1D, 0x00, 0x50, + 0x7F, 0xFF, 0x6A, 0xAA, 0x90, 0x06, 0xFF, 0x0F, 0xF4, 0x3F, 0xF0, 0x7C, 0xF0, 0xF0, 0xF6, 0xE0, + 0xFB, 0xC1, 0x0A, 0x99, 0xF0, 0x03, 0xF0, 0x03, 0x78, 0x0B, 0x3C, 0x0F, 0x3C, 0x0F, 0x1E, 0x2E, + 0x0F, 0x3C, 0x0B, 0x3C, 0x08, 0xBF, 0x3D, 0x00, 0x07, 0x18, 0xFF, 0x3D, 0x7C, 0x03, 0x2F, 0xE0, + 0x0D, 0xAE, 0x1E, 0x7F, 0x3F, 0xFE, 0x34, 0x17, 0x3D, 0x03, 0x2F, 0xEF, 0x0B, 0x0E, 0x7E, 0x07, + 0xB4, 0x03, 0xF0, 0x02, 0xF0, 0x02, 0xD0, 0x03, 0xC0, 0x07, 0x80, 0xAF, 0x40, 0x09, 0x3F, 0x00, + 0x3F, 0xEB, 0x3D, 0xFF, 0xF8, 0x06, 0x22, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x88, 0x00, +}; + +// stuff from the game that we call. source: rb3 decompilation +#define OS_BUS_CLOCK_SPEED (*(uint32_t *)0x800000F8) +#define OS_TIME_SPEED (OS_BUS_CLOCK_SPEED / 4) +#define OSMillisecondsToTicks(x) ((x) * (OS_TIME_SPEED / 1000)) +typedef struct _GXColor { + uint8_t r, g, b, a; +} GXColor; +typedef struct OSContext { + uint32_t gprs[32]; // at 0x0 + uint32_t cr; // at 0x80 + uint32_t lr; // at 0x84 + uint32_t ctr; // at 0x88 + uint32_t xer; // at 0x8C + double fprs[32]; // at 0x90 + uint32_t fpscr_pad; // at 0x190 + uint32_t fpscr; // at 0x194 + uint32_t srr0; // at 0x198 + uint32_t srr1; // at 0x19C + uint16_t mode; // at 0x1A0 + uint16_t state; // at 0x1A2 + uint32_t gqrs[8]; // at 0x1A4 + uint32_t psf_pad; // at 0x1C4 + double psfs[32]; // at 0x1C8 +} OSContext; +typedef enum _OSErrorType { + OS_ERR_SYSTEM_RESET, + OS_ERR_MACHINE_CHECK, + OS_ERR_DSI, + OS_ERR_ISI, + OS_ERR_EXT_INTERRUPT, + OS_ERR_ALIGNMENT, + OS_ERR_PROGRAM, + OS_ERR_FP_UNAVAIL, + OS_ERR_DECREMENTER, + OS_ERR_SYSTEM_CALL, + OS_ERR_TRACE, + OS_ERR_PERF_MONITOR, + OS_ERR_IABR, + OS_ERR_SMI, + OS_ERR_THERMAL_INT, + OS_ERR_PROTECTION, + OS_ERR_FP_EXCEPTION, + OS_ERR_MAX +} OSErrorType; +void OSFatal(GXColor textColor, GXColor bgColor, const char* msg); +void *OSSetErrorHandler(uint16_t error, void *handler); +void PPCHalt(); +void OSReturnToMenu(); +uint64_t OSGetTime(); + +char RB3E_ExceptionHandlerEntered = 0; +char RB3E_ExceptionText[0x600]; +char RB3E_ExceptionFormatBuffer[128]; + +void RB3EBase(); + +static inline char IsAddressValid(uint32_t address) { + // 64MB MEM1 + if (address >= 0x80000000 && address < 0x84000000) + return 1; + // 256MB MEM2 + if (address >= 0x90000000 && address < 0xA0000000) + return 1; + return 0; +} + +// this is a function that gets called after OSFatal writes to the screen +#define REBOOT_SECONDS 30 +static void ReplaceHaltAfterFatal() { + for (int i = REBOOT_SECONDS; i > 0; i--) { + RB3E_PRINT("Restarting in %i seconds...\n", i); + uint64_t start = OSGetTime(); + do { + } while (OSGetTime() - start < OSMillisecondsToTicks(1000)); + } + // if we have the HBC stub, use that + if (*(uint64_t *)0x80001804 == 0x5354554248415858ull) { // detect 'STUBHAXX' + RB3E_DEBUG("Using HBC stub\n", NULL); + //*(uint64_t *)0x80002F00 = 0x4c4f41444b544858ull; // 'LOADKTHX' + //*(uint64_t *)0x80002F08 = 0x0000000100000002ull; // system menu + ((void(*)(void))0x80001800)(); + } + // this doesn't work... + // TODO(Emma): rig up manual IPC to reload to the Wii Menu in lieu of STUBHAXX + OSReturnToMenu(); +} + +void RB3E_ErrorOnException(uint8_t error, OSContext *ctx, uint32_t dsisr, uint32_t dar) { + // if we've already entered our exception handler, we must've crashed *in* our handler.. oops + // so we just immediately crash + if (RB3E_ExceptionHandlerEntered) + PPCHalt(); + RB3E_ExceptionHandlerEntered = 1; + + RB3E_PRINT("--== RB3ENHANCED EXCEPTION ==--\n"); + + // build out the exception text + RB3E_ExceptionText[0] = '\0'; + strcat(RB3E_ExceptionText, "Rock Band 3 has crashed. Restarting in 30 seconds...\n" "If this happens again, report to the RB3Enhanced developers!\n\n"); +#define fmtExc(...) { sprintf(RB3E_ExceptionFormatBuffer, __VA_ARGS__); strcat(RB3E_ExceptionText, RB3E_ExceptionFormatBuffer); printf(RB3E_ExceptionFormatBuffer); } + // print out the RB3E version and base address + fmtExc("RB3Enhanced " RB3E_BUILDTAG " @ 0x%08X\n", (uint32_t)&RB3EBase); + + // print info about the exception + if (error == OS_ERR_DSI) { // data storage exception - we want to know the instruction (SRR0), address (DAR) and info (DSISR) + fmtExc("Data Error: %08X %08X %08X\n", ctx->srr0, dar, dsisr); + } else if (error == OS_ERR_ISI) { // instruction storage exception - we want to know the attempted addy (SRR0) + fmtExc("Instruction Error: %08X\n", ctx->srr0); + } else if (error == OS_ERR_ALIGNMENT) { // alignment exception - we want to know the instruction (SRR0) and address (DAR) + fmtExc("Alignment Error: %08X %08X\n", ctx->srr0, dar); + } else if (error == OS_ERR_PROGRAM) { // program exception - god help us all + fmtExc("Program Error: %08X probably??\n", ctx->srr0); + } else { + fmtExc("Error %i: %08X", error, ctx->srr0); + } + + // print out all the registers + for (int i = 0; i < 32; i++) { + if (i < 10) { + fmtExc("R%i:%08X ", i, ctx->gprs[i]); + } else { + fmtExc("%i:%08X ", i, ctx->gprs[i]); + } + // split every 4 registers + if ((i % 4) == 3) fmtExc("\n"); + } + fmtExc("LR:%08X PC:%08X CR:%08X MSR:%08X\n", ctx->lr, ctx->srr0, ctx->cr, ctx->srr1); + + // walk back the stack + fmtExc("Stack: "); + int iters = 0; + int iterating = 1; + uint32_t *sp = (uint32_t *)ctx->gprs[1]; + do { + // if the stack pointer isn't valid, break out + if (!IsAddressValid((uint32_t)sp)) { + iterating = 0; + break; + } + fmtExc("%08X ", sp[1]); + sp = (uint32_t *)sp[0]; + // start a new line after 4 stack addresses + // break after 9 or after the address is invalid + if (iters == 4) { + fmtExc("\n"); + } else if (iters == 9 || !IsAddressValid((uint32_t)sp)) { + iterating = 0; + break; + } + } while (iterating == 1); + + RB3E_PRINT("\n--== END EXCEPTION ==--\n"); + +#undef fmtExc + + GXColor bl = {127, 0, 40, 255}; + GXColor wh = {255, 255, 255, 255}; + OSFatal(wh, bl, RB3E_ExceptionText); +} + +int OSReadROMReplacement(void *dst, int size, int src) { + src -= 0x1FCF00; // offset of the ANSI font in the IPL + memcpy(dst, dolphin_osfatal_font + src, size); + return 1; +} + +void RB3E_InstallWiiExceptionHandler() { + // register our functions + POKE_B(OSFatal, PORT_OSFATAL); + POKE_B(OSSetErrorHandler, PORT_OSSETERRORHANDLER); + POKE_B(PPCHalt, PORT_PPCHALT); + POKE_B(OSReturnToMenu, PORT_OSRETURNTOMENU); + // clear instruction cache probably + uint32_t addr = (((uint32_t)OSFatal) - 0x1f) & ~0x20; + DCFlushRange((void *)(addr), 0x80); + ICInvalidateRange((void *)(addr), 0x80); + // install the exception handler + OSSetErrorHandler(OS_ERR_MACHINE_CHECK, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_DSI, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_ISI, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_ALIGNMENT, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_PROGRAM, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_SYSTEM_CALL, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_IABR, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_SMI, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_THERMAL_INT, RB3E_ErrorOnException); + OSSetErrorHandler(OS_ERR_PROTECTION, RB3E_ErrorOnException); + // hack OSFatal-Halt routine to be more favorable + POKE_32(PORT_OSFATAL_HALT_X_OFFSET, LI(7, 30)); // x offset for text rendering + POKE_32(PORT_OSFATAL_HALT_Y_OFFSET, LI(8, 20)); // y offset for text rendering + POKE_32(PORT_SCREENREPORT_X_NEWLINE, 0x3bc4FFE1); // x offset for text rendering newline cutoff (-30) + POKE_32(PORT_OSFATAL_HALT_OSREPORT, NOP); // call to OSReport + POKE_B(PORT_OSFATAL_HALT_PPCHALT, ReplaceHaltAfterFatal); // call to PPCHalt + // hook __OSReadROM + POKE_B(PORT_OSREADROM, OSReadROMReplacement); + RB3E_DEBUG("Installed exception handler!", NULL); +} + +#endif From 44e63d1914ede55823cdbe8a3e6be6095eebf378 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 10 May 2025 19:37:16 +0100 Subject: [PATCH 032/123] [bank8] log fatal errors and dont restart after crashes --- source/wii_exceptions.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/source/wii_exceptions.c b/source/wii_exceptions.c index 9e6fd68..44e7df6 100644 --- a/source/wii_exceptions.c +++ b/source/wii_exceptions.c @@ -522,7 +522,11 @@ void RB3E_ErrorOnException(uint8_t error, OSContext *ctx, uint32_t dsisr, uint32 // build out the exception text RB3E_ExceptionText[0] = '\0'; +#ifndef RB3E_WII_BANK8 strcat(RB3E_ExceptionText, "Rock Band 3 has crashed. Restarting in 30 seconds...\n" "If this happens again, report to the RB3Enhanced developers!\n\n"); +#else + strcat(RB3E_ExceptionText, "Rock Band 3 has crashed. (DEBUG BUILD)\n\n"); +#endif #define fmtExc(...) { sprintf(RB3E_ExceptionFormatBuffer, __VA_ARGS__); strcat(RB3E_ExceptionText, RB3E_ExceptionFormatBuffer); printf(RB3E_ExceptionFormatBuffer); } // print out the RB3E version and base address fmtExc("RB3Enhanced " RB3E_BUILDTAG " @ 0x%08X\n", (uint32_t)&RB3EBase); @@ -615,8 +619,10 @@ void RB3E_InstallWiiExceptionHandler() { POKE_32(PORT_OSFATAL_HALT_X_OFFSET, LI(7, 30)); // x offset for text rendering POKE_32(PORT_OSFATAL_HALT_Y_OFFSET, LI(8, 20)); // y offset for text rendering POKE_32(PORT_SCREENREPORT_X_NEWLINE, 0x3bc4FFE1); // x offset for text rendering newline cutoff (-30) +#ifndef RB3E_WII_BANK8 POKE_32(PORT_OSFATAL_HALT_OSREPORT, NOP); // call to OSReport POKE_B(PORT_OSFATAL_HALT_PPCHALT, ReplaceHaltAfterFatal); // call to PPCHalt +#endif // hook __OSReadROM POKE_B(PORT_OSREADROM, OSReadROMReplacement); RB3E_DEBUG("Installed exception handler!", NULL); From f7c37fff78c0527734233d945d49334de965950c Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sun, 11 May 2025 21:32:42 +0100 Subject: [PATCH 033/123] [wii] be slightly more mindful of CPU cache --- include/rb3/UsbWii.h | 52 +++++++++++++++++++++++++++++++++++++---- include/rb3enhanced.h | 7 ++++++ source/_functions.c | 5 ++-- source/rb3enhanced.c | 1 + source/wii.c | 7 ++++++ source/wii_exceptions.c | 2 +- source/wii_usbhid.c | 6 ++--- source/xbox360.c | 7 +++++- 8 files changed, 75 insertions(+), 12 deletions(-) diff --git a/include/rb3/UsbWii.h b/include/rb3/UsbWii.h index cf0b5c3..b82514a 100644 --- a/include/rb3/UsbWii.h +++ b/include/rb3/UsbWii.h @@ -3,12 +3,56 @@ #ifdef RB3E_WII typedef struct _HIDDevice { - int deviceID; - unsigned short vendorID; - unsigned short productID; - int token; + int fd; + unsigned short vid; + unsigned short pid; + unsigned char iInterface; + unsigned char endpoint_address_in; + unsigned char endpoint_address_out; + unsigned char inst; + void *p_hdd; + void *p_hcd; + void *p_hid; + void *p_hed0; + void *p_hed1; } HIDDevice; +typedef struct _UsbDevice { + HIDDevice *dd; // 0x0 + unsigned int type; // 0x4 + unsigned int state; // 0x8 + unsigned int flags; // 0xC + unsigned int inactivity; // 0x10 + unsigned char unk_0x14[0x2]; // 0x14 + short ledNum; // 0x16 + unsigned int unk6; // 0x18 + unsigned int buttonMask; // 0x1C + unsigned short lstickX; // 0x20 + unsigned short lstickY; // 0x22 + unsigned short rstickX; // 0x24 + unsigned short rstickY; // 0x26 + unsigned char pressures[4]; // 0x28 + unsigned char extended[0x10]; // 0x2C + unsigned char unk_0x3C[0x4]; // 0x3C + unsigned char packet[0x40]; // 0x40 + unsigned char unk_0x80[0x80]; // 0x80 +} UsbDevice; // 0x100 + +typedef enum _UsbType { + kUsbNone = 0, + kUsbGuitar = 1, + kUsbGuitarRb2 = 2, + kUsbDrums = 3, + kUsbDrumsRb2 = 4, + kUsbMidiGuitarMustang = 5, + kUsbMidiGuitarSquire = 6, + kUsbMidiKeyboard = 7, + kUsbUnused = 8, + kUsbMidiKeyboardMpa = 9, + kUsbMidiDrums = 10, + kUsbTypeMax = 11, +} UsbType; + extern int UsbWiiGetType(HIDDevice *device); #endif // RB3E_WII diff --git a/include/rb3enhanced.h b/include/rb3enhanced.h index 10b30af..e1658d0 100644 --- a/include/rb3enhanced.h +++ b/include/rb3enhanced.h @@ -27,6 +27,13 @@ void RB3E_CloseFile(int file); int RB3E_CreateThread(void *address, void *arg, int stack_size); void RB3E_Sleep(int ms); int RB3E_RelaunchGame(); +void RB3E_FlushCache(void * address, unsigned int size); + +// stub function at the start of the .text segment - doubles as the start of _functions.c +void RB3EBase(); + +// stub function at the end of _functions.c after all stubs are made +void RB3EStubEnd(); // loaded song count - done in SongHooks.c extern int RB3E_LoadedSongCount; diff --git a/source/_functions.c b/source/_functions.c index 17f1697..59fdea6 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -25,10 +25,7 @@ } #endif -#ifndef RB3E_XBOX RB3E_STUB(RB3EBase); // this will always be the start of .text -// Xbox is loaded at a fixed address unlike Wii/PS3/PC -#endif // function stub definitions #ifndef RB3E_WII @@ -120,3 +117,5 @@ RB3E_STUB(OSSetErrorHandler) RB3E_STUB(PPCHalt) RB3E_STUB(OSReturnToMenu) #endif + +RB3E_STUB(RB3EStubEnd); diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 2ce444c..da93d82 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -351,6 +351,7 @@ void InitialiseFunctions() POKE_B(&BinstreamRead, PORT_BINSTREAMREAD); POKE_B(&BinstreamWriteEndian, PORT_BINSTREAMWRITEENDIAN); POKE_B(&BinstreamReadEndian, PORT_BINSTREAMREADENDIAN); + RB3E_FlushCache((void *)RB3EBase, (unsigned int)RB3EStubEnd - (unsigned int)RB3EBase); RB3E_MSG("Functions initialized!", NULL); } diff --git a/source/wii.c b/source/wii.c index 78a88b3..95d6444 100644 --- a/source/wii.c +++ b/source/wii.c @@ -83,6 +83,13 @@ int RB3E_CreateThread(void *address, void *arg, int stack_size) return -1; } +void RB3E_FlushCache(void * address, unsigned int size) { + unsigned int alignedAddress = ((unsigned int)address & ~0x1F); + unsigned int alignedSize = ((unsigned int)address & ~0x1F) + 0x20; + DCFlushRange((void *)alignedAddress, alignedSize); + ICInvalidateRange((void *)alignedAddress, alignedSize); +} + int RB3E_RelaunchGame() { /* diff --git a/source/wii_exceptions.c b/source/wii_exceptions.c index 44e7df6..b1dbde7 100644 --- a/source/wii_exceptions.c +++ b/source/wii_exceptions.c @@ -601,7 +601,7 @@ void RB3E_InstallWiiExceptionHandler() { POKE_B(PPCHalt, PORT_PPCHALT); POKE_B(OSReturnToMenu, PORT_OSRETURNTOMENU); // clear instruction cache probably - uint32_t addr = (((uint32_t)OSFatal) - 0x1f) & ~0x20; + uint32_t addr = (((uint32_t)OSFatal)) & ~0x1F; DCFlushRange((void *)(addr), 0x80); ICInvalidateRange((void *)(addr), 0x80); // install the exception handler diff --git a/source/wii_usbhid.c b/source/wii_usbhid.c index df5893b..41fc808 100644 --- a/source/wii_usbhid.c +++ b/source/wii_usbhid.c @@ -13,9 +13,9 @@ int UsbWiiGetTypeHook(HIDDevice *device) { int r = 0; - if (device->vendorID == 0x12BA) + if (device->vid == 0x12BA) { // sony computer entertainment - switch (device->productID) + switch (device->pid) { case 0x0100: // gh guitar case 0x0200: // rb guitar @@ -31,7 +31,7 @@ int UsbWiiGetTypeHook(HIDDevice *device) { r = UsbWiiGetType(device); } - RB3E_DEBUG("Device connected: 0x%04x 0x%04x - type %i", device->vendorID, device->productID, r); + RB3E_DEBUG("Device connected: 0x%04x 0x%04x - type %i", device->vid, device->pid, r); return r; } diff --git a/source/xbox360.c b/source/xbox360.c index 15701a6..1f362ec 100644 --- a/source/xbox360.c +++ b/source/xbox360.c @@ -70,11 +70,16 @@ int RB3E_CreateThread(void *address, void *arg, int stack_size) int RB3E_RelaunchGame() { - // no idea if this actually works + // we should probably get the current exec name from the kernel XLaunchNewImage("default.xex", 0); return 0; } +void RB3E_FlushCache(void * address, unsigned int size) +{ + // TODO(Emma): clear dcache (and icache if possible) on xbox +} + static void CTHook(void *ThisApp, int argc, char **argv) { // initialise hooks for XeCrypt From 2fa1871270ef7292af974ec56793832c6169454b Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Mon, 12 May 2025 01:53:52 +0100 Subject: [PATCH 034/123] [wii] FileSD backend for SD cards --- include/FileSD.h | 25 +++++ include/ports_wii.h | 2 + include/ports_wii_bank8.h | 2 + include/rb3/File.h | 53 ++++++++++- include/rb3e_include.h | 1 + source/FileSD.c | 193 ++++++++++++++++++++++++++++++++++++++ source/_functions.c | 1 + source/rb3enhanced.c | 19 ++++ 8 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 include/FileSD.h create mode 100644 source/FileSD.c diff --git a/include/FileSD.h b/include/FileSD.h new file mode 100644 index 0000000..e7b06d1 --- /dev/null +++ b/include/FileSD.h @@ -0,0 +1,25 @@ +#ifdef RB3E_WII + +#include +#include "rb3/File.h" + +typedef struct _FileSD +{ + File_vtable *vtable; // 0x0 + + char *mFilePath; + char mWriteEnabled; + char mFailed; + int mFd; + FILE_STRUCT mFileStruct; + int mFilesize; + int mCurrentPosition; + int mLastBytesRead; + int mLastBytesWrote; + +} FileSD; + +void FileSD_InitVtable(); +FileSD *FileSD_New(const char *filepath, int flags); + +#endif diff --git a/include/ports_wii.h b/include/ports_wii.h index dfe6b0e..842265b 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -100,6 +100,8 @@ #define PORT_DATAONELEM 0x8031dc40 // DataOnElem #define PORT_HEAPINIT 0x80352cbc // Heap::Init #define PORT_DATAREGISTERFUNC 0x8031b2b8 // DataRegisterFunc +#define PORT_FILEISLOCAL 0x802fb548 // FileIsLocal +#define PORT_FILEISDLC 0x802fb54c // FileIsDLC // instance addresses #define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index 5d13a0c..faccd3b 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -102,6 +102,8 @@ #define PORT_DATAONELEM 0x80457bf0 // DataOnElem #define PORT_HEAPINIT 0x8049f030 // Heap::Init #define PORT_DATAREGISTERFUNC 0x804545e0 // DataRegisterFunc +#define PORT_FILEISLOCAL 0x80422560 // FileIsLocal +#define PORT_FILEISDLC 0x80422570 // FileIsDLC // instance addresses #define PORT_MODIFIERMGR_POINTER 0x80c904a8 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80c91818 // address of RockCentralGateway diff --git a/include/rb3/File.h b/include/rb3/File.h index ac251e2..8a21686 100644 --- a/include/rb3/File.h +++ b/include/rb3/File.h @@ -23,7 +23,14 @@ typedef char (*FileWriteDone_t)(File *thisFile, int *oBytes); typedef struct _File_vtable { +#ifdef RB3E_WII + void *rtti; + void *null; +#endif FileDestructor_t destructor; +#ifdef RB3E_PS3 + FileDestructor_t destructor2; +#endif FileFilename_t Filename; FileRead_t Read; FileReadAsync_t ReadAsync; @@ -40,12 +47,56 @@ typedef struct _File_vtable FileWriteDone_t WriteDone; } File_vtable; -struct _File +struct File { File_vtable *vtable; }; +typedef struct _AsyncFile AsyncFile; + +typedef void (*AsyncFile_OpenAsync_t)(AsyncFile *thisFile); +typedef char (*AsyncFile_OpenDone_t)(AsyncFile *thisFile); +typedef void (*AsyncFile_WriteAsync_t)(AsyncFile *thisFile, void *iData, int iBytes); +typedef char (*AsyncFile_WriteDone_t)(AsyncFile *thisFile); +typedef void (*AsyncFile_SeekToTell_t)(AsyncFile *thisFile); +typedef void (*AsyncFile_ReadAsync_t)(AsyncFile *thisFile, void *iData, int iBytes); +typedef char (*AsyncFile_ReadDone_t)(AsyncFile *thisFile); +typedef void (*AsyncFile_Close_t)(AsyncFile *thisFile); + +typedef struct _AsyncFile_vtable +{ + File_vtable file; + AsyncFile_OpenAsync_t _OpenAsync; + AsyncFile_OpenDone_t _OpenDone; + AsyncFile_WriteAsync_t _WriteAsync; + AsyncFile_WriteDone_t _WriteDone; + AsyncFile_SeekToTell_t _SeekToTell; + AsyncFile_ReadAsync_t _ReadAsync; + AsyncFile_ReadDone_t _ReadDone; + AsyncFile_Close_t _Close; +} AsyncFile_vtable; + +struct AsyncFile +{ + AsyncFile_vtable *vtable; + int mMode; + char mFail; + char unk9; + String mFilename; + unsigned int mTell; + int mOffset; + unsigned int mSize; + unsigned int mUCSize; + char *mBuffer; + char *mData; + int mBytesLeft; + int mBytesRead; +}; + // flags can be used to check the existence of files both inside and outside the ARK, similar to how the NewFile flags work char FileExists(char *path, int flags); +char FileIsDLC(char *path); +char FileIsLocal(char *path); + #endif // _FILE_H diff --git a/include/rb3e_include.h b/include/rb3e_include.h index 0698911..b9a85ea 100644 --- a/include/rb3e_include.h +++ b/include/rb3e_include.h @@ -6,6 +6,7 @@ #include "crc32.h" #include "DataDebug.h" #include "DTAFunctions.h" +#include "FileSD.h" #include "GameHooks.h" #include "GemHooks.h" #include "GlobalSymbols.h" diff --git a/source/FileSD.c b/source/FileSD.c new file mode 100644 index 0000000..596d34d --- /dev/null +++ b/source/FileSD.c @@ -0,0 +1,193 @@ +#ifdef RB3E_WII + +#include +#include +#include +#include +#include +#include +#include "FileSD.h" +#include "rb3/File.h" +#include "rb3/Mem.h" +#include "rb3enhanced.h" +#include "ports.h" + +#define LE16(i) ((((i) & 0xFF) << 8 | ((i) >> 8) & 0xFF) & 0xFFFF) +#define LE(i) (((i) & 0xff) << 24 | ((i) & 0xff00) << 8 | ((i) & 0xff0000) >> 8 | ((i) >> 24) & 0xff) + +File_vtable FileSD_vtable; + +FileSD *FileSD_Destroy(FileSD *file) +{ + RB3E_DEBUG("FileSD_Destroy(file=%p)", file); + if (file->mFd != -1) + SD_close(file->mFd); + if (file->mFilePath != NULL) + MemFree(file->mFilePath); + MemFree(file); + return NULL; +} + +FileSD *FileSD_New(const char *filepath, int flags) +{ + RB3E_DEBUG("FileSD_New(filepath='%s', flags=0x%x)", filepath, flags); + // create the new object + FileSD *sd = MemAlloc(sizeof(FileSD), 0); + if (sd == NULL) { + RB3E_DEBUG("Not enough memory to read file '%s' from SD!", filepath); + return NULL; + } + memset(sd, 0, sizeof(FileSD)); + FileSD_InitVtable(); // TODO(Emma): find a better place to put this + sd->vtable = &FileSD_vtable; + sd->mFd = -1; + + // set the file path + sd->mFilePath = MemAlloc(strlen(filepath) + 1, 0); + if (sd->mFilePath == NULL) { + RB3E_DEBUG("Not enough memory to read file '%s' from SD!", filepath); + FileSD_Destroy(sd); + return NULL; + } + strcpy(sd->mFilePath, filepath); + + // determine the flags to use + int fileMode = O_RDONLY; + if ((flags & 0x4)) { + fileMode |= (O_RDWR | O_CREAT); + sd->mWriteEnabled = true; + } + if ((flags & 0x100)) { + fileMode |= O_APPEND; + } + RB3E_DEBUG("fileMode = 0x%x", fileMode); + + // open the file + sd->mFd = SD_open(&sd->mFileStruct, filepath, fileMode); + if (sd->mFd == -1) { + RB3E_DEBUG("Failed to open file '%s'", filepath); + FileSD_Destroy(sd); + return NULL; + } + + sd->mFilesize = sd->mFileStruct.filesize; + + return sd; +} + +String *FileSD_Filename(String *str, FileSD *file) +{ + // really we should be creating a new SD card + RB3E_DEBUG("FileSD_Filename(str=%p, file=%p)", str, file); + str->length = strlen(file->mFilePath); + str->buf = file->mFilePath; + return str; +} + +int FileSD_Read(FileSD *file, void *iData, int iBytes) { + //RB3E_DEBUG("FileSD_Read(file=%p, iData=%p, iBytes=%i)", file, iData, iBytes); + int read = SD_read(file->mFd, iData, iBytes); + if (read >= 0) { + file->mCurrentPosition += read; + } + return read; +} + +char FileSD_ReadAsync(FileSD *file, void *iData, int iBytes) { + // TODO(Emma): async SD card reads + //RB3E_DEBUG("FileSD_ReadAsync(file=%p, iData=%p, iBytes=%i)", file, iData, iBytes); + int read = FileSD_Read(file, iData, iBytes); + file->mLastBytesRead = read; + return (read >= 0); +} + +int FileSD_Write(FileSD *file, void *iData, int iBytes) { + // RB3E_DEBUG("FileSD_Write(file=%p, iData=%p, iBytes=%i)", file, iData, iBytes); + int wrote = SD_write(file->mFd, iData, iBytes); + if (wrote >= 0) { + file->mCurrentPosition += wrote; + } + return wrote; +} + +char FileSD_WriteAsync(FileSD *file, void *iData, int iBytes) { + // TODO(Emma): async SD card writes + // RB3E_DEBUG("FileSD_WriteAsync(file=%p, iData=%p, iBytes=%i)", file, iData, iBytes); + int wrote = FileSD_Write(file, iData, iBytes); + file->mLastBytesWrote = wrote; + return (wrote >= 0); +} + +int FileSD_Seek(FileSD *file, int iOffset, int iSeekType) { + RB3E_DEBUG("FileSD_Seek(file=%p, iOffset=%i, iSeekType=%i)", file, iOffset, iSeekType); + switch (iSeekType) { + case SEEK_SET: + file->mCurrentPosition = iOffset; + break; + case SEEK_CUR: + file->mCurrentPosition += iOffset; + break; + case SEEK_END: + file->mCurrentPosition = file->mFilesize - iOffset; + break; + } + file->mCurrentPosition = SD_seek(file->mFd, file->mCurrentPosition, SEEK_SET); + return file->mCurrentPosition; +} + +int FileSD_Tell(FileSD *file) { + RB3E_DEBUG("FileSD_Tell(file=%p)", file); + return file->mCurrentPosition; +} + +void FileSD_Flush(FileSD *file) { + RB3E_DEBUG("FileSD_Flush(file=%p)", file); + return; +} + +char FileSD_Eof(FileSD *file) { + //RB3E_DEBUG("FileSD_Eof(file=%p)", file); + return (file->mCurrentPosition >= file->mFilesize); +} + +char FileSD_Fail(FileSD *file) { + //RB3E_DEBUG("FileSD_Fail(file=%p)", file); + return file->mFailed; +} + +int FileSD_Size(FileSD *file) { + RB3E_DEBUG("FileSD_Size(file=%p)", file); + return file->mFilesize; +} + +char FileSD_ReadDone(FileSD *file, int *oBytes) { + //RB3E_DEBUG("FileSD_ReadDone(file=%p)", file); + *oBytes = file->mLastBytesRead; + return 1; +} + +char FileSD_WriteDone(FileSD *file, int *oBytes) { + //RB3E_DEBUG("FileSD_WriteDone(file=%p)", file); + *oBytes = file->mLastBytesWrote; + return 0; +} + +void FileSD_InitVtable() { + FileSD_vtable.destructor = (FileDestructor_t)FileSD_Destroy; + FileSD_vtable.Filename = (FileFilename_t)FileSD_Filename; + FileSD_vtable.Read = (FileRead_t)FileSD_Read; + FileSD_vtable.ReadAsync = (FileReadAsync_t)FileSD_ReadAsync; + FileSD_vtable.Write = (FileWrite_t)FileSD_Write; + FileSD_vtable.WriteAsync = (FileWriteAsync_t)FileSD_WriteAsync; + FileSD_vtable.Seek = (FileSeek_t)FileSD_Seek; + FileSD_vtable.Tell = (FileTell_t)FileSD_Tell; + FileSD_vtable.Flush = (FileFlush_t)FileSD_Flush; + FileSD_vtable.Eof = (FileEof_t)FileSD_Eof; + FileSD_vtable.Fail = (FileFail_t)FileSD_Fail; + FileSD_vtable.Size = (FileSize_t)FileSD_Size; + FileSD_vtable.UncompressedSize = (FileUncompressedSize_t)FileSD_Size; + FileSD_vtable.ReadDone = (FileReadDone_t)FileSD_ReadDone; + FileSD_vtable.WriteDone = (FileWriteDone_t)FileSD_WriteDone; +} + +#endif // RB3E_WII diff --git a/source/_functions.c b/source/_functions.c index 59fdea6..898db47 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -61,6 +61,7 @@ RB3E_STUB(MemPrint) RB3E_STUB(MemNumHeaps) RB3E_STUB(MemAlloc) RB3E_STUB(MemFree) +RB3E_STUB(FileIsDLC) #ifndef RB3E_XBOX RB3E_STUB(DataRegisterFunc) // DataRegisterFunc is inlined on 360 #endif diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index da93d82..0c35e26 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -71,6 +71,13 @@ void *NewFileHook(char *fileName, int flags) fileName = new_path; flags = 0x10002; // tell the game to load this file raw } +#ifdef RB3E_WII + // load the FileSD + if (RB3E_Mounted > 0 && + (memcmp(fileName, "sd:/", 3) == 0 || new_path != NULL)) { + return FileSD_New(fileName, flags); + } +#endif LOAD_ORIGINAL: if (config.LogFileAccess) RB3E_MSG("File: %s (%s)", fileName, (flags & 0x10000) ? "Raw" : "ARK"); @@ -300,6 +307,14 @@ void SymbolPreInitHook(int stringTableSize, int hashTableSize) InitGlobalSymbols(); } +#ifdef RB3E_WII +char FileIsLocalHook(char *filepath) { + if (memcmp(filepath, "sd:/", 4) == 0) + return 1; + return FileIsDLC(filepath); +} +#endif + void InitialiseFunctions() { #ifndef RB3E_WII @@ -351,6 +366,9 @@ void InitialiseFunctions() POKE_B(&BinstreamRead, PORT_BINSTREAMREAD); POKE_B(&BinstreamWriteEndian, PORT_BINSTREAMWRITEENDIAN); POKE_B(&BinstreamReadEndian, PORT_BINSTREAMREADENDIAN); +#ifdef RB3E_WII + POKE_B(&FileIsDLC, PORT_FILEISDLC); +#endif RB3E_FlushCache((void *)RB3EBase, (unsigned int)RB3EStubEnd - (unsigned int)RB3EBase); RB3E_MSG("Functions initialized!", NULL); } @@ -394,6 +412,7 @@ void ApplyHooks() #ifdef RB3E_WII // wii exclusive hooks // HookFunction(PORT_USBWIIGETTYPE, &UsbWiiGetType, &UsbWiiGetTypeHook); HookFunction(PORT_WIINETINIT_DNSLOOKUP, &StartDNSLookup, &StartDNSLookupHook); + POKE_B(PORT_FILEISLOCAL, &FileIsLocalHook); #elif RB3E_XBOX // 360 exclusive hooks HookFunction(PORT_STAGEKIT_SET_STATE, &StagekitSetState, &StagekitSetStateHook); HookFunction(PORT_SETSONGNAMEFROMNODE, &SetSongNameFromNode, &SetSongNameFromNodeHook); From 790e5f5393524cb74f8a07fc4c9a887d88f78efa Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Wed, 14 May 2025 06:23:07 +0100 Subject: [PATCH 035/123] add DTA functions for getting song metadata by ID --- include/GlobalSymbols.h | 5 ++ include/rb3/SongMetadata.h | 9 +-- include/rvl/cnt.h | 55 ++++++++++++++++ source/DTAFunctions.c | 124 +++++++++++++++++++++++++++++++++++++ source/GlobalSymbols.c | 5 ++ source/SetlistHooks.c | 4 +- source/net_http_server.c | 10 +-- 7 files changed, 201 insertions(+), 11 deletions(-) create mode 100644 include/rvl/cnt.h diff --git a/include/GlobalSymbols.h b/include/GlobalSymbols.h index f006498..354a98b 100644 --- a/include/GlobalSymbols.h +++ b/include/GlobalSymbols.h @@ -20,6 +20,11 @@ typedef struct _GlobalSymbols Symbol rb3e_relaunch_game; Symbol rb3e_get_song_count; Symbol rb3e_send_event_string; + Symbol rb3e_get_song_name; + Symbol rb3e_get_artist; + Symbol rb3e_get_album; + Symbol rb3e_get_origin; + Symbol rb3e_get_genre; // modifiers Symbol forceHopos; diff --git a/include/rb3/SongMetadata.h b/include/rb3/SongMetadata.h index 032872d..2d8ff08 100644 --- a/include/rb3/SongMetadata.h +++ b/include/rb3/SongMetadata.h @@ -2,6 +2,7 @@ #define _SONGMETADATA_H #include "String.h" +#include "Symbol.h" // technically BandSongMetadata? typedef struct _SongMetadata @@ -12,10 +13,10 @@ typedef struct _SongMetadata #else char unknown[0x28]; #endif - char *shortname; + Symbol shortname; int song_id; char unknown2[0x4]; - char *gameOrigin; + Symbol gameOrigin; char unknown3[0x10]; String title; String artist; @@ -25,9 +26,9 @@ typedef struct _SongMetadata #else char unknown6[0x18]; #endif - char *genre; + Symbol genre; int animTempo; - char *vocalGender; + Symbol vocalGender; int lengthMs; } SongMetadata; diff --git a/include/rvl/cnt.h b/include/rvl/cnt.h new file mode 100644 index 0000000..55f94b8 --- /dev/null +++ b/include/rvl/cnt.h @@ -0,0 +1,55 @@ +#ifndef _RVL_CNT_H +#define _RVL_CNT_H + +// -- ARC, on-disk -- + +// https://wiki.tockdom.com/wiki/U8_(File_Format) +// header of ARC/U8 files +typedef struct _arc_header_t { + unsigned int magic; + int first_node; + int nodes_size; + int data_offset; + int reserved[4]; +} arc_header_t; // 0x20 + +// the types of nodes within an ARC +typedef enum _arc_node_type_t { + arcnode_file = 0, + arcnode_dir = 1 +} arc_node_type_t; + +// info about a file node +typedef struct _arc_node_file_t { + int data_offset; + int data_size; +} arc_node_file_t; +// info about a directory node +typedef struct _arc_node_dir_t { + int parent_node_index; + int next_node; +} arc_node_dir_t; + +// an ARC node itself +typedef struct _arc_node_t { + int node_type : 8; + int string_offset : 24; + union { + arc_node_file_t file; + arc_node_dir_t dir; + }; +} arc_node_t; // 0xC + +// -- CNT, internally -- + +typedef enum _cnt_handle_type { + cnthandle_nand = 0, + cnthandle_dvd = 1 +} cnt_handle_type; + +typedef struct _cnt_handle { + unsigned char backing[0x28]; // TODO: split between ARC/NAND and DVD + unsigned char type; +} cnt_handle; + +#endif // _RVL_CNT_H diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index 6c10a10..e1b1595 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -11,6 +11,8 @@ #include "config.h" #include "net_events.h" #include "rb3/Data.h" +#include "rb3/SongMetadata.h" +#include "rb3/BandSongMgr.h" #include "rb3enhanced.h" DataNode *PrintToDebugger(DataNode *node, DataArray *args) @@ -168,6 +170,123 @@ DataNode *DTASendModData(DataNode *node, DataArray *args) return node; } +DataNode *DTAGetSongName(DataNode *node, DataArray *args) +{ + // return "Darude Sandstorm"; + DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); + Symbol rb3e_no_song_name; + SymbolConstruct(&rb3e_no_song_name, "rb3e_no_song_name"); + node->type = SYMBOL; + node->value.string = rb3e_no_song_name.sym; + if (firstArg->type != INT_VALUE) + RB3E_MSG("Invalid types for arguments to rb3e_get_song_name! 1st: %i", firstArg->type); + else + { + SongMetadata *songmet = GetMetadata((BandSongMgr *)PORT_THESONGMGR, firstArg->value.intVal); + RB3E_DEBUG("rb3e_get_song_name %i", firstArg->value.intVal); + if (songmet == NULL) { + RB3E_MSG("!! FAILED TO GET SONG METADATA FOR %i !!", firstArg->value.intVal); + } else { + Symbol titleSym; + SymbolConstruct(&titleSym, songmet->title.buf); + node->value.string = titleSym.sym; + } + } + return node; +} + +DataNode *DTAGetArtist(DataNode *node, DataArray *args) +{ + DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); + Symbol rb3e_no_artist; + SymbolConstruct(&rb3e_no_artist, "rb3e_no_artist"); + node->type = SYMBOL; + node->value.string = rb3e_no_artist.sym; + if (firstArg->type != INT_VALUE) + RB3E_MSG("Invalid types for arguments to rb3e_get_artist! 1st: %i", firstArg->type); + else + { + SongMetadata *songmet = GetMetadata((BandSongMgr *)PORT_THESONGMGR, firstArg->value.intVal); + RB3E_DEBUG("rb3e_get_artist %i", firstArg->value.intVal); + if (songmet == NULL) { + RB3E_MSG("!! FAILED TO GET SONG METADATA FOR %i !!", firstArg->value.intVal); + } else { + Symbol artistSym; + SymbolConstruct(&artistSym, songmet->artist.buf); + node->value.string = artistSym.sym; + } + } + return node; +} + +DataNode *DTAGetAlbum(DataNode *node, DataArray *args) +{ + DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); + Symbol rb3e_no_album; + SymbolConstruct(&rb3e_no_album, "rb3e_no_album"); + node->type = SYMBOL; + node->value.string = rb3e_no_album.sym; + if (firstArg->type != INT_VALUE) + RB3E_MSG("Invalid types for arguments to rb3e_get_album! 1st: %i", firstArg->type); + else + { + SongMetadata *songmet = GetMetadata((BandSongMgr *)PORT_THESONGMGR, firstArg->value.intVal); + RB3E_DEBUG("rb3e_get_album %i", firstArg->value.intVal); + if (songmet == NULL) { + RB3E_MSG("!! FAILED TO GET SONG METADATA FOR %i !!", firstArg->value.intVal); + } else { + Symbol albumSym; + SymbolConstruct(&albumSym, songmet->artist.buf); + node->value.string = albumSym.sym; + } + } + return node; +} + +DataNode *DTAGetGenre(DataNode *node, DataArray *args) +{ + DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); + Symbol rb3e_no_genre; + SymbolConstruct(&rb3e_no_genre, "rb3e_no_genre"); + node->type = SYMBOL; + node->value.string = rb3e_no_genre.sym; + if (firstArg->type != INT_VALUE) + RB3E_MSG("Invalid types for arguments to rb3e_no_genre! 1st: %i", firstArg->type); + else + { + SongMetadata *songmet = GetMetadata((BandSongMgr *)PORT_THESONGMGR, firstArg->value.intVal); + RB3E_DEBUG("rb3e_no_genre %i", firstArg->value.intVal); + if (songmet == NULL) { + RB3E_MSG("!! FAILED TO GET SONG METADATA FOR %i !!", firstArg->value.intVal); + } else { + node->value.string = songmet->genre.sym; + } + } + return node; +} + +DataNode *DTAGetOrigin(DataNode *node, DataArray *args) +{ + DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); + Symbol rb3e_no_origin; + SymbolConstruct(&rb3e_no_origin, "rb3e_no_origin"); + node->type = SYMBOL; + node->value.string = rb3e_no_origin.sym; + if (firstArg->type != INT_VALUE) + RB3E_MSG("Invalid types for arguments to rb3e_no_origin! 1st: %i", firstArg->type); + else + { + SongMetadata *songmet = GetMetadata((BandSongMgr *)PORT_THESONGMGR, firstArg->value.intVal); + RB3E_DEBUG("rb3e_no_origin %i", firstArg->value.intVal); + if (songmet == NULL) { + RB3E_MSG("!! FAILED TO GET SONG METADATA FOR %i !!", firstArg->value.intVal); + } else { + node->value.string = songmet->gameOrigin.sym; + } + } + return node; +} + #ifdef RB3E_XBOX // this function is inlined on the Xbox version, so we re-create it void DataRegisterFunc(Symbol name, DTAFunction_t func) @@ -188,5 +307,10 @@ void AddDTAFunctions() DataRegisterFunc(globalSymbols.rb3e_relaunch_game, DTARelaunchGame); DataRegisterFunc(globalSymbols.rb3e_get_song_count, DTAGetSongCount); DataRegisterFunc(globalSymbols.rb3e_send_event_string, DTASendModData); + DataRegisterFunc(globalSymbols.rb3e_get_song_name, DTAGetSongName); + DataRegisterFunc(globalSymbols.rb3e_get_artist, DTAGetArtist); + DataRegisterFunc(globalSymbols.rb3e_get_album, DTAGetArtist); + DataRegisterFunc(globalSymbols.rb3e_get_origin, DTAGetOrigin); + DataRegisterFunc(globalSymbols.rb3e_get_genre, DTAGetGenre); RB3E_MSG("Added DTA functions!", NULL); } diff --git a/source/GlobalSymbols.c b/source/GlobalSymbols.c index ec759e2..5a17a8f 100644 --- a/source/GlobalSymbols.c +++ b/source/GlobalSymbols.c @@ -36,6 +36,11 @@ void InitGlobalSymbols() SymbolConstruct(&globalSymbols.rb3e_relaunch_game, "rb3e_relaunch_game"); SymbolConstruct(&globalSymbols.rb3e_get_song_count, "rb3e_get_song_count"); SymbolConstruct(&globalSymbols.rb3e_send_event_string, "rb3e_send_event_string"); + SymbolConstruct(&globalSymbols.rb3e_get_song_name, "rb3e_get_song_name"); + SymbolConstruct(&globalSymbols.rb3e_get_artist, "rb3e_get_artist"); + SymbolConstruct(&globalSymbols.rb3e_get_album, "rb3e_get_album"); + SymbolConstruct(&globalSymbols.rb3e_get_origin, "rb3e_get_origin"); + SymbolConstruct(&globalSymbols.rb3e_get_genre, "rb3e_get_genre"); SymbolConstruct(&globalSymbols.blackBackground, "mod_black_background"); SymbolConstruct(&globalSymbols.colorShuffle, "mod_color_shuffle"); diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 6f1fd85..efd4347 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -44,7 +44,7 @@ void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) SetSongAndArtistName(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->somethingElse->metaData->gameOrigin, originToIcon[i][0]) == 0) + if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; @@ -72,7 +72,7 @@ void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) SetSongNameFromNode(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->somethingElse->metaData->gameOrigin, originToIcon[i][0]) == 0) + if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; diff --git a/source/net_http_server.c b/source/net_http_server.c index 1245e9f..27f4e02 100644 --- a/source/net_http_server.c +++ b/source/net_http_server.c @@ -218,7 +218,7 @@ void HTTP_Server_Accept(void *connection) strcat(response_buffer, "Content-Type: text/plain\r\n"); strcat(response_buffer, "\r\n"); strcat(response_buffer, "shortname="); - strcat(response_buffer, song_metadata->shortname); + strcat(response_buffer, song_metadata->shortname.sym); strcat(response_buffer, "\r\n"); strcat(response_buffer, "title="); strcat(response_buffer, song_metadata->title.buf); @@ -230,7 +230,7 @@ void HTTP_Server_Accept(void *connection) strcat(response_buffer, song_metadata->album.buf); strcat(response_buffer, "\r\n"); strcat(response_buffer, "origin="); - strcat(response_buffer, song_metadata->gameOrigin); + strcat(response_buffer, song_metadata->gameOrigin.sym); strcat(response_buffer, "\r\n"); strcat(response_buffer, "\r\n"); RB3E_TCP_Send(s, (void *)response_buffer, strlen(response_buffer)); @@ -280,10 +280,10 @@ void HTTP_Server_Accept(void *connection) if (song_metadata != NULL) { strcat(response_buffer, "["); - strcat(response_buffer, song_metadata->shortname); + strcat(response_buffer, song_metadata->shortname.sym); strcat(response_buffer, "]\r\n"); strcat(response_buffer, "shortname="); - strcat(response_buffer, song_metadata->shortname); + strcat(response_buffer, song_metadata->shortname.sym); strcat(response_buffer, "\r\n"); strcat(response_buffer, "title="); strcat(response_buffer, song_metadata->title.buf); @@ -295,7 +295,7 @@ void HTTP_Server_Accept(void *connection) strcat(response_buffer, song_metadata->album.buf); strcat(response_buffer, "\r\n"); strcat(response_buffer, "origin="); - strcat(response_buffer, song_metadata->gameOrigin); + strcat(response_buffer, song_metadata->gameOrigin.sym); strcat(response_buffer, "\r\n"); strcat(response_buffer, "\r\n"); if (strlen(response_buffer) > 1500) From 83e4bb0d7c54f3e9adab73bd4fe8bd7864c44147 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Wed, 14 May 2025 22:18:23 +0100 Subject: [PATCH 036/123] [dolphin] allow read-only reads from NAND (for dx ace.dta) --- include/FileWiiNAND.h | 23 +++++ include/rb3e_include.h | 1 + include/rvl/cnt.h | 77 +++++++++++++---- include/rvl/ec.h | 26 ++++++ include/rvl/tmd.h | 44 ++++++++++ include/rvl/wad.h | 45 ++++++++++ include/wii_ipc.h | 121 -------------------------- source/FileWiiNAND.c | 190 +++++++++++++++++++++++++++++++++++++++++ source/rb3enhanced.c | 5 ++ source/wii.c | 1 - 10 files changed, 396 insertions(+), 137 deletions(-) create mode 100644 include/FileWiiNAND.h create mode 100644 include/rvl/ec.h create mode 100644 include/rvl/tmd.h create mode 100644 include/rvl/wad.h delete mode 100644 include/wii_ipc.h create mode 100644 source/FileWiiNAND.c diff --git a/include/FileWiiNAND.h b/include/FileWiiNAND.h new file mode 100644 index 0000000..fd490cc --- /dev/null +++ b/include/FileWiiNAND.h @@ -0,0 +1,23 @@ +#ifdef RB3E_WII + +#include +#include "rb3/File.h" + +typedef struct _FileWiiNAND +{ + File_vtable *vtable; // 0x0 + + char *mFilePath; + char mWriteEnabled; + char mFailed; + ios_fd_t mFd; + int mFilesize; + int mCurrentPosition; + int mLastBytesRead; + int mLastBytesWrote; +} FileWiiNAND; + +void FileWiiNAND_InitVtable(); +FileWiiNAND *FileWiiNAND_New(const char *filepath); + +#endif diff --git a/include/rb3e_include.h b/include/rb3e_include.h index b9a85ea..79e748c 100644 --- a/include/rb3e_include.h +++ b/include/rb3e_include.h @@ -7,6 +7,7 @@ #include "DataDebug.h" #include "DTAFunctions.h" #include "FileSD.h" +#include "FileWiiNAND.h" #include "GameHooks.h" #include "GemHooks.h" #include "GlobalSymbols.h" diff --git a/include/rvl/cnt.h b/include/rvl/cnt.h index 55f94b8..635964e 100644 --- a/include/rvl/cnt.h +++ b/include/rvl/cnt.h @@ -1,50 +1,66 @@ #ifndef _RVL_CNT_H #define _RVL_CNT_H -// -- ARC, on-disk -- +// -- ARC/U8, on-disk -- + +// 'U.8-' +#define ARC_MAGIC 0x55AA382D // https://wiki.tockdom.com/wiki/U8_(File_Format) // header of ARC/U8 files -typedef struct _arc_header_t { +typedef struct _u8_header_t { unsigned int magic; int first_node; int nodes_size; int data_offset; int reserved[4]; -} arc_header_t; // 0x20 +} u8_header_t; // 0x20 // the types of nodes within an ARC -typedef enum _arc_node_type_t { +typedef enum _u8_node_type_t { arcnode_file = 0, arcnode_dir = 1 -} arc_node_type_t; +} u8_node_type_t; // info about a file node -typedef struct _arc_node_file_t { +typedef struct _u8_node_file_t { int data_offset; int data_size; -} arc_node_file_t; +} u8_node_file_t; // info about a directory node -typedef struct _arc_node_dir_t { +typedef struct _u8_node_dir_t { int parent_node_index; int next_node; -} arc_node_dir_t; +} u8_node_dir_t; // an ARC node itself -typedef struct _arc_node_t { +typedef struct _u8_node_t { int node_type : 8; int string_offset : 24; union { - arc_node_file_t file; - arc_node_dir_t dir; + u8_node_file_t file; + u8_node_dir_t dir; }; -} arc_node_t; // 0xC +} u8_node_t; // 0xC + +// -- ARC, internally -- + +typedef struct _arc_handle_t { + unsigned char backing[0x1C]; +} arc_handle_t; + +typedef struct _arc_entry_t { + arc_handle_t *handle; + unsigned int path; + int type; + const char *name; +} arc_entry_t; // -- CNT, internally -- typedef enum _cnt_handle_type { - cnthandle_nand = 0, - cnthandle_dvd = 1 + cnthandle_nand = 1, + cnthandle_dvd = 2 } cnt_handle_type; typedef struct _cnt_handle { @@ -52,4 +68,35 @@ typedef struct _cnt_handle { unsigned char type; } cnt_handle; +typedef struct _cnt_dir { + unsigned char backing[0x10]; + unsigned char type; +} cnt_dir; + +typedef struct _cnt_dir_entry { + union { + arc_entry_t arc; + }; + unsigned char type; +} cnt_dir_entry; +int a = sizeof(cnt_dir_entry); + +typedef struct _cnt_file_info { + unsigned char backing[0x40]; + unsigned char type; +} cnt_file_info; + +cnt_handle *contentInitHandleTitleNAND(unsigned long long title_id, unsigned int content_index, cnt_handle *handle, void *allocator); +int CNTReleaseHandle(cnt_handle *handle); + +int CNTOpen(cnt_handle *handle, const char *path, cnt_file_info *file_info); +int CNTGetLength(cnt_file_info *file_info); +int CNTClose(cnt_file_info *file_info); +int CNTRead(cnt_file_info *file_info, void *buffer, int length); +int CNTSeek(cnt_file_info *file_info, int position, int seek_from); + +int CNTOpenDir(cnt_handle *handle, const char *path, cnt_dir *dir); +int CNTReadDir(cnt_dir *dir, cnt_dir_entry *entry); +int CNTCloseDir(cnt_dir *dir); + #endif // _RVL_CNT_H diff --git a/include/rvl/ec.h b/include/rvl/ec.h new file mode 100644 index 0000000..c7ae95d --- /dev/null +++ b/include/rvl/ec.h @@ -0,0 +1,26 @@ +#ifndef _RVL_EC_H +#define _RVL_EC_H + +typedef struct _ec_content_info_t { + unsigned int flags; + unsigned short index; + unsigned short type; + unsigned long long size; +} ec_content_info_t; + +typedef struct _ec_title_info_t { + unsigned long long titleId; + int isTmdPresent; + int isOnDevice; + unsigned int type; + unsigned int version; + unsigned int occupiedUserBlocks; + unsigned int occupiedUserInodes; + unsigned int occupiedSysBlocks; + unsigned int occupiedSysInodes; +} ec_title_info_t; + +int EC_GetContentInfos(unsigned long long titleId, ec_content_info_t *contentInfos, int *numContentInfos); +int EC_DeleteContents(unsigned long long titleId, unsigned short *contents, int numContents); + +#endif // _RVL_EC_H diff --git a/include/rvl/tmd.h b/include/rvl/tmd.h new file mode 100644 index 0000000..b883fab --- /dev/null +++ b/include/rvl/tmd.h @@ -0,0 +1,44 @@ +#ifndef _RVL_TMD_H +#define _RVL_TMD_H + +typedef struct _tmd_header_t { + unsigned int signature_type; // assumes RSA-2048 / 0x00010001 + unsigned char signature[0x100]; + unsigned char padding[0x3C]; + unsigned char issuer[0x40]; + unsigned char version; + unsigned char ca_crl_version; + unsigned char signer_crl_version; + unsigned char vwii; + unsigned long long ios_version; + union { + unsigned long long id; + struct { + unsigned int pad; + unsigned char code[4]; + } name; + } title_id; + unsigned int title_type; + unsigned short group_id; + unsigned short padding2; + unsigned short region; + unsigned char ratings[0x10]; + unsigned char padding3[0xC]; + unsigned char ipc_mask[0xC]; + unsigned char padding4[0x12]; + unsigned int access_rights; + unsigned short title_version; + unsigned short num_contents; + unsigned short boot_index; + unsigned short padding5; +} __attribute__((packed)) tmd_header_t; + +typedef struct _tmd_content_t { + unsigned int id; + unsigned short index; + unsigned short type; + unsigned long long size; + unsigned char SHA1[0x14]; +} __attribute__((packed)) tmd_content_t; + +#endif // _RVL_TMD_H diff --git a/include/rvl/wad.h b/include/rvl/wad.h new file mode 100644 index 0000000..1e6cfbc --- /dev/null +++ b/include/rvl/wad.h @@ -0,0 +1,45 @@ +#ifndef _RVL_WAD_H +#define _RVL_WAD_H + +// .bin backup files on the SD card + +// at offset 0x4 in the file, NOT 0x0 +#define BACKUP_WAD_MAGIC 0x426B0001 + +typedef struct _backup_wad_header_t { + unsigned int header_size; + unsigned short type; + unsigned short version; + unsigned int console_id; + unsigned int save_file_count; + unsigned int save_file_data_size; + unsigned int content_tmd_size; + unsigned int content_data_size; + unsigned int backup_area_size; + unsigned char included_contents[0x40]; //bitfield + union { + unsigned long long id; + struct { + unsigned int pad; + unsigned char code[4]; + } name; + } title_id; + unsigned char mac_address[0x06]; + unsigned char reserved[0x02]; + unsigned char pad[0x10]; +} backup_wad_header_t; + +/* + files are aligned to 0x40 byte boundaries + +-------------------------------+ + | backup_wad_header_t header | + +-------------------------------+ + | tmd_t tmd of content_tmd_size | + | padding to 0x40 bytes | + +-------------------------------+ + | binary of content_data_size | + | padding to 0x40 bytes | + +-------------------------------+ +*/ + +#endif // _RVL_WAD_H diff --git a/include/wii_ipc.h b/include/wii_ipc.h deleted file mode 100644 index 371fb09..0000000 --- a/include/wii_ipc.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - RB3Enhanced - wii_ipc.h - IPC structures for interacting with the IOS processor. -*/ -#ifdef RB3E_WII -#ifndef _WII_IPC_H -#define _WII_IPC_H -// For IPCCommandType, IOSOpenMode and SeekMode: -// Copyright 2008 Dolphin Emulator Project -// Licensed under GPLv2+ - -#include - -typedef int32_t s32; -typedef uint32_t u32; -typedef uint16_t u16; -typedef uint8_t u8; - -typedef enum _IPCCommandType -{ - IPC_CMD_OPEN = 1, - IPC_CMD_CLOSE = 2, - IPC_CMD_READ = 3, - IPC_CMD_WRITE = 4, - IPC_CMD_SEEK = 5, - IPC_CMD_IOCTL = 6, - IPC_CMD_IOCTLV = 7, - IPC_REPLY = 8, - IPC_INTERRUPT = 9, -} IPCCommandType; - -typedef enum _IOSOpenMode -{ - IOS_OPEN_READ = 1, - IOS_OPEN_WRITE = 2, - IOS_OPEN_RW = (IOS_OPEN_READ | IOS_OPEN_WRITE) -} IOSOpenMode; - -typedef struct _IOSResourceOpenRequest -{ - const u8 *path; - IOSOpenMode flags; - u32 uid; // some sort of ID (UID/PID?) - u16 gid; // probably gid based on the SetGid syscall -} IOSResourceOpenRequest; - -typedef struct _IOSResourceCloseRequest -{ -} IOSResourceCloseRequest; - -typedef struct _IOSResourceReadWriteRequest -{ - u8 *data; - u32 length; -} IOSResourceReadWriteRequest; - -typedef enum _SeekMode -{ - IOS_SEEK_SET = 0, - IOS_SEEK_CUR = 1, - IOS_SEEK_END = 2, -} SeekMode; - -typedef struct _IOSResourceSeekRequest -{ - u32 offset; - SeekMode mode; -} IOSResourceSeekRequest; - -typedef struct _IOSResourceIOCtlRequest -{ - u32 request; - u8 *in; - u32 in_size; - u8 *out; - u32 out_size; -} IOSResourceIOCtlRequest; - -typedef struct _IOVector -{ - u8 *base; - u32 length; -} IOVector; - -typedef struct _IOSResourceIOCtlVRequest -{ - u32 request; - u32 in_count; - u32 io_count; - IOVector *vectors; -} IOSResourceIOCtlVRequest; - -typedef union _IOSResourceArgs -{ - IOSResourceOpenRequest open; - IOSResourceCloseRequest close; - IOSResourceReadWriteRequest read; - IOSResourceReadWriteRequest write; - IOSResourceSeekRequest seek; - IOSResourceIOCtlRequest ioctl; - IOSResourceIOCtlVRequest ioctlv; -} IOSResourceArgs; - -typedef struct _IOSRequest -{ - IPCCommandType cmd; - s32 ret; - u32 fd; - IOSResourceArgs args; -} IOSRequest; - -typedef struct _IOSMessage -{ - IOSRequest request; - void *callback; - u32 caller_data; - u32 relaunch; -} IOSMessage; - -#endif // _WII_IPC_H -#endif // RB3E_WII diff --git a/source/FileWiiNAND.c b/source/FileWiiNAND.c new file mode 100644 index 0000000..e6a7409 --- /dev/null +++ b/source/FileWiiNAND.c @@ -0,0 +1,190 @@ +#ifdef RB3E_WII + +#include +#include +#include +#include +#include +#include +#include "FileWiiNAND.h" +#include "rb3/File.h" +#include "rb3/Mem.h" +#include "rb3enhanced.h" +#include "ports.h" + +#define LE16(i) ((((i) & 0xFF) << 8 | ((i) >> 8) & 0xFF) & 0xFFFF) +#define LE(i) (((i) & 0xff) << 24 | ((i) & 0xff00) << 8 | ((i) & 0xff0000) >> 8 | ((i) >> 24) & 0xff) + +File_vtable FileWiiNAND_vtable; + +FileWiiNAND *FileWiiNAND_Destroy(FileWiiNAND *file) +{ + RB3E_DEBUG("FileWiiNAND_Destroy(file=%p)", file); + if (file->mFd != -1) + IOS_Close(file->mFd); + if (file->mFilePath != NULL) + MemFree(file->mFilePath); + MemFree(file); + return NULL; +} + +typedef struct _nand_file_stats_t { + uint32_t length; + uint32_t pos; +} nand_file_stats_t; + +FileWiiNAND *FileWiiNAND_New(const char *filepath) +{ + // never ever allow this on real hardware + if (RB3E_IsEmulator()) + return NULL; + + RB3E_DEBUG("FileWiiNAND_New(filepath='%s')", filepath); + // create the new object + FileWiiNAND *nd = MemAlloc(sizeof(FileWiiNAND), 0); + if (nd == NULL) { + RB3E_DEBUG("Not enough memory to read file '%s' from NAND!", filepath); + return NULL; + } + memset(nd, 0, sizeof(FileWiiNAND)); + FileWiiNAND_InitVtable(); // TODO(Emma): find a better place to put this + nd->vtable = &FileWiiNAND_vtable; + nd->mFd = -1; + + // set the file path + nd->mFilePath = MemAlloc(strlen(filepath) + 1, 0); + if (nd->mFilePath == NULL) { + RB3E_DEBUG("Not enough memory to read file '%s' from NAND!", filepath); + FileWiiNAND_Destroy(nd); + return NULL; + } + strcpy(nd->mFilePath, filepath); + + // open the file + nd->mFd = IOS_Open(filepath, IPC_OPEN_READ); + if (nd->mFd == -1) { + RB3E_DEBUG("Failed to open file '%s'", filepath); + FileWiiNAND_Destroy(nd); + return NULL; + } + + // get the filesize + nand_file_stats_t stats __attribute__((aligned(32))); + ios_ret_t r = IOS_Ioctl(nd->mFd, 11, NULL, 0, &stats, sizeof(nand_file_stats_t)); + if (r != IPC_OK) { + RB3E_DEBUG("IOS returned %i trying to get stats", r); + FileWiiNAND_Destroy(nd); + return NULL; + } + + nd->mFilesize = stats.length; + + return nd; +} + +String *FileWiiNAND_Filename(String *str, FileWiiNAND *file) +{ + RB3E_DEBUG("FileWiiNAND_Filename(str=%p, file=%p)", str, file); + str->length = strlen(file->mFilePath); + str->buf = file->mFilePath; + return str; +} + +int FileWiiNAND_Read(FileWiiNAND *file, void *iData, int iBytes) { + //RB3E_DEBUG("FileWiiNAND_Read(file=%p, iData=%p, iBytes=%i)", file, iData, iBytes); + int read = IOS_Read(file->mFd, iData, iBytes); + if (read >= 0) { + file->mCurrentPosition += read; + } + return read; +} + +char FileWiiNAND_ReadAsync(FileWiiNAND *file, void *iData, int iBytes) { + // TODO(Emma): async SD card reads + //RB3E_DEBUG("FileWiiNAND_ReadAsync(file=%p, iData=%p, iBytes=%i)", file, iData, iBytes); + int read = FileWiiNAND_Read(file, iData, iBytes); + file->mLastBytesRead = read; + return (read >= 0); +} + +int FileWiiNAND_Write(FileWiiNAND *file, void *iData, int iBytes) { + return 0; +} + +char FileWiiNAND_WriteAsync(FileWiiNAND *file, void *iData, int iBytes) { + return 0; +} + +int FileWiiNAND_Seek(FileWiiNAND *file, int iOffset, int iSeekType) { + RB3E_DEBUG("FileWiiNAND_Seek(file=%p, iOffset=%i, iSeekType=%i)", file, iOffset, iSeekType); + switch (iSeekType) { + case SEEK_SET: + file->mCurrentPosition = iOffset; + break; + case SEEK_CUR: + file->mCurrentPosition += iOffset; + break; + case SEEK_END: + file->mCurrentPosition = file->mFilesize - iOffset; + break; + } + IOS_Seek(file->mFd, file->mCurrentPosition, SEEK_SET); + return file->mCurrentPosition; +} + +int FileWiiNAND_Tell(FileWiiNAND *file) { + RB3E_DEBUG("FileWiiNAND_Tell(file=%p)", file); + return file->mCurrentPosition; +} + +void FileWiiNAND_Flush(FileWiiNAND *file) { + RB3E_DEBUG("FileWiiNAND_Flush(file=%p)", file); + return; +} + +char FileWiiNAND_Eof(FileWiiNAND *file) { + //RB3E_DEBUG("FileWiiNAND_Eof(file=%p)", file); + return (file->mCurrentPosition >= file->mFilesize); +} + +char FileWiiNAND_Fail(FileWiiNAND *file) { + //RB3E_DEBUG("FileWiiNAND_Fail(file=%p)", file); + return file->mFailed; +} + +int FileWiiNAND_Size(FileWiiNAND *file) { + RB3E_DEBUG("FileWiiNAND_Size(file=%p)", file); + return file->mFilesize; +} + +char FileWiiNAND_ReadDone(FileWiiNAND *file, int *oBytes) { + //RB3E_DEBUG("FileWiiNAND_ReadDone(file=%p)", file); + *oBytes = file->mLastBytesRead; + return 1; +} + +char FileWiiNAND_WriteDone(FileWiiNAND *file, int *oBytes) { + //RB3E_DEBUG("FileWiiNAND_WriteDone(file=%p)", file); + *oBytes = file->mLastBytesWrote; + return 0; +} + +void FileWiiNAND_InitVtable() { + FileWiiNAND_vtable.destructor = (FileDestructor_t)FileWiiNAND_Destroy; + FileWiiNAND_vtable.Filename = (FileFilename_t)FileWiiNAND_Filename; + FileWiiNAND_vtable.Read = (FileRead_t)FileWiiNAND_Read; + FileWiiNAND_vtable.ReadAsync = (FileReadAsync_t)FileWiiNAND_ReadAsync; + FileWiiNAND_vtable.Write = (FileWrite_t)FileWiiNAND_Write; + FileWiiNAND_vtable.WriteAsync = (FileWriteAsync_t)FileWiiNAND_WriteAsync; + FileWiiNAND_vtable.Seek = (FileSeek_t)FileWiiNAND_Seek; + FileWiiNAND_vtable.Tell = (FileTell_t)FileWiiNAND_Tell; + FileWiiNAND_vtable.Flush = (FileFlush_t)FileWiiNAND_Flush; + FileWiiNAND_vtable.Eof = (FileEof_t)FileWiiNAND_Eof; + FileWiiNAND_vtable.Fail = (FileFail_t)FileWiiNAND_Fail; + FileWiiNAND_vtable.Size = (FileSize_t)FileWiiNAND_Size; + FileWiiNAND_vtable.UncompressedSize = (FileUncompressedSize_t)FileWiiNAND_Size; + FileWiiNAND_vtable.ReadDone = (FileReadDone_t)FileWiiNAND_ReadDone; + FileWiiNAND_vtable.WriteDone = (FileWriteDone_t)FileWiiNAND_WriteDone; +} + +#endif // RB3E_WII diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 0c35e26..b21f875 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -77,6 +77,11 @@ void *NewFileHook(char *fileName, int flags) (memcmp(fileName, "sd:/", 3) == 0 || new_path != NULL)) { return FileSD_New(fileName, flags); } +#ifdef RB3EDEBUG + if (RB3E_IsEmulator() && memcmp(fileName, "nand/", 5) == 0) { + return FileWiiNAND_New(fileName + 4); + } +#endif #endif LOAD_ORIGINAL: if (config.LogFileAccess) diff --git a/source/wii.c b/source/wii.c index 95d6444..bf6129c 100644 --- a/source/wii.c +++ b/source/wii.c @@ -21,7 +21,6 @@ #include "GlobalSymbols.h" #include "config.h" #include "rb3/WiiMemMgr.h" -#include "wii_ipc.h" #include "utilities.h" char _has256MBMem2 = 0; From 7212d94a579ca4c3b29f29b4ce702f66e205af5d Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Thu, 15 May 2025 00:13:39 +0100 Subject: [PATCH 037/123] [wii] fix dta interop --- source/FileWiiNAND.c | 2 +- source/rb3enhanced.c | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/source/FileWiiNAND.c b/source/FileWiiNAND.c index e6a7409..dc4f39d 100644 --- a/source/FileWiiNAND.c +++ b/source/FileWiiNAND.c @@ -36,7 +36,7 @@ typedef struct _nand_file_stats_t { FileWiiNAND *FileWiiNAND_New(const char *filepath) { // never ever allow this on real hardware - if (RB3E_IsEmulator()) + if (!RB3E_IsEmulator()) return NULL; RB3E_DEBUG("FileWiiNAND_New(filepath='%s')", filepath); diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index b21f875..a5052eb 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -347,8 +347,6 @@ void InitialiseFunctions() POKE_B(&HmxFactoryFuncAt, PORT_HMXFACTORYFUNCAT); // TODO(Emma): port to bank8 POKE_B(&ObjectFindUIPanel, PORT_OBJECTFINDUIPANEL); -#else - POKE_B(&DataRegisterFunc, PORT_DATAREGISTERFUNC); #endif POKE_B(&RandomInt, PORT_RANDOMINT); POKE_B(&DataNodeEvaluate, PORT_DATANODEEVALUATE); @@ -371,6 +369,9 @@ void InitialiseFunctions() POKE_B(&BinstreamRead, PORT_BINSTREAMREAD); POKE_B(&BinstreamWriteEndian, PORT_BINSTREAMWRITEENDIAN); POKE_B(&BinstreamReadEndian, PORT_BINSTREAMREADENDIAN); +#ifndef RB3E_XBOX + POKE_B(&DataRegisterFunc, PORT_DATAREGISTERFUNC); +#endif #ifdef RB3E_WII POKE_B(&FileIsDLC, PORT_FILEISDLC); #endif From 29107fa8b5fdfbd810b1aa6ad2398eeac3f43f3d Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Thu, 15 May 2025 16:45:13 -0500 Subject: [PATCH 038/123] fix typo in get album --- source/DTAFunctions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index e1b1595..de3ae02 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -236,7 +236,7 @@ DataNode *DTAGetAlbum(DataNode *node, DataArray *args) RB3E_MSG("!! FAILED TO GET SONG METADATA FOR %i !!", firstArg->value.intVal); } else { Symbol albumSym; - SymbolConstruct(&albumSym, songmet->artist.buf); + SymbolConstruct(&albumSym, songmet->album.buf); node->value.string = albumSym.sym; } } From 3f0939f2aca81a09d8e8966bbe6cdd2603249e57 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Thu, 15 May 2025 17:12:46 -0500 Subject: [PATCH 039/123] A second typo has hit the album func (#37) * wait is this it * please run my action thanks * preesh * Update DTAFunctions.c --- source/DTAFunctions.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index de3ae02..c75683d 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -255,7 +255,7 @@ DataNode *DTAGetGenre(DataNode *node, DataArray *args) else { SongMetadata *songmet = GetMetadata((BandSongMgr *)PORT_THESONGMGR, firstArg->value.intVal); - RB3E_DEBUG("rb3e_no_genre %i", firstArg->value.intVal); + RB3E_DEBUG("rb3e_get_genre %i", firstArg->value.intVal); if (songmet == NULL) { RB3E_MSG("!! FAILED TO GET SONG METADATA FOR %i !!", firstArg->value.intVal); } else { @@ -309,7 +309,7 @@ void AddDTAFunctions() DataRegisterFunc(globalSymbols.rb3e_send_event_string, DTASendModData); DataRegisterFunc(globalSymbols.rb3e_get_song_name, DTAGetSongName); DataRegisterFunc(globalSymbols.rb3e_get_artist, DTAGetArtist); - DataRegisterFunc(globalSymbols.rb3e_get_album, DTAGetArtist); + DataRegisterFunc(globalSymbols.rb3e_get_album, DTAGetAlbum); DataRegisterFunc(globalSymbols.rb3e_get_origin, DTAGetOrigin); DataRegisterFunc(globalSymbols.rb3e_get_genre, DTAGetGenre); RB3E_MSG("Added DTA functions!", NULL); From c3e2ded1fce074e1f1f63583724a2d03880bf29b Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 17 May 2025 11:04:18 +0100 Subject: [PATCH 040/123] [wii] load encrypted SD DLC as if they were on NAND --- include/aes.h | 95 +++++++ include/ports_wii.h | 6 + include/ports_wii_bank8.h | 6 + include/rvl/cnt.h | 57 +++- include/rvl/mem.h | 20 ++ include/wii_cnt_crypt.h | 26 ++ include/wii_cnt_hooks.h | 9 + source/_functions.c | 6 + source/aes.c | 575 ++++++++++++++++++++++++++++++++++++++ source/rb3enhanced.c | 8 + source/wii.c | 1 + source/wii_cnt_crypt.c | 308 ++++++++++++++++++++ source/wii_cnt_hooks.c | 174 ++++++++++++ 13 files changed, 1285 insertions(+), 6 deletions(-) create mode 100644 include/aes.h create mode 100644 include/rvl/mem.h create mode 100644 include/wii_cnt_crypt.h create mode 100644 include/wii_cnt_hooks.h create mode 100644 source/aes.c create mode 100644 source/wii_cnt_crypt.c create mode 100644 source/wii_cnt_hooks.c diff --git a/include/aes.h b/include/aes.h new file mode 100644 index 0000000..4867d1a --- /dev/null +++ b/include/aes.h @@ -0,0 +1,95 @@ +#ifdef RB3E_WII + +#ifndef _AES_H_ +#define _AES_H_ + +#include +#include + +// #define the macros below to 1/0 to enable/disable the mode of operation. +// +// CBC enables AES encryption in CBC-mode of operation. +// CTR enables encryption in counter-mode. +// ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously. + +// The #ifndef-guard allows it to be configured before #include'ing or at compile time. +#ifndef CBC + #define CBC 1 +#endif + +#ifndef ECB + #define ECB 1 +#endif + +#ifndef CTR + #define CTR 0 +#endif + + +#define AES128 1 +//#define AES192 1 +//#define AES256 1 + +#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only + +#if defined(AES256) && (AES256 == 1) + #define AES_KEYLEN 32 + #define AES_keyExpSize 240 +#elif defined(AES192) && (AES192 == 1) + #define AES_KEYLEN 24 + #define AES_keyExpSize 208 +#else + #define AES_KEYLEN 16 // Key length in bytes + #define AES_keyExpSize 176 +#endif + +struct AES_ctx +{ + uint8_t RoundKey[AES_keyExpSize]; +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) + uint8_t Iv[AES_BLOCKLEN]; +#endif +}; + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); +#endif + +#if defined(ECB) && (ECB == 1) +// buffer size is exactly AES_BLOCKLEN bytes; +// you need only AES_init_ctx as IV is not used in ECB +// NB: ECB is considered insecure for most uses +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); + +#endif // #if defined(ECB) && (ECB == !) + + +#if defined(CBC) && (CBC == 1) +// buffer size MUST be mutile of AES_BLOCKLEN; +// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#endif // #if defined(CBC) && (CBC == 1) + + +#if defined(CTR) && (CTR == 1) + +// Same function for encrypting as for decrypting. +// IV is incremented for every block, and used after encryption as XOR-compliment for output +// Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#endif // #if defined(CTR) && (CTR == 1) + + +#endif // _AES_H_ + +#endif // RB3E_WII diff --git a/include/ports_wii.h b/include/ports_wii.h index 842265b..46dba05 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -102,6 +102,7 @@ #define PORT_DATAREGISTERFUNC 0x8031b2b8 // DataRegisterFunc #define PORT_FILEISLOCAL 0x802fb548 // FileIsLocal #define PORT_FILEISDLC 0x802fb54c // FileIsDLC +#define PORT_SDMODECHECK 0x802F5638 // WiiContentMgr::SDModeCheck // instance addresses #define PORT_MODIFIERMGR_POINTER 0x808fda68 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80900870 // address of RockCentralGateway @@ -141,6 +142,11 @@ #define PORT_PPCHALT 0x80706390 // PPCHalt #define PORT_OSRETURNTOMENU 0x8076a610 // OSReturnToMenu #define PORT_OSREADROM 0x8076ae20 // OSReadROM +#define PORT_ARCINITHANDLE 0x806ffdb0 // ARCInitHandle +#define PORT_CONTENTINITHANDLETITLENAND 0x80733d60 // contentInitHandleTitleNAND +#define PORT_CNTRELEASEHANDLE 0x80734620 // CNTReleaseHandle +#define PORT_CNTREAD 0x807349d0 // CNTRead +#define PORT_ECGETCONTENTINFOS 0x807a9680 // EC_GetContentInfos // define logging functions #define RB3E_PRINT printf diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index faccd3b..5f01c1e 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -104,6 +104,7 @@ #define PORT_DATAREGISTERFUNC 0x804545e0 // DataRegisterFunc #define PORT_FILEISLOCAL 0x80422560 // FileIsLocal #define PORT_FILEISDLC 0x80422570 // FileIsDLC +#define PORT_SDMODECHECK 0x8041a790 // WiiContentMgr::SDModeCheck // instance addresses #define PORT_MODIFIERMGR_POINTER 0x80c904a8 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x80c91818 // address of RockCentralGateway @@ -143,6 +144,11 @@ #define PORT_PPCHALT 0x80a49600 // PPCHalt #define PORT_OSRETURNTOMENU 0x80aad970 // OSReturnToMenu #define PORT_OSREADROM 0x80aae180 // OSReadROM +#define PORT_ARCINITHANDLE 0x80a43020 // ARCInitHandle +#define PORT_CONTENTINITHANDLETITLENAND 0x80a76fd0 // contentInitHandleTitleNAND +#define PORT_CNTRELEASEHANDLE 0x80a77890 // CNTReleaseHandle +#define PORT_CNTREAD 0x80a77c40 // CNTRead +#define PORT_ECGETCONTENTINFOS 0x80aed870 // EC_GetContentInfos // bank8 specific stuff #define PORT_BANK8_MEM2_RSO_ASSERT1 0x804428e8 #define PORT_BANK8_MEM2_RSO_ASSERT2 0x80442940 diff --git a/include/rvl/cnt.h b/include/rvl/cnt.h index 635964e..545756f 100644 --- a/include/rvl/cnt.h +++ b/include/rvl/cnt.h @@ -1,6 +1,10 @@ #ifndef _RVL_CNT_H #define _RVL_CNT_H +#include +#include +#include "emvolution/dvd.h" + // -- ARC/U8, on-disk -- // 'U.8-' @@ -13,7 +17,7 @@ typedef struct _u8_header_t { int first_node; int nodes_size; int data_offset; - int reserved[4]; + unsigned int reserved[4]; } u8_header_t; // 0x20 // the types of nodes within an ARC @@ -46,9 +50,21 @@ typedef struct _u8_node_t { // -- ARC, internally -- typedef struct _arc_handle_t { - unsigned char backing[0x1C]; + u8_header_t *header; + u8_node_t *nodes; + uint8_t *file; + unsigned int count; + const char *strings; + unsigned int fstSize; + int entrynum; } arc_handle_t; +typedef struct _arc_file_info_t { + arc_handle_t *handle; + unsigned int offset; + unsigned int size; +} arc_file_info_t; + typedef struct _arc_entry_t { arc_handle_t *handle; unsigned int path; @@ -63,8 +79,21 @@ typedef enum _cnt_handle_type { cnthandle_dvd = 2 } cnt_handle_type; +typedef struct _cnt_handle_nand { + arc_handle_t ArcHandle; + long FileDescriptor; + void *allocator; +} cnt_handle_nand; + +typedef struct _cnt_handle_dvd { + unsigned char backing[0x24]; +} cnt_handle_dvd; + typedef struct _cnt_handle { - unsigned char backing[0x28]; // TODO: split between ARC/NAND and DVD + union { + cnt_handle_nand nand; + cnt_handle_dvd dvd; + }; unsigned char type; } cnt_handle; @@ -79,14 +108,28 @@ typedef struct _cnt_dir_entry { }; unsigned char type; } cnt_dir_entry; -int a = sizeof(cnt_dir_entry); + +typedef struct _cnt_file_info_nand { + cnt_handle_nand *CntHandle; + unsigned int startoffset; + unsigned int length; + long readOffset; +} cnt_file_info_nand; + +typedef struct _cnt_file_info_dvd { + DVDFileInfo fileInfo; + long readOffset; +} cnt_file_info_dvd; typedef struct _cnt_file_info { - unsigned char backing[0x40]; + union { + cnt_file_info_nand nand; + cnt_file_info_dvd dvd; + }; unsigned char type; } cnt_file_info; -cnt_handle *contentInitHandleTitleNAND(unsigned long long title_id, unsigned int content_index, cnt_handle *handle, void *allocator); +int contentInitHandleTitleNAND(unsigned long long title_id, unsigned int content_index, cnt_handle *handle, MEMAllocator *allocator); int CNTReleaseHandle(cnt_handle *handle); int CNTOpen(cnt_handle *handle, const char *path, cnt_file_info *file_info); @@ -99,4 +142,6 @@ int CNTOpenDir(cnt_handle *handle, const char *path, cnt_dir *dir); int CNTReadDir(cnt_dir *dir, cnt_dir_entry *entry); int CNTCloseDir(cnt_dir *dir); +int ARCInitHandle(void *bin, arc_handle_t *handle); + #endif // _RVL_CNT_H diff --git a/include/rvl/mem.h b/include/rvl/mem.h new file mode 100644 index 0000000..2cbf51f --- /dev/null +++ b/include/rvl/mem.h @@ -0,0 +1,20 @@ +#ifndef _RVL_MEM_H + +typedef struct MEMAllocator; + +typedef void* (*MEMAllocatorAllocFunc)(struct MEMAllocator* allocator, unsigned int size); +typedef void (*MEMAllocatorFreeFunc)(struct MEMAllocator* allocator, void* block); + +typedef struct _MEMAllocatorFuncs { + MEMAllocatorAllocFunc allocFunc; + MEMAllocatorFreeFunc freeFunc; +} MEMAllocatorFuncs; + +typedef struct _MEMAllocator { + MEMAllocatorFuncs *funcs; + void *heap; + unsigned int heapParam1; + unsigned int heapParam2; +} MEMAllocator; + +#endif // _RVL_MEM_H diff --git a/include/wii_cnt_crypt.h b/include/wii_cnt_crypt.h new file mode 100644 index 0000000..ebabe67 --- /dev/null +++ b/include/wii_cnt_crypt.h @@ -0,0 +1,26 @@ +/* + RB3Enhanced - wii_cnt_crypt.h +*/ + +#ifdef RB3E_WII + +#include + +typedef struct _RB3E_CNTFileSD { + int fd; + uint32_t titleId; + int contentLength; + int startOffset; + uint16_t contentIndex; + struct AES_ctx *aesCtx; + uint8_t *arcHeader; + int lastBlockIndex; + uint8_t lastBlock[0x10]; + uint8_t lastBlockEnc[0x10]; +} RB3E_CNTFileSD; + +RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath); +void RB3E_CloseCNTFileSD(RB3E_CNTFileSD *file); +void RB3E_CNTFileRead(RB3E_CNTFileSD *file, int offset, uint8_t *buffer, int length); + +#endif // RB3E_WII diff --git a/include/wii_cnt_hooks.h b/include/wii_cnt_hooks.h new file mode 100644 index 0000000..1f84958 --- /dev/null +++ b/include/wii_cnt_hooks.h @@ -0,0 +1,9 @@ +/* + RB3Enhanced - wii_cnt_hooks.h +*/ + +#ifdef RB3E_WII + +void InitCNTHooks(); + +#endif diff --git a/source/_functions.c b/source/_functions.c index 898db47..c219990 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -117,6 +117,12 @@ RB3E_STUB(OSFatal) RB3E_STUB(OSSetErrorHandler) RB3E_STUB(PPCHalt) RB3E_STUB(OSReturnToMenu) +RB3E_STUB(ARCInitHandle) +RB3E_STUB(contentInitHandleTitleNAND) +RB3E_STUB(CNTReleaseHandle) +RB3E_STUB(CNTRead) +RB3E_STUB(EC_GetContentInfos) +RB3E_STUB(CNTOpen) #endif RB3E_STUB(RB3EStubEnd); diff --git a/source/aes.c b/source/aes.c new file mode 100644 index 0000000..9ab5131 --- /dev/null +++ b/source/aes.c @@ -0,0 +1,575 @@ +#ifdef RB3E_WII + +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 3ad77bb40d7a3660a89ecaf32466ef97 + f5d3d58503b9699de785895a96fdbaaf + 43b1cd7f598ece23881b00e3ed030688 + 7b0c785e27e8ad3f8223207104725dd4 + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For AES192/256 the key size is proportionally larger. + +*/ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include // CBC mode, for memset +#include "aes.h" + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 + +#if defined(AES256) && (AES256 == 1) + #define Nk 8 + #define Nr 14 +#elif defined(AES192) && (AES192 == 1) + #define Nk 6 + #define Nr 12 +#else + #define Nk 4 // The number of 32 bit words in a key. + #define Nr 10 // The number of rounds in AES Cipher. +#endif + +// jcallan@github points out that declaring Multiply as a function +// reduces code size considerably with the Keil ARM compiler. +// See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 +#ifndef MULTIPLY_AS_A_FUNCTION + #define MULTIPLY_AS_A_FUNCTION 0 +#endif + + + + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + + + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static const uint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; +#endif + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const uint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +/* +static uint8_t getSBoxValue(uint8_t num) +{ + return sbox[num]; +} +*/ +#define getSBoxValue(num) (sbox[(num)]) + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) +{ + unsigned i, j, k; + uint8_t tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0]=RoundKey[k + 0]; + tempa[1]=RoundKey[k + 1]; + tempa[2]=RoundKey[k + 2]; + tempa[3]=RoundKey[k + 3]; + + } + + if (i % Nk == 0) + { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } +#if defined(AES256) && (AES256 == 1) + if (i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } +#endif + j = i * 4; k=(i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) +{ + KeyExpansion(ctx->RoundKey, key); +} +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) +{ + KeyExpansion(ctx->RoundKey, key); + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) +{ + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +#endif + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) +{ + uint8_t i,j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to left + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +static uint8_t xtime(uint8_t x) +{ + return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +} + +// MixColumns function mixes the columns of the state matrix +static void MixColumns(state_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; + Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; + Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; + Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; + Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary +// The compiler seems to be able to vectorize the operation better this way. +// See https://github.com/kokke/tiny-AES-c/pull/34 +#if MULTIPLY_AS_A_FUNCTION +static uint8_t Multiply(uint8_t x, uint8_t y) +{ + return (((y & 1) * x) ^ + ((y>>1 & 1) * xtime(x)) ^ + ((y>>2 & 1) * xtime(xtime(x))) ^ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ + } +#else +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * xtime(x)) ^ \ + ((y>>2 & 1) * xtime(xtime(x))) ^ \ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ + +#endif + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +/* +static uint8_t getSBoxInvert(uint8_t num) +{ + return rsbox[num]; +} +*/ +#define getSBoxInvert(num) (rsbox[(num)]) + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns(state_t* state) +{ + int i; + uint8_t a, b, c, d; + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +static void InvShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to right + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + // Rotate second row 2 columns to right + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to right + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +// Cipher is the main function that encrypts the PlainText. +static void Cipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (round = 1; ; ++round) + { + SubBytes(state); + ShiftRows(state); + if (round == Nr) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static void InvCipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without InvMixColumn() + for (round = (Nr - 1); ; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + if (round == 0) { + break; + } + InvMixColumns(state); + } + +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ +#if defined(ECB) && (ECB == 1) + + +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher((state_t*)buf, ctx->RoundKey); +} + +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher((state_t*)buf, ctx->RoundKey); +} + + +#endif // #if defined(ECB) && (ECB == 1) + + + + + +#if defined(CBC) && (CBC == 1) + + +static void XorWithIv(uint8_t* buf, const uint8_t* Iv) +{ + uint8_t i; + for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size + { + buf[i] ^= Iv[i]; + } +} + +void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t *Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + XorWithIv(buf, Iv); + Cipher((state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } + +} + +#endif // #if defined(CBC) && (CBC == 1) + + + +#if defined(CTR) && (CTR == 1) + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + uint8_t buffer[AES_BLOCKLEN]; + + size_t i; + int bi; + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); + Cipher((state_t*)buffer,ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + ctx->Iv[bi] += 1; + break; + } + bi = 0; + } + + buf[i] = (buf[i] ^ buffer[bi]); + } +} + +#endif // #if defined(CTR) && (CTR == 1) + +#endif // RB3E_WII diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index a5052eb..a941f7f 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -78,6 +78,7 @@ void *NewFileHook(char *fileName, int flags) return FileSD_New(fileName, flags); } #ifdef RB3EDEBUG + // only on debug builds and only on Dolphin, we can read from NAND if (RB3E_IsEmulator() && memcmp(fileName, "nand/", 5) == 0) { return FileWiiNAND_New(fileName + 4); } @@ -433,6 +434,8 @@ void ApplyHooks() RB3E_MSG("Hooks applied!", NULL); } +void InitCNTHooks(); + void StartupHook(void *ThisApp, int argc, char **argv) { RB3E_MSG("Loaded! Version " RB3E_BUILDTAG " (" RB3E_BUILDCOMMIT ")", NULL); @@ -451,6 +454,11 @@ void StartupHook(void *ThisApp, int argc, char **argv) // apply any patches that are only added after config loads ApplyConfigurablePatches(); +#ifdef RB3E_WII + if (RB3E_Mounted && config.LegacySDMode == false) + InitCNTHooks(); +#endif + // start the game by calling the proper app constructor RB3E_MSG("Starting Rock Band 3...", NULL); AppConstructor(ThisApp, argc, argv); diff --git a/source/wii.c b/source/wii.c index bf6129c..9f8d0dd 100644 --- a/source/wii.c +++ b/source/wii.c @@ -63,6 +63,7 @@ int RB3E_IsEmulator() if (HasRunDetection) return DetectionResult; DetectionResult = DetectDolphin(); + HasRunDetection = 1; return DetectionResult; } diff --git a/source/wii_cnt_crypt.c b/source/wii_cnt_crypt.c new file mode 100644 index 0000000..fb79273 --- /dev/null +++ b/source/wii_cnt_crypt.c @@ -0,0 +1,308 @@ +/* + RB3Enhanced - wii_cnt_crypt.c + Handling for encrypted and unencrypted Wii CNT(, CNTSD, ARC) files +*/ + +#ifdef RB3E_WII + +#include +#include +#include +#include +#include +#include "rvl/cnt.h" +#include "rvl/wad.h" +#include "rvl/tmd.h" +#include "rb3/Mem.h" +#include "aes.h" +#include "ports.h" +#include "rb3enhanced.h" +#include "wii_cnt_crypt.h" + +#define PRINT_BLOCK(name, x) RB3E_DEBUG(name ": %08x%08x%08x%08x", *(uint32_t *)&x[0], *(uint32_t *)&x[4], *(uint32_t *)&x[8], *(uint32_t *)&x[12]) + +// to be filled in by the brainslug launcher +uint8_t RB3E_ConsolePRNGKey[0x10]; + +uint64_t OSGetTime(); +uint64_t timeSpentInAES = 0; +uint64_t timeSpentInSD = 0; +uint64_t timeSpentInSeek = 0; +void RB3E_CNTFileReadBlock(RB3E_CNTFileSD *file, int blockIndex) +{ + // if we're trying to read the same block we're on, don't do anything + if (blockIndex == file->lastBlockIndex) + return; + + uint64_t time = 0; + if (file->aesCtx == NULL) // unencrypted reads + { + if (blockIndex == 0) + { + SD_seek(file->fd, file->startOffset, SEEK_SET); + } + else if (blockIndex != file->lastBlockIndex + 1) + { + RB3E_DEBUG("WARNING! CNTFileReadBlock had to seek back, this is gonna hurt", NULL); + SD_seek(file->fd, file->startOffset + (blockIndex * 0x10), SEEK_SET); + } + SD_read(file->fd, file->lastBlock, 0x10); + file->lastBlockIndex = blockIndex; + } + else // encrypted reads + { + if (blockIndex == 0) + { + time = OSGetTime(); + // block index 0 uses the IV derived from the content index + memset(file->aesCtx->Iv, 0, 0x10); + *(uint16_t *)file->aesCtx->Iv = file->contentIndex; + SD_seek(file->fd, file->startOffset, SEEK_SET); + timeSpentInSeek += OSGetTime() - time; + } + else if (blockIndex == file->lastBlockIndex + 1) + { + time = OSGetTime(); + // if we're reading in the next block, we don't need to seek backwards + memcpy(file->aesCtx->Iv, file->lastBlockEnc, 0x10); + timeSpentInSeek += OSGetTime() - time; + } + else + { + time = OSGetTime(); + RB3E_DEBUG("WARNING! CNTFileReadBlock had to seek back, this is gonna hurt", NULL); + // all other blocks use the IV with the previous block's ciphertext + SD_seek(file->fd, file->startOffset + ((blockIndex - 1) * 0x10), SEEK_SET); + SD_read(file->fd, file->aesCtx->Iv, 0x10); + timeSpentInSeek += OSGetTime() - time; + } + time = OSGetTime(); + // read and decrypt the current block + SD_read(file->fd, file->lastBlock, 0x10); + timeSpentInSD += OSGetTime() - time; + + time = OSGetTime(); + memcpy(file->lastBlockEnc, file->lastBlock, 0x10); // keep a copy of the encrypted last block to use as the next IV + // TODO(Emma): can we use the hardware AES engine to make this faster? + // TODO(Emma): can we + AES_CBC_decrypt_buffer(file->aesCtx, file->lastBlock, 0x10); + timeSpentInAES += OSGetTime() - time; + file->lastBlockIndex = blockIndex; + } +} + +void RB3E_CNTFileRead(RB3E_CNTFileSD *file, int offset, uint8_t *buffer, int length) +{ + int blockOffset = offset & 0xF; + int curBlock = (offset & ~0xF) >> 4; + int readBytes = 0; + uint64_t timeStart = OSGetTime(); + timeSpentInAES = 0; + timeSpentInSD = 0; + timeSpentInSeek = 0; + while (readBytes < length) + { + int toRead = 0x10 - blockOffset; + if (length - readBytes < 0x10) + toRead -= (toRead - (length - readBytes)); + if (toRead < 1 || toRead > 0x10) + { + RB3E_MSG("TRIED TO READ 0x%x BYTE BLOCK!", toRead); + break; + } + RB3E_CNTFileReadBlock(file, curBlock); + memcpy(buffer + readBytes, file->lastBlock + blockOffset, toRead); + //RB3E_DEBUG("Wrote 0x%x to %p", toRead, buffer + readBytes); + curBlock++; + blockOffset = 0; + readBytes += toRead; + } + RB3E_DEBUG("%p:0x%x:0x%x - %lli tb", buffer, offset, length, OSGetTime() - timeStart); + RB3E_DEBUG("seek %lli sd %lli aes %lli", timeSpentInSeek, timeSpentInSD, timeSpentInAES); +} + +void RB3E_CloseCNTFileSD(RB3E_CNTFileSD *file) +{ + if (file->fd != -1) + { + RB3E_CloseFile(file->fd); + file->fd = -1; + } + if (file->aesCtx != NULL) + { + MemFree(file->aesCtx); + file->aesCtx = NULL; + } + if (file->arcHeader != NULL) + { + MemFree(file->arcHeader); + file->arcHeader = NULL; + } + MemFree(file); +} + +static inline int alignoffset(int off, int align) +{ + return ((off + align - 1) & ~(align - 1)); +} + +static inline int getFlippedContent(uint8_t *contentFlags) +{ + int index = 0, i = 0, j = 0; + for (i = 0; i < 0x40; i++) + { + for (j = 0; j < 8; j++) + { + unsigned char mask = (1 << j); + if (contentFlags[i] == mask) goto found; + index++; + } + } +found: + return index; +} + +RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) +{ + // open a handle to the file + int fd = RB3E_OpenFile(filepath, 0); + if (fd == -1) + { + RB3E_MSG("Failed to open CNT archive '%s'", filepath); + return NULL; + } + // allocate a buffer for our file metadata + RB3E_CNTFileSD *file = (RB3E_CNTFileSD *)MemAlloc(sizeof(RB3E_CNTFileSD), 0); + if (file == NULL) + { + RB3E_MSG("Could not allocate enough memory for CNTSD file", NULL); + RB3E_CloseFile(fd); + return NULL; + } + memset(file, 0, sizeof(RB3E_CNTFileSD)); + file->fd = fd; + file->lastBlockIndex = -1; + // read the header of the file + uint32_t magic[2]; + RB3E_ReadFile(fd, 0, magic, 8); + if (magic[1] == BACKUP_WAD_MAGIC) + { + // we're an encrypted file, allocate an AES context + file->aesCtx = MemAlloc(sizeof(struct AES_ctx), 0); + if (file->aesCtx == NULL) + { + RB3E_MSG("Failed to allocate space for the AES context", NULL); + RB3E_CloseCNTFileSD(file); + return NULL; + } + // read the WAD header and get the current content index from it + backup_wad_header_t wad; + RB3E_ReadFile(fd, 0, &wad, sizeof(backup_wad_header_t)); + int index = getFlippedContent(wad.included_contents); + RB3E_MSG("Host %.4s, TMD size 0x%x, Content size 0x%x", wad.title_id.name.code, wad.content_tmd_size, wad.content_data_size); + if (index == 0 || index > 511) + { + RB3E_MSG("! Invalid index %i!", index); + RB3E_CloseCNTFileSD(file); + return NULL; + } + RB3E_DEBUG("Content index %i", index); + file->contentIndex = index; + // read the TMD to get the true content length + tmd_header_t tmd; + RB3E_ReadFile(fd, sizeof(backup_wad_header_t), &tmd, sizeof(tmd_header_t)); + if (tmd.num_contents < index) + { + RB3E_MSG("Invalid number of contents!", NULL); + RB3E_CloseCNTFileSD(file); + return NULL; + } + // i don't know if this is *always* true but i'm assuming it is true that content indices are always sequential + tmd_content_t tmd_cnt; + RB3E_ReadFile(fd, sizeof(backup_wad_header_t) + sizeof(tmd_header_t) + (index * sizeof(tmd_content_t)), &tmd_cnt, sizeof(tmd_content_t)); + if (tmd_cnt.index != index) + { + RB3E_MSG("Wrong TMD content index (tmd:%i != flipped:%i)", tmd_cnt.index, index); + RB3E_CloseCNTFileSD(file); + return NULL; + } + RB3E_MSG("%.4s/%03i - content: %08x, size: 0x%llx", tmd.title_id.name.code, index, tmd_cnt.id, tmd_cnt.size); + file->titleId = *(uint32_t *)tmd.title_id.name.code; + file->contentLength = tmd_cnt.size; + // get the start of the encrypted data blob + int startOfData = alignoffset(sizeof(backup_wad_header_t) + wad.content_tmd_size, 0x40); + RB3E_DEBUG("Content starts at 0x%x", startOfData); + file->startOffset = startOfData; + // try to decrypt it with the NULL PRNG key (sZA-sZG) + RB3E_DEBUG("Attempting to read with NULLed key", NULL); + static uint8_t nullKey[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + PRINT_BLOCK("Key", nullKey); + AES_init_ctx(file->aesCtx, nullKey); + RB3E_CNTFileReadBlock(file, 0); + if (*(uint32_t *)file->lastBlock != ARC_MAGIC) + { + // that failed - try to decrypt it with the console-specific PRNG key (sZH+) + RB3E_DEBUG("Trying console specific PRNG key", NULL); + PRINT_BLOCK("Key", RB3E_ConsolePRNGKey); + AES_init_ctx(file->aesCtx, RB3E_ConsolePRNGKey); + file->lastBlockIndex = -1; + RB3E_CNTFileReadBlock(file, 0); + if (*(uint32_t *)file->lastBlock != ARC_MAGIC) + { + // couldn't decrypt + RB3E_MSG("Failed to decrypt the BIN file", NULL); + PRINT_BLOCK("Last Block", file->lastBlock); + RB3E_CloseCNTFileSD(file); + return NULL; + } + } + RB3E_MSG("Opened BIN successfully!", NULL); + // read the ARC file header into RAM + u8_header_t archeader; + RB3E_CNTFileRead(file, 0, (uint8_t *)&archeader, sizeof(u8_header_t)); + // allocate the size of the header - data_offset doubles as the length of the full "header" + void *fullarcheader = MemAlloc(archeader.data_offset, 0); + if (fullarcheader == NULL) + { + RB3E_MSG("Failed to allocate space for the header", NULL); + RB3E_CloseCNTFileSD(file); + return NULL; + } + // read the header, including node and string tables + RB3E_CNTFileRead(file, 0, fullarcheader, archeader.data_offset); + file->arcHeader = fullarcheader; + RB3E_DEBUG("Created file %p", file); + return file; + } + else if (magic[0] == ARC_MAGIC) + { + // untested, just try to handle the ARC file directly + file->aesCtx = NULL; + file->contentIndex = 0xFFFF; // TODO(Emma): get this from the filename and a TMD file + file->contentLength = RB3E_FileSize(file->fd); + file->startOffset = 0; + file->titleId = 0x72423345; // 'rB3E' + // read the ARC file header into RAM + u8_header_t archeader; + RB3E_CNTFileRead(file, 0, (uint8_t *)&archeader, sizeof(u8_header_t)); + // allocate the size of the header - data_offset doubles as the length of the full "header" + void *fullarcheader = MemAlloc(archeader.data_offset, 0); + if (fullarcheader == NULL) { + RB3E_MSG("Failed to allocate space for the header", NULL); + RB3E_CloseCNTFileSD(file); + return NULL; + } + // read the header, including node and string tables + RB3E_CNTFileRead(file, 0, fullarcheader, archeader.data_offset); + file->arcHeader = fullarcheader; + return file; + } + else + { + RB3E_MSG("'%s' was not valid CNT file.", filepath); + RB3E_CloseCNTFileSD(file); + return NULL; + } +} + +#endif // RB3E_WII diff --git a/source/wii_cnt_hooks.c b/source/wii_cnt_hooks.c new file mode 100644 index 0000000..b550b0a --- /dev/null +++ b/source/wii_cnt_hooks.c @@ -0,0 +1,174 @@ +/* + RB3Enhanced - wii_cnt_hooks.c + Hooks to CNT and EC functions to allow for custom content loading. +*/ + +#ifdef RB3E_WII + +#include +#include +#include +#include +#include "rvl/cnt.h" +#include "rvl/ec.h" +#include "rb3enhanced.h" +#include "utilities.h" +#include "ports.h" +#include "ppcasm.h" +#include "wii_cnt_crypt.h" + +int contentInitHandleTitleNAND_hook(unsigned long long title_id, unsigned int content_index, cnt_handle *handle, MEMAllocator *allocator) +{ + // make a file path of the SD DLC file + char titleid[8]; + char sdfilepath[256]; + memcpy(titleid, &title_id, 8); + sprintf(sdfilepath, "sd:/private/wii/data/%.4s/%03i.bin", &titleid[4], content_index); + // if the file exists then we're going to be opening that + if (RB3E_FileExists(sdfilepath)) + { + // open the bin file on the SD + RB3E_CNTFileSD *cntfilesd = RB3E_OpenCNTFileSD(sdfilepath); + if (cntfilesd == NULL) { + return -1; // TODO: what's the proper CNT error for "file not found"? + } + // open an ARC handle + arc_handle_t archandle; + ARCInitHandle(cntfilesd->arcHeader, &archandle); + // fill our output struct + memcpy(&handle->nand.ArcHandle, &archandle, sizeof(arc_handle_t)); + handle->nand.FileDescriptor = -0xBEEF; // dude is hungry + handle->nand.allocator = allocator; + handle->type = cnthandle_nand; + // sneak the handle to our RB3E_CNTFileSD in there + handle->nand.ArcHandle.header->reserved[0] = (unsigned int)cntfilesd; + // return success + return 0; + } + // fall back to reading from NAND + return contentInitHandleTitleNAND(title_id, content_index, handle, allocator); +} + +int CNTReleaseHandle_hook(cnt_handle *handle) +{ + // check if the file is reading from NAND and has our fake beef fd + if (handle->type == cnthandle_nand && handle->nand.FileDescriptor == -0xBEEF) + { + // get out the handle to our RB3E_CNTFileSD + RB3E_CNTFileSD *cntfilesd = (RB3E_CNTFileSD *)handle->nand.ArcHandle.header->reserved[0]; + RB3E_DEBUG("Freeing %p", cntfilesd); + RB3E_CloseCNTFileSD(cntfilesd); + // we don't call the original release function as we manage our own memory + return 0; + } + // fall back to the original release function + return CNTReleaseHandle(handle); +} + +int CNTRead_hook(cnt_file_info *file_info, void *buffer, int size) +{ + // check if the file is reading from NAND and has our fake beef fd + if (file_info->type == cnthandle_nand && file_info->nand.CntHandle->FileDescriptor == -0xBEEF) + { + //RB3E_DEBUG("File read %p 0x%x", buffer, size); + RB3E_CNTFileSD *cntfilesd = (RB3E_CNTFileSD *)file_info->nand.CntHandle->ArcHandle.header->reserved[0]; + RB3E_CNTFileRead(cntfilesd, file_info->nand.startoffset + file_info->nand.readOffset, buffer, size); + file_info->nand.readOffset += size; + return size; + } + // fall back to the original read function + return CNTRead(file_info, buffer, size); +} + +int EC_GetContentInfos_hook(unsigned long long titleId, ec_content_info_t *contentInfos, int *numContentInfos) +{ + // make a file path of the SD DLC folder + char titleid[8]; + char sddirpath[256]; + memcpy(titleid, &titleId, 8); + sprintf(sddirpath, "sd:/private/wii/data/%.4s/", &titleid[4]); + if (numContentInfos != NULL) + { + int originalNumContentInfos = *numContentInfos; + int r = 0; + DIR_STATE_STRUCT ds; + if (SD_diropen(&ds, sddirpath) != NULL) + { + RB3E_DEBUG("Enumerating %s", sddirpath); + // call the original so information about files on the NAND are populated + EC_GetContentInfos(titleId, contentInfos, numContentInfos); + *numContentInfos = originalNumContentInfos; + // we *do* have a directory, check if we need to fill the contentInfos buffer + if (contentInfos != NULL) + { + char filename[MAX_FILENAME_LENGTH]; + struct stat fs; + while (true) + { + if (SD_dirnext(&ds, filename, &fs) == 0) + { + // hacky check to see if the filename is some form of "000.BIN" + if ((filename[0] >= '0' && filename[0] <= '9') && + (filename[1] >= '0' && filename[1] <= '9') && + (filename[2] >= '0' && filename[2] <= '9') && + (filename[3] == '.') && + (filename[4] & ~0x20) == 'B' && + (filename[5] & ~0x20) == 'I' && + (filename[6] & ~0x20) == 'N') + { + // make a content index out of the filename + int contentIndex = + ((filename[0] - '0') * 100) + ((filename[1] - '0') * 10) + (filename[2] - '0'); + if (contentIndex < originalNumContentInfos) + { + // mark that DLC as installed + contentInfos[contentIndex].flags = 3; + contentInfos[contentIndex].index = contentIndex; + contentInfos[contentIndex].type = 0x4001; // DLC + // ROCK BAND HACK - the filesize of the DLC is checked to determine if something is a song or not + contentInfos[contentIndex].size = (contentIndex & 1) == 0 ? 0x414141 : 1; + } + else + { + RB3E_DEBUG("%i is out of range of %i", contentIndex, originalNumContentInfos); + } + } + } + else break; + } + return r; + } + else + { + *numContentInfos = 512; // act like we have every possible content index + RB3E_DEBUG("Returning %i", *numContentInfos); + SD_dirclose(&ds); + } + return r; + } + else + { + RB3E_DEBUG("No SD DLC for %.4s", &titleid[4]); + // call the original function, we don't have our own directory + return EC_GetContentInfos(titleId, contentInfos, numContentInfos); + } + return r; + } + else + { + return EC_GetContentInfos(titleId, contentInfos, numContentInfos); + } +} + +void InitCNTHooks() +{ + POKE_32(PORT_SDMODECHECK, LI(3, 3)); + POKE_32(PORT_SDMODECHECK + 4, BLR); + POKE_B(&ARCInitHandle, PORT_ARCINITHANDLE); + HookFunction(PORT_CONTENTINITHANDLETITLENAND, contentInitHandleTitleNAND, contentInitHandleTitleNAND_hook); + HookFunction(PORT_CNTRELEASEHANDLE, CNTReleaseHandle, CNTReleaseHandle_hook); + HookFunction(PORT_CNTREAD, CNTRead, CNTRead_hook); + HookFunction(PORT_ECGETCONTENTINFOS, EC_GetContentInfos, EC_GetContentInfos_hook); +} + +#endif // RB3E_WII From 8e2e748b932592c981ff5fd6b3e0d7a6063f5eb0 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sun, 18 May 2025 02:40:50 +0100 Subject: [PATCH 041/123] [wii] correct filesize when enumerating CNTSD DLC, read key from SD --- include/wii_cnt_crypt.h | 25 ++++---- source/rb3enhanced.c | 4 ++ source/wii_cnt_crypt.c | 138 +++++++++++++++++++++++++++++++++++++--- source/wii_cnt_hooks.c | 6 +- 4 files changed, 150 insertions(+), 23 deletions(-) diff --git a/include/wii_cnt_crypt.h b/include/wii_cnt_crypt.h index ebabe67..6812683 100644 --- a/include/wii_cnt_crypt.h +++ b/include/wii_cnt_crypt.h @@ -7,20 +7,23 @@ #include typedef struct _RB3E_CNTFileSD { - int fd; - uint32_t titleId; - int contentLength; - int startOffset; - uint16_t contentIndex; - struct AES_ctx *aesCtx; - uint8_t *arcHeader; - int lastBlockIndex; - uint8_t lastBlock[0x10]; - uint8_t lastBlockEnc[0x10]; + int fd; // 0x0 + uint32_t titleId; // 0x4 + int contentLength; // 0x8 + int startOffset; // 0xC + uint16_t contentIndex; // 0x10 + uint16_t padding; // 0x12 - probably not needed + struct AES_ctx *aesCtx; // 0x14 + uint8_t *arcHeader; // 0x18 + int lastBlockIndex; // 0x1c + uint8_t aesKey[0x10]; // 0x20 + uint8_t lastBlock[0x10]; // 0x30 + uint8_t lastBlockEnc[0x10]; // 0x40 } RB3E_CNTFileSD; -RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath); +RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath, unsigned long long titleid, unsigned int index); void RB3E_CloseCNTFileSD(RB3E_CNTFileSD *file); void RB3E_CNTFileRead(RB3E_CNTFileSD *file, int offset, uint8_t *buffer, int length); +void TryToLoadPRNGKeyFromFile(); #endif // RB3E_WII diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index a941f7f..e9b3cb3 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -435,6 +435,7 @@ void ApplyHooks() } void InitCNTHooks(); +void TryToLoadPRNGKeyFromFile(); void StartupHook(void *ThisApp, int argc, char **argv) { @@ -456,7 +457,10 @@ void StartupHook(void *ThisApp, int argc, char **argv) #ifdef RB3E_WII if (RB3E_Mounted && config.LegacySDMode == false) + { + TryToLoadPRNGKeyFromFile(); InitCNTHooks(); + } #endif // start the game by calling the proper app constructor diff --git a/source/wii_cnt_crypt.c b/source/wii_cnt_crypt.c index fb79273..45cbf3d 100644 --- a/source/wii_cnt_crypt.c +++ b/source/wii_cnt_crypt.c @@ -22,7 +22,31 @@ #define PRINT_BLOCK(name, x) RB3E_DEBUG(name ": %08x%08x%08x%08x", *(uint32_t *)&x[0], *(uint32_t *)&x[4], *(uint32_t *)&x[8], *(uint32_t *)&x[12]) // to be filled in by the brainslug launcher -uint8_t RB3E_ConsolePRNGKey[0x10]; +uint8_t RB3E_ConsolePRNGKey[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +static uint8_t nullKey[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + +// This is really slow apparently +void IOS_AES_Decrypt(uint8_t *input, uint8_t *key, uint8_t *iv, uint8_t *output) +{ + static ios_fd_t aesFd = -1; + if (aesFd == -1) + aesFd = IOS_Open("/dev/aes", IPC_OPEN_NONE); + + ioctlv vec[4] __attribute__((align(32))); + // input vector + vec[0].data = input; + vec[0].len = 0x10; + vec[1].data = key; + vec[1].len = 0x10; + // output vector + vec[2].data = output; + vec[2].len = 0x10; + vec[3].data = iv; + vec[3].len = 0x10; + // decrypt + IOS_Ioctlv(aesFd, 0x3, 2, 2, vec); +} uint64_t OSGetTime(); uint64_t timeSpentInAES = 0; @@ -78,13 +102,12 @@ void RB3E_CNTFileReadBlock(RB3E_CNTFileSD *file, int blockIndex) } time = OSGetTime(); // read and decrypt the current block - SD_read(file->fd, file->lastBlock, 0x10); + SD_read(file->fd, file->lastBlockEnc, 0x10); timeSpentInSD += OSGetTime() - time; time = OSGetTime(); - memcpy(file->lastBlockEnc, file->lastBlock, 0x10); // keep a copy of the encrypted last block to use as the next IV - // TODO(Emma): can we use the hardware AES engine to make this faster? - // TODO(Emma): can we + //memcpy(file->lastBlockEnc, file->lastBlock, 0x10); // keep a copy of the encrypted last block to use as the next IV + memcpy(file->lastBlock, file->lastBlockEnc, 0x10); AES_CBC_decrypt_buffer(file->aesCtx, file->lastBlock, 0x10); timeSpentInAES += OSGetTime() - time; file->lastBlockIndex = blockIndex; @@ -162,7 +185,7 @@ static inline int getFlippedContent(uint8_t *contentFlags) return index; } -RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) +RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath, unsigned long long titleid, unsigned int index) { // open a handle to the file int fd = RB3E_OpenFile(filepath, 0); @@ -235,9 +258,9 @@ RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) file->startOffset = startOfData; // try to decrypt it with the NULL PRNG key (sZA-sZG) RB3E_DEBUG("Attempting to read with NULLed key", NULL); - static uint8_t nullKey[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; PRINT_BLOCK("Key", nullKey); AES_init_ctx(file->aesCtx, nullKey); + memcpy(file->aesKey, nullKey, 0x10); RB3E_CNTFileReadBlock(file, 0); if (*(uint32_t *)file->lastBlock != ARC_MAGIC) { @@ -245,6 +268,7 @@ RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) RB3E_DEBUG("Trying console specific PRNG key", NULL); PRINT_BLOCK("Key", RB3E_ConsolePRNGKey); AES_init_ctx(file->aesCtx, RB3E_ConsolePRNGKey); + memcpy(file->aesKey, RB3E_ConsolePRNGKey, 0x10); file->lastBlockIndex = -1; RB3E_CNTFileReadBlock(file, 0); if (*(uint32_t *)file->lastBlock != ARC_MAGIC) @@ -278,10 +302,10 @@ RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) { // untested, just try to handle the ARC file directly file->aesCtx = NULL; - file->contentIndex = 0xFFFF; // TODO(Emma): get this from the filename and a TMD file + file->contentIndex = index; file->contentLength = RB3E_FileSize(file->fd); file->startOffset = 0; - file->titleId = 0x72423345; // 'rB3E' + file->titleId = (uint32_t)titleid; // read the ARC file header into RAM u8_header_t archeader; RB3E_CNTFileRead(file, 0, (uint8_t *)&archeader, sizeof(u8_header_t)); @@ -305,4 +329,100 @@ RB3E_CNTFileSD *RB3E_OpenCNTFileSD(const char *filepath) } } +void ParseKeysFile(uint8_t *keys_data, size_t keys_len) +{ + RB3E_DEBUG("0x%x bytes key file", keys_len); + // check if its a BootMii-style keys.bin + if (keys_len >= 0x400 && *(uint32_t *)&keys_data[0x114] == 0xEBE42A22) + { + memcpy(RB3E_ConsolePRNGKey, keys_data + 0x168, 0x10); + } + // check if its an OTP file dump + else if (keys_len >= 0x100 && *(uint32_t *)&keys_data[0x14] == 0xEBE42A22) + { + memcpy(RB3E_ConsolePRNGKey, keys_data + 0x68, 0x10); + } + // check if its a straight up key + else if (keys_len == 0x10) + { + memcpy(RB3E_ConsolePRNGKey, keys_data, 0x10); + } + else + { + RB3E_DEBUG("FAILED TO LOAD KEY!", NULL); + return; + } + PRINT_BLOCK("Loaded Key", RB3E_ConsolePRNGKey); +} + +void TryToLoadPRNGKeyFromFile() +{ + uint8_t keys_buffer[0x400]; // size of keys.bin file + + // check if the loader already loaded a prng key + if (memcmp(RB3E_ConsolePRNGKey, nullKey, 0x10) != 0) + { + RB3E_DEBUG("PRNG key loaded, not scanning for file", NULL); + return; + } + + // DOLPHIN ONLY: try to load keys.bin from the NAND + if (RB3E_IsEmulator()) + { + ios_fd_t nandfd = IOS_Open("/keys.bin", IPC_OPEN_READ); + if (nandfd >= 0) + { + RB3E_DEBUG("Loading keys from NAND", NULL); + ios_ret_t readret = IOS_Read(nandfd, keys_buffer, sizeof(keys_buffer)); + IOS_Close(nandfd); + if (readret >= IPC_OK) + { + ParseKeysFile(keys_buffer, readret); + } + return; + } + } + + // try a few files on SD + // PRNG key as binary + if (RB3E_FileExists("sd:/prng_key.bin")) + { + int keysfd = RB3E_OpenFile("sd:/prng_key.bin", 0); + if (keysfd != -1) + { + RB3E_DEBUG("Loading keys from prng_key.bin", NULL); + int r = RB3E_ReadFile(keysfd, 0, keys_buffer, 0x10); + RB3E_CloseFile(keysfd); + ParseKeysFile(keys_buffer, r == 0x10 ? 0x10 : 0); + return; + } + } + // vWii/xyzzy OTP backup + if (RB3E_FileExists("sd:/otp.bin")) + { + int otpfd = RB3E_OpenFile("sd:/otp.bin", 0); + if (otpfd != -1) + { + RB3E_DEBUG("Loading keys from otp.bin", NULL); + int r = RB3E_ReadFile(otpfd, 0, keys_buffer, sizeof(keys_buffer)); + RB3E_CloseFile(otpfd); + ParseKeysFile(keys_buffer, r); + return; + } + } + // BootMii/xyzzy keys.bin backup + if (RB3E_FileExists("sd:/keys.bin")) + { + int keysfd = RB3E_OpenFile("sd:/keys.bin", 0); + if (keysfd != -1) + { + RB3E_DEBUG("Loading keys from keys.bin", NULL); + int r = RB3E_ReadFile(keysfd, 0, keys_buffer, sizeof(keys_buffer)); + RB3E_CloseFile(keysfd); + ParseKeysFile(keys_buffer, r); + return; + } + } +} + #endif // RB3E_WII diff --git a/source/wii_cnt_hooks.c b/source/wii_cnt_hooks.c index b550b0a..62d4bca 100644 --- a/source/wii_cnt_hooks.c +++ b/source/wii_cnt_hooks.c @@ -28,7 +28,7 @@ int contentInitHandleTitleNAND_hook(unsigned long long title_id, unsigned int co if (RB3E_FileExists(sdfilepath)) { // open the bin file on the SD - RB3E_CNTFileSD *cntfilesd = RB3E_OpenCNTFileSD(sdfilepath); + RB3E_CNTFileSD *cntfilesd = RB3E_OpenCNTFileSD(sdfilepath, title_id, content_index); if (cntfilesd == NULL) { return -1; // TODO: what's the proper CNT error for "file not found"? } @@ -125,8 +125,8 @@ int EC_GetContentInfos_hook(unsigned long long titleId, ec_content_info_t *conte contentInfos[contentIndex].flags = 3; contentInfos[contentIndex].index = contentIndex; contentInfos[contentIndex].type = 0x4001; // DLC - // ROCK BAND HACK - the filesize of the DLC is checked to determine if something is a song or not - contentInfos[contentIndex].size = (contentIndex & 1) == 0 ? 0x414141 : 1; + // the filesize of the DLC is checked to determine if something is a song or not + contentInfos[contentIndex].size = fs.st_size; } else { From 3b29b87edd3bb7c84db5cff4afb04913e9b08215 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 20 May 2025 11:06:14 +0100 Subject: [PATCH 042/123] [wii] allow unencrypted .app DLC from SD, very small speed improvement --- source/wii_cnt_crypt.c | 6 +++--- source/wii_cnt_hooks.c | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/source/wii_cnt_crypt.c b/source/wii_cnt_crypt.c index 45cbf3d..fa1d73e 100644 --- a/source/wii_cnt_crypt.c +++ b/source/wii_cnt_crypt.c @@ -88,7 +88,7 @@ void RB3E_CNTFileReadBlock(RB3E_CNTFileSD *file, int blockIndex) { time = OSGetTime(); // if we're reading in the next block, we don't need to seek backwards - memcpy(file->aesCtx->Iv, file->lastBlockEnc, 0x10); + //memcpy(file->aesCtx->Iv, file->lastBlockEnc, 0x10); timeSpentInSeek += OSGetTime() - time; } else @@ -102,12 +102,12 @@ void RB3E_CNTFileReadBlock(RB3E_CNTFileSD *file, int blockIndex) } time = OSGetTime(); // read and decrypt the current block - SD_read(file->fd, file->lastBlockEnc, 0x10); + SD_read(file->fd, file->lastBlock, 0x10); timeSpentInSD += OSGetTime() - time; time = OSGetTime(); //memcpy(file->lastBlockEnc, file->lastBlock, 0x10); // keep a copy of the encrypted last block to use as the next IV - memcpy(file->lastBlock, file->lastBlockEnc, 0x10); + //memcpy(file->lastBlock, file->lastBlockEnc, 0x10); AES_CBC_decrypt_buffer(file->aesCtx, file->lastBlock, 0x10); timeSpentInAES += OSGetTime() - time; file->lastBlockIndex = blockIndex; diff --git a/source/wii_cnt_hooks.c b/source/wii_cnt_hooks.c index 62d4bca..cf39ecf 100644 --- a/source/wii_cnt_hooks.c +++ b/source/wii_cnt_hooks.c @@ -107,14 +107,13 @@ int EC_GetContentInfos_hook(unsigned long long titleId, ec_content_info_t *conte { if (SD_dirnext(&ds, filename, &fs) == 0) { - // hacky check to see if the filename is some form of "000.BIN" + // hacky check to see if the filename is some form of "000.BIN" or "000.APP" if ((filename[0] >= '0' && filename[0] <= '9') && (filename[1] >= '0' && filename[1] <= '9') && (filename[2] >= '0' && filename[2] <= '9') && (filename[3] == '.') && - (filename[4] & ~0x20) == 'B' && - (filename[5] & ~0x20) == 'I' && - (filename[6] & ~0x20) == 'N') + (((filename[4] & ~0x20) == 'B' && (filename[5] & ~0x20) == 'I' && (filename[6] & ~0x20) == 'N') || + ((filename[4] & ~0x20) == 'A' && (filename[5] & ~0x20) == 'P' && (filename[6] & ~0x20) == 'P'))) { // make a content index out of the filename int contentIndex = From 2b3fc1dab51af78986be65eb981d140797dc0a5a Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 02:52:22 -0500 Subject: [PATCH 043/123] xbox: fix typo in song metadata struct (#41) --- include/rb3/SongMetadata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rb3/SongMetadata.h b/include/rb3/SongMetadata.h index 2d8ff08..05aa6a2 100644 --- a/include/rb3/SongMetadata.h +++ b/include/rb3/SongMetadata.h @@ -24,7 +24,7 @@ typedef struct _SongMetadata #ifdef RB3E_WII char unknown6[0x10]; #else - char unknown6[0x18]; + char unknown6[0x14]; #endif Symbol genre; int animTempo; From 0d38b633539b1a5239438b1e19ee33a35c2738de Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 24 May 2025 19:38:26 +0100 Subject: [PATCH 044/123] [wii] INT_VALUE = 6 on Wii --- include/rb3/Data.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/rb3/Data.h b/include/rb3/Data.h index 80c8f00..97faccf 100644 --- a/include/rb3/Data.h +++ b/include/rb3/Data.h @@ -14,13 +14,21 @@ typedef union _DataNode_Value typedef enum _DataNode_Type { +#ifndef RB3E_WII INT_VALUE = 0, +#else + EMPTY = 0, +#endif FLOAT_VALUE = 1, VAR = 2, FUNC = 3, OBJECT = 4, SYMBOL = 5, +#ifndef RB3E_WII EMPTY = 6, +#else + INT_VALUE = 6, +#endif IFDEF = 7, ELSE = 8, ENDIF = 9, From d87713a9ab66153a66873796020921e3f83134fb Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 15:48:35 -0500 Subject: [PATCH 045/123] move ports to their proper spot --- .gitignore | 3 ++- include/ports_wii.h | 21 +++++++++++++++++++++ include/ports_xbox360.h | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9783ef0..bcba554 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ build_wii/ *.map *.o # automatically generated files -version.h \ No newline at end of file +version.h +.DS_Store diff --git a/include/ports_wii.h b/include/ports_wii.h index 842265b..2057140 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -28,6 +28,7 @@ #define PORT_UPDATEPRESENCEBLOCK_B 0x80188194 // branch after the failure case in a function that calls UpdatePresence #define PORT_MULTIPLAYER_CRASH 0x80018a78 // branch to a function that can crash in online multiplayer #define PORT_MULTIPLAYER_FIX 0x806ec0e8 // the function that doesn't crash +#define PORT_ADDTRACKVECTOR_BL 0x80480a88 // bl to vector_push_back inside of SongData::AddTrack // #define PORT_LOADOBJS_BCTRL 0x827562e4 // function patch addresses #define PORT_SETDISKERROR 0x8030ce7c // PlatformMgr::SetDiskError @@ -42,6 +43,7 @@ #define PORT_EXECUTEDTA 0x802cf7e0 // RockCentralGateway::ExecuteConfig #define PORT_BANDLABELSETDISPLAYTEXT 0x803b1858 // BandLabel::SetDisplayText #define PORT_SETSONGANDARTISTNAME 0x801b68a8 // BandLabel::SetSongAndArtistName +#define PORT_SETSONGNAMEFROMNODE 0x801b6358 // BandLabel::SetSongNameFromNode #define PORT_KEYSONGUITAR 0x80242ab4 // function that checks "key_keys_on_guitar" #define PORT_HMXFACTORYFUNCAT 0x8031b2f8 // HmxObjectFactoryFunc::_at #define PORT_WILLBENOSTRUM 0x80463010 // GameGemList::WillBeNoStrum @@ -51,6 +53,7 @@ #define PORT_GETWIDGETBYNAME 0x800d59b0 // GemManager::GetWidgetByName #define PORT_DATANODEEVALUATE 0x80322e9c // DataNode::Evaluate #define PORT_GETSLOTCOLOR 0x800e42a4 // TrackConfig::GetSlotColor +#define PORT_ADDSMASHERPLATETOVECTOR 0x804316d4 // AddSmasherPlateToVector #define PORT_USBWIIGETTYPE 0x806c1a3c // UsbWii::GetType #define PORT_FILE_EXISTS 0x802fa134 // FileExists #define PORT_QUEUEMESSAGE 0x80253c50 // PassiveMessagesPanel::QueueMessage @@ -64,6 +67,7 @@ #define PORT_BUILDINSTRUMENTSELECTION 0x802478a8 // BuildInstrumentSelectionList(?) - actual name not known #define PORT_PREPARESOMEVECTORMAYBE 0x80247c58 // Prepares some vector, used by BuildInstrumentSelectionList #define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back +#define PORT_VECTORPUSHBACK 0x800a6ef4 // vector_push_back #define PORT_POSTPROC_DOPOST 0x806b52b4 // WiiPostProc::DoPost #define PORT_MUSICLIBRARYSELECTMAYBE 0x80230d64 // Selects an entry in the Music Library screen - actual name not known #define PORT_GETSYMBOLBYGAMEORIGIN 0x8027dd3c // SongSortByRecent::GetSymbolByGameOrigin @@ -88,8 +92,25 @@ #define PORT_QUEUINGSOCKET_BIND 0x800478d4 // Quazal::QueuingSocket::Bind #define PORT_QUAZALSOCKET_BIND 0x8001cd10 // Quazal::Socket::Bind #define PORT_INITSONGMETADATA 0x805147a4 // InitSongMetadata +#define PORT_SONGMETADATACONSTRUCTOR 0x80514880 // SongMetadata::__ct +#define PORT_SONGMETADATALOAD 0x801d2090 // SongMetadata::Load #define PORT_UPDATEPRESENCE 0x801879d4 // PresenceMgr::UpdatePresence #define PORT_STEPSEQUENCEJOBSETSTEP 0x80025364 // Quazal::StepSequenceJob::SetStep +#define PORT_RNDTEXNEWOBJECT 0x80639904 // RndTex::NewObject +#define PORT_RNDMATNEWOBJECT 0x8063996c // RndMat::NewObject +#define PORT_RNDTEXSETBITMAP 0x8063fccc // RndTex::SetBitmap +#define PORT_RNDTEXSETBITMAP2 0x8063f830 // RndTex::SetBitmap2 +#define PORT_RNDTEXSETBITMAP3 0x8063fb2c // RndTex::SetBitmap3 +#define PORT_FILEPATHCONSTRUCTOR 0x8000ec5c // FilePath::__ct +#define PORT_MUSICLIBRARY_CT 0x8022d978 // MusicLibrary::__ct +#define PORT_MUSICLIBRARYMAT 0x80231c5c // MusicLibrary::Mat +#define PORT_NODESORTGETNODE 0x80279314 // MusicLibrary::GetNodeByIndex +#define PORT_GAMEGEMDB_CT 0x80460f64 // GameGemDB::__ct +#define PORT_ADDMULTIGEM 0x80461160 // GameGemDB::AddMultiGem +#define PORT_GETGAMELIST 0x8048553c // SongData::GetGameList +#define PORT_SONGSORTMGRGETSORT 0x80281b20 // SongSortMgr::GetSort +#define PORT_RNDMATSETDIFFUSETEX 0x8025ab90 // RndMat::SetDiffuseTex +#define PORT_DYNAMICTEX_CT 0x80292a70 // DynamicTex::__ct #define PORT_BINSTREAMWRITE 0x80342f48 // BinStream::Write #define PORT_BINSTREAMREAD 0x80342e7c // BinStream::Read #define PORT_BINSTREAMREADENDIAN 0x803430bc // BinStream::ReadEndian diff --git a/include/ports_xbox360.h b/include/ports_xbox360.h index 9b73cbd..d632192 100644 --- a/include/ports_xbox360.h +++ b/include/ports_xbox360.h @@ -48,6 +48,8 @@ #define PORT_LOADOBJS_BCTRL 0x827562e4 // bctrl to Object::PreLoad insie of DirLoader::LoadObjs #define PORT_SONGMGR_ISDEMO_CHECK 0x82575f9c // "bne" after IsUGC check inside SongMgr::IsDemo #define PORT_STAGEKIT_EXISTS 0x8228d03c // StageKit check. nop over to allow for fog command without a physical StageKit connected. +#define PORT_ADDTRACKVECTOR_BL 0x82777b70 // bl to vector_push_back inside of SongData::AddTrack +#define PORT_GETGAMELIST 0x82770730 // SongData::GetGameList #define PORT_VERTEX_READ_1 0x82418704 // call to Vector3::operator>> to read vertex position #define PORT_VERTEX_READ_2 0x82418748 // call to Vector3::operator>> to read vertex normals #define PORT_MULTIPLAYER_CRASH 0x82ae6880 // branch to a function that can crash in online multiplayer @@ -89,6 +91,7 @@ #define PORT_RANDOMINT 0x824f2f90 // RandomInt(min, max) #define PORT_GETWIDGETBYNAME 0x82b9b880 // GemManager::GetWidgetByName #define PORT_GETSLOTCOLOR 0x82baa308 // TrackConfig::GetSlotColor +#define PORT_ADDSMASHERPLATETOVECTOR 0x82356980 // AddSmasherPlateToVector #define PORT_ARCHIVE_CT 0x82514408 // Archive::_ct #define PORT_ARCHIVE_SETLOCATIONHARDDRIVE 0x82512b00 // Archive::SetLocationHardDrive #define PORT_ARCHIVE_MERGE 0x82513ee8 // Archive::Merge @@ -112,6 +115,7 @@ #define PORT_BUILDINSTRUMENTSELECTION 0x82668c70 // BuildInstrumentSelectionList(?) - actual name not known #define PORT_PREPARESOMEVECTORMAYBE 0x82796d90 // Prepares some vector, used by BuildInstrumentSelectionList #define PORT_SOMEVECTORPUSHBACKMAYBE 0x82b6aa10 // vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back +#define PORT_VECTORPUSHBACK 0x82b5f808 // vector_push_back #define PORT_POSTPROC_DOPOST 0x82b89a08 // NgPostProc::DoPost #define PORT_MUSICLIBRARYSELECTMAYBE 0x8253EB00 // Selects an entry in the Music Library screen - actual name not known #define PORT_GETSYMBOLBYGAMEORIGIN 0x8265bb78 // SongSortByRecent::GetSymbolByGameOrigin @@ -133,8 +137,24 @@ #define PORT_MEMPRINT 0x827bc970 // MemPrint #define PORT_MEMNUMHEAPS 0x827bb628 // MemNumHeaps #define PORT_INITSONGMETADATA 0x827aa450 // InitSongMetadata +#define PORT_SONGMETADATACONSTRUCTOR 0x827aa6e8 // SongMetadata::__ct +#define PORT_SONGMETADATALOAD 0x825a3f58 // SongMetadata::Load #define PORT_UPDATEPRESENCE 0x82680430 // PresenceMgr::UpdatePresence #define PORT_STEPSEQUENCEJOBSETSTEP 0x82af92b8 // Quazal::StepSequenceJob::SetStep +#define PORT_RNDTEXNEWOBJECT 0x82273de0 // RndTex::NewObject +#define PORT_RNDMATNEWOBJECT 0x8240f5d0 // RndMat::NewObject +#define PORT_RNDTEXSETBITMAP 0x823ff678 // RndTex::SetBitmap +#define PORT_RNDTEXSETBITMAP2 0x823ff240 // RndTex::SetBitmap2 +#define PORT_RNDTEXSETBITMAP3 0x823ff510 // RndTex::SetBitmap3 +#define PORT_FILEPATHCONSTRUCTOR 0x82270210 // FilePath::__ct +#define PORT_MUSICLIBRARY_CT 0x825451c8 // MusicLibrary::__ct +#define PORT_MUSICLIBRARYMAT 0x8253b440 // MusicLibrary::Mat +#define PORT_NODESORTGETNODE 0x825bf708 // MusicLibrary::GetNodeByIndex +#define PORT_GAMEGEMDB_CT 0x827931e0 // GameGemDB::__ct +#define PORT_ADDMULTIGEM 0x827930d8 // GameGemDB::AddMultiGem +#define PORT_SONGSORTMGRGETSORT 0x82595ff8 // SongSortMgr::GetSort +#define PORT_DYNAMICTEX_CT 0x825f2318 // DynamicTex::__ct +#define PORT_RNDMATSETDIFFUSETEX 0x8238b130 // RndMat::SetDiffuseTex #define PORT_BINSTREAMWRITE 0x827c4f58 // BinStream::Write #define PORT_BINSTREAMREAD 0x827c4ea8 // BinStream::Read #define PORT_BINSTREAMREADENDIAN 0x827c5058 // BinStream::ReadEndian From 6dde9c436dfa091571a3fc029f3463292b4ba25d Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 15:53:22 -0500 Subject: [PATCH 046/123] update calls to metadata struct --- source/GameHooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/GameHooks.c b/source/GameHooks.c index 2a918e2..307174f 100644 --- a/source/GameHooks.c +++ b/source/GameHooks.c @@ -39,7 +39,7 @@ void *GameConstructHook(void *theGame) // You just lost if (metadata != NULL) { RB3E_DEBUG("Metadata: %p", metadata); - RB3E_DEBUG("Started song: '%s' - %s (ID: %i, %s)", metadata->title.buf, metadata->artist.buf, metadata->mSongID, metadata->mShortName); + RB3E_DEBUG("Started song: '%s' - %s (ID: %i, %s)", metadata->title.buf, metadata->artist.buf, metadata->song_id, metadata->shortname); RB3E_SendEvent(RB3E_EVENT_SONG_NAME, metadata->title.buf, metadata->title.length); RB3E_SendEvent(RB3E_EVENT_SONG_ARTIST, metadata->artist.buf, metadata->artist.length); } From b0d1ea9f56764ee91d4f021210269a917735f6e1 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 15:59:34 -0500 Subject: [PATCH 047/123] oh --- include/rb3/ModifierManager.h | 2 +- source/rb3enhanced.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rb3/ModifierManager.h b/include/rb3/ModifierManager.h index 5d803f2..e7c41b2 100644 --- a/include/rb3/ModifierManager.h +++ b/include/rb3/ModifierManager.h @@ -11,7 +11,7 @@ typedef struct _Modifier bool enabled; } Modifier; -extern Modifier *ModifierIsActive(int thisModifierManager, Symbol symbol, bool defaultValue); +extern Modifier *ModifierActive(int thisModifierManager, Symbol symbol, bool defaultValue); extern void *ModifierManagerConstructor(int thisModifierManager, int unk); #endif // _MODIFIERMANAGER_H \ No newline at end of file diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 21e2e88..56090d7 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -33,7 +33,7 @@ void SetVenueHook(int *thisMetaPerformer, Symbol venue) Modifier *blackBackgroundModifier; SymbolConstruct(&blackBackground, "mod_black_background"); - blackBackgroundModifier = ModifierIsActive(*(int *)PORT_MODIFIERMGR_POINTER, blackBackground, 0); + blackBackgroundModifier = ModifierActive(*(int *)PORT_MODIFIERMGR_POINTER, blackBackground, 0); if (blackBackgroundModifier->enabled) venue.sym = "none"; From 153ad04569b8a83e0e482a54488c61bcb75d4ef5 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:00:48 -0500 Subject: [PATCH 048/123] oh duh --- source/GameHooks.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/GameHooks.c b/source/GameHooks.c index 307174f..4b5379a 100644 --- a/source/GameHooks.c +++ b/source/GameHooks.c @@ -26,9 +26,9 @@ void *GameConstructHook(void *theGame) // You just lost RB3E_EventBandInfo bandevent = {0}; #ifdef RB3E_XBOX Symbol song; - GetSongSymbol(&song, *(int *)PORT_THEMETAPERFORMER); + GetSongShortname(&song, *(int *)PORT_THEMETAPERFORMER); #else - Symbol song = GetSongSymbol(*(int *)PORT_THEMETAPERFORMER); + Symbol song = GetSongShortname(*(int *)PORT_THEMETAPERFORMER); #endif if (song.sym != NULL) { @@ -49,10 +49,10 @@ void *GameConstructHook(void *theGame) // You just lost bandUser = GetBandUserFromSlot(*(int *)PORT_THEBANDUSERMGR, i); if (bandUser != NULL) { - RB3E_DEBUG("BandUser %i: %p - Track: %i, Controller: %i, Difficulty: %i", i, bandUser, bandUser->mTrackType, bandUser->mControllerType, bandUser->mDifficulty); + RB3E_DEBUG("BandUser %i: %p - Track: %i, Controller: %i, Difficulty: %i", i, bandUser, bandUser->trackType, bandUser->controllerType, bandUser->difficulty); bandevent.MemberExists[i] = 1; - bandevent.Difficulty[i] = bandUser->mDifficulty; - bandevent.TrackType[i] = bandUser->mTrackType; + bandevent.Difficulty[i] = bandUser->difficulty; + bandevent.TrackType[i] = bandUser->trackType; } else { @@ -76,4 +76,4 @@ void *GameDestructHook(void *theGame, int r4) #endif return GameDestruct(theGame, r4); -} +} \ No newline at end of file From ede488521cedd35356708e8765e3f0da733f4377 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:05:00 -0500 Subject: [PATCH 049/123] names may have shifted --- include/rb3/MetaPerformer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rb3/MetaPerformer.h b/include/rb3/MetaPerformer.h index 9745d41..2d07f56 100644 --- a/include/rb3/MetaPerformer.h +++ b/include/rb3/MetaPerformer.h @@ -11,10 +11,10 @@ typedef struct _MetaPerformer extern void SetVenue(int *thisMetaPerformer, Symbol venue); #ifdef RB3E_XBOX -extern void GetSongSymbol(Symbol *symOut, int metaPerformer); +extern void GetSongShortname(Symbol *symOut, int metaPerformer); #else // this calling convention makes more sense? wtf microsoft -extern Symbol GetSongSymbol(int metaPerformer); +extern Symbol GetSongShortname(int metaPerformer); #endif #endif // _METAPERFORMER_H \ No newline at end of file From 6d86072aa198694623fab1ada3d74012a18c9727 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:06:34 -0500 Subject: [PATCH 050/123] these too --- source/_functions.c | 2 +- source/rb3enhanced.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/_functions.c b/source/_functions.c index be4ba77..eb01ec9 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -44,7 +44,7 @@ RB3E_STUB(FileExists) RB3E_STUB(QueueMessage) RB3E_STUB(GetMetadata) RB3E_STUB(GetSongIDFromShortname) -RB3E_STUB(GetSongSymbol) +RB3E_STUB(GetSongShortname) RB3E_STUB(GetBandUsers) RB3E_STUB(GetBandUserFromSlot) RB3E_STUB(ChunkStreamConstructor) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 56090d7..8e1a1ff 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -354,7 +354,7 @@ void InitialiseFunctions() POKE_B(&SetAddress, PORT_SETADDRESS); POKE_B(&QueueMessage, PORT_QUEUEMESSAGE); POKE_B(&MusicLibrarySelect, PORT_MUSICLIBRARYSELECTMAYBE); - POKE_B(&GetSongSymbol, PORT_GETSONGSYMBOL); + POKE_B(&GetSongShortname, PORT_GETSONGSHORTNAME); POKE_B(&GetMetadata, PORT_GETMETADATA); POKE_B(&GetSongIDFromShortname, PORT_GETSONGIDFROMSHORTNAME); POKE_B(&GetBandUsers, PORT_GETBANDUSERS); From a4c68e164cf1cfda4011df797bad39d610216636 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:12:08 -0500 Subject: [PATCH 051/123] sure --- include/rb3/BandUser.h | 6 +++--- source/SongParserHooks.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/rb3/BandUser.h b/include/rb3/BandUser.h index 0e01eb6..b3c7720 100644 --- a/include/rb3/BandUser.h +++ b/include/rb3/BandUser.h @@ -60,11 +60,11 @@ typedef struct _BandUser // BandUser members int unknown_0x0; int unknown_0x4; - Difficulty mDifficulty; + Difficulty difficulty; char mUnknown; char pad[3]; - TrackType mTrackType; - ControllerType mControllerType; + TrackType trackType; + ControllerType controllerType; char mTrackSelected; char mUnknown2; char pad2[2]; diff --git a/source/SongParserHooks.c b/source/SongParserHooks.c index 2069ebb..24a800c 100644 --- a/source/SongParserHooks.c +++ b/source/SongParserHooks.c @@ -21,7 +21,7 @@ int SongParserPitchToSlotHook(SongParser *thisSongParser, int pitch, int *diffic if (doubleBassModifier->enabled) { // make sure the current track type is drums - if (thisSongParser->mTrackType == DRUMS) + if (thisSongParser->TrackType == DRUMS) { // check for MIDI note pitch 95 (which is the 2x bass pedal note) if (pitch == 95) From ba8866d0a7be9639a9295743bf22c96de1a6d2a9 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:14:34 -0500 Subject: [PATCH 052/123] well yeah of course --- include/GemHooks.h | 67 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 12 deletions(-) diff --git a/include/GemHooks.h b/include/GemHooks.h index d42203c..f8af40c 100644 --- a/include/GemHooks.h +++ b/include/GemHooks.h @@ -1,12 +1,55 @@ -/* - RB3Enhanced - GemHooks.h - Hooks for dealing with gems. -*/ - -#include "rb3/GameGem.h" -#include "rb3/Symbol.h" - -int WillBeNoStrumHook(int *gameGemListPtr, int *multiGemInfoPtr); -int *GetWidgetByNameHook(int *gemManager, Symbol name); -int AddGameGemHook(GameGemList *gameGemList, GameGem *gem, NoStrumState noStrumState); -Symbol GetSlotColorHook(int *bandUser); \ No newline at end of file +#ifndef _GAMEGEM_H +#define _GAMEGEM_H + +#include "Symbol.h" + +typedef struct _GameGem +{ + float milliSeconds; + int tick; + short durationMs; + short durationTicks; + + unsigned char unkBitfield1; + unsigned char unkBitfield2; + unsigned char unkBitfield3; + char unk1 : 1; + char unk2 : 1; + char unk3 : 1; + + // There is likely a lot more to this for Pro Keys/Guitar/Bass/Drums in the previous bitfields, but this works fine for 5-lane instruments + char orange : 1; + char blue : 1; + char yellow : 1; + char red : 1; + char green : 1; + + char gemSeen : 1; // Updated in real-time once the player has seen this gem and either hit or missed it + char isHopo : 1; // Does nothing on the first note of the song even if set + char normalNote1 : 1; // Unsure what these both represent but sustains have them set to 0 and setting them to 0 forces a sustain + char normalNote2 : 1; + char unk5 : 1; + char unk6 : 1; + char unk7 : 1; + char unk8 : 1; + + // Unsure about these two + unsigned char unknown; + int unknown2; + + // There is more after this that I am too lazy to map. Probably pro instrument stuff +} GameGem; + +typedef enum _NoStrumState +{ + kStrumForceOn = 0, + kStrumForceOff = 1, + kStrumDefault = 2 +} NoStrumState; + +extern int WillBeNoStrum(int *gameGemListPtr, int *multiGemInfoPtr); +extern int AddGameGem(int *gameGemList, GameGem *gem, NoStrumState gemType); +extern int *GetWidgetByName(int *gemManager, Symbol sym); +extern Symbol GetSlotColor(int *bandUser); + +#endif // _GAMEGEM_H \ No newline at end of file From ae610f1ba98a38de224d5eadb1b16d99dd67a158 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:24:23 -0500 Subject: [PATCH 053/123] im bad at merging i guess --- include/rb3/GameGem.h | 62 +++++++++++-------------------------------- source/GameHooks.c | 2 +- source/_functions.c | 2 +- 3 files changed, 18 insertions(+), 48 deletions(-) diff --git a/include/rb3/GameGem.h b/include/rb3/GameGem.h index 996491d..f8af40c 100644 --- a/include/rb3/GameGem.h +++ b/include/rb3/GameGem.h @@ -1,8 +1,7 @@ #ifndef _GAMEGEM_H #define _GAMEGEM_H -#include "rb3/Symbol.h" -#include "rb3/Vector.h" +#include "Symbol.h" typedef struct _GameGem { @@ -14,7 +13,6 @@ typedef struct _GameGem unsigned char unkBitfield1; unsigned char unkBitfield2; unsigned char unkBitfield3; - char unk1 : 1; char unk2 : 1; char unk3 : 1; @@ -26,16 +24,18 @@ typedef struct _GameGem char red : 1; char green : 1; - char isPlayed : 1; // Updated in real-time once the player has seen this gem and either hit or missed it - char isHopo : 1; // Does nothing on the first note of the song even if set - char ignoreDuration : 1; - char isCymbal : 1; - char showChordNames : 1; - char showSlashes : 1; - char useAltDuration : 1; - char autoPlayable : 1; - unsigned char playableBy; - char isRealGuitar; + char gemSeen : 1; // Updated in real-time once the player has seen this gem and either hit or missed it + char isHopo : 1; // Does nothing on the first note of the song even if set + char normalNote1 : 1; // Unsure what these both represent but sustains have them set to 0 and setting them to 0 forces a sustain + char normalNote2 : 1; + char unk5 : 1; + char unk6 : 1; + char unk7 : 1; + char unk8 : 1; + + // Unsure about these two + unsigned char unknown; + int unknown2; // There is more after this that I am too lazy to map. Probably pro instrument stuff } GameGem; @@ -47,39 +47,9 @@ typedef enum _NoStrumState kStrumDefault = 2 } NoStrumState; -typedef struct _GameGemList -{ - int mHopoThreshold; - vector mGems; -} GameGemList; - -typedef struct _GameGemDB -{ - vector mGems; - int mHopoThreshold; -} GameGemDB; - -typedef struct _MultiGemInfo -{ - int track; - int slots; - float ms; - float duration_ms; - int tick; - int duration_ticks; - char ignore_duration; - char is_cymbal; - char pad[2]; - int players; - NoStrumState no_strum; -} MultiGemInfo; - -extern int WillBeNoStrum(GameGemList *thisGameGemList, int *multiGemInfoPtr); -extern int AddGameGem(GameGemList *gameGemList, GameGem *gem, NoStrumState gemType); -extern char AddMultiGem(GameGemDB *this, int diff, MultiGemInfo *info); +extern int WillBeNoStrum(int *gameGemListPtr, int *multiGemInfoPtr); +extern int AddGameGem(int *gameGemList, GameGem *gem, NoStrumState gemType); extern int *GetWidgetByName(int *gemManager, Symbol sym); -extern Symbol GetSlotColor(int *bandUser, int slot); -extern GameGemDB *GameGemDBConstructor(GameGemDB *thisGameGemDB, int num_difficulties, int hopo_threshold); -extern GameGemList *GetGameGemList(void *songData, int unk); +extern Symbol GetSlotColor(int *bandUser); #endif // _GAMEGEM_H \ No newline at end of file diff --git a/source/GameHooks.c b/source/GameHooks.c index 4b5379a..da06978 100644 --- a/source/GameHooks.c +++ b/source/GameHooks.c @@ -39,7 +39,7 @@ void *GameConstructHook(void *theGame) // You just lost if (metadata != NULL) { RB3E_DEBUG("Metadata: %p", metadata); - RB3E_DEBUG("Started song: '%s' - %s (ID: %i, %s)", metadata->title.buf, metadata->artist.buf, metadata->song_id, metadata->shortname); + RB3E_DEBUG("Started song: '%s' - %s (ID: %i, %s)", metadata->title.buf, metadata->artist.buf, song_id, metadata->shortname.sym); RB3E_SendEvent(RB3E_EVENT_SONG_NAME, metadata->title.buf, metadata->title.length); RB3E_SendEvent(RB3E_EVENT_SONG_ARTIST, metadata->artist.buf, metadata->artist.length); } diff --git a/source/_functions.c b/source/_functions.c index eb01ec9..9a630b4 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -33,7 +33,7 @@ RB3E_STUB(AppConstructor) // AppConstructor is handled by the BrainSlug engine #endif RB3E_STUB(ExecuteDTA) RB3E_STUB(SymbolConstruct) -RB3E_STUB(ModifierIsActive) +RB3E_STUB(ModifierActive) RB3E_STUB(HmxFactoryFuncAt) RB3E_STUB(BandLabelSetDisplayText) RB3E_STUB(RandomInt) From 95171adb86e0943d5ced3b8ece2367fa93cddb02 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:28:05 -0500 Subject: [PATCH 054/123] merge --- include/rb3/Vector.h | 7 ------- source/MiloSceneHooks.c | 4 ++-- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/include/rb3/Vector.h b/include/rb3/Vector.h index db01c41..506a527 100644 --- a/include/rb3/Vector.h +++ b/include/rb3/Vector.h @@ -26,13 +26,6 @@ typedef struct _Vector2 float y; } Vector2; -typedef struct _Vector3 -{ - float x; - float y; - float z; -} Vector3; - typedef struct _Vector4 { float x; diff --git a/source/MiloSceneHooks.c b/source/MiloSceneHooks.c index c1ee8f2..518f686 100644 --- a/source/MiloSceneHooks.c +++ b/source/MiloSceneHooks.c @@ -43,8 +43,8 @@ void LoadObj(Object *object, BinStream *stream) { RB3E_DEBUG("Replacing Milo scene asset %s with file at %s", object->name, replacementPath); - // create FileStream with kReadNoArk so it looks outside of the ARK for the file - fileStream = *FileStreamConstructor(&fileStream, replacementPath, kReadNoArk, 0); + // create FileStream with filetype 2 so it looks outside of the ARK for the file + fileStream = *FileStreamConstructor(&fileStream, replacementPath, 2, 0); // attempt to detect endianness of replacement asset // this allows assets from GH2 and earlier to work From f9da00834cb39084d609447250f7c2390ee445c1 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:34:08 -0500 Subject: [PATCH 055/123] Update ChunkStream.h --- include/rb3/ChunkStream.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/include/rb3/ChunkStream.h b/include/rb3/ChunkStream.h index 08f9ba5..90962bd 100644 --- a/include/rb3/ChunkStream.h +++ b/include/rb3/ChunkStream.h @@ -1,10 +1,6 @@ #ifndef _CHUNKSTREAM_H #define _CHUNKSTREAM_H -#include "rb3/BinStream.h" -#include "rb3/File.h" -#include "rb3/Platform.h" - typedef struct _ChunkStream ChunkStream; typedef int (*ChunkStreamDestructor_t)(ChunkStream *thisChunkStream, int unk); @@ -13,9 +9,9 @@ typedef int (*ChunkStreamTell_t)(ChunkStream *thisChunkStream); typedef int (*ChunkStreamEOF_t)(ChunkStream *thisChunkStream); typedef int (*ChunkStreamFail_t)(ChunkStream *thisChunkStream); typedef char *(*ChunkStreamName_t)(ChunkStream *thisChunkStream); -typedef char *(*ChunkStreamReadImpl_t)(ChunkStream *thisChunkStream, void *data, int bytes); -typedef char *(*ChunkStreamWriteImpl_t)(ChunkStream *thisChunkStream, void *data, int bytes); -typedef char *(*ChunkStreamSeekImpl_t)(ChunkStream *thisChunkStream, int offset, SeekType seekType); +typedef char *(*ChunkStreamReadImpl_t)(ChunkStream *thisChunkStream, void *unk, int unk2); +typedef char *(*ChunkStreamWriteImpl_t)(ChunkStream *thisChunkStream, void *unk, int unk2); +typedef char *(*ChunkStreamSeekImpl_t)(ChunkStream *thisChunkStream, int unk, int seekType); typedef int (*ChunkStreamReturnsZero_t)(); typedef struct _ChunkStream_vtable @@ -38,6 +34,6 @@ struct _ChunkStream ChunkStream_vtable *vtable; }; -ChunkStream *ChunkStreamConstructor(ChunkStream *thisChunkStream, char *fileName, FileType fileType, int chunkSize, char compress, Platform platform, char cached); +ChunkStream *ChunkStreamConstructor(ChunkStream *thisChunkStream, char *fileName, int fileType, int flags, char unk2, int platform, char unk3); #endif // _CHUNKSTREAM_H \ No newline at end of file From aad90652edc8cd575e41be7731c15be7f2139537 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:35:30 -0500 Subject: [PATCH 056/123] lawl --- include/rb3/FileStream.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/include/rb3/FileStream.h b/include/rb3/FileStream.h index ccdce2b..eb46211 100644 --- a/include/rb3/FileStream.h +++ b/include/rb3/FileStream.h @@ -1,9 +1,6 @@ #ifndef _FILESTREAM_H #define _FILESTREAM_H -#include "rb3/BinStream.h" -#include "rb3/File.h" - typedef struct _FileStream FileStream; typedef int (*FileStreamDestructor_t)(FileStream *thisFileStream, int unk); @@ -14,7 +11,7 @@ typedef int (*FileStreamFail_t)(FileStream *thisFileStream); typedef char *(*FileStreamName_t)(FileStream *thisFileStream); typedef char *(*FileStreamReadImpl_t)(FileStream *thisFileStream, void *data, int bytes); typedef char *(*FileStreamWriteImpl_t)(FileStream *thisFileStream, void *data, int bytes); -typedef char *(*FileStreamSeekImpl_t)(FileStream *thisFileStream, int offset, SeekType seekType); +typedef char *(*FileStreamSeekImpl_t)(FileStream *thisFileStream, int offset, int seekType); typedef int (*FileStreamReturnsZero_t)(); typedef struct _FileStream_vtable @@ -39,6 +36,6 @@ struct _FileStream char unk[0x256]; }; -FileStream *FileStreamConstructor(FileStream *thisFileStream, char *fileName, FileType fileType, char littleEndian); +FileStream *FileStreamConstructor(FileStream *thisFileStream, char *fileName, int fileType, char littleEndian); #endif // _FILESTREAM_H \ No newline at end of file From ec26b26fec660f04999a33a6ed25d1371a956e6b Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:38:17 -0500 Subject: [PATCH 057/123] yeah bro --- include/rb3/UI/UIPanel.h | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/include/rb3/UI/UIPanel.h b/include/rb3/UI/UIPanel.h index 292876c..caee228 100644 --- a/include/rb3/UI/UIPanel.h +++ b/include/rb3/UI/UIPanel.h @@ -3,12 +3,6 @@ typedef struct _UIPanel UIPanel; -typedef enum _PanelState -{ - kPanelDown = 0, - kPanelUp = 1 -} PanelState; - struct _UIPanel { int *vtable; // 0x0 @@ -19,10 +13,10 @@ struct _UIPanel int unk5; // 0x14 int unk6; // 0x18 #ifdef RB3E_WII - PanelState mState; // 0x1c + int is_up; // 0x1c #else - int unk7; // 0x1c - PanelState mState; // 0x20 + int unk7; // 0x1c + int is_up; // 0x20 #endif }; From dceb3b9c4a59ab82bd3ad212ec9dbe39f52bad38 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:49:30 -0500 Subject: [PATCH 058/123] yeah lets try it sure --- include/GemHooks.h | 67 +++++------------------- include/LocaleHooks.h | 4 +- include/OvershellHooks.h | 2 +- include/QuazalHooks.h | 10 ++-- include/SetlistHooks.h | 5 -- include/rb3/App.h | 2 +- include/rb3/BandSongMgr.h | 2 +- include/rb3/Data.h | 2 +- include/rb3/Locale.h | 2 +- include/rb3/Quazal/StepSequenceJobStep.h | 14 +++++ include/rb3_include.h | 7 --- source/GameHooks.c | 4 +- source/OvershellHooks.c | 54 +++++++++---------- source/QuazalHooks.c | 6 +-- source/RndPropAnimHooks.c | 2 +- source/SongParserHooks.c | 2 +- source/rb3enhanced.c | 14 ++--- 17 files changed, 79 insertions(+), 120 deletions(-) create mode 100644 include/rb3/Quazal/StepSequenceJobStep.h diff --git a/include/GemHooks.h b/include/GemHooks.h index f8af40c..c498e88 100644 --- a/include/GemHooks.h +++ b/include/GemHooks.h @@ -1,55 +1,12 @@ -#ifndef _GAMEGEM_H -#define _GAMEGEM_H - -#include "Symbol.h" - -typedef struct _GameGem -{ - float milliSeconds; - int tick; - short durationMs; - short durationTicks; - - unsigned char unkBitfield1; - unsigned char unkBitfield2; - unsigned char unkBitfield3; - char unk1 : 1; - char unk2 : 1; - char unk3 : 1; - - // There is likely a lot more to this for Pro Keys/Guitar/Bass/Drums in the previous bitfields, but this works fine for 5-lane instruments - char orange : 1; - char blue : 1; - char yellow : 1; - char red : 1; - char green : 1; - - char gemSeen : 1; // Updated in real-time once the player has seen this gem and either hit or missed it - char isHopo : 1; // Does nothing on the first note of the song even if set - char normalNote1 : 1; // Unsure what these both represent but sustains have them set to 0 and setting them to 0 forces a sustain - char normalNote2 : 1; - char unk5 : 1; - char unk6 : 1; - char unk7 : 1; - char unk8 : 1; - - // Unsure about these two - unsigned char unknown; - int unknown2; - - // There is more after this that I am too lazy to map. Probably pro instrument stuff -} GameGem; - -typedef enum _NoStrumState -{ - kStrumForceOn = 0, - kStrumForceOff = 1, - kStrumDefault = 2 -} NoStrumState; - -extern int WillBeNoStrum(int *gameGemListPtr, int *multiGemInfoPtr); -extern int AddGameGem(int *gameGemList, GameGem *gem, NoStrumState gemType); -extern int *GetWidgetByName(int *gemManager, Symbol sym); -extern Symbol GetSlotColor(int *bandUser); - -#endif // _GAMEGEM_H \ No newline at end of file +/* + RB3Enhanced - GemHooks.h + Hooks for dealing with gems. +*/ + +#include "rb3/GameGem.h" +#include "rb3/Symbol.h" + +int WillBeNoStrumHook(int *gameGemListPtr, int *multiGemInfoPtr); +int *GetWidgetByNameHook(int *gemManager, Symbol sym); +int AddGameGemHook(int *gameGemList, GameGem *gem, NoStrumState gemType); +Symbol GetSlotColorHook(int *bandUser); \ No newline at end of file diff --git a/include/LocaleHooks.h b/include/LocaleHooks.h index 8cfec69..8e4c9b5 100644 --- a/include/LocaleHooks.h +++ b/include/LocaleHooks.h @@ -6,5 +6,5 @@ #include "rb3/Symbol.h" char *LocalizeHook(int thisLocale, Symbol sym, int fail); -void SetSystemLanguageHook(Symbol lang, int cheat); -int IsSupportedLanguageHook(Symbol lang, int cheat); \ No newline at end of file +void SetSystemLanguageHook(Symbol lang, int r4); +int IsSupportedLanguageHook(Symbol lang, int r4); \ No newline at end of file diff --git a/include/OvershellHooks.h b/include/OvershellHooks.h index a660ba6..eebac55 100644 --- a/include/OvershellHooks.h +++ b/include/OvershellHooks.h @@ -28,7 +28,7 @@ typedef struct _OvershellListEntry char icon; } OvershellListEntry; -void OvershellPartSelectProviderReload(OvershellSlot *thisOvershellSlot, ControllerType controllerType, BandUser *bandUser); +void BuildInstrumentSelectionList(OvershellSlot *thisOvershellSlot, ControllerType controllerType, BandUser *bandUser); // Not overshell related but this is the only place we use them and IDK how to correctly organise code void PrepareSomeVectorMaybe(int *r3, int r4, int r5); diff --git a/include/QuazalHooks.h b/include/QuazalHooks.h index a546dc7..e419a05 100644 --- a/include/QuazalHooks.h +++ b/include/QuazalHooks.h @@ -4,9 +4,9 @@ Quazal is the creator of the networking middleware used by RB3. */ -#include "rb3/Quazal/Step.h" +#include "rb3/Quazal/StepSequenceJobStep.h" -extern void OperatorEqualsFmt(char *thisString, char *szString); -void OperatorEqualsFmtHook(char *thisString, char *szString); -extern int StepSequenceJobSetStep(int *thisStepSequenceJob, Step *oStep); -int StepSequenceJobSetStepHook(int *thisStepSequenceJob, Step *oStep); +extern void OperatorEqualsFmt(char *r3, char *r4); +void OperatorEqualsFmtHook(char *r3, char *r4); +extern int StepSequenceJobSetStep(int *unk, StepSequenceJobStep *step); +int StepSequenceJobSetStepHook(int *unk, StepSequenceJobStep *step); diff --git a/include/SetlistHooks.h b/include/SetlistHooks.h index a29606e..51d1e34 100644 --- a/include/SetlistHooks.h +++ b/include/SetlistHooks.h @@ -3,13 +3,8 @@ Hooks for modifying the setlist/song selection screen. */ -#include "rb3/MusicLibrary.h" #include "rb3/BandLabel.h" #include "rb3/SortNode.h" -#include "rb3/Rnd/RndMat.h" void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode); void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode); -RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int unk, int unk2, int *listSlot); -SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc); -char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream); diff --git a/include/rb3/App.h b/include/rb3/App.h index 71d3569..470ca4e 100644 --- a/include/rb3/App.h +++ b/include/rb3/App.h @@ -2,6 +2,6 @@ #define _APP_H extern void AppConstructor(void *thisApp, int argc, char **argv); -extern void *NewFile(char *iFilename, int iMode); // doesn't belong to a namespace? +extern void *NewFile(char *fileName, int flags); // doesn't belong to a namespace? #endif // _APP_H \ No newline at end of file diff --git a/include/rb3/BandSongMgr.h b/include/rb3/BandSongMgr.h index 6ac1327..3df138c 100644 --- a/include/rb3/BandSongMgr.h +++ b/include/rb3/BandSongMgr.h @@ -11,6 +11,6 @@ typedef struct _BandSongMgr extern int GetSongIDFromShortname(BandSongMgr *thisSongMgr, Symbol shortName, int fail); // fail seems to be unused on retail builds, but it would simulate a failure on debug builds extern SongMetadata *GetMetadata(BandSongMgr *thisSongMgr, int songId); -extern int SongMgrGetRankedSongs(BandSongMgr *thisSongMgr, void *songs, char demosIfGameModeAllows, char includeRestricted); +extern int SongMgrGetRankedSongs(BandSongMgr *thisSongMgr, void *vector, char demosIfGameModeAllows, char includeRestricted); #endif // _BANDSONGMGR_H diff --git a/include/rb3/Data.h b/include/rb3/Data.h index 05988e0..97faccf 100644 --- a/include/rb3/Data.h +++ b/include/rb3/Data.h @@ -63,7 +63,7 @@ typedef struct _DataArray short mNodeCount; short mRefCount; short mLine; - short mDeprecated; // assuming this is for them to mark scripts or functions as being deprecated and etc.? + short mUnknown; } DataArray; // gets a pointer to the addr of a DTA function diff --git a/include/rb3/Locale.h b/include/rb3/Locale.h index 4d67514..94991bc 100644 --- a/include/rb3/Locale.h +++ b/include/rb3/Locale.h @@ -2,7 +2,7 @@ #define _LOCALE_H // I believe "fail" being set to 1 means that if there is no locale, it will return the raw Symbol and not a blank string or vice versa -extern char *Localize(int thisLocale, Symbol token, int fail); +extern char *Localize(int thisLocale, Symbol sym, int fail); extern void SetSystemLanguage(Symbol lang, int cheat); #endif // _LOCALE_H \ No newline at end of file diff --git a/include/rb3/Quazal/StepSequenceJobStep.h b/include/rb3/Quazal/StepSequenceJobStep.h new file mode 100644 index 0000000..3adb8c6 --- /dev/null +++ b/include/rb3/Quazal/StepSequenceJobStep.h @@ -0,0 +1,14 @@ +#ifndef _STEPSEQUENCEJOBSTEP_H +#define _STEPSEQUENCEJOBSTEP_H + +typedef struct _StepSequenceJobStep +{ + void *jobFunc; // the function that gets called when the job is ran (?) + int unk; +#ifdef RB3E_WII + int unk2; // field only appears to exist on Wii +#endif + char *jobName; +} StepSequenceJobStep; + +#endif // _STEPSEQUENCEJOBSTEP_H \ No newline at end of file diff --git a/include/rb3_include.h b/include/rb3_include.h index 464e1ee..5100c0d 100644 --- a/include/rb3_include.h +++ b/include/rb3_include.h @@ -14,18 +14,15 @@ #include "rb3/Data.h" #include "rb3/DirLoader.h" #include "rb3/File.h" -#include "rb3/FilePath.h" #include "rb3/FileStream.h" #include "rb3/Game.h" #include "rb3/GameGem.h" -#include "rb3/GemTrackResrcManager.h" #include "rb3/Joypad.h" #include "rb3/Locale.h" #include "rb3/Mem.h" #include "rb3/MetaPerformer.h" #include "rb3/ModifierManager.h" #include "rb3/MusicLibrary.h" -#include "rb3/NodeSort.h" #include "rb3/Object.h" #include "rb3/PassiveMessagesPanel.h" #include "rb3/PresenceMgr.h" @@ -34,7 +31,6 @@ #include "rb3/SongMetadata.h" #include "rb3/SongParser.h" #include "rb3/SongSortByRecentEntry.h" -#include "rb3/SongSortMgr.h" #include "rb3/SortNode.h" #include "rb3/String.h" #include "rb3/Symbol.h" @@ -46,10 +42,7 @@ #include "quazal/QuazalSocket.h" // rnd headers -#include "rb3/Rnd/DynamicTex.h" #include "rb3/Rnd/RndPropAnim.h" -#include "rb3/Rnd/RndMat.h" -#include "rb3/Rnd/RndTex.h" // UI headers #include "rb3/UI/UIPanel.h" diff --git a/source/GameHooks.c b/source/GameHooks.c index da06978..798e94f 100644 --- a/source/GameHooks.c +++ b/source/GameHooks.c @@ -39,7 +39,7 @@ void *GameConstructHook(void *theGame) // You just lost if (metadata != NULL) { RB3E_DEBUG("Metadata: %p", metadata); - RB3E_DEBUG("Started song: '%s' - %s (ID: %i, %s)", metadata->title.buf, metadata->artist.buf, song_id, metadata->shortname.sym); + RB3E_DEBUG("Started song: '%s' - %s (ID: %i, %s)", metadata->title.buf, metadata->artist.buf, metadata->song_id, metadata->shortname); RB3E_SendEvent(RB3E_EVENT_SONG_NAME, metadata->title.buf, metadata->title.length); RB3E_SendEvent(RB3E_EVENT_SONG_ARTIST, metadata->artist.buf, metadata->artist.length); } @@ -76,4 +76,4 @@ void *GameDestructHook(void *theGame, int r4) #endif return GameDestruct(theGame, r4); -} \ No newline at end of file +} diff --git a/source/OvershellHooks.c b/source/OvershellHooks.c index 8afdc79..02f2fad 100644 --- a/source/OvershellHooks.c +++ b/source/OvershellHooks.c @@ -10,7 +10,7 @@ #include "ports.h" // MSVC inlined this function in the original game so redefine it -void OvershellPartSelectProviderAddPart(OvershellSlot *thisOvershellSlot, Symbol *instrumentToAdd, TrackType trackType, char *icon) +void AddInstrumentToList(OvershellSlot *thisOvershellSlot, Symbol *instrumentToAdd, TrackType trackType, char *icon) { OvershellListEntry entry; entry.icon = icon[0]; @@ -21,7 +21,7 @@ void OvershellPartSelectProviderAddPart(OvershellSlot *thisOvershellSlot, Symbol } // Monkey patching the function will be hard, so make a realistic, dark and gritty reboot. -void OvershellPartSelectProviderReload(OvershellSlot *thisOvershellSlot, ControllerType controllerType, BandUser *bandUser) +void BuildInstrumentSelectionList(OvershellSlot *thisOvershellSlot, ControllerType controllerType, BandUser *bandUser) { PrepareSomeVectorMaybe(&thisOvershellSlot->list_vector_maybe, thisOvershellSlot->list_vector_maybe, thisOvershellSlot->unk_var_2); thisOvershellSlot->controllerType = controllerType; @@ -29,36 +29,36 @@ void OvershellPartSelectProviderReload(OvershellSlot *thisOvershellSlot, Control // RB3E_DEBUG("Building overshell selection for controller type %i.", controllerType); switch (controllerType) { - case kControllerVocals: - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_vocal_solo, kTrackVocals, "V"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_vocal_harmony, kTrackVocals, "2"); + case VOX_CONT: + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_vocal_solo, VOCALS, "V"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_vocal_harmony, HARMONIES, "2"); // allow pad to play as guitar, bass, keys and drums :D - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_guitar, kTrackGuitar, "G"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_bass, kTrackBass, "B"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_keys, kTrackKeys, "K"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_drums, kTrackDrum, "D"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_guitar, GUITAR, "G"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_bass, BASS, "B"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_keys, KEYS, "K"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_drums, DRUMS, "D"); break; - case kControllerGuitar: - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_guitar, kTrackGuitar, "G"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_bass, kTrackBass, "B"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_keys, kTrackKeys, "K"); + case GUITAR_CONT: + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_guitar, GUITAR, "G"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_bass, BASS, "B"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_keys, KEYS, "K"); break; - case kControllerDrum: - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_drums, kTrackDrum, "D"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_drums_pro, kTrackDrum, "d"); + case DRUM_CONT: + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_drums, DRUMS, "D"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_drums_pro, DRUMS, "d"); break; - case kControllerKeys: - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_keys, kTrackKeys, "K"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_real_keys, kTrackRealKeys, "k"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_guitar, kTrackGuitar, "G"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_bass, kTrackBass, "B"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_drums, kTrackDrum, "D"); + case KEYBOARD_CONT: + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_keys, KEYS, "K"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_real_keys, PRO_KEYS, "k"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_guitar, GUITAR, "G"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_bass, BASS, "B"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_drums, DRUMS, "D"); break; - case kControllerRealGuitar: - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_real_guitar, kTrackRealGuitar, "g"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_real_bass, kTrackRealBass, "b"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_guitar, kTrackGuitar, "G"); - OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_bass, kTrackBass, "B"); + case PRO_GUITAR_CONT: + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_real_guitar, PRO_GUITAR, "g"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_real_bass, PRO_BASS, "b"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_guitar, GUITAR, "G"); + AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_bass, BASS, "B"); break; } } diff --git a/source/QuazalHooks.c b/source/QuazalHooks.c index 306c67c..af70ade 100644 --- a/source/QuazalHooks.c +++ b/source/QuazalHooks.c @@ -15,12 +15,12 @@ void OperatorEqualsFmtHook(char *r3, char *r4) return; } -int StepSequenceJobSetStepHook(int *unk, Step *step) +int StepSequenceJobSetStepHook(int *unk, StepSequenceJobStep *step) { // steps can have no name it seems; make sure we are not trying to print a null pointer - if (step != NULL && step->m_szStepDescription != NULL) + if (step != NULL && step->jobName != NULL) { - RB3E_DEBUG("Quazal Job: %s", step->m_szStepDescription); + RB3E_DEBUG("Quazal Job: %s", step->jobName); } return StepSequenceJobSetStep(unk, step); } \ No newline at end of file diff --git a/source/RndPropAnimHooks.c b/source/RndPropAnimHooks.c index 5918f9b..eec9c15 100644 --- a/source/RndPropAnimHooks.c +++ b/source/RndPropAnimHooks.c @@ -10,7 +10,7 @@ // see https://github.com/RBEnhanced/RB3Enhanced/issues/2 void PropAnimSetFrameHook(RndPropAnim *rndPropAnim, float frame, float time) { - if (strcmp(rndPropAnim->mObject.name, "slot_positions.anim") == 0) + if (strcmp(rndPropAnim->object.name, "slot_positions.anim") == 0) { // check that it is not setting the frame to 0 (which is the default slot positions for overshell) if (frame != 0.0f) diff --git a/source/SongParserHooks.c b/source/SongParserHooks.c index 24a800c..2069ebb 100644 --- a/source/SongParserHooks.c +++ b/source/SongParserHooks.c @@ -21,7 +21,7 @@ int SongParserPitchToSlotHook(SongParser *thisSongParser, int pitch, int *diffic if (doubleBassModifier->enabled) { // make sure the current track type is drums - if (thisSongParser->TrackType == DRUMS) + if (thisSongParser->mTrackType == DRUMS) { // check for MIDI note pitch 95 (which is the 2x bass pedal note) if (pitch == 95) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 8e1a1ff..7301411 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -57,7 +57,7 @@ void UpdatePresenceHook(void *thisPresenceMgr) } // New file hook, for ARKless file loading -void *NewFileHook(char *iFilename, int iMode) +void *NewFileHook(char *fileName, int flags) { char *new_path = NULL; if (strlen(fileName) > 250) @@ -65,11 +65,11 @@ void *NewFileHook(char *iFilename, int iMode) if (config.DisableRawfiles) goto LOAD_ORIGINAL; // checks the platform-specific APIs for the file - new_path = RB3E_GetRawfilePath(iFilename, 0); + new_path = RB3E_GetRawfilePath(fileName, 0); if (new_path != NULL) { - iFilename = new_path; - iMode = 0x10002; // tell the game to load this file raw + fileName = new_path; + flags = 0x10002; // tell the game to load this file raw } #ifdef RB3E_WII // load the FileSD @@ -85,8 +85,8 @@ void *NewFileHook(char *iFilename, int iMode) #endif LOAD_ORIGINAL: if (config.LogFileAccess) - RB3E_MSG("File: %s (%s)", iFilename, (iMode & 0x10000) ? "Raw" : "ARK"); - return NewFile(iFilename, iMode); + RB3E_MSG("File: %s (%s)", fileName, (flags & 0x10000) ? "Raw" : "ARK"); + return NewFile(fileName, flags); } DataArray *DataReadFileHook(char *path, int dtb) @@ -404,6 +404,7 @@ void ApplyHooks() HookFunction(PORT_LOCALIZE, &Localize, &LocalizeHook); HookFunction(PORT_WILLBENOSTRUM, &WillBeNoStrum, &WillBeNoStrumHook); HookFunction(PORT_ADDGAMEGEM, &AddGameGem, &AddGameGemHook); + HookFunction(PORT_SETSONGANDARTISTNAME, &SetSongAndArtistName, SetSongAndArtistNameHook); HookFunction(PORT_SETVENUE, &SetVenue, &SetVenueHook); HookFunction(PORT_MODIFIERMGR_CT, &ModifierManagerConstructor, &ModifierManagerConstructorHook); HookFunction(PORT_NEWFILE, &NewFile, &NewFileHook); @@ -436,7 +437,6 @@ void ApplyHooks() POKE_B(PORT_FILEISLOCAL, &FileIsLocalHook); #elif RB3E_XBOX // 360 exclusive hooks HookFunction(PORT_STAGEKIT_SET_STATE, &StagekitSetState, &StagekitSetStateHook); - // TODO: port these to Wii HookFunction(PORT_SETSONGNAMEFROMNODE, &SetSongNameFromNode, &SetSongNameFromNodeHook); // TODO: port these to Wii HookFunction(PORT_DATANODEGETOBJ, &DataNodeGetObj, &DataNodeGetObjHook); From e2e67de13b54d9d762fd36128517fc2fca74f081 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:53:41 -0500 Subject: [PATCH 059/123] Update MiloSceneHooks.h --- include/MiloSceneHooks.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/include/MiloSceneHooks.h b/include/MiloSceneHooks.h index 1d824a0..67b889e 100644 --- a/include/MiloSceneHooks.h +++ b/include/MiloSceneHooks.h @@ -6,8 +6,6 @@ #include "rb3/BinStream.h" #include "rb3/DirLoader.h" #include "rb3/Object.h" -#include "rb3/Vector.h" // void DirLoaderOpenFileHook(DirLoader *thisDirLoader); -void LoadObj(Object *object, BinStream *stream); -void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3); \ No newline at end of file +void LoadObj(Object *object, BinStream *stream); \ No newline at end of file From 80d929ea4c8a02df0e5746eb21f63a00f7ca21df Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:55:11 -0500 Subject: [PATCH 060/123] lol --- source/MiloSceneHooks.c | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/source/MiloSceneHooks.c b/source/MiloSceneHooks.c index 518f686..95887c5 100644 --- a/source/MiloSceneHooks.c +++ b/source/MiloSceneHooks.c @@ -67,28 +67,4 @@ void LoadObj(Object *object, BinStream *stream) object_pre_load: object->table->preLoad(object, stream); return; -} - -// This hook allows for GH2-360/RB1/RB2 meshes to load correctly in RB3 -void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3) -{ -#ifdef RB3E_XBOX - // the gRev of the current mesh - int gRev = *(int *)PORT_MESH_GREV; - char empty[4] = {0}; - - BinstreamReadEndian(thisBinStream, (void *)&vec3->x, 4); - BinstreamReadEndian(thisBinStream, (void *)&vec3->y, 4); - BinstreamReadEndian(thisBinStream, (void *)&vec3->z, 4); - - // if the current RndMesh being read is the GH2-360/RB1/RB2 format, we need to skip over the W component - // RB3 (for whatever reason) expects vertices in these versions of meshes to not include W - // even though they *do* in actual GH2-360/RB1/RB2 meshes - if (gRev == 34) - { - BinstreamReadEndian(thisBinStream, (void *)&empty, 4); - } - - return; -#endif } \ No newline at end of file From 482d0d98465c22d764165c07511fd197f2ee22eb Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 16:57:03 -0500 Subject: [PATCH 061/123] ya --- source/OvershellHooks.c | 56 ++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/source/OvershellHooks.c b/source/OvershellHooks.c index 02f2fad..e2363b4 100644 --- a/source/OvershellHooks.c +++ b/source/OvershellHooks.c @@ -10,7 +10,7 @@ #include "ports.h" // MSVC inlined this function in the original game so redefine it -void AddInstrumentToList(OvershellSlot *thisOvershellSlot, Symbol *instrumentToAdd, TrackType trackType, char *icon) +void OvershellPartSelectProviderAddPart(OvershellSlot *thisOvershellSlot, Symbol *instrumentToAdd, TrackType trackType, char *icon) { OvershellListEntry entry; entry.icon = icon[0]; @@ -21,7 +21,7 @@ void AddInstrumentToList(OvershellSlot *thisOvershellSlot, Symbol *instrumentToA } // Monkey patching the function will be hard, so make a realistic, dark and gritty reboot. -void BuildInstrumentSelectionList(OvershellSlot *thisOvershellSlot, ControllerType controllerType, BandUser *bandUser) +void OvershellPartSelectProviderReload(OvershellSlot *thisOvershellSlot, ControllerType controllerType, BandUser *bandUser) { PrepareSomeVectorMaybe(&thisOvershellSlot->list_vector_maybe, thisOvershellSlot->list_vector_maybe, thisOvershellSlot->unk_var_2); thisOvershellSlot->controllerType = controllerType; @@ -29,36 +29,36 @@ void BuildInstrumentSelectionList(OvershellSlot *thisOvershellSlot, ControllerTy // RB3E_DEBUG("Building overshell selection for controller type %i.", controllerType); switch (controllerType) { - case VOX_CONT: - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_vocal_solo, VOCALS, "V"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_vocal_harmony, HARMONIES, "2"); + case kControllerVocals: + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_vocal_solo, kTrackVocals, "V"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_vocal_harmony, kTrackVocals, "2"); // allow pad to play as guitar, bass, keys and drums :D - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_guitar, GUITAR, "G"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_bass, BASS, "B"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_keys, KEYS, "K"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_drums, DRUMS, "D"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_guitar, kTrackGuitar, "G"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_bass, kTrackBass, "B"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_keys, kTrackKeys, "K"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_drums, kTrackDrum, "D"); break; - case GUITAR_CONT: - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_guitar, GUITAR, "G"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_bass, BASS, "B"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_keys, KEYS, "K"); + case kControllerGuitar: + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_guitar, kTrackGuitar, "G"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_bass, kTrackBass, "B"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_keys, kTrackKeys, "K"); break; - case DRUM_CONT: - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_drums, DRUMS, "D"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_drums_pro, DRUMS, "d"); + case kControllerDrum: + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_drums, kTrackDrum, "D"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_drums_pro, kTrackDrum, "d"); break; - case KEYBOARD_CONT: - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_keys, KEYS, "K"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_real_keys, PRO_KEYS, "k"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_guitar, GUITAR, "G"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_bass, BASS, "B"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_drums, DRUMS, "D"); + case kControllerKeys: + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_keys, kTrackKeys, "K"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_real_keys, kTrackRealKeys, "k"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_guitar, kTrackGuitar, "G"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_bass, kTrackBass, "B"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_drums, kTrackDrum, "D"); break; - case PRO_GUITAR_CONT: - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_real_guitar, PRO_GUITAR, "g"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_real_bass, PRO_BASS, "b"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_guitar, GUITAR, "G"); - AddInstrumentToList(thisOvershellSlot, &globalSymbols.overshell_bass, BASS, "B"); + case kControllerRealGuitar: + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_real_guitar, kTrackRealGuitar, "g"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_real_bass, kTrackRealBass, "b"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_guitar, kTrackGuitar, "G"); + OvershellPartSelectProviderAddPart(thisOvershellSlot, &globalSymbols.overshell_bass, kTrackBass, "B"); break; } -} +} \ No newline at end of file From 9eaa94a02f0b90218d358b97d180a5dc6b0a1e1b Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:23:47 -0500 Subject: [PATCH 062/123] hail mary --- include/GemHooks.h | 4 +-- include/LocaleHooks.h | 4 +-- include/OvershellHooks.h | 2 +- include/SetlistHooks.h | 5 +++ include/rb3/BandSongMgr.h | 2 +- include/rb3/ChunkStream.h | 12 +++++--- include/rb3/Data.h | 4 +-- include/rb3/FileStream.h | 7 +++-- include/rb3/GameGem.h | 62 ++++++++++++++++++++++++++++---------- include/rb3/Locale.h | 2 +- include/rb3/SongMetadata.h | 4 +++ include/rb3/Vector.h | 13 -------- include/rb3_include.h | 8 ++++- source/GemHooks.c | 12 +++++--- source/LocaleHooks.c | 23 +++++++------- source/RndPropAnimHooks.c | 2 +- source/rb3enhanced.c | 2 ++ 17 files changed, 106 insertions(+), 62 deletions(-) diff --git a/include/GemHooks.h b/include/GemHooks.h index c498e88..d42203c 100644 --- a/include/GemHooks.h +++ b/include/GemHooks.h @@ -7,6 +7,6 @@ #include "rb3/Symbol.h" int WillBeNoStrumHook(int *gameGemListPtr, int *multiGemInfoPtr); -int *GetWidgetByNameHook(int *gemManager, Symbol sym); -int AddGameGemHook(int *gameGemList, GameGem *gem, NoStrumState gemType); +int *GetWidgetByNameHook(int *gemManager, Symbol name); +int AddGameGemHook(GameGemList *gameGemList, GameGem *gem, NoStrumState noStrumState); Symbol GetSlotColorHook(int *bandUser); \ No newline at end of file diff --git a/include/LocaleHooks.h b/include/LocaleHooks.h index 8e4c9b5..8cfec69 100644 --- a/include/LocaleHooks.h +++ b/include/LocaleHooks.h @@ -6,5 +6,5 @@ #include "rb3/Symbol.h" char *LocalizeHook(int thisLocale, Symbol sym, int fail); -void SetSystemLanguageHook(Symbol lang, int r4); -int IsSupportedLanguageHook(Symbol lang, int r4); \ No newline at end of file +void SetSystemLanguageHook(Symbol lang, int cheat); +int IsSupportedLanguageHook(Symbol lang, int cheat); \ No newline at end of file diff --git a/include/OvershellHooks.h b/include/OvershellHooks.h index eebac55..a660ba6 100644 --- a/include/OvershellHooks.h +++ b/include/OvershellHooks.h @@ -28,7 +28,7 @@ typedef struct _OvershellListEntry char icon; } OvershellListEntry; -void BuildInstrumentSelectionList(OvershellSlot *thisOvershellSlot, ControllerType controllerType, BandUser *bandUser); +void OvershellPartSelectProviderReload(OvershellSlot *thisOvershellSlot, ControllerType controllerType, BandUser *bandUser); // Not overshell related but this is the only place we use them and IDK how to correctly organise code void PrepareSomeVectorMaybe(int *r3, int r4, int r5); diff --git a/include/SetlistHooks.h b/include/SetlistHooks.h index 51d1e34..a29606e 100644 --- a/include/SetlistHooks.h +++ b/include/SetlistHooks.h @@ -3,8 +3,13 @@ Hooks for modifying the setlist/song selection screen. */ +#include "rb3/MusicLibrary.h" #include "rb3/BandLabel.h" #include "rb3/SortNode.h" +#include "rb3/Rnd/RndMat.h" void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode); void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode); +RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int unk, int unk2, int *listSlot); +SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc); +char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream); diff --git a/include/rb3/BandSongMgr.h b/include/rb3/BandSongMgr.h index 3df138c..6ac1327 100644 --- a/include/rb3/BandSongMgr.h +++ b/include/rb3/BandSongMgr.h @@ -11,6 +11,6 @@ typedef struct _BandSongMgr extern int GetSongIDFromShortname(BandSongMgr *thisSongMgr, Symbol shortName, int fail); // fail seems to be unused on retail builds, but it would simulate a failure on debug builds extern SongMetadata *GetMetadata(BandSongMgr *thisSongMgr, int songId); -extern int SongMgrGetRankedSongs(BandSongMgr *thisSongMgr, void *vector, char demosIfGameModeAllows, char includeRestricted); +extern int SongMgrGetRankedSongs(BandSongMgr *thisSongMgr, void *songs, char demosIfGameModeAllows, char includeRestricted); #endif // _BANDSONGMGR_H diff --git a/include/rb3/ChunkStream.h b/include/rb3/ChunkStream.h index 90962bd..08f9ba5 100644 --- a/include/rb3/ChunkStream.h +++ b/include/rb3/ChunkStream.h @@ -1,6 +1,10 @@ #ifndef _CHUNKSTREAM_H #define _CHUNKSTREAM_H +#include "rb3/BinStream.h" +#include "rb3/File.h" +#include "rb3/Platform.h" + typedef struct _ChunkStream ChunkStream; typedef int (*ChunkStreamDestructor_t)(ChunkStream *thisChunkStream, int unk); @@ -9,9 +13,9 @@ typedef int (*ChunkStreamTell_t)(ChunkStream *thisChunkStream); typedef int (*ChunkStreamEOF_t)(ChunkStream *thisChunkStream); typedef int (*ChunkStreamFail_t)(ChunkStream *thisChunkStream); typedef char *(*ChunkStreamName_t)(ChunkStream *thisChunkStream); -typedef char *(*ChunkStreamReadImpl_t)(ChunkStream *thisChunkStream, void *unk, int unk2); -typedef char *(*ChunkStreamWriteImpl_t)(ChunkStream *thisChunkStream, void *unk, int unk2); -typedef char *(*ChunkStreamSeekImpl_t)(ChunkStream *thisChunkStream, int unk, int seekType); +typedef char *(*ChunkStreamReadImpl_t)(ChunkStream *thisChunkStream, void *data, int bytes); +typedef char *(*ChunkStreamWriteImpl_t)(ChunkStream *thisChunkStream, void *data, int bytes); +typedef char *(*ChunkStreamSeekImpl_t)(ChunkStream *thisChunkStream, int offset, SeekType seekType); typedef int (*ChunkStreamReturnsZero_t)(); typedef struct _ChunkStream_vtable @@ -34,6 +38,6 @@ struct _ChunkStream ChunkStream_vtable *vtable; }; -ChunkStream *ChunkStreamConstructor(ChunkStream *thisChunkStream, char *fileName, int fileType, int flags, char unk2, int platform, char unk3); +ChunkStream *ChunkStreamConstructor(ChunkStream *thisChunkStream, char *fileName, FileType fileType, int chunkSize, char compress, Platform platform, char cached); #endif // _CHUNKSTREAM_H \ No newline at end of file diff --git a/include/rb3/Data.h b/include/rb3/Data.h index 97faccf..a1f9cd3 100644 --- a/include/rb3/Data.h +++ b/include/rb3/Data.h @@ -7,7 +7,7 @@ typedef union _DataNode_Value { int intVal; float floatVal; - void *dataArray; + int *dataArray; int *object; char *string; } DataNode_Value; @@ -63,7 +63,7 @@ typedef struct _DataArray short mNodeCount; short mRefCount; short mLine; - short mUnknown; + short mDeprecated; // assuming this is for them to mark scripts or functions as being deprecated and etc.? } DataArray; // gets a pointer to the addr of a DTA function diff --git a/include/rb3/FileStream.h b/include/rb3/FileStream.h index eb46211..ccdce2b 100644 --- a/include/rb3/FileStream.h +++ b/include/rb3/FileStream.h @@ -1,6 +1,9 @@ #ifndef _FILESTREAM_H #define _FILESTREAM_H +#include "rb3/BinStream.h" +#include "rb3/File.h" + typedef struct _FileStream FileStream; typedef int (*FileStreamDestructor_t)(FileStream *thisFileStream, int unk); @@ -11,7 +14,7 @@ typedef int (*FileStreamFail_t)(FileStream *thisFileStream); typedef char *(*FileStreamName_t)(FileStream *thisFileStream); typedef char *(*FileStreamReadImpl_t)(FileStream *thisFileStream, void *data, int bytes); typedef char *(*FileStreamWriteImpl_t)(FileStream *thisFileStream, void *data, int bytes); -typedef char *(*FileStreamSeekImpl_t)(FileStream *thisFileStream, int offset, int seekType); +typedef char *(*FileStreamSeekImpl_t)(FileStream *thisFileStream, int offset, SeekType seekType); typedef int (*FileStreamReturnsZero_t)(); typedef struct _FileStream_vtable @@ -36,6 +39,6 @@ struct _FileStream char unk[0x256]; }; -FileStream *FileStreamConstructor(FileStream *thisFileStream, char *fileName, int fileType, char littleEndian); +FileStream *FileStreamConstructor(FileStream *thisFileStream, char *fileName, FileType fileType, char littleEndian); #endif // _FILESTREAM_H \ No newline at end of file diff --git a/include/rb3/GameGem.h b/include/rb3/GameGem.h index f8af40c..996491d 100644 --- a/include/rb3/GameGem.h +++ b/include/rb3/GameGem.h @@ -1,7 +1,8 @@ #ifndef _GAMEGEM_H #define _GAMEGEM_H -#include "Symbol.h" +#include "rb3/Symbol.h" +#include "rb3/Vector.h" typedef struct _GameGem { @@ -13,6 +14,7 @@ typedef struct _GameGem unsigned char unkBitfield1; unsigned char unkBitfield2; unsigned char unkBitfield3; + char unk1 : 1; char unk2 : 1; char unk3 : 1; @@ -24,18 +26,16 @@ typedef struct _GameGem char red : 1; char green : 1; - char gemSeen : 1; // Updated in real-time once the player has seen this gem and either hit or missed it - char isHopo : 1; // Does nothing on the first note of the song even if set - char normalNote1 : 1; // Unsure what these both represent but sustains have them set to 0 and setting them to 0 forces a sustain - char normalNote2 : 1; - char unk5 : 1; - char unk6 : 1; - char unk7 : 1; - char unk8 : 1; - - // Unsure about these two - unsigned char unknown; - int unknown2; + char isPlayed : 1; // Updated in real-time once the player has seen this gem and either hit or missed it + char isHopo : 1; // Does nothing on the first note of the song even if set + char ignoreDuration : 1; + char isCymbal : 1; + char showChordNames : 1; + char showSlashes : 1; + char useAltDuration : 1; + char autoPlayable : 1; + unsigned char playableBy; + char isRealGuitar; // There is more after this that I am too lazy to map. Probably pro instrument stuff } GameGem; @@ -47,9 +47,39 @@ typedef enum _NoStrumState kStrumDefault = 2 } NoStrumState; -extern int WillBeNoStrum(int *gameGemListPtr, int *multiGemInfoPtr); -extern int AddGameGem(int *gameGemList, GameGem *gem, NoStrumState gemType); +typedef struct _GameGemList +{ + int mHopoThreshold; + vector mGems; +} GameGemList; + +typedef struct _GameGemDB +{ + vector mGems; + int mHopoThreshold; +} GameGemDB; + +typedef struct _MultiGemInfo +{ + int track; + int slots; + float ms; + float duration_ms; + int tick; + int duration_ticks; + char ignore_duration; + char is_cymbal; + char pad[2]; + int players; + NoStrumState no_strum; +} MultiGemInfo; + +extern int WillBeNoStrum(GameGemList *thisGameGemList, int *multiGemInfoPtr); +extern int AddGameGem(GameGemList *gameGemList, GameGem *gem, NoStrumState gemType); +extern char AddMultiGem(GameGemDB *this, int diff, MultiGemInfo *info); extern int *GetWidgetByName(int *gemManager, Symbol sym); -extern Symbol GetSlotColor(int *bandUser); +extern Symbol GetSlotColor(int *bandUser, int slot); +extern GameGemDB *GameGemDBConstructor(GameGemDB *thisGameGemDB, int num_difficulties, int hopo_threshold); +extern GameGemList *GetGameGemList(void *songData, int unk); #endif // _GAMEGEM_H \ No newline at end of file diff --git a/include/rb3/Locale.h b/include/rb3/Locale.h index 94991bc..4d67514 100644 --- a/include/rb3/Locale.h +++ b/include/rb3/Locale.h @@ -2,7 +2,7 @@ #define _LOCALE_H // I believe "fail" being set to 1 means that if there is no locale, it will return the raw Symbol and not a blank string or vice versa -extern char *Localize(int thisLocale, Symbol sym, int fail); +extern char *Localize(int thisLocale, Symbol token, int fail); extern void SetSystemLanguage(Symbol lang, int cheat); #endif // _LOCALE_H \ No newline at end of file diff --git a/include/rb3/SongMetadata.h b/include/rb3/SongMetadata.h index 05aa6a2..9fb0cf8 100644 --- a/include/rb3/SongMetadata.h +++ b/include/rb3/SongMetadata.h @@ -1,6 +1,8 @@ #ifndef _SONGMETADATA_H #define _SONGMETADATA_H +#include "rb3/BinStream.h" +#include "rb3/Data.h" #include "String.h" #include "Symbol.h" @@ -33,5 +35,7 @@ typedef struct _SongMetadata } SongMetadata; extern SongMetadata *InitSongMetadata(SongMetadata *data); +extern SongMetadata *SongMetadataConstructor(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc); +extern char SongMetadataLoad(SongMetadata *thisSongMetadata, BinStream *stream); #endif // _SONGMETADATA_H diff --git a/include/rb3/Vector.h b/include/rb3/Vector.h index 506a527..8dce691 100644 --- a/include/rb3/Vector.h +++ b/include/rb3/Vector.h @@ -20,17 +20,4 @@ typedef struct _vector void vector_push_back(vector *v, void *item); -typedef struct _Vector2 -{ - float x; - float y; -} Vector2; - -typedef struct _Vector4 -{ - float x; - float y; - float z; - float w; -} Vector4; #endif // _VECTOR_H \ No newline at end of file diff --git a/include/rb3_include.h b/include/rb3_include.h index 5100c0d..6e6662f 100644 --- a/include/rb3_include.h +++ b/include/rb3_include.h @@ -14,15 +14,18 @@ #include "rb3/Data.h" #include "rb3/DirLoader.h" #include "rb3/File.h" +#include "rb3/FilePath.h" #include "rb3/FileStream.h" #include "rb3/Game.h" #include "rb3/GameGem.h" +#include "rb3/GemTrackResrcManager.h" #include "rb3/Joypad.h" #include "rb3/Locale.h" #include "rb3/Mem.h" #include "rb3/MetaPerformer.h" #include "rb3/ModifierManager.h" #include "rb3/MusicLibrary.h" +#include "rb3/NodeSort.h" #include "rb3/Object.h" #include "rb3/PassiveMessagesPanel.h" #include "rb3/PresenceMgr.h" @@ -31,18 +34,21 @@ #include "rb3/SongMetadata.h" #include "rb3/SongParser.h" #include "rb3/SongSortByRecentEntry.h" +#include "rb3/SongSortMgr.h" #include "rb3/SortNode.h" #include "rb3/String.h" #include "rb3/Symbol.h" #include "rb3/TextStream.h" #include "rb3/TrackPanelDirBase.h" #include "rb3/UsbWii.h" -#include "rb3/Vector.h" #include "quazal/InetAddress.h" #include "quazal/QuazalSocket.h" // rnd headers +#include "rb3/Rnd/DynamicTex.h" #include "rb3/Rnd/RndPropAnim.h" +#include "rb3/Rnd/RndMat.h" +#include "rb3/Rnd/RndTex.h" // UI headers #include "rb3/UI/UIPanel.h" diff --git a/source/GemHooks.c b/source/GemHooks.c index d2cf90e..38a50ec 100644 --- a/source/GemHooks.c +++ b/source/GemHooks.c @@ -1,11 +1,13 @@ #include "GlobalSymbols.h" #include "ports.h" +#include "rb3/Mem.h" #include "rb3/ModifierManager.h" #include "rb3/GameGem.h" #include "rb3/Random.h" #include "rb3/Symbol.h" +#include "rb3/Vector.h" -int WillBeNoStrumHook(int *gameGemListPtr, int *multiGemInfoPtr) +int WillBeNoStrumHook(GameGemList *thisGameGemList, int *gem) { Modifier *forceHoposModifier; @@ -16,7 +18,7 @@ int WillBeNoStrumHook(int *gameGemListPtr, int *multiGemInfoPtr) } else { - return WillBeNoStrum(gameGemListPtr, multiGemInfoPtr); + return WillBeNoStrum(thisGameGemList, gem); } } @@ -62,12 +64,12 @@ int *GetWidgetByNameHook(int *gemManager, Symbol sym) } } -Symbol GetSlotColorHook(int *bandUser) +Symbol GetSlotColorHook(int *bandUser, int slot) { Modifier *colorShuffleModifier; Symbol *colors[5] = {&globalSymbols.green, &globalSymbols.red, &globalSymbols.yellow, &globalSymbols.blue, &globalSymbols.orange}; - Symbol slotColor = GetSlotColor(bandUser); + Symbol slotColor = GetSlotColor(bandUser, slot); colorShuffleModifier = ModifierActive(*(int *)PORT_MODIFIERMGR_POINTER, globalSymbols.colorShuffle, 0); @@ -120,7 +122,7 @@ void shuffleColors(GameGem *gem) gem->orange = colors[4]; } -int AddGameGemHook(void *gameGemList, GameGem *gem, NoStrumState gemType) +int AddGameGemHook(GameGemList *gameGemList, GameGem *gem, NoStrumState gemType) { Modifier *mirrorModeModifier; Modifier *gemShuffleModifier; diff --git a/source/LocaleHooks.c b/source/LocaleHooks.c index 8b3c3ca..18c5a9e 100644 --- a/source/LocaleHooks.c +++ b/source/LocaleHooks.c @@ -42,7 +42,8 @@ static char *newLocales[][2] = { {"mod_mirror_mode", "Mirror Mode"}, {"mod_color_shuffle", "Gem Color Shuffle"}, {"mod_gem_shuffle", "Note Shuffle"}, - {"mod_double_bass", "Double Bass Pedal"}}; + {"mod_double_bass", "Double Bass Pedal"} +}; static int numNewLocales = sizeof(newLocales) / sizeof(newLocales[0]); char RB3E_ActiveLocale[RB3E_LANG_LEN + 1] = {0}; @@ -67,17 +68,17 @@ int IsSupportedLanguageHook(Symbol lang, int r4) // this has to be accessible at all times, so can't put it on the stack static char mod_locale_string[0x80]; -char *LocalizeHook(int thisLocale, Symbol sym, int fail) +char *LocalizeHook(int thisLocale, Symbol token, int fail) { int i = 0; char newLocaleName[0x50]; Symbol newLocale; char *original; // game origin icons relies on using different formatting for the song/artist name - if (config.GameOriginIcons == 1 && sym.sym != NULL && strcmp(sym.sym, "song_artist_fmt") == 0) + if (config.GameOriginIcons == 1 && token.sym != NULL && strcmp(token.sym, "song_artist_fmt") == 0) return "%s %s"; // if the string starts with message_motd_ (but isn't message_motd), it's our motd - if (memcmp(sym.sym, "message_motd_", 13) == 0) + if (memcmp(token.sym, "message_motd_", 13) == 0) { // check for "rb3e_mod_string" SymbolConstruct(&newLocale, "rb3e_mod_string"); @@ -92,19 +93,19 @@ char *LocalizeHook(int thisLocale, Symbol sym, int fail) } #ifdef RB3E_XBOX // replace the "invite friends" label with "Manage Liveless" - if (config.LivelessAddress[0] != 0 && strcmp(sym.sym, "overshell_invite") == 0) + if (config.LivelessAddress[0] != 0 && strcmp(token.sym, "overshell_invite") == 0) return "Manage Liveless"; #endif // if our string is in the override list, use that string entirely for (i = 0; i < numOverrideLocales; i++) { - if (strcmp(sym.sym, overrideLocales[i][0]) == 0) + if (strcmp(token.sym, overrideLocales[i][0]) == 0) { // length sanity check before sprintf - if (strlen(sym.sym) > 0x40) - return Localize(thisLocale, sym, fail); + if (strlen(token.sym) > 0x40) + return Localize(thisLocale, token, fail); // check if rb3e_localename exists - sprintf(newLocaleName, "rb3e_%s", sym.sym); + sprintf(newLocaleName, "rb3e_%s", token.sym); SymbolConstruct(&newLocale, newLocaleName); original = Localize(thisLocale, newLocale, fail); if (original == NULL) @@ -113,12 +114,12 @@ char *LocalizeHook(int thisLocale, Symbol sym, int fail) } } // try to localize through to the original string - original = Localize(thisLocale, sym, fail); + original = Localize(thisLocale, token, fail); if (original == NULL) { // check to see if it's one of our new localisations for (i = 0; i < numNewLocales; i++) - if (strcmp(sym.sym, newLocales[i][0]) == 0) + if (strcmp(token.sym, newLocales[i][0]) == 0) return newLocales[i][1]; } return original; diff --git a/source/RndPropAnimHooks.c b/source/RndPropAnimHooks.c index eec9c15..5918f9b 100644 --- a/source/RndPropAnimHooks.c +++ b/source/RndPropAnimHooks.c @@ -10,7 +10,7 @@ // see https://github.com/RBEnhanced/RB3Enhanced/issues/2 void PropAnimSetFrameHook(RndPropAnim *rndPropAnim, float frame, float time) { - if (strcmp(rndPropAnim->object.name, "slot_positions.anim") == 0) + if (strcmp(rndPropAnim->mObject.name, "slot_positions.anim") == 0) { // check that it is not setting the frame to 0 (which is the default slot positions for overshell) if (frame != 0.0f) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 7301411..cbe8181 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -358,6 +358,8 @@ void InitialiseFunctions() POKE_B(&GetMetadata, PORT_GETMETADATA); POKE_B(&GetSongIDFromShortname, PORT_GETSONGIDFROMSHORTNAME); POKE_B(&GetBandUsers, PORT_GETBANDUSERS); + POKE_B(&GetBandUserFromSlot, PORT_GETBANDUSERFROMSLOT); + POKE_B(&ObjectFindUIPanel, PORT_OBJECTFINDUIPANEL); POKE_B(&FileStreamConstructor, PORT_FILESTREAM_CT); POKE_B(&ChunkStreamConstructor, PORT_CHUNKSTREAM_CT); POKE_B(&Dynamic_Cast, PORT_DYNAMICCAST); From 56afede29418d81938415e304afffdb68dd66082 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:27:50 -0500 Subject: [PATCH 063/123] idk --- include/rb3/Data.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rb3/Data.h b/include/rb3/Data.h index a1f9cd3..05988e0 100644 --- a/include/rb3/Data.h +++ b/include/rb3/Data.h @@ -7,7 +7,7 @@ typedef union _DataNode_Value { int intVal; float floatVal; - int *dataArray; + void *dataArray; int *object; char *string; } DataNode_Value; From b02dea11ca636f0384ea870f8322b8f2c42b820c Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:29:03 -0500 Subject: [PATCH 064/123] please --- include/rb3/Locale.h | 2 +- source/LocaleHooks.c | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/rb3/Locale.h b/include/rb3/Locale.h index 4d67514..94991bc 100644 --- a/include/rb3/Locale.h +++ b/include/rb3/Locale.h @@ -2,7 +2,7 @@ #define _LOCALE_H // I believe "fail" being set to 1 means that if there is no locale, it will return the raw Symbol and not a blank string or vice versa -extern char *Localize(int thisLocale, Symbol token, int fail); +extern char *Localize(int thisLocale, Symbol sym, int fail); extern void SetSystemLanguage(Symbol lang, int cheat); #endif // _LOCALE_H \ No newline at end of file diff --git a/source/LocaleHooks.c b/source/LocaleHooks.c index 18c5a9e..f258f1e 100644 --- a/source/LocaleHooks.c +++ b/source/LocaleHooks.c @@ -68,17 +68,17 @@ int IsSupportedLanguageHook(Symbol lang, int r4) // this has to be accessible at all times, so can't put it on the stack static char mod_locale_string[0x80]; -char *LocalizeHook(int thisLocale, Symbol token, int fail) +char *LocalizeHook(int thisLocale, Symbol sym, int fail) { int i = 0; char newLocaleName[0x50]; Symbol newLocale; char *original; // game origin icons relies on using different formatting for the song/artist name - if (config.GameOriginIcons == 1 && token.sym != NULL && strcmp(token.sym, "song_artist_fmt") == 0) + if (config.GameOriginIcons == 1 && sym.sym != NULL && strcmp(sym.sym, "song_artist_fmt") == 0) return "%s %s"; // if the string starts with message_motd_ (but isn't message_motd), it's our motd - if (memcmp(token.sym, "message_motd_", 13) == 0) + if (memcmp(sym.sym, "message_motd_", 13) == 0) { // check for "rb3e_mod_string" SymbolConstruct(&newLocale, "rb3e_mod_string"); @@ -93,19 +93,19 @@ char *LocalizeHook(int thisLocale, Symbol token, int fail) } #ifdef RB3E_XBOX // replace the "invite friends" label with "Manage Liveless" - if (config.LivelessAddress[0] != 0 && strcmp(token.sym, "overshell_invite") == 0) + if (config.LivelessAddress[0] != 0 && strcmp(sym.sym, "overshell_invite") == 0) return "Manage Liveless"; #endif // if our string is in the override list, use that string entirely for (i = 0; i < numOverrideLocales; i++) { - if (strcmp(token.sym, overrideLocales[i][0]) == 0) + if (strcmp(sym.sym, overrideLocales[i][0]) == 0) { // length sanity check before sprintf - if (strlen(token.sym) > 0x40) - return Localize(thisLocale, token, fail); + if (strlen(sym.sym) > 0x40) + return Localize(thisLocale, sym, fail); // check if rb3e_localename exists - sprintf(newLocaleName, "rb3e_%s", token.sym); + sprintf(newLocaleName, "rb3e_%s", sym.sym); SymbolConstruct(&newLocale, newLocaleName); original = Localize(thisLocale, newLocale, fail); if (original == NULL) @@ -114,12 +114,12 @@ char *LocalizeHook(int thisLocale, Symbol token, int fail) } } // try to localize through to the original string - original = Localize(thisLocale, token, fail); + original = Localize(thisLocale, sym, fail); if (original == NULL) { // check to see if it's one of our new localisations for (i = 0; i < numNewLocales; i++) - if (strcmp(token.sym, newLocales[i][0]) == 0) + if (strcmp(sym.sym, newLocales[i][0]) == 0) return newLocales[i][1]; } return original; From d5f5cfed1aa60872866286122cc22748bf4f1a94 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:30:48 -0500 Subject: [PATCH 065/123] yeah --- include/rb3/ChunkStream.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rb3/ChunkStream.h b/include/rb3/ChunkStream.h index 08f9ba5..a40a79c 100644 --- a/include/rb3/ChunkStream.h +++ b/include/rb3/ChunkStream.h @@ -38,6 +38,6 @@ struct _ChunkStream ChunkStream_vtable *vtable; }; -ChunkStream *ChunkStreamConstructor(ChunkStream *thisChunkStream, char *fileName, FileType fileType, int chunkSize, char compress, Platform platform, char cached); +ChunkStream *ChunkStreamConstructor(ChunkStream *thisChunkStream, char *fileName, int fileType, int chunkSize, char compress, Platform platform, char cached); #endif // _CHUNKSTREAM_H \ No newline at end of file From 07ea283436b1cca5c25cc659686c64f81a3828af Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:31:47 -0500 Subject: [PATCH 066/123] Update FileStream.h --- include/rb3/FileStream.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rb3/FileStream.h b/include/rb3/FileStream.h index ccdce2b..d159cc2 100644 --- a/include/rb3/FileStream.h +++ b/include/rb3/FileStream.h @@ -39,6 +39,6 @@ struct _FileStream char unk[0x256]; }; -FileStream *FileStreamConstructor(FileStream *thisFileStream, char *fileName, FileType fileType, char littleEndian); +FileStream *FileStreamConstructor(FileStream *thisFileStream, char *fileName, int fileType, char littleEndian); #endif // _FILESTREAM_H \ No newline at end of file From 2fc55356ac87a71ade04e520d9ecd48915121b7d Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:36:08 -0500 Subject: [PATCH 067/123] add ports --- include/ports_wii.h | 1 + include/ports_xbox360.h | 1 + 2 files changed, 2 insertions(+) diff --git a/include/ports_wii.h b/include/ports_wii.h index 2057140..b3b7b91 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -76,6 +76,7 @@ #define PORT_FILESTREAM_CT 0x8034c9f8 // FileStream::__ct (the one that takes a char * path instead of a File object) #define PORT_CHUNKSTREAM_CT 0x8034aa90 // ChunkStream::__ct #define PORT_GETBANDUSERFROMSLOT 0x8010021c // BandUserMgr::GetBandUserFromSlot +#define PORT_OVERSHELLPARTSELECTPROVIDERRELOAD 0x802478a8 // OvershellPartSelectProvider::Reload #define PORT_GETBANDUSERS 0x80100558 // BandUserMgr::GetBandUsers #define PORT_GETSONGSHORTNAME 0x80224edc // MetaPerformer::GetSongShortname(?) - actual name not known #define PORT_GETMETADATA 0x80515510 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) diff --git a/include/ports_xbox360.h b/include/ports_xbox360.h index d632192..456d4bf 100644 --- a/include/ports_xbox360.h +++ b/include/ports_xbox360.h @@ -108,6 +108,7 @@ #define PORT_GAME_CT 0x8267bf30 // Game::__ct #define PORT_GAME_DT 0x8267b1f0 // Game::__dt #define PORT_GAMEGETACTIVEPLAYER 0x82678e88 // Game::GetActivePlayer +#define PORT_OVERSHELLPARTSELECTPROVIDERRELOAD 0x82668c70 // OvershellPartSelectProvider::Reload #define PORT_GETBANDUSERS 0x82683b78 // BandUserMgr::GetBandUsers #define PORT_GETBANDUSERFROMSLOT 0x82682b60 // BandUserMgr::GetBandUserFromSlot #define PORT_GETSONGID 0x827a87f0 // GetSongID, function used when adding songs to BandSongMgr From 15ad5ebfa3b71558af8d4da94bde1e27976b335f Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:39:18 -0500 Subject: [PATCH 068/123] unused? --- source/rb3enhanced.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index cbe8181..22680ce 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -398,9 +398,9 @@ void ApplyHooks() POKE_B(PORT_DATAINITFUNCS_TAIL, &AddDTAFunctions); POKE_B(PORT_ISSUPPORTEDLANGUAGE, &IsSupportedLanguageHook); POKE_B(PORT_OVERSHELLPARTSELECTPROVIDERRELOAD, &OvershellPartSelectProviderReload); -#ifndef RB3E_WII_BANK8 - POKE_B(PORT_BUILDINSTRUMENTSELECTION, &BuildInstrumentSelectionList); -#endif +//#ifndef RB3E_WII_BANK8 +// POKE_B(PORT_BUILDINSTRUMENTSELECTION, &BuildInstrumentSelectionList); +//#endif POKE_BL(PORT_OPTIONSTR_DEFINE, &DefinesHook); POKE_BL(PORT_RUNLOOP_SPARE, &RB3E_RunLoop); HookFunction(PORT_LOCALIZE, &Localize, &LocalizeHook); From ad473795b0983e81510efcddd4d5a288b74e8fc6 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:41:23 -0500 Subject: [PATCH 069/123] Update SetlistHooks.c --- source/SetlistHooks.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index b63075d..fdeffdc 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -93,12 +93,12 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U // do a basic null check here, sometimes it can be null if (node != NULL && node->record != NULL && node->record->metaData != NULL && - node->record->metaData->mGameOrigin.sym != NULL) + node->record->metaData->gameOrigin.sym != NULL) { // this shit fucking sucks lol for (curInfo = 0; curInfo < numGameOrigins; curInfo++) { - if (strcmp(node->record->metaData->mGameOrigin.sym, originInfo[curInfo].gameOrigin) == 0) + if (strcmp(node->record->metaData->gameOrigin.sym, originInfo[curInfo].gameOrigin) == 0) { if (textures[originInfo[curInfo].num] != NULL && textures[originInfo[curInfo].num]->mMat != NULL) { @@ -166,9 +166,9 @@ SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataAr thisSongMetadata = SongMetadataConstructor(thisSongMetadata, data, backupData, isOnDisc); // make sure the game origin isn't null - if (thisSongMetadata->mGameOrigin.sym != 0) + if (thisSongMetadata->gameOrigin.sym != 0) { - AddGameOriginToIconList(thisSongMetadata->mGameOrigin.sym); + AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); return thisSongMetadata; } @@ -181,9 +181,9 @@ char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream) char ret = SongMetadataLoad(thisSongMetadata, stream); // make sure the game origin isn't null - if (thisSongMetadata->mGameOrigin.sym != 0) + if (thisSongMetadata->gameOrigin.sym != 0) { - AddGameOriginToIconList(thisSongMetadata->mGameOrigin.sym); + AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); return ret; } From 34f6046efc5659b20175be9d83494c21232e48ab Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:49:03 -0500 Subject: [PATCH 070/123] bro this shit is ass --- source/SongHooks.c | 183 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 172 insertions(+), 11 deletions(-) diff --git a/source/SongHooks.c b/source/SongHooks.c index 6b7b856..5013c3b 100644 --- a/source/SongHooks.c +++ b/source/SongHooks.c @@ -1,23 +1,82 @@ /* - RB3Enhanced - SongHooks.c - Hooks related to loading songs into the game. + RB3Enhanced - SetlistHooks.h + Hooks for modifying the setlist/song selection screen. */ -#include +#include #include +#include +#include "config.h" #include "ports.h" -#include "crc32.h" #include "GameOriginInfo.h" +#include "rb3/BandLabel.h" #include "rb3/File.h" #include "rb3/PassiveMessagesPanel.h" #include "rb3/SongMetadata.h" #include "rb3/Data.h" #include "rb3/Random.h" +#include "rb3/FilePath.h" +#include "rb3/MusicLibrary.h" +#include "rb3/NodeSort.h" +#include "rb3/UI/UIListSlot.h" +#include "rb3/UI/UIPanel.h" +#include "SongSort.h" +#include "rb3/SortNode.h" +#include "rb3/SongSortMgr.h" +#include "rb3/Rnd/DynamicTex.h" +#include "rb3/Rnd/RndMat.h" int RB3E_LoadedSongCount = 0; +DynamicTex *textures[100] = {0}; static int HasShownCorrectionError = 0; static int CorrectSongID(DataNode *id_node, int ct_metadata) + +void CreateMaterial(GameOriginInfo *info) +{ + // alloc the memory for the dynamic tex + DynamicTex *tex = NULL; + char path[0x200]; + + // TODO: we should identify and hook some function that runs when you leave the song select screen to call DynamicTex's destructor + // it will free the dynamic tex itself, the material it created, and the texture too, so it nicely wraps it all up for you + // this way there is not a chunk of memory permanently dedicated to game origin icons (even if it is not a large amount) + tex = MemAlloc(0x20, 0); + + // build the ark path (so dont include /gen/ or the _platform extension etc.) + RB3E_DEBUG("Creating dynamic tex for game origin '%s'", info->gameOrigin); + strcpy(path, "ui/resource/game_origins/"); + strcat(path, info->gameOrigin); + strcat(path, ".png"); + + // create and pray + DynamicTexConstructor(tex, path, info->gameOrigin, 1, 0); + + textures[info->num] = tex; + + RB3E_DEBUG("Setting diffuse texture for dynamic tex '%s'", path); + RndTexSetBitmap3(tex->mTex, tex->mFileLoader); + + // diffuse tex setter function doesn't exist on 360, so we manually set it, but manually setting it doesn't work on wii and we must use the setter + // TODO: figure out why we can't just set it manually on both, probably a structure inaccuracy or something + #ifdef RB3E_XBOX + tex->mMat->diffuseTex.tex = tex->mTex; + #else + RndMatSetDiffuseTex(tex->mMat, tex->mTex); + #endif + + // print the material name + RB3E_DEBUG("Dynamic tex created at %p with material '%s'", textures[info->num], textures[info->num]->mMatName.buf); + + +} + +int *MusicLibraryConstructorHook(MusicLibrary *thisMusicLibrary, int *songPreview) +{ + return MusicLibraryConstructor(thisMusicLibrary, songPreview); +} + +RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) { unsigned int checksum = 0; // shouldn't be possible to get here @@ -34,28 +93,112 @@ static int CorrectSongID(DataNode *id_node, int ct_metadata) RB3E_MSG("Corrected song_id '%s' into ID: %i", id_node->value.string, checksum); // display a warning message on screen for the first bad song found if (HasShownCorrectionError == 0) + if (listSlot != NULL && thisMusicLibrary != NULL) { HasShownCorrectionError = 1; DisplayMessage("One (or more) of your songs has been corrected."); + if (strcmp(listSlot->mMatchName.buf, "game_origin_icon") == 0) + { + int *ret = 0; + SortNode *node = 0; + SongNodeType nodeType = kNodeNone; + int curInfo = 0; + + ret = SongSortMgrGetSort(*(SongSortMgr **)PORT_THESONGSORTMGR, thisMusicLibrary->mSortType); + if (ret != NULL) + { + node = NodeSortGetNode(ret, idx); + if (node != NULL) + { + nodeType = node->vtable->getNodeType(); + + // ensure this is actually a song node and not like a function node or etc. + if (nodeType == kNodeSong) + { + // do a basic null check here, sometimes it can be null + if (node != NULL && node->record != NULL && + node->record->metaData != NULL && + node->record->metaData->gameOrigin.sym != NULL) + { + // this shit fucking sucks lol + for (curInfo = 0; curInfo < numGameOrigins; curInfo++) + { + if (strcmp(node->record->metaData->gameOrigin.sym, originInfo[curInfo].gameOrigin) == 0) + { + if (textures[originInfo[curInfo].num] != NULL && textures[originInfo[curInfo].num]->mMat != NULL) + { + RB3E_DEBUG("Returning material for game origin '%s'", originInfo[curInfo].gameOrigin); + return textures[originInfo[curInfo].num]->mMat; + } + else + { + RB3E_DEBUG("Material for game origin '%s' is NULL, creation seemingly failed", originInfo[curInfo].gameOrigin); + } + } + } + } else { + RB3E_DEBUG("Node record or its metadata is NULL, skipping", NULL); + } + } + } + } + } } - return checksum; + + return MusicLibraryMat(thisMusicLibrary, data, idx, listSlot); } int MetadataSongIDHook(DataNode *song_id) +int numGameOrigins; +GameOriginInfo originInfo[100] = {0}; + +void AddGameOriginToIconList(char *gameOrigin) { DataNode *eval = DataNodeEvaluate(song_id); + if(gameOrigin != NULL && strcmp(gameOrigin, "") != 0) + { + int i = 0; + + // check that we havent already added 100 game origins + if (numGameOrigins >= 100) + { + RB3E_DEBUG("Too many game origins with icons, not adding '%s'", gameOrigin); + return; + } + + // make sure the game origin isn't already in the array + for (i = 0; i < numGameOrigins; i++) + { + if (strcmp(originInfo[i].gameOrigin, gameOrigin) == 0) + { + return; + } + } + + originInfo[numGameOrigins].gameOrigin = gameOrigin; + originInfo[numGameOrigins].num = numGameOrigins; + CreateMaterial(&originInfo[numGameOrigins]); + numGameOrigins++; + RB3E_DEBUG("Adding game origin '%s' to icon list, total is now %i", gameOrigin, numGameOrigins); + } + else + { + RB3E_DEBUG("Game origin is NULL or empty, not adding to icon list", NULL); + } // if our song id isn't an int, correct it with crc32 over the string if (eval->type != INT_VALUE) eval->value.intVal = CorrectSongID(eval, 1); return eval->value.intVal; } -int GetSongIDHook(DataArray *song, DataArray *missing_data_maybe) +// this will be called any time a song is loaded from DTA (on disc or when loading into the cache) +SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc) { Symbol song_id; DataNode *found = NULL; DataArray *array = NULL; SymbolConstruct(&song_id, "song_id"); + thisSongMetadata = SongMetadataConstructor(thisSongMetadata, data, backupData, isOnDisc); if (song == NULL) return 0; // check missing song data first if we have it @@ -73,12 +216,30 @@ int GetSongIDHook(DataArray *song, DataArray *missing_data_maybe) return 0; if (found->type != INT_VALUE) return CorrectSongID(found, 0); - return found->value.intVal; + + // make sure the game origin isn't null + if (thisSongMetadata->gameOrigin.sym != 0) + { + AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); + return thisSongMetadata; + } + + return thisSongMetadata; } -SongMetadata *InitSongMetadataHook(SongMetadata *songMetadata) +// this will be called when a song is loaded from cache +char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream) { // increment the loaded song count based on how many songmetadata objects there are - RB3E_LoadedSongCount++; - return InitSongMetadata(songMetadata); -} + char ret = SongMetadataLoad(thisSongMetadata, stream); + + // make sure the game origin isn't null + if (thisSongMetadata->gameOrigin.sym != 0) + { + RB3E_LoadedSongCount++; + AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); + return ret; + } + + return ret; +} \ No newline at end of file From c456707f27141dca2bcf4b76abdd26036793d1ce Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:52:39 -0500 Subject: [PATCH 071/123] oof --- source/SongHooks.c | 54 ---------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/source/SongHooks.c b/source/SongHooks.c index 5013c3b..fdeffdc 100644 --- a/source/SongHooks.c +++ b/source/SongHooks.c @@ -11,10 +11,6 @@ #include "GameOriginInfo.h" #include "rb3/BandLabel.h" #include "rb3/File.h" -#include "rb3/PassiveMessagesPanel.h" -#include "rb3/SongMetadata.h" -#include "rb3/Data.h" -#include "rb3/Random.h" #include "rb3/FilePath.h" #include "rb3/MusicLibrary.h" #include "rb3/NodeSort.h" @@ -26,11 +22,7 @@ #include "rb3/Rnd/DynamicTex.h" #include "rb3/Rnd/RndMat.h" -int RB3E_LoadedSongCount = 0; - DynamicTex *textures[100] = {0}; -static int HasShownCorrectionError = 0; -static int CorrectSongID(DataNode *id_node, int ct_metadata) void CreateMaterial(GameOriginInfo *info) { @@ -78,25 +70,8 @@ int *MusicLibraryConstructorHook(MusicLibrary *thisMusicLibrary, int *songPrevie RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) { - unsigned int checksum = 0; - // shouldn't be possible to get here - if (id_node->type == INT_VALUE || id_node->value.string == NULL) - return id_node->value.intVal; - // run a CRC32 sum over the whole length of the string - crc32(id_node->value.string, strlen(id_node->value.string), &checksum); - // move it around a bit just to make things more consistent - // risks introducing more collisions, BAD CUSTOMS ARE STILL BAD!! - checksum %= 9999999; - checksum += 2130000000; - // output to the log whenever things are corrected when crafting SongMetadata - if (ct_metadata) - RB3E_MSG("Corrected song_id '%s' into ID: %i", id_node->value.string, checksum); - // display a warning message on screen for the first bad song found - if (HasShownCorrectionError == 0) if (listSlot != NULL && thisMusicLibrary != NULL) { - HasShownCorrectionError = 1; - DisplayMessage("One (or more) of your songs has been corrected."); if (strcmp(listSlot->mMatchName.buf, "game_origin_icon") == 0) { int *ret = 0; @@ -148,13 +123,11 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U return MusicLibraryMat(thisMusicLibrary, data, idx, listSlot); } -int MetadataSongIDHook(DataNode *song_id) int numGameOrigins; GameOriginInfo originInfo[100] = {0}; void AddGameOriginToIconList(char *gameOrigin) { - DataNode *eval = DataNodeEvaluate(song_id); if(gameOrigin != NULL && strcmp(gameOrigin, "") != 0) { int i = 0; @@ -185,37 +158,12 @@ void AddGameOriginToIconList(char *gameOrigin) { RB3E_DEBUG("Game origin is NULL or empty, not adding to icon list", NULL); } - // if our song id isn't an int, correct it with crc32 over the string - if (eval->type != INT_VALUE) - eval->value.intVal = CorrectSongID(eval, 1); - return eval->value.intVal; } // this will be called any time a song is loaded from DTA (on disc or when loading into the cache) SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc) { - Symbol song_id; - DataNode *found = NULL; - DataArray *array = NULL; - SymbolConstruct(&song_id, "song_id"); thisSongMetadata = SongMetadataConstructor(thisSongMetadata, data, backupData, isOnDisc); - if (song == NULL) - return 0; - // check missing song data first if we have it - if (missing_data_maybe != NULL) - array = DataFindArray(missing_data_maybe, song_id); - // if there's nothing in missing song data, check song array - if (array == NULL) - array = DataFindArray(song, song_id); - // no song_id? idk man - if (array == NULL) - return 0; - // get the value from song_id - found = DataNodeEvaluate(&array->mNodes->n[1]); - if (found == NULL) - return 0; - if (found->type != INT_VALUE) - return CorrectSongID(found, 0); // make sure the game origin isn't null if (thisSongMetadata->gameOrigin.sym != 0) @@ -230,13 +178,11 @@ SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataAr // this will be called when a song is loaded from cache char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream) { - // increment the loaded song count based on how many songmetadata objects there are char ret = SongMetadataLoad(thisSongMetadata, stream); // make sure the game origin isn't null if (thisSongMetadata->gameOrigin.sym != 0) { - RB3E_LoadedSongCount++; AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); return ret; } From 76b8503c77012bd91615bc5950ba2c857854ada9 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:54:11 -0500 Subject: [PATCH 072/123] idk --- source/SongHooks.c | 227 ++++++++++++--------------------------------- 1 file changed, 60 insertions(+), 167 deletions(-) diff --git a/source/SongHooks.c b/source/SongHooks.c index fdeffdc..fbf8538 100644 --- a/source/SongHooks.c +++ b/source/SongHooks.c @@ -1,191 +1,84 @@ /* - RB3Enhanced - SetlistHooks.h - Hooks for modifying the setlist/song selection screen. + RB3Enhanced - SongHooks.c + Hooks related to loading songs into the game. */ -#include -#include #include -#include "config.h" +#include #include "ports.h" +#include "crc32.h" #include "GameOriginInfo.h" -#include "rb3/BandLabel.h" #include "rb3/File.h" -#include "rb3/FilePath.h" -#include "rb3/MusicLibrary.h" -#include "rb3/NodeSort.h" -#include "rb3/UI/UIListSlot.h" -#include "rb3/UI/UIPanel.h" -#include "SongSort.h" -#include "rb3/SortNode.h" -#include "rb3/SongSortMgr.h" -#include "rb3/Rnd/DynamicTex.h" -#include "rb3/Rnd/RndMat.h" - -DynamicTex *textures[100] = {0}; - -void CreateMaterial(GameOriginInfo *info) -{ - // alloc the memory for the dynamic tex - DynamicTex *tex = NULL; - char path[0x200]; - - // TODO: we should identify and hook some function that runs when you leave the song select screen to call DynamicTex's destructor - // it will free the dynamic tex itself, the material it created, and the texture too, so it nicely wraps it all up for you - // this way there is not a chunk of memory permanently dedicated to game origin icons (even if it is not a large amount) - tex = MemAlloc(0x20, 0); - - // build the ark path (so dont include /gen/ or the _platform extension etc.) - RB3E_DEBUG("Creating dynamic tex for game origin '%s'", info->gameOrigin); - strcpy(path, "ui/resource/game_origins/"); - strcat(path, info->gameOrigin); - strcat(path, ".png"); - - // create and pray - DynamicTexConstructor(tex, path, info->gameOrigin, 1, 0); - - textures[info->num] = tex; +#include "rb3/PassiveMessagesPanel.h" +#include "rb3/SongMetadata.h" +#include "rb3/Data.h" +#include "rb3/Random.h" - RB3E_DEBUG("Setting diffuse texture for dynamic tex '%s'", path); - RndTexSetBitmap3(tex->mTex, tex->mFileLoader); +int RB3E_LoadedSongCount = 0; - // diffuse tex setter function doesn't exist on 360, so we manually set it, but manually setting it doesn't work on wii and we must use the setter - // TODO: figure out why we can't just set it manually on both, probably a structure inaccuracy or something - #ifdef RB3E_XBOX - tex->mMat->diffuseTex.tex = tex->mTex; - #else - RndMatSetDiffuseTex(tex->mMat, tex->mTex); - #endif - - // print the material name - RB3E_DEBUG("Dynamic tex created at %p with material '%s'", textures[info->num], textures[info->num]->mMatName.buf); - - -} - -int *MusicLibraryConstructorHook(MusicLibrary *thisMusicLibrary, int *songPreview) +static int HasShownCorrectionError = 0; +static int CorrectSongID(DataNode *id_node, int ct_metadata) { - return MusicLibraryConstructor(thisMusicLibrary, songPreview); -} - -RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) -{ - if (listSlot != NULL && thisMusicLibrary != NULL) + unsigned int checksum = 0; + // shouldn't be possible to get here + if (id_node->type == INT_VALUE || id_node->value.string == NULL) + return id_node->value.intVal; + // run a CRC32 sum over the whole length of the string + crc32(id_node->value.string, strlen(id_node->value.string), &checksum); + // move it around a bit just to make things more consistent + // risks introducing more collisions, BAD CUSTOMS ARE STILL BAD!! + checksum %= 9999999; + checksum += 2130000000; + // output to the log whenever things are corrected when crafting SongMetadata + if (ct_metadata) + RB3E_MSG("Corrected song_id '%s' into ID: %i", id_node->value.string, checksum); + // display a warning message on screen for the first bad song found + if (HasShownCorrectionError == 0) { - if (strcmp(listSlot->mMatchName.buf, "game_origin_icon") == 0) - { - int *ret = 0; - SortNode *node = 0; - SongNodeType nodeType = kNodeNone; - int curInfo = 0; - - ret = SongSortMgrGetSort(*(SongSortMgr **)PORT_THESONGSORTMGR, thisMusicLibrary->mSortType); - if (ret != NULL) - { - node = NodeSortGetNode(ret, idx); - if (node != NULL) - { - nodeType = node->vtable->getNodeType(); - - // ensure this is actually a song node and not like a function node or etc. - if (nodeType == kNodeSong) - { - // do a basic null check here, sometimes it can be null - if (node != NULL && node->record != NULL && - node->record->metaData != NULL && - node->record->metaData->gameOrigin.sym != NULL) - { - // this shit fucking sucks lol - for (curInfo = 0; curInfo < numGameOrigins; curInfo++) - { - if (strcmp(node->record->metaData->gameOrigin.sym, originInfo[curInfo].gameOrigin) == 0) - { - if (textures[originInfo[curInfo].num] != NULL && textures[originInfo[curInfo].num]->mMat != NULL) - { - RB3E_DEBUG("Returning material for game origin '%s'", originInfo[curInfo].gameOrigin); - return textures[originInfo[curInfo].num]->mMat; - } - else - { - RB3E_DEBUG("Material for game origin '%s' is NULL, creation seemingly failed", originInfo[curInfo].gameOrigin); - } - } - } - } else { - RB3E_DEBUG("Node record or its metadata is NULL, skipping", NULL); - } - } - } - } - } + HasShownCorrectionError = 1; + DisplayMessage("One (or more) of your songs has been corrected."); } - - return MusicLibraryMat(thisMusicLibrary, data, idx, listSlot); + return checksum; } -int numGameOrigins; -GameOriginInfo originInfo[100] = {0}; - -void AddGameOriginToIconList(char *gameOrigin) +int MetadataSongIDHook(DataNode *song_id) { - if(gameOrigin != NULL && strcmp(gameOrigin, "") != 0) - { - int i = 0; - - // check that we havent already added 100 game origins - if (numGameOrigins >= 100) - { - RB3E_DEBUG("Too many game origins with icons, not adding '%s'", gameOrigin); - return; - } - - // make sure the game origin isn't already in the array - for (i = 0; i < numGameOrigins; i++) - { - if (strcmp(originInfo[i].gameOrigin, gameOrigin) == 0) - { - return; - } - } - - originInfo[numGameOrigins].gameOrigin = gameOrigin; - originInfo[numGameOrigins].num = numGameOrigins; - CreateMaterial(&originInfo[numGameOrigins]); - numGameOrigins++; - RB3E_DEBUG("Adding game origin '%s' to icon list, total is now %i", gameOrigin, numGameOrigins); - } - else - { - RB3E_DEBUG("Game origin is NULL or empty, not adding to icon list", NULL); - } + DataNode *eval = DataNodeEvaluate(song_id); + // if our song id isn't an int, correct it with crc32 over the string + if (eval->type != INT_VALUE) + eval->value.intVal = CorrectSongID(eval, 1); + return eval->value.intVal; } -// this will be called any time a song is loaded from DTA (on disc or when loading into the cache) -SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc) +int GetSongIDHook(DataArray *song, DataArray *missing_data_maybe) { - thisSongMetadata = SongMetadataConstructor(thisSongMetadata, data, backupData, isOnDisc); - - // make sure the game origin isn't null - if (thisSongMetadata->gameOrigin.sym != 0) + Symbol song_id; + DataNode *found; + DataArray *array; + SymbolConstruct(&song_id, "song_id"); + if (song == NULL) + return 0; + array = DataFindArray(song, song_id); + if (array == NULL) { - AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); - return thisSongMetadata; + if (missing_data_maybe == NULL) + return 0; + else + array = DataFindArray(missing_data_maybe, song_id); } - - return thisSongMetadata; + if (array == NULL) + return 0; + found = DataNodeEvaluate(&array->mNodes->n[1]); + if (found == NULL) + return 0; + if (found->type != INT_VALUE) + return CorrectSongID(found, 0); + return found->value.intVal; } -// this will be called when a song is loaded from cache -char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream) +SongMetadata *InitSongMetadataHook(SongMetadata *songMetadata) { - char ret = SongMetadataLoad(thisSongMetadata, stream); - - // make sure the game origin isn't null - if (thisSongMetadata->gameOrigin.sym != 0) - { - AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); - return ret; - } - - return ret; + // increment the loaded song count based on how many songmetadata objects there are + RB3E_LoadedSongCount++; + return InitSongMetadata(songMetadata); } \ No newline at end of file From e3f6234430227a62d242db08eb363605d3ccdd90 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:55:29 -0500 Subject: [PATCH 073/123] Update SetlistHooks.c --- source/SetlistHooks.c | 213 +++++++++++------------------------------- 1 file changed, 55 insertions(+), 158 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index fdeffdc..efd4347 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -8,184 +8,81 @@ #include #include "config.h" #include "ports.h" -#include "GameOriginInfo.h" #include "rb3/BandLabel.h" -#include "rb3/File.h" -#include "rb3/FilePath.h" -#include "rb3/MusicLibrary.h" -#include "rb3/NodeSort.h" -#include "rb3/UI/UIListSlot.h" #include "rb3/UI/UIPanel.h" -#include "SongSort.h" #include "rb3/SortNode.h" -#include "rb3/SongSortMgr.h" -#include "rb3/Rnd/DynamicTex.h" -#include "rb3/Rnd/RndMat.h" -DynamicTex *textures[100] = {0}; - -void CreateMaterial(GameOriginInfo *info) -{ - // alloc the memory for the dynamic tex - DynamicTex *tex = NULL; - char path[0x200]; - - // TODO: we should identify and hook some function that runs when you leave the song select screen to call DynamicTex's destructor - // it will free the dynamic tex itself, the material it created, and the texture too, so it nicely wraps it all up for you - // this way there is not a chunk of memory permanently dedicated to game origin icons (even if it is not a large amount) - tex = MemAlloc(0x20, 0); - - // build the ark path (so dont include /gen/ or the _platform extension etc.) - RB3E_DEBUG("Creating dynamic tex for game origin '%s'", info->gameOrigin); - strcpy(path, "ui/resource/game_origins/"); - strcat(path, info->gameOrigin); - strcat(path, ".png"); - - // create and pray - DynamicTexConstructor(tex, path, info->gameOrigin, 1, 0); - - textures[info->num] = tex; - - RB3E_DEBUG("Setting diffuse texture for dynamic tex '%s'", path); - RndTexSetBitmap3(tex->mTex, tex->mFileLoader); - - // diffuse tex setter function doesn't exist on 360, so we manually set it, but manually setting it doesn't work on wii and we must use the setter - // TODO: figure out why we can't just set it manually on both, probably a structure inaccuracy or something - #ifdef RB3E_XBOX - tex->mMat->diffuseTex.tex = tex->mTex; - #else - RndMatSetDiffuseTex(tex->mMat, tex->mTex); - #endif - - // print the material name - RB3E_DEBUG("Dynamic tex created at %p with material '%s'", textures[info->num], textures[info->num]->mMatName.buf); - - -} - -int *MusicLibraryConstructorHook(MusicLibrary *thisMusicLibrary, int *songPreview) +static char *originToIcon[][2] = { + {"rb1", "Y "}, + {"rb2", "2 "}, + {"rb3", "B "}, + {"rb4", "1 "}, + {"rb_blitz", "X "}, + {"ugc", "U "}, + {"ugc_c3", "y "}, + {"ugc_plus", "U "}, + {"ugc1", "U "}, + {"ugc2", "U "}, + {"lego", "A "}, + {"greenday", "0 "}, + {"beatles", "b "}, + {"gh1", "R "}, + {"gh2", "S "}, + {"gh3", "s "}, + {"onyxite", "G "}, +}; +static int numOriginToIcon = sizeof(originToIcon) / sizeof(originToIcon[0]); + +void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) { - return MusicLibraryConstructor(thisMusicLibrary, songPreview); -} + char newLabel[1024] = {0}; + char *originLabel = "0 "; // default + int i = 0; -RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) -{ - if (listSlot != NULL && thisMusicLibrary != NULL) + if (config.GameOriginIcons == 1 && strlen(label->string) < 1000) { - if (strcmp(listSlot->mMatchName.buf, "game_origin_icon") == 0) + SetSongAndArtistName(label, sortNode); + for (i = 0; i < numOriginToIcon; i++) { - int *ret = 0; - SortNode *node = 0; - SongNodeType nodeType = kNodeNone; - int curInfo = 0; - - ret = SongSortMgrGetSort(*(SongSortMgr **)PORT_THESONGSORTMGR, thisMusicLibrary->mSortType); - if (ret != NULL) + if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { - node = NodeSortGetNode(ret, idx); - if (node != NULL) - { - nodeType = node->vtable->getNodeType(); - - // ensure this is actually a song node and not like a function node or etc. - if (nodeType == kNodeSong) - { - // do a basic null check here, sometimes it can be null - if (node != NULL && node->record != NULL && - node->record->metaData != NULL && - node->record->metaData->gameOrigin.sym != NULL) - { - // this shit fucking sucks lol - for (curInfo = 0; curInfo < numGameOrigins; curInfo++) - { - if (strcmp(node->record->metaData->gameOrigin.sym, originInfo[curInfo].gameOrigin) == 0) - { - if (textures[originInfo[curInfo].num] != NULL && textures[originInfo[curInfo].num]->mMat != NULL) - { - RB3E_DEBUG("Returning material for game origin '%s'", originInfo[curInfo].gameOrigin); - return textures[originInfo[curInfo].num]->mMat; - } - else - { - RB3E_DEBUG("Material for game origin '%s' is NULL, creation seemingly failed", originInfo[curInfo].gameOrigin); - } - } - } - } else { - RB3E_DEBUG("Node record or its metadata is NULL, skipping", NULL); - } - } - } + originLabel = originToIcon[i][1]; + break; } } + strcat(newLabel, originLabel); + strcat(newLabel, label->string); + BandLabelSetDisplayText(label, newLabel, 1); + return; } - - return MusicLibraryMat(thisMusicLibrary, data, idx, listSlot); + SetSongAndArtistName(label, sortNode); + return; } -int numGameOrigins; -GameOriginInfo originInfo[100] = {0}; - -void AddGameOriginToIconList(char *gameOrigin) +void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) { - if(gameOrigin != NULL && strcmp(gameOrigin, "") != 0) - { + char newLabel[1024] = {0}; + char *originLabel = "0 "; // default int i = 0; - // check that we havent already added 100 game origins - if (numGameOrigins >= 100) - { - RB3E_DEBUG("Too many game origins with icons, not adding '%s'", gameOrigin); - return; - } + RB3E_DEBUG("SetSongNameFromNode: %s", label->string); - // make sure the game origin isn't already in the array - for (i = 0; i < numGameOrigins; i++) + if (config.GameOriginIcons == 1 && strlen(label->string) < 1000) { - if (strcmp(originInfo[i].gameOrigin, gameOrigin) == 0) + SetSongNameFromNode(label, sortNode); + for (i = 0; i < numOriginToIcon; i++) { - return; + if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + { + originLabel = originToIcon[i][1]; + break; + } } + strcat(newLabel, originLabel); + strcat(newLabel, label->string); + BandLabelSetDisplayText(label, newLabel, 1); + return; } - - originInfo[numGameOrigins].gameOrigin = gameOrigin; - originInfo[numGameOrigins].num = numGameOrigins; - CreateMaterial(&originInfo[numGameOrigins]); - numGameOrigins++; - RB3E_DEBUG("Adding game origin '%s' to icon list, total is now %i", gameOrigin, numGameOrigins); - } - else - { - RB3E_DEBUG("Game origin is NULL or empty, not adding to icon list", NULL); - } -} - -// this will be called any time a song is loaded from DTA (on disc or when loading into the cache) -SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc) -{ - thisSongMetadata = SongMetadataConstructor(thisSongMetadata, data, backupData, isOnDisc); - - // make sure the game origin isn't null - if (thisSongMetadata->gameOrigin.sym != 0) - { - AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); - return thisSongMetadata; - } - - return thisSongMetadata; -} - -// this will be called when a song is loaded from cache -char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream) -{ - char ret = SongMetadataLoad(thisSongMetadata, stream); - - // make sure the game origin isn't null - if (thisSongMetadata->gameOrigin.sym != 0) - { - AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); - return ret; - } - - return ret; + SetSongNameFromNode(label, sortNode); + return; } \ No newline at end of file From f936041495cda5bb00a43eeced9c5716f2e922f5 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:56:48 -0500 Subject: [PATCH 074/123] Update SetlistHooks.c --- source/SetlistHooks.c | 212 +++++++++++++++++++++++++++++++----------- 1 file changed, 158 insertions(+), 54 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index efd4347..86d200c 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -8,81 +8,185 @@ #include #include "config.h" #include "ports.h" +#include "GameOriginInfo.h" #include "rb3/BandLabel.h" +#include "rb3/File.h" +#include "rb3/FilePath.h" +#include "rb3/MusicLibrary.h" +#include "rb3/NodeSort.h" +#include "rb3/UI/UIListSlot.h" #include "rb3/UI/UIPanel.h" +#include "SongSort.h" #include "rb3/SortNode.h" - -static char *originToIcon[][2] = { - {"rb1", "Y "}, - {"rb2", "2 "}, - {"rb3", "B "}, - {"rb4", "1 "}, - {"rb_blitz", "X "}, - {"ugc", "U "}, - {"ugc_c3", "y "}, - {"ugc_plus", "U "}, - {"ugc1", "U "}, - {"ugc2", "U "}, - {"lego", "A "}, - {"greenday", "0 "}, - {"beatles", "b "}, - {"gh1", "R "}, - {"gh2", "S "}, - {"gh3", "s "}, - {"onyxite", "G "}, -}; -static int numOriginToIcon = sizeof(originToIcon) / sizeof(originToIcon[0]); +#include "rb3/SongSortMgr.h" +#include "rb3/Rnd/DynamicTex.h" +#include "rb3/Rnd/RndMat.h" void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) +DynamicTex *textures[100] = {0}; + +void CreateMaterial(GameOriginInfo *info) { - char newLabel[1024] = {0}; - char *originLabel = "0 "; // default - int i = 0; + // alloc the memory for the dynamic tex + DynamicTex *tex = NULL; + char path[0x200]; + + // TODO: we should identify and hook some function that runs when you leave the song select screen to call DynamicTex's destructor + // it will free the dynamic tex itself, the material it created, and the texture too, so it nicely wraps it all up for you + // this way there is not a chunk of memory permanently dedicated to game origin icons (even if it is not a large amount) + tex = MemAlloc(0x20, 0); + + // build the ark path (so dont include /gen/ or the _platform extension etc.) + RB3E_DEBUG("Creating dynamic tex for game origin '%s'", info->gameOrigin); + strcpy(path, "ui/resource/game_origins/"); + strcat(path, info->gameOrigin); + strcat(path, ".png"); + + // create and pray + DynamicTexConstructor(tex, path, info->gameOrigin, 1, 0); + + textures[info->num] = tex; + + RB3E_DEBUG("Setting diffuse texture for dynamic tex '%s'", path); + RndTexSetBitmap3(tex->mTex, tex->mFileLoader); + + // diffuse tex setter function doesn't exist on 360, so we manually set it, but manually setting it doesn't work on wii and we must use the setter + // TODO: figure out why we can't just set it manually on both, probably a structure inaccuracy or something + #ifdef RB3E_XBOX + tex->mMat->diffuseTex.tex = tex->mTex; + #else + RndMatSetDiffuseTex(tex->mMat, tex->mTex); + #endif + + // print the material name + RB3E_DEBUG("Dynamic tex created at %p with material '%s'", textures[info->num], textures[info->num]->mMatName.buf); + + +} + +int *MusicLibraryConstructorHook(MusicLibrary *thisMusicLibrary, int *songPreview) +{ + return MusicLibraryConstructor(thisMusicLibrary, songPreview); +} - if (config.GameOriginIcons == 1 && strlen(label->string) < 1000) +RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) +{ + if (listSlot != NULL && thisMusicLibrary != NULL) { - SetSongAndArtistName(label, sortNode); - for (i = 0; i < numOriginToIcon; i++) + if (strcmp(listSlot->mMatchName.buf, "game_origin_icon") == 0) { - if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + int *ret = 0; + SortNode *node = 0; + SongNodeType nodeType = kNodeNone; + int curInfo = 0; + + ret = SongSortMgrGetSort(*(SongSortMgr **)PORT_THESONGSORTMGR, thisMusicLibrary->mSortType); + if (ret != NULL) { - originLabel = originToIcon[i][1]; - break; + node = NodeSortGetNode(ret, idx); + if (node != NULL) + { + nodeType = node->vtable->getNodeType(); + + // ensure this is actually a song node and not like a function node or etc. + if (nodeType == kNodeSong) + { + // do a basic null check here, sometimes it can be null + if (node != NULL && node->record != NULL && + node->record->metaData != NULL && + node->record->metaData->mGameOrigin.sym != NULL) + { + // this shit fucking sucks lol + for (curInfo = 0; curInfo < numGameOrigins; curInfo++) + { + if (strcmp(node->record->metaData->mGameOrigin.sym, originInfo[curInfo].gameOrigin) == 0) + { + if (textures[originInfo[curInfo].num] != NULL && textures[originInfo[curInfo].num]->mMat != NULL) + { + RB3E_DEBUG("Returning material for game origin '%s'", originInfo[curInfo].gameOrigin); + return textures[originInfo[curInfo].num]->mMat; + } + else + { + RB3E_DEBUG("Material for game origin '%s' is NULL, creation seemingly failed", originInfo[curInfo].gameOrigin); + } + } + } + } else { + RB3E_DEBUG("Node record or its metadata is NULL, skipping", NULL); + } + } + } } } - strcat(newLabel, originLabel); - strcat(newLabel, label->string); - BandLabelSetDisplayText(label, newLabel, 1); - return; } - SetSongAndArtistName(label, sortNode); - return; + + return MusicLibraryMat(thisMusicLibrary, data, idx, listSlot); } -void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) +int numGameOrigins; +GameOriginInfo originInfo[100] = {0}; + +void AddGameOriginToIconList(char *gameOrigin) { - char newLabel[1024] = {0}; - char *originLabel = "0 "; // default + if(gameOrigin != NULL && strcmp(gameOrigin, "") != 0) + { int i = 0; - RB3E_DEBUG("SetSongNameFromNode: %s", label->string); + // check that we havent already added 100 game origins + if (numGameOrigins >= 100) + { + RB3E_DEBUG("Too many game origins with icons, not adding '%s'", gameOrigin); + return; + } - if (config.GameOriginIcons == 1 && strlen(label->string) < 1000) + // make sure the game origin isn't already in the array + for (i = 0; i < numGameOrigins; i++) { - SetSongNameFromNode(label, sortNode); - for (i = 0; i < numOriginToIcon; i++) + if (strcmp(originInfo[i].gameOrigin, gameOrigin) == 0) { - if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) - { - originLabel = originToIcon[i][1]; - break; - } + return; } - strcat(newLabel, originLabel); - strcat(newLabel, label->string); - BandLabelSetDisplayText(label, newLabel, 1); - return; } - SetSongNameFromNode(label, sortNode); - return; + + originInfo[numGameOrigins].gameOrigin = gameOrigin; + originInfo[numGameOrigins].num = numGameOrigins; + CreateMaterial(&originInfo[numGameOrigins]); + numGameOrigins++; + RB3E_DEBUG("Adding game origin '%s' to icon list, total is now %i", gameOrigin, numGameOrigins); + } + else + { + RB3E_DEBUG("Game origin is NULL or empty, not adding to icon list", NULL); + } +} + +// this will be called any time a song is loaded from DTA (on disc or when loading into the cache) +SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc) +{ + thisSongMetadata = SongMetadataConstructor(thisSongMetadata, data, backupData, isOnDisc); + + // make sure the game origin isn't null + if (thisSongMetadata->mGameOrigin.sym != 0) + { + AddGameOriginToIconList(thisSongMetadata->mGameOrigin.sym); + return thisSongMetadata; + } + + return thisSongMetadata; +} + +// this will be called when a song is loaded from cache +char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream) +{ + char ret = SongMetadataLoad(thisSongMetadata, stream); + + // make sure the game origin isn't null + if (thisSongMetadata->mGameOrigin.sym != 0) + { + AddGameOriginToIconList(thisSongMetadata->mGameOrigin.sym); + return ret; + } + + return ret; } \ No newline at end of file From cfd7698d470a0366fcb3467da60d8766203d6f17 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 17:58:49 -0500 Subject: [PATCH 075/123] Update SetlistHooks.c --- source/SetlistHooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 86d200c..78babd3 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -22,8 +22,8 @@ #include "rb3/Rnd/DynamicTex.h" #include "rb3/Rnd/RndMat.h" -void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) DynamicTex *textures[100] = {0}; +void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) void CreateMaterial(GameOriginInfo *info) { From 96d205d91350cc5b1444c489118bd19d15a23c11 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:00:30 -0500 Subject: [PATCH 076/123] Update SetlistHooks.c --- source/SetlistHooks.c | 53 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 78babd3..e46c921 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -23,7 +23,60 @@ #include "rb3/Rnd/RndMat.h" DynamicTex *textures[100] = {0}; + void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) +{ + char newLabel[1024] = {0}; + char *originLabel = "0 "; // default + int i = 0; + + if (config.GameOriginIcons == 1 && strlen(label->string) < 1000) + { + SetSongAndArtistName(label, sortNode); + for (i = 0; i < numOriginToIcon; i++) + { + if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + { + originLabel = originToIcon[i][1]; + break; + } + } + strcat(newLabel, originLabel); + strcat(newLabel, label->string); + BandLabelSetDisplayText(label, newLabel, 1); + return; + } + SetSongAndArtistName(label, sortNode); + return; +} + +void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) +{ + char newLabel[1024] = {0}; + char *originLabel = "0 "; // default + int i = 0; + + RB3E_DEBUG("SetSongNameFromNode: %s", label->string); + + if (config.GameOriginIcons == 1 && strlen(label->string) < 1000) + { + SetSongNameFromNode(label, sortNode); + for (i = 0; i < numOriginToIcon; i++) + { + if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + { + originLabel = originToIcon[i][1]; + break; + } + } + strcat(newLabel, originLabel); + strcat(newLabel, label->string); + BandLabelSetDisplayText(label, newLabel, 1); + return; + } + SetSongNameFromNode(label, sortNode); + return; +} void CreateMaterial(GameOriginInfo *info) { From d36ba40424cf3b718a3f0f14082d1479eceac5f5 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:03:52 -0500 Subject: [PATCH 077/123] sure --- include/rb3/SortNode.h | 20 +++++++++++++++++++- source/SetlistHooks.c | 1 + 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/include/rb3/SortNode.h b/include/rb3/SortNode.h index 1b14d71..02cc0c4 100644 --- a/include/rb3/SortNode.h +++ b/include/rb3/SortNode.h @@ -17,7 +17,25 @@ typedef enum _SongNodeType kNodeStoreSong = 7 } SongNodeType; -typedef struct _SortNode SortNode; +typedef struct _Unknown2 +{ +#ifdef RB3E_WII + char unknown[0xfc]; +#else + char unknown[0x108]; +#endif + SongMetadata *metaData; +} Unknown2; + +typedef struct _SortNode +{ +#ifdef RB3E_WII + char something[0x34]; +#else + char something[0x40]; +#endif + Unknown2 *somethingElse; +} SortNode; typedef int (*ReturnsZero_t)(); typedef void (*OnlyReturns_t)(); diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index e46c921..e5fa83b 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -23,6 +23,7 @@ #include "rb3/Rnd/RndMat.h" DynamicTex *textures[100] = {0}; +static int numOriginToIcon = sizeof(originToIcon) / sizeof(originToIcon[0]); void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) { From 0e7a2969d11f9ca0a11d4d9be4d2334648105881 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:05:13 -0500 Subject: [PATCH 078/123] Update SortNode.h --- include/rb3/SortNode.h | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/include/rb3/SortNode.h b/include/rb3/SortNode.h index 02cc0c4..2b77af3 100644 --- a/include/rb3/SortNode.h +++ b/include/rb3/SortNode.h @@ -27,16 +27,6 @@ typedef struct _Unknown2 SongMetadata *metaData; } Unknown2; -typedef struct _SortNode -{ -#ifdef RB3E_WII - char something[0x34]; -#else - char something[0x40]; -#endif - Unknown2 *somethingElse; -} SortNode; - typedef int (*ReturnsZero_t)(); typedef void (*OnlyReturns_t)(); typedef SongNodeType (*GetNodeType_t)(); From d272173eea59f2dbdeb47609dfb90d6b82faab1c Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:07:20 -0500 Subject: [PATCH 079/123] Update SortNode.h --- include/rb3/SortNode.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/include/rb3/SortNode.h b/include/rb3/SortNode.h index 2b77af3..4c6e9d2 100644 --- a/include/rb3/SortNode.h +++ b/include/rb3/SortNode.h @@ -27,6 +27,17 @@ typedef struct _Unknown2 SongMetadata *metaData; } Unknown2; +struct _SortNode +{ + ShortcutNodeVtable *vtable; +#ifdef RB3E_WII + char something[0x30]; +#else + char something[0x3c]; +#endif + SongRecord *record; +}; + typedef int (*ReturnsZero_t)(); typedef void (*OnlyReturns_t)(); typedef SongNodeType (*GetNodeType_t)(); @@ -62,15 +73,4 @@ typedef struct _SongRecord SongMetadata *metaData; } SongRecord; -struct _SortNode -{ - ShortcutNodeVtable *vtable; -#ifdef RB3E_WII - char something[0x30]; -#else - char something[0x3c]; -#endif - SongRecord *record; -}; - #endif // _SORTNODE_H \ No newline at end of file From 7bd44ee0573beb6db8ce84fc81d4a4544084f5fd Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:09:40 -0500 Subject: [PATCH 080/123] Update SortNode.h --- include/rb3/SortNode.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/rb3/SortNode.h b/include/rb3/SortNode.h index 4c6e9d2..73ed37f 100644 --- a/include/rb3/SortNode.h +++ b/include/rb3/SortNode.h @@ -5,6 +5,10 @@ #include "Symbol.h" #include "SongMetadata.h" +typedef struct _SortNode SortNode; +typedef struct _ShortcutNodeVtable ShortcutNodeVtable; +typedef struct _SongRecord SongRecord; + typedef enum _SongNodeType { kNodeNone = 0, From a43007cf5804ee5c9d2072481f2740563d66ab45 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:14:21 -0500 Subject: [PATCH 081/123] Update SetlistHooks.c --- source/SetlistHooks.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index e5fa83b..4826623 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -23,7 +23,7 @@ #include "rb3/Rnd/RndMat.h" DynamicTex *textures[100] = {0}; -static int numOriginToIcon = sizeof(originToIcon) / sizeof(originToIcon[0]); +static int numOriginToIcon = sizeof(numOriginToIcon) / sizeof(originToIcon[0]); void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) { @@ -148,12 +148,12 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U // do a basic null check here, sometimes it can be null if (node != NULL && node->record != NULL && node->record->metaData != NULL && - node->record->metaData->mGameOrigin.sym != NULL) + node->record->metaData->gameOrigin.sym != NULL) { // this shit fucking sucks lol for (curInfo = 0; curInfo < numGameOrigins; curInfo++) { - if (strcmp(node->record->metaData->mGameOrigin.sym, originInfo[curInfo].gameOrigin) == 0) + if (strcmp(node->record->metaData->gameOrigin.sym, originInfo[curInfo].gameOrigin) == 0) { if (textures[originInfo[curInfo].num] != NULL && textures[originInfo[curInfo].num]->mMat != NULL) { @@ -221,9 +221,9 @@ SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataAr thisSongMetadata = SongMetadataConstructor(thisSongMetadata, data, backupData, isOnDisc); // make sure the game origin isn't null - if (thisSongMetadata->mGameOrigin.sym != 0) + if (thisSongMetadata->gameOrigin.sym != 0) { - AddGameOriginToIconList(thisSongMetadata->mGameOrigin.sym); + AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); return thisSongMetadata; } @@ -236,9 +236,9 @@ char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream) char ret = SongMetadataLoad(thisSongMetadata, stream); // make sure the game origin isn't null - if (thisSongMetadata->mGameOrigin.sym != 0) + if (thisSongMetadata->gameOrigin.sym != 0) { - AddGameOriginToIconList(thisSongMetadata->mGameOrigin.sym); + AddGameOriginToIconList(thisSongMetadata->gameOrigin.sym); return ret; } From b28ae14225c5731e6cf246139a3db94f169c1bad Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:25:22 -0500 Subject: [PATCH 082/123] update calls --- include/rb3/MusicLibrary.h | 2 +- source/SetlistHooks.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/rb3/MusicLibrary.h b/include/rb3/MusicLibrary.h index 4f50f47..107e603 100644 --- a/include/rb3/MusicLibrary.h +++ b/include/rb3/MusicLibrary.h @@ -16,6 +16,6 @@ typedef struct _MusicLibrary // Jumps to a given entry in the music library void MusicLibrarySelect(int theMusicLibrary, Symbol entryName, int sortType, int unk_r6); int *MusicLibraryConstructor(int *thisMusicLibrary, int *songPreview); -int *MusicLibraryConstructorHook(MusicLibrary *thisMusicLibrary, int *songPreview); +int *MusicLibraryConstructorHook(int *thisMusicLibrary, int *songPreview); #endif // _MUSICLIBRARY_H_ diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 4826623..61186ae 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -36,7 +36,7 @@ void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) SetSongAndArtistName(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + if (strcmp(sortNode->something->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; @@ -64,7 +64,7 @@ void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) SetSongNameFromNode(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->somethingElse->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + if (strcmp(sortNode->something->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; @@ -137,7 +137,7 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U ret = SongSortMgrGetSort(*(SongSortMgr **)PORT_THESONGSORTMGR, thisMusicLibrary->mSortType); if (ret != NULL) { - node = NodeSortGetNode(ret, idx); + node = NodeSortGetNode(int, idx); if (node != NULL) { nodeType = node->vtable->getNodeType(); @@ -175,7 +175,7 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U } } - return MusicLibraryMat(thisMusicLibrary, data, idx, listSlot); + return MusicLibraryMat(thisMusicLibrary, data, idx, UIListSlot); } int numGameOrigins; From d9c6113ff8e0edc928aca793ed1d692e9a4ae5da Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:44:50 -0500 Subject: [PATCH 083/123] Update SetlistHooks.c --- source/SetlistHooks.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 61186ae..b89ec43 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -23,7 +23,7 @@ #include "rb3/Rnd/RndMat.h" DynamicTex *textures[100] = {0}; -static int numOriginToIcon = sizeof(numOriginToIcon) / sizeof(originToIcon[0]); +static int numOriginToIcon = sizeof(numOriginToIcon) / sizeof(numOriginToIcon[0]); void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) { @@ -36,7 +36,7 @@ void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) SetSongAndArtistName(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->something->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + if (strcmp(sortNode->ShortcutNodeVtable->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; @@ -64,7 +64,7 @@ void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) SetSongNameFromNode(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->something->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + if (strcmp(sortNode->ShortcutNodeVtable->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; @@ -118,12 +118,12 @@ void CreateMaterial(GameOriginInfo *info) } -int *MusicLibraryConstructorHook(MusicLibrary *thisMusicLibrary, int *songPreview) +int *MusicLibraryConstructorHook(int *thisMusicLibrary, int *songPreview) { return MusicLibraryConstructor(thisMusicLibrary, songPreview); } -RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) +RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) { if (listSlot != NULL && thisMusicLibrary != NULL) { From b5281e2a150c66c1cff7a8f359ad83a704cd4e27 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:47:36 -0500 Subject: [PATCH 084/123] Update SetlistHooks.c --- source/SetlistHooks.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index b89ec43..4409773 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -23,7 +23,26 @@ #include "rb3/Rnd/RndMat.h" DynamicTex *textures[100] = {0}; -static int numOriginToIcon = sizeof(numOriginToIcon) / sizeof(numOriginToIcon[0]); +static char *originToIcon[][2] = { + {"rb1", "Y "}, + {"rb2", "2 "}, + {"rb3", "B "}, + {"rb4", "1 "}, + {"rb_blitz", "X "}, + {"ugc", "U "}, + {"ugc_c3", "y "}, + {"ugc_plus", "U "}, + {"ugc1", "U "}, + {"ugc2", "U "}, + {"lego", "A "}, + {"greenday", "0 "}, + {"beatles", "b "}, + {"gh1", "R "}, + {"gh2", "S "}, + {"gh3", "s "}, + {"onyxite", "G "}, +}; +static int numOriginToIcon = sizeof(originToIcon) / sizeof(originToIcon[0]); void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) { @@ -36,7 +55,7 @@ void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) SetSongAndArtistName(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->ShortcutNodeVtable->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + if (strcmp(sortNode->vtable->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; @@ -64,7 +83,7 @@ void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) SetSongNameFromNode(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->ShortcutNodeVtable->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + if (strcmp(sortNode->vtable->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; From bf921506579d90809aaa373c08e65e5668c34354 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:49:26 -0500 Subject: [PATCH 085/123] Update SetlistHooks.c --- source/SetlistHooks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 4409773..5003045 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -55,7 +55,7 @@ void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) SetSongAndArtistName(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->vtable->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + if (strcmp(_Unknown2->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; @@ -83,7 +83,7 @@ void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) SetSongNameFromNode(label, sortNode); for (i = 0; i < numOriginToIcon; i++) { - if (strcmp(sortNode->vtable->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) + if (strcmp(_Unknown2->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) { originLabel = originToIcon[i][1]; break; From 5f72dfae4890563bed4f444504f6b9ea29726857 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:51:45 -0500 Subject: [PATCH 086/123] oh remove this? --- include/SetlistHooks.h | 2 +- source/SetlistHooks.c | 78 ++---------------------------------------- 2 files changed, 3 insertions(+), 77 deletions(-) diff --git a/include/SetlistHooks.h b/include/SetlistHooks.h index a29606e..1f4449d 100644 --- a/include/SetlistHooks.h +++ b/include/SetlistHooks.h @@ -12,4 +12,4 @@ void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode); void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode); RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int unk, int unk2, int *listSlot); SongMetadata *SongMetadataConstructorHook(SongMetadata *thisSongMetadata, DataArray *data, DataArray *backupData, char isOnDisc); -char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream); +char SongMetadataLoadHook(SongMetadata *thisSongMetadata, BinStream *stream); \ No newline at end of file diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 5003045..91aca89 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -23,80 +23,6 @@ #include "rb3/Rnd/RndMat.h" DynamicTex *textures[100] = {0}; -static char *originToIcon[][2] = { - {"rb1", "Y "}, - {"rb2", "2 "}, - {"rb3", "B "}, - {"rb4", "1 "}, - {"rb_blitz", "X "}, - {"ugc", "U "}, - {"ugc_c3", "y "}, - {"ugc_plus", "U "}, - {"ugc1", "U "}, - {"ugc2", "U "}, - {"lego", "A "}, - {"greenday", "0 "}, - {"beatles", "b "}, - {"gh1", "R "}, - {"gh2", "S "}, - {"gh3", "s "}, - {"onyxite", "G "}, -}; -static int numOriginToIcon = sizeof(originToIcon) / sizeof(originToIcon[0]); - -void SetSongAndArtistNameHook(BandLabel *label, SortNode *sortNode) -{ - char newLabel[1024] = {0}; - char *originLabel = "0 "; // default - int i = 0; - - if (config.GameOriginIcons == 1 && strlen(label->string) < 1000) - { - SetSongAndArtistName(label, sortNode); - for (i = 0; i < numOriginToIcon; i++) - { - if (strcmp(_Unknown2->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) - { - originLabel = originToIcon[i][1]; - break; - } - } - strcat(newLabel, originLabel); - strcat(newLabel, label->string); - BandLabelSetDisplayText(label, newLabel, 1); - return; - } - SetSongAndArtistName(label, sortNode); - return; -} - -void SetSongNameFromNodeHook(BandLabel *label, SortNode *sortNode) -{ - char newLabel[1024] = {0}; - char *originLabel = "0 "; // default - int i = 0; - - RB3E_DEBUG("SetSongNameFromNode: %s", label->string); - - if (config.GameOriginIcons == 1 && strlen(label->string) < 1000) - { - SetSongNameFromNode(label, sortNode); - for (i = 0; i < numOriginToIcon; i++) - { - if (strcmp(_Unknown2->metaData->gameOrigin.sym, originToIcon[i][0]) == 0) - { - originLabel = originToIcon[i][1]; - break; - } - } - strcat(newLabel, originLabel); - strcat(newLabel, label->string); - BandLabelSetDisplayText(label, newLabel, 1); - return; - } - SetSongNameFromNode(label, sortNode); - return; -} void CreateMaterial(GameOriginInfo *info) { @@ -156,7 +82,7 @@ RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, UIListSlot ret = SongSortMgrGetSort(*(SongSortMgr **)PORT_THESONGSORTMGR, thisMusicLibrary->mSortType); if (ret != NULL) { - node = NodeSortGetNode(int, idx); + node = NodeSortGetNode(ret, idx); if (node != NULL) { nodeType = node->vtable->getNodeType(); @@ -194,7 +120,7 @@ RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, UIListSlot } } - return MusicLibraryMat(thisMusicLibrary, data, idx, UIListSlot); + return MusicLibraryMat(thisMusicLibrary, data, idx, listSlot); } int numGameOrigins; From dc8ff87ea7f4e94992cca45c3911ec58b84a3007 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:54:54 -0500 Subject: [PATCH 087/123] Update NodeSort.h --- include/rb3/NodeSort.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rb3/NodeSort.h b/include/rb3/NodeSort.h index c7c3b64..4cdf5b4 100644 --- a/include/rb3/NodeSort.h +++ b/include/rb3/NodeSort.h @@ -17,6 +17,6 @@ typedef struct _NodeSort Symbol mSortName; } NodeSort; -SortNode *NodeSortGetNode(NodeSort *thisNodeSort, int idx); +SortNode *NodeSortGetNode(int *thisNodeSort, int idx); #endif // _NODESORT_H \ No newline at end of file From 1cac1b68e1c84a32e74441464d2e43b3645a8941 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:56:22 -0500 Subject: [PATCH 088/123] Update SetlistHooks.c --- source/SetlistHooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 91aca89..d726301 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -68,7 +68,7 @@ int *MusicLibraryConstructorHook(int *thisMusicLibrary, int *songPreview) return MusicLibraryConstructor(thisMusicLibrary, songPreview); } -RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) +RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, int *listSlot) { if (listSlot != NULL && thisMusicLibrary != NULL) { From 7956c8a7d420559b9d522fa5d309ca26e8676c15 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:57:46 -0500 Subject: [PATCH 089/123] list slot --- include/rb3/BandLabel.h | 2 +- source/SetlistHooks.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rb3/BandLabel.h b/include/rb3/BandLabel.h index 5409ce1..eaf0ab6 100644 --- a/include/rb3/BandLabel.h +++ b/include/rb3/BandLabel.h @@ -17,6 +17,6 @@ typedef struct _BandLabel extern void SetSongAndArtistName(BandLabel *label, SortNode *unk); extern void SetSongNameFromNode(BandLabel *label, SortNode *unk); extern void BandLabelSetDisplayText(BandLabel *label, char *text, char clear_token); -extern int MusicLibraryMat(void *thisMusicLibrary, int data, int unk2, int *listSlot); // returns the material for UIListMeshes in the music library +extern int MusicLibraryMat(void *thisMusicLibrary, int data, int unk2, UIListSlot *listSlot); // returns the material for UIListMeshes in the music library #endif // _BANDLABEL_H \ No newline at end of file diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index d726301..91aca89 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -68,7 +68,7 @@ int *MusicLibraryConstructorHook(int *thisMusicLibrary, int *songPreview) return MusicLibraryConstructor(thisMusicLibrary, songPreview); } -RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, int *listSlot) +RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) { if (listSlot != NULL && thisMusicLibrary != NULL) { From e956294937cd2a0269aa02dc5f91d90b487eca78 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 18:59:00 -0500 Subject: [PATCH 090/123] list int --- include/rb3/BandLabel.h | 2 +- source/SetlistHooks.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rb3/BandLabel.h b/include/rb3/BandLabel.h index eaf0ab6..5409ce1 100644 --- a/include/rb3/BandLabel.h +++ b/include/rb3/BandLabel.h @@ -17,6 +17,6 @@ typedef struct _BandLabel extern void SetSongAndArtistName(BandLabel *label, SortNode *unk); extern void SetSongNameFromNode(BandLabel *label, SortNode *unk); extern void BandLabelSetDisplayText(BandLabel *label, char *text, char clear_token); -extern int MusicLibraryMat(void *thisMusicLibrary, int data, int unk2, UIListSlot *listSlot); // returns the material for UIListMeshes in the music library +extern int MusicLibraryMat(void *thisMusicLibrary, int data, int unk2, int *listSlot); // returns the material for UIListMeshes in the music library #endif // _BANDLABEL_H \ No newline at end of file diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 91aca89..d726301 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -68,7 +68,7 @@ int *MusicLibraryConstructorHook(int *thisMusicLibrary, int *songPreview) return MusicLibraryConstructor(thisMusicLibrary, songPreview); } -RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) +RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, int *listSlot) { if (listSlot != NULL && thisMusicLibrary != NULL) { From 3ee115f58596f55b096c91c35dd318ea7f33db41 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:02:56 -0500 Subject: [PATCH 091/123] Update SetlistHooks.c --- source/SetlistHooks.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index d726301..b3627fd 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -5,6 +5,7 @@ #include #include +#include #include #include "config.h" #include "ports.h" @@ -33,7 +34,7 @@ void CreateMaterial(GameOriginInfo *info) // TODO: we should identify and hook some function that runs when you leave the song select screen to call DynamicTex's destructor // it will free the dynamic tex itself, the material it created, and the texture too, so it nicely wraps it all up for you // this way there is not a chunk of memory permanently dedicated to game origin icons (even if it is not a large amount) - tex = MemAlloc(0x20, 0); + tex = (DynamicTex*)MemAlloc(0x20, 0); // build the ark path (so dont include /gen/ or the _platform extension etc.) RB3E_DEBUG("Creating dynamic tex for game origin '%s'", info->gameOrigin); @@ -68,7 +69,7 @@ int *MusicLibraryConstructorHook(int *thisMusicLibrary, int *songPreview) return MusicLibraryConstructor(thisMusicLibrary, songPreview); } -RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, int *listSlot) +RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) { if (listSlot != NULL && thisMusicLibrary != NULL) { @@ -120,7 +121,7 @@ RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, int *listS } } - return MusicLibraryMat(thisMusicLibrary, data, idx, listSlot); + return (RndMat*)(intptr_t)MusicLibraryMat((void*)thisMusicLibrary, data, idx, (int*)listSlot); } int numGameOrigins; From e0ed791f53055e82fd5767b53e79ca882edd866a Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:04:38 -0500 Subject: [PATCH 092/123] Update SetlistHooks.c --- source/SetlistHooks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index b3627fd..fb4b66a 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -34,7 +34,7 @@ void CreateMaterial(GameOriginInfo *info) // TODO: we should identify and hook some function that runs when you leave the song select screen to call DynamicTex's destructor // it will free the dynamic tex itself, the material it created, and the texture too, so it nicely wraps it all up for you // this way there is not a chunk of memory permanently dedicated to game origin icons (even if it is not a large amount) - tex = (DynamicTex*)MemAlloc(0x20, 0); + tex = MemAlloc(0x20, 0); // build the ark path (so dont include /gen/ or the _platform extension etc.) RB3E_DEBUG("Creating dynamic tex for game origin '%s'", info->gameOrigin); @@ -69,7 +69,7 @@ int *MusicLibraryConstructorHook(int *thisMusicLibrary, int *songPreview) return MusicLibraryConstructor(thisMusicLibrary, songPreview); } -RndMat *MusicLibraryMatHook(int *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) +RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, UIListSlot *listSlot) { if (listSlot != NULL && thisMusicLibrary != NULL) { From ebc8cb144b64772ab4b57e397dc1337db20f6626 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:06:17 -0500 Subject: [PATCH 093/123] Update SetlistHooks.c --- source/SetlistHooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index fb4b66a..b986b37 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -75,7 +75,7 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U { if (strcmp(listSlot->mMatchName.buf, "game_origin_icon") == 0) { - int *ret = 0; + NodeSort *ret = 0; SortNode *node = 0; SongNodeType nodeType = kNodeNone; int curInfo = 0; From b496bbe057271b412dfe60cc789c04c20ee30c27 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:08:23 -0500 Subject: [PATCH 094/123] Update SetlistHooks.c --- source/SetlistHooks.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index b986b37..898f4ee 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -75,7 +75,7 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U { if (strcmp(listSlot->mMatchName.buf, "game_origin_icon") == 0) { - NodeSort *ret = 0; + int *ret = 0; SortNode *node = 0; SongNodeType nodeType = kNodeNone; int curInfo = 0; @@ -83,7 +83,7 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U ret = SongSortMgrGetSort(*(SongSortMgr **)PORT_THESONGSORTMGR, thisMusicLibrary->mSortType); if (ret != NULL) { - node = NodeSortGetNode(ret, idx); + node = NodeSortGetNode(int, idx); if (node != NULL) { nodeType = node->vtable->getNodeType(); From 47009c0c673efabc3b4108336620c4a55351505f Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:09:19 -0500 Subject: [PATCH 095/123] Update SetlistHooks.c --- source/SetlistHooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 898f4ee..fb4b66a 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -83,7 +83,7 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U ret = SongSortMgrGetSort(*(SongSortMgr **)PORT_THESONGSORTMGR, thisMusicLibrary->mSortType); if (ret != NULL) { - node = NodeSortGetNode(int, idx); + node = NodeSortGetNode(ret, idx); if (node != NULL) { nodeType = node->vtable->getNodeType(); From e4dca183cd56baeb7b5f5c5b0eff799a0ab433b7 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:17:06 -0500 Subject: [PATCH 096/123] include mem --- source/SetlistHooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index fb4b66a..b38b13e 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -5,7 +5,6 @@ #include #include -#include #include #include "config.h" #include "ports.h" @@ -13,6 +12,7 @@ #include "rb3/BandLabel.h" #include "rb3/File.h" #include "rb3/FilePath.h" +#include "rb3/Mem.h" #include "rb3/MusicLibrary.h" #include "rb3/NodeSort.h" #include "rb3/UI/UIListSlot.h" From 9856428709febeef58d5cb5cf4e715bc03e7da6b Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:18:06 -0500 Subject: [PATCH 097/123] Update SetlistHooks.c --- source/SetlistHooks.c | 1 + 1 file changed, 1 insertion(+) diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index b38b13e..3ecdb08 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -5,6 +5,7 @@ #include #include +#include #include #include "config.h" #include "ports.h" From af66b02b42dfa817055ff8b2a20eb7d4c85729c0 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:21:29 -0500 Subject: [PATCH 098/123] NodeSort --- include/rb3/NodeSort.h | 2 +- source/SetlistHooks.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rb3/NodeSort.h b/include/rb3/NodeSort.h index 4cdf5b4..c7c3b64 100644 --- a/include/rb3/NodeSort.h +++ b/include/rb3/NodeSort.h @@ -17,6 +17,6 @@ typedef struct _NodeSort Symbol mSortName; } NodeSort; -SortNode *NodeSortGetNode(int *thisNodeSort, int idx); +SortNode *NodeSortGetNode(NodeSort *thisNodeSort, int idx); #endif // _NODESORT_H \ No newline at end of file diff --git a/source/SetlistHooks.c b/source/SetlistHooks.c index 3ecdb08..a25ace8 100644 --- a/source/SetlistHooks.c +++ b/source/SetlistHooks.c @@ -76,7 +76,7 @@ RndMat *MusicLibraryMatHook(MusicLibrary *thisMusicLibrary, int data, int idx, U { if (strcmp(listSlot->mMatchName.buf, "game_origin_icon") == 0) { - int *ret = 0; + NodeSort *ret = 0; SortNode *node = 0; SongNodeType nodeType = kNodeNone; int curInfo = 0; From 67a9d82cd80f94353d71acb7c1dca8bbdc1ea5a2 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:27:40 -0500 Subject: [PATCH 099/123] Update SongParserHooks.c --- source/SongParserHooks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/SongParserHooks.c b/source/SongParserHooks.c index 2069ebb..fbc9d59 100644 --- a/source/SongParserHooks.c +++ b/source/SongParserHooks.c @@ -21,7 +21,7 @@ int SongParserPitchToSlotHook(SongParser *thisSongParser, int pitch, int *diffic if (doubleBassModifier->enabled) { // make sure the current track type is drums - if (thisSongParser->mTrackType == DRUMS) + if (thisSongParser->mTrackType == kTrackDrum) { // check for MIDI note pitch 95 (which is the 2x bass pedal note) if (pitch == 95) From 39c41c9d8454a182caac0ef088dcdce71de39c0e Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:35:35 -0500 Subject: [PATCH 100/123] add vertex read hook --- include/MiloSceneHooks.h | 5 ++++- source/MiloSceneHooks.c | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/include/MiloSceneHooks.h b/include/MiloSceneHooks.h index 67b889e..629698e 100644 --- a/include/MiloSceneHooks.h +++ b/include/MiloSceneHooks.h @@ -6,6 +6,9 @@ #include "rb3/BinStream.h" #include "rb3/DirLoader.h" #include "rb3/Object.h" +#include "rb3/Rnd/Transform.h" + // void DirLoaderOpenFileHook(DirLoader *thisDirLoader); -void LoadObj(Object *object, BinStream *stream); \ No newline at end of file +void LoadObj(Object *object, BinStream *stream); +void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3); \ No newline at end of file diff --git a/source/MiloSceneHooks.c b/source/MiloSceneHooks.c index 95887c5..f7928dd 100644 --- a/source/MiloSceneHooks.c +++ b/source/MiloSceneHooks.c @@ -67,4 +67,29 @@ void LoadObj(Object *object, BinStream *stream) object_pre_load: object->table->preLoad(object, stream); return; +} + + +// This hook allows for GH2-360/RB1/RB2 meshes to load correctly in RB3 +void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3) +{ +#ifdef RB3E_XBOX + // the gRev of the current mesh + int gRev = *(int *)PORT_MESH_GREV; + char empty[4] = {0}; + + BinstreamReadEndian(thisBinStream, (void *)&vec3->x, 4); + BinstreamReadEndian(thisBinStream, (void *)&vec3->y, 4); + BinstreamReadEndian(thisBinStream, (void *)&vec3->z, 4); + + // if the current RndMesh being read is the GH2-360/RB1/RB2 format, we need to skip over the W component + // RB3 (for whatever reason) expects vertices in these versions of meshes to not include W + // even though they *do* in actual GH2-360/RB1/RB2 meshes + if (gRev == 34) + { + BinstreamReadEndian(thisBinStream, (void *)&empty, 4); + } + + return; +#endif } \ No newline at end of file From d2ea91f7f4611bc842d0e06254ac50966003f4e7 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sat, 24 May 2025 19:39:03 -0500 Subject: [PATCH 101/123] Update rb3enhanced.c --- source/rb3enhanced.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 22680ce..536c5ca 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -406,7 +406,6 @@ void ApplyHooks() HookFunction(PORT_LOCALIZE, &Localize, &LocalizeHook); HookFunction(PORT_WILLBENOSTRUM, &WillBeNoStrum, &WillBeNoStrumHook); HookFunction(PORT_ADDGAMEGEM, &AddGameGem, &AddGameGemHook); - HookFunction(PORT_SETSONGANDARTISTNAME, &SetSongAndArtistName, SetSongAndArtistNameHook); HookFunction(PORT_SETVENUE, &SetVenue, &SetVenueHook); HookFunction(PORT_MODIFIERMGR_CT, &ModifierManagerConstructor, &ModifierManagerConstructorHook); HookFunction(PORT_NEWFILE, &NewFile, &NewFileHook); @@ -439,7 +438,6 @@ void ApplyHooks() POKE_B(PORT_FILEISLOCAL, &FileIsLocalHook); #elif RB3E_XBOX // 360 exclusive hooks HookFunction(PORT_STAGEKIT_SET_STATE, &StagekitSetState, &StagekitSetStateHook); - HookFunction(PORT_SETSONGNAMEFROMNODE, &SetSongNameFromNode, &SetSongNameFromNodeHook); // TODO: port these to Wii HookFunction(PORT_DATANODEGETOBJ, &DataNodeGetObj, &DataNodeGetObjHook); POKE_B(PORT_GETSONGID, &GetSongIDHook); From 89fa860cab6c6ab562540b25f4d58143d09b0400 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 25 May 2025 15:25:49 -0500 Subject: [PATCH 102/123] correct some issues i noticed I put the wrong version of songhooks.c --- source/GemHooks.c | 2 -- source/LocaleHooks.c | 3 +-- source/MiloSceneHooks.c | 1 - source/SongHooks.c | 20 +++++++++----------- source/rb3enhanced.c | 1 + 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/source/GemHooks.c b/source/GemHooks.c index 38a50ec..f8f08ba 100644 --- a/source/GemHooks.c +++ b/source/GemHooks.c @@ -1,11 +1,9 @@ #include "GlobalSymbols.h" #include "ports.h" -#include "rb3/Mem.h" #include "rb3/ModifierManager.h" #include "rb3/GameGem.h" #include "rb3/Random.h" #include "rb3/Symbol.h" -#include "rb3/Vector.h" int WillBeNoStrumHook(GameGemList *thisGameGemList, int *gem) { diff --git a/source/LocaleHooks.c b/source/LocaleHooks.c index f258f1e..8b3c3ca 100644 --- a/source/LocaleHooks.c +++ b/source/LocaleHooks.c @@ -42,8 +42,7 @@ static char *newLocales[][2] = { {"mod_mirror_mode", "Mirror Mode"}, {"mod_color_shuffle", "Gem Color Shuffle"}, {"mod_gem_shuffle", "Note Shuffle"}, - {"mod_double_bass", "Double Bass Pedal"} -}; + {"mod_double_bass", "Double Bass Pedal"}}; static int numNewLocales = sizeof(newLocales) / sizeof(newLocales[0]); char RB3E_ActiveLocale[RB3E_LANG_LEN + 1] = {0}; diff --git a/source/MiloSceneHooks.c b/source/MiloSceneHooks.c index f7928dd..518f686 100644 --- a/source/MiloSceneHooks.c +++ b/source/MiloSceneHooks.c @@ -69,7 +69,6 @@ void LoadObj(Object *object, BinStream *stream) return; } - // This hook allows for GH2-360/RB1/RB2 meshes to load correctly in RB3 void VertexReadHook(BinStream *thisBinStream, Vector3 *vec3) { diff --git a/source/SongHooks.c b/source/SongHooks.c index fbf8538..28e19ae 100644 --- a/source/SongHooks.c +++ b/source/SongHooks.c @@ -7,8 +7,6 @@ #include #include "ports.h" #include "crc32.h" -#include "GameOriginInfo.h" -#include "rb3/File.h" #include "rb3/PassiveMessagesPanel.h" #include "rb3/SongMetadata.h" #include "rb3/Data.h" @@ -53,21 +51,21 @@ int MetadataSongIDHook(DataNode *song_id) int GetSongIDHook(DataArray *song, DataArray *missing_data_maybe) { Symbol song_id; - DataNode *found; - DataArray *array; + DataNode *found = NULL; + DataArray *array = NULL; SymbolConstruct(&song_id, "song_id"); if (song == NULL) return 0; - array = DataFindArray(song, song_id); + // check missing song data first if we have it + if (missing_data_maybe != NULL) + array = DataFindArray(missing_data_maybe, song_id); + // if there's nothing in missing song data, check song array if (array == NULL) - { - if (missing_data_maybe == NULL) - return 0; - else - array = DataFindArray(missing_data_maybe, song_id); - } + array = DataFindArray(song, song_id); + // no song_id? idk man if (array == NULL) return 0; + // get the value from song_id found = DataNodeEvaluate(&array->mNodes->n[1]); if (found == NULL) return 0; diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 536c5ca..afa29cc 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -432,6 +432,7 @@ void ApplyHooks() HookFunction(PORT_DATASET, &DataSet, &DataSetHook); HookFunction(PORT_DATASETELEM, &DataSetElem, &DataSetElemHook); HookFunction(PORT_DATAONELEM, &DataOnElem, &DataOnElemHook); + #ifdef RB3E_WII // wii exclusive hooks // HookFunction(PORT_USBWIIGETTYPE, &UsbWiiGetType, &UsbWiiGetTypeHook); HookFunction(PORT_WIINETINIT_DNSLOOKUP, &StartDNSLookup, &StartDNSLookupHook); From fd8813edf33cadaf41f160fe7343b14229a9a9c3 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 27 May 2025 00:20:58 +0100 Subject: [PATCH 103/123] [wii] add ModernSDMode toggle for emulated CNTSD --- include/config.h | 1 + source/config.c | 6 ++++-- source/rb3enhanced.c | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/config.h b/include/config.h index 82f121d..967e453 100644 --- a/include/config.h +++ b/include/config.h @@ -57,6 +57,7 @@ typedef struct _RB3E_Config // [Wii] char NASServer[RB3E_MAX_DOMAIN]; char LegacySDMode; + char ModernSDMode; #endif #ifdef RB3EDEBUG // [Debug] diff --git a/source/config.c b/source/config.c index 166e6b2..a6f6a64 100644 --- a/source/config.c +++ b/source/config.c @@ -27,8 +27,8 @@ void InitDefaultConfig() memset(&config, 0, sizeof(config)); strcpy(config.RawfilesDir, "rb3"); #ifdef RB3E_WII - // uncomment when GoCentral has a default instance that uses naswii auth - // strcpy(config.NASServer, "naswii.rbenhanced.rocks"); + strcpy(config.NASServer, "naswii.rbenhanced.rocks"); + config.ModernSDMode = 1; #endif config.SongSpeedMultiplier = 1.0; config.TrackSpeedMultiplier = 1.0; @@ -105,6 +105,8 @@ static int INIHandler(void *user, const char *section, const char *name, const c strncpy(config.NASServer, value, RB3E_MAX_DOMAIN); if (strcmp(name, "LegacySDMode") == 0) config.LegacySDMode = RB3E_CONFIG_BOOL(value); + if (strcmp(name, "ModernSDMode") == 0) + config.ModernSDMode = RB3E_CONFIG_BOOL(value); } #elif RB3E_XBOX if (strcmp(section, "Xbox360") == 0) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index e9b3cb3..4da721a 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -456,7 +456,7 @@ void StartupHook(void *ThisApp, int argc, char **argv) ApplyConfigurablePatches(); #ifdef RB3E_WII - if (RB3E_Mounted && config.LegacySDMode == false) + if (RB3E_Mounted && config.LegacySDMode == false && config.ModernSDMode == true) { TryToLoadPRNGKeyFromFile(); InitCNTHooks(); From b617594385e40cfa46774b7c250fc646ffe506e1 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Tue, 27 May 2025 01:10:53 +0100 Subject: [PATCH 104/123] [chore] update building, contributing and readme --- .gitignore | 11 +++++- BUILDING.md | 45 ++++++++++++++-------- CONTRIBUTING.md | 49 +++++++++++++++++------- README.md | 33 ++++++++++------ assets/wii_default_rb3.ini | 45 ++++++++++++++++++++++ assets/{rb3.ini => xbox_default_rb3.ini} | 27 ++++++++++--- make_xbox.bat | 41 -------------------- 7 files changed, 161 insertions(+), 90 deletions(-) create mode 100644 assets/wii_default_rb3.ini rename assets/{rb3.ini => xbox_default_rb3.ini} (62%) delete mode 100644 make_xbox.bat diff --git a/.gitignore b/.gitignore index 9783ef0..46bc31a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,17 @@ build_xbox/ # wii build artifacts build_wii/ *.mod +# ps3 build artifacts +build_ps3/ +*.sprx +*.prx +# gcc build artifacts *.elf *.map *.o # automatically generated files -version.h \ No newline at end of file +version.h +# OS artifacts +Thumbs.db +desktop.ini +.DS_Store diff --git a/BUILDING.md b/BUILDING.md index ae04e2c..4cbf580 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,31 +1,42 @@ # Building RB3Enhanced -RB3Enhanced is a cross-platform mod for both Xbox 360 and Wii. It's designed to be compiled on a Windows system, however both versions are able to compile on macOS and Linux. +RB3Enhanced is a cross-platform mod for both Xbox 360 and Wii. It's designed to +be compiled on a Windows system, however both versions are able to compile on +macOS and Linux. ## Prerequisites -* [devkitPPC](https://devkitpro.org/wiki/Getting_Started) (for Wii compilation) -* [BrainSlug](https://github.com/Chadderz121/brainslug-wii/blob/master/INSTALLING) (for Wii compilation) +* [devkitPro with devkitPPC](https://devkitpro.org/wiki/Getting_Started) +* [InvoxiPlayGames/BrainSlug](https://github.com/InvoxiPlayGames/brainslug-wii/blob/master/INSTALLING) * Xbox 360 SDK, with build tools installed (for Xbox compilation) -* Make \*, \*\* +* Make \* -You do not need devkitPPC to compile the mod for Xbox, and you do not need the Xbox 360 SDK to compile the mod for Wii. +The environment variable `XEDK` must be set to the install directory for the +Xbox 360 SDK, and the environment variable `DEVKITPPC` must be set to the +install directory for devkitPPC. The installers for the respective toolchains +will automatically do this for you. -The environment variable `XEDK` must be set to the install directory for the Xbox 360 SDK, and the environment variable `DEVKITPPC` must be set to the install directory for devkitPPC. The installers for the respective toolchains will automatically do this for you. - -If compiling the Xbox 360 version under macOS or Linux, set the environment variable `WINDOWS_SHIM` to the path to your Wine executable. Note that some Wine versions may not work correctly with MSVC. You will also have to ensure `XEDK` is set to a path accessible under Wine, e.g. `Z:\Users\emma\xedk\`. - -*\* There is a build script provided as `make_xbox.bat` that will compile using only the Xbox 360 SDK. This is not recommended.* - -*\*\* On Windows, the only tested configuration is with Make installed via MSYS2. Any other configuration has not been tested, and may not work.* +If compiling the Xbox 360 version under macOS or Linux, set the environment +variable `WINDOWS_SHIM` to the path to your Wine executable. Note that some Wine +versions may not work correctly with MSVC. You will also have to ensure `XEDK` +is set to a path accessible under Wine, e.g. `Z:\Users\emma\xedk\`. +*\* On Windows, the only tested configuration is with Make installed via +devkitPro's MSYS2. Any other configuration has not been tested, and may not +work.* ## Compilation -Open a Terminal (or Command Prompt) window in the same folder you have cloned the source code into. +Open a Terminal (or Command Prompt) window in the same folder you have cloned +the source code into. -* To build for Xbox 360, type `make xbox`. A DLL file will be output at `out/RB3Enhanced.dll`, that can then be loaded with RB3ELoader or a modified Rock Band 3 XEX. -* To build for Wii, type `make wii`. A MOD file will be output at `out/RB3Enhanced.mod`, that can then be loaded with RB3ELoader or the BrainSlug channel. -* Typing `make` or `make all` will compile both. Adding `-jX` (where X is your processor's thread count) will speed up compliation times. +* To build for Xbox 360, type `make xbox`. A DLL file will be output at + `out/RB3Enhanced.dll`, that can then be loaded with the RB3ELoader Dashlaunch + plugin, or the Xenia patch. +* To build for Wii, type `make wii`. A MOD file will be output at + `out/RB3Enhanced.mod`, that can then be loaded with the RB3Enhanced launcher. +* Typing `make` or `make all` will compile both. Adding `-jX` (where X is your + processor's thread count) will speed up compliation times. -Adding `DEBUG=1` to the make command will build a version of RB3E that will output more debugging information to the logs. \ No newline at end of file +Adding `DEBUG=1` to the make command will build a version of RB3E that will +output more debugging information to the logs. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7bfdd1b..7f03adb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,24 +1,45 @@ # Contributing to RB3Enhanced -We welcome all contributions to help make RB3Enhanced a better mod, by improving existing features or adding new ones. +We welcome all contributions to help make RB3Enhanced a better mod, by improving +existing features or adding new ones. ## Codebase -* Visual Studio Code config files are included in the repository, so if you are using VSCode as your IDE, make sure it is following this file. -* For cross-platform compatibility reasons, the codebase aims to be C89 / ANSI C. Variables must be defined at the start of functions rather than inline. - * Wii-specific implementations (must be guarded by #ifdef RB3E_WII) may be compliant GNU C99. - * Xbox-specific implementations (must be guarded by #ifdef RB3E_XBOX) may be slightly non-compliant, as MSVC's C89 implementation is non-standard. +* Visual Studio Code config files are included in the repository, so if you are + using VSCode as your IDE, make sure it is following this file. +* For cross-platform compatibility reasons, the codebase aims to be C89/ANSI C. + Variables must be defined at the start of functions rather than inline. + * Xbox-specific implementations (must be guarded by `#ifdef RB3E_XBOX`) may + be slightly non-compliant, as MSVC's C89 implementation is non-standard. + * Wii or PS3-specific implementations (must be guarded by `#ifdef RB3E_WII` + and/or `#ifdef RB3E_PS3`) may be compliant GNU C99. * All indentations should be 4 spaces. * Try to adhere to any existing code styling already used in the codebase. -* Function addresses should be added to `include/ports.h` in the respective platform section. -* When adding a function reference (whether as a hook or as a callee), make sure to add a unique stub in `source/_functions.c` and definitions of the original function in a header file in `include/rb3`. -* Please make sure all your contributions are able to be licensed under the GPLv2 or later. This means not using non-free or incompatibly licensed libraries or external code. +* Function addresses should be added to the applicable + `include/ports_{platform}.h` header file. +* When adding a function reference (whether as a hook or as a callee), make sure + to add a unique stub in `source/_functions.c` and definitions of the original + function in a header file in `include/rb3`. +* Please make sure all your contributions are able to be licensed under the + GPLv2 or later. This means not using non-free or incompatibly licensed + libraries or external code. + * The output of LLMs (e.g. ChatGPT) is not compatible with the GPLv2. Please + don't use these to generate code when working on code in RB3Enhanced! ## Pull requests -* Test your changes before submitting them as a pull request, to ensure that they don't crash. - * Emulators may mask fault behaviour, or return different results compared to a real console, so it's advised to use real hardware if possible. -* If applicable, try to include port addresses for both Xbox 360 and Wii in your contribution. - * If you can't do this, guard the implementation behind #ifdef RB3E_{PLATFORM}. -* It's recommended that you enable "Allow contributors to edit code" in your pull request, so quick fixes can be added by us before merging. -* Try to keep a clean Git history. \ No newline at end of file +* Test your changes before submitting them as a pull request, to ensure that + they don't crash. + * Emulators may mask fault behaviour, or return different results compared + to a real console, so it's advised to use real hardware if possible. + * Let us know what platform you've tested on. +* If possible, try to include port addresses for both Xbox 360, Wii, PS3 + and Bank 8 in your contribution. + * If you can't do this, guard the implementation behind + `#ifdef RB3E_{PLATFORM}`. +* It's recommended that you enable "Allow contributors to edit code" in your + pull request, so quick fixes can be added by us before merging. +* Try to keep a clean Git history. + * Don't make excessive commits and don't force push too often. + * If your PR hasn't been merged, try to keep it up to date with changes + in the `master` branch. diff --git a/README.md b/README.md index 77b315e..3a0748d 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,43 @@ # RB3Enhanced -RB3Enhanced is a mod for Rock Band 3 that adds new features, fixes some flaws and overall makes it a nicer experience, compatible with Xbox 360 and Wii. _(Note: RB3Enhanced requires a modified console. It will not work on a retail console.)_ +RB3Enhanced is a mod for Rock Band 3 that adds new features, fixes some flaws +and overall makes it a nicer experience, compatible with Xbox 360 and Wii. +_(Note: RB3Enhanced requires a modified console. It will not work on a retail +console.)_ -For more information, check out https://rb3e.rbenhanced.rocks or join the [RBEnhanced Discord](https://discord.gg/6rRUWXPYwb). +For more information, check out https://rb3e.rbenhanced.rocks, join the +[RBEnhanced Discord](https://discord.gg/6rRUWXPYwb) or `#rbenhanced` on +irc.libera.chat. -If you want to contribute, check BUILDING.md and CONTRIBUTING.md for more information. +If you want to contribute, check BUILDING.md and CONTRIBUTING.md for more +information. ## Downloading -* **(Recommended)** For the most tested and stable version, download from https://rb3e.rbenhanced.rocks/download.html -* For the bleeding edge latest commit, download the latest debug build from https://nightly.link/RBEnhanced/RB3Enhanced/workflows/build/master +* **(Recommended)** For the most tested and stable version, download from + https://rb3e.rbenhanced.rocks/download.html +* For the bleeding edge latest commit, download the latest debug build from + https://nightly.link/RBEnhanced/RB3Enhanced/workflows/build/master * You will need files from the stable release to use debug builds. ## Features * Raised song limit, allowing you to have up to 8000 songs in your library. * Play custom songs on the latest update of Rock Band 3. -* Access unlockables such as keys on guitar and in-game cosmetic items, without having to grind. +* Unlock additional features such as keys on guitar and in-game cosmetic items. * Additional modifiers, like black backgrounds and gem colour shuffle. * Game origin icons in the setlist to see what game your songs came from. -* Fixes the infamous infinite loading bug on modified Corona consoles and development kits. -* Easy mod support by placing raw files in the game directory without repacking the archives. +* Support and integration with [Rock Band 3 Deluxe](https://rb3dx.milohax.org) * Online multiplayer without using Xbox Live or Nintendo WFC. -* Sending data from the game to another device, for stream overlays and status indicators. +* Integration with [GoCentral](https://github.com/ihatecompvir/GoCentral) * ...and more! ## License -RB3Enhanced is licensed under the GNU General Public License version 2 (or later, at your choice) as found in the LICENSE.txt file. All source files are subject to this license unless specified otherwise in the header. +RB3Enhanced is licensed under the GNU General Public License version 2 (or +later, at your choice) as found in the LICENSE.txt file. All source files are +subject to this license unless specified otherwise in the header. -RB3Enhanced makes use of the [inih library by Ben Hoyt](https://github.com/benhoyt/inih), under the 3-clause BSD license. +RB3Enhanced makes use of the +[inih library by Ben Hoyt](https://github.com/benhoyt/inih), under the 3-clause +BSD license. diff --git a/assets/wii_default_rb3.ini b/assets/wii_default_rb3.ini new file mode 100644 index 0000000..2f92329 --- /dev/null +++ b/assets/wii_default_rb3.ini @@ -0,0 +1,45 @@ +[General] +# The multiplier to apply to the speed of the note highway. +# 1.0 = 100%, 1.2 = 120%, 1.5 = 150% +TrackSpeedMultiplier = 1.0 +# The multiplier to apply to the speed of the song audio. +# 1.0 = 100%, 1.2 = 120%, 1.5 = 150% +SongSpeedMultiplier = 1.0 +# The name of the venue to force. (e.g. "none" for black background) +# To disable this and use default venue selection, set this to "false" +ForcedVenue = false +# Whether to unlock all clothing and other unlockables by default. +UnlockClothing = false +# The folder to check on the SD card for raw files. +# The default is "rb3", uncomment this line to change it +#RawfilesDir = rb3 + +[Events] +# Enables sending events (song start, etc) to a computer or other networked device. +EnableEvents = false +# The IP address to send these events to (set to 255.255.255.255 to broadcast to the entire local network.) +BroadcastTarget = 255.255.255.255 + +[Network] +# Enables either using NATPMP or PCP protocols to open port 9103 on your Wii for +# a more reliable online multiplayer experience. Only turn this off if you don't play +# online or if it causes issues. +EnableNATPMP=true + +[GoCentral] +# Enables redirecting Rock Central servers to a custom server. +EnableGoCentral = true +# The IP address of the GoCentral server. +GoCentralAddress = gocentral-wii.rbenhanced.rocks + +[Wii] +# The device authentication server to use for GoCentral. +NASServer = naswii.rbenhanced.rocks + +# Whether to disable RB3Enhanced's SD card support at boot. +# This will allow SD card DLC songs installed from the Wii Shop and C3 CON Tools to work, however +# it will also disable rawfiles and LavaManager songs. +# Remove the # from the start of this line to enable. +#LegacySDMode = true + +# End of rb3.ini diff --git a/assets/rb3.ini b/assets/xbox_default_rb3.ini similarity index 62% rename from assets/rb3.ini rename to assets/xbox_default_rb3.ini index 519e148..24c8176 100644 --- a/assets/rb3.ini +++ b/assets/xbox_default_rb3.ini @@ -14,7 +14,7 @@ GameOriginIcons = true # Whether to unlock all clothing and other unlockables by default. UnlockClothing = false # The folder to check on the Hard Drive and USB drives for raw files. -# The default is "rb3", uncomment this line to change it +# The default is "rb3", remove the # at the start of this line to change it #RawfilesDir = rb3 [Events] @@ -29,19 +29,34 @@ BroadcastTarget = 255.255.255.255 # (This setting is ignored if using an Xbox 360 and connected to official services.) EnableGoCentral = true # The IP address of the GoCentral server. -#GoCentralAddress = 0.0.0.0 +GoCentralAddress = gocentral-xbox.rbenhanced.rocks + +[Network] +# Enables either using NATPMP, PCP or UPnP protocols to open port 9103 on your Xbox for +# a more reliable online multiplayer experience. Only turn this off if you don't play +# online or if it causes issues. +EnableNATPMP=true +EnableUPnP=true + +[HTTP] +# Enables a website accessible on http://[your xbox ip]:21070/ from the same local network +# to search for and jump to songs. Only enable this on personal home Wi-Fi networks. +EnableHTTPServer=false [Xbox360] # Enables playing online multiplayer without the need for Xbox Live. # (This setting is ignored if using an Xbox 360 and connected to official services.) EnableLiveless = true -# The IP to direct connect to. If hosting a game, set this to "127.0.0.1" +# The RB3Enhanced Liveless Rooms server to use, to join others via room codes instead of direct IP connection. +LivelessAddress = liveless-testing.ipg.pw +# The IP to direct connect to when searching for a game. If hosting a game, set this to "127.0.0.1" +# It is recommended that you use Liveless Rooms instead. DirectConnectIP = 127.0.0.1 # The STUN server to contact to request your public IP address, for liveless multiplayer. (TCP required.) -# Uncomment these lines to enable. +# Remove the # from the start of these lines to enable the feature. #STUNServer = stun.l.google.com #STUNServerPort = 19302 -# Your external IPv4 address, for liveless multiplayer. Overrode by STUN, if enabled. +# Your external IPv4 address, for liveless multiplayer. Overrode by STUN, Liveless Rooms or UPnP, if enabled. ExternalIP = 0.0.0.0 -# End of rb3.ini \ No newline at end of file +# End of rb3.ini diff --git a/make_xbox.bat b/make_xbox.bat deleted file mode 100644 index 7b2beab..0000000 --- a/make_xbox.bat +++ /dev/null @@ -1,41 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -REM Set compilation variables. -set OUTNAME=RB3Enhanced -set BUILD_DIR=build_xbox -set OUT_DIR=out -set SRC_DIR=source -set INC_DIR=include -set LIBS=xapilib.lib xboxkrnl.lib xnet.lib xonline.lib -set DEFINES=-D _XBOX -D NDEBUG -D RB3E_XBOX -D RB3E -D RB3EDEBUG -REM Set the SDK paths to use. -set INCLUDE=%XEDK%\include\xbox -set LIB=%XEDK%\lib\xbox -set BINARYPATH=%XEDK%\bin\win32 - -REM Warning, since this build script is just to tide X360-only people over without the full toolchain. -echo It is highly recommended that you install the build tools as noted in BUILDING.md, and use "make xbox" to build an Xbox 360 version. -timeout /t 3 - -REM Create folders. -@mkdir %BUILD_DIR% >nul 2>&1 -@mkdir %OUT_DIR% >nul 2>&1 - -REM Pre-build script -call scripts\version.bat > source\version.h - -REM Compile all .c source files. -for %%a in (%SRC_DIR%/*.c) do "%BINARYPATH%\cl.exe" -c -Zi -nologo -W3 -WX- -Ox -Os %DEFINES% -GF -Gm- -MT -GS- -Gy -fp:fast -fp:except- -Zc:wchar_t -Zc:forScope -GR- -openmp- -FI"%XEDK%/include/xbox/xbox_intellisense_platform.h" -Fd"%BUILD_DIR%/" -I "%INC_DIR%" -Fo"%BUILD_DIR%/%%a.obj" -TC %SRC_DIR%/%%a - -REM Fetch all created .obj files. -set OBJECTS= -for %%a in (%BUILD_DIR%/*.c.obj) do set OBJECTS=%BUILD_DIR%/%%a !OBJECTS! -echo Linking DLL... -"%BINARYPATH%\link.exe" -ERRORREPORT:PROMPT -INCREMENTAL:NO -NOLOGO %LIBS% -MANIFESTUAC:"level='asInvoker' uiAccess='false'" -DEBUG -STACK:"262144","262144" -OPT:REF -OPT:ICF -TLBID:1 -RELEASE -dll -entry:"_DllMainCRTStartup" -XEX:NO -OUT:"%BUILD_DIR%/%OUTNAME%.exe" -PDB:"%BUILD_DIR%/%OUTNAME%.pdb" -IMPLIB:"%BUILD_DIR%/%OUTNAME%" %OBJECTS% - -REM Create a final XEXDLL. -echo Creating XEXDLL... -"%BINARYPATH%\imagexex.exe" -nologo -config:"xex.xml" -out:"%OUT_DIR%/%OUTNAME%.dll" "%BUILD_DIR%/%OUTNAME%.exe" - -endlocal \ No newline at end of file From 326548c1c052ed4ffc1def6eb7cdea71d2021665 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Tue, 27 May 2025 19:04:15 -0500 Subject: [PATCH 105/123] add bank8 ports --- include/ports_wii_bank8.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index 5f01c1e..0646d53 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -28,6 +28,8 @@ // #define PORT_MICCHECK 0x8024a4e8 // NOT NEEDED? a bne that throws an error on the song select screen if the mic is not connected #define PORT_BIGSYMBOLFUNC_TAIL 0x804d3bac // blr after a function that initialises a bunch of symbols #define PORT_UPDATEPRESENCEBLOCK_B 0x8021cbd0 // branch after the failure case in a function that calls UpdatePresence +#define PORT_ADDTRACKVECTOR_BL 0x8064e288 // FIX ME // bl to vector_push_back inside of SongData::AddTrack // actually SongDB::AddTrack +#define PORT_GETGAMELIST 0x801dbfa0 // SongData::GetGameList // actually SongDB::GetGems #define PORT_MULTIPLAYER_CRASH 0x80023fec // branch to a function that can crash in online multiplayer #define PORT_MULTIPLAYER_FIX 0x80a2f0c4 // the function that doesn't crash // #define PORT_LOADOBJS_BCTRL 0x827562e4 @@ -44,6 +46,7 @@ #define PORT_EXECUTEDTA 0x803edbd0 // RockCentralGateway::ExecuteConfig #define PORT_BANDLABELSETDISPLAYTEXT 0x80518360 // BandLabel::SetDisplayText #define PORT_SETSONGANDARTISTNAME 0x80254650 // BandLabel::SetSongAndArtistName +#define PORT_SETSONGNAMEFROMNODE 0x80253ec0 // BandLabel::SetSongNameFromNode #define PORT_KEYSONGUITAR 0x8031c1b0 // OvershellPanel::CanGuitarPlayKeys // #define PORT_HMXFACTORYFUNCAT 0x8031b2f8 // FIXME HmxObjectFactoryFunc::_at - inlined in bank8? #define PORT_WILLBENOSTRUM 0x8062b2d0 // GameGemList::WillBeNoStrum @@ -53,6 +56,7 @@ #define PORT_GETWIDGETBYNAME 0x80135f30 // GemManager::GetWidgetByName #define PORT_DATANODEEVALUATE 0x8045e030 // DataNode::Evaluate #define PORT_GETSLOTCOLOR 0x801484d0 // TrackConfig::GetSlotColor +#define PORT_ADDSMASHERPLATETOVECTOR 0x80bf0828 // FIX ME INLINED? // AddSmasherPlateToVector #define PORT_USBWIIGETTYPE 0x809fd840 // UsbWii::GetType #define PORT_FILE_EXISTS 0x804207c0 // FileExists #define PORT_QUEUEMESSAGE 0x80333140 // PassiveMessagesPanel::QueueMessage @@ -66,6 +70,7 @@ // #define PORT_BUILDINSTRUMENTSELECTION 0x802478a8 // FIXME BuildInstrumentSelectionList(?) - actual name not known // #define PORT_PREPARESOMEVECTORMAYBE 0x80247c58 // FIXME Prepares some vector, used by BuildInstrumentSelectionList // #define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // FIXME vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back +#define PORT_VECTORPUSHBACK 0x801dced0 // FIX ME (?) // vector_push_back #define PORT_POSTPROC_DOPOST 0x809ef2d0 // WiiPostProc::DoPost #define PORT_MUSICLIBRARYSELECTMAYBE 0x802ff140 // UNSURE MusicLibrary::TryToSetHighlight, Selects an entry in the Music Library screen - actual name not known #define PORT_GETSYMBOLBYGAMEORIGIN 0x80374910 // RecentCmp::RecentTypeToOrigin @@ -74,6 +79,7 @@ #define PORT_FILESTREAM_CT 0x80494fd0 // FileStream::__ct (the one that takes a char * path instead of a File object) #define PORT_CHUNKSTREAM_CT 0x804929a0 // ChunkStream::__ct #define PORT_GETBANDUSERFROMSLOT 0x80168010 // BandUserMgr::GetUserFromSlot +#define PORT_OVERSHELLPARTSELECTPROVIDERRELOAD 0x80322620 // OvershellPartSelectProvider::Reload #define PORT_GETBANDUSERS 0x801683e0 // BandUserMgr::GetBandUsers #define PORT_GETSONGSHORTNAME 0x802f5dd0 // MetaPerformer::GetSongSymbol #define PORT_GETMETADATA 0x80271de0 // BandSongMgr::Data (function renamed from the original name to avoid any confusion with Data.h) @@ -90,8 +96,24 @@ #define PORT_QUEUINGSOCKET_BIND 0x80068820 // Quazal::QueuingSocket::Bind #define PORT_QUAZALSOCKET_BIND 0x80029dc0 // Quazal::Socket::Bind #define PORT_INITSONGMETADATA 0x8075a0c0 // InitSongMetadata +#define PORT_SONGMETADATACONSTRUCTOR 0x8075a1a0 // SongMetadata::__ct +#define PORT_SONGMETADATALOAD 0x8075a910 // SongMetadata::Load #define PORT_UPDATEPRESENCE 0x8021c3d0 // PresenceMgr::UpdatePresence #define PORT_STEPSEQUENCEJOBSETSTEP 0x80035cf0 // Quazal::StepSequenceJob::SetStep +#define PORT_RNDTEXNEWOBJECT 0x80931050 // RndTex::NewObject +#define PORT_RNDMATNEWOBJECT 0x80931010 // RndMat::NewObject +#define PORT_RNDTEXSETBITMAP 0x8093b810 // RndTex::SetBitmap +#define PORT_RNDTEXSETBITMAP2 0x8093b430 // RndTex::SetBitmap2 +#define PORT_RNDTEXSETBITMAP3 0x8093b5b0 // RndTex::SetBitmap3 +#define PORT_FILEPATHCONSTRUCTOR 0x80462460 // FilePath::__ct +#define PORT_MUSICLIBRARY_CT 0x802f9e10 // MusicLibrary::__ct +#define PORT_MUSICLIBRARYMAT 0x80300390 // MusicLibrary::Mat +#define PORT_NODESORTGETNODE 0x825bf708 // MusicLibrary::GetNodeByIndex //actually NodeSort::GetNode +#define PORT_GAMEGEMDB_CT 0x80628230 // GameGemDB::__ct +#define PORT_ADDMULTIGEM 0x806284e0 // GameGemDB::AddMultiGem +#define PORT_SONGSORTMGRGETSORT 0x8037a510 // SongSortMgr::GetSort +#define PORT_DYNAMICTEX_CT 0x80393bd0 // DynamicTex::__ct +#define PORT_RNDMATSETDIFFUSETEX 0x808d36b0 // RndMat::SetDiffuseTex #define PORT_BINSTREAMWRITE 0x804892b0 // BinStream::Write #define PORT_BINSTREAMREAD 0x80489140 // BinStream::Read #define PORT_BINSTREAMREADENDIAN 0x804894f0 // BinStream::ReadEndian From 1b6e0dae59971dd6d9d1c50e89ab4f7860f61d2e Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Tue, 27 May 2025 19:06:38 -0500 Subject: [PATCH 106/123] do not define this outside of bank8 --- source/rb3enhanced.c | 1 - 1 file changed, 1 deletion(-) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 89447aa..c19b71c 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -360,7 +360,6 @@ void InitialiseFunctions() POKE_B(&GetSongIDFromShortname, PORT_GETSONGIDFROMSHORTNAME); POKE_B(&GetBandUsers, PORT_GETBANDUSERS); POKE_B(&GetBandUserFromSlot, PORT_GETBANDUSERFROMSLOT); - POKE_B(&ObjectFindUIPanel, PORT_OBJECTFINDUIPANEL); POKE_B(&FileStreamConstructor, PORT_FILESTREAM_CT); POKE_B(&ChunkStreamConstructor, PORT_CHUNKSTREAM_CT); POKE_B(&Dynamic_Cast, PORT_DYNAMICCAST); From bea604be33f9a5f112d77b4f44bca1589919eba8 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Tue, 27 May 2025 19:10:27 -0500 Subject: [PATCH 107/123] wait this isnt even used --- include/ports_wii_bank8.h | 6 +++--- source/rb3enhanced.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index 0646d53..d7dd378 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -28,7 +28,7 @@ // #define PORT_MICCHECK 0x8024a4e8 // NOT NEEDED? a bne that throws an error on the song select screen if the mic is not connected #define PORT_BIGSYMBOLFUNC_TAIL 0x804d3bac // blr after a function that initialises a bunch of symbols #define PORT_UPDATEPRESENCEBLOCK_B 0x8021cbd0 // branch after the failure case in a function that calls UpdatePresence -#define PORT_ADDTRACKVECTOR_BL 0x8064e288 // FIX ME // bl to vector_push_back inside of SongData::AddTrack // actually SongDB::AddTrack +// FIX ME INLINED? #define PORT_ADDTRACKVECTOR_BL 0x8064e288 // bl to vector_push_back inside of SongData::AddTrack // actually SongDB::AddTrack #define PORT_GETGAMELIST 0x801dbfa0 // SongData::GetGameList // actually SongDB::GetGems #define PORT_MULTIPLAYER_CRASH 0x80023fec // branch to a function that can crash in online multiplayer #define PORT_MULTIPLAYER_FIX 0x80a2f0c4 // the function that doesn't crash @@ -56,7 +56,7 @@ #define PORT_GETWIDGETBYNAME 0x80135f30 // GemManager::GetWidgetByName #define PORT_DATANODEEVALUATE 0x8045e030 // DataNode::Evaluate #define PORT_GETSLOTCOLOR 0x801484d0 // TrackConfig::GetSlotColor -#define PORT_ADDSMASHERPLATETOVECTOR 0x80bf0828 // FIX ME INLINED? // AddSmasherPlateToVector +// FIX ME INLINED? #define PORT_ADDSMASHERPLATETOVECTOR 0x80bf0828 // AddSmasherPlateToVector #define PORT_USBWIIGETTYPE 0x809fd840 // UsbWii::GetType #define PORT_FILE_EXISTS 0x804207c0 // FileExists #define PORT_QUEUEMESSAGE 0x80333140 // PassiveMessagesPanel::QueueMessage @@ -70,7 +70,7 @@ // #define PORT_BUILDINSTRUMENTSELECTION 0x802478a8 // FIXME BuildInstrumentSelectionList(?) - actual name not known // #define PORT_PREPARESOMEVECTORMAYBE 0x80247c58 // FIXME Prepares some vector, used by BuildInstrumentSelectionList // #define PORT_SOMEVECTORPUSHBACKMAYBE 0x802484a8 // FIXME vector,class_stlpmtx_std::StlNodeAlloc_>_>::push_back -#define PORT_VECTORPUSHBACK 0x801dced0 // FIX ME (?) // vector_push_back +// FIX ME (?) #define PORT_VECTORPUSHBACK 0x801dced0 // vector_push_back #define PORT_POSTPROC_DOPOST 0x809ef2d0 // WiiPostProc::DoPost #define PORT_MUSICLIBRARYSELECTMAYBE 0x802ff140 // UNSURE MusicLibrary::TryToSetHighlight, Selects an entry in the Music Library screen - actual name not known #define PORT_GETSYMBOLBYGAMEORIGIN 0x80374910 // RecentCmp::RecentTypeToOrigin diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index c19b71c..9d856c1 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -348,6 +348,7 @@ void InitialiseFunctions() POKE_B(&HmxFactoryFuncAt, PORT_HMXFACTORYFUNCAT); // TODO(Emma): port to bank8 POKE_B(&ObjectFindUIPanel, PORT_OBJECTFINDUIPANEL); + POKE_B(&vector_push_back, PORT_VECTORPUSHBACK); // otherwise unused it seems? #endif POKE_B(&RandomInt, PORT_RANDOMINT); POKE_B(&DataNodeEvaluate, PORT_DATANODEEVALUATE); @@ -373,7 +374,6 @@ void InitialiseFunctions() POKE_B(&RndTexSetBitmap2, PORT_RNDTEXSETBITMAP2); POKE_B(&FilePathConstructor, PORT_FILEPATHCONSTRUCTOR); POKE_B(&NodeSortGetNode, PORT_NODESORTGETNODE); - POKE_B(&vector_push_back, PORT_VECTORPUSHBACK); POKE_B(&GameGemDBConstructor, PORT_GAMEGEMDB_CT); POKE_B(&SongSortMgrGetSort, PORT_SONGSORTMGRGETSORT); POKE_B(&DynamicTexConstructor, PORT_DYNAMICTEX_CT); From 7d1b3ea703e6623f4f23c91c7805d360d7ee1401 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Tue, 27 May 2025 19:52:11 -0500 Subject: [PATCH 108/123] fix address --- include/ports_wii_bank8.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index d7dd378..2ec57e7 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -108,7 +108,7 @@ #define PORT_FILEPATHCONSTRUCTOR 0x80462460 // FilePath::__ct #define PORT_MUSICLIBRARY_CT 0x802f9e10 // MusicLibrary::__ct #define PORT_MUSICLIBRARYMAT 0x80300390 // MusicLibrary::Mat -#define PORT_NODESORTGETNODE 0x825bf708 // MusicLibrary::GetNodeByIndex //actually NodeSort::GetNode +#define PORT_NODESORTGETNODE 0x8036f5e0 // MusicLibrary::GetNodeByIndex //actually NodeSort::GetNode #define PORT_GAMEGEMDB_CT 0x80628230 // GameGemDB::__ct #define PORT_ADDMULTIGEM 0x806284e0 // GameGemDB::AddMultiGem #define PORT_SONGSORTMGRGETSORT 0x8037a510 // SongSortMgr::GetSort From 0eb8d899efd15688a70911184464e187bb0311f3 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Tue, 27 May 2025 21:12:49 -0500 Subject: [PATCH 109/123] bank8: fix enumerate fail when legacy SD mode is disabled (#44) --- source/rb3enhanced.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 4da721a..4aac620 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -224,6 +224,10 @@ void ApplyPatches() POKE_32(PORT_NASWII_HOST, NOP); // always fire the UpdatePresence function. TODO(Emma): look into it, still not firing when screen is changed :/ POKE_32(PORT_UPDATEPRESENCEBLOCK_B, NOP); +#ifdef RB3E_WII_BANK8 + // nop debug crash enumerating content with legacysdmode disabled + POKE_32(0x80419158, NOP); +#endif #ifndef RB3E_WII_BANK8 // always take the branch to 0x8024a628 so vocals can be selected without a mic plugged in // bank 8 does not have the mic check From a3a7a710a4290143218c2e45aa498402fc0b10d3 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 1 Jun 2025 04:17:39 -0500 Subject: [PATCH 110/123] [feature] allow ini setting to disable metamusic loading (#45) --- assets/wii_default_rb3.ini | 2 ++ assets/xbox_default_rb3.ini | 2 ++ include/config.h | 1 + include/ports_wii.h | 4 ++++ include/ports_wii_bank8.h | 4 ++++ include/ports_xbox360.h | 4 ++++ source/config.c | 2 ++ source/rb3enhanced.c | 11 +++++++++++ 8 files changed, 30 insertions(+) diff --git a/assets/wii_default_rb3.ini b/assets/wii_default_rb3.ini index 2f92329..c09bab7 100644 --- a/assets/wii_default_rb3.ini +++ b/assets/wii_default_rb3.ini @@ -10,6 +10,8 @@ SongSpeedMultiplier = 1.0 ForcedVenue = false # Whether to unlock all clothing and other unlockables by default. UnlockClothing = false +# Whether to disable all menu background ambient music by default. +DisableMenuMusic = false # The folder to check on the SD card for raw files. # The default is "rb3", uncomment this line to change it #RawfilesDir = rb3 diff --git a/assets/xbox_default_rb3.ini b/assets/xbox_default_rb3.ini index 24c8176..2ded2f4 100644 --- a/assets/xbox_default_rb3.ini +++ b/assets/xbox_default_rb3.ini @@ -13,6 +13,8 @@ ForcedVenue = false GameOriginIcons = true # Whether to unlock all clothing and other unlockables by default. UnlockClothing = false +# Whether to disable all menu background ambient music by default. +DisableMenuMusic = false # The folder to check on the Hard Drive and USB drives for raw files. # The default is "rb3", remove the # at the start of this line to change it #RawfilesDir = rb3 diff --git a/include/config.h b/include/config.h index 967e453..13b00f5 100644 --- a/include/config.h +++ b/include/config.h @@ -23,6 +23,7 @@ typedef struct _RB3E_Config char GameOriginIcons; char LogFileAccess; char UnlockClothing; + char DisableMenuMusic; char LanguageOverride[RB3E_LANG_LEN + 1]; char RawfilesDir[RB3E_MAX_CONFIG_LEN]; char DisableRawfiles; diff --git a/include/ports_wii.h b/include/ports_wii.h index 46dba05..fafb17c 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -87,6 +87,10 @@ #define PORT_SYMBOLPREINIT 0x80364c74 // Symbol::PreInit #define PORT_QUEUINGSOCKET_BIND 0x800478d4 // Quazal::QueuingSocket::Bind #define PORT_QUAZALSOCKET_BIND 0x8001cd10 // Quazal::Socket::Bind +#define PORT_METAMUSICISLOADED 0x80678cec // MetaMusic::Loaded +#define PORT_METAMUSICLOAD 0x80678990 // MetaMusic::Load +#define PORT_METAMUSICPOLL 0x80678d20 // MetaMusic::Poll +#define PORT_METAMUSICSTART 0x80678fa4 // MetaMusic::Start #define PORT_INITSONGMETADATA 0x805147a4 // InitSongMetadata #define PORT_UPDATEPRESENCE 0x801879d4 // PresenceMgr::UpdatePresence #define PORT_STEPSEQUENCEJOBSETSTEP 0x80025364 // Quazal::StepSequenceJob::SetStep diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index 5f01c1e..55e7b51 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -89,6 +89,10 @@ #define PORT_SYMBOLPREINIT 0x804be150 // Symbol::PreInit #define PORT_QUEUINGSOCKET_BIND 0x80068820 // Quazal::QueuingSocket::Bind #define PORT_QUAZALSOCKET_BIND 0x80029dc0 // Quazal::Socket::Bind +#define PORT_METAMUSICISLOADED 0x809975d0 // MetaMusic::Loaded +#define PORT_METAMUSICLOAD 0x809970d0 // MetaMusic::Load +#define PORT_METAMUSICPOLL 0x80997610 // MetaMusic::Poll +#define PORT_METAMUSICSTART 0x80997990 // MetaMusic::Start #define PORT_INITSONGMETADATA 0x8075a0c0 // InitSongMetadata #define PORT_UPDATEPRESENCE 0x8021c3d0 // PresenceMgr::UpdatePresence #define PORT_STEPSEQUENCEJOBSETSTEP 0x80035cf0 // Quazal::StepSequenceJob::SetStep diff --git a/include/ports_xbox360.h b/include/ports_xbox360.h index 9b73cbd..d664d35 100644 --- a/include/ports_xbox360.h +++ b/include/ports_xbox360.h @@ -132,6 +132,10 @@ #define PORT_MEMPRINTOVERVIEW 0x827bc838 // MemPrintOverview #define PORT_MEMPRINT 0x827bc970 // MemPrint #define PORT_MEMNUMHEAPS 0x827bb628 // MemNumHeaps +#define PORT_METAMUSICISLOADED 0x8270fab8 // MetaMusic::Loaded +#define PORT_METAMUSICLOAD 0x82710ab0 // MetaMusic::Load +#define PORT_METAMUSICPOLL 0x82711438 // MetaMusic::Poll +#define PORT_METAMUSICSTART 0x82711a00 // MetaMusic::Start #define PORT_INITSONGMETADATA 0x827aa450 // InitSongMetadata #define PORT_UPDATEPRESENCE 0x82680430 // PresenceMgr::UpdatePresence #define PORT_STEPSEQUENCEJOBSETSTEP 0x82af92b8 // Quazal::StepSequenceJob::SetStep diff --git a/source/config.c b/source/config.c index a6f6a64..eb3afa9 100644 --- a/source/config.c +++ b/source/config.c @@ -59,6 +59,8 @@ static int INIHandler(void *user, const char *section, const char *name, const c config.LogFileAccess = RB3E_CONFIG_BOOL(value); if (strcmp(name, "UnlockClothing") == 0) config.UnlockClothing = RB3E_CONFIG_BOOL(value); + if (strcmp(name, "DisableMenuMusic") == 0) + config.DisableMenuMusic = RB3E_CONFIG_BOOL(value); if (strcmp(name, "LanguageOverride") == 0 && strlen(value) == RB3E_LANG_LEN) strncpy(config.LanguageOverride, value, RB3E_LANG_LEN); if (strcmp(name, "RawfilesDir") == 0 && !RB3E_CONFIG_FALSE(value)) diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 4aac620..c063ba1 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -279,6 +279,17 @@ void ApplyConfigurablePatches() POKE_32(PORT_VIDEO_VENUE_CHECK, LI(3, 1)); } + if (config.DisableMenuMusic == 1) + { + // Disables MetaMusic from loading, saves space on the heap + POKE_32(PORT_METAMUSICLOAD, BLR); + POKE_32(PORT_METAMUSICSTART, BLR); + POKE_32(PORT_METAMUSICPOLL, BLR); + // Always return 1 from MetaMusic::Loaded, in MetaPanel::IsLoaded + POKE_32(PORT_METAMUSICISLOADED, LI(3, 1)); + POKE_32(PORT_METAMUSICISLOADED + 4, BLR); + } + #ifdef RB3EDEBUG if (config.QuazalLogging == 1) { From 7c46a3dab9f881e074d3c1dc6cba3d22d9726545 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sun, 1 Jun 2025 10:39:35 +0100 Subject: [PATCH 111/123] [xbox] graphical exception handler and exception dumper --- .vscode/c_cpp_properties.json | 1 + include/exceptions.h | 63 +++++++ include/ports_xbox360.h | 4 + include/rb3enhanced.h | 1 + include/xbox360.h | 2 + source/_functions.c | 1 + source/wii_exceptions.c | 18 +- source/xbox360.c | 7 +- source/xbox360_exceptions.c | 301 ++++++++++++++++++++++++++++++++++ source/xbox360_files.c | 13 +- 10 files changed, 392 insertions(+), 19 deletions(-) create mode 100644 include/exceptions.h create mode 100644 source/xbox360_exceptions.c diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 89fdfd5..44f26ac 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -9,6 +9,7 @@ "defines": [ "_DEBUG", "_XBOX", + "_PPC_", "RB3E", "RB3E_XBOX", "RB3EDEBUG" diff --git a/include/exceptions.h b/include/exceptions.h new file mode 100644 index 0000000..6680256 --- /dev/null +++ b/include/exceptions.h @@ -0,0 +1,63 @@ +/* + RB3Enhanced - exceptions.h + Structures and function definitions for exception handling. +*/ + +#ifndef _EXCEPTIONS_H +#define _EXCEPTIONS_H + +#include + +#ifdef RB3E_XBOX +#include +#endif + +#ifdef RB3E_WII +typedef struct OSContext { + uint32_t gprs[32]; // at 0x0 + uint32_t cr; // at 0x80 + uint32_t lr; // at 0x84 + uint32_t ctr; // at 0x88 + uint32_t xer; // at 0x8C + double fprs[32]; // at 0x90 + uint32_t fpscr_pad; // at 0x190 + uint32_t fpscr; // at 0x194 + uint32_t srr0; // at 0x198 + uint32_t srr1; // at 0x19C + uint16_t mode; // at 0x1A0 + uint16_t state; // at 0x1A2 + uint32_t gqrs[8]; // at 0x1A4 + uint32_t psf_pad; // at 0x1C4 + double psfs[32]; // at 0x1C8 +} OSContext; +typedef struct _rb3e_exception_wii { + uint32_t rb3e_base_address; + uint8_t error; + uint32_t dsisr; + uint32_t dar; + OSContext thread_ctx; +} rb3e_exception_wii; +#elif RB3E_XBOX +#define EXCEPTION_CONTEXT_SIZE 560 +#endif + +// '3EXx' where x is W for Wumbo, X for Xbox and P for PS3 +#define EXCEPTIONPACK_HEADER_WII 0x33455857 +#define EXCEPTIONPACK_HEADER_XBOX 0x33455858 +#define EXCEPTIONPACK_HEADER_PS3 0x33455850 + +typedef struct _rb3e_exception_header { + uint32_t magic; + uint32_t version; + char rb3e_buildtag[48]; + char rb3e_commit[48]; + uint16_t num_stackwalk; + uint16_t num_memchunks; +} rb3e_exception_header; + +typedef struct _rb3e_exception_memchunk { + uint32_t address; + uint32_t length; +} rb3e_exception_memchunk; + +#endif // _EXCEPTIONS_H diff --git a/include/ports_xbox360.h b/include/ports_xbox360.h index d664d35..8c7c136 100644 --- a/include/ports_xbox360.h +++ b/include/ports_xbox360.h @@ -53,6 +53,7 @@ #define PORT_MULTIPLAYER_CRASH 0x82ae6880 // branch to a function that can crash in online multiplayer #define PORT_MULTIPLAYER_FIX 0x8282b238 // the function that doesn't crash #define PORT_QUAZAL_BREAKPOINT 0x828410c0 // address to DbgBreakPoint in Quazal::Platform::Breakpoint +#define PORT_MAINSEH 0x82272e60 // address to __CxxFrameHandler above the main() function // function patch addresses #define PORT_SETDISKERROR 0x82516320 // PlatformMgr::SetDiskError #define PORT_APP_RUN 0x82272e90 // App::Run @@ -148,6 +149,7 @@ #define PORT_DATASETELEM 0x82760b38 // DataSetElem #define PORT_DATAONELEM 0x8275ff50 // DataOnElem #define PORT_DATANODEGETOBJ 0x8274b088 // DataNode::GetObj +#define PORT_DXRND_SUSPEND 0x8273A370 // DxRnd::Suspend // instance addresses #define PORT_MODIFIERMGR_POINTER 0x82dfec08 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x82cc8f60 // address of RockCentralGateway @@ -164,6 +166,7 @@ #define PORT_THEGAME 0x82e02128 // pointer to TheGame (you lost) #define PORT_OBJECTDIRMAINDIR 0x82e054b8 // ObjectDir::sMainDir #define PORT_MESH_GREV 0x82cc2638 // address of RndMesh::gRev +#define PORT_DXRND 0x82e04b38 // address of TheDxRnd // import function stubs #define PORT_XEKEYSSETKEY_STUB 0x82c4c47c #define PORT_XEKEYSAESCBC_STUB 0x82c4c48c @@ -180,6 +183,7 @@ #define PORT_XAMUSERGETSIGNINSTATE 0x82c4bcfc #define PORT_XAMUSERCHECKPRIVILEGE 0x82c4bd1c #define PORT_XAMSHOWFRIENDSUI 0x8283d710 +#define PORT_XAMLOADERTERMINATETITLE 0x82c4bccc // define logging functions void DbgPrint(const char *s, ...); diff --git a/include/rb3enhanced.h b/include/rb3enhanced.h index e1658d0..4953c15 100644 --- a/include/rb3enhanced.h +++ b/include/rb3enhanced.h @@ -23,6 +23,7 @@ int RB3E_FileExists(char *filename); int RB3E_OpenFile(char *filename, char readWrite); int RB3E_FileSize(int file); int RB3E_ReadFile(int file, int offset, void *buffer, int size); +int RB3E_WriteFile(int file, int offset, void *buffer, int size); void RB3E_CloseFile(int file); int RB3E_CreateThread(void *address, void *arg, int stack_size); void RB3E_Sleep(int ms); diff --git a/include/xbox360.h b/include/xbox360.h index d00e717..c023bc4 100644 --- a/include/xbox360.h +++ b/include/xbox360.h @@ -28,6 +28,8 @@ DWORD XexGetProcedureAddress(HANDLE ModuleHandle, DWORD Ordinal, PVOID OutAddres int XeCryptSha(void *input_1, int input_1_size, void *input_2, int input_2_size, void *input_3, int input_3_size, void *output, int output_size); int XeCryptHmacSha(void *key, int key_size, void *input_1, int input_1_size, void *input_2, int input_2_size, void *input_3, int input_3_size, void *output, int output_size); int XeKeysConsolePrivateKeySign(unsigned char hash[0x14], unsigned char output_cert_sig[0x228]); +// memory management +int MmIsAddressValid(DWORD address); // structure for xnet typedef struct _XnpRouteEntry_t diff --git a/source/_functions.c b/source/_functions.c index c219990..b47c6f5 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -62,6 +62,7 @@ RB3E_STUB(MemNumHeaps) RB3E_STUB(MemAlloc) RB3E_STUB(MemFree) RB3E_STUB(FileIsDLC) +RB3E_STUB(DxRndSuspend) #ifndef RB3E_XBOX RB3E_STUB(DataRegisterFunc) // DataRegisterFunc is inlined on 360 #endif diff --git a/source/wii_exceptions.c b/source/wii_exceptions.c index b1dbde7..2757396 100644 --- a/source/wii_exceptions.c +++ b/source/wii_exceptions.c @@ -11,6 +11,7 @@ #include #include #include +#include "exceptions.h" #include "version.h" #include "ppcasm.h" #include "ports.h" @@ -431,23 +432,6 @@ static unsigned char dolphin_osfatal_font[] = { typedef struct _GXColor { uint8_t r, g, b, a; } GXColor; -typedef struct OSContext { - uint32_t gprs[32]; // at 0x0 - uint32_t cr; // at 0x80 - uint32_t lr; // at 0x84 - uint32_t ctr; // at 0x88 - uint32_t xer; // at 0x8C - double fprs[32]; // at 0x90 - uint32_t fpscr_pad; // at 0x190 - uint32_t fpscr; // at 0x194 - uint32_t srr0; // at 0x198 - uint32_t srr1; // at 0x19C - uint16_t mode; // at 0x1A0 - uint16_t state; // at 0x1A2 - uint32_t gqrs[8]; // at 0x1A4 - uint32_t psf_pad; // at 0x1C4 - double psfs[32]; // at 0x1C8 -} OSContext; typedef enum _OSErrorType { OS_ERR_SYSTEM_RESET, OS_ERR_MACHINE_CHECK, diff --git a/source/xbox360.c b/source/xbox360.c index 1f362ec..ca76418 100644 --- a/source/xbox360.c +++ b/source/xbox360.c @@ -10,6 +10,7 @@ #include "ports.h" #include "rb3enhanced.h" #include "xbox360.h" +#include "version.h" static int HasRunDetection = 0; static int DetectionResult = 0; @@ -132,11 +133,15 @@ static void EnableSockpatch() } } +// defined in xbox360_exceptions.c +int RB3E_ExceptionHandler(EXCEPTION_RECORD *ExceptionRecord, void *EstablisherFrame, CONTEXT *ContextRecord, struct _DISPATCHER_CONTEXT *DispatcherContext); + BOOL APIENTRY DllMain(HANDLE hInstDLL, DWORD reason, LPVOID lpReserved) { if (reason == DLL_PROCESS_ATTACH) { - RB3E_DEBUG("DLL has been loaded"); + RB3E_DEBUG("DLL has been loaded", NULL); + POKE_32(PORT_MAINSEH, (DWORD)RB3E_ExceptionHandler); // Replace App::Run with App::RunWithoutDebugging POKE_BL(PORT_APP_RUN, PORT_APP_RUNNODEBUG); // Apply our hook diff --git a/source/xbox360_exceptions.c b/source/xbox360_exceptions.c new file mode 100644 index 0000000..ce67ae2 --- /dev/null +++ b/source/xbox360_exceptions.c @@ -0,0 +1,301 @@ +/* + RB3Enhanced - xbox360_exceptions.c + Low-level exception handling for RB3Enhanced on Xbox. +*/ +#ifdef RB3E_XBOX + +#include +#include +#include +#include "exceptions.h" +#include "ppcasm.h" +#include "ports.h" +#include "rb3enhanced.h" +#include "xbox360.h" +#include "version.h" + +static int hadException = 0; + +static EXCEPTION_RECORD exceptionRecord; +static CONTEXT exceptionContext; +static BYTE exceptionHandlerStack[0x2000]; + +static LPWSTR buttons[1] = {L"Return to Dashboard"}; +static MESSAGEBOX_RESULT result; +static XOVERLAPPED overlapped; +static void MessageBoxAndWait(wchar_t *text) +{ + if (XShowMessageBoxUI(XUSER_INDEX_ANY, L"RB3Enhanced Exception", text, 1, buttons, 0, XMB_ERRORICON, &result, &overlapped) == ERROR_IO_PENDING) + { + while (!XHasOverlappedIoCompleted(&overlapped)) + Sleep(50); + } +} + +static int didSuccessfullyWriteFile = 0; +static char exceptionOutputFilename[64]; + +static char *dataReadWriteNames[2] = { "read", "write" }; + +void DxRndSuspend(void *theDxRnd); + +static void GraphicalExceptionDisplay() +{ + wchar_t exceptionText[1024] = {0}; + wchar_t exceptionFmtBuffer[128]; + wcscat(exceptionText, L"Rock Band 3 has crashed. If this happens again, report this to the RB3Enhanced developers!\n\n"); +#define fmtExc(...) { wsprintfW(exceptionFmtBuffer, __VA_ARGS__); wcscat(exceptionText, exceptionFmtBuffer); } + + fmtExc(L"RB3Enhanced %S\n", RB3E_BUILDTAG); + + // print info about the exception + if (exceptionRecord.ExceptionCode == STATUS_ACCESS_VIOLATION) { + fmtExc(L"Data Error: %08X %08X %S\n", exceptionRecord.ExceptionAddress, exceptionRecord.ExceptionInformation[1], dataReadWriteNames[exceptionRecord.ExceptionInformation[0] & 1]); + } else if (exceptionRecord.ExceptionCode == STATUS_ILLEGAL_INSTRUCTION) { + fmtExc(L"Instruction Error: %08X\n", exceptionRecord.ExceptionAddress); + } else { + fmtExc(L"Error %X: %08X\n", exceptionRecord.ExceptionCode, exceptionRecord.ExceptionAddress); + } + + // print some core registers - not worth having them all... + fmtExc(L"R1:%08X R12:%08X R13:%08X\n", exceptionContext.Gpr1, exceptionContext.Gpr12, exceptionContext.Gpr13); + fmtExc(L"PC:%08X LR:%08X MSR:%08X\n", exceptionContext.Iar, exceptionContext.Lr, exceptionContext.Msr); + + // walk the stack + fmtExc(L"Stack: "); + { + int i = 0; + DWORD *stackPtr = (DWORD *)exceptionContext.Gpr1; + // sanity check: make sure our stack pointer is valid + if (MmIsAddressValid((DWORD)stackPtr)) + { + do { + DWORD lr; + stackPtr = (DWORD *)stackPtr[0]; + // break if the stack pointer isn't valid + if (!MmIsAddressValid((DWORD)stackPtr)) + break; + lr = stackPtr[-2]; + // break if the LR isn't valid + if (!MmIsAddressValid((DWORD)lr)) + break; + // print it out + fmtExc(L"%08X ", lr); + i++; + } while (i < 7); + } + } + fmtExc(L"\n"); + + if (didSuccessfullyWriteFile) + { + char *exceptionOutputFilenameCut = exceptionOutputFilename; + if (exceptionOutputFilenameCut[0] == 'R') // starts with RB3 so skip it + exceptionOutputFilenameCut += 3; + fmtExc(L"\nCrash dump saved to\n%S", exceptionOutputFilenameCut); + } + else + { + fmtExc(L"\nFailed to save crash dump."); + } +#undef fmtExc + // suspend TheDxRnd so XAM can render our error message + DxRndSuspend((void *)PORT_DXRND); + MessageBoxAndWait(exceptionText); +} + +static int openExceptionFile = -1; +static uint16_t totalStackwalks = 0; +static uint16_t totalMemchunks = 0; +static uint32_t totalMemchunkData = 0; + +static void WriteHeaderToFile(uint16_t num_stackwalk, uint16_t num_memchunks) +{ + rb3e_exception_header header; + header.magic = EXCEPTIONPACK_HEADER_XBOX; + header.version = 0; + strncpy(header.rb3e_buildtag, RB3E_BUILDTAG, sizeof(header.rb3e_buildtag)); + strncpy(header.rb3e_commit, RB3E_BUILDCOMMIT, sizeof(header.rb3e_commit)); + header.num_stackwalk = num_stackwalk; + header.num_memchunks = num_memchunks; + // always write to the start of the file + RB3E_WriteFile(openExceptionFile, 0, &header, sizeof(rb3e_exception_header)); +} + +static void WriteExceptionInfoToFile() +{ + // always comes after the header + RB3E_WriteFile(openExceptionFile, sizeof(rb3e_exception_header) + 0, &exceptionRecord, sizeof(exceptionRecord)); + RB3E_WriteFile(openExceptionFile, sizeof(rb3e_exception_header) + sizeof(EXCEPTION_RECORD), &exceptionContext, EXCEPTION_CONTEXT_SIZE); +} + +static void WriteMemChunkToFile(void *buffer, uint32_t length) +{ + rb3e_exception_memchunk chunkheader; + // make sure the address is valid as well as the total range + if (!MmIsAddressValid((DWORD)buffer) || !MmIsAddressValid((DWORD)buffer + length)) + return; + // craft the header + chunkheader.address = (uint32_t)buffer; + chunkheader.length = length; + // write the header to the file + // oh geez that offset calculation is getting long im starting to regret not just having fwrite equivalent but we stay silly + RB3E_WriteFile(openExceptionFile, sizeof(rb3e_exception_header) + sizeof(EXCEPTION_RECORD) + EXCEPTION_CONTEXT_SIZE + + (totalStackwalks * sizeof(DWORD)) + (totalMemchunks * sizeof(rb3e_exception_memchunk)) + totalMemchunkData, &chunkheader, sizeof(rb3e_exception_memchunk)); + // increment our pointers + totalMemchunks++; + // for the sake of simplicity write data here + RB3E_WriteFile(openExceptionFile, sizeof(rb3e_exception_header) + sizeof(EXCEPTION_RECORD) + EXCEPTION_CONTEXT_SIZE + + (totalStackwalks * sizeof(DWORD)) + (totalMemchunks * sizeof(rb3e_exception_memchunk)) + totalMemchunkData, buffer, length); + totalMemchunkData += length; +} + +static void WriteStackWalkToFile() +{ + DWORD *stackPtr = (DWORD *)exceptionContext.Gpr1; + // sanity check: make sure our stack pointer is valid + if (MmIsAddressValid((DWORD)stackPtr)) + { + do { + DWORD lr; + stackPtr = (DWORD *)stackPtr[0]; + // break if the stack pointer isn't valid + if (!MmIsAddressValid((DWORD)stackPtr)) + break; + lr = stackPtr[-2]; + // break if the LR isn't valid + if (!MmIsAddressValid((DWORD)lr)) + break; + // write the address out to the file + RB3E_WriteFile(openExceptionFile, sizeof(rb3e_exception_header) + sizeof(EXCEPTION_RECORD) + EXCEPTION_CONTEXT_SIZE + (totalStackwalks * sizeof(DWORD)), &lr, sizeof(DWORD)); + totalStackwalks++; + } while (TRUE); + } + // this code is ugly, but do that all again so we can actually dump out the contents of the stack as memchunks + stackPtr = (DWORD *)exceptionContext.Gpr1; + if (MmIsAddressValid((DWORD)stackPtr)) + { + DWORD totalStackBytes = 0; + DWORD lastPtr = (DWORD)stackPtr; + do { + DWORD length; + // set an upper limit for the amount of stack we can dump + if (totalStackBytes > 0x4000) + break; + stackPtr = (DWORD *)stackPtr[0]; + // break if the stack pointer isn't valid + if (!MmIsAddressValid((DWORD)stackPtr)) + break; + // calculate the length of this chunk of the stack and if its too long then dont bother + length = (DWORD)stackPtr - lastPtr; + if (length > 0x4000) + break; + // write the chunk out to the file + WriteMemChunkToFile((void *)lastPtr, length); + lastPtr = (DWORD)stackPtr; + totalStackBytes += length; + } while (TRUE); + } +} + +static void ExceptionWriteToFile() +{ + char exceptionFolder[32]; + char exceptionFilename[32]; + // get the path of rb3.ini, this is the folder we'll write the crash dump to + char *configFilePath = RB3E_GetRawfilePath("rb3.ini", 1); + // create the filename for the execption + SYSTEMTIME sysTime; + GetSystemTime(&sysTime); + sprintf(exceptionFilename, "crash_%04d%02d%02d_%02d%02d%02d.exc", sysTime.wYear, sysTime.wMonth, sysTime.wDay, sysTime.wHour, sysTime.wMinute, sysTime.wSecond); + // try to open the file + if (configFilePath == NULL) + { + // try saving to the game directory first + sprintf(exceptionOutputFilename, "GAME:\\%s", exceptionFilename); + openExceptionFile = RB3E_OpenFile(exceptionOutputFilename, 1); + // if that fails, try saving to the root of the HDD + if (openExceptionFile == -1 && RB3E_Mounted) + { + sprintf(exceptionOutputFilename, "RB3HDD:\\%s", exceptionFilename); + openExceptionFile = RB3E_OpenFile(exceptionOutputFilename, 1); + } + } + else + { + // save to the path of rb3.ini, so remove it from the filename path + char *rb3inipos = strstr(configFilePath, "rb3.ini"); + strncpy(exceptionFolder, configFilePath, rb3inipos - configFilePath); + sprintf(exceptionOutputFilename, "%s%s", exceptionFolder, exceptionFilename); + openExceptionFile = RB3E_OpenFile(exceptionOutputFilename, 1); + } + // if we couldn't create the new file then bail out + if (openExceptionFile == -1) + { + didSuccessfullyWriteFile = 0; + return; + } + // write the header and exception info + WriteHeaderToFile(0, 0); + WriteExceptionInfoToFile(); + // write the contents of the stack + WriteStackWalkToFile(); + // write memory chunks for valid memory addresses we have in GPRs + // TODO(Emma): dedupe + { + ULONGLONG *registers = &exceptionContext.Gpr0; + int i = 0; + for (i = 0; i < 32; i++) + { + DWORD reg32 = (DWORD)registers[i]; + if ((reg32 & 0xF0000000) == 0x40000000 || // heap memory + (reg32 & 0xF0000000) == 0x70000000 || // stack memory + (reg32 & 0xF0000000) == 0x80000000 || // 64K executable memory + (reg32 & 0xF0000000) == 0x90000000) // 4K executable memory + { + if (MmIsAddressValid(reg32 & 0xFFFFFF00)) + { + WriteMemChunkToFile((void *)(reg32 & 0xFFFFFF00), 0x200); + } + } + } + } + // TODO(Emma): write heap debugging information, and other useful trinkets + // rewrite the header with the total stack walked + WriteHeaderToFile(totalStackwalks, totalMemchunks); + didSuccessfullyWriteFile = 1; + // close the file and flush to disk + RB3E_CloseFile(openExceptionFile); +} + +static void ExceptionHandler() +{ + ExceptionWriteToFile(); + GraphicalExceptionDisplay(); + // XamLoaderTerminateTitle + ((void(*)(void))PORT_XAMLOADERTERMINATETITLE)(); +} + +int RB3E_ExceptionHandler(EXCEPTION_RECORD *ExceptionRecord, void *EstablisherFrame, CONTEXT *ContextRecord, struct _DISPATCHER_CONTEXT *DispatcherContext) +{ + POKE_B(&DxRndSuspend, PORT_DXRND_SUSPEND); + + // if we've already had an exception, try to terminate title without displaying to the user + if (hadException) + { + ContextRecord->Iar = PORT_XAMLOADERTERMINATETITLE; + return 0; + } + + // back up our exception record and stack + memcpy(&exceptionRecord, ExceptionRecord, sizeof(EXCEPTION_RECORD)); + memcpy(&exceptionContext, ContextRecord, sizeof(CONTEXT)); + // change the thread to go to our exception handler + ContextRecord->Iar = (DWORD)ExceptionHandler; + // we change the stack too to avoid clobbering the original stack and make it viable to dump info from + ContextRecord->Gpr1 = (DWORD)exceptionHandlerStack + sizeof(exceptionHandlerStack); + hadException = 1; + return 0; // continue execution +} + +#endif diff --git a/source/xbox360_files.c b/source/xbox360_files.c index 97cdbdb..ccf37f1 100644 --- a/source/xbox360_files.c +++ b/source/xbox360_files.c @@ -105,7 +105,10 @@ int RB3E_FileExists(char *filename) } int RB3E_OpenFile(char *filename, char readWrite) { - HANDLE handle = CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + DWORD desiredAccess = readWrite ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ; + DWORD shareMode = readWrite ? (FILE_SHARE_READ | FILE_SHARE_WRITE) : FILE_SHARE_READ; + DWORD creationDisposition = readWrite ? OPEN_ALWAYS : OPEN_EXISTING; + HANDLE handle = CreateFile(filename, desiredAccess, shareMode, NULL, creationDisposition, 0, NULL); if (handle == INVALID_HANDLE_VALUE) return -1; return (int)handle; @@ -122,6 +125,14 @@ int RB3E_ReadFile(int file, int offset, void *buffer, int size) return -1; return readBytes; } +int RB3E_WriteFile(int file, int offset, void *buffer, int size) +{ + DWORD wroteBytes; + SetFilePointer((HANDLE)file, offset, NULL, FILE_BEGIN); + if (WriteFile((HANDLE)file, buffer, size, &wroteBytes, NULL) == FALSE) + return -1; + return wroteBytes; +} void RB3E_CloseFile(int file) { XCloseHandle((HANDLE)file); From 8d2ad727493f17ebf4ac878fd5091f8f6ebe2534 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Mon, 2 Jun 2025 00:09:17 +0100 Subject: [PATCH 112/123] [feature] clear song cache from DTA, log content name/mount --- include/GlobalSymbols.h | 1 + include/config.h | 1 + include/ports_xbox360.h | 2 + include/rb3/Cache.h | 20 +++++++++ include/rb3/XboxCache.h | 35 ++++++++++++++++ include/rb3/XboxContent.h | 41 +++++++++++++++++++ include/rb3enhanced.h | 1 + include/xbox360.h | 1 + source/DTAFunctions.c | 8 ++++ source/GlobalSymbols.c | 1 + source/_functions.c | 2 + source/config.c | 2 + source/wii.c | 7 ++++ source/xbox360.c | 4 ++ source/xbox360_content.c | 85 +++++++++++++++++++++++++++++++++++++++ 15 files changed, 211 insertions(+) create mode 100644 include/rb3/Cache.h create mode 100644 include/rb3/XboxCache.h create mode 100644 include/rb3/XboxContent.h create mode 100644 source/xbox360_content.c diff --git a/include/GlobalSymbols.h b/include/GlobalSymbols.h index 354a98b..5a273f8 100644 --- a/include/GlobalSymbols.h +++ b/include/GlobalSymbols.h @@ -25,6 +25,7 @@ typedef struct _GlobalSymbols Symbol rb3e_get_album; Symbol rb3e_get_origin; Symbol rb3e_get_genre; + Symbol rb3e_delete_songcache; // modifiers Symbol forceHopos; diff --git a/include/config.h b/include/config.h index 13b00f5..bae2377 100644 --- a/include/config.h +++ b/include/config.h @@ -28,6 +28,7 @@ typedef struct _RB3E_Config char RawfilesDir[RB3E_MAX_CONFIG_LEN]; char DisableRawfiles; char QuazalLogging; + char ContentLogging; // [Graphics] int RenderResX; int RenderResY; diff --git a/include/ports_xbox360.h b/include/ports_xbox360.h index 8c7c136..9319722 100644 --- a/include/ports_xbox360.h +++ b/include/ports_xbox360.h @@ -150,6 +150,8 @@ #define PORT_DATAONELEM 0x8275ff50 // DataOnElem #define PORT_DATANODEGETOBJ 0x8274b088 // DataNode::GetObj #define PORT_DXRND_SUSPEND 0x8273A370 // DxRnd::Suspend +#define PORT_XBOXCONTENT_CONSTRUCTOR 0x8251fb40 // XboxContent::__ct +#define PORT_CACHEMGRXBOX_MOUNTASYNC 0x827d7b38 // CacheMgrXbox::MountAsync // instance addresses #define PORT_MODIFIERMGR_POINTER 0x82dfec08 // pointer to ModifierManager #define PORT_ROCKCENTRALGATEWAY 0x82cc8f60 // address of RockCentralGateway diff --git a/include/rb3/Cache.h b/include/rb3/Cache.h new file mode 100644 index 0000000..94231a4 --- /dev/null +++ b/include/rb3/Cache.h @@ -0,0 +1,20 @@ + +#ifndef _CACHE_H +#define _CACHE_H + +typedef struct _Cache +{ + void *vtable; + int mOpCur; + int mLastResult; +} Cache; + +typedef struct _CacheMgr +{ + void *vtable; + int mCacheIDStore[3]; // vector + int mOpCur; + int mLastResult; +} CacheMgr; + +#endif // _CACHE_H diff --git a/include/rb3/XboxCache.h b/include/rb3/XboxCache.h new file mode 100644 index 0000000..4e5034f --- /dev/null +++ b/include/rb3/XboxCache.h @@ -0,0 +1,35 @@ + +#ifndef _XBOXCACHE_H +#define _XBOXCACHE_H +#ifdef RB3E_XBOX + +#include +#include "rb3/Cache.h" +#include "rb3/String.h" +#include "rb3/Symbol.h" + +typedef struct _CacheIDXbox +{ + void *vtable; + String mStrCacheName; + XCONTENT_DATA mContentData; +} CacheIDXbox; + +typedef struct _CacheMgrXbox +{ + CacheMgr root; + int mEnumerationHandle; + XOVERLAPPED mOverlapped; + CacheIDXbox **mOutCacheIDPtr; + void *mPendingCachePtr; + void *mPendingObject; + CacheIDXbox *mPendingCacheID; + XCONTENT_DATA mContentData; + String mCacheSearchName; + String unk; +} CacheMgrXbox; + +int CacheMgrXbox_MountAsync(CacheMgrXbox *thisCacheMgr, CacheIDXbox *cacheID, Cache **outCache, void *object); + +#endif // RB3E_XBOX +#endif // _XBOXCACHE_H diff --git a/include/rb3/XboxContent.h b/include/rb3/XboxContent.h new file mode 100644 index 0000000..c875da8 --- /dev/null +++ b/include/rb3/XboxContent.h @@ -0,0 +1,41 @@ + +#ifndef _XBOXCONTENT_H +#define _XBOXCONTENT_H +#ifdef RB3E_XBOX + +#include +#include "rb3/String.h" +#include "rb3/Symbol.h" + +typedef enum _ContentState +{ + kUnmounted = 0, + kNeedsMounting = 1, + kMounting = 2, + kUnmounting = 3, + kMounted = 4, + kDeleting = 6, + kDeleted = 7, + kFailed = 8, +} ContentState; + +typedef struct _XboxContent +{ + void *vtable; + XOVERLAPPED *mOverlapped; + XCONTENT_CROSS_TITLE_DATA mTitleData; + int mLicenseBits; + char mLicenseBitsValid; // bool + String mRoot; + String mMountPath; + int mState; + int mPadNum; + char mDeleted; // bool + Symbol mFileName; + int mLRM; // what? +} XboxContent; + +XboxContent *XboxContentConstruct(XboxContent *thisXboxContent, XCONTENT_CROSS_TITLE_DATA *titleData, int contentIndex, int padNum, char needsMount); + +#endif // RB3E_XBOX +#endif // _XBOXCONTENT_H diff --git a/include/rb3enhanced.h b/include/rb3enhanced.h index 4953c15..1d57254 100644 --- a/include/rb3enhanced.h +++ b/include/rb3enhanced.h @@ -29,6 +29,7 @@ int RB3E_CreateThread(void *address, void *arg, int stack_size); void RB3E_Sleep(int ms); int RB3E_RelaunchGame(); void RB3E_FlushCache(void * address, unsigned int size); +int RB3E_DeleteSongCache(); // stub function at the start of the .text segment - doubles as the start of _functions.c void RB3EBase(); diff --git a/include/xbox360.h b/include/xbox360.h index c023bc4..c2635e2 100644 --- a/include/xbox360.h +++ b/include/xbox360.h @@ -53,4 +53,5 @@ void XNetLogonGetExtendedStatus(unsigned int *login_status, unsigned int *login_ void InitCryptoHooks(); void InitLivelessHooks(); void InitInputHooks(); +void InitContentHooks(); #endif // RB3E_XBOX diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index c75683d..ee2d577 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -287,6 +287,13 @@ DataNode *DTAGetOrigin(DataNode *node, DataArray *args) return node; } +DataNode *DTADeleteSongCache(DataNode *node, DataArray *args) +{ + node->type = INT_VALUE; + node->value.intVal = RB3E_DeleteSongCache(); + return node; +} + #ifdef RB3E_XBOX // this function is inlined on the Xbox version, so we re-create it void DataRegisterFunc(Symbol name, DTAFunction_t func) @@ -312,5 +319,6 @@ void AddDTAFunctions() DataRegisterFunc(globalSymbols.rb3e_get_album, DTAGetAlbum); DataRegisterFunc(globalSymbols.rb3e_get_origin, DTAGetOrigin); DataRegisterFunc(globalSymbols.rb3e_get_genre, DTAGetGenre); + DataRegisterFunc(globalSymbols.rb3e_delete_songcache, DTADeleteSongCache); RB3E_MSG("Added DTA functions!", NULL); } diff --git a/source/GlobalSymbols.c b/source/GlobalSymbols.c index 5a17a8f..a18e7ee 100644 --- a/source/GlobalSymbols.c +++ b/source/GlobalSymbols.c @@ -41,6 +41,7 @@ void InitGlobalSymbols() SymbolConstruct(&globalSymbols.rb3e_get_album, "rb3e_get_album"); SymbolConstruct(&globalSymbols.rb3e_get_origin, "rb3e_get_origin"); SymbolConstruct(&globalSymbols.rb3e_get_genre, "rb3e_get_genre"); + SymbolConstruct(&globalSymbols.rb3e_delete_songcache, "rb3e_delete_songcache"); SymbolConstruct(&globalSymbols.blackBackground, "mod_black_background"); SymbolConstruct(&globalSymbols.colorShuffle, "mod_color_shuffle"); diff --git a/source/_functions.c b/source/_functions.c index b47c6f5..6d68d42 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -110,6 +110,8 @@ RB3E_STUB(DataOnElem) RB3E_STUB(DataNodeGetObj) RB3E_STUB(HeapInit) RB3E_STUB(ResolvedModuleKeyboard) +RB3E_STUB(XboxContentConstruct) +RB3E_STUB(CacheMgrXbox_MountAsync) #ifdef RB3E_WII // Wii-specific functions diff --git a/source/config.c b/source/config.c index eb3afa9..540c607 100644 --- a/source/config.c +++ b/source/config.c @@ -69,6 +69,8 @@ static int INIHandler(void *user, const char *section, const char *name, const c config.DisableRawfiles = RB3E_CONFIG_BOOL(value); if (strcmp(name, "QuazalLogging") == 0) config.QuazalLogging = RB3E_CONFIG_BOOL(value); + if (strcmp(name, "ContentLogging") == 0) + config.ContentLogging = RB3E_CONFIG_BOOL(value); } if (strcmp(section, "Events") == 0) { diff --git a/source/wii.c b/source/wii.c index 9f8d0dd..7d113dc 100644 --- a/source/wii.c +++ b/source/wii.c @@ -90,6 +90,13 @@ void RB3E_FlushCache(void * address, unsigned int size) { ICInvalidateRange((void *)alignedAddress, alignedSize); } +int RB3E_DeleteSongCache() +{ + // TODO(Emma): delete song cache on Wii + RB3E_MSG("Tried to delete song cache on Wii, unsupported.", NULL); + return 0; +} + int RB3E_RelaunchGame() { /* diff --git a/source/xbox360.c b/source/xbox360.c index ca76418..bd2d12f 100644 --- a/source/xbox360.c +++ b/source/xbox360.c @@ -6,8 +6,10 @@ #ifdef RB3E_XBOX #include +#include "rb3/XboxContent.h" #include "ppcasm.h" #include "ports.h" +#include "utilities.h" #include "rb3enhanced.h" #include "xbox360.h" #include "version.h" @@ -87,6 +89,8 @@ static void CTHook(void *ThisApp, int argc, char **argv) InitCryptoHooks(); // initialise hooks for input InitInputHooks(); + // initialise hooks for content + InitContentHooks(); // initialise hooks for liveless - this has to be done *after* systeminit POKE_BL(PORT_SYSTEMINIT_BLANK, &InitLivelessHooks); // launch game diff --git a/source/xbox360_content.c b/source/xbox360_content.c new file mode 100644 index 0000000..cd53c71 --- /dev/null +++ b/source/xbox360_content.c @@ -0,0 +1,85 @@ +/* + RB3Enhanced - xbox360_content.c + Xbox 360 content and cache hooks. +*/ + +#ifdef RB3E_XBOX + +#include +#include +#include +#include "rb3/XboxCache.h" +#include "rb3/XboxContent.h" +#include "config.h" +#include "ports.h" +#include "utilities.h" +#include "rb3enhanced.h" +#include "xbox360.h" + +int hasLastMountedCache = 0; +CacheIDXbox lastMountedCache; + +int RB3E_DeleteSongCache() +{ + DWORD r; + XDEVICE_DATA deviceData; + if (hasLastMountedCache == 0) + { + RB3E_MSG("Tried to delete a song cache, but we don't have one...", NULL); + return 0; + } + if (XContentGetDeviceData(lastMountedCache.mContentData.DeviceID, &deviceData) == 0) + { + RB3E_DEBUG("Deleting song cache '%s' on \"%S\"", lastMountedCache.mContentData.szFileName, deviceData.wszFriendlyName); + r = XContentDelete(0xFF, &lastMountedCache.mContentData, NULL); + if (r != 0) + { + RB3E_MSG("Song cache delete failed (%i)", r); + return 0; + } + return 1; + } + else + { + RB3E_MSG("Tried to delete a song cache, but the device is gone...", NULL); + return 0; + } +} + +int CacheMgrXbox_MountAsync_Hook(CacheMgrXbox *thisCacheMgr, CacheIDXbox *cacheID, Cache **outCache, void *object) +{ + XDEVICE_DATA deviceData; + int r = CacheMgrXbox_MountAsync(thisCacheMgr, cacheID, outCache, object); + if ((strcmp(cacheID->mContentData.szFileName, "songcache") == 0 || strcmp(cacheID->mContentData.szFileName, "rbdxcache") == 0) && + XContentGetDeviceData(cacheID->mContentData.DeviceID, &deviceData) == 0) + { + RB3E_DEBUG("Song cache '%s' on \"%S\" mounted", cacheID->mContentData.szFileName, deviceData.wszFriendlyName); + memcpy(&lastMountedCache, cacheID, sizeof(CacheIDXbox)); + hasLastMountedCache = 1; + } + return r; +} + +XboxContent *XboxContentConstructHook(XboxContent *thisXboxContent, XCONTENT_CROSS_TITLE_DATA *titleData, int contentIndex, int padNum, char needsMount) +{ + XDEVICE_DATA deviceData; + XboxContent *r = XboxContentConstruct(thisXboxContent, titleData, contentIndex, padNum, needsMount); + if (config.ContentLogging && XContentGetDeviceData(titleData->DeviceID, &deviceData) == 0) + { + RB3E_DEBUG("Content \"%S\" (\"%S\", %08x/%08x/%s) = %s", + thisXboxContent->mTitleData.szDisplayName, + deviceData.wszFriendlyName, + thisXboxContent->mTitleData.dwTitleId, thisXboxContent->mTitleData.dwContentType, thisXboxContent->mTitleData.szFileName, + thisXboxContent->mRoot.buf + ); + } + return r; +} + +void InitContentHooks() +{ + HookFunction(PORT_CACHEMGRXBOX_MOUNTASYNC, &CacheMgrXbox_MountAsync, &CacheMgrXbox_MountAsync_Hook); + HookFunction(PORT_XBOXCONTENT_CONSTRUCTOR, &XboxContentConstruct, &XboxContentConstructHook); +} + +#endif // RB3E_XBOX From 02e352e928db75e192d2be715c8305d7473a9c3a Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Sat, 30 Aug 2025 14:13:03 +0100 Subject: [PATCH 113/123] [chore] housekeeping --- .github/workflows/build.yml | 2 +- BUILDING.md | 2 +- CONTRIBUTING.md | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fdf2764..bd1f147 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -59,7 +59,7 @@ jobs: Xbox: if: github.repository == 'RBEnhanced/RB3Enhanced' - runs-on: windows-2019 + runs-on: windows-2022 timeout-minutes: 10 steps: diff --git a/BUILDING.md b/BUILDING.md index 4cbf580..e240e18 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -2,7 +2,7 @@ RB3Enhanced is a cross-platform mod for both Xbox 360 and Wii. It's designed to be compiled on a Windows system, however both versions are able to compile on -macOS and Linux. +macOS and Linux via Wine. ## Prerequisites diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7f03adb..0276d1d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,8 +33,8 @@ existing features or adding new ones. * Emulators may mask fault behaviour, or return different results compared to a real console, so it's advised to use real hardware if possible. * Let us know what platform you've tested on. -* If possible, try to include port addresses for both Xbox 360, Wii, PS3 - and Bank 8 in your contribution. +* If possible, try to include port addresses for Xbox 360, Wii, PS3 and Bank 8 + in your contribution. * If you can't do this, guard the implementation behind `#ifdef RB3E_{PLATFORM}`. * It's recommended that you enable "Allow contributors to edit code" in your From eb1accb67f507a47074a1fda9517def81aa7841c Mon Sep 17 00:00:00 2001 From: Sulfrix Date: Sat, 13 Sep 2025 11:24:07 -0500 Subject: [PATCH 114/123] [xbox] add keyboard support back into xbox retail (#49) * [xbox] add keyboard support back into xbox retail * clean up TranslateVK --- include/ports_xbox360.h | 1 + include/rb3/Data.h | 2 + source/_functions.c | 1 + source/rb3enhanced.c | 5 ++ source/xbox_keyboard.c | 131 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+) create mode 100644 source/xbox_keyboard.c diff --git a/include/ports_xbox360.h b/include/ports_xbox360.h index 9319722..70ee695 100644 --- a/include/ports_xbox360.h +++ b/include/ports_xbox360.h @@ -149,6 +149,7 @@ #define PORT_DATASETELEM 0x82760b38 // DataSetElem #define PORT_DATAONELEM 0x8275ff50 // DataOnElem #define PORT_DATANODEGETOBJ 0x8274b088 // DataNode::GetObj +#define PORT_DATAARRAYEXECUTE 0x8274d198 // DataArray::Execute #define PORT_DXRND_SUSPEND 0x8273A370 // DxRnd::Suspend #define PORT_XBOXCONTENT_CONSTRUCTOR 0x8251fb40 // XboxContent::__ct #define PORT_CACHEMGRXBOX_MOUNTASYNC 0x827d7b38 // CacheMgrXbox::MountAsync diff --git a/include/rb3/Data.h b/include/rb3/Data.h index 97faccf..52b36b9 100644 --- a/include/rb3/Data.h +++ b/include/rb3/Data.h @@ -82,6 +82,8 @@ extern DataNode *DataSet(DataNode *ret, DataArray *array); extern DataNode *DataSetElem(DataNode *ret, DataArray *array); extern DataNode *DataOnElem(DataNode *ret, DataArray *array); +extern DataNode *DataArrayExecute(DataNode *ret, DataArray *thisArr); + extern void *DataNodeGetObj(DataNode *node); // inlined on 360 diff --git a/source/_functions.c b/source/_functions.c index 6d68d42..0d712fe 100644 --- a/source/_functions.c +++ b/source/_functions.c @@ -112,6 +112,7 @@ RB3E_STUB(HeapInit) RB3E_STUB(ResolvedModuleKeyboard) RB3E_STUB(XboxContentConstruct) RB3E_STUB(CacheMgrXbox_MountAsync) +RB3E_STUB(DataArrayExecute) #ifdef RB3E_WII // Wii-specific functions diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index c063ba1..7ab811a 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -171,6 +171,7 @@ static unsigned int framecount = 0; // STUFF YOU DO HERE WILL **DIRECTLY** IMPACT THE GAME'S FRAMERATE! void HTTP_Server_RunLoop(); void Liveless_Poll(); +void KeyboardPoll(); void RB3E_RunLoop() { if (config.EnableNATPMP) @@ -182,6 +183,7 @@ void RB3E_RunLoop() Liveless_Poll(); if (config.EnableUPnP) UPNP_Poll(); + KeyboardPoll(); #endif #ifdef RB3EDEBUG // print out memory every 5 seconds @@ -385,6 +387,9 @@ void InitialiseFunctions() POKE_B(&BinstreamRead, PORT_BINSTREAMREAD); POKE_B(&BinstreamWriteEndian, PORT_BINSTREAMWRITEENDIAN); POKE_B(&BinstreamReadEndian, PORT_BINSTREAMREADENDIAN); +#ifdef RB3E_XBOX + POKE_B(&DataArrayExecute, PORT_DATAARRAYEXECUTE); +#endif #ifndef RB3E_XBOX POKE_B(&DataRegisterFunc, PORT_DATAREGISTERFUNC); #endif diff --git a/source/xbox_keyboard.c b/source/xbox_keyboard.c new file mode 100644 index 0000000..5bd20b9 --- /dev/null +++ b/source/xbox_keyboard.c @@ -0,0 +1,131 @@ +#ifdef RB3E_XBOX +#include +#include "rb3/Data.h" +#include "ports.h" + +// Reimplementation of DC3's TranslateVK +// This function translates the system XInput VirtualKey value into Harmonix's keycode values +int TranslateVK(DWORD virtualKey, char shift) +{ + // ASCII range + // Honestly not sure why this range is even mapped, at these ranges the Unicode translation should be handling it + if (virtualKey >= 0x41 && virtualKey <= 0x5a) { + if (!shift) { + return virtualKey; + } else { + return tolower(virtualKey); + } + } + + // Function key range + if (virtualKey >= VK_F1 && virtualKey <= VK_F12) { + return virtualKey + 0x121; + } + + // Other keycode mappings + switch (virtualKey) { + case VK_HOME: + return 0x138; + case VK_CAPITAL: + return 0x122; + case VK_BACK: + return 0x08; + case VK_TAB: + return 0x09; + case VK_RETURN: + return 0x0a; + case VK_PAUSE: + return 0x12d; + case VK_ESCAPE: + return 0x12e; + case VK_PRIOR: + return 0x13a; + case VK_NEXT: + return 0x13b; + case VK_END: + return 0x139; + case VK_PRINT: + return 0x12c; + case VK_LEFT: + return 0x140; + case VK_UP: + return 0x142; + case VK_RIGHT: + return 0x141; + case VK_DOWN: + return 0x143; + case VK_INSERT: + return 0x136; + case VK_DELETE: + return 0x137; + case VK_NUMLOCK: + return 0x123; + case VK_SCROLL: + return 0x124; + } + + return virtualKey; +} + + +// Reimplementation of KeyboardPoll from DC3 +void KeyboardPoll() { + XINPUT_KEYSTROKE keystroke; + DWORD keystrokeResult; + static DataNode msgNodes[6] = {0}; + static DataArray keyMsg = {0}; + static DataNode retNode; + Symbol uiSym; + Symbol keySym; + char unicodeChar[4]; + int keyCode; + + keystrokeResult = XInputGetKeystroke(XUSER_INDEX_ANY, 2, &keystroke); + if (keystrokeResult == ERROR_SUCCESS && (keystroke.Flags & XINPUT_KEYSTROKE_KEYUP) == 0) { + WideCharToMultiByte(0, 0, &keystroke.Unicode, 1, unicodeChar, 2, 0x0, 0x0); + if (unicodeChar[0] == '\0' || unicodeChar[0] < ' ' || unicodeChar[0] > '~') { + keyCode = TranslateVK(keystroke.VirtualKey, (keystroke.Flags & XINPUT_KEYSTROKE_SHIFT) > 0); + } else { + keyCode = (int)unicodeChar[0]; + } + + keyMsg.mNodes = (DataNodes *)&msgNodes; + keyMsg.mNodeCount = 6; + keyMsg.mRefCount = 1; + keyMsg.mFile = *(Symbol *)PORT_NULLSYMBOL; + + SymbolConstruct(&uiSym, "ui"); + SymbolConstruct(&keySym, "key"); + + // Construct a command array to do this: + // {ui key $key $shift $ctrl $alt} + + // ui object + msgNodes[0].type = SYMBOL; + msgNodes[0].value.string = uiSym.sym; + + // key handler + msgNodes[1].type = SYMBOL; + msgNodes[1].value.string = keySym.sym; + + // key + msgNodes[2].type = INT_VALUE; + msgNodes[2].value.intVal = keyCode; + + // shift + msgNodes[3].type = INT_VALUE; + msgNodes[3].value.intVal = (keystroke.Flags & XINPUT_KEYSTROKE_SHIFT) > 0; + + // ctrl + msgNodes[4].type = INT_VALUE; + msgNodes[4].value.intVal = (keystroke.Flags & XINPUT_KEYSTROKE_CTRL) > 0; + + // alt + msgNodes[5].type = INT_VALUE; + msgNodes[5].value.intVal = (keystroke.Flags & XINPUT_KEYSTROKE_ALT) > 0; + + DataArrayExecute(&retNode, &keyMsg); + + } +} +#endif \ No newline at end of file From 09070cdd3175cdfe268d57d86f09fe68fccade81 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:41:04 -0500 Subject: [PATCH 115/123] [http] fix only 1 search result with no api key (#50) --- assets/rb3e_index.html | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/assets/rb3e_index.html b/assets/rb3e_index.html index bb50319..3060512 100644 --- a/assets/rb3e_index.html +++ b/assets/rb3e_index.html @@ -487,10 +487,12 @@ // Immediately start fetching album art for each song as it is added const albumArt = document.querySelectorAll('.album-art')[loaded_songs]; - const cacheKey = albumArt.dataset.cacheKey; - if (!albumArtCache[cacheKey]) { - albumArt.src = placeholderArtUrl; // Reset image to placeholder - albumArtQueue.push({ album: song.album, artist: song.artist, imgElement: albumArt, cacheKey: cacheKey, songIndex: loaded_songs }); + if (albumArt && albumArt.dataset && albumArt.dataset.cacheKey) { + const cacheKey = albumArt.dataset.cacheKey; + if (!albumArtCache[cacheKey]) { + albumArt.src = placeholderArtUrl // Reset image to placeholder + albumArtQueue.push({ album: song.album, artist: song.artist, imgElement: albumArt, cacheKey, songIndex: loaded_songs}); + } } loaded_songs++; @@ -621,7 +623,7 @@
- +
From 3ac406bab636997d8ed4dd7e3878ccba0b9cdaf6 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 28 Sep 2025 13:25:13 -0500 Subject: [PATCH 116/123] split ports to proper place --- include/ports_wii.h | 3 +++ include/ports_xbox360.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/include/ports_wii.h b/include/ports_wii.h index 5a859e4..2131c48 100644 --- a/include/ports_wii.h +++ b/include/ports_wii.h @@ -116,6 +116,9 @@ #define PORT_SONGSORTMGRGETSORT 0x80281b20 // SongSortMgr::GetSort #define PORT_RNDMATSETDIFFUSETEX 0x8025ab90 // RndMat::SetDiffuseTex #define PORT_DYNAMICTEX_CT 0x80292a70 // DynamicTex::__ct +#define PORT_DYNAMICTEX_DT 0x80292bcc // DynamicTex::__dt +#define PORT_MUSICLIBRARYONENTER 0x8022dd24 // MusicLibrary::OnEnter +#define PORT_MUSICLIBRARYONUNLOAD 0x8022e87c // MusicLibrary::OnExit #define PORT_BINSTREAMWRITE 0x80342f48 // BinStream::Write #define PORT_BINSTREAMREAD 0x80342e7c // BinStream::Read #define PORT_BINSTREAMREADENDIAN 0x803430bc // BinStream::ReadEndian diff --git a/include/ports_xbox360.h b/include/ports_xbox360.h index 0cb39e7..4dc442b 100644 --- a/include/ports_xbox360.h +++ b/include/ports_xbox360.h @@ -160,7 +160,10 @@ #define PORT_ADDMULTIGEM 0x827930d8 // GameGemDB::AddMultiGem #define PORT_SONGSORTMGRGETSORT 0x82595ff8 // SongSortMgr::GetSort #define PORT_DYNAMICTEX_CT 0x825f2318 // DynamicTex::__ct +#define PORT_DYNAMICTEX_DT 0x825f2210 // DynamicTex::__dt #define PORT_RNDMATSETDIFFUSETEX 0x8238b130 // RndMat::SetDiffuseTex +#define PORT_MUSICLIBRARYONENTER 0x82542238 // MusicLibrary::OnEnter +#define PORT_MUSICLIBRARYONUNLOAD 0x82540450 // MusicLibrary::OnExit #define PORT_BINSTREAMWRITE 0x827c4f58 // BinStream::Write #define PORT_BINSTREAMREAD 0x827c4ea8 // BinStream::Read #define PORT_BINSTREAMREADENDIAN 0x827c5058 // BinStream::ReadEndian From 8a9b6c5325b027bf9fcc4bb6880bf11ce1a5d0b5 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 28 Sep 2025 13:35:09 -0500 Subject: [PATCH 117/123] bank 8 ports --- include/ports_wii_bank8.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/include/ports_wii_bank8.h b/include/ports_wii_bank8.h index bd1b520..64a3271 100644 --- a/include/ports_wii_bank8.h +++ b/include/ports_wii_bank8.h @@ -117,6 +117,9 @@ #define PORT_ADDMULTIGEM 0x806284e0 // GameGemDB::AddMultiGem #define PORT_SONGSORTMGRGETSORT 0x8037a510 // SongSortMgr::GetSort #define PORT_DYNAMICTEX_CT 0x80393bd0 // DynamicTex::__ct +#define PORT_DYNAMICTEX_DT 0x80393ef0 // DynamicTex::__dt +#define PORT_MUSICLIBRARYONENTER 0x802fa750 // MusicLibrary::OnEnter +#define PORT_MUSICLIBRARYONUNLOAD 0x802fb2a0 // MusicLibrary::OnExit #define PORT_RNDMATSETDIFFUSETEX 0x808d36b0 // RndMat::SetDiffuseTex #define PORT_BINSTREAMWRITE 0x804892b0 // BinStream::Write #define PORT_BINSTREAMREAD 0x80489140 // BinStream::Read From fedb7d25615865f32bebc80690fa28c76fa308d3 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 28 Sep 2025 13:35:18 -0500 Subject: [PATCH 118/123] song_select rawfile --- .../gen/list_song_select_browser.milo_xbox | Bin 0 -> 364849 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/rawfiles/xbox/ui/resource/list/gen/list_song_select_browser.milo_xbox diff --git a/assets/rawfiles/xbox/ui/resource/list/gen/list_song_select_browser.milo_xbox b/assets/rawfiles/xbox/ui/resource/list/gen/list_song_select_browser.milo_xbox new file mode 100644 index 0000000000000000000000000000000000000000..8cce17362b9c2218b1e25c76ddca6b60f75d39f4 GIT binary patch literal 364849 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 literal 0 HcmV?d00001 From 1587de78104bca08576b4f28b068fd88b8599d12 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 28 Sep 2025 13:49:57 -0500 Subject: [PATCH 119/123] keep alt style for artist --- source/LocaleHooks.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/source/LocaleHooks.c b/source/LocaleHooks.c index 8b3c3ca..e997ccf 100644 --- a/source/LocaleHooks.c +++ b/source/LocaleHooks.c @@ -73,9 +73,12 @@ char *LocalizeHook(int thisLocale, Symbol sym, int fail) char newLocaleName[0x50]; Symbol newLocale; char *original; + + // No longer needed with origin rewrite // game origin icons relies on using different formatting for the song/artist name - if (config.GameOriginIcons == 1 && sym.sym != NULL && strcmp(sym.sym, "song_artist_fmt") == 0) - return "%s %s"; + // if (config.GameOriginIcons == 1 && sym.sym != NULL && strcmp(sym.sym, "song_artist_fmt") == 0) + // return "%s %s"; + // if the string starts with message_motd_ (but isn't message_motd), it's our motd if (memcmp(sym.sym, "message_motd_", 13) == 0) { From 332546b9f58cc2e8459a2ca74083d77eca25d739 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 28 Sep 2025 15:22:07 -0500 Subject: [PATCH 120/123] luna's updated milo Co-Authored-By: luna <37005649+lunalawl@users.noreply.github.com> Co-Authored-By: Sulfrix <22404294+Sulfrix@users.noreply.github.com> --- .../gen/list_song_select_browser.milo_xbox | Bin 364849 -> 364853 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/rawfiles/xbox/ui/resource/list/gen/list_song_select_browser.milo_xbox b/assets/rawfiles/xbox/ui/resource/list/gen/list_song_select_browser.milo_xbox index 8cce17362b9c2218b1e25c76ddca6b60f75d39f4..3630e0b2a0f146122ddf3976f87ad7ce484bbb54 100644 GIT binary patch delta 72 zcmdnENNnpOu?Z5)3c8FN6+f|0G+=8M=4cn@U<6_&AZ7+)mhHkEtfwocmvpe|O`l`K ZDm2l+WV>G@>nC2Os}9?p`dQyI0ssNB77G9X delta 64 zcmdnGNNnRGu?Z5)GP;Z#6+f|0G+=8M=4cn@U<6_&AZ7+)mhHkEtfwocn{=}lY!7N= Q{lv?3ongCMKkIu&0AVx}TL1t6 From 8664c16964a9929566a803b52a46112c7cf957a1 Mon Sep 17 00:00:00 2001 From: jnack <55568980+jnackmclain@users.noreply.github.com> Date: Sun, 28 Sep 2025 18:19:50 -0500 Subject: [PATCH 121/123] attempt to fix song metadata struct genre offset (#46) --- include/rb3/SongMetadata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rb3/SongMetadata.h b/include/rb3/SongMetadata.h index 05aa6a2..9cfa48a 100644 --- a/include/rb3/SongMetadata.h +++ b/include/rb3/SongMetadata.h @@ -24,7 +24,7 @@ typedef struct _SongMetadata #ifdef RB3E_WII char unknown6[0x10]; #else - char unknown6[0x14]; + char unknown6[0x10]; // hotfixed from 0x18, then 0x14? what is happening #endif Symbol genre; int animTempo; From 1bf9705e8abb0c9fd4c26cb438b3a3a1a8386412 Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Mon, 29 Sep 2025 01:51:01 +0100 Subject: [PATCH 122/123] [dta] add several DTA functions and RB3E_HAS_VERSION define rb3e_api_version, rb3e_build_tag, rb3e_commit and rb3e_local_ip --- include/GlobalSymbols.h | 8 ++++ source/DTAFunctions.c | 86 +++++++++++++++++++++++++++++++++++------ source/GlobalSymbols.c | 8 ++++ source/rb3enhanced.c | 3 +- 4 files changed, 92 insertions(+), 13 deletions(-) diff --git a/include/GlobalSymbols.h b/include/GlobalSymbols.h index 5a273f8..b1196cb 100644 --- a/include/GlobalSymbols.h +++ b/include/GlobalSymbols.h @@ -9,8 +9,15 @@ void InitGlobalSymbols(); typedef struct _GlobalSymbols { + // rb3e version + Symbol buildtag; + Symbol commit; + // dta functions Symbol print_debug; + Symbol rb3e_api_version; + Symbol rb3e_build_tag; + Symbol rb3e_commit; Symbol rb3e_change_music_speed; Symbol rb3e_change_track_speed; Symbol rb3e_get_music_speed; @@ -26,6 +33,7 @@ typedef struct _GlobalSymbols Symbol rb3e_get_origin; Symbol rb3e_get_genre; Symbol rb3e_delete_songcache; + Symbol rb3e_local_ip; // modifiers Symbol forceHopos; diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index ee2d577..c60a9ea 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -14,8 +14,40 @@ #include "rb3/SongMetadata.h" #include "rb3/BandSongMgr.h" #include "rb3enhanced.h" +#include "net.h" +#include "version.h" -DataNode *PrintToDebugger(DataNode *node, DataArray *args) +DataNode *DTAGetRB3EBuildTag(DataNode *node, DataArray *args) +{ + node->type = SYMBOL; + node->value.string = globalSymbols.buildtag.sym; + return node; +} + +DataNode *DTAGetRB3ECommit(DataNode *node, DataArray *args) +{ + node->type = SYMBOL; + node->value.string = globalSymbols.commit.sym; + return node; +} + +DataNode *DTAGetAPIVersion(DataNode *node, DataArray *args) +{ + /* + This API version should be incremented every time a DTA function gets + added, has functionality modified, or removed, as well as every time a + major feature is added/removed from RB3E or a new major version is + released. + + API version history: + 0 - 29/09/2025 - Initial API version. + */ + node->type = INT_VALUE; + node->value.intVal = 0; + return node; +} + +DataNode *DTAPrintToDebugger(DataNode *node, DataArray *args) { DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); switch (firstArg->type) @@ -38,21 +70,21 @@ DataNode *PrintToDebugger(DataNode *node, DataArray *args) return node; } -// Get configuration values -DataNode *GetMusicSpeed(DataNode *node, DataArray *args) +DataNode *DTAGetMusicSpeed(DataNode *node, DataArray *args) { node->type = FLOAT_VALUE; node->value.floatVal = config.SongSpeedMultiplier; return node; } -DataNode *GetTrackSpeed(DataNode *node, DataArray *args) + +DataNode *DTAGetTrackSpeed(DataNode *node, DataArray *args) { node->type = FLOAT_VALUE; node->value.floatVal = config.TrackSpeedMultiplier; return node; } -// Set configuration values -DataNode *ChangeMusicSpeed(DataNode *node, DataArray *args) + +DataNode *DTAChangeMusicSpeed(DataNode *node, DataArray *args) { DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); switch (firstArg->type) @@ -73,7 +105,8 @@ DataNode *ChangeMusicSpeed(DataNode *node, DataArray *args) node->value.intVal = 1; return node; } -DataNode *ChangeTrackSpeed(DataNode *node, DataArray *args) + +DataNode *DTAChangeTrackSpeed(DataNode *node, DataArray *args) { DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); switch (firstArg->type) @@ -94,6 +127,7 @@ DataNode *ChangeTrackSpeed(DataNode *node, DataArray *args) node->value.intVal = 1; return node; } + DataNode *DTASetVenue(DataNode *node, DataArray *args) { DataNode *firstArg = DataNodeEvaluate(&args->mNodes->n[1]); @@ -294,6 +328,30 @@ DataNode *DTADeleteSongCache(DataNode *node, DataArray *args) return node; } +DataNode *DTALocalIP(DataNode *node, DataArray *args) +{ + Symbol noIpSym; + Symbol ipSym; + unsigned int localIP = RB3E_GetInternalIP(); + if (localIP == 0) + { + SymbolConstruct(&noIpSym, "(not connected)"); + node->type = SYMBOL; + node->value.string = noIpSym.sym; + return node; + } + else + { + char ipBuffer[24]; + unsigned char *ipParts = &localIP; + sprintf(ipBuffer, "%i.%i.%i.%i", ipParts[0], ipParts[1], ipParts[2], ipParts[3]); + SymbolConstruct(&ipSym, ipBuffer); + node->type = SYMBOL; + node->value.string = ipSym.sym; + return node; + } +} + #ifdef RB3E_XBOX // this function is inlined on the Xbox version, so we re-create it void DataRegisterFunc(Symbol name, DTAFunction_t func) @@ -304,11 +362,14 @@ void DataRegisterFunc(Symbol name, DTAFunction_t func) void AddDTAFunctions() { - DataRegisterFunc(globalSymbols.print_debug, PrintToDebugger); - DataRegisterFunc(globalSymbols.rb3e_change_music_speed, ChangeMusicSpeed); - DataRegisterFunc(globalSymbols.rb3e_change_track_speed, ChangeTrackSpeed); - DataRegisterFunc(globalSymbols.rb3e_get_music_speed, GetMusicSpeed); - DataRegisterFunc(globalSymbols.rb3e_get_track_speed, GetTrackSpeed); + DataRegisterFunc(globalSymbols.print_debug, DTAPrintToDebugger); + DataRegisterFunc(globalSymbols.rb3e_api_version, DTAGetAPIVersion); + DataRegisterFunc(globalSymbols.rb3e_build_tag, DTAGetRB3EBuildTag); + DataRegisterFunc(globalSymbols.rb3e_commit, DTAGetRB3ECommit); + DataRegisterFunc(globalSymbols.rb3e_change_music_speed, DTAChangeMusicSpeed); + DataRegisterFunc(globalSymbols.rb3e_change_track_speed, DTAChangeTrackSpeed); + DataRegisterFunc(globalSymbols.rb3e_get_music_speed, DTAGetMusicSpeed); + DataRegisterFunc(globalSymbols.rb3e_get_track_speed, DTAGetTrackSpeed); DataRegisterFunc(globalSymbols.rb3e_set_venue, DTASetVenue); DataRegisterFunc(globalSymbols.rb3e_is_emulator, DTAIsEmulator); DataRegisterFunc(globalSymbols.rb3e_relaunch_game, DTARelaunchGame); @@ -320,5 +381,6 @@ void AddDTAFunctions() DataRegisterFunc(globalSymbols.rb3e_get_origin, DTAGetOrigin); DataRegisterFunc(globalSymbols.rb3e_get_genre, DTAGetGenre); DataRegisterFunc(globalSymbols.rb3e_delete_songcache, DTADeleteSongCache); + DataRegisterFunc(globalSymbols.rb3e_local_ip, DTALocalIP); RB3E_MSG("Added DTA functions!", NULL); } diff --git a/source/GlobalSymbols.c b/source/GlobalSymbols.c index a18e7ee..af6f8fb 100644 --- a/source/GlobalSymbols.c +++ b/source/GlobalSymbols.c @@ -9,6 +9,7 @@ #include "rb3/Symbol.h" #include "ports.h" #include "GlobalSymbols.h" +#include "version.h" GlobalSymbols globalSymbols; static char globalSymbolsInitialised = 0; @@ -26,7 +27,13 @@ void InitGlobalSymbols() memset(&globalSymbols, 0, sizeof(globalSymbols)); + SymbolConstruct(&globalSymbols.buildtag, RB3E_BUILDTAG); + SymbolConstruct(&globalSymbols.commit, RB3E_BUILDCOMMIT); + SymbolConstruct(&globalSymbols.print_debug, "print_debug"); + SymbolConstruct(&globalSymbols.rb3e_api_version, "rb3e_api_version"); + SymbolConstruct(&globalSymbols.rb3e_build_tag, "rb3e_build_tag"); + SymbolConstruct(&globalSymbols.rb3e_commit, "rb3e_commit"); SymbolConstruct(&globalSymbols.rb3e_change_music_speed, "rb3e_change_music_speed"); SymbolConstruct(&globalSymbols.rb3e_change_track_speed, "rb3e_change_track_speed"); SymbolConstruct(&globalSymbols.rb3e_get_music_speed, "rb3e_get_music_speed"); @@ -42,6 +49,7 @@ void InitGlobalSymbols() SymbolConstruct(&globalSymbols.rb3e_get_origin, "rb3e_get_origin"); SymbolConstruct(&globalSymbols.rb3e_get_genre, "rb3e_get_genre"); SymbolConstruct(&globalSymbols.rb3e_delete_songcache, "rb3e_delete_songcache"); + SymbolConstruct(&globalSymbols.rb3e_local_ip, "rb3e_local_ip"); SymbolConstruct(&globalSymbols.blackBackground, "mod_black_background"); SymbolConstruct(&globalSymbols.colorShuffle, "mod_color_shuffle"); diff --git a/source/rb3enhanced.c b/source/rb3enhanced.c index 7ab811a..c907162 100644 --- a/source/rb3enhanced.c +++ b/source/rb3enhanced.c @@ -18,7 +18,8 @@ char *DefinesHook(char *string_define, int always_null) case 0: return "RB3E"; case 1: - // might not be working? + return "RB3E_HAS_VERSION"; + case 2: if (RB3E_IsEmulator()) return "RB3E_EMULATOR"; return NULL; From e472129aa0371faaa65acbbf84d7bd4650ab5c6b Mon Sep 17 00:00:00 2001 From: InvoxiPlayGames Date: Mon, 29 Sep 2025 01:59:31 +0100 Subject: [PATCH 123/123] [bugfix] bixby, fix the wii compiler error --- source/DTAFunctions.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/DTAFunctions.c b/source/DTAFunctions.c index c60a9ea..0558093 100644 --- a/source/DTAFunctions.c +++ b/source/DTAFunctions.c @@ -343,7 +343,7 @@ DataNode *DTALocalIP(DataNode *node, DataArray *args) else { char ipBuffer[24]; - unsigned char *ipParts = &localIP; + unsigned char *ipParts = (unsigned char *)&localIP; sprintf(ipBuffer, "%i.%i.%i.%i", ipParts[0], ipParts[1], ipParts[2], ipParts[3]); SymbolConstruct(&ipSym, ipBuffer); node->type = SYMBOL;