Cache Script Pointers (#211)

* Cache Script Pointers

* Rewrite script pointers
This commit is contained in:
Arthur
2025-04-22 00:56:57 +03:00
committed by GitHub
parent 2f1727530f
commit 16a6bc9081
23 changed files with 505 additions and 237 deletions
+15 -71
View File
@@ -1,67 +1,13 @@
#include "ScriptPatches.hpp"
#include "game/backend/ScriptPointers.hpp"
#include "game/pointers/Pointers.hpp"
#include "types/script/scrProgram.hpp"
namespace YimMenu
{
static uint8_t* GetCodeLocation(ScriptPatches::Data* data, int idx)
{
return &data->GetData()[idx >> 14][idx & 0x3FFF];
}
// TODO: refactor this and cache results
static std::optional<int32_t> GetCodeLocationByPattern(ScriptPatches::Data* data, const SimplePattern& pattern)
{
uint32_t code_size = data->GetSize();
for (uint32_t i = 0; i < (code_size - pattern.m_Bytes.size()); i++)
{
for (uint32_t j = 0; j < pattern.m_Bytes.size(); j++)
{
if (pattern.m_Bytes[j].has_value())
{
int loc = i + j;
if (pattern.m_Bytes[j] != *GetCodeLocation(data, loc))
goto incorrect;
}
}
return i;
incorrect:
continue;
}
return std::nullopt;
}
ScriptPatches::Data::Data(rage::scrProgram* program)
{
auto pages = new uint8_t*[program->GetNumCodePages()];
for (auto i = 0u; i < program->GetNumCodePages(); i++)
{
pages[i] = new uint8_t[program->GetCodePageSize(i)];
std::memcpy(pages[i], program->GetCodePage(i), program->GetCodePageSize(i));
}
m_Data = pages;
m_Pages = program->GetNumCodePages();
m_Size = program->GetFullCodeSize();
}
ScriptPatches::Data::~Data()
{
for (auto i = 0u; i < m_Pages; i++)
{
delete[] m_Data[i];
}
delete[] m_Data;
m_Data = nullptr;
}
std::optional<int32_t> ScriptPatches::Patch::GetPC()
std::optional<uint32_t> ScriptPatches::Patch::GetPC()
{
if (m_Pc.has_value())
return m_Pc;
@@ -71,15 +17,14 @@ namespace YimMenu
if (!data)
return std::nullopt;
auto location = GetCodeLocationByPattern(data, m_Pattern);
if (!location.has_value())
if (auto address = ScriptPointers::GetPointer(Joaat(m_Pointer.GetName())))
{
LOG(FATAL) << "Failed to find pattern for script " << HEX(m_Hash);
m_Pc = address;
}
else
{
m_Pc = location.value() + m_Offset;
m_Pc = m_Pointer.Scan(data).As<std::uint32_t>();
ScriptPointers::CachePointer(Joaat(m_Pointer.GetName()), m_Pc.value());
}
return m_Pc;
@@ -96,10 +41,10 @@ namespace YimMenu
if (m_OriginalBytes.size() == 0)
{
m_OriginalBytes.resize(m_PatchedBytes.size());
memcpy(m_OriginalBytes.data(), GetCodeLocation(data, pc.value()), m_PatchedBytes.size());
memcpy(m_OriginalBytes.data(), ScriptData::GetCodeLocation(data, pc.value()), m_PatchedBytes.size());
}
memcpy(GetCodeLocation(data, pc.value()), m_PatchedBytes.data(), m_PatchedBytes.size());
memcpy(ScriptData::GetCodeLocation(data, pc.value()), m_PatchedBytes.data(), m_PatchedBytes.size());
}
void ScriptPatches::Patch::Restore()
@@ -113,13 +58,12 @@ namespace YimMenu
if (!pc || !data)
return;
memcpy(GetCodeLocation(data, pc.value()), m_OriginalBytes.data(), m_OriginalBytes.size());
memcpy(ScriptData::GetCodeLocation(data, pc.value()), m_OriginalBytes.data(), m_OriginalBytes.size());
}
ScriptPatches::Patch::Patch(joaat_t script, SimplePattern pattern, int32_t offset, std::vector<uint8_t> patch) :
m_Pattern(pattern),
m_Offset(offset),
ScriptPatches::Patch::Patch(joaat_t script, ScriptPointer pointer, std::vector<uint8_t> patch) :
m_PatchedBytes(patch),
m_Pointer(pointer),
m_Enabled(false),
m_Pc(std::nullopt),
m_OriginalBytes({}),
@@ -162,9 +106,9 @@ namespace YimMenu
return m_Hash == hash;
}
std::shared_ptr<ScriptPatches::Patch> ScriptPatches::AddPatchImpl(joaat_t script, const std::string& pattern, int32_t offset, std::vector<uint8_t> patch)
std::shared_ptr<ScriptPatches::Patch> ScriptPatches::AddPatchImpl(joaat_t script, ScriptPointer pointer, std::vector<uint8_t> patch)
{
auto scr_patch = std::make_shared<Patch>(script, SimplePattern(pattern), offset, patch);
auto scr_patch = std::make_shared<Patch>(script, pointer, patch);
// add patch to map
m_Patches.push_back(scr_patch);
@@ -202,7 +146,7 @@ namespace YimMenu
if (!in_scope)
return; // avoid polluting memory with unused program bytecode
m_Datas.emplace(program->m_NameHash, std::move(std::make_unique<Data>(program)));
m_Datas.emplace(program->m_NameHash, std::move(std::make_unique<ScriptData>(program)));
// refresh patches so they can enable themselves
for (auto& patch : m_Patches)
@@ -248,7 +192,7 @@ namespace YimMenu
}
}
ScriptPatches::Data* ScriptPatches::GetDataImpl(joaat_t script)
ScriptData* ScriptPatches::GetDataImpl(joaat_t script)
{
if (auto it = m_Datas.find(script); it != m_Datas.end())
return it->second.get();
+11 -33
View File
@@ -1,6 +1,7 @@
#pragma once
#include "core/memory/Pattern.hpp"
#include "core/util/Joaat.hpp"
#include "game/gta/ScriptData.hpp"
#include "game/gta/ScriptPointer.hpp"
namespace rage
{
@@ -12,52 +13,29 @@ namespace YimMenu
class ScriptPatches
{
public:
class Data
{
std::uint8_t** m_Data;
std::uint32_t m_Pages;
std::uint32_t m_Size;
public:
inline std::uint8_t** GetData() const
{
return m_Data;
}
inline std::uint32_t GetSize() const
{
return m_Size;
}
Data(rage::scrProgram* program);
~Data();
};
class Patch
{
std::vector<uint8_t> m_OriginalBytes;
std::vector<uint8_t> m_PatchedBytes;
std::optional<int32_t> m_Pc;
SimplePattern m_Pattern;
int32_t m_Offset;
std::optional<uint32_t> m_Pc;
ScriptPointer m_Pointer;
bool m_Enabled;
std::uint32_t m_Hash;
std::optional<int32_t> GetPC();
std::optional<uint32_t> GetPC();
void Apply();
void Restore();
public:
Patch(joaat_t script, SimplePattern pattern, int32_t offset, std::vector<uint8_t> patch);
Patch(joaat_t script, ScriptPointer pointer, std::vector<uint8_t> patch);
void Enable();
void Disable();
void Update();
bool InScope(std::uint32_t hash);
};
static std::shared_ptr<Patch> AddPatch(joaat_t script, const std::string& pattern, int32_t offset, std::vector<uint8_t> patch)
static std::shared_ptr<Patch> AddPatch(joaat_t script, ScriptPointer pointer, std::vector<uint8_t> patch)
{
return GetInstance().AddPatchImpl(script, pattern, offset, patch);
return GetInstance().AddPatchImpl(script, pointer, patch);
}
static void RegisterProgram(rage::scrProgram* program)
@@ -90,15 +68,15 @@ namespace YimMenu
return Instance;
}
std::shared_ptr<Patch> AddPatchImpl(joaat_t script, const std::string& pattern, int32_t offset, std::vector<uint8_t> patch);
std::shared_ptr<Patch> AddPatchImpl(joaat_t script, ScriptPointer pointer, std::vector<uint8_t> patch);
void RegisterProgramImpl(rage::scrProgram* program);
void UnregisterProgramImpl(rage::scrProgram* program);
void OnScriptVMEnterImpl(rage::scrProgram* program);
void OnScriptVMLeaveImpl(rage::scrProgram* program);
Data* GetDataImpl(joaat_t script);
ScriptData* GetDataImpl(joaat_t script);
std::vector<std::shared_ptr<Patch>> m_Patches;
std::unordered_map<joaat_t, std::unique_ptr<Data>> m_Datas;
std::unordered_map<joaat_t, std::unique_ptr<ScriptData>> m_Datas;
std::uint8_t** m_CurrentlyReplacedBytecode;
};
+75
View File
@@ -0,0 +1,75 @@
#include "ScriptPointers.hpp"
#include "core/filemgr/FileMgr.hpp"
#include "core/memory/ModuleMgr.hpp"
namespace YimMenu
{
ScriptPointers::ScriptPointers() :
m_CacheFile(FileMgr::GetProjectFile("./scr_pointers.bin"))
{
}
void ScriptPointers::InitImpl()
{
m_CacheFile.Load();
if (m_CacheFile.UpToDate(ModuleMgr.Get("GTA5_Enhanced.exe"_J)->GetNtHeader()->FileHeader.TimeDateStamp))
{
Load();
}
}
void ScriptPointers::CachePointerImpl(joaat_t nameHash, std::uint32_t address)
{
m_ScriptPointers.emplace(nameHash, address);
Save();
}
std::uint32_t ScriptPointers::GetPointerImpl(joaat_t nameHash)
{
if (auto it = m_ScriptPointers.find(nameHash); it != m_ScriptPointers.end())
{
return it->second;
}
return 0;
}
void ScriptPointers::Save()
{
auto dataSize = sizeof(std::uint32_t) + sizeof(scrPointerSaveStruct) * m_ScriptPointers.size();
auto data = std::make_unique<uint8_t[]>(dataSize);
auto dataPtr = data.get();
*(std::uint32_t*)dataPtr = m_ScriptPointers.size();
dataPtr += sizeof(std::uint32_t);
for (auto& [hash, val] : m_ScriptPointers)
{
auto saveStruct = (scrPointerSaveStruct*)dataPtr;
saveStruct->m_NameHash = hash;
saveStruct->m_Address = val;
dataPtr += sizeof(scrPointerSaveStruct);
}
m_CacheFile.SetHeaderVersion(ModuleMgr.Get("GTA5_Enhanced.exe"_J)->GetNtHeader()->FileHeader.TimeDateStamp);
m_CacheFile.SetData(std::move(data), dataSize);
m_CacheFile.Write();
}
void ScriptPointers::Load()
{
auto data = m_CacheFile.Data();
auto numPointers = *(std::uint32_t*)data;
data += sizeof(std::uint32_t);
for (int i = 0; i < numPointers; i++)
{
auto saveStruct = (scrPointerSaveStruct*)data;
m_ScriptPointers.emplace(saveStruct->m_NameHash, saveStruct->m_Address);
data += sizeof(scrPointerSaveStruct);
}
LOG(INFO) << "Loaded " << m_ScriptPointers.size() << " script pointers from cache.";
}
}
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include "core/util/Joaat.hpp"
#include "core/filemgr/CacheFile.hpp"
namespace YimMenu
{
#pragma pack(push, 1)
struct scrPointerSaveStruct
{
joaat_t m_NameHash;
std::uint32_t m_Address;
};
#pragma pack(pop)
class ScriptPointers
{
static ScriptPointers& GetInstance()
{
static ScriptPointers instance;
return instance;
}
void InitImpl();
void CachePointerImpl(joaat_t nameHash, std::uint32_t address);
std::uint32_t GetPointerImpl(joaat_t nameHash);
void Save();
void Load();
CacheFile m_CacheFile;
std::unordered_map<joaat_t, std::uint32_t> m_ScriptPointers{};
public:
ScriptPointers();
static void Init()
{
GetInstance().InitImpl();
}
static void CachePointer(joaat_t nameHash, std::uint32_t address)
{
GetInstance().CachePointerImpl(nameHash, address);
}
static std::uint32_t GetPointer(joaat_t nameHash)
{
return GetInstance().GetPointerImpl(nameHash);
}
};
}
@@ -13,7 +13,7 @@ namespace YimMenu::Features
{
if (!m_DeathBarriersPatch)
{
m_DeathBarriersPatch = ScriptPatches::AddPatch("freemode"_J, "2D 01 09 00 00 5D ? ? ? 56 ? ? 3A", 5, {0x2E, 0x01, 0x00});
m_DeathBarriersPatch = ScriptPatches::AddPatch("freemode"_J, ScriptPointer("DeathBarriersPatch", "2D 01 09 00 00 5D ? ? ? 56 ? ? 3A").Add(5), {0x2E, 0x01, 0x00});
}
m_DeathBarriersPatch->Enable();
}
+3 -3
View File
@@ -15,19 +15,19 @@ namespace YimMenu::Features
{
if (!m_VehiclePOSIXPatch)
{
m_VehiclePOSIXPatch = ScriptPatches::AddPatch("appinternet"_J, "59 ? ? 72 2E 02 01", 0, {0x2B, 0x00, 0x00});
m_VehiclePOSIXPatch = ScriptPatches::AddPatch("appinternet"_J, ScriptPointer("VehiclePOSIXPatch", "59 ? ? 72 2E 02 01"), {0x2B, 0x00, 0x00});
}
m_VehiclePOSIXPatch->Enable();
if (!m_GetVehiclePricePatch)
{
m_GetVehiclePricePatch = ScriptPatches::AddPatch("appinternet"_J, "56 ? ? 70 2E 04 01 38 01", 0, {0x55});
m_GetVehiclePricePatch = ScriptPatches::AddPatch("appinternet"_J, ScriptPointer("GetVehiclePricePatch", "56 ? ? 70 2E 04 01 38 01"), {0x55});
}
m_GetVehiclePricePatch->Enable();
if (!m_BuyVehiclePatch)
{
m_BuyVehiclePatch = ScriptPatches::AddPatch("appinternet"_J, "5D ? ? ? 06 56 ? ? 38 00 25 ? 50", 5, {0x55});
m_BuyVehiclePatch = ScriptPatches::AddPatch("appinternet"_J, ScriptPointer("BuyVehiclePatch", "5D ? ? ? 06 56 ? ? 38 00 25 ? 50").Add(5), {0x55});
}
m_BuyVehiclePatch->Enable();
}
@@ -2,14 +2,13 @@
#include "core/backend/ScriptMgr.hpp"
#include "game/backend/Self.hpp"
#include "game/gta/Natives.hpp"
#include "game/gta/Scripts.hpp"
#include "game/gta/ScriptFunction.hpp"
#include "game/gta/ScriptLocal.hpp"
#include "types/script/locals/VehicleRewardData.hpp"
namespace YimMenu
{
static ScriptFunction giveVehicleReward("GiveVehicleReward", "AM_MP_VEHICLE_REWARD"_J, "2D 0C 1E 00 00");
bool GiveVehicleReward::IsSafeToRunScript()
{
return Self::GetVehicle().IsValid() && *Pointers.IsSessionStarted;
@@ -67,6 +66,7 @@ namespace YimMenu
if (auto VehicleRewardData = VEHICLE_REWARD_DATA::Get(m_Thread))
{
auto VehicleMenuData = ScriptLocal(m_Thread, 176).As<int*>(); // TO-DO: add struct for this?
static ScriptFunction giveVehicleReward("AM_MP_VEHICLE_REWARD"_J, ScriptPointer("GiveVehicleReward", "2D 0C 1E 00 00"));
if (giveVehicleReward.Call<bool>(Self::GetVehicle().GetHandle(), VehicleMenuData, &VehicleRewardData->TransactionStatus, &VehicleRewardData->Garage, &VehicleRewardData->GarageOffset, &VehicleRewardData->ControlStatus, false, true, true, false, 0, -1))
{
if (VehicleRewardData->ControlStatus != 3)
@@ -30,62 +30,62 @@ namespace YimMenu::Features
{
if (!m_CanLaunchJobPatch)
{
m_CanLaunchJobPatch = ScriptPatches::AddPatch("fmmc_launcher"_J, "2D 05 0C 00 00 71", 5, {0x72, 0x2E, 0x05, 0x01}); // the main patch that bypasses the not enough players alert
m_CanLaunchJobPatch = ScriptPatches::AddPatch("fmmc_launcher"_J, ScriptPointer("CanLaunchJobPatch", "2D 05 0C 00 00 71").Add(5), {0x72, 0x2E, 0x05, 0x01}); // the main patch that bypasses the not enough players alert
}
m_CanLaunchJobPatch->Enable();
// these patches allow the mission request system to launch fm_mission_controller with insufficent players
if (!m_MissionRequestPatch1)
{
m_MissionRequestPatch1 = ScriptPatches::AddPatch("freemode"_J, "38 00 72 5D ? ? ? 38 00 5D ? ? ? 2E 02 00 38 00 38 01", 0, std::vector<std::uint8_t>(14, 0x0));
m_MissionRequestPatch1 = ScriptPatches::AddPatch("freemode"_J, ScriptPointer("MissionRequestPatch1", "38 00 72 5D ? ? ? 38 00 5D ? ? ? 2E 02 00 38 00 38 01"), std::vector<std::uint8_t>(14, 0x0));
}
m_MissionRequestPatch1->Enable();
if (!m_MissionRequestPatch2)
{
m_MissionRequestPatch2 = ScriptPatches::AddPatch("freemode"_J, "25 ? 37 07 33 37 07 5D ? ? ? 39 11", 20, {0x72});
m_MissionRequestPatch2 = ScriptPatches::AddPatch("freemode"_J, ScriptPointer("MissionRequestPatch2", "25 ? 37 07 33 37 07 5D ? ? ? 39 11").Add(20), {0x72});
}
m_MissionRequestPatch2->Enable();
if (!m_MissionRequestPatch3)
{
m_MissionRequestPatch3 = ScriptPatches::AddPatch("freemode"_J, "2D 03 1A 00 00 38 00 5D ? ? ? 39 05", 5, {0x72, 0x2E, 0x03, 0x01});
m_MissionRequestPatch3 = ScriptPatches::AddPatch("freemode"_J, ScriptPointer("MissionRequestPatch3", "2D 03 1A 00 00 38 00 5D ? ? ? 39 05").Add(5), {0x72, 0x2E, 0x03, 0x01});
}
m_MissionRequestPatch3->Enable();
if (!m_MissionRequestPatch4)
{
m_MissionRequestPatch4 = ScriptPatches::AddPatch("freemode"_J, "5D ? ? ? 39 06 38 01 5D ? ? ? 25", 0, {0x72, 0x2E, 0x04, 0x01});
m_MissionRequestPatch4 = ScriptPatches::AddPatch("freemode"_J, ScriptPointer("MissionRequestPatch4", "5D ? ? ? 39 06 38 01 5D ? ? ? 25"), {0x72, 0x2E, 0x04, 0x01});
}
m_MissionRequestPatch4->Enable();
if (!m_PlayerCountWatchdogPatch1)
{
m_PlayerCountWatchdogPatch1 = ScriptPatches::AddPatch("fmmc_launcher"_J, "2D 02 09 00 00 25 A4", 5, {0x71, 0x2E, 0x02, 0x01}); // instant kick if this check fails
m_PlayerCountWatchdogPatch1 = ScriptPatches::AddPatch("fmmc_launcher"_J, ScriptPointer("PlayerCountWatchdogPatch1", "2D 02 09 00 00 25 A4").Add(5), {0x71, 0x2E, 0x02, 0x01}); // instant kick if this check fails
}
m_PlayerCountWatchdogPatch1->Enable();
if (!m_PlayerCountWatchdogPatch2)
{
m_PlayerCountWatchdogPatch2 = ScriptPatches::AddPatch("fmmc_launcher"_J, "2D 04 0B 00 00 2C ? ? ? 71 5D", 5, {0x71, 0x2E, 0x04, 0x01});
m_PlayerCountWatchdogPatch2 = ScriptPatches::AddPatch("fmmc_launcher"_J, ScriptPointer("PlayerCountWatchdogPatch2", "2D 04 0B 00 00 2C ? ? ? 71 5D").Add(5), {0x71, 0x2E, 0x04, 0x01});
}
m_PlayerCountWatchdogPatch2->Enable();
if (!m_HeistTeamsPatch1)
{
m_HeistTeamsPatch1 = ScriptPatches::AddPatch("fmmc_launcher"_J, "47 ? ? 5B 7B 00 38 04", 0, {0x2B, 0x2B, 0x00, 0x55}); // TODO: doesn't work for doomsday heist
m_HeistTeamsPatch1 = ScriptPatches::AddPatch("fmmc_launcher"_J, ScriptPointer("HeistTeamsPatch1", "47 ? ? 5B 7B 00 38 04"), {0x2B, 0x2B, 0x00, 0x55}); // TODO: doesn't work for doomsday heist
}
m_HeistTeamsPatch1->Enable();
if (!m_HeistTeamsPatch2)
{
m_HeistTeamsPatch2 = ScriptPatches::AddPatch("fmmc_launcher"_J, "2D 01 05 00 00 25 5D", 5, {0x72, 0x2E, 0x01, 0x01});
m_HeistTeamsPatch2 = ScriptPatches::AddPatch("fmmc_launcher"_J, ScriptPointer("HeistTeamsPatch2", "2D 01 05 00 00 25 5D").Add(5), {0x72, 0x2E, 0x01, 0x01});
}
m_HeistTeamsPatch2->Enable();
if (!m_ShouldFailMissionPatch1)
{
m_ShouldFailMissionPatch1 = ScriptPatches::AddPatch("fm_mission_controller"_J, "50 ? ? 78 82 06 1F 56 ? ? 38 02", 0, {0x00, 0x00, 0x00, 0x72, 0x00});
m_ShouldFailMissionPatch1 = ScriptPatches::AddPatch("fm_mission_controller"_J, ScriptPointer("ShouldFailMissionPatch1", "50 ? ? 78 82 06 1F 56 ? ? 38 02"), {0x00, 0x00, 0x00, 0x72, 0x00});
}
m_ShouldFailMissionPatch1->Enable();
@@ -94,7 +94,10 @@ namespace YimMenu::Features
// TODO: this is a very bad idea that can break anytime
// for some reason, Rockstar thought it's a good idea to copy-paste the same failure check in four different branches, "just in case"
for (int i = 0; i < 4; i++)
m_ShouldFailMissionPatches.push_back(ScriptPatches::AddPatch("fm_mission_controller"_J, "38 02 5D ? ? ? 71 09 2A 56 07 00 71 5D ? ? ? 06 1F 56", 0, {0x71, 0x00, 0x00, 0x00, 0x00, 0x00}));
{
std::string ptrName = "ShouldFailMissionPatches" + std::to_string(i);
m_ShouldFailMissionPatches.push_back(ScriptPatches::AddPatch("fm_mission_controller"_J, ScriptPointer(ptrName, "38 02 5D ? ? ? 71 09 2A 56 07 00 71 5D ? ? ? 06 1F 56"), {0x71, 0x00, 0x00, 0x00, 0x00, 0x00}));
}
}
for (auto& patch : m_ShouldFailMissionPatches)
@@ -102,37 +105,37 @@ namespace YimMenu::Features
if (!m_ShouldFailMissionPatch2)
{
m_ShouldFailMissionPatch2 = ScriptPatches::AddPatch("fm_mission_controller"_J, "56 07 00 38 02 5D ? ? ? 20 56 BC 01", 0, {0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x71}); // some heist check, more or less same as above
m_ShouldFailMissionPatch2 = ScriptPatches::AddPatch("fm_mission_controller"_J, ScriptPointer("ShouldFailMissionPatch2", "56 07 00 38 02 5D ? ? ? 20 56 BC 01"), {0x2B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x71}); // some heist check, more or less same as above
}
m_ShouldFailMissionPatch2->Enable();
if (!m_ShouldFailMissionPatch3)
{
m_ShouldFailMissionPatch3 = ScriptPatches::AddPatch("fm_mission_controller"_J, "50 ? ? 78 82 06 1F 56 2E 01", 0, {0x00, 0x00, 0x00, 0x00, 0x72});
m_ShouldFailMissionPatch3 = ScriptPatches::AddPatch("fm_mission_controller"_J, ScriptPointer("ShouldFailMissionPatch3", "50 ? ? 78 82 06 1F 56 2E 01"), {0x00, 0x00, 0x00, 0x00, 0x72});
}
m_ShouldFailMissionPatch3->Enable();
if (!m_NotEnoughTeamsPatch)
{
m_NotEnoughTeamsPatch = ScriptPatches::AddPatch("fm_mission_controller"_J, "2D 00 02 00 00 61 ? ? ? 47 ? ? 72 57 04 00 72 2E 00 01 4F", 5, {0x71, 0x2E, 0x00, 0x01});
m_NotEnoughTeamsPatch = ScriptPatches::AddPatch("fm_mission_controller"_J, ScriptPointer("NotEnoughTeamsPatch", "2D 00 02 00 00 61 ? ? ? 47 ? ? 72 57 04 00 72 2E 00 01 4F").Add(5), {0x71, 0x2E, 0x00, 0x01});
}
m_NotEnoughTeamsPatch->Enable();
if (!m_IsTeamValidPatch)
{
m_IsTeamValidPatch = ScriptPatches::AddPatch("fm_mission_controller"_J, "2D 01 03 00 00 38 00 71 57 1D 00", 5, {0x72, 0x2E, 0x01, 0x01});
m_IsTeamValidPatch = ScriptPatches::AddPatch("fm_mission_controller"_J, ScriptPointer("IsTeamValidPatch", "2D 01 03 00 00 38 00 71 57 1D 00").Add(5), {0x72, 0x2E, 0x01, 0x01});
}
m_IsTeamValidPatch->Enable();
if (!m_EnsureMissionPassedPatch)
{
m_EnsureMissionPassedPatch = ScriptPatches::AddPatch("fm_mission_controller"_J, "55 C8 FF 61", 3, {0x2E, 0x00, 0x00}); // the game does one final check to ensure all players exist before the mission is passed
m_EnsureMissionPassedPatch = ScriptPatches::AddPatch("fm_mission_controller"_J, ScriptPointer("EnsureMissionPassedPatch", "55 C8 FF 61").Add(3), {0x2E, 0x00, 0x00}); // the game does one final check to ensure all players exist before the mission is passed
}
m_EnsureMissionPassedPatch->Enable();
if (!m_ProcessPhoneHackingPatch)
{
m_ProcessPhoneHackingPatch = ScriptPatches::AddPatch("fm_mission_controller"_J, "58 13 00 38 00 4F ? ? 48", 0, {0x2B, 0x00, 0x00});
m_ProcessPhoneHackingPatch = ScriptPatches::AddPatch("fm_mission_controller"_J, ScriptPointer("ProcessPhoneHackingPatch", "58 13 00 38 00 4F ? ? 48"), {0x2B, 0x00, 0x00});
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ namespace YimMenu::Features
{
if (!m_ShopPatch)
{
m_ShopPatch = ScriptPatches::AddPatch("shop_controller"_J, "2D 01 04 00 00 2C ? ? ? 56 ? ? 71", 5, {0x71, 0x2E, 0x01, 0x01}); // disable the kick out subroutine
m_ShopPatch = ScriptPatches::AddPatch("shop_controller"_J, ScriptPointer("ShopPatch", "2D 01 04 00 00 2C ? ? ? 56 ? ? 71").Add(5), {0x71, 0x2E, 0x01, 0x01}); // disable the kick out subroutine
}
m_ShopPatch->Enable();
}
@@ -15,13 +15,13 @@ namespace YimMenu::Features
{
if (!m_CanUseVehiclePatch)
{
m_CanUseVehiclePatch = ScriptPatches::AddPatch("carmod_shop"_J, "2D ? ? ? ? 38 ? 5D ? ? ? 56 ? ? 71 2E ? ? 5D", 5, {0x72, 0x2E, 0x03, 0x01});
m_CanUseVehiclePatch = ScriptPatches::AddPatch("carmod_shop"_J, ScriptPointer("CanUseVehiclePatch", "2D ? ? ? ? 38 ? 5D ? ? ? 56 ? ? 71 2E ? ? 5D").Add(5), {0x72, 0x2E, 0x03, 0x01});
}
m_CanUseVehiclePatch->Enable();
if (!m_BlockMenuOptionPatch)
{
m_BlockMenuOptionPatch = ScriptPatches::AddPatch("carmod_shop"_J, "2D ? ? ? ? 38 ? 5D ? ? ? 5D ? ? ? 56", 5, {0x71, 0x2E, 0x01, 0x01});
m_BlockMenuOptionPatch = ScriptPatches::AddPatch("carmod_shop"_J, ScriptPointer("BlockMenuOptionPatch", "2D ? ? ? ? 38 ? 5D ? ? ? 5D ? ? ? 56").Add(5), {0x71, 0x2E, 0x01, 0x01});
}
m_BlockMenuOptionPatch->Enable();
}
+1 -1
View File
@@ -105,7 +105,7 @@ namespace YimMenu::Features
// ensure shop controller can't control the weather
if (!m_ShopControllerWeatherPatch)
{
m_ShopControllerWeatherPatch = ScriptPatches::AddPatch("shop_controller"_J, "2D 00 07 00 00 71 39 02 71", 5, {0x2E, 0x00, 0x00});
m_ShopControllerWeatherPatch = ScriptPatches::AddPatch("shop_controller"_J, ScriptPointer("ShopControllerWeatherPatch", "2D 00 07 00 00 71 39 02 71").Add(5), {0x2E, 0x00, 0x00});
}
m_ShopControllerWeatherPatch->Enable();
}
+1 -1
View File
@@ -18,7 +18,7 @@ namespace YimMenu::Submenus
if (ImGui::Button("fm_mission_controller DoTeamSwap"))
{
FiberPool::Push([] {
static ScriptFunction DoTeamSwap("DoTeamSwap", "fm_mission_controller"_J, "2D 02 04 00 00 38 00 50");
static ScriptFunction DoTeamSwap("fm_mission_controller"_J, ScriptPointer("DoTeamSwap", "2D 02 04 00 00 38 00 50"));
DoTeamSwap.Call<void>(team, true);
});
}
@@ -8,7 +8,6 @@
#include "game/gta/data/RandomEvents.hpp"
#include "game/gta/Scripts.hpp"
#include "game/gta/ScriptFunction.hpp"
#include "game/pointers/Pointers.hpp"
#include "types/script/globals/GPBD_FM_2.hpp"
#include "types/script/globals/GSBD_RandomEvents.hpp"
#include "types/script/locals/FMRandomEvents.hpp"
@@ -47,7 +46,6 @@ namespace YimMenu::Submenus
};
static std::vector<ScriptPatch> sendUpdateRECoordsTSECooldownPatches{};
static ScriptFunction getNumFMMCVariations("GetNumFMMCVariations", "freemode"_J, "5D ? ? ? 01 72 02 39 04", 1, true);
static GPBD_FM_2* GPBDFM2 = nullptr;
static GSBD_RandomEvents* GSBDRandomEvents = nullptr;
static RANDOM_EVENTS_FREEMODE_DATA* FMRandomEvents = nullptr;
@@ -105,17 +103,51 @@ namespace YimMenu::Submenus
static void OnComboChange()
{
selectedSubvariation = 0;
static ScriptFunction getNumFMMCVariations("freemode"_J, ScriptPointer("GetNumFMMCVariations", "5D ? ? ? 01 72 02 39 04").Add(1).Rip());
numSubvariations = getNumFMMCVariations.Call<int>(FMRandomEvents->MissionData.FMMCData[selectedEvent].FMMCType, 0) - 1;
selectedSubvariation = 0;
ResetEventTunables(selectedEvent);
}
static void KillActiveEvent()
{
if (auto eventThread = Scripts::FindScriptThread(randomEventScripts[(int)selectedEvent]))
{
if (auto NetComponent = reinterpret_cast<GtaThread*>(eventThread)->m_NetComponent)
{
if (auto eventProgram = Scripts::FindScriptProgram(randomEventScripts[(int)selectedEvent]))
{
// TO-DO: Cache these too?
if (NetComponent->IsLocalPlayerHost())
{
std::string ptrName = "SetFMContentScriptServerState" + std::to_string(selectedEvent);
ScriptFunction setFMContentScriptServerState(randomEventScripts[(int)selectedEvent], ScriptPointer(ptrName, "5D ? ? ? 55 2E 00 5D").Add(1).Rip());
setFMContentScriptServerState.Call<void>(3);
}
else
{
std::string ptrName = "SetFMContentScriptClientState" + std::to_string(selectedEvent);
ScriptFunction setFMContentScriptClientState(randomEventScripts[(int)selectedEvent], ScriptPointer(ptrName, "5D ? ? ? 55 08 00 74").Add(1).Rip());
setFMContentScriptClientState.Call<void>(3);
}
}
}
}
else
{
Notifications::Show("Random Events", "Event script is not active. Are you a participant?", NotificationType::Error);
}
}
std::shared_ptr<Category> BuildRandomEventsMenu()
{
if (sendUpdateRECoordsTSECooldownPatches.empty())
{
for (int event = DRUG_VEHICLE; event < MAX_EVENTS; event++)
sendUpdateRECoordsTSECooldownPatches.push_back(ScriptPatches::AddPatch(randomEventScripts[event], "43 88 13 2E 00 01", 0, {0x71, 0x00, 0x00}));
{
std::string ptrName = "SendUpdateRECoordsTSECooldownPatch" + std::to_string(event);
sendUpdateRECoordsTSECooldownPatches.push_back(ScriptPatches::AddPatch(randomEventScripts[event], ScriptPointer(ptrName, "43 88 13 2E 00 01"), {0x71, 0x00, 0x00}));
}
}
for (auto& patch : sendUpdateRECoordsTSECooldownPatches)
@@ -224,26 +256,7 @@ namespace YimMenu::Submenus
}
else if (GSBDRandomEvents->EventData[selectedEvent].State == eRandomEventState::ACTIVE)
{
if (auto eventThread = Scripts::FindScriptThread(randomEventScripts[(int)selectedEvent]))
{
if (auto NetComponent = reinterpret_cast<GtaThread*>(eventThread)->m_NetComponent)
{
if (NetComponent->IsLocalPlayerHost())
{
ScriptFunction setFMContentScriptServerState("SetFMContentScriptServerState", randomEventScripts[(int)selectedEvent], "5D ? ? ? 55 2E 00 5D", 1, true);
setFMContentScriptServerState.Call<void>(3);
}
else
{
ScriptFunction setFMContentScriptClientState("SetFMContentScriptClientState", randomEventScripts[(int)selectedEvent], "5D ? ? ? 55 08 00 74", 1, true);
setFMContentScriptClientState.Call<void>(3);
}
}
}
else
{
Notifications::Show("Random Events", "Event script is not active. Are you a participant?", NotificationType::Error);
}
KillActiveEvent();
}
else
{
+2 -2
View File
@@ -18,10 +18,10 @@ namespace YimMenu::Network
{
void LaunchJoinType(JoinType to_launch)
{
static ScriptFunction send_to_clouds("SendToClouds", "shop_controller"_J, "2D 00 02 00 00 72 5D ? ? ? 72");
static ScriptFunction sendToClouds("shop_controller"_J, ScriptPointer("SendToClouds", "2D 00 02 00 00 72 5D ? ? ? 72"));
static ScriptGlobal join_type_global(1575038);
send_to_clouds.Call<void>();
sendToClouds.Call<void>();
*join_type_global.As<JoinType*>() = to_launch;
}
+61
View File
@@ -0,0 +1,61 @@
#include "ScriptData.hpp"
#include "types/script/scrProgram.hpp"
namespace YimMenu
{
ScriptData::ScriptData(rage::scrProgram* program)
{
auto pages = new uint8_t*[program->GetNumCodePages()];
for (auto i = 0u; i < program->GetNumCodePages(); i++)
{
pages[i] = new uint8_t[program->GetCodePageSize(i)];
std::memcpy(pages[i], program->GetCodePage(i), program->GetCodePageSize(i));
}
m_Data = pages;
m_Pages = program->GetNumCodePages();
m_Size = program->GetFullCodeSize();
m_Name = program->m_Name;
}
ScriptData::~ScriptData()
{
for (auto i = 0u; i < m_Pages; i++)
{
delete[] m_Data[i];
}
delete[] m_Data;
m_Data = nullptr;
m_Name = nullptr;
}
std::uint8_t* ScriptData::GetCodeLocation(ScriptData* data, int idx)
{
return &data->GetData()[idx >> 14][idx & 0x3FFF];
}
std::optional<std::uint32_t> ScriptData::GetCodeLocationByPattern(ScriptData* data, const SimplePattern& pattern)
{
std::uint32_t codeSize = data->GetSize();
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())
{
int loc = i + j;
if (pattern.m_Bytes[j] != *GetCodeLocation(data, loc))
goto incorrect;
}
}
return i;
incorrect:
continue;
}
return std::nullopt;
}
}
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include "core/memory/Pattern.hpp"
namespace rage
{
class scrProgram;
}
namespace YimMenu
{
class ScriptData
{
std::uint8_t** m_Data;
std::uint32_t m_Pages;
std::uint32_t m_Size;
const char* m_Name;
public:
inline std::uint8_t** GetData() const
{
return m_Data;
}
inline std::uint32_t GetSize() const
{
return m_Size;
}
inline const char* GetName() const
{
return m_Name;
}
ScriptData(rage::scrProgram* program);
~ScriptData();
static uint8_t* GetCodeLocation(ScriptData* data, int idx);
static std::optional<uint32_t> GetCodeLocationByPattern(ScriptData* data, const SimplePattern& pattern);
};
}
+52 -20
View File
@@ -1,34 +1,66 @@
#include "ScriptFunction.hpp"
#include "game/backend/ScriptPointers.hpp"
#include "game/pointers/Pointers.hpp"
#include "game/gta/Scripts.hpp"
#include "game/gta/ScriptPointer.hpp"
#include "types/script/scrThread.hpp"
#include "types/script/scrProgram.hpp"
#include "types/rage/tlsContext.hpp"
namespace YimMenu
{
ScriptFunction::ScriptFunction(const std::string& name, const joaat_t script, const SimplePattern& pattern, std::int32_t offset, bool rip) :
m_Name(name),
m_Script(script),
m_Pattern(pattern),
m_Offset(offset),
m_Rip(rip),
m_PC(0)
ScriptFunction::ScriptFunction(const joaat_t script, ScriptPointer ptr) :
m_Script(script),
m_Pointer(ptr),
m_Pc(0)
{
}
std::uint32_t ScriptFunction::GetPC(rage::scrProgram* program)
void ScriptFunction::CallImpl(const std::vector<uint64_t>& args, void* returnValue, std::uint32_t returnSize)
{
if (m_PC != 0)
return m_PC;
auto thread = Scripts::FindScriptThread(m_Script);
auto program = Scripts::FindScriptProgram(m_Script);
if (auto location = Scripts::GetCodeLocationByPattern(program, m_Pattern))
if (!thread || !program)
return;
if (!m_Pc)
{
m_PC = location.value() + m_Offset;
if (m_Rip)
m_PC = ReadThreeByte(program->GetCodeAddress(m_PC));
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;
if (auto address = ScriptPointers::GetPointer(Joaat(m_Pointer.GetName())))
{
m_Pc = address;
}
else
{
m_Pc = m_Pointer.Scan(program).As<std::uint32_t>();
ScriptPointers::CachePointer(Joaat(m_Pointer.GetName()), m_Pc);
}
if (!m_Pc) // if still not valid
return;
}
return m_PC;
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;
for (auto& arg : args)
stack[ctx.m_StackPointer++] = arg;
stack[ctx.m_StackPointer++] = 0;
ctx.m_ProgramCounter = m_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 (returnValue)
std::memcpy(returnValue, stack + topStack, returnSize);
}
}
+16 -49
View File
@@ -1,74 +1,41 @@
#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"
#include "game/gta/ScriptPointer.hpp"
namespace YimMenu
{
class ScriptFunction
{
joaat_t m_Script;
const SimplePattern m_Pattern;
std::int32_t m_Offset;
bool m_Rip;
std::uint32_t m_PC;
std::string m_Name;
std::uint32_t GetPC(rage::scrProgram* program);
ScriptPointer m_Pointer;
std::uint32_t m_Pc;
template<typename Arg>
void PushArg(std::uint64_t* stack, std::uint32_t& stackPtr, Arg&& value)
static void PushArg(std::uint64_t* stack, std::uint32_t& stackPtr, Arg&& value)
{
*reinterpret_cast<std::remove_cv_t<std::remove_reference_t<Arg>>*>(reinterpret_cast<std::uint64_t*>(stack) + (stackPtr++)) = std::forward<Arg>(value);
}
std::uint32_t ReadThreeByte(std::uint8_t* arr)
{
return arr[0] + (arr[1] << 8) + (arr[2] << 16);
}
void CallImpl(const std::vector<uint64_t>& args, void* returnValue = 0, std::uint32_t returnSize = 0);
public:
ScriptFunction(const std::string& name, const joaat_t script, const SimplePattern& pattern, std::int32_t offset = 0, bool rip = false);
ScriptFunction(const joaat_t script, ScriptPointer ptr);
template<typename Ret, typename... Args>
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>(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;
std::uint32_t index{};
std::vector<std::uint64_t> params(sizeof...(Args));
(PushArg(params.data(), index, std::forward<Args>(args)), ...);
if constexpr (!std::is_same_v<Ret, void>)
{
return *reinterpret_cast<Ret*>(stack + topStack);
Ret returnValue;
CallImpl(params, &returnValue, sizeof(returnValue));
return returnValue;
}
else
{
CallImpl(params);
}
}
};
+67
View File
@@ -0,0 +1,67 @@
#include "ScriptPointer.hpp"
#include "game/gta/Scripts.hpp"
#include "types/script/scrProgram.hpp"
namespace YimMenu
{
static std::uint32_t ReadThreeByte(std::uint8_t* arr)
{
return arr[0] + (arr[1] << 8) + (arr[2] << 16);
}
ScriptPointer::ScriptPointer(const std::string& name, const SimplePattern& pattern, std::int32_t offset, bool rip, std::uint32_t address) :
m_Name(name),
m_Pattern(pattern),
m_Offset(offset),
m_Rip(rip),
m_Address(address)
{
}
ScriptPointer ScriptPointer::Add(std::uint32_t offset)
{
return ScriptPointer(m_Name, m_Pattern, m_Offset + offset, m_Rip, m_Address);
}
ScriptPointer ScriptPointer::Sub(std::uint32_t offset)
{
return ScriptPointer(m_Name, m_Pattern, m_Offset - offset, m_Rip, m_Address);
}
ScriptPointer ScriptPointer::Rip()
{
return ScriptPointer(m_Name, m_Pattern, m_Offset, true, m_Address);
}
ScriptPointer ScriptPointer::Scan(rage::scrProgram* program)
{
if (auto location = Scripts::GetCodeLocationByPattern(program, m_Pattern))
{
m_Address = location.value() + m_Offset;
if (m_Rip)
m_Address = ReadThreeByte(program->GetCodeAddress(m_Address));
LOG(INFO) << "Found pattern for " << m_Name << " at " << HEX(m_Address) << " in script " << program->m_Name;
return ScriptPointer(m_Name, m_Pattern, m_Offset, m_Rip, m_Address);
}
LOG(FATAL) << "Failed to find pattern for " << m_Name;
return ScriptPointer(m_Name, m_Pattern, m_Offset, m_Rip, 0);
}
ScriptPointer ScriptPointer::Scan(ScriptData* data)
{
if (auto location = ScriptData::GetCodeLocationByPattern(data, m_Pattern))
{
m_Address = location.value() + m_Offset;
if (m_Rip)
m_Address = ReadThreeByte(ScriptData::GetCodeLocation(data, m_Address));
LOG(INFO) << "Found pattern for " << m_Name << " at " << HEX(m_Address) << " in script " << data->GetName();
return ScriptPointer(m_Name, m_Pattern, m_Offset, m_Rip, m_Address);
}
LOG(FATAL) << "Failed to find pattern for " << m_Name;
return ScriptPointer(m_Name, m_Pattern, m_Offset, m_Rip, 0);
}
}
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include "game/gta/ScriptData.hpp"
namespace YimMenu
{
class ScriptPointer
{
const std::string m_Name;
SimplePattern m_Pattern;
std::int32_t m_Offset;
bool m_Rip;
std::uint32_t m_Address;
public:
ScriptPointer(const std::string& name, const SimplePattern& pattern, std::int32_t offset = 0, bool rip = false, std::uint32_t address = 0);
ScriptPointer Add(std::uint32_t offset);
ScriptPointer Sub(std::uint32_t offset);
ScriptPointer Rip();
ScriptPointer Scan(rage::scrProgram* program);
ScriptPointer Scan(ScriptData* data); // For script patches
template<typename T>
T As()
{
return (T)m_Address;
}
std::string GetName() const
{
return m_Name;
}
};
}
+3
View File
@@ -17,6 +17,7 @@
#include "game/backend/Self.hpp"
#include "game/backend/NativeHooks.hpp"
#include "game/backend/Tunables.hpp"
#include "game/backend/ScriptPointers.hpp"
#include "game/frontend/GUI.hpp"
#include "game/pointers/Pointers.hpp"
#include "game/features/recovery/GiveVehicleReward.hpp"
@@ -50,6 +51,8 @@ namespace YimMenu
ScriptMgr::Init();
LOG(INFO) << "ScriptMgr initialized";
ScriptPointers::Init();
GUI::Init();
ScriptMgr::AddScript(std::make_unique<Script>(&NativeHooks::RunScript)); // runs once
+10 -9
View File
@@ -1,4 +1,3 @@
#include "GlobalPlayerBD.hpp"
#include "GPBD_FM.hpp"
#include "GPBD_FM_2.hpp"
#include "GPBD_FM_3.hpp"
@@ -10,17 +9,19 @@
#include "GSBD_FM_Events.hpp"
#include "GSBD_Kicking.hpp"
#include "GSBD_PropertyInstances.hpp"
#include "g_AMC_playerBD.hpp"
#include "GSBD_RandomEvents.hpp"
#include "GlobalPlayerBD.hpp"
#include "g_AMC_playerBD.hpp"
#include "game/gta/ScriptGlobal.hpp"
#define DEFINE_GLOBAL_ACCESSOR(str, global) str* str::Get()\
{ \
auto glb = YimMenu::ScriptGlobal(global); \
if (!glb.CanAccess()) \
return nullptr; \
return glb.As<str*>(); \
}
#define DEFINE_GLOBAL_ACCESSOR(str, global) \
str* str::Get() \
{ \
auto glb = YimMenu::ScriptGlobal(global); \
if (!glb.CanAccess()) \
return nullptr; \
return glb.As<str*>(); \
}
DEFINE_GLOBAL_ACCESSOR(GlobalPlayerBD, 2657994);
DEFINE_GLOBAL_ACCESSOR(GPBD_FM, 1845270);
+1 -1
View File
@@ -1,5 +1,5 @@
#include "VehicleRewardData.hpp"
#include "FMRandomEvents.hpp"
#include "VehicleRewardData.hpp"
#include "game/gta/ScriptLocal.hpp"
#define DEFINE_LOCAL_ACCESSOR(str, local) \