diff --git a/primedev/Northstar.cmake b/primedev/Northstar.cmake index c64b0bc7d..df44d83ee 100644 --- a/primedev/Northstar.cmake +++ b/primedev/Northstar.cmake @@ -26,6 +26,7 @@ add_library( "client/r2client.cpp" "client/r2client.h" "client/rejectconnectionfixes.cpp" + "client/weaponx.h" "config/profile.cpp" "config/profile.h" "core/convar/concommand.cpp" @@ -91,6 +92,8 @@ add_library( "mods/modmanager.h" "mods/modsavefiles.cpp" "mods/modsavefiles.h" + "mods/modweaponvars.cpp" + "mods/modweaponvars.h" "plugins/interfaces/interface.h" "plugins/interfaces/interface.cpp" "plugins/interfaces/sys/ISys.h" @@ -135,6 +138,7 @@ add_library( "server/servernethooks.cpp" "server/serverpresence.cpp" "server/serverpresence.h" + "server/weaponx.h" "shared/exploit_fixes/exploitfixes.cpp" "shared/exploit_fixes/exploitfixes_lzss.cpp" "shared/exploit_fixes/exploitfixes_utf8parser.cpp" diff --git a/primedev/client/weaponx.h b/primedev/client/weaponx.h new file mode 100644 index 000000000..7058005b7 --- /dev/null +++ b/primedev/client/weaponx.h @@ -0,0 +1,17 @@ +#pragma once + +inline void* C_WeaponX_vftable = nullptr; + +#pragma pack(push, 1) +struct C_WeaponX +{ +public: + void* vftable; + const char gap_8[5552]; + int currentModBitfield; // 0x15B8 + const char gap_15BC[324]; + // is a struct in reality. + const char weaponVars[0xCA0]; +}; +#pragma pack(pop) +static_assert(offsetof(C_WeaponX, weaponVars) == 0x1700); diff --git a/primedev/mods/modweaponvars.cpp b/primedev/mods/modweaponvars.cpp new file mode 100644 index 000000000..a9ed9f613 --- /dev/null +++ b/primedev/mods/modweaponvars.cpp @@ -0,0 +1,341 @@ + +#include "client/weaponx.h" +#include "server/weaponx.h" +#include "modweaponvars.h" +#include "squirrel/squirrel.h" +#include "client/r2client.h" +#include "server/r2server.h" + +std::map sv_modWeaponVarStrings; +std::map cl_modWeaponVarStrings; +std::hash hasher; + +typedef bool (*calculateWeaponValuesType)(int bitfield, void* weapon, void* weaponVarLocation); +typedef char* (*get2ndParamForRecalcModFuncType)(void* weapon); + +template get2ndParamForRecalcModFuncType get2ndParamForRecalcModFunc; +template calculateWeaponValuesType _CalculateWeaponValues; +template WeaponVarInfo* weaponVarArray; +template std::vector weaponModCallbacks; + +template bool IsWeapon(void** ent) +{ + if (context == ScriptContext::SERVER) + return *ent == CWeaponX_vftable; + else + return *ent == C_WeaponX_vftable; +} + +AUTOHOOK_INIT() + +// Names might be wrong? Correct if needed +AUTOHOOK(CWeaponX__RegenerateAmmo, server.dll + 0x69E7A0, int, , (CWeaponX * self, CBasePlayer* player, int offhandSlot)) +{ + SQObject* entInstance = g_pSquirrel->__sq_createscriptinstance(self); + + g_pSquirrel->Call("CodeCallback_DoWeaponModsForPlayer", entInstance); + return CWeaponX__RegenerateAmmo(self, player, offhandSlot); +} + +AUTOHOOK(C_WeaponX__RegenerateAmmo, client.dll + 0x5B6020, int, , (C_WeaponX * self, CBasePlayer* player, int offhandSlot)) +{ + SQObject* entInstance = g_pSquirrel->__sq_createscriptinstance(self); + + g_pSquirrel->Call("CodeCallback_PredictWeaponMods", entInstance); + return C_WeaponX__RegenerateAmmo(self, player, offhandSlot); +} + +bool IsBadReadPtr(void* p) +{ + MEMORY_BASIC_INFORMATION mbi = {0}; + if (::VirtualQuery(p, &mbi, sizeof(mbi))) + { + DWORD mask = + (PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); + bool b = !(mbi.Protect & mask); + // check the page is not a guard page + if (mbi.Protect & (PAGE_GUARD | PAGE_NOACCESS)) + b = true; + + return b; + } + return true; +} + +AUTOHOOK( + C_WeaponX__CalcWeaponMods, client.dll + 0x3CA0B0, bool, __fastcall, (int mods, char* unk_1, char* weaponVars, bool unk_3, int unk_4)) +{ + bool result; + + if (IsBadReadPtr(weaponVars - offsetof(C_WeaponX, weaponVars))) + return C_WeaponX__CalcWeaponMods(mods, unk_1, weaponVars, unk_3, unk_4); + + if (IsWeapon((void**)(weaponVars - offsetof(C_WeaponX, weaponVars)))) + { + result = C_WeaponX__CalcWeaponMods(mods, unk_1, weaponVars, unk_3, unk_4); + SQObject* entInstance = + g_pSquirrel->__sq_createscriptinstance((void**)(weaponVars - offsetof(C_WeaponX, weaponVars))); + g_pSquirrel->Call("CodeCallback_ApplyModWeaponVars", entInstance); + } + else + { + return C_WeaponX__CalcWeaponMods(mods, unk_1, weaponVars, unk_3, unk_4); + } + + return result; +} + +AUTOHOOK( + CWeaponX__CalcWeaponMods, server.dll + 0x6C8B80, bool, __fastcall, (int unk_0, char* unk_1, char* weaponVars, bool unk_3, int unk_4)) +{ + bool result = CWeaponX__CalcWeaponMods(unk_0, unk_1, weaponVars, unk_3, unk_4); + + if (IsBadReadPtr(weaponVars - offsetof(CWeaponX, weaponVars))) + return result; + + if (result && IsWeapon((void**)(weaponVars - offsetof(CWeaponX, weaponVars)))) + { + SQObject* entInstance = + g_pSquirrel->__sq_createscriptinstance((void**)(weaponVars - offsetof(CWeaponX, weaponVars))); + g_pSquirrel->Call("CodeCallback_ApplyModWeaponVars", entInstance); + } + + return result; +} + +ADD_SQFUNC("void", ModWeaponVars_SetInt, "entity weapon, int weaponVar, int value", "", ScriptContext::SERVER | ScriptContext::CLIENT) +{ + void** ent = g_pSquirrel->getentity(sqvm, 1); + if (!IsWeapon(ent)) + { + g_pSquirrel->raiseerror(sqvm, "Entity is not a weapon"); + return SQRESULT_ERROR; + } + int weaponVar = g_pSquirrel->getinteger(sqvm, 2); + int value = g_pSquirrel->getinteger(sqvm, 3); + if (weaponVar < 1 || weaponVar > WEAPON_VAR_COUNT) // weapon vars start at one and end at 725 inclusive + { + // invalid weapon var index + g_pSquirrel->raiseerror(sqvm, "Invalid eWeaponVar!"); + return SQRESULT_ERROR; + } + + WeaponVarInfo* varInfo = &weaponVarArray[weaponVar]; + if (varInfo->type != WVT_INTEGER) + { + // invalid type used + g_pSquirrel->raiseerror(sqvm, "eWeaponVar is not an integer!"); + return SQRESULT_ERROR; + } + + if (context == ScriptContext::SERVER) + { + CWeaponX* weapon = (CWeaponX*)ent; + *(int*)(&weapon->weaponVars[varInfo->offset]) = value; + } + else // if (context == ScriptContext::CLIENT) + { + C_WeaponX* weapon = (C_WeaponX*)ent; + *(int*)(&weapon->weaponVars[varInfo->offset]) = value; + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("void", ModWeaponVars_SetFloat, "entity weapon, int weaponVar, float value", "", ScriptContext::SERVER | ScriptContext::CLIENT) +{ + void** ent = g_pSquirrel->getentity(sqvm, 1); + if (!IsWeapon(ent)) + { + g_pSquirrel->raiseerror(sqvm, "Entity is not a weapon"); + return SQRESULT_ERROR; + } + int weaponVar = g_pSquirrel->getinteger(sqvm, 2); + float value = g_pSquirrel->getfloat(sqvm, 3); + if (weaponVar < 1 || weaponVar > WEAPON_VAR_COUNT) // weapon vars start at one and end at 725 inclusive + { + // invalid weapon var index + g_pSquirrel->raiseerror(sqvm, "Invalid eWeaponVar!"); + return SQRESULT_ERROR; + } + + WeaponVarInfo* varInfo = &weaponVarArray[weaponVar]; + if (varInfo->type != WVT_FLOAT32) + { + // invalid type used + g_pSquirrel->raiseerror(sqvm, "eWeaponVar is not a float!"); + return SQRESULT_ERROR; + } + + if (context == ScriptContext::SERVER) + { + CWeaponX* weapon = (CWeaponX*)ent; + *(float*)(&weapon->weaponVars[varInfo->offset]) = value; + } + else // if (context == ScriptContext::CLIENT) + { + C_WeaponX* weapon = (C_WeaponX*)ent; + *(float*)(&weapon->weaponVars[varInfo->offset]) = value; + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("int", ModWeaponVars_GetType, "int eWeaponVar", "", ScriptContext::SERVER | ScriptContext::CLIENT) +{ + int weaponVar = g_pSquirrel->getinteger(sqvm, 1); + + if (weaponVar < 1 || weaponVar > WEAPON_VAR_COUNT) // weapon vars start at one and end at 725 inclusive + { + // invalid weapon var index + g_pSquirrel->raiseerror(sqvm, "Invalid eWeaponVar!"); + return SQRESULT_ERROR; + } + WeaponVarInfo* varInfo = &weaponVarArray[weaponVar]; + g_pSquirrel->pushinteger(sqvm, varInfo->type); + + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("void", ModWeaponVars_SetString, "entity weapon, int weaponVar, string value", "", ScriptContext::SERVER | ScriptContext::CLIENT) +{ + void** ent = g_pSquirrel->getentity(sqvm, 1); + if (!IsWeapon(ent)) + { + g_pSquirrel->raiseerror(sqvm, "Entity is not a weapon"); + return SQRESULT_ERROR; + } + int weaponVar = g_pSquirrel->getinteger(sqvm, 2); + std::string value = g_pSquirrel->getstring(sqvm, 3); + if (weaponVar < 1 || weaponVar > WEAPON_VAR_COUNT) // weapon vars start at one and end at 725 inclusive + { + // invalid weapon var index + g_pSquirrel->raiseerror(sqvm, "Invalid eWeaponVar!"); + return SQRESULT_ERROR; + } + + const WeaponVarInfo* varInfo = &weaponVarArray[weaponVar]; + + if (varInfo->type != WVT_STRING) + { + // invalid type used + g_pSquirrel->raiseerror(sqvm, "eWeaponVar is not a string!"); + return SQRESULT_ERROR; + } + + size_t valueHash = hasher(value); + if (context == ScriptContext::SERVER) + { + if (sv_modWeaponVarStrings.find(valueHash) == sv_modWeaponVarStrings.end()) + sv_modWeaponVarStrings.emplace(valueHash, value); + CWeaponX* weapon = (CWeaponX*)ent; + *(const char**)(&weapon->weaponVars[varInfo->offset]) = sv_modWeaponVarStrings[valueHash].c_str(); + } + else // if (context == ScriptContext::CLIENT) + { + if (cl_modWeaponVarStrings.find(valueHash) == cl_modWeaponVarStrings.end()) + cl_modWeaponVarStrings.emplace(valueHash, value); + + C_WeaponX* weapon = (C_WeaponX*)ent; + *(const char**)(&weapon->weaponVars[varInfo->offset]) = cl_modWeaponVarStrings[valueHash].c_str(); + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("void", ModWeaponVars_SetBool, "entity weapon, int weaponVar, bool value", "", ScriptContext::SERVER | ScriptContext::CLIENT) +{ + void** ent = g_pSquirrel->getentity(sqvm, 1); + if (!IsWeapon(ent)) + { + g_pSquirrel->raiseerror(sqvm, "Entity is not a weapon"); + return SQRESULT_ERROR; + } + int weaponVar = g_pSquirrel->getinteger(sqvm, 2); + bool value = g_pSquirrel->getbool(sqvm, 3); + if (weaponVar < 1 || weaponVar > WEAPON_VAR_COUNT) // weapon vars start at one and end at 725 inclusive + { + // invalid weapon var index + g_pSquirrel->raiseerror(sqvm, "Invalid eWeaponVar!"); + return SQRESULT_ERROR; + } + + WeaponVarInfo* varInfo = &weaponVarArray[weaponVar]; + if (varInfo->type != WVT_BOOLEAN) + { + // invalid type used + g_pSquirrel->raiseerror(sqvm, "eWeaponVar is not a boolean!"); + return SQRESULT_ERROR; + } + + if (context == ScriptContext::SERVER) + { + CWeaponX* weapon = (CWeaponX*)ent; + *(bool*)(&weapon->weaponVars[varInfo->offset]) = value; + } + else // if (context == ScriptContext::CLIENT) + { + C_WeaponX* weapon = (C_WeaponX*)ent; + *(bool*)(&weapon->weaponVars[varInfo->offset]) = value; + } + + return SQRESULT_NULL; +} + +// SERVER only because client does this very often +ADD_SQFUNC("void", ModWeaponVars_CalculateWeaponMods, "entity weapon", "", ScriptContext::SERVER | ScriptContext::CLIENT) +{ + void** ent = g_pSquirrel->getentity(sqvm, 1); + if (ent == nullptr) + { + g_pSquirrel->raiseerror(sqvm, "Entity is not a weapon"); + return SQRESULT_ERROR; + } + if (!IsWeapon(ent)) + { + g_pSquirrel->raiseerror(sqvm, "Entity is not a weapon"); + return SQRESULT_ERROR; + } + if (context == ScriptContext::SERVER) + { + CWeaponX* weapon = (CWeaponX*)ent; + char* secondParamForCalcWeaponValues = get2ndParamForRecalcModFunc(weapon); + if (!_CalculateWeaponValues(weapon->currentModBitfield, secondParamForCalcWeaponValues, (void*)&weapon->weaponVars)) + { + g_pSquirrel->raiseerror(sqvm, "Weapon var calculation failed..."); + return SQRESULT_ERROR; + } + } + else + { + C_WeaponX* weapon = (C_WeaponX*)ent; + char* secondParamForCalcWeaponValues = get2ndParamForRecalcModFunc(weapon); + if (!_CalculateWeaponValues(weapon->currentModBitfield, secondParamForCalcWeaponValues, (void*)&weapon->weaponVars)) + { + g_pSquirrel->raiseerror(sqvm, "Weapon var calculation failed..."); + return SQRESULT_ERROR; + } + } + + return SQRESULT_NULL; +} + +ON_DLL_LOAD("server.dll", ModWeaponVars_ServerInit, (CModule mod)) +{ + const ScriptContext context = ScriptContext::SERVER; + weaponVarArray = mod.Offset(0x997dc0).RCast(); + _CalculateWeaponValues = mod.Offset(0x6C8B30).RCast(); + get2ndParamForRecalcModFunc = mod.Offset(0xF0CD0).RCast(); + CWeaponX_vftable = mod.Offset(0x98E2B8).RCast(); + AUTOHOOK_DISPATCH_MODULE(server.dll); +} + +ON_DLL_LOAD_CLIENT("client.dll", ModWeaponVars_ClientInit, (CModule mod)) +{ + const ScriptContext context = ScriptContext::CLIENT; + weaponVarArray = mod.Offset(0x942ca0).RCast(); + _CalculateWeaponValues = mod.Offset(0x3CA060).RCast(); + get2ndParamForRecalcModFunc = mod.Offset(0xBB4B0).RCast(); + C_WeaponX_vftable = mod.Offset(0x998638).RCast(); + AUTOHOOK_DISPATCH_MODULE(client.dll); +} diff --git a/primedev/mods/modweaponvars.h b/primedev/mods/modweaponvars.h new file mode 100644 index 000000000..9faeee315 --- /dev/null +++ b/primedev/mods/modweaponvars.h @@ -0,0 +1,34 @@ +#pragma once +enum WeaponVarType : __int8 +{ + WVT_INTEGER = 1, + WVT_FLOAT32 = 2, + WVT_BOOLEAN = 3, + WVT_STRING = 4, + // NOT SUPPORTED ATM // + WVT_ASSET = 5, + WVT_VECTOR = 6 +}; + +struct WeaponModCallback +{ + int priority; + SQObject func; +}; + +#pragma pack(push, 1) +class WeaponVarInfo +{ +public: + const char unk_0[25]; // 0x0 + char type; // 0x19 + int unk_1A; + unsigned short offset; +}; +#pragma pack(pop) +static_assert(sizeof(WeaponVarInfo) == 0x20); + +const int WEAPON_VAR_COUNT = 725; +extern std::map sv_modWeaponVarStrings; +extern std::map cl_modWeaponVarStrings; +extern std::hash hasher; diff --git a/primedev/server/weaponx.h b/primedev/server/weaponx.h new file mode 100644 index 000000000..4120d85b6 --- /dev/null +++ b/primedev/server/weaponx.h @@ -0,0 +1,20 @@ +#pragma once +#include "client/weaponx.h" + +inline void* CWeaponX_vftable = nullptr; + +#pragma pack(push, 1) +struct CWeaponX +{ +public: + void* vftable; + const char gap_8[4800]; + int currentModBitfield; // 0x12C8 + const char gap_12C4[324]; // 0x12CC + // this is used by the game as the initial offset + // for getting eWeaponVar values. No idea about + // the actual datatype. Start of another struct? + const char weaponVars[0xCA0]; // 0x1410 +}; +#pragma pack(pop) +static_assert(offsetof(CWeaponX, weaponVars) == 0x1410); diff --git a/primedev/squirrel/squirrel.cpp b/primedev/squirrel/squirrel.cpp index 4141d04df..171f2d909 100644 --- a/primedev/squirrel/squirrel.cpp +++ b/primedev/squirrel/squirrel.cpp @@ -10,6 +10,7 @@ #include "plugins/pluginmanager.h" #include "ns_version.h" #include "core/vanilla.h" +#include "mods/modweaponvars.h" #include "vscript/vscript.h" @@ -236,6 +237,11 @@ template void SquirrelManager::VMDestroyed() } } + if (context == ScriptContext::SERVER) + sv_modWeaponVarStrings.clear(); + else if (context == ScriptContext::CLIENT) + cl_modWeaponVarStrings.clear(); + g_pPluginManager->InformSqvmDestroying(m_pSQVM); // Discard the previous vm and delete the message buffer.