From 50f6049a7ed82fefa9ce3f8e25684350c2ab89fc Mon Sep 17 00:00:00 2001 From: Antoine Willerval Date: Fri, 15 Dec 2023 10:01:22 +0100 Subject: [PATCH] Custom xassets loading (#28) * add custom gsc/lua/raw file loading and gsc hooks * add gsic file injection with detours/builtins/lazylinks * add custom string tables, fix old func --- .gitmodules | 3 + deps/premake/rapidcsv.lua | 18 + deps/rapidcsv | 1 + source/proxy-dll/component/gsc_custom.cpp | 322 +++++++++ source/proxy-dll/component/gsc_custom.hpp | 29 + source/proxy-dll/component/gsc_funcs.cpp | 100 ++- source/proxy-dll/component/gsc_funcs.hpp | 4 + source/proxy-dll/component/mods.cpp | 743 +++++++++++++++++++++ source/proxy-dll/definitions/game.hpp | 155 ++++- source/proxy-dll/definitions/variables.cpp | 21 + source/proxy-dll/definitions/variables.hpp | 2 + source/proxy-dll/definitions/xassets.hpp | 260 +++++++ source/proxy-dll/std_include.hpp | 1 + 13 files changed, 1628 insertions(+), 31 deletions(-) create mode 100644 deps/premake/rapidcsv.lua create mode 160000 deps/rapidcsv create mode 100644 source/proxy-dll/component/gsc_custom.cpp create mode 100644 source/proxy-dll/component/gsc_custom.hpp create mode 100644 source/proxy-dll/component/mods.cpp create mode 100644 source/proxy-dll/definitions/xassets.hpp diff --git a/.gitmodules b/.gitmodules index aa85270..c647edd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -28,3 +28,6 @@ [submodule "deps/xxhash"] path = deps/xxhash url = https://github.com/stbrumme/xxhash.git +[submodule "deps/rapidcsv"] + path = deps/rapidcsv + url = https://github.com/d99kris/rapidcsv diff --git a/deps/premake/rapidcsv.lua b/deps/premake/rapidcsv.lua new file mode 100644 index 0000000..5a0d248 --- /dev/null +++ b/deps/premake/rapidcsv.lua @@ -0,0 +1,18 @@ +rapidcsv = { + source = path.join(dependencies.basePath, "rapidcsv"), +} + +function rapidcsv.import() + rapidcsv.includes() +end + +function rapidcsv.includes() + includedirs { + path.join(rapidcsv.source, "src"), + } +end + +function rapidcsv.project() +end + +table.insert(dependencies, rapidcsv) diff --git a/deps/rapidcsv b/deps/rapidcsv new file mode 160000 index 0000000..f032c52 --- /dev/null +++ b/deps/rapidcsv @@ -0,0 +1 @@ +Subproject commit f032c527caa2533a3b5b17d11d9c29feaa1cca06 diff --git a/source/proxy-dll/component/gsc_custom.cpp b/source/proxy-dll/component/gsc_custom.cpp new file mode 100644 index 0000000..65708d3 --- /dev/null +++ b/source/proxy-dll/component/gsc_custom.cpp @@ -0,0 +1,322 @@ +#include +#include "gsc_custom.hpp" +#include "gsc_funcs.hpp" +#include "definitions/game.hpp" +#include "loader/component_loader.hpp" +#include +#include + +namespace gsc_custom +{ + namespace + { + struct gsic_link_detour_data + { + const gsic_info& gsic_info; + uint32_t latest_script_index{}; + }; + + template + inline byte* align_ptr(byte* ptr) + { + return reinterpret_cast((reinterpret_cast(ptr) + sizeof(T) - 1) & ~(sizeof(T) - 1)); + } + + std::vector gsic_data[game::SCRIPTINSTANCE_MAX]{ {}, {} }; + + byte* find_export(game::scriptInstance_t inst, uint64_t target_script, uint32_t name_space, uint32_t name) + { + uint32_t count = game::gObjFileInfoCount[inst]; + + game::objFileInfo_t* end = (*game::gObjFileInfo)[inst] + count; + + auto its = std::find_if((*game::gObjFileInfo)[inst], end, + [target_script](const game::objFileInfo_t& info) { return info.activeVersion->name == target_script; }); + + if (its == end) + { + return nullptr; // can't find target script + } + + game::GSC_OBJ* obj = its->activeVersion; + + auto ite = std::find_if(obj->get_exports(), obj->get_exports_end(), + [name_space, name](const game::GSC_EXPORT_ITEM& exp) { return exp.name == name && exp.name_space == name_space; }); + + if (ite == obj->get_exports_end()) + { + return nullptr; // can't find target export + } + + return obj->magic + ite->address; + } + + byte* find_detour(game::scriptInstance_t inst, byte* origin, uint64_t target_script, uint32_t name_space, uint32_t name) + { + for (const gsic_link_detour_data& data : gsic_data[inst]) + { + for (const gsic_detour& detour : data.gsic_info.detours) + { + if (detour.target_script != target_script || name_space != detour.replace_namespace || name != detour.replace_function) + { + continue; // not our target + } + if (detour.fixup_function <= origin && detour.fixup_function + detour.fixup_offset > origin) + { + continue; // inside the detour + } + return detour.fixup_function; + } + } + + return nullptr; + } + + + void clear_gsic(game::scriptInstance_t inst) + { + // clear previously register GSIC + gsic_data[inst].clear(); + } + + void link_detours(game::scriptInstance_t inst) + { + // link the GSIC detours + + for (gsic_link_detour_data& data : gsic_data[inst]) + { + for (; data.latest_script_index < game::gObjFileInfoCount[inst]; data.latest_script_index++) + { + game::GSC_OBJ* obj = (*game::gObjFileInfo)[inst][data.latest_script_index].activeVersion; + // link the detour + for (const gsic_detour& detour : data.gsic_info.detours) + { + if (std::find(obj->get_includes(), obj->get_includes_end(), detour.target_script) == obj->get_includes_end() && obj->name != detour.target_script) + { + continue; // the target script isn't in the includes, so we have nothing to link + } + + // reading imports + + game::GSC_IMPORT_ITEM* import_item = obj->get_imports(); + + for (size_t i = 0; i < obj->imports_count; i++) + { + uint32_t* addresses = reinterpret_cast(import_item + 1); + + if (import_item->name_space == detour.replace_namespace && import_item->name == detour.replace_function) + { + // replace the linking + + // see GscObjResolve(scriptInstance_t, GSC_OBJ*)0x142746A30_g for info + + int offset; + switch (import_item->flags & 0xF) + { + case 1: // &namespace::function + { + offset = 0; // only function/method calls are using params + } + break; + case 2: // func() + case 3: // thread func() + case 4: // childthread func() + case 5: // self method() + case 6: // self thread method() + case 7: // self childthread method() + { + offset = 1; + } + break; + default: + logger::write(logger::LOG_TYPE_ERROR, std::format("can't link import item with flag {:x}", import_item->flags & 0xF)); + assert(false); // if the game didn't crash before this point it's impressive + return; + } + + for (size_t j = 0; j < import_item->num_address; j++) + { + // opcode loc + byte* loc = align_ptr(obj->magic + addresses[j]); + + if (loc >= detour.fixup_function && loc < detour.fixup_function + detour.fixup_size) + { + continue; // this import is inside the detour definition, we do not replace it + } + + // align for ptr + byte** ptr_loc = (byte**)align_ptr(loc + 2 + offset); +#ifdef _DEBUG_DETOUR + logger::write(logger::LOG_TYPE_DEBUG, + std::format( + "linking detours for namespace_{:x}::function_{:x} at {} : {} -> {} (0x{:x})", + detour.replace_namespace, obj->name, detour.replace_function, + (void*)ptr_loc, + (void*)(*ptr_loc), + (void*)(detour.fixup_function), *(uint64_t*)detour.fixup_function) + ); +#endif + + // TODO: replace opcode for api function detours + //uint16_t* opcode_loc = (uint16_t*)loc; + + *ptr_loc = detour.fixup_function; + } + } + + // goto to the next element after the addresses + import_item = reinterpret_cast(addresses + import_item->num_address); + } + } + } + } + } + } + + void sync_gsic(game::scriptInstance_t inst, gsic_info& info) + { + // add a new GSIC file for this instance + + auto& inst_data = gsic_data[inst]; + + if (std::find_if(inst_data.begin(), inst_data.end(), [&info](const gsic_link_detour_data& data) { return &data.gsic_info == &info; }) != inst_data.end()) + { + return; // already sync + } + + for (const gsic_link_detour_data& link_data : inst_data) + { + for (const gsic_detour& detour : link_data.gsic_info.detours) + { + auto it = std::find_if(info.detours.begin(), info.detours.end(), + [&detour](const gsic_detour& detour2) + { + return detour.target_script == detour2.target_script + && detour.replace_function == detour2.replace_function + && detour.replace_namespace == detour2.replace_namespace; + } + ); + + if (it != info.detours.end()) + { + gsc_funcs::gsc_error("the detour namespace_%x::function_%x was registered twice", inst, true, detour.replace_namespace, detour.target_script, detour.replace_function); + return; + } + } + } + + inst_data.emplace_back(info); + } + + void vm_op_custom_lazylink(game::scriptInstance_t inst, game::function_stack_t* fs_0, game::ScrVmContext_t* vmc, bool* terminate) + { + byte* base = align_ptr(fs_0->pos); + uint32_t name_space = *(uint32_t*)base; + uint32_t name = *(uint32_t*)(base + 4); + uint64_t script = *(uint64_t*)(base + 8); + + // pass the data + fs_0->pos = base + 0x10; + + // find the detour first + byte* detour_result = find_detour(inst, base, script, name_space, name); + + if (detour_result) + { + // push detour function + fs_0->top++; + + fs_0->top->type = game::TYPE_SCRIPT_FUNCTION; + fs_0->top->u.codePosValue = detour_result; + } + else if (script) + { + // lazy link script function + byte* exp = find_export(inst, script, name_space, name); + + + // push the result or undefined + fs_0->top++; + + if (exp) + { + fs_0->top->type = game::TYPE_SCRIPT_FUNCTION; + fs_0->top->u.codePosValue = exp; + } + else + { + fs_0->top->type = game::TYPE_UNDEFINED; + fs_0->top->u.intValue = 0; + } + } + else + { + // lazy link api function + int type = 0; + int unused = 0; + void* func = nullptr; + + if (inst) + { + func = game::CScr_GetFunction(name, &type, &unused, &unused); + if (!func || (type && !gsc_funcs::enable_dev_func)) + { + func = game::CScr_GetMethod(name, &type, &unused, &unused); + } + } + else + { + func = game::Scr_GetFunction(name, &type, &unused, &unused); + if (!func || (type && !gsc_funcs::enable_dev_func)) + { + func = game::Scr_GetMethod(name, &type, &unused, &unused); + } + } + + fs_0->top++; + + if (func && (!type || gsc_funcs::enable_dev_func)) + { + // do not allow dev functions if it is not asked by the user + fs_0->top->type = game::TYPE_API_FUNCTION; + fs_0->top->u.codePosValue = (byte*)func; + } + else + { + fs_0->top->type = game::TYPE_UNDEFINED; + fs_0->top->u.intValue = 0; + } + } + + } + + utilities::hook::detour scr_get_gsc_obj_hook; + void scr_get_gsc_obj_stub(game::scriptInstance_t inst, game::BO4_AssetRef_t* name, bool runScript) + { + if (game::gObjFileInfoCount[inst] == 0) + { + // first script for this instance, we can clear previous GSIC + gsc_custom::clear_gsic(inst); + } + + scr_get_gsc_obj_hook.invoke(inst, name, runScript); + + // link the detours with the new linked scripts group + gsc_custom::link_detours(inst); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + // t8compiler custom opcode + game::gVmOpJumpTable[0x16] = vm_op_custom_lazylink; + + // group gsc link + scr_get_gsc_obj_hook.create(0x142748BB0_g, scr_get_gsc_obj_stub); + } + }; +} + +REGISTER_COMPONENT(gsc_custom::component) \ No newline at end of file diff --git a/source/proxy-dll/component/gsc_custom.hpp b/source/proxy-dll/component/gsc_custom.hpp new file mode 100644 index 0000000..af5e549 --- /dev/null +++ b/source/proxy-dll/component/gsc_custom.hpp @@ -0,0 +1,29 @@ +#pragma once +#include "definitions/game.hpp" + +namespace gsc_custom +{ + enum gsic_field_type + { + GSIC_FIELD_DETOUR = 0 + }; + + struct gsic_detour + { + uint32_t fixup_name{}; + uint32_t replace_namespace{}; + uint32_t replace_function{}; + uint32_t fixup_offset{}; + uint32_t fixup_size{}; + uint64_t target_script{}; + byte* fixup_function{}; + }; + + struct gsic_info + { + bool sync{}; + std::vector detours{}; + }; + + void sync_gsic(game::scriptInstance_t inst, gsic_info& info); +} \ No newline at end of file diff --git a/source/proxy-dll/component/gsc_funcs.cpp b/source/proxy-dll/component/gsc_funcs.cpp index 48fc283..b88a891 100644 --- a/source/proxy-dll/component/gsc_funcs.cpp +++ b/source/proxy-dll/component/gsc_funcs.cpp @@ -1,5 +1,6 @@ #include #include "gsc_funcs.hpp" +#include "gsc_custom.hpp" #include "definitions/game.hpp" #include "loader/component_loader.hpp" #include "component/scheduler.hpp" @@ -81,7 +82,7 @@ namespace gsc_funcs delta = 0; break; case HUD_ALIGN_X_CENTER: - delta = width / 2; + delta = -width / 2; break; case HUD_ALIGN_X_RIGHT: delta = -width; @@ -109,7 +110,7 @@ namespace gsc_funcs delta = 0; break; case HUD_ALIGN_Y_MIDDLE: - delta = height / 2; + delta = -height / 2; break; case HUD_ALIGN_Y_BOTTOM: delta = -height; @@ -131,33 +132,33 @@ namespace gsc_funcs std::unordered_map hud_elems{}; - void shield_log(game::scriptInstance_t inst) + void shield_log_from(game::scriptInstance_t inst, unsigned int offset) { - game::ScrVarType_t type = game::ScrVm_GetType(inst, 0); + game::ScrVarType_t type = game::ScrVm_GetType(inst, offset); switch (type) { case game::TYPE_UNDEFINED: logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] undefined", inst ? "CSC" : "GSC"); break; case game::TYPE_STRING: - logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %s", inst ? "CSC" : "GSC", game::ScrVm_GetString(inst, 0)); + logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %s", inst ? "CSC" : "GSC", game::ScrVm_GetString(inst, offset)); break; case game::TYPE_HASH: { game::BO4_AssetRef_t hash{}; - logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %llx", inst ? "CSC" : "GSC", game::ScrVm_GetHash(&hash, inst, 0)->hash); + logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %llx", inst ? "CSC" : "GSC", game::ScrVm_GetHash(&hash, inst, offset)->hash); } break; case game::TYPE_INTEGER: - logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %lld", inst ? "CSC" : "GSC", game::ScrVm_GetInt(inst, 0)); + logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %lld", inst ? "CSC" : "GSC", game::ScrVm_GetInt(inst, offset)); break; case game::TYPE_FLOAT: - logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %f", inst ? "CSC" : "GSC", game::ScrVm_GetFloat(inst, 0)); + logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %f", inst ? "CSC" : "GSC", game::ScrVm_GetFloat(inst, offset)); break; case game::TYPE_VECTOR: { game::vec3_t vec{}; - game::ScrVm_GetVector(inst, 0, &vec); + game::ScrVm_GetVector(inst, offset, &vec); logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] (%f, %f, %f)", inst ? "CSC" : "GSC", vec[0], vec[1], vec[2]); } break; @@ -167,6 +168,11 @@ namespace gsc_funcs } } + void shield_log(game::scriptInstance_t inst) + { + shield_log_from(inst, 0); + } + void shield_clear_hud_elems(game::scriptInstance_t inst) { hud_elems.clear(); @@ -352,14 +358,7 @@ namespace gsc_funcs return; } - float val = game::ScrVm_GetFloat(inst, 1); - if (val >= 0.01) - { - it->second.y = val; - } - else { - gsc_error("bad scale value: %f", inst, false, val); - } + it->second.y = game::ScrVm_GetFloat(inst, 1); } void shield_hud_elem_set_color(game::scriptInstance_t inst) @@ -447,7 +446,38 @@ namespace gsc_funcs return; } - it->second.scale = game::ScrVm_GetFloat(inst, 1); + float val = game::ScrVm_GetFloat(inst, 1); + if (val >= 0.01) + { + it->second.scale = val; + } + else { + gsc_error("bad scale value: %f", inst, false, val); + } + } + + void serious_custom_func(game::scriptInstance_t inst) + { + // the t8compiler is converting the calls of compiler::func_name(...) to + // SeriousCustom(#"func_name", ...) using the canon id hash for func_name + uint64_t hash = ((uint64_t)game::ScrVm_GetInt(inst, 0)) & 0xFFFFFFFF; + + static uint64_t detour_cid = canon_hash("detour"); + static uint64_t relink_detour_cid = canon_hash("relinkdetours"); + static uint64_t nprintln_cid = canon_hash("nprintln"); + + if (hash == detour_cid || hash == relink_detour_cid) + { + logger::write(logger::LOG_TYPE_WARN, "a detour link function was called, but it isn't required by this client."); + } + else if (hash == nprintln_cid) + { + shield_log_from(inst, 1); + } + else + { + gsc_error("compiler::function_%llx not implemented", inst, false, hash); + } } game::BO4_BuiltinFunctionDef custom_functions_gsc[] = @@ -501,19 +531,26 @@ namespace gsc_funcs .actionFunc = shield_hud_elem_set_y, .type = 0, }, - { // ShieldHudElemSetY(id, color_vec) | ShieldHudElemSetY(id, color_rgba) | ShieldHudElemSetY(id, r, g, b) + { // ShieldHudElemSetColor(id, color_vec) | ShieldHudElemSetColor(id, color_rgba) | ShieldHudElemSetColor(id, r, g, b) .canonId = canon_hash("ShieldHudElemSetColor"), .min_args = 2, .max_args = 4, .actionFunc = shield_hud_elem_set_color, .type = 0, }, - { // ShieldHudElemSetY(id, scale) + { // ShieldHudElemSetScale(id, scale) .canonId = canon_hash("ShieldHudElemSetScale"), .min_args = 2, .max_args = 2, .actionFunc = shield_hud_elem_set_scale, .type = 0, + }, + { // SeriousCustom(func_hash, ...) + .canonId = canon_hash(serious_custom_func_name), + .min_args = 1, + .max_args = 255, + .actionFunc = serious_custom_func, + .type = 0, } }; game::BO4_BuiltinFunctionDef custom_functions_csc[] = @@ -567,19 +604,26 @@ namespace gsc_funcs .actionFunc = shield_hud_elem_set_y, .type = 0, }, - { // ShieldHudElemSetY(id, color_vec) | ShieldHudElemSetY(id, color_rgba) | ShieldHudElemSetY(id, r, g, b) + { // ShieldHudElemSetColor(id, color_vec) | ShieldHudElemSetColor(id, color_rgba) | ShieldHudElemSetColor(id, r, g, b) .canonId = canon_hash("ShieldHudElemSetColor"), .min_args = 2, .max_args = 4, .actionFunc = shield_hud_elem_set_color, .type = 0, }, - { // ShieldHudElemSetY(id, scale) + { // ShieldHudElemSetScale(id, scale) .canonId = canon_hash("ShieldHudElemSetScale"), .min_args = 2, .max_args = 2, .actionFunc = shield_hud_elem_set_scale, .type = 0, + }, + { // SeriousCustom(func_hash, ...) + .canonId = canon_hash(serious_custom_func_name), + .min_args = 1, + .max_args = 255, + .actionFunc = serious_custom_func, + .type = 0, } }; @@ -748,34 +792,34 @@ namespace gsc_funcs { const char* msg = game::ScrVm_GetString(inst, 1); sprintf_s(buffer[inst], "assert fail: %s", msg); - game::scrVarPub[inst]->error_message = buffer[inst]; + game::scrVarPub[inst].error_message = buffer[inst]; } break; case 1385570291:// AssertMsg(msg) { const char* msg = game::ScrVm_GetString(inst, 0); sprintf_s(buffer[inst], "assert fail: %s", msg); - game::scrVarPub[inst]->error_message = buffer[inst]; + game::scrVarPub[inst].error_message = buffer[inst]; } break; case 2532286589:// ErrorMsg(msg) { const char* msg = game::ScrVm_GetString(inst, 0); sprintf_s(buffer[inst], "error: %s", msg); - game::scrVarPub[inst]->error_message = buffer[inst]; + game::scrVarPub[inst].error_message = buffer[inst]; } break; default: // put custom message for our id if (code == custom_error_id) { - game::scrVarPub[inst]->error_message = unused; + game::scrVarPub[inst].error_message = unused; } break; } logger::write(terminal ? logger::LOG_TYPE_ERROR : logger::LOG_TYPE_WARN, "[ %s VM ] %s (%lld)", - inst ? "CSC" : "GSC", game::scrVarPub[inst]->error_message ? game::scrVarPub[inst]->error_message : "no message", code); + inst ? "CSC" : "GSC", game::scrVarPub[inst].error_message ? game::scrVarPub[inst].error_message : "no message", code); scrvm_error.invoke(code, inst, unused, terminal); } @@ -817,7 +861,7 @@ namespace gsc_funcs // log gsc errors scrvm_error.create(0x142770330_g, scrvm_error_stub); utilities::hook::jump(0x142890470_g, scrvm_log_compiler_error); - + scheduler::loop(draw_hud, scheduler::renderer); } }; diff --git a/source/proxy-dll/component/gsc_funcs.hpp b/source/proxy-dll/component/gsc_funcs.hpp index ec0ae7b..0cb592b 100644 --- a/source/proxy-dll/component/gsc_funcs.hpp +++ b/source/proxy-dll/component/gsc_funcs.hpp @@ -4,6 +4,10 @@ namespace gsc_funcs { + constexpr auto serious_custom_func_name = "SeriousCustom"; + + extern bool enable_dev_func; + uint32_t canon_hash(const char* str); constexpr uint64_t custom_error_id = 0x42693201; diff --git a/source/proxy-dll/component/mods.cpp b/source/proxy-dll/component/mods.cpp new file mode 100644 index 0000000..351d7b7 --- /dev/null +++ b/source/proxy-dll/component/mods.cpp @@ -0,0 +1,743 @@ +#include +#include "definitions/xassets.hpp" +#include "definitions/game.hpp" + +#include "game_console.hpp" + +#include "gsc_funcs.hpp" +#include "gsc_custom.hpp" +#include "loader/component_loader.hpp" + +#include +#include + +namespace mods { + // GSC File magic (8 bytes) + constexpr uint64_t gsc_magic = 0x36000A0D43534780; + // Serious' GSIC File Magic (4 bytes) + constexpr const char* gsic_magic = "GSIC"; + + constexpr const char* mod_metadata_file = "metadata.json"; + std::filesystem::path mod_dir = "project-bo4/mods"; + + namespace { + struct raw_file + { + xassets::raw_file_header header{}; + + std::string data{}; + + auto* get_header() + { + header.buffer = data.data(); + header.size = (uint32_t)data.length(); + + return &header; + } + }; + struct scriptparsetree + { + xassets::scriptparsetree_header header{}; + + std::string data{}; + size_t gsic_header_size{}; + std::unordered_set hooks{}; + gsc_custom::gsic_info gsic{}; + + auto* get_header() + { + header.buffer = reinterpret_cast(data.data()); + header.size = (uint32_t)data.length(); + + for (gsc_custom::gsic_detour& detour : gsic.detours) + { + detour.fixup_function = header.buffer->magic + detour.fixup_offset; + } + + return &header; + } + + bool can_read_gsic(size_t bytes) + { + return data.length() >= gsic_header_size + bytes; + } + + bool load_gsic() + { + byte* ptr = (byte*)data.data(); + + if (!can_read_gsic(4) || memcmp(gsic_magic, ptr, 4)) + { + return true; // not a gsic file + } + gsic_header_size += 4; + + if (!can_read_gsic(4)) + { + logger::write(logger::LOG_TYPE_ERROR, "can't read gsic fields"); + return false; + } + int32_t fields = *reinterpret_cast(ptr + gsic_header_size); + gsic_header_size += 4; + + for (size_t i = 0; i < fields; i++) + { + if (!can_read_gsic(4)) + { + logger::write(logger::LOG_TYPE_ERROR, "can't read gsic field type"); + return false; + } + + int32_t field_type = *reinterpret_cast(ptr + gsic_header_size); + gsic_header_size += 4; + + switch (field_type) + { + case gsc_custom::gsic_field_type::GSIC_FIELD_DETOUR: + { + // detours + if (!can_read_gsic(4)) + { + logger::write(logger::LOG_TYPE_ERROR, "can't read gsic detours count"); + return false; + } + int32_t detour_count = *reinterpret_cast(ptr + gsic_header_size); + gsic_header_size += 4; + + + if (!can_read_gsic(detour_count * 256ull)) + { + logger::write(logger::LOG_TYPE_ERROR, "can't read detours"); + return false; + } + + for (size_t j = 0; j < detour_count; j++) + { + gsc_custom::gsic_detour& detour = gsic.detours.emplace_back(); + + detour.fixup_name = *reinterpret_cast(ptr + gsic_header_size); + detour.replace_namespace = *reinterpret_cast(ptr + gsic_header_size + 4); + detour.replace_function = *reinterpret_cast(ptr + gsic_header_size + 8); + detour.fixup_offset = *reinterpret_cast(ptr + gsic_header_size + 12); + detour.fixup_size = *reinterpret_cast(ptr + gsic_header_size + 16); + detour.target_script = *reinterpret_cast(ptr + gsic_header_size + 20); + + logger::write(logger::LOG_TYPE_DEBUG, std::format( + "read detour {:x} : namespace_{:x}::function_{:x} / offset={:x}+{:x}", + detour.fixup_name, detour.replace_namespace, detour.target_script, detour.replace_function, + detour.fixup_offset, detour.fixup_size + )); + + gsic_header_size += 256; + } + } + break; + default: + logger::write(logger::LOG_TYPE_ERROR, "bad gsic field type {}", field_type); + return false; + } + } + + // we need to remove the header to keep the alignment + data = data.substr(gsic_header_size, data.length() - gsic_header_size); + + return true; + } + }; + struct lua_file + { + xassets::lua_file_header header{}; + + std::string data{}; + + auto* get_header() + { + header.buffer = reinterpret_cast(data.data()); + header.size = (uint32_t)data.length(); + + return &header; + } + }; + struct string_table_file + { + xassets::stringtable_header header{}; + + std::string data{}; + std::vector cells{}; + + auto* get_header() + { + header.values = cells.data(); + + return &header; + } + }; + + + class mod_storage + { + public: + std::mutex load_mutex{}; + std::vector allocated_strings{}; + std::vector gsc_files{}; + std::vector raw_files{}; + std::vector lua_files{}; + std::vector csv_files{}; + + ~mod_storage() + { + for (char* str : allocated_strings) + { + delete str; + } + } + + void clear() + { + // clear previously loaded files + raw_files.clear(); + gsc_files.clear(); + lua_files.clear(); + csv_files.clear(); + + for (char* str : allocated_strings) + { + delete str; + } + allocated_strings.clear(); + } + + char* allocate_string(const std::string& string) + { + char* str = new char[string.length() + 1]; + memcpy(str, string.c_str(), string.length() + 1); + + allocated_strings.emplace_back(str); + + return str; + } + + void* get_xasset(xassets::XAssetType type, uint64_t name) + { + std::lock_guard lg{ load_mutex }; + switch (type) + { + case xassets::ASSET_TYPE_SCRIPTPARSETREE: + { + auto it = std::find_if(gsc_files.begin(), gsc_files.end(), [name](const scriptparsetree& file) { return file.header.name == name; }); + + if (it == gsc_files.end()) return nullptr; + + return it->get_header(); + } + case xassets::ASSET_TYPE_RAWFILE: + { + auto it = std::find_if(raw_files.begin(), raw_files.end(), [name](const raw_file& file) { return file.header.name == name; }); + + if (it == raw_files.end()) return nullptr; + + return it->get_header(); + } + case xassets::ASSET_TYPE_LUAFILE: + { + auto it = std::find_if(lua_files.begin(), lua_files.end(), [name](const lua_file& file) { return file.header.name == name; }); + + if (it == lua_files.end()) return nullptr; + + return it->get_header(); + } + case xassets::ASSET_TYPE_STRINGTABLE: + { + auto it = std::find_if(csv_files.begin(), csv_files.end(), [name](const string_table_file& file) { return file.header.name == name; }); + + if (it == csv_files.end()) return nullptr; + + return it->get_header(); + } + default: + return nullptr; // unknown resource type + } + } + + bool read_data_entry(rapidjson::Value& member, const char* mod_name, const std::filesystem::path& mod_path) + { + auto type = member.FindMember("type"); + + if (type == member.MemberEnd() || !type->value.IsString()) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a data member without a valid type", mod_name)); + return false; + } + + const char* type_val = type->value.GetString(); + + if (!_strcmpi("scriptparsetree", type_val)) + { + auto name_mb = member.FindMember("name"); + auto path_mb = member.FindMember("path"); + + if ( + name_mb == member.MemberEnd() || path_mb == member.MemberEnd() + || !name_mb->value.IsString() || !path_mb->value.IsString() + ) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad scriptparsetree def, missing/bad name or path", mod_name)); + return false; + } + + scriptparsetree tmp{}; + std::filesystem::path path_cfg = path_mb->value.GetString(); + auto spt_path = path_cfg.is_absolute() ? path_cfg : (mod_path / path_cfg); + tmp.header.name = fnv1a::generate_hash_pattern(name_mb->value.GetString()); + + auto hooks = member.FindMember("hooks"); + + if (hooks != member.MemberEnd()) + { + // no hooks might not be an error, to replace a script for example + + if (!hooks->value.IsArray()) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad scriptparsetree hook def, not an array for {}", mod_name, spt_path.string())); + return false; + } + + for (auto& hook : hooks->value.GetArray()) + { + if (!hook.IsString()) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad scriptparsetree hook def, not a string for {}", mod_name, spt_path.string())); + return false; + } + + tmp.hooks.insert(fnv1a::generate_hash_pattern(hook.GetString())); + } + } + + if (!utilities::io::read_file(spt_path.string(), &tmp.data)) + { + logger::write(logger::LOG_TYPE_ERROR, std::format("can't read scriptparsetree {} for mod {}", spt_path.string(), mod_name)); + return false; + } + + if (!tmp.load_gsic()) + { + logger::write(logger::LOG_TYPE_ERROR, std::format("error when reading GSIC header of {} for mod {}", spt_path.string(), mod_name)); + return false; + } + + if (tmp.gsic.detours.size()) + { + logger::write(logger::LOG_TYPE_DEBUG, std::format("loaded {} detours", tmp.gsic.detours.size())); + } + + if (tmp.data.length() < sizeof(game::GSC_OBJ) || *reinterpret_cast(tmp.data.data()) != gsc_magic) + { + logger::write(logger::LOG_TYPE_ERROR, std::format("bad scriptparsetree magic in {} for mod {}", spt_path.string(), mod_name)); + return false; + } + + // after this point we assume that the GSC file is well formatted + + game::GSC_OBJ* script_obj = reinterpret_cast(tmp.data.data()); + + // fix compiler script name + script_obj->name = tmp.header.name; + + // fix compiler custom namespace + game::GSC_IMPORT_ITEM* imports = script_obj->get_imports(); + + static uint32_t isprofilebuild_hash = gsc_funcs::canon_hash("IsProfileBuild"); + static uint32_t serious_custom_func_name_hash = gsc_funcs::canon_hash(gsc_funcs::serious_custom_func_name); + for (size_t imp = 0; imp < script_obj->imports_count; imp++) + { + if (imports->name == isprofilebuild_hash && imports->param_count != 0) + { + // compiler:: calls, replace the call to our custom function + imports->name = serious_custom_func_name_hash; + } + + imports = reinterpret_cast((uint32_t*)&imports[1] + imports->num_address); + } + + logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded scriptparsetree {} -> {:x}", mod_name, spt_path.string(), tmp.header.name)); + gsc_files.emplace_back(tmp); + } + else if (!_strcmpi("rawfile", type_val)) + { + auto name_mb = member.FindMember("name"); + auto path_mb = member.FindMember("path"); + + if ( + name_mb == member.MemberEnd() || path_mb == member.MemberEnd() + || !name_mb->value.IsString() || !path_mb->value.IsString() + ) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad rawfile def, missing/bad name or path", mod_name)); + return false; + } + + raw_file tmp{}; + std::filesystem::path path_cfg = path_mb->value.GetString(); + auto raw_file_path = path_cfg.is_absolute() ? path_cfg : (mod_path / path_cfg); + tmp.header.name = fnv1a::generate_hash_pattern(name_mb->value.GetString()); + + if (!utilities::io::read_file(raw_file_path.string(), &tmp.data)) + { + logger::write(logger::LOG_TYPE_ERROR, std::format("can't read raw file {} for mod {}", raw_file_path.string(), mod_name)); + return false; + } + + logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded raw file {} -> {:x}", mod_name, raw_file_path.string(), tmp.header.name)); + raw_files.emplace_back(tmp); + } + else if (!_strcmpi("luafile", type_val)) + { + auto name_mb = member.FindMember("name"); + auto path_mb = member.FindMember("path"); + + if ( + name_mb == member.MemberEnd() || path_mb == member.MemberEnd() + || !name_mb->value.IsString() || !path_mb->value.IsString() + ) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad luafile def, missing/bad name or path", mod_name)); + return false; + } + + lua_file tmp{}; + std::filesystem::path path_cfg = path_mb->value.GetString(); + auto lua_file_path = path_cfg.is_absolute() ? path_cfg : (mod_path / path_cfg); + tmp.header.name = fnv1a::generate_hash_pattern(name_mb->value.GetString()); + + if (!utilities::io::read_file(lua_file_path.string(), &tmp.data)) + { + logger::write(logger::LOG_TYPE_ERROR, std::format("can't read lua file {} for mod {}", lua_file_path.string(), mod_name)); + return false; + } + + logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded lua file {} -> {:x}", mod_name, lua_file_path.string(), tmp.header.name)); + lua_files.emplace_back(tmp); + } + else if (!_strcmpi("stringtable", type_val)) + { + auto name_mb = member.FindMember("name"); + auto path_mb = member.FindMember("path"); + + if ( + name_mb == member.MemberEnd() || path_mb == member.MemberEnd() + || !name_mb->value.IsString() || !path_mb->value.IsString() + ) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad stringtable def, missing/bad name or path", mod_name)); + return false; + } + + string_table_file tmp{}; + std::filesystem::path path_cfg = path_mb->value.GetString(); + auto stringtable_file_path = path_cfg.is_absolute() ? path_cfg : (mod_path / path_cfg); + tmp.header.name = fnv1a::generate_hash_pattern(name_mb->value.GetString()); + + if (!utilities::io::read_file(stringtable_file_path.string(), &tmp.data)) + { + logger::write(logger::LOG_TYPE_ERROR, std::format("can't read stringtable file {} for mod {}", stringtable_file_path.string(), mod_name)); + return false; + } + + rapidcsv::Document doc{}; + + std::stringstream stream{ tmp.data }; + + doc.Load(stream, rapidcsv::LabelParams(-1, -1)); + + size_t rows_count_tmp = doc.GetRowCount(); + tmp.header.rows_count = rows_count_tmp != 0 ? (int32_t)(rows_count_tmp - 1) : 0; + tmp.header.columns_count = (int32_t)doc.GetColumnCount(); + + std::vector cell_types{}; + + for (size_t i = 0; i < tmp.header.columns_count; i++) + { + // read cell types + const std::string cell = doc.GetCell(i, 0); + + xassets::stringtable_cell_type cell_type = xassets::STC_TYPE_STRING; + if (cell == "undefined") + { + cell_type = xassets::STC_TYPE_UNDEFINED; + } + else if (cell == "string") + { + cell_type = xassets::STC_TYPE_STRING; + } + else if (cell == "int") + { + cell_type = xassets::STC_TYPE_INT; + } + else if (cell == "float") + { + cell_type = xassets::STC_TYPE_FLOAT; + } + else if (cell == "hash") + { + cell_type = xassets::STC_TYPE_HASHED2; + } + else if (cell == "hash7") + { + cell_type = xassets::STC_TYPE_HASHED7; + } + else if (cell == "hash8") + { + cell_type = xassets::STC_TYPE_HASHED8; + } + else if (cell == "bool") + { + cell_type = xassets::STC_TYPE_BOOL; + } + else + { + logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} : can't read stringtable {} type of column {} : '{}'", mod_name, stringtable_file_path.string(), i, cell)); + return false; + } + + cell_types.emplace_back(cell_type); + } + + for (size_t row = 1; row <= tmp.header.rows_count; row++) + { + // read cells + for (size_t column = 0; column < tmp.header.columns_count; column++) + { + xassets::stringtable_cell_type cell_type = cell_types[column]; + + const std::string cell_str = doc.GetCell(column, row); + + xassets::stringtable_cell& cell = tmp.cells.emplace_back(); + cell.type = cell_type; + + try + { + switch (cell_type) + { + case xassets::STC_TYPE_UNDEFINED: + cell.value.int_value = 0; + break; + case xassets::STC_TYPE_BOOL: + cell.value.bool_value = cell_str == "true"; + break; + case xassets::STC_TYPE_HASHED2: + case xassets::STC_TYPE_HASHED7: + case xassets::STC_TYPE_HASHED8: + cell.value.hash_value = fnv1a::generate_hash_pattern(cell_str.c_str()); + break; + case xassets::STC_TYPE_INT: + if (cell_str.starts_with("0x")) + { + cell.value.int_value = std::stoull(cell_str.substr(2), nullptr, 16); + } + else + { + cell.value.int_value = std::stoll(cell_str); + } + break; + case xassets::STC_TYPE_FLOAT: + cell.value.float_value = std::stof(cell_str); + break; + case xassets::STC_TYPE_STRING: + cell.value.string_value = allocate_string(cell_str); + break; + } + } + catch (const std::invalid_argument& e) + { + logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: error when loading stringtable file {} : {} [line {} col {}] '{}'", mod_name, stringtable_file_path.string(), e.what(), row, column, cell_str)); + return false; + } + } + } + + logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded stringtable file {} -> {:x}", mod_name, stringtable_file_path.string(), tmp.header.name)); + csv_files.emplace_back(tmp); + } + else + { + logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is load data member with an unknown type '{}'", mod_name, type_val)); + return false; + } + + return true; + } + + bool load_mods() + { + std::lock_guard lg{ load_mutex }; + clear(); + rapidjson::Document info{}; + std::string mod_metadata{}; + + bool err = false; + + std::filesystem::create_directories(mod_dir); + for (const auto& mod : std::filesystem::directory_iterator{ mod_dir }) + { + if (!mod.is_directory()) continue; // not a directory + + std::filesystem::path mod_path = mod.path(); + std::filesystem::path mod_metadata_path = mod_path / mod_metadata_file; + + if (!std::filesystem::exists(mod_metadata_path)) continue; // doesn't contain the metadata file + + + std::string filename = mod_metadata_path.string(); + if (!utilities::io::read_file(filename, &mod_metadata)) + { + logger::write(logger::LOG_TYPE_ERROR, std::format("can't read mod metadata file '{}'", filename)); + err = true; + continue; + } + + info.Parse(mod_metadata); + + if (info.HasParseError()) { + logger::write(logger::LOG_TYPE_ERROR, std::format("can't parse mod json metadata '{}'", filename)); + err = true; + continue; + } + + auto name_member = info.FindMember("name"); + + const char* mod_name; + + if (name_member != info.MemberEnd() && name_member->value.IsString()) + { + mod_name = name_member->value.GetString(); + } + else + { + mod_name = filename.c_str(); + } + logger::write(logger::LOG_TYPE_INFO, std::format("loading mod {}...", mod_name)); + + int mod_errors = 0; + auto data_member = info.FindMember("data"); + + if (data_member != info.MemberEnd() && data_member->value.IsArray()) + { + auto data_array = data_member->value.GetArray(); + + for (rapidjson::Value& member : data_array) + { + if (!member.IsObject()) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad data member", mod_name)); + mod_errors++; + continue; + } + + if (!read_data_entry(member, mod_name, mod_path)) + { + mod_errors++; + } + } + } + + if (mod_errors) + { + logger::write(logger::LOG_TYPE_WARN, std::format("mod {} loaded with {} error{}.", mod_name, mod_errors, mod_errors > 1 ? "s" : "")); + err = true; + } + } + return err; + } + }; + + mod_storage storage{}; + + + void load_mods_cmd() + { + if (!game::Com_IsRunningUILevel()) + { + // avoid gsc issues, but if a script is loaded in the frontend, it will still crash + game_console::print("can't load mods while in-game!"); + return; + } + + if (!storage.load_mods()) + { + game_console::print("mods reloaded."); + } + else + { + game_console::print("mods reloaded with errors, see logs."); + } + } + } + + utilities::hook::detour db_find_xasset_header_hook; + utilities::hook::detour scr_gsc_obj_link_hook; + + void* db_find_xasset_header_stub(xassets::XAssetType type, game::BO4_AssetRef_t* name, bool errorIfMissing, int waitTime) + { + void* header = storage.get_xasset(type, name->hash); + + if (header) + { + return header; // overwrite/load custom data + } + + return db_find_xasset_header_hook.invoke(type, name, errorIfMissing, waitTime); + } + + int scr_gsc_obj_link_stub(game::scriptInstance_t inst, game::GSC_OBJ* prime_obj, bool runScript) + { + // link the injected scripts if we find a hook, sync the gsic fields at the same time + // because we know the instance. + for (auto& spt : storage.gsc_files) + { + if (spt.hooks.find(prime_obj->name) != spt.hooks.end()) + { + gsc_custom::sync_gsic(inst, spt.gsic); + int err = scr_gsc_obj_link_hook.invoke(inst, spt.get_header()->buffer, runScript); + + if (err < 0) + { + return err; // error when linking + } + } + } + + auto custom_replaced_it = std::find_if(storage.gsc_files.begin(), storage.gsc_files.end(), + [prime_obj](scriptparsetree& e){ return e.get_header()->buffer == prime_obj; }); + + if (custom_replaced_it != storage.gsc_files.end()) + { + // replaced gsc file + gsc_custom::sync_gsic(inst, custom_replaced_it->gsic); + } + + return scr_gsc_obj_link_hook.invoke(inst, prime_obj, runScript); + } + + class component final : public component_interface + { + public: + void post_unpack() override + { + // custom assets loading + db_find_xasset_header_hook.create(0x142EB75B0_g, db_find_xasset_header_stub); + + scr_gsc_obj_link_hook.create(0x142748F10_g, scr_gsc_obj_link_stub); + + // register load mods command + Cmd_AddCommand("reload_mods", load_mods_cmd); + } + + void pre_start() override + { + storage.load_mods(); + } + }; +} + +REGISTER_COMPONENT(mods::component) \ No newline at end of file diff --git a/source/proxy-dll/definitions/game.hpp b/source/proxy-dll/definitions/game.hpp index f760e7b..ae2dd2d 100644 --- a/source/proxy-dll/definitions/game.hpp +++ b/source/proxy-dll/definitions/game.hpp @@ -1,6 +1,7 @@ #pragma once -#include "definitions\discovery.hpp" +#include "definitions/discovery.hpp" +#include "definitions/variables.hpp" #define WEAK __declspec(selectany) @@ -39,6 +40,93 @@ namespace game return m128i; } + typedef void (*xcommand_t)(void); + + struct cmd_function_t + { + cmd_function_t* next; + uint64_t name; + uint64_t pad0; + uint64_t pad1; + uint64_t pad2; + xcommand_t function; + }; + + struct GSC_IMPORT_ITEM + { + uint32_t name; + uint32_t name_space; + uint16_t num_address; + uint8_t param_count; + uint8_t flags; + }; + + struct GSC_EXPORT_ITEM + { + uint32_t checksum; + uint32_t address; + uint32_t name; + uint32_t name_space; + uint32_t callback_event; + uint8_t param_count; + uint8_t flags; + }; + + struct GSC_OBJ + { + byte magic[8]; + int32_t crc; + int32_t pad; + uint64_t name; + int32_t include_offset; + uint16_t string_count; + uint16_t exports_count; + int32_t ukn20; + int32_t string_offset; + int16_t imports_count; + uint16_t fixup_count; + int32_t ukn2c; + int32_t exports_offset; + int32_t ukn34; + int32_t imports_offset; + uint16_t globalvar_count; + int32_t fixup_offset; + int32_t globalvar_offset; + int32_t script_size; + int32_t ukn4c_offset; + int32_t ukn50; + int32_t ukn54; + uint16_t include_count; + byte ukn5a; + byte ukn4c_count; + + inline GSC_EXPORT_ITEM* get_exports() + { + return reinterpret_cast(magic + exports_offset); + } + + inline GSC_IMPORT_ITEM* get_imports() + { + return reinterpret_cast(magic + imports_offset); + } + + inline uint64_t* get_includes() + { + return reinterpret_cast(magic + include_offset); + } + + inline GSC_EXPORT_ITEM* get_exports_end() + { + return get_exports() + exports_count; + } + + inline uint64_t* get_includes_end() + { + return get_includes() + include_count; + } + }; + + enum scriptInstance_t : int32_t { SCRIPTINSTANCE_SERVER = 0x0, @@ -48,6 +136,7 @@ namespace game typedef void (*BuiltinFunction)(scriptInstance_t); + struct BO4_BuiltinFunctionDef { uint32_t canonId; @@ -115,7 +204,52 @@ namespace game uint32_t numVarAllocations; int32_t varHighWatermarkId; }; - + + union ScrVarValueUnion_t + { + int64_t intValue; + uintptr_t uintptrValue; + float floatValue; + int32_t stringValue; + const float* vectorValue; + byte* codePosValue; + ScrVarIndex_t pointerValue; + }; + + struct ScrVarValue_t + { + ScrVarValueUnion_t u; + ScrVarType_t type; + }; + + struct function_stack_t + { + byte* pos; + ScrVarValue_t* top; + ScrVarValue_t* startTop; + ScrVarIndex_t threadId; + uint16_t localVarCount; + uint16_t profileInfoCount; + }; + + struct ScrVmContext_t + { + ScrVarIndex_t fieldValueId; + ScrVarIndex_t objectId; + byte* lastGoodPos; + ScrVarValue_t* lastGoodTop; + }; + + typedef void (*VM_OP_FUNC)(scriptInstance_t, function_stack_t*, ScrVmContext_t*, bool*); + + struct objFileInfo_t + { + GSC_OBJ* activeVersion; + int slot; + int refCount; + uint32_t groupId; + }; + enum keyNum_t { K_NONE = 0x00, @@ -595,9 +729,24 @@ namespace game WEAK symbol ScrVm_GetPointerType{ 0x1427746E0_g }; WEAK symbol ScrVm_GetType{ 0x142774A20_g }; + WEAK symbol CScr_GetFunction{ 0x141F13140_g }; + WEAK symbol Scr_GetFunction{ 0x1433AF840_g }; + WEAK symbol CScr_GetMethod{ 0x141F13650_g }; + WEAK symbol Scr_GetMethod{ 0x1433AFC20_g }; + WEAK symbol ScrVm_Error{ 0x142770330_g }; - WEAK symbol scrVarPub{ 0x148307880_g }; + WEAK symbol scrVarPub{ 0x148307880_g }; + + WEAK symbol gVmOpJumpTable{ 0x144EED340_g }; + WEAK symbol gObjFileInfoCount{ 0x1482F76B0_g }; + WEAK symbol gObjFileInfo{ 0x1482EFCD0_g }; + + WEAK symbol Cmd_AddCommandInternal{0x143CDEE80_g}; +#define Cmd_AddCommand(name, function) \ + static game::cmd_function_t __cmd_func_##function; \ + game::BO4_AssetRef_t __cmd_func_name_##function { (int64_t)fnv1a::generate_hash(name), 0 }; \ + game::Cmd_AddCommandInternal(&__cmd_func_name_##function, function, &__cmd_func_##function) #define R_AddCmdDrawText(TXT, MC, F, X, Y, XS, YS, R, C, S) \ T8_AddBaseDrawTextCmd(TXT, MC, F, X, Y, XS, YS, R, C, S, -1, 0, 0) diff --git a/source/proxy-dll/definitions/variables.cpp b/source/proxy-dll/definitions/variables.cpp index 7dbb1b5..b0fc822 100644 --- a/source/proxy-dll/definitions/variables.cpp +++ b/source/proxy-dll/definitions/variables.cpp @@ -15,6 +15,22 @@ namespace fnv1a return (Result & 0x7FFFFFFFFFFFFFFF); } + + uint64_t generate_hash_pattern(const char* string) + { + std::string_view v{ string }; + + // basic notations hash_123, file_123, script_123 + if (!v.rfind("hash_", 0)) return std::strtoull(&string[5], nullptr, 16); + if (!v.rfind("file_", 0)) return std::strtoull(&string[5], nullptr, 16); + if (!v.rfind("script_", 0)) return std::strtoull(&string[7], nullptr, 16); + + // lua notation x64:123 + if (!v.rfind("x64:", 0)) return std::strtoull(&string[4], nullptr, 16); + + // unknown, use hashed value + return generate_hash(string); + } } namespace variables @@ -6479,6 +6495,11 @@ namespace variables "quit", "Shutdown the Game [Com_Quit_f]", 0x1DEE6107B26F8BB6 + }, + { + "reload_mods", + "Reload the shield mods", + 0x6cb53357b4ef835c } }; diff --git a/source/proxy-dll/definitions/variables.hpp b/source/proxy-dll/definitions/variables.hpp index e6a9d04..a0e2818 100644 --- a/source/proxy-dll/definitions/variables.hpp +++ b/source/proxy-dll/definitions/variables.hpp @@ -3,6 +3,8 @@ namespace fnv1a { uint64_t generate_hash(const char* string); + + uint64_t generate_hash_pattern(const char* string); } namespace variables diff --git a/source/proxy-dll/definitions/xassets.hpp b/source/proxy-dll/definitions/xassets.hpp new file mode 100644 index 0000000..6b08b69 --- /dev/null +++ b/source/proxy-dll/definitions/xassets.hpp @@ -0,0 +1,260 @@ +#pragma once + +#include "game.hpp" + +namespace xassets +{ + struct raw_file_header + { + uint64_t name{}; + uint64_t pad8{}; + uint64_t size{}; + const char* buffer{}; + }; + + struct scriptparsetree_header + { + uint64_t name{}; + uint64_t pad8{}; + game::GSC_OBJ* buffer{}; + uint32_t size{}; + uint32_t pad20{}; + }; + + struct lua_file_header + { + uint64_t name{}; + uint64_t pad8{}; + uint64_t size{}; + byte* buffer{}; + }; + + + union stringtable_cell_value + { + byte bytes[0x10]; + const char* string_value; + int64_t int_value; + float float_value; + byte bool_value; + uint64_t hash_value; + }; + + enum stringtable_cell_type : byte + { + STC_TYPE_UNDEFINED = 0, + STC_TYPE_STRING = 1, + STC_TYPE_HASHED2 = 2, + STC_TYPE_INT = 4, + STC_TYPE_FLOAT = 5, + STC_TYPE_BOOL = 6, + STC_TYPE_HASHED7 = 7, + STC_TYPE_HASHED8 = 8, + }; + + struct stringtable_cell + { + stringtable_cell_value value{}; + uint32_t pad10{}; + stringtable_cell_type type{}; + }; + + struct stringtable_header + { + uint64_t name{}; + int32_t pad8{}; + int32_t pad12{}; + int32_t columns_count{}; + int32_t rows_count{}; + int32_t cells_count{}; // 0 + int32_t unk24{}; + uintptr_t cells{}; // empty + stringtable_cell* values{}; + uintptr_t unk48{}; + uintptr_t unk56{}; + }; + + union xasset_header + { + raw_file_header* raw_file; + lua_file_header* lua_file; + scriptparsetree_header* scriptparsetree; + stringtable_header* stringtable; + }; + + enum XAssetType : byte + { + ASSET_TYPE_PHYSPRESET = 0x0, + ASSET_TYPE_PHYSCONSTRAINTS = 0x1, + ASSET_TYPE_DESTRUCTIBLEDEF = 0x2, + ASSET_TYPE_XANIM = 0x3, + ASSET_TYPE_XMODEL = 0x4, + ASSET_TYPE_XMODELMESH = 0x5, + ASSET_TYPE_MATERIAL = 0x6, + ASSET_TYPE_COMPUTE_SHADER_SET = 0x7, + ASSET_TYPE_TECHNIQUE_SET = 0x8, + ASSET_TYPE_IMAGE = 0x9, + ASSET_TYPE_SOUND = 0xA, + ASSET_TYPE_CLIPMAP = 0xB, + ASSET_TYPE_COMWORLD = 0xC, + ASSET_TYPE_GAMEWORLD = 0xD, + ASSET_TYPE_GFXWORLD = 0xE, + ASSET_TYPE_FONTICON = 0xF, + ASSET_TYPE_LOCALIZE_ENTRY = 0x10, + ASSET_TYPE_LOCALIZE_LIST = 0x11, + ASSET_TYPE_GESTURE = 0x12, + ASSET_TYPE_GESTURE_TABLE = 0x13, + ASSET_TYPE_WEAPON = 0x14, + ASSET_TYPE_WEAPON_FULL = 0x15, + ASSET_TYPE_WEAPON_TUNABLES = 0x16, + ASSET_TYPE_CGMEDIA = 0x17, + ASSET_TYPE_PLAYERSOUNDS = 0x18, + ASSET_TYPE_PLAYERFX = 0x19, + ASSET_TYPE_SHAREDWEAPONSOUNDS = 0x1A, + ASSET_TYPE_ATTACHMENT = 0x1B, + ASSET_TYPE_ATTACHMENT_UNIQUE = 0x1C, + ASSET_TYPE_WEAPON_CAMO = 0x1D, + ASSET_TYPE_CUSTOMIZATION_TABLE = 0x1E, + ASSET_TYPE_CUSTOMIZATION_TABLE_FE_IMAGES = 0x1F, + ASSET_TYPE_SNDDRIVER_GLOBALS = 0x20, + ASSET_TYPE_FX = 0x21, + ASSET_TYPE_TAGFX = 0x22, + ASSET_TYPE_KLF = 0x23, + ASSET_TYPE_IMPACT_FX = 0x24, + ASSET_TYPE_IMPACT_SOUND = 0x25, + ASSET_TYPE_AITYPE = 0x26, + ASSET_TYPE_CHARACTER = 0x27, + ASSET_TYPE_XMODELALIAS = 0x28, + ASSET_TYPE_RAWFILE = 0x29, + ASSET_TYPE_XANIM_TREE = 0x2A, + ASSET_TYPE_STRINGTABLE = 0x2B, + ASSET_TYPE_STRUCTURED_TABLE = 0x2C, + ASSET_TYPE_LEADERBOARD = 0x2D, + ASSET_TYPE_DDL = 0x2E, + ASSET_TYPE_GLASSES = 0x2F, + ASSET_TYPE_SCRIPTPARSETREE = 0x30, + ASSET_TYPE_SCRIPTPARSETREEDBG = 0x31, + ASSET_TYPE_SCRIPTPARSETREEFORCED = 0x32, + ASSET_TYPE_KEYVALUEPAIRS = 0x33, + ASSET_TYPE_VEHICLEDEF = 0x34, + ASSET_TYPE_TRACER = 0x35, + ASSET_TYPE_SURFACEFX_TABLE = 0x36, + ASSET_TYPE_SURFACESOUNDDEF = 0x37, + ASSET_TYPE_FOOTSTEP_TABLE = 0x38, + ASSET_TYPE_ENTITYFXIMPACTS = 0x39, + ASSET_TYPE_ENTITYSOUNDIMPACTS = 0x3A, + ASSET_TYPE_ZBARRIER = 0x3B, + ASSET_TYPE_VEHICLEFXDEF = 0x3C, + ASSET_TYPE_VEHICLESOUNDDEF = 0x3D, + ASSET_TYPE_TYPEINFO = 0x3E, + ASSET_TYPE_SCRIPTBUNDLE = 0x3F, + ASSET_TYPE_SCRIPTBUNDLELIST = 0x40, + ASSET_TYPE_RUMBLE = 0x41, + ASSET_TYPE_BULLETPENETRATION = 0x42, + ASSET_TYPE_LOCDMGTABLE = 0x43, + ASSET_TYPE_AIMTABLE = 0x44, + ASSET_TYPE_SHOOTTABLE = 0x45, + ASSET_TYPE_PLAYERGLOBALTUNABLES = 0x46, + ASSET_TYPE_ANIMSELECTORTABLESET = 0x47, + ASSET_TYPE_ANIMMAPPINGTABLE = 0x48, + ASSET_TYPE_ANIMSTATEMACHINE = 0x49, + ASSET_TYPE_BEHAVIORTREE = 0x4A, + ASSET_TYPE_BEHAVIORSTATEMACHINE = 0x4B, + ASSET_TYPE_TTF = 0x4C, + ASSET_TYPE_SANIM = 0x4D, + ASSET_TYPE_LIGHT_DESCRIPTION = 0x4E, + ASSET_TYPE_SHELLSHOCK = 0x4F, + ASSET_TYPE_STATUS_EFFECT = 0x50, + ASSET_TYPE_CINEMATIC_CAMERA = 0x51, + ASSET_TYPE_CINEMATIC_SEQUENCE = 0x52, + ASSET_TYPE_SPECTATE_CAMERA = 0x53, + ASSET_TYPE_XCAM = 0x54, + ASSET_TYPE_BG_CACHE = 0x55, + ASSET_TYPE_TEXTURE_COMBO = 0x56, + ASSET_TYPE_FLAMETABLE = 0x57, + ASSET_TYPE_BITFIELD = 0x58, + ASSET_TYPE_MAPTABLE = 0x59, + ASSET_TYPE_MAPTABLE_LIST = 0x5A, + ASSET_TYPE_MAPTABLE_LOADING_IMAGES = 0x5B, + ASSET_TYPE_MAPTABLE_PREVIEW_IMAGES = 0x5C, + ASSET_TYPE_MAPTABLEENTRY_LEVEL_ASSETS = 0x5D, + ASSET_TYPE_OBJECTIVE = 0x5E, + ASSET_TYPE_OBJECTIVE_LIST = 0x5F, + ASSET_TYPE_NAVMESH = 0x60, + ASSET_TYPE_NAVVOLUME = 0x61, + ASSET_TYPE_LASER = 0x62, + ASSET_TYPE_BEAM = 0x63, + ASSET_TYPE_STREAMER_HINT = 0x64, + ASSET_TYPE_FLOWGRAPH = 0x65, + ASSET_TYPE_POSTFXBUNDLE = 0x66, + ASSET_TYPE_LUAFILE = 0x67, + ASSET_TYPE_LUAFILE_DBG = 0x68, + ASSET_TYPE_RENDEROVERRIDEBUNDLE = 0x69, + ASSET_TYPE_STATIC_LEVEL_FX_LIST = 0x6A, + ASSET_TYPE_TRIGGER_LIST = 0x6B, + ASSET_TYPE_PLAYER_ROLE_TEMPLATE = 0x6C, + ASSET_TYPE_PLAYER_ROLE_CATEGORY_TABLE = 0x6D, + ASSET_TYPE_PLAYER_ROLE_CATEGORY = 0x6E, + ASSET_TYPE_CHARACTER_BODY_TYPE = 0x6F, + ASSET_TYPE_PLAYER_OUTFIT = 0x70, + ASSET_TYPE_GAMETYPETABLE = 0x71, + ASSET_TYPE_FEATURE = 0x72, + ASSET_TYPE_FEATURETABLE = 0x73, + ASSET_TYPE_UNLOCKABLE_ITEM = 0x74, + ASSET_TYPE_UNLOCKABLE_ITEM_TABLE = 0x75, + ASSET_TYPE_ENTITY_LIST = 0x76, + ASSET_TYPE_PLAYLISTS = 0x77, + ASSET_TYPE_PLAYLIST_GLOBAL_SETTINGS = 0x78, + ASSET_TYPE_PLAYLIST_SCHEDULE = 0x79, + ASSET_TYPE_MOTION_MATCHING_INPUT = 0x7A, + ASSET_TYPE_BLACKBOARD = 0x7B, + ASSET_TYPE_TACTICALQUERY = 0x7C, + ASSET_TYPE_PLAYER_MOVEMENT_TUNABLES = 0x7D, + ASSET_TYPE_HIERARCHICAL_TASK_NETWORK = 0x7E, + ASSET_TYPE_RAGDOLL = 0x7F, + ASSET_TYPE_STORAGEFILE = 0x80, + ASSET_TYPE_STORAGEFILELIST = 0x81, + ASSET_TYPE_CHARMIXER = 0x82, + ASSET_TYPE_STOREPRODUCT = 0x83, + ASSET_TYPE_STORECATEGORY = 0x84, + ASSET_TYPE_STORECATEGORYLIST = 0x85, + ASSET_TYPE_RANK = 0x86, + ASSET_TYPE_RANKTABLE = 0x87, + ASSET_TYPE_PRESTIGE = 0x88, + ASSET_TYPE_PRESTIGETABLE = 0x89, + ASSET_TYPE_FIRSTPARTYENTITLEMENT = 0x8A, + ASSET_TYPE_FIRSTPARTYENTITLEMENTLIST = 0x8B, + ASSET_TYPE_ENTITLEMENT = 0x8C, + ASSET_TYPE_ENTITLEMENTLIST = 0x8D, + ASSET_TYPE_SKU = 0x8E, + ASSET_TYPE_LABELSTORE = 0x8F, + ASSET_TYPE_LABELSTORELIST = 0x90, + ASSET_TYPE_CPU_OCCLUSION_DATA = 0x91, + ASSET_TYPE_LIGHTING = 0x92, + ASSET_TYPE_STREAMERWORLD = 0x93, + ASSET_TYPE_TALENT = 0x94, + ASSET_TYPE_PLAYERTALENTTEMPLATE = 0x95, + ASSET_TYPE_PLAYERANIMATION = 0x96, + ASSET_TYPE_ERR_UNUSED = 0x97, + ASSET_TYPE_TERRAINGFX = 0x98, + ASSET_TYPE_HIGHLIGHTREELINFODEFINES = 0x99, + ASSET_TYPE_HIGHLIGHTREELPROFILEWEIGHTING = 0x9A, + ASSET_TYPE_HIGHLIGHTREELSTARLEVELS = 0x9B, + ASSET_TYPE_DLOGEVENT = 0x9C, + ASSET_TYPE_RAWSTRING = 0x9D, + ASSET_TYPE_BALLISTICDESC = 0x9E, + ASSET_TYPE_STREAMKEY = 0x9F, + ASSET_TYPE_RENDERTARGETS = 0xA0, + ASSET_TYPE_DRAWNODES = 0xA1, + ASSET_TYPE_GROUPLODMODEL = 0xA2, + ASSET_TYPE_FXLIBRARYVOLUME = 0xA3, + ASSET_TYPE_ARENASEASONS = 0xA4, + ASSET_TYPE_SPRAYORGESTUREITEM = 0xA5, + ASSET_TYPE_SPRAYORGESTURELIST = 0xA6, + ASSET_TYPE_HWPLATFORM = 0xA7, + ASSET_TYPE_COUNT = 0xA8, + ASSET_TYPE_ASSETLIST = 0xA8, + ASSET_TYPE_REPORT = 0xA9, + ASSET_TYPE_FULL_COUNT = 0xAA, + }; +} \ No newline at end of file diff --git a/source/proxy-dll/std_include.hpp b/source/proxy-dll/std_include.hpp index e44db26..958cf54 100644 --- a/source/proxy-dll/std_include.hpp +++ b/source/proxy-dll/std_include.hpp @@ -73,6 +73,7 @@ #include #include +#include #include #include #include