diff --git a/UnleashedRecomp/api/CSD/Manager/csdmNode.h b/UnleashedRecomp/api/CSD/Manager/csdmNode.h index e4d6b53..3c87642 100644 --- a/UnleashedRecomp/api/CSD/Manager/csdmNode.h +++ b/UnleashedRecomp/api/CSD/Manager/csdmNode.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include namespace Chao::CSD { diff --git a/UnleashedRecomp/api/Hedgehog/Math/Vector.h b/UnleashedRecomp/api/Hedgehog/Math/Vector.h new file mode 100644 index 0000000..b1c0f3d --- /dev/null +++ b/UnleashedRecomp/api/Hedgehog/Math/Vector.h @@ -0,0 +1,30 @@ +#pragma once + +#include "SWA.inl" + +namespace Hedgehog::Math +{ + class CVector2 + { + public: + be X; + be Y; + }; + + class CVector + { + public: + be X; + be Y; + be Z; + }; + + class CVector4 + { + public: + be X; + be Y; + be Z; + be W; + }; +} diff --git a/UnleashedRecomp/api/Hedgehog/Math/Vector2.h b/UnleashedRecomp/api/Hedgehog/Math/Vector2.h deleted file mode 100644 index bc16719..0000000 --- a/UnleashedRecomp/api/Hedgehog/Math/Vector2.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include "SWA.inl" - -namespace Hedgehog::Math -{ - class CVector2 - { - public: - be X; - be Y; - }; -} diff --git a/UnleashedRecomp/api/README.md b/UnleashedRecomp/api/README.md new file mode 100644 index 0000000..8239ff7 --- /dev/null +++ b/UnleashedRecomp/api/README.md @@ -0,0 +1,47 @@ +# SWA + +## Contribution Guide + +### Naming Conventions + +- Use `camelCase` for local variables, `SNAKE_CASE` for preprocessor macros, and `PascalCase` for everything else. SWA-specific types that don't exist in the game should use `snake_case` for better differentiation. +- Class names should be prefixed with `C`, e.g., `CSonicContext`. +- Struct names should be prefixed with `S`, e.g., `SUpdateInfo`. +- Class members should be prefixed with `m_`, e.g., `m_Time`. Do not use this prefix for struct members. +- Enum names should be prefixed with `E`, e.g., `ELightType`. +- Enum members should start with `e`, followed by the enum name and an underscore, e.g., `eLightType_Point`. +- For enum members indicating the count of elements, prefix with `n`, followed by the name, e.g., `nLightType`. +- Pointers should be prefixed with `p`, e.g., `pSonicContext`. +- Shared pointers should be prefixed with `sp`, e.g., `spDatabase`. +- References should be prefixed with `r`, e.g., `rMessage`. +- Input function arguments should be prefixed with `in_`, e.g., `in_Name`. +- Output function arguments should be prefixed with `out_`, e.g., `out_Value`. +- Static class members should be prefixed with `ms_`, e.g., `ms_Instance`. +- Static members outside a class should be prefixed with `g_`, e.g., `g_AllocationTracker`. +- SWA-specific preprocessor macros should start with `SWA_`, e.g., `SWA_INSERT_PADDING`. +- Hedgehog namespace-specific preprocessor macros should start with `HH_` along with the library's shorthand, e.g., `HH_FND_MSG_MAKE_TYPE`. +- Function pointers should be prefixed with `fp`, e.g., `fpCGameObjectConstructor`. + +Combine prefixes as necessary, e.g., `m_sp` for a shared pointer as a class member or `in_r` for a const reference as a function argument. + +### Coding Style + +- Always place curly brackets on a new line. +- Prefer forward declaring types over including their respective headers. +- Use <> includes relative to the project's root directory path. +- Use C++17's nested namespace feature instead of defining multiple namespaces on separate lines. +- Enum classes are prohibited as they were not available when the game was developed. +- Avoid placing function definitions in .h files, instead, implement functions in the header's respective .inl file, similar to a .cpp file. +- Ensure that all class members are declared as public. Even if you suspect that a class member was private in the original code, having it public is more convenient in a modding API. +- Avoid placing multiple class definitions in a single header file unless you have a good reason to do so. +- Keep function pointers or addresses outside functions, define them as global variables in the corresponding .inl file. Mark these global variables as `inline` and never nest them within class definitions. You do not need to use the `g_` prefix for function pointers, `fp` is sufficient. +- Use primitive types defined in `cstdint` instead of using types that come with the language, e.g., use `uint32_t` instead of `unsigned int`. Using `float`, `double` and `bool` is okay. + +### Mapping Rules + +- Always include the corresponding `offsetof`/`sizeof` assertions for mapped classes/structs. If you are uncertain about the type's size, you can omit the `sizeof` assertion. +- Use the exact type name from the game if it's available through RTTI, otherwise, you can look for shared pointers that may reveal the original type name. +- If you are unsure about the name of a class/struct member, use `Field` followed by the hexadecimal byte offset (e.g., `m_Field194`). Avoid names like `m_StoresThisThingMaybe`, you can write comments next to the definition for speculations. +- If a portion of the byte range is irrelevant to your research or not mapped yet, use the `SWA_INSERT_PADDING` macro to align class/struct members correctly. +- When the class has a virtual function table, if you don't want to map every function in it, you can map only the virtual destructor. +- The original file locations are likely available in the executable file as assertion file paths. If you cannot find the file path, use your intuition to place the file in a sensible place. \ No newline at end of file diff --git a/UnleashedRecomp/api/SWA.h b/UnleashedRecomp/api/SWA.h index b00aa8d..04399ab 100644 --- a/UnleashedRecomp/api/SWA.h +++ b/UnleashedRecomp/api/SWA.h @@ -27,7 +27,7 @@ #include "Hedgehog/Base/Type/hhSharedString.h" #include "Hedgehog/Base/hhObject.h" #include "Hedgehog/Database/System/hhDatabaseData.h" -#include "Hedgehog/Math/Vector2.h" +#include "Hedgehog/Math/Vector.h" #include "Hedgehog/MirageCore/Misc/hhVertexDeclarationPtr.h" #include "Hedgehog/MirageCore/RenderData/hhMaterialData.h" #include "Hedgehog/MirageCore/RenderData/hhMeshData.h" @@ -89,6 +89,8 @@ #include "SWA/System/GameMode/Title/TitleMenu.h" #include "SWA/System/GameMode/Title/TitleStateBase.h" #include "SWA/System/GameObject.h" +#include "SWA/System/GameParameter.h" +#include "SWA/System/GammaController.h" #include "SWA/System/InputState.h" #include "SWA/System/PadState.h" #include "SWA/System/World.h" diff --git a/UnleashedRecomp/api/SWA/System/ApplicationDocument.h b/UnleashedRecomp/api/SWA/System/ApplicationDocument.h index fb81b07..faf00b3 100644 --- a/UnleashedRecomp/api/SWA/System/ApplicationDocument.h +++ b/UnleashedRecomp/api/SWA/System/ApplicationDocument.h @@ -2,9 +2,35 @@ #include #include +#include +#include + +namespace Hedgehog::Base +{ + class CCriticalSection; +} + +namespace Hedgehog::Database +{ + class CDatabase; +} + +namespace Hedgehog::Mirage +{ + class CMatrixNode; + class CRenderScene; +} + +namespace Hedgehog::Universe +{ + class CParallelJobManagerD3D9; +} namespace SWA { + class CApplication; + class CDatabaseTree; + enum ELanguage : uint32_t { eLanguage_English, @@ -33,12 +59,29 @@ namespace SWA class CMember { public: + xpointer m_pApplication; + boost::shared_ptr m_spParallelJobManagerD3D9; + SWA_INSERT_PADDING(0x14); + boost::shared_ptr m_spGame; + SWA_INSERT_PADDING(0x14); + boost::shared_ptr m_spInspireDatabase; + SWA_INSERT_PADDING(0x30); + Hedgehog::Base::CSharedString m_Field74; + SWA_INSERT_PADDING(0x0C); + boost::shared_ptr m_spMatrixNodeRoot; + SWA_INSERT_PADDING(0x14); + CGammaController m_GammaController; + SWA_INSERT_PADDING(0x1C); + boost::shared_ptr m_spAchievementManager; + boost::shared_ptr m_spDatabaseTree; + Hedgehog::Base::CSharedString m_Field10C; + SWA_INSERT_PADDING(0x1C); + boost::shared_ptr m_spRenderScene; + SWA_INSERT_PADDING(0x04); + boost::shared_ptr m_spGameParameter; + SWA_INSERT_PADDING(0x78); + boost::shared_ptr m_spCriticalSection; SWA_INSERT_PADDING(0x20); - boost::shared_ptr m_pGame; - SWA_INSERT_PADDING(0xD4); - xpointer m_pAchievementManager; - SWA_INSERT_PADDING(0x3C); - xpointer m_spGameParameter; }; // TODO: Hedgehog::Base::TSynchronizedPtr @@ -51,7 +94,31 @@ namespace SWA be m_Region; bool m_InspireVoices; bool m_InspireSubtitles; + SWA_INSERT_PADDING(0x28); }; + + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_pApplication, 0x00); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spParallelJobManagerD3D9, 0x04); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spGame, 0x20); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spInspireDatabase, 0x3C); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_Field74, 0x74); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spMatrixNodeRoot, 0x84); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_GammaController, 0xA0); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spAchievementManager, 0xFC); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spDatabaseTree, 0x104); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_Field10C, 0x10C); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spRenderScene, 0x12C); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spGameParameter, 0x138); + SWA_ASSERT_OFFSETOF(CApplicationDocument::CMember, m_spCriticalSection, 0x1B8); + SWA_ASSERT_SIZEOF(CApplicationDocument::CMember, 0x1E0); + + SWA_ASSERT_OFFSETOF(CApplicationDocument, m_pMember, 0x04); + SWA_ASSERT_OFFSETOF(CApplicationDocument, m_Language, 0x08); + SWA_ASSERT_OFFSETOF(CApplicationDocument, m_VoiceLanguage, 0x0C); + SWA_ASSERT_OFFSETOF(CApplicationDocument, m_Region, 0x18); + SWA_ASSERT_OFFSETOF(CApplicationDocument, m_InspireVoices, 0x1C); + SWA_ASSERT_OFFSETOF(CApplicationDocument, m_InspireSubtitles, 0x1D); + SWA_ASSERT_SIZEOF(CApplicationDocument, 0x48); } #include "ApplicationDocument.inl" diff --git a/UnleashedRecomp/api/SWA/System/GameDocument.h b/UnleashedRecomp/api/SWA/System/GameDocument.h index f07c324..150ebd9 100644 --- a/UnleashedRecomp/api/SWA/System/GameDocument.h +++ b/UnleashedRecomp/api/SWA/System/GameDocument.h @@ -13,12 +13,30 @@ namespace SWA class CMember { public: + struct SScoreInfo + { + be SRank; + be ARank; + be BRank; + be CRank; + be DRank; + SWA_INSERT_PADDING(0x0C); + be PointMarkerRecordSpeed; + SWA_INSERT_PADDING(0x0C); + be PointMarkerCount; + be EnemyScore; + be TrickScore; + SWA_INSERT_PADDING(0x10); + }; + SWA_INSERT_PADDING(0x1C); boost::shared_ptr m_spDatabase; - SWA_INSERT_PADDING(0x8C); + SWA_INSERT_PADDING(0x88); + Hedgehog::Base::CSharedString m_StageName; xpointer m_pSoundAdministrator; - SWA_INSERT_PADDING(0x158); - be m_Score; + SWA_INSERT_PADDING(0x124); + SScoreInfo m_ScoreInfo; + SWA_INSERT_PADDING(0x0C); }; // TODO: Hedgehog::Base::TSynchronizedPtr @@ -27,6 +45,26 @@ namespace SWA xpointer m_pVftable; xpointer m_pMember; }; + + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, SRank, 0x00); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, ARank, 0x04); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, BRank, 0x08); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, CRank, 0x0C); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, DRank, 0x10); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, PointMarkerRecordSpeed, 0x20); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, PointMarkerCount, 0x30); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, EnemyScore, 0x34); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember::SScoreInfo, TrickScore, 0x38); + SWA_ASSERT_SIZEOF(CGameDocument::CMember::SScoreInfo, 0x4C); + + SWA_ASSERT_OFFSETOF(CGameDocument::CMember, m_spDatabase, 0x1C); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember, m_StageName, 0xAC); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember, m_pSoundAdministrator, 0xB0); + SWA_ASSERT_OFFSETOF(CGameDocument::CMember, m_ScoreInfo, 0x1D8); + SWA_ASSERT_SIZEOF(CGameDocument::CMember, 0x230); + + SWA_ASSERT_OFFSETOF(CGameDocument, m_pMember, 0x08); + SWA_ASSERT_SIZEOF(CGameDocument, 0x0C); } #include "GameDocument.inl" diff --git a/UnleashedRecomp/api/SWA/System/GameParameter.h b/UnleashedRecomp/api/SWA/System/GameParameter.h new file mode 100644 index 0000000..9561814 --- /dev/null +++ b/UnleashedRecomp/api/SWA/System/GameParameter.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace SWA +{ + class CGameParameter // : public Hedgehog::Universe::CMessageActor + { + public: + struct SSaveData; + struct SStageParameter; + + SWA_INSERT_PADDING(0x94); + xpointer m_pSaveData; + xpointer m_pStageParameter; + }; +} diff --git a/UnleashedRecomp/api/SWA/System/GammaController.h b/UnleashedRecomp/api/SWA/System/GammaController.h new file mode 100644 index 0000000..d695acf --- /dev/null +++ b/UnleashedRecomp/api/SWA/System/GammaController.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + +namespace SWA +{ + class CGammaController : public Hedgehog::Base::CObject + { + public: + SWA_INSERT_PADDING(0x10); + uint8_t m_Field10; + SWA_INSERT_PADDING(0x0F); + Hedgehog::Math::CVector4 m_Gamma; + Hedgehog::Math::CVector4 m_Field30; + }; + + SWA_ASSERT_OFFSETOF(CGammaController, m_Field10, 0x10); + SWA_ASSERT_OFFSETOF(CGammaController, m_Gamma, 0x20); + SWA_ASSERT_OFFSETOF(CGammaController, m_Field30, 0x30); + SWA_ASSERT_SIZEOF(CGammaController, 0x40); +} diff --git a/UnleashedRecomp/patches/player_patches.cpp b/UnleashedRecomp/patches/player_patches.cpp index 9d0003c..07d00f8 100644 --- a/UnleashedRecomp/patches/player_patches.cpp +++ b/UnleashedRecomp/patches/player_patches.cpp @@ -4,9 +4,10 @@ #include #include -uint32_t m_lastCheckpointScore = 0; -float m_lastDarkGaiaEnergy = 0.0f; -bool m_isUnleashCancelled = false; +static uint32_t g_lastEnemyScore; +static uint32_t g_lastTrickScore; +static float g_lastDarkGaiaEnergy; +static bool g_isUnleashCancelled; void PostureDPadSupportMidAsmHook(PPCRegister& r3) { @@ -38,14 +39,13 @@ PPC_FUNC(sub_82624308) if (!Config::SaveScoreAtCheckpoints) return; - auto pGameDocument = SWA::CGameDocument::GetInstance(); + if (auto pGameDocument = SWA::CGameDocument::GetInstance()) + { + g_lastEnemyScore = pGameDocument->m_pMember->m_ScoreInfo.EnemyScore; + g_lastTrickScore = pGameDocument->m_pMember->m_ScoreInfo.TrickScore; - if (!pGameDocument) - return; - - m_lastCheckpointScore = pGameDocument->m_pMember->m_Score; - - LOGFN("Score: {}", m_lastCheckpointScore); + LOGFN("Score: {}", g_lastEnemyScore + g_lastTrickScore); + } } /* Hook function that resets the score @@ -58,19 +58,22 @@ PPC_FUNC(sub_8245F048) if (!Config::SaveScoreAtCheckpoints) return; - auto pGameDocument = SWA::CGameDocument::GetInstance(); + if (auto pGameDocument = SWA::CGameDocument::GetInstance()) + { + LOGFN("Score: {}", g_lastEnemyScore + g_lastTrickScore); - if (!pGameDocument) - return; - - LOGFN("Score: {}", m_lastCheckpointScore); - - pGameDocument->m_pMember->m_Score = m_lastCheckpointScore; + pGameDocument->m_pMember->m_ScoreInfo.EnemyScore = g_lastEnemyScore; + pGameDocument->m_pMember->m_ScoreInfo.TrickScore = g_lastTrickScore; + } } void ResetScoreOnRestartMidAsmHook() { - m_lastCheckpointScore = 0; + if (auto pGameDocument = SWA::CGameDocument::GetInstance()) + { + pGameDocument->m_pMember->m_ScoreInfo.EnemyScore = 0; + pGameDocument->m_pMember->m_ScoreInfo.TrickScore = 0; + } } // Dark Gaia energy change hook. @@ -79,7 +82,7 @@ PPC_FUNC(sub_823AF7A8) { auto pEvilSonicContext = (SWA::Player::CEvilSonicContext*)g_memory.Translate(ctx.r3.u32); - m_lastDarkGaiaEnergy = pEvilSonicContext->m_DarkGaiaEnergy; + g_lastDarkGaiaEnergy = pEvilSonicContext->m_DarkGaiaEnergy; // Don't drain energy if out of control. if (Config::FixUnleashOutOfControlDrain && pEvilSonicContext->m_OutOfControlCount && ctx.f1.f64 < 0.0) @@ -99,19 +102,19 @@ PPC_FUNC(sub_823AF7A8) if (pInputState->GetPadState().IsTapped(SWA::eKeyState_RightBumper)) { pEvilSonicContext->m_DarkGaiaEnergy = 0.0f; - m_isUnleashCancelled = true; + g_isUnleashCancelled = true; } } void PostUnleashMidAsmHook(PPCRegister& r30) { - if (m_isUnleashCancelled) - { - if (auto pEvilSonicContext = (SWA::Player::CEvilSonicContext*)g_memory.Translate(r30.u32)) - pEvilSonicContext->m_DarkGaiaEnergy = std::max(0.0f, m_lastDarkGaiaEnergy - 35.0f); + if (!g_isUnleashCancelled) + return; - m_isUnleashCancelled = false; - } + if (auto pEvilSonicContext = (SWA::Player::CEvilSonicContext*)g_memory.Translate(r30.u32)) + pEvilSonicContext->m_DarkGaiaEnergy = std::max(0.0f, g_lastDarkGaiaEnergy - 35.0f); + + g_isUnleashCancelled = false; } void SetXButtonHomingMidAsmHook(PPCRegister& r30) diff --git a/UnleashedRecomp/patches/resident_patches.cpp b/UnleashedRecomp/patches/resident_patches.cpp index 0235238..526c2dc 100644 --- a/UnleashedRecomp/patches/resident_patches.cpp +++ b/UnleashedRecomp/patches/resident_patches.cpp @@ -5,15 +5,8 @@ #include #include -const char* m_pStageID; - bool m_isSavedAchievementData = false; -void GetStageIDMidAsmHook(PPCRegister& r5) -{ - m_pStageID = *(xpointer*)g_memory.Translate(r5.u32); -} - // SWA::Message::MsgRequestStartLoading::Impl PPC_FUNC_IMPL(__imp__sub_824DCF38); PPC_FUNC(sub_824DCF38) @@ -27,12 +20,17 @@ PPC_FUNC(sub_824DCF38) ctx.r4.u32 = SWA::eLoadingDisplayType_Arrows; } - if (m_pStageID) + if (auto pGameDocument = SWA::CGameDocument::GetInstance()) { - /* Fix restarting Eggmanland as the Werehog - erroneously using the Event Gallery transition. */ - if (ctx.r4.u32 == SWA::eLoadingDisplayType_EventGallery && !strcmp(m_pStageID, "Act_EggmanLand")) - ctx.r4.u32 = SWA::eLoadingDisplayType_NowLoading; + auto stageName = pGameDocument->m_pMember->m_StageName.c_str(); + + if (stageName && strlen(stageName)) + { + /* Fix restarting Eggmanland as the Werehog + erroneously using the Event Gallery transition. */ + if (ctx.r4.u32 == SWA::eLoadingDisplayType_EventGallery && !strcmp(stageName, "Act_EggmanLand")) + ctx.r4.u32 = SWA::eLoadingDisplayType_NowLoading; + } } __imp__sub_824DCF38(ctx, base); diff --git a/UnleashedRecompLib/config/SWA.toml b/UnleashedRecompLib/config/SWA.toml index 9ece1ab..10b9c2c 100644 --- a/UnleashedRecompLib/config/SWA.toml +++ b/UnleashedRecompLib/config/SWA.toml @@ -337,11 +337,6 @@ name = "Camera2DSlopeLerpFixMidAsmHook" address = 0x82476778 # Slope registers = ["f1", "f28"] -[[midasm_hook]] -name = "GetStageIDMidAsmHook" -address = 0x82528198 -registers = ["r5"] - [[midasm_hook]] name = "PostUnleashMidAsmHook" address = 0x823C6788