From e8d0e5f21fc00161a4e302e43b448eefdd4f6fde Mon Sep 17 00:00:00 2001 From: qaate47 Date: Sat, 30 Sep 2023 14:38:40 +0200 Subject: [PATCH] hotload/detour stringtables --- data/badwords.csv | 2 + data/events_schedule.csv | 24 +++ data/stringtables.csv | 6 + data/test_injection.csv | 3 + src/bo4-dll/bo4.hpp | 87 ++++++---- src/bo4-dll/cli_connect.cpp | 5 + src/bo4-dll/data/stringtables.cpp | 272 ++++++++++++++++++++++++++++++ src/bo4-dll/data/stringtables.hpp | 8 + src/bo4-dll/detours.cpp | 11 +- src/bo4-dll/dll_includes.hpp | 3 + src/bo4-dll/hash_lookup.cpp | 2 +- src/cli/tools/pool.cpp | 37 ++-- src/shared/hash.hpp | 4 + 13 files changed, 412 insertions(+), 52 deletions(-) create mode 100644 data/badwords.csv create mode 100644 data/events_schedule.csv create mode 100644 data/stringtables.csv create mode 100644 data/test_injection.csv create mode 100644 src/bo4-dll/data/stringtables.cpp create mode 100644 src/bo4-dll/data/stringtables.hpp diff --git a/data/badwords.csv b/data/badwords.csv new file mode 100644 index 0000000..032d844 --- /dev/null +++ b/data/badwords.csv @@ -0,0 +1,2 @@ +string +bonjour \ No newline at end of file diff --git a/data/events_schedule.csv b/data/events_schedule.csv new file mode 100644 index 0000000..46f194b --- /dev/null +++ b/data/events_schedule.csv @@ -0,0 +1,24 @@ +string,int,int,string +season_2_stream,1573581601,2147968800,all +season_2_bonus_stream,1548784800,2147968800,all +digital_refresh_v3,1569862800,2147968800,all +reserves_drop_12,1573581600,2147968800,all +sunset_features,1573581600,2147364000,all +bribe_offer_launch,1573581600,1576000800,all +bribe_offer_holidays_2019,1576000800,2147364000,all +reserve_completion_meter,1573581600,2147364000,all +global_2xtier_mp_server,1594915200,2147364000,all +global_2xtier_mp_client,1594918800,2147364000,all +global_2xtier_wz_server,1594915200,2147364000,all +global_2xtier_wz_client,1594918800,2147364000,all +global_2wxp_mp_server,1545156000,2147364000,all +global_2wxp_mp_client,1545156000,2147364000,all +global_2wxp_zm_server,1545156000,2147364000,all +global_2wxp_zm_client,1545156000,2147364000,all +global_2xnp_zm_server,1545156000,2147364000,all +global_2xnp_zm_client,1545156000,2147364000,all +global_2xp_mp_server,1545156000,2147364000,all +global_2xp_mp_client,1545156000,2147364000,all +zm_lab_titanium_treble_slot_1,1545156000,2147364000,all +zm_lab_tungsten_tripler_slot_2,1545156000,2147364000,all + diff --git a/data/stringtables.csv b/data/stringtables.csv new file mode 100644 index 0000000..e69246b --- /dev/null +++ b/data/stringtables.csv @@ -0,0 +1,6 @@ +file_name,stringtable_name +events_schedule.csv,gamedata/events/schedule_pc.csv +events_schedule.csv,gamedata/events/schedule_xbox.csv +events_schedule.csv,gamedata/events/schedule_ps4.csv +badwords.csv,gamedata/tables/badwords.csv +test_injection.csv,acts/test_injection.csv \ No newline at end of file diff --git a/data/test_injection.csv b/data/test_injection.csv new file mode 100644 index 0000000..b9d7303 --- /dev/null +++ b/data/test_injection.csv @@ -0,0 +1,3 @@ +string,int,bool,hash,float,undefined +atian,42,true,atianmenu,45.2,, +yolo,69,false,ar_accurate_t8,2.0,, diff --git a/src/bo4-dll/bo4.hpp b/src/bo4-dll/bo4.hpp index 2747d71..ebb721c 100644 --- a/src/bo4-dll/bo4.hpp +++ b/src/bo4-dll/bo4.hpp @@ -127,40 +127,52 @@ namespace bo4 { INT32 Pad0; }; - enum offsetbo4 : uintptr_t { - OFFSET_ScrVm_Error = 0x2770330, - OFFSET_ScrVm_RuntimeError = 0x27775B0, - OFFSET_DB_FindXAssetHeader = 0x2EB75B0, - OFFSET_StringTable_GetAsset = 0x28A2660, - OFFSET_LogCompilerError = 0x2890470, - OFFSET_Error = 0x3D36CC0, - - // Scr link functions - OFFSET_Scr_GetFunctionReverseLookup = 0x33AF8A0, - OFFSET_CScr_GetFunctionReverseLookup = 0x1F132A0, - OFFSET_Scr_GetFunction = 0x1F13140, - OFFSET_CScr_GetFunction = 0x33AF840, - OFFSET_Scr_GetMethod = 0x33AFC20, - OFFSET_CScr_GetMethod = 0x1F13650, - - // Scr vm functions - OFFSET_ScrVm_AddBool = 0x276E760, - OFFSET_ScrVm_AddFloat = 0x276E9B0, - OFFSET_ScrVm_AddHash = 0x276EAB0, - OFFSET_ScrVm_AddInt = 0x276EB80, - OFFSET_ScrVm_AddString = 0x276EE30, - OFFSET_ScrVm_AddUndefined = 0x276F3C0, - OFFSET_ScrVm_AddConstString = 0x276E5F0, - OFFSET_ScrVm_GetBool = 0x2772AB0, - OFFSET_ScrVm_GetFloat = 0x27733F0, - OFFSET_ScrVm_GetHash = 0x27738E0, - OFFSET_ScrVm_GetInt = 0x2773B50, - OFFSET_ScrVm_GetNumParam = 0x2774440, - OFFSET_ScrVm_GetPointerType = 0x27746E0, - OFFSET_ScrVm_GetString = 0x2774840, - OFFSET_ScrVm_GetType = 0x2774A20, - OFFSET_ScrVm_GetVector = 0x2774E40, - OFFSET_ScrVm_GetConstString = 0x02772E10 + struct CmdFunction + { + CmdFunction* next; + uint64_t name; + uint64_t pad0; + uint64_t pad1; + uint64_t pad2; + void (*function)(); + }; + + enum offsetbo4 : uintptr_t { + OFFSET_ScrVm_Error = 0x2770330, + OFFSET_ScrVm_RuntimeError = 0x27775B0, + OFFSET_DB_FindXAssetHeader = 0x2EB75B0, + OFFSET_StringTable_GetAsset = 0x28A2660, + OFFSET_LogCompilerError = 0x2890470, + OFFSET_Error = 0x3D36CC0, + + // Scr link functions + OFFSET_Scr_GetFunctionReverseLookup = 0x33AF8A0, + OFFSET_CScr_GetFunctionReverseLookup = 0x1F132A0, + OFFSET_Scr_GetFunction = 0x1F13140, + OFFSET_CScr_GetFunction = 0x33AF840, + OFFSET_Scr_GetMethod = 0x33AFC20, + OFFSET_CScr_GetMethod = 0x1F13650, + + // Scr vm functions + OFFSET_ScrVm_AddBool = 0x276E760, + OFFSET_ScrVm_AddFloat = 0x276E9B0, + OFFSET_ScrVm_AddHash = 0x276EAB0, + OFFSET_ScrVm_AddInt = 0x276EB80, + OFFSET_ScrVm_AddString = 0x276EE30, + OFFSET_ScrVm_AddUndefined = 0x276F3C0, + OFFSET_ScrVm_AddConstString = 0x276E5F0, + OFFSET_ScrVm_GetBool = 0x2772AB0, + OFFSET_ScrVm_GetFloat = 0x27733F0, + OFFSET_ScrVm_GetHash = 0x27738E0, + OFFSET_ScrVm_GetInt = 0x2773B50, + OFFSET_ScrVm_GetNumParam = 0x2774440, + OFFSET_ScrVm_GetPointerType = 0x27746E0, + OFFSET_ScrVm_GetString = 0x2774840, + OFFSET_ScrVm_GetType = 0x2774A20, + OFFSET_ScrVm_GetVector = 0x2774E40, + OFFSET_ScrVm_GetConstString = 0x02772E10, + + OFFSET_Cmd_AddCommandInternal = 0x3CDEE80, }; typedef ObjFileInfo ObjFileInfoTable[650]; @@ -185,6 +197,8 @@ namespace bo4 { const auto ScrVm_GetType = reinterpret_cast(Relativise(OFFSET_ScrVm_GetType)); const auto Internal_ScrVm_Error = reinterpret_cast(Relativise(OFFSET_ScrVm_Error)); + const auto Cmd_AddCommandInternal = reinterpret_cast(Relativise(OFFSET_Cmd_AddCommandInternal)); + extern t8internal::scrVmPub* scrVmPub; extern t8internal::scrVarPub* scrVarPub; extern ObjFileInfoTable* objFileInfo; @@ -194,4 +208,7 @@ namespace bo4 { void ScrVm_Error(scriptinstance::ScriptInstance inst, const char* format, bool terminal, ...); bool FindGSCFuncLocation(BYTE* location, scriptinstance::ScriptInstance& inst, GSCOBJ*& obj, GSCExport*& exp, UINT32& rloc); -} \ No newline at end of file +} +#define REGISTER_COMMAND(id, name, cmd) static bo4::CmdFunction alloc_func_##id;\ + static bo4::Hash alloc_func_hash##id = { hash::Hash64Pattern(name) };\ + bo4::Cmd_AddCommandInternal(&alloc_func_hash##id, cmd, &alloc_func_##id) \ No newline at end of file diff --git a/src/bo4-dll/cli_connect.cpp b/src/bo4-dll/cli_connect.cpp index 6c8141f..2d1dce9 100644 --- a/src/bo4-dll/cli_connect.cpp +++ b/src/bo4-dll/cli_connect.cpp @@ -88,6 +88,11 @@ void SyncCLIOnce(clisync::CliSyncData* data) { hash_lookup::LoadFile(strLookup.data()); + stringtables::SyncTables(); + + // add the commands + + REGISTER_COMMAND(ACTS_CSV_READER, "acts_csv", []() { stringtables::SyncTables(); }); LOG_INFO("Done."); } diff --git a/src/bo4-dll/data/stringtables.cpp b/src/bo4-dll/data/stringtables.cpp new file mode 100644 index 0000000..15b9960 --- /dev/null +++ b/src/bo4-dll/data/stringtables.cpp @@ -0,0 +1,272 @@ +#include + + +enum StringTableCellType : INT { + STC_TYPE_UNDEFINED = 0, + STC_TYPE_STRING = 1, + STC_TYPE_HASHED = 2, + STC_TYPE_INT = 4, + STC_TYPE_FLOAT = 5, + STC_TYPE_BOOL = 6 +}; + +struct StringTableCell { + BYTE value[20] = { 0 }; + StringTableCellType type = STC_TYPE_UNDEFINED; +}; + +struct StringTableEntry { + UINT64 name; + int pad8 = 0; + int pad12 = 0; + int columnCount = 0; + int rowCount = 0; + int cellscount = 0; + int unk24 = 0; + uintptr_t cells = 0; + StringTableCell* values = NULL; + uintptr_t unk48 = 0; + uintptr_t unk56 = 0; + std::vector allocatedValues{}; + std::vector> strings{}; + std::vector types{}; +}; + +std::vector customStringTableEntries{}; +size_t customStringTableEntriesCount = 0; +std::mutex customStringTableEntriesMutex{}; + + +void stringtables::SyncTables() { + auto parentDir = std::filesystem::path{ g_cliData.workDir } / "data"; + auto cstFile = parentDir / "stringtables.csv"; + + LOG_INFO("Syncing custom stringtable '{}'", cstFile.string()); + + { + // clear old refs + std::lock_guard lg(customStringTableEntriesMutex); + + customStringTableEntriesCount = 0; + customStringTableEntries.clear(); + } + + std::ifstream s{ cstFile }; + + if (!s) { + LOG_WARNING("Can't load custom string tables file {}", cstFile.string()); + return; + } + + std::string line{}; + + if (!(s.good() && std::getline(s, line))) { + LOG_WARNING("Can't read stringtables header"); + + s.close(); + return; + } + + while (s.good() && std::getline(s, line)) { + if (line.empty()) { + continue; + } + + auto shift = line.find(','); + + if (shift == std::string::npos) { + LOG_WARNING("Bad custom string tables line: '{}'", line); + continue; + } + + auto lv = std::string_view(line); + + auto local = lv.substr(0, shift); + auto replaced = hash::Hash64Pattern(lv.substr(shift + 1).data()); + + // reading file + + auto csvFile = parentDir / local; + + LOG_INFO("Loading csv {}->{:x}", csvFile.string(), replaced); + + std::ifstream csv{ csvFile }; + if (!csv) { + LOG_ERROR("Can't open custom csv file {}", csvFile.string()); + continue; + } + + std::string line2{}; + + if (!(csv.good() && std::getline(csv, line2))) { + LOG_ERROR("Can't read {} header", csvFile.string()); + + csv.close(); + continue; + } + + StringTableEntry* ePtr; + { + std::lock_guard lg(customStringTableEntriesMutex); + ePtr = &customStringTableEntries.emplace_back(replaced); + } + StringTableEntry& e = *ePtr; + + bool tableError = false; + + auto hlv2 = std::string_view(line2); + + size_t loc = 0; + + // fetch the types in the header + while (loc < line2.size()) { + auto idx = line2.find(',', loc); + + if (idx == std::string::npos) { + idx = line2.size(); + } + + auto type = hlv2.substr(loc, idx - loc); + + StringTableCellType ht; + + + if (type == "int") { + ht = STC_TYPE_INT; + } + else if (type == "float") { + ht = STC_TYPE_FLOAT; + } + else if (type == "hash") { + ht = STC_TYPE_HASHED; + } + else if (type == "bool") { + ht = STC_TYPE_BOOL; + } + else if (type == "undefined") { + ht = STC_TYPE_UNDEFINED; + } + else { + if (type != "string") { + LOG_WARNING("Undefined CSV header type: {}, using string", type); + } + ht = STC_TYPE_STRING; + } + + e.types.push_back(ht); + + loc = idx + 1; + } + + // we know the number of column + e.columnCount = (int)e.types.size(); + e.rowCount = 0; + + // compute the rows + while (csv.good() && std::getline(csv, line2)) { + if (line2.empty()) { + continue; + } + + e.rowCount++; + + size_t loc = 0; + int col = 0; + while (loc <= line2.size() && col != e.types.size()) { + size_t idx; + if (loc == line2.size()) { + idx = line2.size(); + } + else { + idx = line2.find(',', loc); + } + + if (idx == std::string::npos) { + idx = line2.size(); + } + + auto val = line2.substr(loc, idx - loc); + loc = idx + 1; + + auto& v = e.allocatedValues.emplace_back(); + + switch (v.type = e.types[col]) { + case STC_TYPE_BOOL: + *reinterpret_cast(&v.value[0]) = val == "true"; + break; + case STC_TYPE_STRING: + e.strings.push_back(std::make_shared(val)); + *reinterpret_cast(&v.value[0]) = e.strings[e.strings.size() - 1]->data(); + break; + case STC_TYPE_HASHED: + *reinterpret_cast(&v.value[0]) = hash::Hash64Pattern(val.data()); + break; + case STC_TYPE_FLOAT: + *reinterpret_cast(&v.value[0]) = (FLOAT)std::atof(val.data()); + break; + case STC_TYPE_INT: + *reinterpret_cast(&v.value[0]) = (INT64)std::atoll(val.data()); + break; + default: + *reinterpret_cast(&v.value[0]) = 0; + break; + } + + col++; + } + + if (col != e.types.size()) { + LOG_ERROR("Error at row {}, not enough columns! {} != {}", e.rowCount, col, e.types.size()); + tableError = true; + break; + } + if (loc < line2.size()) { + LOG_ERROR("Error at row {}, too many columns!", e.rowCount); + tableError = true; + break; + } + } + if (tableError) { + // clear it + { + std::lock_guard lg(customStringTableEntriesMutex); + customStringTableEntries.pop_back(); + } + } + else { + e.name = replaced; + // set the allocated values + e.values = e.allocatedValues.empty() ? NULL : &e.allocatedValues[0]; + { + std::lock_guard lg(customStringTableEntriesMutex); + customStringTableEntriesCount++; + } + } + + csv.close(); + + } + + s.close(); +} + +LPVOID stringtables::GetTable(UINT64 name) { + static std::unordered_map dones{}; + LPVOID ret; + { + std::lock_guard lg(customStringTableEntriesMutex); + auto end = customStringTableEntries.begin() + customStringTableEntriesCount; + auto it = std::find_if(customStringTableEntries.begin(), end, [name](const auto& e) { return e.name == name; }); + + if (it == end) { + return NULL; + } + ret = &(*it); + } + auto& d = dones[name]; + if (!d) { + LOG_INFO("replace StringTable {}", hash_lookup::ExtractTmp(scriptinstance::SI_COUNT, name)); + d = true; + } + return ret; +} diff --git a/src/bo4-dll/data/stringtables.hpp b/src/bo4-dll/data/stringtables.hpp new file mode 100644 index 0000000..6be3431 --- /dev/null +++ b/src/bo4-dll/data/stringtables.hpp @@ -0,0 +1,8 @@ +#pragma once +#include + +namespace stringtables { + void SyncTables(); + + LPVOID GetTable(UINT64 name); +} diff --git a/src/bo4-dll/detours.cpp b/src/bo4-dll/detours.cpp index 3333b42..5e5de4f 100644 --- a/src/bo4-dll/detours.cpp +++ b/src/bo4-dll/detours.cpp @@ -1,4 +1,4 @@ -#include "dll_includes.hpp" +#include using namespace custom_gsc_func; @@ -18,7 +18,6 @@ static BuiltinFunction CScr_GetFunction(UINT32 name, BuiltinType* type, int* min static BuiltinFunction Scr_GetMethod(UINT32 name, BuiltinType* type, int* min_args, int* max_args); static BuiltinFunction CScr_GetMethod(UINT32 name, BuiltinType* type, int* min_args, int* max_args); - // Detours static cliconnect::DetourInfo dScrVm_Error{ "ScrVm_Error", bo4::OFFSET_ScrVm_Error, ScrVm_Error }; static cliconnect::DetourInfo dDB_FindXAssetHeader{ "DB_FindXAssetHeader", bo4::OFFSET_DB_FindXAssetHeader, DB_FindXAssetHeader }; @@ -115,7 +114,13 @@ static void* StringTable_GetAsset(char const* name) { } static void* DB_FindXAssetHeader(BYTE type, UINT64* name, bool errorIfMissing, int waitTime) { - // for later + if (type == pool::ASSET_TYPE_STRINGTABLE) { + auto* ptr = stringtables::GetTable(*name); + + if (ptr) { + return ptr; + } + } return dDB_FindXAssetHeader(type, name, errorIfMissing, waitTime); } diff --git a/src/bo4-dll/dll_includes.hpp b/src/bo4-dll/dll_includes.hpp index 5c79c0b..c419d8c 100644 --- a/src/bo4-dll/dll_includes.hpp +++ b/src/bo4-dll/dll_includes.hpp @@ -3,10 +3,13 @@ #include #include + #include "bo4.hpp" #include "cli_connect.hpp" #include "custom_gsc_func.hpp" #include "hash_lookup.hpp" #include "error_handler.hpp" +#include "data/stringtables.hpp" + #define EXPORT extern "C" __declspec(dllexport) \ No newline at end of file diff --git a/src/bo4-dll/hash_lookup.cpp b/src/bo4-dll/hash_lookup.cpp index bd63ca3..4095240 100644 --- a/src/bo4-dll/hash_lookup.cpp +++ b/src/bo4-dll/hash_lookup.cpp @@ -1,7 +1,7 @@ #include static std::unordered_map g_lookupMap{}; -static CHAR g_tempBuffer[2][2000]; +static CHAR g_tempBuffer[3][2000]; bool Add(LPCCH str) { diff --git a/src/cli/tools/pool.cpp b/src/cli/tools/pool.cpp index 4d02304..4433ab9 100644 --- a/src/cli/tools/pool.cpp +++ b/src/cli/tools/pool.cpp @@ -135,22 +135,33 @@ inline bool HexValidString(LPCCH str) { return true; } -void WriteHex(uintptr_t base, BYTE* buff, SIZE_T size, const Process& proc) { +void WriteHex(std::ostream& out, uintptr_t base, BYTE* buff, SIZE_T size, const Process& proc) { CHAR strBuffer[101]; for (size_t j = 0; j < size; j++) { if (j % 8 == 0) { if (base) { - std::cout << std::hex << std::setw(16) << std::setfill('0') << (base + j) << "~"; + out << std::hex << std::setw(16) << std::setfill('0') << (base + j) << "~"; } - std::cout << std::hex << std::setw(3) << std::setfill('0') << j << "|"; + out << std::hex << std::setw(3) << std::setfill('0') << j << "|"; if (j + 7 < size) { - std::cout << std::hex << std::setw(16) << std::setfill('0') << *reinterpret_cast(&buff[j]); + out << std::hex << std::setw(16) << std::setfill('0') << *reinterpret_cast(&buff[j]); } } if (j - j % 8 + 7 >= size) { - std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)buff[j]; + out << std::hex << std::setw(2) << std::setfill('0') << (int)buff[j]; } if ((j + 1) % 8 == 0) { + out << "|"; + + for (size_t i = j - 7; i <= j; i++) { + if (buff[i] >= ' ' && buff[i] <= '~') { + out << (char)buff[i]; + } + else { + out << "."; + } + } + // check x64 values if (j >= 7) { auto val = *reinterpret_cast(&buff[j - 7]); @@ -158,28 +169,28 @@ void WriteHex(uintptr_t base, BYTE* buff, SIZE_T size, const Process& proc) { // not null, hash? auto* h = hashutils::ExtractPtr(val); if (h) { - std::cout << " #" << h; + out << " #" << h; } else if (proc.ReadString(strBuffer, val, sizeof(strBuffer) - 1) >= 0 && HexValidString(strBuffer)) { - std::cout << " ->" << strBuffer; + out << " ->" << strBuffer; } else if (proc.ReadMemory(strBuffer, val, sizeof(UINT64))) { auto r = *reinterpret_cast(strBuffer); auto* h = hashutils::ExtractPtr(r); if (h) { - std::cout << " ->#" << h; + out << " ->#" << h; } else { - std::cout << " ->0x" << std::hex << r; + out << " ->0x" << std::hex << r; } } } } - std::cout << "\n"; + out << "\n"; } } - std::cout << "\n"; + out << "\n"; } int pooltool(const Process& proc, int argc, const char* argv[]) { @@ -574,7 +585,7 @@ int pooltool(const Process& proc, int argc, const char* argv[]) { continue; } - WriteHex(p.buffer, test, sizeof(test), proc); + WriteHex(std::cout, p.buffer, test, sizeof(test), proc); } @@ -596,7 +607,7 @@ int pooltool(const Process& proc, int argc, const char* argv[]) { for (size_t i = 0; i < min(10, entry.itemAllocCount); i++) { std::cout << "Element #" << std::dec << i << " " << std::hex << (entry.pool + i * entry.itemSize) << "\n"; - WriteHex(entry.pool, &raw[i * entry.itemSize], entry.itemSize, proc); + WriteHex(std::cout, entry.pool, &raw[i * entry.itemSize], entry.itemSize, proc); } delete[] raw; diff --git a/src/shared/hash.hpp b/src/shared/hash.hpp index bde7ad7..e72ae8a 100644 --- a/src/shared/hash.hpp +++ b/src/shared/hash.hpp @@ -82,6 +82,10 @@ namespace hash { return std::strtoull(&str[5], nullptr, 16); } + if (!v.rfind("file_", 0)) { + return std::strtoull(&str[5], nullptr, 16); + } + return Hash64(str); } } \ No newline at end of file