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
This commit is contained in:
parent
b973aa50d1
commit
50f6049a7e
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
||||
|
18
deps/premake/rapidcsv.lua
vendored
Normal file
18
deps/premake/rapidcsv.lua
vendored
Normal file
@ -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)
|
1
deps/rapidcsv
vendored
Submodule
1
deps/rapidcsv
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit f032c527caa2533a3b5b17d11d9c29feaa1cca06
|
322
source/proxy-dll/component/gsc_custom.cpp
Normal file
322
source/proxy-dll/component/gsc_custom.cpp
Normal file
@ -0,0 +1,322 @@
|
||||
#include <std_include.hpp>
|
||||
#include "gsc_custom.hpp"
|
||||
#include "gsc_funcs.hpp"
|
||||
#include "definitions/game.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utilities/hook.hpp>
|
||||
#include <utilities/json_config.hpp>
|
||||
|
||||
namespace gsc_custom
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct gsic_link_detour_data
|
||||
{
|
||||
const gsic_info& gsic_info;
|
||||
uint32_t latest_script_index{};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
inline byte* align_ptr(byte* ptr)
|
||||
{
|
||||
return reinterpret_cast<byte*>((reinterpret_cast<uintptr_t>(ptr) + sizeof(T) - 1) & ~(sizeof(T) - 1));
|
||||
}
|
||||
|
||||
std::vector<gsic_link_detour_data> 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<uint32_t*>(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<uint16_t>(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<uintptr_t>(loc + 2 + offset);
|
||||
#ifdef _DEBUG_DETOUR
|
||||
logger::write(logger::LOG_TYPE_DEBUG,
|
||||
std::format(
|
||||
"linking detours for namespace_{:x}<script_{: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<game::GSC_IMPORT_ITEM*>(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<script_%llx>::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<uint32_t>(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<void>(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)
|
29
source/proxy-dll/component/gsc_custom.hpp
Normal file
29
source/proxy-dll/component/gsc_custom.hpp
Normal file
@ -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<gsic_detour> detours{};
|
||||
};
|
||||
|
||||
void sync_gsic(game::scriptInstance_t inst, gsic_info& info);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#include <std_include.hpp>
|
||||
#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<uint64_t, hud_elem> 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<void>(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);
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
|
743
source/proxy-dll/component/mods.cpp
Normal file
743
source/proxy-dll/component/mods.cpp
Normal file
@ -0,0 +1,743 @@
|
||||
#include <std_include.hpp>
|
||||
#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 <utilities/io.hpp>
|
||||
#include <utilities/hook.hpp>
|
||||
|
||||
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<uint64_t> hooks{};
|
||||
gsc_custom::gsic_info gsic{};
|
||||
|
||||
auto* get_header()
|
||||
{
|
||||
header.buffer = reinterpret_cast<game::GSC_OBJ*>(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<int32_t*>(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<int32_t*>(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<int32_t*>(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<uint32_t*>(ptr + gsic_header_size);
|
||||
detour.replace_namespace = *reinterpret_cast<uint32_t*>(ptr + gsic_header_size + 4);
|
||||
detour.replace_function = *reinterpret_cast<uint32_t*>(ptr + gsic_header_size + 8);
|
||||
detour.fixup_offset = *reinterpret_cast<uint32_t*>(ptr + gsic_header_size + 12);
|
||||
detour.fixup_size = *reinterpret_cast<uint32_t*>(ptr + gsic_header_size + 16);
|
||||
detour.target_script = *reinterpret_cast<uint64_t*>(ptr + gsic_header_size + 20);
|
||||
|
||||
logger::write(logger::LOG_TYPE_DEBUG, std::format(
|
||||
"read detour {:x} : namespace_{:x}<script_{: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<byte*>(data.data());
|
||||
header.size = (uint32_t)data.length();
|
||||
|
||||
return &header;
|
||||
}
|
||||
};
|
||||
struct string_table_file
|
||||
{
|
||||
xassets::stringtable_header header{};
|
||||
|
||||
std::string data{};
|
||||
std::vector<xassets::stringtable_cell> cells{};
|
||||
|
||||
auto* get_header()
|
||||
{
|
||||
header.values = cells.data();
|
||||
|
||||
return &header;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class mod_storage
|
||||
{
|
||||
public:
|
||||
std::mutex load_mutex{};
|
||||
std::vector<char*> allocated_strings{};
|
||||
std::vector<scriptparsetree> gsc_files{};
|
||||
std::vector<raw_file> raw_files{};
|
||||
std::vector<lua_file> lua_files{};
|
||||
std::vector<string_table_file> 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<uint64_t*>(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<game::GSC_OBJ*>(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<game::GSC_IMPORT_ITEM*>((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<xassets::stringtable_cell_type> cell_types{};
|
||||
|
||||
for (size_t i = 0; i < tmp.header.columns_count; i++)
|
||||
{
|
||||
// read cell types
|
||||
const std::string cell = doc.GetCell<std::string>(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<std::string>(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<void*>(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<int>(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<int>(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)
|
@ -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<GSC_EXPORT_ITEM*>(magic + exports_offset);
|
||||
}
|
||||
|
||||
inline GSC_IMPORT_ITEM* get_imports()
|
||||
{
|
||||
return reinterpret_cast<GSC_IMPORT_ITEM*>(magic + imports_offset);
|
||||
}
|
||||
|
||||
inline uint64_t* get_includes()
|
||||
{
|
||||
return reinterpret_cast<uint64_t*>(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<ScrVarType_t(scriptInstance_t inst, unsigned int index)> ScrVm_GetPointerType{ 0x1427746E0_g };
|
||||
WEAK symbol<ScrVarType_t(scriptInstance_t inst, unsigned int index)> ScrVm_GetType{ 0x142774A20_g };
|
||||
|
||||
WEAK symbol<BuiltinFunction(uint32_t canonId, int* type, int* min_args, int* max_args)> CScr_GetFunction{ 0x141F13140_g };
|
||||
WEAK symbol<BuiltinFunction(uint32_t canonId, int* type, int* min_args, int* max_args)> Scr_GetFunction{ 0x1433AF840_g };
|
||||
WEAK symbol<void*(uint32_t canonId, int* type, int* min_args, int* max_args)> CScr_GetMethod{ 0x141F13650_g };
|
||||
WEAK symbol<void*(uint32_t canonId, int* type, int* min_args, int* max_args)> Scr_GetMethod{ 0x1433AFC20_g };
|
||||
|
||||
WEAK symbol<void(uint64_t code, scriptInstance_t inst, char* unused, bool terminal)> ScrVm_Error{ 0x142770330_g };
|
||||
WEAK symbol<BO4_scrVarPub[scriptInstance_t::SCRIPTINSTANCE_MAX]> scrVarPub{ 0x148307880_g };
|
||||
WEAK symbol<BO4_scrVarPub> scrVarPub{ 0x148307880_g };
|
||||
|
||||
WEAK symbol<VM_OP_FUNC> gVmOpJumpTable{ 0x144EED340_g };
|
||||
WEAK symbol<uint32_t> gObjFileInfoCount{ 0x1482F76B0_g };
|
||||
WEAK symbol<objFileInfo_t[SCRIPTINSTANCE_MAX][650]> gObjFileInfo{ 0x1482EFCD0_g };
|
||||
|
||||
WEAK symbol<void(BO4_AssetRef_t* cmdName, xcommand_t function, cmd_function_t* allocedCmd)> 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)
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace fnv1a
|
||||
{
|
||||
uint64_t generate_hash(const char* string);
|
||||
|
||||
uint64_t generate_hash_pattern(const char* string);
|
||||
}
|
||||
|
||||
namespace variables
|
||||
|
260
source/proxy-dll/definitions/xassets.hpp
Normal file
260
source/proxy-dll/definitions/xassets.hpp
Normal file
@ -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,
|
||||
};
|
||||
}
|
@ -73,6 +73,7 @@
|
||||
#include <variant>
|
||||
#include <cassert>
|
||||
|
||||
#include <rapidcsv.h>
|
||||
#include <MinHook.h>
|
||||
#include <asmjit/core/jitruntime.h>
|
||||
#include <asmjit/x86/x86assembler.h>
|
||||
|
Loading…
Reference in New Issue
Block a user