diff --git a/src/game/features/recovery/GiveVehicleReward.cpp b/src/game/features/recovery/GiveVehicleReward.cpp new file mode 100644 index 0000000..aa4f8c1 --- /dev/null +++ b/src/game/features/recovery/GiveVehicleReward.cpp @@ -0,0 +1,54 @@ +#include "GiveVehicleReward.hpp" +#include "core/backend/ScriptMgr.hpp" +#include "game/backend/Self.hpp" +#include "game/gta/Natives.hpp" +#include "game/gta/ScriptFunction.hpp" +#include "game/gta/ScriptLocal.hpp" +#include "types/script/locals/VehicleRewardData.hpp" + +namespace YimMenu +{ + static ScriptFunction giveVehicleReward("GVR", "AM_MP_VEHICLE_REWARD"_J, "2D 0C 1E 00 00"); + + bool GiveVehicleReward::IsSafeToRunScript() + { + return Self::GetVehicle().IsValid() && *Pointers.IsSessionStarted && SCRIPT::GET_NUMBER_OF_THREADS_RUNNING_THE_SCRIPT_WITH_THIS_HASH("AM_MP_VEHICLE_REWARD"_J) > 0; + } + + void GiveVehicleReward::RunScriptImpl() + { + while (g_Running) + { + if (m_ShouldRunScript) + { + if (IsSafeToRunScript()) + { + if (auto thread = Scripts::FindScriptThread("AM_MP_VEHICLE_REWARD"_J)) + { + if (auto VehicleRewardData = VEHICLE_REWARD_DATA::Get(thread)) + { + auto VehicleMenuData = ScriptLocal(thread, 176).As(); // TO-DO: add struct for this? + if (giveVehicleReward.Call(Self::GetVehicle().GetHandle(), VehicleMenuData, &VehicleRewardData->TransactionStatus, &VehicleRewardData->Garage, &VehicleRewardData->GarageOffset, &VehicleRewardData->ControlStatus, false, true, true, false, 0, -1)) + { + if (VehicleRewardData->ControlStatus != 3) + { + VehicleRewardData->TransactionStatus = 0; + VehicleRewardData->Garage = 0; + VehicleRewardData->GarageOffset = 0; + VehicleRewardData->ControlStatus = 0; + m_ShouldRunScript = false; + } + } + } + } + } + else + { + m_ShouldRunScript = false; + } + } + + ScriptMgr::Yield(); + } + } +} \ No newline at end of file diff --git a/src/game/features/recovery/GiveVehicleReward.hpp b/src/game/features/recovery/GiveVehicleReward.hpp new file mode 100644 index 0000000..850e80f --- /dev/null +++ b/src/game/features/recovery/GiveVehicleReward.hpp @@ -0,0 +1,30 @@ +#pragma once + +namespace YimMenu +{ + class GiveVehicleReward + { + static GiveVehicleReward& GetInstance() + { + static GiveVehicleReward instance; + return instance; + } + + void RunScriptImpl(); + + bool m_ShouldRunScript = false; + + public: + static bool IsSafeToRunScript(); + + static void RunScript() + { + GetInstance().RunScriptImpl(); + } + + static void SetShouldRunScript(bool run) + { + GetInstance().m_ShouldRunScript = run; + } + }; +} \ No newline at end of file diff --git a/src/game/frontend/Menu.cpp b/src/game/frontend/Menu.cpp index 2cbe90b..5f2c10b 100644 --- a/src/game/frontend/Menu.cpp +++ b/src/game/frontend/Menu.cpp @@ -11,6 +11,7 @@ #include "submenus/Teleport.hpp" #include "submenus/Network.hpp" #include "submenus/Players.hpp" +#include "submenus/Recovery.hpp" #include "submenus/Settings.hpp" #include "submenus/Debug.hpp" #include "submenus/World.hpp" @@ -25,6 +26,7 @@ namespace YimMenu UIManager::AddSubmenu(std::make_shared()); UIManager::AddSubmenu(std::make_shared()); UIManager::AddSubmenu(std::make_shared()); + UIManager::AddSubmenu(std::make_shared()); UIManager::AddSubmenu(std::make_shared()); UIManager::AddSubmenu(std::make_shared()); diff --git a/src/game/frontend/submenus/Recovery.cpp b/src/game/frontend/submenus/Recovery.cpp new file mode 100644 index 0000000..0434c27 --- /dev/null +++ b/src/game/frontend/submenus/Recovery.cpp @@ -0,0 +1,26 @@ +#include "Recovery.hpp" +#include "game/frontend/items/Items.hpp" +#include "game/features/recovery/GiveVehicleReward.hpp" + +namespace YimMenu::Submenus +{ + Recovery::Recovery() : + Submenu::Submenu("Recovery") + { + auto shopping = std::make_shared("Shopping"); + auto vehiclesGroup = std::make_shared("Vehicles"); + + vehiclesGroup->AddItem(std::make_unique([] { + if (ImGui::Button("Save This Vehicle as Personal Vehicle")) + { + if (GiveVehicleReward::IsSafeToRunScript()) + { + GiveVehicleReward::SetShouldRunScript(true); + } + } + })); + + shopping->AddItem(vehiclesGroup); + AddCategory(std::move(shopping)); + } +} \ No newline at end of file diff --git a/src/game/frontend/submenus/Recovery.hpp b/src/game/frontend/submenus/Recovery.hpp new file mode 100644 index 0000000..5bddff8 --- /dev/null +++ b/src/game/frontend/submenus/Recovery.hpp @@ -0,0 +1,11 @@ +#pragma once +#include "core/frontend/manager/UIManager.hpp" + +namespace YimMenu::Submenus +{ + class Recovery : public Submenu + { + public: + Recovery(); + }; +} \ No newline at end of file diff --git a/src/game/gta/ScriptFunction.cpp b/src/game/gta/ScriptFunction.cpp new file mode 100644 index 0000000..eb883bd --- /dev/null +++ b/src/game/gta/ScriptFunction.cpp @@ -0,0 +1,30 @@ +#include "ScriptFunction.hpp" + +namespace YimMenu +{ + ScriptFunction::ScriptFunction(const std::string& name, const joaat_t script, const SimplePattern& pattern) : + m_Name(name), + m_Script(script), + m_Pattern(pattern), + m_PC(0) + { + } + + std::uint32_t ScriptFunction::GetPC(rage::scrProgram* program) + { + if (m_PC != 0) + return m_PC; + + if (auto location = Scripts::GetCodeLocationByPattern(program, m_Pattern)) + { + m_PC = location.value(); + LOG(INFO) << "Found pattern for " << m_Name << " at " << HEX(m_PC) << " in script " << program->m_Name; + } + else + { + LOG(FATAL) << "Failed to find pattern for " << m_Name << " in script " << program->m_Name; + } + + return m_PC; + } +} \ No newline at end of file diff --git a/src/game/gta/ScriptFunction.hpp b/src/game/gta/ScriptFunction.hpp new file mode 100644 index 0000000..1641908 --- /dev/null +++ b/src/game/gta/ScriptFunction.hpp @@ -0,0 +1,68 @@ +#pragma once +#include "core/util/Joaat.hpp" +#include "core/memory/Pattern.hpp" +#include "game/pointers/Pointers.hpp" +#include "game/gta/Scripts.hpp" +#include "types/script/scrThread.hpp" +#include "types/script/scrProgram.hpp" +#include "types/rage/tlsContext.hpp" + +namespace YimMenu +{ + class ScriptFunction + { + joaat_t m_Script; + const SimplePattern m_Pattern; + std::uint32_t m_PC; + std::string m_Name; + + std::uint32_t GetPC(rage::scrProgram* program); + + template + void PushArg(std::uint64_t* stack, std::uint32_t& stackPtr, Arg&& value) + { + *reinterpret_cast>*>(reinterpret_cast(stack) + (stackPtr++)) = std::forward(value); + } + + public: + ScriptFunction(const std::string& name, const joaat_t script, const SimplePattern& pattern); + + template + Ret Call(Args... args) + { + auto thread = Scripts::FindScriptThread(m_Script); + auto program = Scripts::FindScriptProgram(m_Script); + + if (!thread || !program) + return Ret(); + + auto pc = GetPC(program); + if (!pc) + return Ret(); + + auto tlsCtx = rage::tlsContext::Get(); + auto stack = (std::uint64_t*)thread->m_Stack; + auto ogThread = tlsCtx->m_CurrentScriptThread; + tlsCtx->m_CurrentScriptThread = thread; + tlsCtx->m_ScriptThreadActive = true; + rage::scrThread::Context ctx = thread->m_Context; + auto topStack = ctx.m_StackPointer; + + (PushArg(stack, ctx.m_StackPointer, std::forward(args)), ...); + + stack[ctx.m_StackPointer++] = 0; + ctx.m_ProgramCounter = pc; + ctx.m_State = rage::scrThread::State::IDLE; + + Pointers.ScriptVM(stack, Pointers.ScriptGlobals, program, &ctx); + + tlsCtx->m_CurrentScriptThread = ogThread; + tlsCtx->m_ScriptThreadActive = ogThread != nullptr; + + if constexpr (!std::is_same_v) + { + return *reinterpret_cast(stack + topStack); + } + } + }; +} \ No newline at end of file diff --git a/src/game/gta/ScriptLocal.cpp b/src/game/gta/ScriptLocal.cpp index 60fffbf..f40214e 100644 --- a/src/game/gta/ScriptLocal.cpp +++ b/src/game/gta/ScriptLocal.cpp @@ -6,4 +6,9 @@ namespace YimMenu { return reinterpret_cast((uintptr_t)m_StackPtr + (m_Index * sizeof(uintptr_t))); } + + bool ScriptLocal::CanAccess() const + { + return m_StackPtr != nullptr; + } } \ No newline at end of file diff --git a/src/game/gta/ScriptLocal.hpp b/src/game/gta/ScriptLocal.hpp index 1fc3b3e..f9d88d9 100644 --- a/src/game/gta/ScriptLocal.hpp +++ b/src/game/gta/ScriptLocal.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace YimMenu { @@ -25,12 +26,11 @@ namespace YimMenu } - // TODO: Uncomment when GTAV-Classes have been added - // constexpr ScriptLocal(rage::scrThread* thread, std::size_t index) : - // ScriptLocal(thread->m_stack, index) - // { + constexpr ScriptLocal(rage::scrThread* thread, std::size_t index) : + ScriptLocal(thread->m_Stack, index) + { - // } + } constexpr ScriptLocal At(std::ptrdiff_t offset) const { @@ -65,6 +65,8 @@ namespace YimMenu return *static_cast>>(Get()); } + bool CanAccess() const; + private: void* Get() const; diff --git a/src/game/gta/Scripts.cpp b/src/game/gta/Scripts.cpp index 9d37a6c..e69c890 100644 --- a/src/game/gta/Scripts.cpp +++ b/src/game/gta/Scripts.cpp @@ -3,6 +3,7 @@ #include "types/rage/atArray.hpp" #include "types/rage/tlsContext.hpp" #include "types/script/GtaThread.hpp" +#include "types/script/scrProgram.hpp" #include "types/script/CGameScriptHandler.hpp" #include "types/script/CGameScriptHandlerNetComponent.hpp" #include "types/script/CGameScriptId.hpp" @@ -10,6 +11,7 @@ #include "game/gta/Packet.hpp" #include "game/backend/Players.hpp" #include "game/backend/Self.hpp" +#include "core/memory/Pattern.hpp" namespace YimMenu::Scripts { @@ -26,6 +28,16 @@ namespace YimMenu::Scripts return nullptr; } + rage::scrProgram* FindScriptProgram(joaat_t hash) + { + for (int i = 0; i < 176; i++) + { + if (Pointers.ScriptPrograms[i] && Pointers.ScriptPrograms[i]->m_NameHash == hash) + return Pointers.ScriptPrograms[i]; + } + + return nullptr; + } void RunAsScript(rage::scrThread* thread, std::function callback) { @@ -82,6 +94,24 @@ namespace YimMenu::Scripts return false; } + std::optional GetCodeLocationByPattern(rage::scrProgram* program, SimplePattern pattern) + { + uint32_t codeSize = program->m_CodeSize; + for (uint32_t i = 0; i < (codeSize - pattern.m_Bytes.size()); i++) + { + for (uint32_t j = 0; j < pattern.m_Bytes.size(); j++) + if (pattern.m_Bytes[j].has_value()) + if (pattern.m_Bytes[j].value() != *program->GetCodeAddress(i + j)) + goto incorrect; + + return i; + incorrect: + continue; + } + + return std::nullopt; + } + void ForceScriptHost(rage::scrThread* thread) { if (auto net_component = reinterpret_cast(thread)->m_NetComponent) diff --git a/src/game/gta/Scripts.hpp b/src/game/gta/Scripts.hpp index 03c5ebf..13b5ada 100644 --- a/src/game/gta/Scripts.hpp +++ b/src/game/gta/Scripts.hpp @@ -7,11 +7,15 @@ namespace rage class scrProgram; } +class SimplePattern; + namespace YimMenu::Scripts { extern rage::scrThread* FindScriptThread(joaat_t hash); + extern rage::scrProgram* FindScriptProgram(joaat_t hash); extern void RunAsScript(rage::scrThread* thread, std::function callback); extern void RunWithSpoofedThreadName(std::uint32_t name, std::function callback); // consider using RunAsScript if possible extern bool SafeToModifyFreemodeBroadcastGlobals(); // TODO: find a better method for this that works for all scripts + extern std::optional GetCodeLocationByPattern(rage::scrProgram* program, SimplePattern pattern); extern void ForceScriptHost(rage::scrThread* thread); // likely unstable, beware! } \ No newline at end of file diff --git a/src/game/pointers/Pointers.cpp b/src/game/pointers/Pointers.cpp index df8dff3..0be5709 100644 --- a/src/game/pointers/Pointers.cpp +++ b/src/game/pointers/Pointers.cpp @@ -204,7 +204,7 @@ namespace YimMenu constexpr auto scriptVMPtrn = Pattern<"49 63 41 1C">("ScriptVM"); scanner.Add(scriptVMPtrn, [this](PointerCalculator ptr) { - ScriptVM = ptr.Sub(0x24).As(); + ScriptVM = ptr.Sub(0x24).As(); }); constexpr auto prepareMetricForSendingPtrn = Pattern<"48 89 F9 FF 50 20 48 8D 15">("PrepareMetricForSending"); diff --git a/src/game/pointers/Pointers.hpp b/src/game/pointers/Pointers.hpp index 779ab33..44d7ccf 100644 --- a/src/game/pointers/Pointers.hpp +++ b/src/game/pointers/Pointers.hpp @@ -39,6 +39,7 @@ namespace YimMenu using RequestControl = void(*)(rage::netObject* object); using EventAck = bool(*)(uintptr_t data, CNetGamePlayer* target_player, uint32_t event_index, uint32_t event_handled_bitset); using SendEventAck = void(*)(rage::netEventMgr* event_manager, CNetGamePlayer* source_player); + using ScriptVM = int (*)(uint64_t* stack, int64_t** scr_globals, rage::scrProgram* program, void* ctx); } struct PointerData @@ -85,7 +86,7 @@ namespace YimMenu Functions::SendEventAck SendEventAck; PVOID QueueDependency; PVOID SigScanMemory; - PVOID ScriptVM; + Functions::ScriptVM ScriptVM; PVOID PrepareMetricForSending; int* BERestartStatus; bool* NeedsBERestart; diff --git a/src/main.cpp b/src/main.cpp index cb2bb53..7395ea6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -16,6 +16,7 @@ #include "game/backend/NativeHooks.hpp" #include "game/frontend/GUI.hpp" #include "game/pointers/Pointers.hpp" +#include "game/features/recovery/GiveVehicleReward.hpp" namespace YimMenu { @@ -55,6 +56,7 @@ namespace YimMenu FiberPool::Init(16); ScriptMgr::AddScript(std::make_unique