mirror of
https://github.com/EricPlayZ/EGameTools.git
synced 2025-07-18 17:37:53 +08:00
- 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:
@ -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.)" }
|
||||
};
|
||||
|
@ -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([]() {
|
||||
|
@ -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) {}
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
Reference in New Issue
Block a user