- Added compatibility with v1.16.2 hotfix update

- Changed the way the mod menu gets the list of player variables, meaning the player variables list should self-update, with no manual intervention required even after a game update
This commit is contained in:
EricPlayZ
2024-05-08 17:19:18 +03:00
parent 0b6c04f107
commit f692641c28
9 changed files with 198 additions and 48 deletions

View File

@ -51,7 +51,7 @@ Thank you everyone for the support <3)" },
- Added function hooking timeout for easier debugging
- Fetch game version using Windows' API instead of using the game's function)" },
{ "v1.2.0",
R"(- Added compatibility with v1.16.1 hotfix update
R"(- Added compatibility with v1.16.2 hotfix update
- Added the ability of using .PAK mods inside "EGameTools\UserModFiles"; just drag and drop a .PAK inside the folder, rename it to whatever you like and enjoy! CREDITS TO @12brendon34 on Discord for finding out how to implement this feature!
- Added "Player Immunity" slider (Player)
- Added "Unlimited Immunity" (Player)
@ -71,6 +71,7 @@ Thank you everyone for the support <3)" },
- Fixed "Disable Out of Bounds Timer" (Player) not working in missions
- Fixed immunity drastically being lowered while rapidly changing the time forward with the "Time" slider (World) at night or while in a dark zone
- Changed the config system to only write to the config file whenever there's a change in the mod menu
- Changed the way the mod menu gets the list of player variables, meaning the player variables list should self-update, with no manual intervention required even after a game update
NOTE: Any mods that are put inside "EGameTools\UserModFiles" as a regular file (.scr or any other file that is usually present in .PAK mods) and NOT a .PAK file will make the game ignore the same files that are present in any of the .PAK mods inside "EGameTools\UserModFiles". I recommend using .PAK for most mods. If you run into issues, try extracting the files inside the PAK into the folder directly.)" }
};

View File

@ -163,8 +163,7 @@ namespace Core {
}
void OnPostUpdate() {
if (!GamePH::PlayerVariables::gotPlayerVars)
GamePH::PlayerVariables::GetPlayerVars();
GamePH::PlayerVariables::GetPlayerVars();
for (auto& menuTab : *Menu::MenuTab::GetInstances())
menuTab.second->Update();
@ -235,8 +234,10 @@ namespace Core {
}
spdlog::warn("Sorting Player Variables");
GamePH::PlayerVariables::SortPlayerVars();
spdlog::info("Player Variables sorted");
std::thread([]() {
GamePH::PlayerVariables::SortPlayerVars();
spdlog::info("Player Variables sorted");
}).detach();
spdlog::warn("Hooking DX11/DX12 renderer");
std::thread([]() {

View File

@ -18,7 +18,7 @@
constexpr const char* MOD_VERSION_STR = "v1.2.0";
constexpr DWORD MOD_VERSION = 10200;
constexpr DWORD GAME_VER_COMPAT = 11601;
constexpr DWORD GAME_VER_COMPAT = 11602;
struct Key {
constexpr Key(std::string_view name, int code, ImGuiKey imGuiCode) : name(name), code(code), imGuiCode(imGuiCode) {}

View File

@ -13,6 +13,7 @@ namespace GamePH {
std::vector<std::pair<std::string, std::pair<std::any, std::string>>> PlayerVariables::playerVarsDefault;
std::vector<std::pair<std::string, std::pair<std::any, std::string>>> PlayerVariables::playerCustomVarsDefault;
bool PlayerVariables::gotPlayerVars = false;
static bool sortedPlayerVars = false;
template <typename T>
static void updateDefaultVar(std::vector<std::pair<std::string, std::pair<std::any, std::string>>>& defaultVars, const std::string& varName, T varValue) {
@ -58,54 +59,149 @@ namespace GamePH {
void PlayerVariables::GetPlayerVars() {
if (gotPlayerVars)
return;
if (!sortedPlayerVars)
return;
if (!Get())
return;
if (playerVars.empty())
return;
if (!Offsets::GetVT_FloatPlayerVariable())
return;
if (!Offsets::GetVT_BoolPlayerVariable())
if (!Offsets::GetVT_FloatPlayerVariable() || !Offsets::GetVT_BoolPlayerVariable())
return;
PDWORD64* playerVarsMem = reinterpret_cast<PDWORD64*>(Get());
DWORD64** playerVarsMem = reinterpret_cast<DWORD64**>(Get());
for (auto& var : playerVars)
processPlayerVar(playerVarsMem, var);
gotPlayerVars = true;
}
void PlayerVariables::SortPlayerVars() {
if (!playerVars.empty())
return;
std::stringstream ss(Config::playerVars);
#pragma region Player Variables Sorting
struct VarTypeFieldMeta {
PlayerVariables::PlayerVarType type;
LPVOID(*getFieldMetaVT)();
};
const std::vector<VarTypeFieldMeta> varTypeFields = {
{ PlayerVariables::PlayerVarType::Float, Offsets::GetVT_TypedFieldMetaFloatPlayerVariable },
{ PlayerVariables::PlayerVarType::Bool, Offsets::GetVT_TypedFieldMetaBoolPlayerVariable }
};
while (ss.good()) {
// separate the string by the , character to get each variable
std::string pVar{};
getline(ss, pVar, ',');
bool isRetInstruction(BYTE* address) {
return address[0] == 0xC3 && address[1] == 0xCC;
}
bool isLeaInstruction(BYTE* address, BYTE REX, BYTE ModRM) {
return address[0] == REX && address[1] == 0x8D && address[2] == ModRM;
}
bool isCallInstruction(BYTE* address) {
return address[0] == 0xE8 && address[4] != 0xE8;
}
bool isBelowFuncSizeLimit(BYTE* address, DWORD64 startOfFunc, size_t sizeLimit) {
return (reinterpret_cast<DWORD64>(address) - startOfFunc) < sizeLimit;
}
std::stringstream ssPVar(pVar);
// to prevent infinite loops, assuming function is no longer than 500000 bytes LMAO Techland... why is your function even like 250000 bytes to begin with? bad code...
static const size_t MAX_FUNC_SIZE = 500000;
static const size_t MAX_LOAD_VAR_FUNC_SIZE = 2000;
std::string varName{};
std::string varType{};
while (ssPVar.good()) {
// seperate the string by the : character to get name and type of variable
std::string subStr{};
getline(ssPVar, subStr, ':');
if (subStr != "float" && subStr != "bool")
varName = subStr;
else
varType = subStr;
const char* getPlayerVarName(BYTE*& funcAddress, DWORD64 startOfFunc) {
const char* playerVarName = nullptr;
while (!playerVarName && !isRetInstruction(funcAddress) && isBelowFuncSizeLimit(funcAddress, startOfFunc, MAX_FUNC_SIZE)) {
// lea r8, varNameString
if (!isLeaInstruction(funcAddress, 0x4C, 0x05)) {
funcAddress++;
continue;
}
PlayerVariables::playerVars.emplace_back(varName, std::make_pair(nullptr, varType));
PlayerVariables::playerVarsDefault.emplace_back(varName, std::make_pair(varType == "float" ? 0.0f : false, varType));
PlayerVariables::playerCustomVarsDefault.emplace_back(varName, std::make_pair(varType == "float" ? 0.0f : false, varType));
playerVarName = reinterpret_cast<const char*>(Utils::Memory::CalcTargetAddrOfRelInst(reinterpret_cast<DWORD64>(funcAddress), 3));
if (!playerVarName) {
funcAddress++;
continue;
}
// add the size of the instruction, so we skip this instruction because this instruction is the name
funcAddress += 0x7;
}
return playerVarName;
}
PlayerVariables::PlayerVarType getPlayerVarType(BYTE*& funcAddress, DWORD64 startOfFunc) {
PlayerVariables::PlayerVarType playerVarType = PlayerVariables::PlayerVarType::NONE;
while (!playerVarType && !isRetInstruction(funcAddress) && isBelowFuncSizeLimit(funcAddress, startOfFunc, MAX_FUNC_SIZE)) {
// call LoadPlayerXVariable
if (!isCallInstruction(funcAddress)) {
funcAddress++;
continue;
}
DWORD64 startOfLoadVarFunc = Utils::Memory::CalcTargetAddrOfRelInst(reinterpret_cast<DWORD64>(funcAddress), 1);
for (const auto& varType : varTypeFields) {
DWORD64 metaVTAddr = reinterpret_cast<DWORD64>(varType.getFieldMetaVT());
BYTE* loadVarFuncAddress = reinterpret_cast<BYTE*>(startOfLoadVarFunc);
DWORD64 metaVTAddrFromFunc = 0;
while (!metaVTAddrFromFunc && !isRetInstruction(loadVarFuncAddress) && isBelowFuncSizeLimit(loadVarFuncAddress, startOfLoadVarFunc, MAX_LOAD_VAR_FUNC_SIZE)) {
// lea rax, typedFieldMetaVT
if (!isLeaInstruction(loadVarFuncAddress, 0x48, 0x05)) {
loadVarFuncAddress++;
continue;
}
metaVTAddrFromFunc = Utils::Memory::CalcTargetAddrOfRelInst(reinterpret_cast<DWORD64>(loadVarFuncAddress), 3);
if (metaVTAddrFromFunc != metaVTAddr) {
metaVTAddrFromFunc = 0;
loadVarFuncAddress++;
continue;
}
}
if (metaVTAddr == metaVTAddrFromFunc) {
playerVarType = varType.type;
break;
}
}
// if it's still NONE after seeing the function doesnt reference any of the variables, break so the loop stops
if (playerVarType == PlayerVariables::PlayerVarType::NONE)
break;
}
return playerVarType;
}
void PlayerVariables::SortPlayerVars() {
DWORD64 startOfFunc = 0;
while (!startOfFunc)
startOfFunc = reinterpret_cast<DWORD64>(Offsets::Get_LoadPlayerVars());
BYTE* funcAddress = reinterpret_cast<BYTE*>(startOfFunc);
while (!isRetInstruction(funcAddress) && (reinterpret_cast<DWORD64>(funcAddress) - startOfFunc) < MAX_FUNC_SIZE) {
const char* playerVarName = getPlayerVarName(funcAddress, startOfFunc);
if (!playerVarName)
continue;
PlayerVarType playerVarType = getPlayerVarType(funcAddress, startOfFunc);
if (!playerVarType)
continue;
std::string varType{};
switch (playerVarType) {
case PlayerVarType::Float:
varType = "float";
break;
case PlayerVarType::Bool:
varType = "bool";
break;
default:
break;
}
PlayerVariables::playerVars.emplace_back(playerVarName, std::make_pair(nullptr, varType));
PlayerVariables::playerVarsDefault.emplace_back(playerVarName, std::make_pair(varType == "float" ? 0.0f : false, varType));
PlayerVariables::playerCustomVarsDefault.emplace_back(playerVarName, std::make_pair(varType == "float" ? 0.0f : false, varType));
}
sortedPlayerVars = true;
}
#pragma endregion
PlayerVariables* PlayerVariables::Get() {
__try {

View File

@ -9,6 +9,13 @@
namespace GamePH {
class PlayerVariables {
public:
enum PlayerVarType {
NONE = 0,
String,
Float,
Bool
};
static std::vector<std::pair<std::string, std::pair<LPVOID, std::string>>> playerVars;
static std::vector<std::pair<std::string, std::pair<std::any, std::string>>> playerVarsDefault;
static std::vector<std::pair<std::string, std::pair<std::any, std::string>>> playerCustomVarsDefault;

View File

@ -6729,8 +6729,7 @@ namespace Menu {
if (!playerVariables.GetValue())
return;
ImGui::BeginDisabled(!GamePH::PlayerVariables::gotPlayerVars);
{
ImGui::BeginDisabled(!GamePH::PlayerVariables::gotPlayerVars); {
if (ImGui::CollapsingHeader("Player variables list", ImGuiTreeNodeFlags_None)) {
ImGui::Indent();
if (ImGui::Button("Save variables to file", "Saves current player variables to chosen file inside the file dialog"))

View File

@ -21,27 +21,25 @@ static DWORD64 Get_## name () {\
return name=reinterpret_cast<DWORD64>(GetModuleHandle(moduleName)) + static_cast<DWORD64>(off);\
}
#define AddVTOffset(name, moduleName, retType)\
#define AddVTOffset(name, moduleName, rttiName, retType)\
static retType GetVT_## name () {\
static retType VT_## name = NULL;\
if (Utils::Memory::IsValidPtr(VT_## name)) return VT_## name;\
return VT_## name=reinterpret_cast<retType>(Utils::RTTI::GetVTablePtr(moduleName, #name));\
return VT_## name=reinterpret_cast<retType>(Utils::RTTI::GetVTablePtr(moduleName, rttiName));\
}
struct Offsets {
AddVTOffset(FloatPlayerVariable, "gamedll_ph_x64_rwdi.dll", LPVOID)
AddVTOffset(BoolPlayerVariable, "gamedll_ph_x64_rwdi.dll", LPVOID)
// ntdll.dll
//AddOffset(LdrpCallInitRoutine, "ntdll.dll", "[48 89 5C 24 08 44 89 44 24 18 48", PatternType::Address, DWORD64*)
//AddOffset(LdrpRunInitializeRoutines, "ntdll.dll", "[48 89 4C 24 08 53 56 57 41 54 41 55 41 56 41 57 48 81 EC 90", PatternType::Address, DWORD64*) // for win7
AddVTOffset(FloatPlayerVariable, "gamedll_ph_x64_rwdi.dll", "FloatPlayerVariable", LPVOID)
AddVTOffset(BoolPlayerVariable, "gamedll_ph_x64_rwdi.dll", "BoolPlayerVariable", LPVOID)
AddVTOffset(TypedFieldMetaFloatPlayerVariable, "gamedll_ph_x64_rwdi.dll", "?$TypedFieldMeta@VFloatPlayerVariable@@@?$FieldsCollection@VPlayerVariables@@@constds", LPVOID)
AddVTOffset(TypedFieldMetaBoolPlayerVariable, "gamedll_ph_x64_rwdi.dll", "?$TypedFieldMeta@VBoolPlayerVariable@@@?$FieldsCollection@VPlayerVariables@@@constds", LPVOID)
// Input related
AddOffset(g_CInput, "engine_x64_rwdi.dll", "48 8B 0D [?? ?? ?? ?? 48 85 C9 74 ?? 48 8B 01 84 D2", Utils::SigScan::PatternType::RelativePointer, DWORD64**)
//AddOffset(WndProc, "engine_x64_rwdi.dll", "40 55 56 57 41 54 41 56 48 83 EC ?? 49 8B E9", PatternType::Address, LPVOID)
// Player vars related
AddStaticOffset(LoadPlayerVariableFunc_size, 0x14E)
//AddOffset(LoadPlayerFloatVariable, "gamedll_ph_x64_rwdi.dll", "E8 [?? ?? ?? ?? 48 8B D0 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8D 94 24 ?? ?? ?? ?? 48 8B 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8D 8C 24 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 8B 84 24 ?? ?? ?? ??", PatternType::RelativePointer, DWORD64*);
AddOffset(LoadPlayerVars, "gamedll_ph_x64_rwdi.dll", "48 89 4C 24 ?? B8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 48 2B E0 48 8B 8C 24", Utils::SigScan::PatternType::Address, LPVOID)
AddOffset(PlayerState, "gamedll_ph_x64_rwdi.dll", "4C 8B 35 [?? ?? ?? ?? 4C 8B E2", Utils::SigScan::PatternType::RelativePointer, LPVOID)
// Game related

View File

@ -26,5 +26,46 @@ namespace Utils {
const DWORD64 moduleEndPoint = moduleEntryPoint + moduleInf.SizeOfImage;
return ptr && (ptr <= moduleEndPoint && ptr >= moduleEntryPoint);
}
static std::string bytesToIDAPattern(BYTE* bytes, size_t size) {
std::stringstream idaPattern;
idaPattern << std::hex << std::uppercase << std::setfill('0');
for (size_t i = 0; i < size; i++) {
const int currentByte = bytes[i];
if (currentByte != 255)
idaPattern << std::setw(2) << currentByte;
else
idaPattern << "??";
if (i != size - 1)
idaPattern << " ";
}
return idaPattern.str();
}
DWORD64 CalcTargetAddrOfRelInst(DWORD64 addrOfInst, size_t opSize) {
int offset = *reinterpret_cast<int*>(addrOfInst + opSize);
return addrOfInst + opSize + 4 + offset;
}
std::vector<DWORD64> GetXrefsTo(DWORD64 address, DWORD64 start, size_t size) {
std::vector<DWORD64> xrefs = {};
const std::string idaPattern = bytesToIDAPattern(reinterpret_cast<BYTE*>(&address), 8);
const DWORD64 end = start + size;
while (start && start < end) {
DWORD64 xref = reinterpret_cast<DWORD64>(Utils::SigScan::PatternScanner::FindPattern(reinterpret_cast<LPVOID>(start), size, { idaPattern.c_str(), Utils::SigScan::PatternType::Address }));
if (!xref)
break;
xrefs.push_back(xref);
start = xref + 8;
}
return xrefs;
}
}
}

View File

@ -10,9 +10,16 @@ namespace Utils {
extern const bool IsAddressValidMod(const DWORD64 ptr, const char* moduleName);
extern DWORD64 CalcTargetAddrOfRelInst(DWORD64 addrOfInst, size_t opSize);
std::vector<DWORD64> GetXrefsTo(DWORD64 address, DWORD64 start, size_t size);
// Templates
template<typename ptrT> bool IsValidPtr(ptrT ptr) {
return !IsBadReadPtr(reinterpret_cast<LPVOID>(ptr), sizeof(LPVOID));
__try {
return !IsBadReadPtr(reinterpret_cast<LPVOID>(ptr), sizeof(LPVOID));
} __except(EXCEPTION_EXECUTE_HANDLER) {
return false;
}
}
template<typename ptrT = LPVOID> bool IsValidPtrMod(ptrT ptr, const char* moduleName, const bool checkForVT = true) {
return IsValidPtr<ptrT>(ptr) && IsAddressValidMod(checkForVT ? *(PDWORD64)(ptr) : (DWORD64)(ptr), moduleName);