From eb6876180402a46038330337f5d91ac2f008c2d4 Mon Sep 17 00:00:00 2001 From: Indra Date: Sun, 21 Jan 2024 02:21:05 +0100 Subject: [PATCH] Implement all Underworld menu functions --- src/game/Game.cpp | 8 ++- src/game/Game.h | 54 +++++++++++++++- src/input/Input.cpp | 4 +- src/instance/Animation.cpp | 16 +++++ src/instance/Animation.h | 6 ++ src/instance/Instance.cpp | 22 ++++++- src/instance/Instance.h | 34 +++++++++- src/instance/Instances.cpp | 3 +- src/instance/Object.h | 16 ++++- src/level/Stream.cpp | 16 +++++ src/level/Stream.h | 23 +++++++ src/modules/InstanceViewer.cpp | 113 ++++++++++++++++++++++++++++++++- src/modules/InstanceViewer.h | 2 + src/modules/MainMenu.cpp | 48 ++++++++++++++ src/modules/MainMenu.h | 12 ++++ src/modules/Skew.cpp | 11 ++++ src/util/Hooking.h | 12 +++- 17 files changed, 387 insertions(+), 13 deletions(-) create mode 100644 src/instance/Animation.cpp create mode 100644 src/instance/Animation.h create mode 100644 src/level/Stream.cpp create mode 100644 src/level/Stream.h create mode 100644 src/modules/MainMenu.cpp create mode 100644 src/modules/MainMenu.h diff --git a/src/game/Game.cpp b/src/game/Game.cpp index bb5f7f2..30d1eb2 100644 --- a/src/game/Game.cpp +++ b/src/game/Game.cpp @@ -9,10 +9,12 @@ Instance* Game::GetPlayerInstance() GameTracker* Game::GetGameTracker() { - return (GameTracker*)0x838330; + return (GameTracker*)GET_ADDRESS(0x10E5370, 0x838330, 0x00E7F088); } -void GAMELOOP_ExitGame(char* name, GameTracker* gameTracker, int doneType) +void GAMELOOP_RequestLevelChangeByName(char* name, GameTracker* gameTracker, int doneType) { - Hooking::Call(0xC61CFA, name, gameTracker, doneType); + auto addr = GET_ADDRESS(0x451970, 0xC61CFA, 0x5DF8C0); + + Hooking::Call(addr, name, gameTracker, doneType); } \ No newline at end of file diff --git a/src/game/Game.h b/src/game/Game.h index 6721d52..53125f7 100644 --- a/src/game/Game.h +++ b/src/game/Game.h @@ -14,6 +14,7 @@ struct WipeInfo float wipeStep; }; +#ifndef TR8 struct GameTracker { menu_t* menu; @@ -50,6 +51,57 @@ struct GameTracker float timeMult; }; +#else +struct GameTracker +{ + int field_0; + int field_4; + Level* level; + Instance* playerInstance; + + int debugFlags; + int debugFlags2; + int debugFlags3; + int debugFlags4; + + int displayFrameCount; + int field_24; + int field_28; + int field_2C; + int field_30; + int field_34; + + char baseAreaName[128]; + char field_B8; + char field_B9; + char field_BA; + + char gameMode; + char cheatMode; + + char field_BD; + char field_BE; + char field_BF; + int StreamUnitID; + int field_C4; + int field_C8; + int field_CC; + int field_D0; + int field_D4; + int field_D8; + int field_DC; + int field_E0; + int field_E4; + int field_E8; + int field_EC; + int field_F0; + int field_F4; + int field_F8; + int field_FC; + + float timeMult; +}; +#endif class Game { @@ -58,4 +110,4 @@ class Game static GameTracker* GetGameTracker(); }; -void GAMELOOP_ExitGame(char* name, GameTracker* gameTracker, int doneType); \ No newline at end of file +void GAMELOOP_RequestLevelChangeByName(char* name, GameTracker* gameTracker, int doneType); \ No newline at end of file diff --git a/src/input/Input.cpp b/src/input/Input.cpp index e68a1dd..1ad0c71 100644 --- a/src/input/Input.cpp +++ b/src/input/Input.cpp @@ -1,6 +1,8 @@ #include "Input.h" +#include "util/Hooking.h" + void Input::DisableInput(bool disable) { - *(bool*)0x8551A9 = disable; + *(bool*)GET_ADDRESS(0x1101689, 0x8551A9, 0xA02B79) = disable; } \ No newline at end of file diff --git a/src/instance/Animation.cpp b/src/instance/Animation.cpp new file mode 100644 index 0000000..f3e18d3 --- /dev/null +++ b/src/instance/Animation.cpp @@ -0,0 +1,16 @@ +#include "Animation.h" +#include "util/Hooking.h" + +void G2EmulationInstanceSetAnimation(Instance* instance, int CurrentSection, int NewAnim, int NewFrame, int Frames) +{ + auto addr = GET_ADDRESS(0x4DEC30, 0x4DE690, 0x5B1EA0); + + Hooking::Call(addr, instance, CurrentSection, NewAnim, NewFrame, Frames); +} + +void G2EmulationInstanceSetMode(Instance* instance, int CurrentSection, int Mode) +{ + auto addr = GET_ADDRESS(0x4DED90, 0x4DE7F0, 0x5B1F50); + + Hooking::Call(addr, instance, CurrentSection, Mode); +} \ No newline at end of file diff --git a/src/instance/Animation.h b/src/instance/Animation.h new file mode 100644 index 0000000..1cbe68c --- /dev/null +++ b/src/instance/Animation.h @@ -0,0 +1,6 @@ +#pragma once + +#include "Instance.h" + +void G2EmulationInstanceSetAnimation(Instance* instance, int CurrentSection, int NewAnim, int NewFrame, int Frames); +void G2EmulationInstanceSetMode(Instance* instance, int CurrentSection, int Mode); \ No newline at end of file diff --git a/src/instance/Instance.cpp b/src/instance/Instance.cpp index 0e20636..1cfb596 100644 --- a/src/instance/Instance.cpp +++ b/src/instance/Instance.cpp @@ -4,10 +4,28 @@ void INSTANCE_Post(Instance* instance, int message, int data) { - Hooking::Call(0x4580B0, instance, message, data); + auto addr = GET_ADDRESS(0x455510, 0x4580B0, 0x5B3750); + + Hooking::Call(addr, instance, message, data); } void INSTANCE_HideUnhideDrawGroup(Instance* instance, int drawGroup, int on) { - Hooking::Call(0x4319B0, instance, drawGroup, on); + auto addr = GET_ADDRESS(0x456230, 0x4319B0, 0x5B3DD0); + + Hooking::Call(addr, instance, drawGroup, on); +} + +void INSTANCE_ReallyRemoveInstance(Instance* instance, int reset, bool keepSave) +{ + auto addr = GET_ADDRESS(0x4575B0, 0x45A3A0, 0x5BC4E0); + + Hooking::Call(addr, instance, reset, keepSave); +} + +Instance* INSTANCE_BirthObjectNoParent(unsigned int unitID, cdc::Vector3* position, cdc::Euler* rotation, IntroData* introData, Object* object, int modelNum, int initEffects) +{ + auto addr = GET_ADDRESS(0x458990, 0x45BA90, 0x5BD0F0); + + return Hooking::CallReturn(addr, unitID, position, rotation, introData, object, modelNum, initEffects); } \ No newline at end of file diff --git a/src/instance/Instance.h b/src/instance/Instance.h index ef0c84d..260a482 100644 --- a/src/instance/Instance.h +++ b/src/instance/Instance.h @@ -15,6 +15,10 @@ struct Intro { }; +struct IntroData +{ +}; + struct ObjectData { unsigned __int16 version; @@ -32,6 +36,7 @@ struct CharacterProxy; struct Instance; +#ifndef TR8 struct BaseInstance { NodeType node; @@ -73,7 +78,34 @@ struct Instance : BaseInstance char pad2[12]; int introUniqueID; + + char pad3[104]; + + void* extraData; +}; +#else +struct Instance +{ + NodeType node; + + Instance* next; + Instance* prev; + + Object* object; + + char pad1[12]; + + cdc::Vector3 position; + cdc::Euler rotation; + + char pad2[24]; + + int introUniqueID; }; +#endif void INSTANCE_Post(Instance* instance, int message, int data); -void INSTANCE_HideUnhideDrawGroup(Instance* instance, int drawGroup, int on); \ No newline at end of file +void INSTANCE_HideUnhideDrawGroup(Instance* instance, int drawGroup, int on); +void INSTANCE_ReallyRemoveInstance(Instance* instance, int reset, bool keepSave); + +Instance* INSTANCE_BirthObjectNoParent(unsigned int unitID, cdc::Vector3* position, cdc::Euler* rotation, IntroData* introData, Object* object, int modelNum, int initEffects); \ No newline at end of file diff --git a/src/instance/Instances.cpp b/src/instance/Instances.cpp index 15a6e3c..e0b261b 100644 --- a/src/instance/Instances.cpp +++ b/src/instance/Instances.cpp @@ -1,8 +1,9 @@ #include "Instances.h" +#include "util/Hooking.h" void Instances::Iterate(std::function callback) { - auto first = *(Instance**)0x817D64; + auto first = *(Instance**)GET_ADDRESS(0x10C5AA4, 0x817D64, 0xD98D54); for (auto instance = first; instance != nullptr; instance = instance->next) { diff --git a/src/instance/Object.h b/src/instance/Object.h index a015c55..5b82e29 100644 --- a/src/instance/Object.h +++ b/src/instance/Object.h @@ -9,6 +9,7 @@ struct AnimFxHeader; struct AnimScriptObject; struct ObjectBase; +#ifndef TR8 struct Object { int oflags; @@ -39,4 +40,17 @@ struct Object void* data; char* name; -}; \ No newline at end of file +}; +#else +struct Object +{ + char pad1[58]; + + __int16 numModels; + __int16 numAnims; + + char pad2[34]; + + char* name; +}; +#endif \ No newline at end of file diff --git a/src/level/Stream.cpp b/src/level/Stream.cpp new file mode 100644 index 0000000..c9e2055 --- /dev/null +++ b/src/level/Stream.cpp @@ -0,0 +1,16 @@ +#include "Stream.h" +#include "util/Hooking.h" + +ObjectTracker* STREAM_GetObjectTrackerByName(char* name) +{ + auto addr = GET_ADDRESS(0x5D4270, 0x5DA260, 0x5C17D0); + + return Hooking::CallReturn(addr, name); +} + +bool STREAM_PollLoadQueue() +{ + auto addr = GET_ADDRESS(0x5D51C0, 0x5DB190, 0x5C1DA0); + + return Hooking::CallReturn(addr); +} \ No newline at end of file diff --git a/src/level/Stream.h b/src/level/Stream.h new file mode 100644 index 0000000..965974d --- /dev/null +++ b/src/level/Stream.h @@ -0,0 +1,23 @@ +#pragma once + +#include "instance/Object.h" + +#if TRAE || TR7 +#define MAX_UNIT_NAME_LENGTH 20 +#else +#define MAX_UNIT_NAME_LENGTH 128 +#endif + +struct ResolveObject; + +struct ObjectTracker +{ + ResolveObject* resolveObj; + char* objectName; + Object* object; + __int16 objectID; + __int16 objectStatus; +}; + +ObjectTracker* STREAM_GetObjectTrackerByName(char* name); +bool STREAM_PollLoadQueue(); \ No newline at end of file diff --git a/src/modules/InstanceViewer.cpp b/src/modules/InstanceViewer.cpp index 08e2dd4..b5ab24f 100644 --- a/src/modules/InstanceViewer.cpp +++ b/src/modules/InstanceViewer.cpp @@ -1,7 +1,10 @@ +#include #include #include "InstanceViewer.h" #include "instance/Instances.h" +#include "instance/Animation.h" +#include "game/Game.h" void InstanceViewer::OnMenu() { @@ -20,12 +23,23 @@ void InstanceViewer::OnDraw() ImGui::Begin("Instances", &m_show); ImGui::Columns(2, "instances"); + // Filter + ImGui::InputText("Name", m_filter, sizeof(m_filter)); + // Instance list ImGui::BeginChild("InstancesTree"); Instances::Iterate([this](Instance* instance) { - if (ImGui::TreeNodeEx((void*)instance, ImGuiTreeNodeFlags_Leaf, "%d %s", instance->introUniqueID, instance->object->name)) + auto name = instance->object->name; + + // Check filter + if (strlen(m_filter) > 0 && strstr(name, m_filter) == 0) + { + return; + } + + if (ImGui::TreeNodeEx((void*)instance, ImGuiTreeNodeFlags_Leaf, "%d %s", instance->introUniqueID, name)) { if (ImGui::IsItemClicked()) { @@ -65,8 +79,105 @@ void InstanceViewer::DrawInstance() auto position = instance->position; auto rotation = instance->rotation; + ImGui::Text("Intro: %d", instance->introUniqueID); ImGui::Text("Position: %f %f %f", position.x, position.y, position.z); ImGui::Text("Rotation: %f %f %f", rotation.x, rotation.y, rotation.z); ImGui::Text("Intro: %d", instance->introUniqueID); ImGui::Text("Address: %p", instance); + + // Buttons + if (ImGui::Button("Goto")) + { + SkewTo(instance); + } + + ImGui::SameLine(); + + if (ImGui::Button("Bring")) + { + auto player = Game::GetPlayerInstance(); + +#ifndef TR8 + instance->oldPos = player->position; +#endif + instance->position = player->position; + } + + ImGui::SameLine(); + + if (ImGui::Button("Delete")) + { + INSTANCE_ReallyRemoveInstance(instance, 0, false); + } + +#ifdef TR8 + ImGui::SameLine(); + + if (ImGui::Button("Unhide")) + { + INSTANCE_Post(instance, 7, 0); + } +#endif + + // Object info + if (ImGui::CollapsingHeader("Object")) + { + auto object = instance->object; + + ImGui::Text("Models: %d", object->numModels); + ImGui::Text("Animations: %d", object->numAnims); + } + + // Draw groups + if (ImGui::CollapsingHeader("Draw groups")) + { + static int drawGroup = 0; + static bool enabled = false; + + ImGui::InputInt("Draw group", &drawGroup); + ImGui::Checkbox("Enabled", &enabled); + + if (ImGui::Button("Toggle")) + { + INSTANCE_HideUnhideDrawGroup(instance, drawGroup, enabled); + } + } + + // Animations + if (ImGui::CollapsingHeader("Animations")) + { + static int animation = 0; + static bool loop = true; + + ImGui::InputInt("Animation", &animation); + ImGui::Checkbox("Loop", &loop); + + if (ImGui::Button("Play")) + { + G2EmulationInstanceSetAnimation(instance, 0, animation, 0, 0); + G2EmulationInstanceSetMode(instance, 0, loop ? 2 : 1); + } + } + + // Messaging + if (ImGui::CollapsingHeader("Messaging")) + { + static int message = 0; + static int data = 0; + + ImGui::InputInt("Message", &message); + ImGui::InputInt("Data", &data); + + if (ImGui::Button("Post")) + { + INSTANCE_Post(instance, message, data); + } + } +} + +void InstanceViewer::SkewTo(Instance* instance) +{ + auto player = Game::GetPlayerInstance(); + + player->position = instance->position; } \ No newline at end of file diff --git a/src/modules/InstanceViewer.h b/src/modules/InstanceViewer.h index 3b41b30..b4fdd5a 100644 --- a/src/modules/InstanceViewer.h +++ b/src/modules/InstanceViewer.h @@ -9,8 +9,10 @@ class InstanceViewer : public Module private: bool m_show = false; Instance* m_selected = nullptr; + char m_filter[64] = ""; void DrawInstance(); + void SkewTo(Instance* instance); public: void OnMenu(); diff --git a/src/modules/MainMenu.cpp b/src/modules/MainMenu.cpp new file mode 100644 index 0000000..bb812fe --- /dev/null +++ b/src/modules/MainMenu.cpp @@ -0,0 +1,48 @@ +#include + +#include "MainMenu.h" +#include "level/Stream.h" +#include "game/Game.h" + +void MainMenu::OnDraw() +{ + ImGui::Begin("Menu"); + + // Unit select + static char unit[MAX_UNIT_NAME_LENGTH] = ""; + ImGui::InputText("Unit", unit, sizeof(unit)); + + if (ImGui::Button("Load unit")) + { + GAMELOOP_RequestLevelChangeByName(unit, Game::GetGameTracker(), 4); + } + + // Birth instance + static char object[64] = ""; + ImGui::InputText("Object", object, sizeof(object)); + + if (ImGui::Button("Birth object")) + { + BirthObject(object); + } + + ImGui::End(); +} + +void MainMenu::BirthObject(char* name) +{ + auto game = Game::GetGameTracker(); + auto player = Game::GetPlayerInstance(); + + if (player == nullptr) + { + return; + } + + // Load the object + auto tracker = STREAM_GetObjectTrackerByName(name); + while (tracker->objectStatus != 2 && STREAM_PollLoadQueue()); + + // Birth the instance at the player position + INSTANCE_BirthObjectNoParent(game->StreamUnitID, &player->position, &player->rotation, nullptr, tracker->object, 0, 1); +} \ No newline at end of file diff --git a/src/modules/MainMenu.h b/src/modules/MainMenu.h new file mode 100644 index 0000000..dbfed32 --- /dev/null +++ b/src/modules/MainMenu.h @@ -0,0 +1,12 @@ +#pragma once + +#include "Module.h" + +class MainMenu : public Module +{ +private: + void BirthObject(char* name); + +public: + void OnDraw(); +}; \ No newline at end of file diff --git a/src/modules/Skew.cpp b/src/modules/Skew.cpp index d2b6026..3717380 100644 --- a/src/modules/Skew.cpp +++ b/src/modules/Skew.cpp @@ -7,8 +7,19 @@ void Skew::ToggleSkew() { auto tracker = Game::GetGameTracker(); + // Ignore if there's no player instance + if (tracker->playerInstance == nullptr) + { + return; + } + tracker->cheatMode = !tracker->cheatMode; + +#ifndef TR8 INSTANCE_Post(tracker->playerInstance, 1048592, tracker->cheatMode); +#else + INSTANCE_Post(tracker->playerInstance, 12, tracker->cheatMode); +#endif } void Skew::Process(UINT msg, WPARAM wParam) diff --git a/src/util/Hooking.h b/src/util/Hooking.h index 67aa738..55218c5 100644 --- a/src/util/Hooking.h +++ b/src/util/Hooking.h @@ -15,7 +15,7 @@ class Hooking template static inline T CallReturn(unsigned int address, Args... args) { - reinterpret_cast(address)(args...); + return reinterpret_cast(address)(args...); } // Calls a class function @@ -29,6 +29,14 @@ class Hooking template static inline T ThisCallReturn(unsigned int address, Args... args) { - reinterpret_cast(address)(args...); + return reinterpret_cast(address)(args...); } }; + +#if TR7 +#define GET_ADDRESS(tr7, trae, tr8) trl +#elif TRAE +#define GET_ADDRESS(tr7, trae, tr8) trae +#elif TR8 +#define GET_ADDRESS(tr7, trae, tr8) tr8 +#endif \ No newline at end of file