From 429f0e3327c78812cae48a684c6fadf8cd80ecac Mon Sep 17 00:00:00 2001 From: Terry Hearst Date: Sat, 18 May 2024 01:38:41 -0400 Subject: [PATCH 1/5] Autosplitter v2 - memory based this time --- engine/source/autosplitter/autosplitter.cpp | 58 ++++++++++++++++++++- engine/source/autosplitter/autosplitter.h | 14 ++++- game/marble/client/scripts/game.cs | 3 ++ game/marble/client/scripts/playGui.cs | 1 + game/marble/client/ui/LevelPreviewGui.gui | 2 + game/marble/server/scripts/easter.cs | 1 + 6 files changed, 76 insertions(+), 3 deletions(-) diff --git a/engine/source/autosplitter/autosplitter.cpp b/engine/source/autosplitter/autosplitter.cpp index 0bce86ea..2ed9b2b5 100644 --- a/engine/source/autosplitter/autosplitter.cpp +++ b/engine/source/autosplitter/autosplitter.cpp @@ -1,6 +1,7 @@ #include #include "autosplitter.h" +#include "console/console.h" #include "platform/platform.h" @@ -33,13 +34,26 @@ Autosplitter *Autosplitter::get() Autosplitter::Autosplitter() { + // Initialize AutosplitterData memory chunk + dMemset(&data, 0, sizeof(data)); + sprintf(data.signature, "OMBU_ASR_"); + // We add the "abcdef" procedurally to guarantee that the sigscan only finds this buffer, rather than potentially + // finding a full "OMBU_ASR_abcdef" string somewhere else in memory + for (int i = 0; i < 6; i++) + { + data.signature[i + 9] = 'a' + i; + } + data.currentLevel = -1; + // The rest is all zeros thanks to the memset, which is what we want. + + // Initialize autosplitter file mActive = false; mFilename = Platform::getPrefsPath(AUTOSPLITTER_FILE_NAME); mFile.open(mFilename, std::ios_base::app); if (!mFile.is_open()) { Con::errorf("Failed to open autosplitter file %s.", mFilename.c_str()); - Con::errorf("Autosplitter is disabled."); + Con::errorf("Autosplitter file is disabled. Autosplitter memory will still work."); return; } Con::printf("Autosplitter Initialized to file %s", mFilename.c_str()); @@ -70,3 +84,45 @@ ConsoleFunction(sendAutosplitterData, void, 2, 2, "") autosplitter->sendData(argv[1]); } + +ConsoleFunction(autosplitterSetLevel, void, 2, 2, "autosplitterSetLevel(level)") +{ + Autosplitter *autosplitter = Autosplitter::get(); + autosplitter->data.currentLevel = atoi(argv[1]); +} + +ConsoleFunction(autosplitterSetIsLoading, void, 2, 2, "autosplitterSetIsLoading(isLoading)") +{ + Autosplitter *autosplitter = Autosplitter::get(); + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0")) + autosplitter->data.isLoading = 0; + else + autosplitter->data.isLoading = 1; +} + +ConsoleFunction(autosplitterSetLevelStarted, void, 2, 2, "autosplitterSetLevelStarted(levelStarted)") +{ + Autosplitter *autosplitter = Autosplitter::get(); + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0")) + autosplitter->data.levelStarted = 0; + else + autosplitter->data.levelStarted = 1; +} + +ConsoleFunction(autosplitterSetLevelFinished, void, 2, 2, "autosplitterSetLevelFinished(levelFinished)") +{ + Autosplitter *autosplitter = Autosplitter::get(); + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0")) + autosplitter->data.levelFinished = 0; + else + autosplitter->data.levelFinished = 1; +} + +ConsoleFunction(autosplitterSetEggFound, void, 2, 2, "autosplitterSetEggFound(eggFound)") +{ + Autosplitter *autosplitter = Autosplitter::get(); + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0")) + autosplitter->data.eggFound = 0; + else + autosplitter->data.eggFound = 1; +} diff --git a/engine/source/autosplitter/autosplitter.h b/engine/source/autosplitter/autosplitter.h index 6b67873b..fcfedd57 100644 --- a/engine/source/autosplitter/autosplitter.h +++ b/engine/source/autosplitter/autosplitter.h @@ -1,14 +1,23 @@ #ifndef _AUTOSPLITTER_H_ #define _AUTOSPLITTER_H_ -#include #include -#include "console/console.h" +#include "platform/types.h" constexpr const char *AUTOSPLITTER_FILE_NAME = "autosplitter.txt"; constexpr U32 AUTOSPLITTER_BUF_SIZE = 512; +// This data can be scanned for in memory and used to drive autosplitters more precisely than via a log file +struct AutosplitterData { + char signature[16]; // Header set to "OMBU_ASR_abcdef", entry point for memory scan + S32 currentLevel; // The level index of the current loading/loaded level + U8 isLoading; // When a loading screen is active + U8 levelStarted; // When a level is started from the menu screen + U8 levelFinished; // When the finish is entered + U8 eggFound; // When an easter egg is collected +}; + class Autosplitter { public: @@ -17,6 +26,7 @@ class Autosplitter static Autosplitter *get(); bool isActive() { return mActive; } void sendData(const char *data); + AutosplitterData data; private: Autosplitter(); ~Autosplitter(); diff --git a/game/marble/client/scripts/game.cs b/game/marble/client/scripts/game.cs index d8ae9c81..27c8843a 100644 --- a/game/marble/client/scripts/game.cs +++ b/game/marble/client/scripts/game.cs @@ -799,6 +799,7 @@ function clientCmdSetGameState(%state, %data) // Tell autosplitter we finished the level XBLivePresenceStopTimer(); + autosplitterSetLevelFinished(true); sendAutosplitterData("finish" SPC GameMissionInfo.getCurrentMission().level); } else @@ -860,6 +861,8 @@ function clientCmdSetGameState(%state, %data) // read the pair of leaderboards XBLiveReadStats($Leaderboard::SPOverall, %mission.level, "", true, true); } + autosplitterSetLevelFinished(false); + autosplitterSetEggFound(false); } // Check here to see if we need to pop the upsell diff --git a/game/marble/client/scripts/playGui.cs b/game/marble/client/scripts/playGui.cs index 2f96c39f..d34b1e7b 100644 --- a/game/marble/client/scripts/playGui.cs +++ b/game/marble/client/scripts/playGui.cs @@ -102,6 +102,7 @@ UpsellGui.displayPDLCUpsell = %isFreeLevel ? false : !%hasLevel; } + autosplitterSetIsLoading(false); sendAutosplitterData("loading finished"); } diff --git a/game/marble/client/ui/LevelPreviewGui.gui b/game/marble/client/ui/LevelPreviewGui.gui index 0ecfc5ee..44f15ff9 100644 --- a/game/marble/client/ui/LevelPreviewGui.gui +++ b/game/marble/client/ui/LevelPreviewGui.gui @@ -230,6 +230,8 @@ function levelPreviewGui::onA() loadMission(GameMissionInfo.getCurrentMission().file, true); // Tell autosplitter to start + autosplitterSetIsLoading(false); + autosplitterSetLevel(GameMissionInfo.getCurrentMission().level); sendAutosplitterData("start" SPC GameMissionInfo.getCurrentMission().level); sendAutosplitterData("loading started"); } diff --git a/game/marble/server/scripts/easter.cs b/game/marble/server/scripts/easter.cs index 186d6980..962d1f8f 100644 --- a/game/marble/server/scripts/easter.cs +++ b/game/marble/server/scripts/easter.cs @@ -65,6 +65,7 @@ function clientCmdOnEasterEggPickup( %index ) return; } + autosplitterSetEggFound(true); sendAutosplitterData("egg" SPC %index); if( hasFoundEgg( %index ) ) From a405c8053ce12f3ea9f9e559e76165d0abcecebc Mon Sep 17 00:00:00 2001 From: Terry Hearst Date: Sat, 18 May 2024 16:15:45 -0400 Subject: [PATCH 2/5] Typo fixes --- engine/source/autosplitter/autosplitter.cpp | 8 ++++---- game/marble/client/ui/LevelPreviewGui.gui | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/engine/source/autosplitter/autosplitter.cpp b/engine/source/autosplitter/autosplitter.cpp index 2ed9b2b5..3aacade5 100644 --- a/engine/source/autosplitter/autosplitter.cpp +++ b/engine/source/autosplitter/autosplitter.cpp @@ -94,7 +94,7 @@ ConsoleFunction(autosplitterSetLevel, void, 2, 2, "autosplitterSetLevel(level)") ConsoleFunction(autosplitterSetIsLoading, void, 2, 2, "autosplitterSetIsLoading(isLoading)") { Autosplitter *autosplitter = Autosplitter::get(); - if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0")) + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) autosplitter->data.isLoading = 0; else autosplitter->data.isLoading = 1; @@ -103,7 +103,7 @@ ConsoleFunction(autosplitterSetIsLoading, void, 2, 2, "autosplitterSetIsLoading( ConsoleFunction(autosplitterSetLevelStarted, void, 2, 2, "autosplitterSetLevelStarted(levelStarted)") { Autosplitter *autosplitter = Autosplitter::get(); - if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0")) + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) autosplitter->data.levelStarted = 0; else autosplitter->data.levelStarted = 1; @@ -112,7 +112,7 @@ ConsoleFunction(autosplitterSetLevelStarted, void, 2, 2, "autosplitterSetLevelSt ConsoleFunction(autosplitterSetLevelFinished, void, 2, 2, "autosplitterSetLevelFinished(levelFinished)") { Autosplitter *autosplitter = Autosplitter::get(); - if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0")) + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) autosplitter->data.levelFinished = 0; else autosplitter->data.levelFinished = 1; @@ -121,7 +121,7 @@ ConsoleFunction(autosplitterSetLevelFinished, void, 2, 2, "autosplitterSetLevelF ConsoleFunction(autosplitterSetEggFound, void, 2, 2, "autosplitterSetEggFound(eggFound)") { Autosplitter *autosplitter = Autosplitter::get(); - if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0")) + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) autosplitter->data.eggFound = 0; else autosplitter->data.eggFound = 1; diff --git a/game/marble/client/ui/LevelPreviewGui.gui b/game/marble/client/ui/LevelPreviewGui.gui index 44f15ff9..385e96be 100644 --- a/game/marble/client/ui/LevelPreviewGui.gui +++ b/game/marble/client/ui/LevelPreviewGui.gui @@ -230,8 +230,9 @@ function levelPreviewGui::onA() loadMission(GameMissionInfo.getCurrentMission().file, true); // Tell autosplitter to start - autosplitterSetIsLoading(false); + autosplitterSetIsLoading(true); autosplitterSetLevel(GameMissionInfo.getCurrentMission().level); + autosplitterSetLevelStarted(true); sendAutosplitterData("start" SPC GameMissionInfo.getCurrentMission().level); sendAutosplitterData("loading started"); } From 2dbd0914f2a3f03add910cffd072d59cc3cd47e7 Mon Sep 17 00:00:00 2001 From: Terry Hearst Date: Mon, 20 May 2024 23:04:10 -0400 Subject: [PATCH 3/5] Unset level started variable Whoops. This fixes the autosplitter only auto-starting once and then you gotta restart the game --- game/marble/client/scripts/playGui.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/game/marble/client/scripts/playGui.cs b/game/marble/client/scripts/playGui.cs index d34b1e7b..f72e5a9d 100644 --- a/game/marble/client/scripts/playGui.cs +++ b/game/marble/client/scripts/playGui.cs @@ -103,6 +103,7 @@ } autosplitterSetIsLoading(false); + autosplitterSetLevelStarted(false); sendAutosplitterData("loading finished"); } From aa999f3076d5589f1194dfee57648ebea50abd22 Mon Sep 17 00:00:00 2001 From: Terry Hearst Date: Thu, 19 Jun 2025 00:44:43 -0400 Subject: [PATCH 4/5] Implement auto reset variable --- engine/source/autosplitter/autosplitter.cpp | 9 +++++++++ engine/source/autosplitter/autosplitter.h | 1 + game/marble/client/scripts/default.bind.cs | 2 ++ game/marble/client/ui/LevelPreviewGui.gui | 1 + 4 files changed, 13 insertions(+) diff --git a/engine/source/autosplitter/autosplitter.cpp b/engine/source/autosplitter/autosplitter.cpp index 3aacade5..389f070e 100644 --- a/engine/source/autosplitter/autosplitter.cpp +++ b/engine/source/autosplitter/autosplitter.cpp @@ -126,3 +126,12 @@ ConsoleFunction(autosplitterSetEggFound, void, 2, 2, "autosplitterSetEggFound(eg else autosplitter->data.eggFound = 1; } + +ConsoleFunction(autosplitterSetQuitToMenu, void, 2, 2, "autosplitterSetQuitToMenu(quitToMenu)") +{ + Autosplitter *autosplitter = Autosplitter::get(); + if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) + autosplitter->data.quitToMenu = 0; + else + autosplitter->data.quitToMenu = 1; +} diff --git a/engine/source/autosplitter/autosplitter.h b/engine/source/autosplitter/autosplitter.h index fcfedd57..572bb762 100644 --- a/engine/source/autosplitter/autosplitter.h +++ b/engine/source/autosplitter/autosplitter.h @@ -16,6 +16,7 @@ struct AutosplitterData { U8 levelStarted; // When a level is started from the menu screen U8 levelFinished; // When the finish is entered U8 eggFound; // When an easter egg is collected + U8 quitToMenu; // When the player quits to menu }; class Autosplitter diff --git a/game/marble/client/scripts/default.bind.cs b/game/marble/client/scripts/default.bind.cs index ea10bbfb..ad5ad471 100644 --- a/game/marble/client/scripts/default.bind.cs +++ b/game/marble/client/scripts/default.bind.cs @@ -106,6 +106,8 @@ function pauseToggle(%defaultItem) { $Client::willfullDisconnect = true; + autosplitterSetQuitToMenu(true); + %killMission = MissionLoadingGui.isAwake(); // if we are hosting a multiplayer server, we just re-enter preview mode // without disconnecting diff --git a/game/marble/client/ui/LevelPreviewGui.gui b/game/marble/client/ui/LevelPreviewGui.gui index 385e96be..9e732f10 100644 --- a/game/marble/client/ui/LevelPreviewGui.gui +++ b/game/marble/client/ui/LevelPreviewGui.gui @@ -233,6 +233,7 @@ function levelPreviewGui::onA() autosplitterSetIsLoading(true); autosplitterSetLevel(GameMissionInfo.getCurrentMission().level); autosplitterSetLevelStarted(true); + autosplitterSetQuitToMenu(false); sendAutosplitterData("start" SPC GameMissionInfo.getCurrentMission().level); sendAutosplitterData("loading started"); } From 680ffe1b0d8a50a2428316a1504be30d41385a31 Mon Sep 17 00:00:00 2001 From: Terry Hearst Date: Sun, 13 Jul 2025 00:49:03 -0400 Subject: [PATCH 5/5] Combine flags into single flags variable --- engine/source/autosplitter/autosplitter.cpp | 20 ++++++++++---------- engine/source/autosplitter/autosplitter.h | 14 +++++++++----- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/engine/source/autosplitter/autosplitter.cpp b/engine/source/autosplitter/autosplitter.cpp index 389f070e..8fc40161 100644 --- a/engine/source/autosplitter/autosplitter.cpp +++ b/engine/source/autosplitter/autosplitter.cpp @@ -95,43 +95,43 @@ ConsoleFunction(autosplitterSetIsLoading, void, 2, 2, "autosplitterSetIsLoading( { Autosplitter *autosplitter = Autosplitter::get(); if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) - autosplitter->data.isLoading = 0; + autosplitter->data.flags &= ~FLAG_IS_LOADING; else - autosplitter->data.isLoading = 1; + autosplitter->data.flags |= FLAG_IS_LOADING; } ConsoleFunction(autosplitterSetLevelStarted, void, 2, 2, "autosplitterSetLevelStarted(levelStarted)") { Autosplitter *autosplitter = Autosplitter::get(); if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) - autosplitter->data.levelStarted = 0; + autosplitter->data.flags &= ~FLAG_LEVEL_STARTED; else - autosplitter->data.levelStarted = 1; + autosplitter->data.flags |= FLAG_LEVEL_STARTED; } ConsoleFunction(autosplitterSetLevelFinished, void, 2, 2, "autosplitterSetLevelFinished(levelFinished)") { Autosplitter *autosplitter = Autosplitter::get(); if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) - autosplitter->data.levelFinished = 0; + autosplitter->data.flags &= ~FLAG_LEVEL_FINISHED; else - autosplitter->data.levelFinished = 1; + autosplitter->data.flags |= FLAG_LEVEL_FINISHED; } ConsoleFunction(autosplitterSetEggFound, void, 2, 2, "autosplitterSetEggFound(eggFound)") { Autosplitter *autosplitter = Autosplitter::get(); if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) - autosplitter->data.eggFound = 0; + autosplitter->data.flags &= ~FLAG_EGG_FOUND; else - autosplitter->data.eggFound = 1; + autosplitter->data.flags |= FLAG_EGG_FOUND; } ConsoleFunction(autosplitterSetQuitToMenu, void, 2, 2, "autosplitterSetQuitToMenu(quitToMenu)") { Autosplitter *autosplitter = Autosplitter::get(); if (dStrcmp(argv[1], "false") == 0 || dStrcmp(argv[1], "0") == 0) - autosplitter->data.quitToMenu = 0; + autosplitter->data.flags &= ~FLAG_QUIT_TO_MENU; else - autosplitter->data.quitToMenu = 1; + autosplitter->data.flags |= FLAG_QUIT_TO_MENU; } diff --git a/engine/source/autosplitter/autosplitter.h b/engine/source/autosplitter/autosplitter.h index 572bb762..dcc67fc8 100644 --- a/engine/source/autosplitter/autosplitter.h +++ b/engine/source/autosplitter/autosplitter.h @@ -12,11 +12,15 @@ constexpr U32 AUTOSPLITTER_BUF_SIZE = 512; struct AutosplitterData { char signature[16]; // Header set to "OMBU_ASR_abcdef", entry point for memory scan S32 currentLevel; // The level index of the current loading/loaded level - U8 isLoading; // When a loading screen is active - U8 levelStarted; // When a level is started from the menu screen - U8 levelFinished; // When the finish is entered - U8 eggFound; // When an easter egg is collected - U8 quitToMenu; // When the player quits to menu + U32 flags; // Boolean flags. 32 is probably overkill but is good for future proofing +}; + +enum { + FLAG_IS_LOADING = (1 << 0), + FLAG_LEVEL_STARTED = (1 << 1), + FLAG_LEVEL_FINISHED = (1 << 2), + FLAG_EGG_FOUND = (1 << 3), + FLAG_QUIT_TO_MENU = (1 << 4), }; class Autosplitter