Lua injection, custom BGCache and other things (#30)
This commit is contained in:
parent
b4d1c8955b
commit
60597903d6
@ -1,8 +1,10 @@
|
||||
#include <std_include.hpp>
|
||||
#include "definitions/game.hpp"
|
||||
#include "definitions/game_runtime_errors.hpp"
|
||||
#include "component/scheduler.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <utilities/hook.hpp>
|
||||
#include <utilities/string.hpp>
|
||||
|
||||
namespace debugging
|
||||
@ -17,7 +19,7 @@ namespace debugging
|
||||
{
|
||||
if ((1 << bitNumber) & infoBitmask)
|
||||
{
|
||||
connectionInfoString[bitNumber * 2] = bitNumber + 0x41;
|
||||
connectionInfoString[bitNumber * 2] = (char)(bitNumber + 0x41);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -42,7 +44,7 @@ namespace debugging
|
||||
|
||||
if (!connected)
|
||||
{
|
||||
float color[4] = { 0.8f, 1.0f, 0.3, 0.8f };
|
||||
float color[4] = { 0.8f, 1.0f, 0.3f, 0.8f };
|
||||
|
||||
const char* sz = get_connectivity_info_string(infoBitmask);
|
||||
|
||||
@ -68,11 +70,30 @@ namespace debugging
|
||||
}
|
||||
}
|
||||
|
||||
utilities::hook::detour sys_error_hook;
|
||||
|
||||
void sys_error_stub(uint32_t code, const char* message)
|
||||
{
|
||||
const char* error_message = game::runtime_errors::get_error_message(code);
|
||||
|
||||
if (error_message)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, "[sys_error] %s (%d): %s", error_message, code, message);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, "[sys_error] %d: %s", code, message);
|
||||
}
|
||||
|
||||
sys_error_hook.invoke<void>(code, message);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
sys_error_hook.create(0x143D36CC0_g, sys_error_stub);
|
||||
scheduler::loop(draw_debug_info, scheduler::renderer);
|
||||
scheduler::loop(test_key_catcher, scheduler::main);
|
||||
}
|
||||
|
@ -1,12 +1,16 @@
|
||||
#include <std_include.hpp>
|
||||
#include "gsc_funcs.hpp"
|
||||
#include "gsc_custom.hpp"
|
||||
#include "hashes.hpp"
|
||||
#include "definitions/game.hpp"
|
||||
#include "definitions/game_runtime_errors.hpp"
|
||||
#include "definitions/xassets.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "component/scheduler.hpp"
|
||||
|
||||
|
||||
#include <utilities/hook.hpp>
|
||||
#include <utilities/io.hpp>
|
||||
#include <utilities/json_config.hpp>
|
||||
|
||||
namespace gsc_funcs
|
||||
@ -21,8 +25,25 @@ namespace gsc_funcs
|
||||
hash = ((c + hash) ^ ((c + hash) << 10)) + (((c + hash) ^ ((c + hash) << 10)) >> 6);
|
||||
}
|
||||
|
||||
return 0x8001 * ((9 * hash) ^ ((9 * hash) >> 11));
|
||||
uint32_t val = 0x8001 * ((9 * hash) ^ ((9 * hash) >> 11));
|
||||
|
||||
hashes::add_hash(val, str);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
uint32_t canon_hash_pattern(const char* str)
|
||||
{
|
||||
std::string_view v{ str };
|
||||
|
||||
// basic notations hash_123, var_123
|
||||
if (!v.rfind("hash_", 0)) return std::strtoul(&str[5], nullptr, 16) & 0xFFFFFFFF;
|
||||
if (!v.rfind("var_", 0)) return std::strtoul(&str[4], nullptr, 16) & 0xFFFFFFFF;
|
||||
|
||||
// unknown, use hashed value
|
||||
return canon_hash(str);
|
||||
}
|
||||
|
||||
void gsc_error(const char* message, game::scriptInstance_t inst, bool terminal, ...)
|
||||
{
|
||||
static char buffer[game::scriptInstance_t::SCRIPTINSTANCE_MAX][0x800];
|
||||
@ -32,11 +53,38 @@ namespace gsc_funcs
|
||||
vsprintf_s(buffer[inst], message, va);
|
||||
va_end(va);
|
||||
|
||||
game::ScrVm_Error(custom_error_id, inst, buffer[inst], terminal);
|
||||
game::ScrVm_Error(game::runtime_errors::custom_error_id, inst, buffer[inst], terminal);
|
||||
}
|
||||
|
||||
const char* lookup_hash(game::scriptInstance_t inst, const char* type, uint64_t hash)
|
||||
{
|
||||
static char buffer[game::SCRIPTINSTANCE_MAX][0x50];
|
||||
const char* str = hashes::lookup(hash);
|
||||
|
||||
if (str)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
sprintf_s(buffer[inst], "%s_%llx", type, hash);
|
||||
|
||||
return buffer[inst];
|
||||
}
|
||||
|
||||
void ScrVm_AddToArrayIntIndexed(game::scriptInstance_t inst, uint64_t index)
|
||||
{
|
||||
auto& vm = game::scrVmPub[inst];
|
||||
--vm.callNesting;
|
||||
--vm.top;
|
||||
game::ScrVarIndex_t varidx = game::ScrVar_NewVariableByIndex(inst, vm.top->u.pointerValue, index);
|
||||
game::ScrVar_SetValue(inst, varidx, vm.top + 1);
|
||||
}
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto gsc_json_data_name_max_length = 40;
|
||||
constexpr const char* gsc_json_type = "$.type";
|
||||
enum hud_elem_align_x
|
||||
{
|
||||
HUD_ALIGN_X_LEFT = 0,
|
||||
@ -140,13 +188,16 @@ namespace gsc_funcs
|
||||
case game::TYPE_UNDEFINED:
|
||||
logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] undefined", inst ? "CSC" : "GSC");
|
||||
break;
|
||||
case game::TYPE_POINTER:
|
||||
logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] Pointer[%s]", inst ? "CSC" : "GSC", game::var_typename[game::ScrVm_GetPointerType(inst, offset)]);
|
||||
break;
|
||||
case game::TYPE_STRING:
|
||||
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, offset)->hash);
|
||||
logger::write(logger::LOG_TYPE_INFO, "[ %s VM ] %llx", inst ? "CSC" : "GSC", lookup_hash(inst, "hash", game::ScrVm_GetHash(&hash, inst, offset)->hash));
|
||||
}
|
||||
break;
|
||||
case game::TYPE_INTEGER:
|
||||
@ -163,7 +214,7 @@ namespace gsc_funcs
|
||||
}
|
||||
break;
|
||||
default:
|
||||
gsc_error("Call of ShieldLog with unknown type: %d", inst, false, type);
|
||||
gsc_error("Call of ShieldLog with unknown type: %s", inst, false, game::var_typename[type]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -476,9 +527,427 @@ namespace gsc_funcs
|
||||
}
|
||||
else
|
||||
{
|
||||
gsc_error("compiler::function_%llx not implemented", inst, false, hash);
|
||||
gsc_error("compiler::%s not implemented", inst, false, lookup_hash(inst, "function", hash));
|
||||
}
|
||||
}
|
||||
|
||||
void pre_cache_resource(game::scriptInstance_t inst)
|
||||
{
|
||||
game::BO4_AssetRef_t hashRef{};
|
||||
|
||||
byte type;
|
||||
if (game::ScrVm_GetType(inst, 0) == game::TYPE_STRING)
|
||||
{
|
||||
type = xassets::BG_Cache_GetTypeIndex(game::ScrVm_GetString(inst, 0));
|
||||
}
|
||||
else if (game::ScrVm_GetType(inst, 0) == game::TYPE_INTEGER)
|
||||
{
|
||||
type = (byte)(game::ScrVm_GetInt(inst, 0) & 0xFF);
|
||||
}
|
||||
else
|
||||
{
|
||||
gsc_error("bad param type for PreCache, excepted int or string, received %s", inst, false, game::var_typename[game::ScrVm_GetType(inst, 0)]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!type || type >= xassets::BG_CACHE_TYPE_COUNT)
|
||||
{
|
||||
gsc_error("bad bgcache type for PreCache", inst, false);
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t res = game::ScrVm_GetHash(&hashRef, inst, 1)->hash;
|
||||
|
||||
logger::write(logger::LOG_TYPE_DEBUG, "precaching resource type=%d/name=hash_%llx", type, res);
|
||||
|
||||
hashRef.hash = res;
|
||||
hashRef.null = 0;
|
||||
xassets::BG_Cache_RegisterAndGet((xassets::BGCacheTypes)type, &hashRef);
|
||||
}
|
||||
|
||||
void shield_to_json_val(game::scriptInstance_t inst, game::ScrVarValue_t* val, rapidjson::Value& member, rapidjson::Document& doc, int depth)
|
||||
{
|
||||
if (depth >= 10)
|
||||
{
|
||||
// avoid recursion
|
||||
member.SetNull();
|
||||
return;
|
||||
}
|
||||
switch (val->type)
|
||||
{
|
||||
case game::TYPE_UNDEFINED: return; // ignore
|
||||
case game::TYPE_FLOAT:
|
||||
{
|
||||
member.SetFloat(val->u.floatValue);
|
||||
}
|
||||
break;
|
||||
case game::TYPE_INTEGER:
|
||||
{
|
||||
member.SetInt64(val->u.intValue);
|
||||
}
|
||||
break;
|
||||
case game::TYPE_STRING:
|
||||
{
|
||||
member.SetString(game::ScrStr_ConvertToString(val->u.pointerValue), doc.GetAllocator());
|
||||
}
|
||||
break;
|
||||
case game::TYPE_VECTOR:
|
||||
{
|
||||
member.SetObject();
|
||||
auto obj = member.GetObj();
|
||||
obj.AddMember(rapidjson::StringRef(gsc_json_type), "vector", doc.GetAllocator());
|
||||
obj.AddMember(rapidjson::StringRef("x"), val->u.vectorValue[0], doc.GetAllocator());
|
||||
obj.AddMember(rapidjson::StringRef("y"), val->u.vectorValue[1], doc.GetAllocator());
|
||||
obj.AddMember(rapidjson::StringRef("z"), val->u.vectorValue[2], doc.GetAllocator());
|
||||
}
|
||||
break;
|
||||
case game::TYPE_HASH:
|
||||
{
|
||||
member.SetObject();
|
||||
auto obj = member.GetObj();
|
||||
obj.AddMember(rapidjson::StringRef(gsc_json_type), "hash", doc.GetAllocator());
|
||||
auto hash = val->u.intValue & 0x7FFFFFFFFFFFFFFF;
|
||||
std::string name = std::format("hash_{:x}", hash);
|
||||
obj.AddMember(rapidjson::StringRef("hash"), name, doc.GetAllocator());
|
||||
}
|
||||
break;
|
||||
case game::TYPE_POINTER:
|
||||
{
|
||||
game::ScrVarIndex_t ptr_id = val->u.pointerValue;
|
||||
game::ScrVarValue_t& ptr_val = game::scrVarGlob[inst].scriptValues[ptr_id];
|
||||
|
||||
member.SetObject();
|
||||
auto obj = member.GetObj();
|
||||
|
||||
|
||||
if (ptr_val.type == game::TYPE_ARRAY)
|
||||
{
|
||||
obj.AddMember(rapidjson::StringRef(gsc_json_type), "array", doc.GetAllocator());
|
||||
|
||||
auto size = game::scrVarGlob[inst].scriptVariablesObjectInfo1[ptr_id].size;
|
||||
if (size)
|
||||
{
|
||||
game::ScrVar_t* var = &game::scrVarGlob[inst].scriptVariables[ptr_val.u.pointerValue];
|
||||
game::ScrVarValue_t* value = &game::scrVarGlob[inst].scriptValues[ptr_val.u.pointerValue];
|
||||
|
||||
while (var)
|
||||
{
|
||||
rapidjson::Value subval{};
|
||||
|
||||
// read struct value
|
||||
shield_to_json_val(inst, value, subval, doc, depth + 1);
|
||||
|
||||
if (var->_anon_0.nameType == 1) // integer index
|
||||
{
|
||||
std::string keyval = std::format("{}", var->nameIndex);
|
||||
rapidjson::Value keyjson{ rapidjson::kStringType };
|
||||
keyjson.SetString(keyval, doc.GetAllocator());
|
||||
obj.AddMember(keyjson, subval, doc.GetAllocator());
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string keyval = std::format("#var_{:x}", var->nameIndex);
|
||||
rapidjson::Value keyjson{ rapidjson::kStringType };
|
||||
keyjson.SetString(keyval, doc.GetAllocator());
|
||||
obj.AddMember(keyjson, subval, doc.GetAllocator());
|
||||
}
|
||||
|
||||
if (!var->nextSibling)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
value = &game::scrVarGlob[inst].scriptValues[var->nextSibling];
|
||||
var = &game::scrVarGlob[inst].scriptVariables[var->nextSibling];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ptr_val.type == game::TYPE_STRUCT)
|
||||
{
|
||||
auto size = game::scrVarGlob[inst].scriptVariablesObjectInfo1[ptr_id].size;
|
||||
if (size)
|
||||
{
|
||||
game::ScrVar_t* var = &game::scrVarGlob[inst].scriptVariables[ptr_val.u.pointerValue];
|
||||
game::ScrVarValue_t* value = &game::scrVarGlob[inst].scriptValues[ptr_val.u.pointerValue];
|
||||
|
||||
while (var)
|
||||
{
|
||||
rapidjson::Value subval{};
|
||||
|
||||
// read struct value
|
||||
shield_to_json_val(inst, value, subval, doc, depth + 1);
|
||||
|
||||
std::string keyval = std::format("var_{:x}", var->nameIndex);
|
||||
rapidjson::Value keyjson{ rapidjson::kStringType };
|
||||
keyjson.SetString(keyval, doc.GetAllocator());
|
||||
obj.AddMember(keyjson, subval, doc.GetAllocator());
|
||||
|
||||
if (!var->nextSibling)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
value = &game::scrVarGlob[inst].scriptValues[var->nextSibling];
|
||||
var = &game::scrVarGlob[inst].scriptVariables[var->nextSibling];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
// shared_struct and entity aren't using the same syntax
|
||||
gsc_error("invalid tojson param pointer type: %s", inst, false, game::var_typename[ptr_val.type]);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
gsc_error("invalid tojson param type: %s", inst, false, game::var_typename[val->type]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shield_to_json(game::scriptInstance_t inst)
|
||||
{
|
||||
const char* fileid = game::ScrVm_GetString(inst, 0);
|
||||
std::string_view v{ fileid };
|
||||
|
||||
if (v.rfind("/", 0) != std::string::npos || v.rfind("\\", 0) != std::string::npos)
|
||||
{
|
||||
gsc_error("can't save json containing '/' or '\\'", inst, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v.length() > gsc_json_data_name_max_length)
|
||||
{
|
||||
gsc_error("json name can't be longer than %d", inst, false, gsc_json_data_name_max_length);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string file{ std::format("project-bo4/saved/{}/{}.json", (inst ? "client" : "server"), fileid) };
|
||||
|
||||
if (inst)
|
||||
{
|
||||
std::filesystem::create_directories("project-bo4/saved/client");
|
||||
}
|
||||
else
|
||||
{
|
||||
std::filesystem::create_directories("project-bo4/saved/server");
|
||||
}
|
||||
|
||||
game::ScrVarValue_t* val = &game::scrVmPub[inst].top[-1];
|
||||
|
||||
if (game::ScrVm_GetNumParam(inst) == 1 || val->type == game::TYPE_UNDEFINED)
|
||||
{
|
||||
std::filesystem::remove(file);
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::Document doc{};
|
||||
|
||||
shield_to_json_val(inst, val, doc, doc, 0);
|
||||
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
|
||||
doc.Accept(writer);
|
||||
|
||||
std::string json_data(buffer.GetString(), buffer.GetLength());
|
||||
utilities::io::write_file(file, json_data);
|
||||
}
|
||||
|
||||
void shield_from_json_push_struct(game::scriptInstance_t inst, rapidjson::Value& member)
|
||||
{
|
||||
if (member.IsString())
|
||||
{
|
||||
game::ScrVm_AddString(inst, member.GetString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (member.IsFloat())
|
||||
{
|
||||
game::ScrVm_AddFloat(inst, member.GetFloat());
|
||||
return;
|
||||
}
|
||||
|
||||
if (member.IsNumber())
|
||||
{
|
||||
game::ScrVm_AddInt(inst, member.GetInt64());
|
||||
return;
|
||||
}
|
||||
|
||||
if (member.IsBool())
|
||||
{
|
||||
game::ScrVm_AddBool(inst, member.GetBool());
|
||||
return;
|
||||
}
|
||||
|
||||
if (member.IsNull())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, "ShieldFromJson: read null");
|
||||
game::ScrVm_AddUndefined(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
if (member.IsArray())
|
||||
{
|
||||
auto arr = member.GetArray();
|
||||
|
||||
game::ScrVar_PushArray(inst);
|
||||
|
||||
for (rapidjson::Value& elem : arr)
|
||||
{
|
||||
shield_from_json_push_struct(inst, elem);
|
||||
game::ScrVm_AddToArray(inst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (member.IsObject())
|
||||
{
|
||||
auto obj = member.GetObj();
|
||||
auto typefield = obj.FindMember(gsc_json_type);
|
||||
|
||||
if (typefield != obj.MemberEnd() && typefield->value.IsString())
|
||||
{
|
||||
const char* type = typefield->value.GetString();
|
||||
|
||||
if (!_strcmpi(type, "array"))
|
||||
{
|
||||
game::ScrVar_PushArray(inst);
|
||||
|
||||
game::BO4_AssetRef_t name{};
|
||||
|
||||
for (auto& [key, elem] : obj)
|
||||
{
|
||||
if (!key.IsString())
|
||||
{
|
||||
gsc_error("read bad key value for array", inst, false);
|
||||
return;
|
||||
}
|
||||
if (!_strcmpi(key.GetString(), gsc_json_type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const char* keystr = key.GetString();
|
||||
if (!*keystr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
shield_from_json_push_struct(inst, elem);
|
||||
|
||||
if (*keystr == '#')
|
||||
{
|
||||
name.hash = fnv1a::generate_hash_pattern(key.GetString() + 1);
|
||||
game::ScrVm_AddToArrayStringIndexed(inst, &name);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint64_t keyval{};
|
||||
try
|
||||
{
|
||||
keyval = std::strtoull(keystr, nullptr, 10);
|
||||
}
|
||||
catch (const std::invalid_argument& e)
|
||||
{
|
||||
gsc_error("invalid key for array member %s", inst, false, e.what());
|
||||
return;
|
||||
}
|
||||
ScrVm_AddToArrayIntIndexed(inst, keyval);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_strcmpi(type, "vector"))
|
||||
{
|
||||
auto x = obj.FindMember("x");
|
||||
auto y = obj.FindMember("y");
|
||||
auto z = obj.FindMember("z");
|
||||
|
||||
if (
|
||||
x != obj.MemberEnd() && y != obj.MemberEnd() && z != obj.MemberEnd()
|
||||
&& x->value.IsNumber() && y->value.IsNumber() && z->value.IsNumber())
|
||||
{
|
||||
game::vec3_t vec{};
|
||||
|
||||
vec[0] = x->value.GetFloat();
|
||||
vec[1] = y->value.GetFloat();
|
||||
vec[2] = z->value.GetFloat();
|
||||
game::ScrVm_AddVector(inst, &vec);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!_strcmpi(type, "hash"))
|
||||
{
|
||||
auto value = obj.FindMember("hash");
|
||||
|
||||
if (value != obj.MemberEnd() && value->value.IsString()) {
|
||||
game::BO4_AssetRef_t hash
|
||||
{
|
||||
.hash = (int64_t)fnv1a::generate_hash_pattern(value->value.GetString())
|
||||
};
|
||||
|
||||
game::ScrVm_AddHash(inst, &hash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// object by default
|
||||
uint32_t struct_id = game::ScrVm_AddStruct(inst);
|
||||
|
||||
for (auto& [key, elem] : obj)
|
||||
{
|
||||
if (!key.IsString())
|
||||
{
|
||||
gsc_error("read bad key value for struct", inst, false);
|
||||
return;
|
||||
}
|
||||
if (!_strcmpi(key.GetString(), gsc_json_type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
shield_from_json_push_struct(inst, elem);
|
||||
game::ScrVm_SetStructField(inst, struct_id, canon_hash_pattern(key.GetString()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
gsc_error("bad json element: %d", inst, false, (int)member.GetType());
|
||||
}
|
||||
|
||||
void shield_from_json(game::scriptInstance_t inst)
|
||||
{
|
||||
const char* fileid = game::ScrVm_GetString(inst, 0);
|
||||
std::string_view v{ fileid };
|
||||
|
||||
if (v.rfind("/", 0) != std::string::npos || v.rfind("\\", 0) != std::string::npos)
|
||||
{
|
||||
gsc_error("can't save json containing '/' or '\\'", inst, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (v.length() > gsc_json_data_name_max_length)
|
||||
{
|
||||
gsc_error("json name can't be longer than %d", inst, false, gsc_json_data_name_max_length);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string file{ std::format("project-bo4/saved/{}/{}.json", (inst ? "client" : "server"), fileid) };
|
||||
|
||||
std::string file_content{};
|
||||
|
||||
if (!utilities::io::read_file(file, &file_content))
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, "trying to read unknown config file %s", file.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
rapidjson::Document doc{};
|
||||
doc.Parse(file_content);
|
||||
|
||||
shield_from_json_push_struct(inst, doc);
|
||||
}
|
||||
|
||||
game::BO4_BuiltinFunctionDef custom_functions_gsc[] =
|
||||
{
|
||||
@ -551,6 +1020,34 @@ namespace gsc_funcs
|
||||
.max_args = 255,
|
||||
.actionFunc = serious_custom_func,
|
||||
.type = 0,
|
||||
},
|
||||
{ // PreCache(type, name)
|
||||
.canonId = canon_hash("PreCache"),
|
||||
.min_args = 2,
|
||||
.max_args = 2,
|
||||
.actionFunc = pre_cache_resource,
|
||||
.type = 0,
|
||||
},
|
||||
{ // ShieldFromJson(name)->object
|
||||
.canonId = canon_hash("ShieldFromJson"),
|
||||
.min_args = 1,
|
||||
.max_args = 1,
|
||||
.actionFunc = shield_from_json,
|
||||
.type = 0
|
||||
},
|
||||
{ // ShieldFromJson(name)
|
||||
.canonId = canon_hash("ShieldRemoveJson"),
|
||||
.min_args = 1,
|
||||
.max_args = 1,
|
||||
.actionFunc = shield_to_json,
|
||||
.type = 0
|
||||
},
|
||||
{// ShieldFromJson(name, object = undefined)
|
||||
.canonId = canon_hash("ShieldToJson"),
|
||||
.min_args = 1,
|
||||
.max_args = 2,
|
||||
.actionFunc = shield_to_json,
|
||||
.type = 0
|
||||
}
|
||||
};
|
||||
game::BO4_BuiltinFunctionDef custom_functions_csc[] =
|
||||
@ -624,6 +1121,27 @@ namespace gsc_funcs
|
||||
.max_args = 255,
|
||||
.actionFunc = serious_custom_func,
|
||||
.type = 0,
|
||||
},
|
||||
{ // ShieldFromJson(name)->object
|
||||
.canonId = canon_hash("ShieldFromJson"),
|
||||
.min_args = 1,
|
||||
.max_args = 1,
|
||||
.actionFunc = shield_from_json,
|
||||
.type = 0
|
||||
},
|
||||
{ // ShieldFromJson(name)
|
||||
.canonId = canon_hash("ShieldRemoveJson"),
|
||||
.min_args = 1,
|
||||
.max_args = 1,
|
||||
.actionFunc = shield_to_json,
|
||||
.type = 0
|
||||
},
|
||||
{// ShieldToJson(name, object = undefined)
|
||||
.canonId = canon_hash("ShieldToJson"),
|
||||
.min_args = 1,
|
||||
.max_args = 2,
|
||||
.actionFunc = shield_to_json,
|
||||
.type = 0
|
||||
}
|
||||
};
|
||||
|
||||
@ -811,10 +1329,14 @@ namespace gsc_funcs
|
||||
break;
|
||||
default:
|
||||
// put custom message for our id
|
||||
if (code == custom_error_id)
|
||||
if (code == game::runtime_errors::custom_error_id)
|
||||
{
|
||||
game::scrVarPub[inst].error_message = unused;
|
||||
}
|
||||
else
|
||||
{
|
||||
game::scrVarPub[inst].error_message = game::runtime_errors::get_error_message(code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -843,6 +1365,100 @@ namespace gsc_funcs
|
||||
logger::write(logger::LOG_TYPE_ERROR, str);
|
||||
}
|
||||
|
||||
void patch_scrvm_runtime_error()
|
||||
{
|
||||
const auto scrvm_runtimeerror = 0x1427775B0_g;
|
||||
void* stub = utilities::hook::assemble([scrvm_runtimeerror](utilities::hook::assembler& a)
|
||||
{
|
||||
a.mov(ebx, 0x20); // set errorcode to 0x20
|
||||
a.mov(al, 0); // set ZF to avoid the ebx overwrite
|
||||
|
||||
a.jmp(scrvm_runtimeerror + 0x3DC);
|
||||
}
|
||||
);
|
||||
|
||||
utilities::hook::jump(scrvm_runtimeerror + 0x3A0, stub);
|
||||
}
|
||||
|
||||
void get_gsc_export_info(game::scriptInstance_t inst, byte* codepos, const char** scriptname, int32_t* sloc, int32_t* crc, int32_t* vm)
|
||||
{
|
||||
static char scriptnamebuffer[game::scriptInstance_t::SCRIPTINSTANCE_MAX][0x200];
|
||||
game::GSC_OBJ* script_obj = nullptr;
|
||||
{
|
||||
game::scoped_critical_section scs{ 0x36, game::SCOPED_CRITSECT_NORMAL };
|
||||
|
||||
uint32_t count = game::gObjFileInfoCount[inst];
|
||||
|
||||
for (size_t i = 0; i < count; i++)
|
||||
{
|
||||
game::objFileInfo_t& info = (*game::gObjFileInfo)[inst][i];
|
||||
|
||||
game::GSC_OBJ* obj = info.activeVersion;
|
||||
|
||||
if (codepos >= obj->magic + obj->start_data && codepos < obj->magic + obj->start_data + obj->data_length)
|
||||
{
|
||||
script_obj = obj;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (script_obj)
|
||||
{
|
||||
game::GSC_EXPORT_ITEM* export_item = nullptr;
|
||||
|
||||
uint32_t rloc = (uint32_t)(codepos - script_obj->magic);
|
||||
|
||||
for (size_t i = 0; i < script_obj->exports_count; i++)
|
||||
{
|
||||
game::GSC_EXPORT_ITEM* exp = reinterpret_cast<game::GSC_EXPORT_ITEM*>(script_obj->magic + script_obj->exports_offset) + i;
|
||||
|
||||
if (rloc < exp->address)
|
||||
{
|
||||
continue; // our code is after
|
||||
}
|
||||
|
||||
if (export_item && export_item->address > exp->address)
|
||||
{
|
||||
continue; // we already have a better candidate
|
||||
}
|
||||
|
||||
export_item = exp;
|
||||
}
|
||||
|
||||
|
||||
if (scriptname)
|
||||
{
|
||||
if (export_item)
|
||||
{
|
||||
std::string script_name = lookup_hash(inst, "script", script_obj->name & 0x7FFFFFFFFFFFFFFF);
|
||||
|
||||
sprintf_s(scriptnamebuffer[inst], "%s::%s@%x", script_name.c_str(), lookup_hash(inst, "function", export_item->name), rloc - export_item->address);
|
||||
} else
|
||||
{
|
||||
sprintf_s(scriptnamebuffer[inst], "%s", lookup_hash(inst, "script", script_obj->name & 0x7FFFFFFFFFFFFFFF));
|
||||
}
|
||||
|
||||
*scriptname = scriptnamebuffer[inst];
|
||||
}
|
||||
|
||||
if (sloc)
|
||||
{
|
||||
*sloc = rloc;
|
||||
}
|
||||
|
||||
if (crc)
|
||||
{
|
||||
*crc = script_obj->crc;
|
||||
}
|
||||
|
||||
if (vm)
|
||||
{
|
||||
*vm = script_obj->magic[7];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
@ -861,6 +1477,10 @@ namespace gsc_funcs
|
||||
// log gsc errors
|
||||
scrvm_error.create(0x142770330_g, scrvm_error_stub);
|
||||
utilities::hook::jump(0x142890470_g, scrvm_log_compiler_error);
|
||||
|
||||
// better runtime error
|
||||
utilities::hook::jump(0x142748550_g, get_gsc_export_info);
|
||||
patch_scrvm_runtime_error();
|
||||
|
||||
scheduler::loop(draw_hud, scheduler::renderer);
|
||||
}
|
||||
|
@ -9,8 +9,10 @@ namespace gsc_funcs
|
||||
extern bool enable_dev_func;
|
||||
|
||||
uint32_t canon_hash(const char* str);
|
||||
|
||||
constexpr uint64_t custom_error_id = 0x42693201;
|
||||
|
||||
uint32_t canon_hash_pattern(const char* str);
|
||||
|
||||
void gsc_error(const char* message, game::scriptInstance_t inst, bool terminal, ...);
|
||||
const char* lookup_hash(game::scriptInstance_t inst, const char* type, uint64_t hash);
|
||||
|
||||
void ScrVm_AddToArrayIntIndexed(game::scriptInstance_t inst, uint64_t index);
|
||||
}
|
256
source/proxy-dll/component/hashes.cpp
Normal file
256
source/proxy-dll/component/hashes.cpp
Normal file
@ -0,0 +1,256 @@
|
||||
#include <std_include.hpp>
|
||||
#include "hashes.hpp"
|
||||
#include "gsc_funcs.hpp"
|
||||
|
||||
#include "definitions/variables.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utilities/json_config.hpp>
|
||||
|
||||
namespace hashes
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool enabled = true;
|
||||
std::unordered_map<uint64_t, std::string>& hash_storage()
|
||||
{
|
||||
static std::unordered_map<uint64_t, std::string> storage{};
|
||||
return storage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char* hff_names[]
|
||||
{
|
||||
"COMMON",
|
||||
"STRING",
|
||||
"SERIOUS_COMPILER"
|
||||
};
|
||||
|
||||
bool is_valid_h32(const char* str)
|
||||
{
|
||||
const char* s = str;
|
||||
|
||||
while (*s)
|
||||
{
|
||||
char c = *(s++);
|
||||
|
||||
if (!(
|
||||
(c >= 'A' && c <= 'Z')
|
||||
|| (c >= 'a' && c <= 'z')
|
||||
|| (c >= '0' && c <= '9')
|
||||
|| c == '_'))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true; // [A-Za-z0-9_]+
|
||||
}
|
||||
}
|
||||
|
||||
const char* lookup(uint64_t hash)
|
||||
{
|
||||
auto& hs = hash_storage();
|
||||
auto it = hs.find(hash);
|
||||
if (it == hs.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return it->second.c_str();
|
||||
}
|
||||
|
||||
const char* lookup_tmp(const char* type, uint64_t hash)
|
||||
{
|
||||
static char tmp_buffer[0x50];
|
||||
|
||||
const char* val = lookup(hash);
|
||||
|
||||
if (val)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
sprintf_s(tmp_buffer, "%s_%llx", type, hash);
|
||||
|
||||
return tmp_buffer;
|
||||
}
|
||||
|
||||
void add_hash(uint64_t hash, const char* value)
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
hash_storage()[hash] = value;
|
||||
}
|
||||
|
||||
const char* get_format_name(hashes_file_format format)
|
||||
{
|
||||
if (format >= 0 && format < HFF_COUNT)
|
||||
{
|
||||
return hff_names[format];
|
||||
}
|
||||
return "<invalid>";
|
||||
}
|
||||
|
||||
hashes_file_format get_format_idx(const char* name)
|
||||
{
|
||||
for (size_t i = 0; i < HFF_COUNT; i++)
|
||||
{
|
||||
if (!_strcmpi(name, hff_names[i]))
|
||||
{
|
||||
return (hashes_file_format)i;
|
||||
}
|
||||
}
|
||||
return HFF_COUNT;
|
||||
}
|
||||
|
||||
bool load_file(std::filesystem::path& file, hashes_file_format format)
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
logger::write(logger::LOG_TYPE_DEBUG, std::format("loading hash file {}", file.string()));
|
||||
switch (format)
|
||||
{
|
||||
case HFF_STRING:
|
||||
{
|
||||
// basic format, each line is a string, allows fast updates
|
||||
|
||||
std::ifstream s(file);
|
||||
|
||||
if (!s)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("can't read hash file {}", file.string()));
|
||||
return false; // nothing to read
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (s.good() && std::getline(s, line))
|
||||
{
|
||||
const char* str = line.c_str();
|
||||
|
||||
if (is_valid_h32(str))
|
||||
{
|
||||
add_hash(gsc_funcs::canon_hash(str), str);
|
||||
}
|
||||
|
||||
add_hash(fnv1a::generate_hash(str), str);
|
||||
}
|
||||
|
||||
s.close();
|
||||
return true;
|
||||
}
|
||||
case HFF_COMMON:
|
||||
{
|
||||
// common precomputed format used by greyhound index and other tools
|
||||
// each line: <hash>,<string>
|
||||
|
||||
std::ifstream s(file);
|
||||
|
||||
if (!s)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("can't read hash file {}", file.string()));
|
||||
return false; // nothing to read
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (s.good() && std::getline(s, line))
|
||||
{
|
||||
auto idx = line.rfind(", ", 18);
|
||||
|
||||
if (idx == std::string::npos)
|
||||
{
|
||||
continue; // bad line
|
||||
}
|
||||
|
||||
char value[18] = {};
|
||||
const char* str = line.c_str();
|
||||
|
||||
memcpy(value, str, idx);
|
||||
|
||||
add_hash(std::strtoull(value, nullptr, 16), str + idx + 1);
|
||||
}
|
||||
|
||||
s.close();
|
||||
return false;
|
||||
}
|
||||
case HFF_SERIOUS_COMPILER:
|
||||
{
|
||||
// precomputed format generated by the serious compiler
|
||||
// each line: 0x<hash>, <string>
|
||||
|
||||
std::ifstream s(file);
|
||||
|
||||
if (!s)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("can't read hash file {}", file.string()));
|
||||
return false; // nothing to read
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (s.good() && std::getline(s, line))
|
||||
{
|
||||
if (!line.starts_with("0x"))
|
||||
{
|
||||
continue; // bad line
|
||||
}
|
||||
|
||||
auto idx = line.rfind(", ", 18);
|
||||
|
||||
if (idx == std::string::npos)
|
||||
{
|
||||
continue; // bad line
|
||||
}
|
||||
|
||||
char value[18] = {};
|
||||
const char* str = line.c_str();
|
||||
|
||||
memcpy(value, str + 2, idx - 2);
|
||||
|
||||
add_hash(std::strtoull(value, nullptr, 16), str + idx + 2);
|
||||
}
|
||||
|
||||
s.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("bad format type {} for file {}", (int)format, file.string()));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void pre_start() override
|
||||
{
|
||||
enabled = utilities::json_config::ReadBoolean("gsc", "hashes", true);
|
||||
|
||||
if (!enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// load default files
|
||||
std::filesystem::path default_file_name_str = "project-bo4/strings.txt";
|
||||
|
||||
if (std::filesystem::exists(default_file_name_str))
|
||||
{
|
||||
load_file(default_file_name_str, HFF_STRING);
|
||||
}
|
||||
|
||||
std::filesystem::path default_file_name_common = "project-bo4/hashes.csv";
|
||||
|
||||
if (std::filesystem::exists(default_file_name_common))
|
||||
{
|
||||
load_file(default_file_name_common, HFF_COMMON);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(hashes::component)
|
18
source/proxy-dll/component/hashes.hpp
Normal file
18
source/proxy-dll/component/hashes.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
namespace hashes
|
||||
{
|
||||
enum hashes_file_format
|
||||
{
|
||||
HFF_COMMON = 0,
|
||||
HFF_STRING,
|
||||
HFF_SERIOUS_COMPILER,
|
||||
HFF_COUNT
|
||||
};
|
||||
const char* lookup(uint64_t hash);
|
||||
const char* lookup_tmp(const char* type, uint64_t hash);
|
||||
void add_hash(uint64_t hash, const char* value);
|
||||
const char* get_format_name(hashes_file_format format);
|
||||
hashes_file_format get_format_idx(const char* name);
|
||||
bool load_file(std::filesystem::path& file, hashes_file_format format);
|
||||
}
|
211
source/proxy-dll/component/lua.cpp
Normal file
211
source/proxy-dll/component/lua.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
#include <std_include.hpp>
|
||||
#include "definitions/game.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <utilities/json_config.hpp>
|
||||
#include <utilities/hook.hpp>
|
||||
|
||||
namespace lua {
|
||||
typedef void (*HksLogFunc)(game::lua_state* s, const char* fmt, ...);
|
||||
|
||||
utilities::hook::detour lual_error_hook;
|
||||
utilities::hook::detour hksi_lual_error_hook;
|
||||
|
||||
void lual_error_stub(game::lua_state* state, const char* fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
char buffer[0x800];
|
||||
|
||||
vsprintf_s(buffer, fmt, va);
|
||||
va_end(va);
|
||||
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("[lual_error] {}", buffer));
|
||||
lual_error_hook.invoke<void>(state, "%s", buffer);
|
||||
}
|
||||
|
||||
void hksi_lual_error_stub(game::lua_state* state, const char* fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start(va, fmt);
|
||||
char buffer[0x800];
|
||||
|
||||
vsprintf_s(buffer, fmt, va);
|
||||
va_end(va);
|
||||
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("[hksi_lual_error] {}", buffer));
|
||||
hksi_lual_error_hook.invoke<void>(state, "%s", buffer);
|
||||
}
|
||||
|
||||
void print_out(logger::type type, game::consoleLabel_e label, const char* info)
|
||||
{
|
||||
size_t len = std::strlen(info);
|
||||
|
||||
while (len && info[len - 1] == '\n')
|
||||
{
|
||||
len--;
|
||||
}
|
||||
|
||||
std::string buff{ info, len };
|
||||
|
||||
if (label != game::CON_LABEL_TEMP)
|
||||
{
|
||||
const char* label_str;
|
||||
|
||||
if (label > 0 && label < game::CON_LABEL_COUNT)
|
||||
{
|
||||
label_str = game::builtinLabels[label];
|
||||
}
|
||||
else
|
||||
{
|
||||
label_str = "INVALID";
|
||||
}
|
||||
|
||||
logger::write(type, std::format("[lua] {} - {}", label_str, buff));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// no label
|
||||
logger::write(type, std::format("[lua] {}", buff));
|
||||
}
|
||||
}
|
||||
|
||||
void lua_engine_print(logger::type type, game::lua_state* s)
|
||||
{
|
||||
game::hks_object* base = s->m_apistack.base;
|
||||
game::hks_object* top = s->m_apistack.top;
|
||||
|
||||
game::consoleLabel_e label = game::CON_LABEL_TEMP;
|
||||
const char* info = "";
|
||||
|
||||
if (base < top)
|
||||
{
|
||||
label = (game::consoleLabel_e)game::hks_obj_tonumber(s, base);
|
||||
}
|
||||
if (base + 1 < top)
|
||||
{
|
||||
info = game::hks_obj_tolstring(s, base + 1, nullptr);
|
||||
}
|
||||
|
||||
print_out(type, label, info);
|
||||
}
|
||||
|
||||
void lua_engine_print_info(game::lua_state* s)
|
||||
{
|
||||
lua_engine_print(logger::LOG_TYPE_INFO, s);
|
||||
}
|
||||
|
||||
void lua_engine_print_warning(game::lua_state* s)
|
||||
{
|
||||
lua_engine_print(logger::LOG_TYPE_WARN, s);
|
||||
}
|
||||
|
||||
void lua_engine_print_error(game::lua_state* s)
|
||||
{
|
||||
lua_engine_print(logger::LOG_TYPE_ERROR, s);
|
||||
}
|
||||
|
||||
int lua_unsafe_function_stub(game::lua_state* state)
|
||||
{
|
||||
static std::once_flag f{};
|
||||
|
||||
std::call_once(f, []() { logger::write(logger::LOG_TYPE_ERROR, "calling of a disabled lua unsafe method, these methods can enabled in the config file."); });
|
||||
return 0;
|
||||
}
|
||||
|
||||
void patch_unsafe_lua_functions()
|
||||
{
|
||||
if (utilities::json_config::ReadBoolean("lua", "allow_unsafe_function", false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// in boiii, need to be added, not in bo4?
|
||||
// - 0x141FD3220_g engine_openurl
|
||||
// - 0x141D34190_g debug
|
||||
|
||||
utilities::hook::jump(0x1437358D0_g, lua_unsafe_function_stub); // base_loadfile
|
||||
utilities::hook::jump(0x143736A50_g, lua_unsafe_function_stub); // base_load
|
||||
utilities::hook::jump(0x14373B640_g, lua_unsafe_function_stub); // string_dump
|
||||
|
||||
|
||||
// io helpers
|
||||
utilities::hook::jump(0x14373F000_g, lua_unsafe_function_stub); // os_exit
|
||||
utilities::hook::jump(0x14373F020_g, lua_unsafe_function_stub); // os_remove
|
||||
utilities::hook::jump(0x14373F150_g, lua_unsafe_function_stub); // os_rename
|
||||
utilities::hook::jump(0x14373EEC0_g, lua_unsafe_function_stub); // os_tmpname
|
||||
utilities::hook::jump(0x14373EE90_g, lua_unsafe_function_stub); // os_sleep
|
||||
utilities::hook::jump(0x14373ED30_g, lua_unsafe_function_stub); // os_execute
|
||||
utilities::hook::jump(0x14373ED90_g, lua_unsafe_function_stub); // os_getenv
|
||||
|
||||
utilities::hook::jump(0x14373D170_g, lua_unsafe_function_stub); // io_close
|
||||
utilities::hook::jump(0x14373D060_g, lua_unsafe_function_stub); // io_close_file
|
||||
utilities::hook::jump(0x14373D030_g, lua_unsafe_function_stub); // io_flush
|
||||
utilities::hook::jump(0x14375C7B0_g, lua_unsafe_function_stub); // io_output/io_input
|
||||
utilities::hook::jump(0x14373D7B0_g, lua_unsafe_function_stub); // io_lines
|
||||
utilities::hook::jump(0x14373D670_g, lua_unsafe_function_stub); // io_lines
|
||||
utilities::hook::jump(0x14373DD20_g, lua_unsafe_function_stub); // io_open
|
||||
utilities::hook::jump(0x14373D5B0_g, lua_unsafe_function_stub); // io_read
|
||||
utilities::hook::jump(0x14373D200_g, lua_unsafe_function_stub); // io_read_file
|
||||
utilities::hook::jump(0x14373DA90_g, lua_unsafe_function_stub); // io_type
|
||||
utilities::hook::jump(0x14373CFB0_g, lua_unsafe_function_stub); // io_write
|
||||
utilities::hook::jump(0x14373CF70_g, lua_unsafe_function_stub); // io_write_file
|
||||
utilities::hook::jump(0x14373DC60_g, lua_unsafe_function_stub); // io_tmpfile
|
||||
utilities::hook::jump(0x14373E160_g, lua_unsafe_function_stub); // io_popen
|
||||
utilities::hook::jump(0x14373DE40_g, lua_unsafe_function_stub); // io_seek_file
|
||||
utilities::hook::jump(0x14373DF80_g, lua_unsafe_function_stub); // io_setvbuf
|
||||
utilities::hook::jump(0x14373E0A0_g, lua_unsafe_function_stub); // io_tostring
|
||||
utilities::hook::jump(0x143735360_g, lua_unsafe_function_stub); // serialize_persist
|
||||
utilities::hook::jump(0x143735590_g, lua_unsafe_function_stub); // serialize_unpersist
|
||||
|
||||
utilities::hook::jump(0x14373BC40_g, lua_unsafe_function_stub); // havokscript_compiler_settings
|
||||
utilities::hook::jump(0x14373C670_g, lua_unsafe_function_stub); // havokscript_getgcweights
|
||||
utilities::hook::jump(0x14373C100_g, lua_unsafe_function_stub); // havokscript_setgcweights
|
||||
utilities::hook::jump(0x143734DC0_g, lua_unsafe_function_stub); // package_loadlib
|
||||
|
||||
}
|
||||
|
||||
int lui_panic_stub(game::lua_state* vm)
|
||||
{
|
||||
game::hks_object* arg0 = vm->m_apistack.top - 1;
|
||||
|
||||
const char* error_message = "";
|
||||
if (arg0 >= vm->m_apistack.base)
|
||||
{
|
||||
error_message = game::hks_obj_tolstring(vm, arg0, nullptr);
|
||||
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("[lui_panic] {}", error_message));
|
||||
}
|
||||
else {
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("[lui_panic] empty"));
|
||||
}
|
||||
|
||||
game::Lua_CoD_LuaStateManager_Error(100007, error_message, vm);
|
||||
return 0;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (utilities::json_config::ReadBoolean("lua", "info", false))
|
||||
{
|
||||
utilities::hook::jump(0x143956010_g, lua_engine_print_info);
|
||||
|
||||
}
|
||||
if (utilities::json_config::ReadBoolean("lua", "error", false))
|
||||
{
|
||||
hksi_lual_error_hook.create(0x143757780_g, hksi_lual_error_stub);
|
||||
lual_error_hook.create(0x14375D410_g, lual_error_stub);
|
||||
utilities::hook::jump(0x14423A1D0_g, lui_panic_stub);
|
||||
utilities::hook::jump(0x143955FA0_g, lua_engine_print_error);
|
||||
utilities::hook::jump(0x143956090_g, lua_engine_print_warning);
|
||||
}
|
||||
|
||||
patch_unsafe_lua_functions();
|
||||
}
|
||||
};
|
||||
}
|
||||
REGISTER_COMPONENT(lua::component)
|
@ -6,8 +6,10 @@
|
||||
|
||||
#include "gsc_funcs.hpp"
|
||||
#include "gsc_custom.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "dvars.hpp"
|
||||
#include "hashes.hpp"
|
||||
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utilities/io.hpp>
|
||||
#include <utilities/hook.hpp>
|
||||
|
||||
@ -148,6 +150,9 @@ namespace mods {
|
||||
{
|
||||
xassets::lua_file_header header{};
|
||||
|
||||
std::unordered_set<uint64_t> hooks{};
|
||||
uint64_t noext_name{};
|
||||
std::unordered_set<uint64_t> hooks_post{};
|
||||
std::string data{};
|
||||
|
||||
auto* get_header()
|
||||
@ -172,6 +177,28 @@ namespace mods {
|
||||
return &header;
|
||||
}
|
||||
};
|
||||
struct localize
|
||||
{
|
||||
xassets::localize_entry_header header{};
|
||||
|
||||
std::string str{};
|
||||
|
||||
auto* get_header()
|
||||
{
|
||||
header.string = str.data();
|
||||
|
||||
return &header;
|
||||
}
|
||||
|
||||
};
|
||||
struct cache_entry
|
||||
{
|
||||
game::BO4_AssetRef_t name{};
|
||||
xassets::BGCacheTypes type{};
|
||||
std::unordered_set<game::eModes> hooks_modes{};
|
||||
std::unordered_set<uint64_t> hooks_map{};
|
||||
std::unordered_set<uint64_t> hooks_gametype{};
|
||||
};
|
||||
|
||||
|
||||
class mod_storage
|
||||
@ -183,6 +210,18 @@ namespace mods {
|
||||
std::vector<raw_file> raw_files{};
|
||||
std::vector<lua_file> lua_files{};
|
||||
std::vector<string_table_file> csv_files{};
|
||||
std::vector<localize> localizes{};
|
||||
std::vector<cache_entry> cache_entries{};
|
||||
std::unordered_map<int64_t, int64_t> assets_redirects[xassets::ASSET_TYPE_COUNT] = {};
|
||||
std::vector<xassets::bg_cache_info_def> custom_cache_entries{};
|
||||
|
||||
xassets::bg_cache_info custom_cache
|
||||
{
|
||||
.name
|
||||
{
|
||||
.hash = (int64_t)fnv1a::generate_hash("shield_cache") // 2c4f76fcf5cfbebd
|
||||
}
|
||||
};
|
||||
|
||||
~mod_storage()
|
||||
{
|
||||
@ -199,6 +238,13 @@ namespace mods {
|
||||
gsc_files.clear();
|
||||
lua_files.clear();
|
||||
csv_files.clear();
|
||||
localizes.clear();
|
||||
cache_entries.clear();
|
||||
|
||||
for (auto& redirect : assets_redirects)
|
||||
{
|
||||
redirect.clear();
|
||||
}
|
||||
|
||||
for (char* str : allocated_strings)
|
||||
{
|
||||
@ -217,6 +263,54 @@ namespace mods {
|
||||
return str;
|
||||
}
|
||||
|
||||
void sync_cache_entries()
|
||||
{
|
||||
std::lock_guard lg{ load_mutex };
|
||||
custom_cache.defCount = 0;
|
||||
custom_cache.def = nullptr;
|
||||
custom_cache_entries.clear();
|
||||
|
||||
if (!cache_entries.size())
|
||||
{
|
||||
return; // nothing to sync
|
||||
}
|
||||
|
||||
game::dvar_t* sv_mapname = dvars::find_dvar("sv_mapname");
|
||||
game::dvar_t* g_gametype = dvars::find_dvar("g_gametype");
|
||||
|
||||
if (!sv_mapname || !g_gametype)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, "Can't find bgcache dvars");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string mapname = dvars::get_value_string(sv_mapname, &sv_mapname->value->current);
|
||||
std::string gametype = dvars::get_value_string(g_gametype, &g_gametype->value->current);
|
||||
game::eModes mode = game::Com_SessionMode_GetMode();
|
||||
|
||||
uint64_t mapname_hash = fnv1a::generate_hash(mapname.data());
|
||||
uint64_t gametype_hash = fnv1a::generate_hash(gametype.data());
|
||||
|
||||
int count = 0;
|
||||
for (auto& entry : cache_entries)
|
||||
{
|
||||
if (
|
||||
entry.hooks_modes.find(mode) != entry.hooks_modes.end()
|
||||
|| entry.hooks_map.find(mapname_hash) != entry.hooks_map.end()
|
||||
|| entry.hooks_gametype.find(gametype_hash) != entry.hooks_gametype.end()
|
||||
)
|
||||
{
|
||||
auto& ref = custom_cache_entries.emplace_back();
|
||||
ref.type = entry.type;
|
||||
ref.name.hash = entry.name.hash;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
custom_cache.def = custom_cache_entries.data();
|
||||
custom_cache.defCount = (int)custom_cache_entries.size();
|
||||
logger::write(logger::LOG_TYPE_DEBUG, "sync %d custom bgcache entries", count);
|
||||
}
|
||||
|
||||
void* get_xasset(xassets::XAssetType type, uint64_t name)
|
||||
{
|
||||
std::lock_guard lg{ load_mutex };
|
||||
@ -254,6 +348,14 @@ namespace mods {
|
||||
|
||||
return it->get_header();
|
||||
}
|
||||
case xassets::ASSET_TYPE_LOCALIZE_ENTRY:
|
||||
{
|
||||
auto it = std::find_if(localizes.begin(), localizes.end(), [name](const localize& file) { return file.header.name == name; });
|
||||
|
||||
if (it == localizes.end()) return nullptr;
|
||||
|
||||
return it->get_header();
|
||||
}
|
||||
default:
|
||||
return nullptr; // unknown resource type
|
||||
}
|
||||
@ -391,6 +493,27 @@ namespace mods {
|
||||
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("localizeentry", type_val))
|
||||
{
|
||||
auto name_mb = member.FindMember("name");
|
||||
auto value_mb = member.FindMember("value");
|
||||
|
||||
if (
|
||||
name_mb == member.MemberEnd() || value_mb == member.MemberEnd()
|
||||
|| !name_mb->value.IsString() || !value_mb->value.IsString()
|
||||
)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad localized entry def, missing/bad name or value", mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
localize tmp{};
|
||||
tmp.str = value_mb->value.GetString();
|
||||
tmp.header.name = fnv1a::generate_hash_pattern(name_mb->value.GetString());
|
||||
|
||||
logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded localized entry {:x}", mod_name, tmp.header.name));
|
||||
localizes.emplace_back(tmp);
|
||||
}
|
||||
else if (!_strcmpi("luafile", type_val))
|
||||
{
|
||||
auto name_mb = member.FindMember("name");
|
||||
@ -408,7 +531,55 @@ namespace mods {
|
||||
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());
|
||||
// it injects the name without the .lua and load the name with the .lua, good luck to replace with an unknown hash!
|
||||
tmp.noext_name = fnv1a::generate_hash_pattern(name_mb->value.GetString());
|
||||
tmp.header.name = fnv1a::generate_hash(".lua", tmp.noext_name);
|
||||
|
||||
auto hooks = member.FindMember("hooks_pre");
|
||||
|
||||
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 luafile pre hook def, not an array for {}", mod_name, lua_file_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 luafile pre hook def, not a string for {}", mod_name, lua_file_path.string()));
|
||||
return false;
|
||||
}
|
||||
|
||||
tmp.hooks.insert(fnv1a::generate_hash_pattern(hook.GetString()));
|
||||
}
|
||||
}
|
||||
|
||||
auto hooks_post = member.FindMember("hooks_post");
|
||||
|
||||
if (hooks_post != member.MemberEnd())
|
||||
{
|
||||
if (!hooks_post->value.IsArray())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad luafile post hook def, not an array for {}", mod_name, lua_file_path.string()));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& hook : hooks_post->value.GetArray())
|
||||
{
|
||||
if (!hook.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad luafile post hook def, not a string for {}", mod_name, lua_file_path.string()));
|
||||
return false;
|
||||
}
|
||||
|
||||
tmp.hooks_post.insert(fnv1a::generate_hash_pattern(hook.GetString()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!utilities::io::read_file(lua_file_path.string(), &tmp.data))
|
||||
{
|
||||
@ -416,7 +587,7 @@ namespace mods {
|
||||
return false;
|
||||
}
|
||||
|
||||
logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded lua file {} -> {:x}", mod_name, lua_file_path.string(), tmp.header.name));
|
||||
logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded lua file {} -> x64:{:x}.lua ({:x})", mod_name, lua_file_path.string(), tmp.noext_name, tmp.header.name));
|
||||
lua_files.emplace_back(tmp);
|
||||
}
|
||||
else if (!_strcmpi("stringtable", type_val))
|
||||
@ -448,7 +619,29 @@ namespace mods {
|
||||
|
||||
std::stringstream stream{ tmp.data };
|
||||
|
||||
doc.Load(stream, rapidcsv::LabelParams(-1, -1));
|
||||
auto separator_mb = member.FindMember("separator");
|
||||
|
||||
char sep = ',';
|
||||
|
||||
if (separator_mb != member.MemberEnd())
|
||||
{
|
||||
if (!separator_mb->value.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("bad separator type for stringtable file {} for mod {}", stringtable_file_path.string(), mod_name));
|
||||
return false;
|
||||
}
|
||||
const char* sepval = separator_mb->value.GetString();
|
||||
|
||||
if (!sepval[0] || sepval[1])
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("bad separator for stringtable file {} for mod {}, a separator should contain only one character", stringtable_file_path.string(), mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
sep = *sepval;
|
||||
}
|
||||
|
||||
doc.Load(stream, rapidcsv::LabelParams(-1, -1), rapidcsv::SeparatorParams(sep));
|
||||
|
||||
size_t rows_count_tmp = doc.GetRowCount();
|
||||
tmp.header.rows_count = rows_count_tmp != 0 ? (int32_t)(rows_count_tmp - 1) : 0;
|
||||
@ -556,9 +749,33 @@ namespace mods {
|
||||
}
|
||||
}
|
||||
|
||||
logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded stringtable file {} -> {:x}", mod_name, stringtable_file_path.string(), tmp.header.name));
|
||||
logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded stringtable file {} -> {:x} ({}x{})", mod_name, stringtable_file_path.string(), tmp.header.name, tmp.header.columns_count, tmp.header.rows_count));
|
||||
csv_files.emplace_back(tmp);
|
||||
}
|
||||
else if (!_strcmpi("hashes", type_val))
|
||||
{
|
||||
auto path_mb = member.FindMember("path");
|
||||
|
||||
if (path_mb == member.MemberEnd() || !path_mb->value.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a hashes storage without a path", mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto format_mb = member.FindMember("format");
|
||||
hashes::hashes_file_format format;
|
||||
|
||||
if (format_mb == member.MemberEnd() || !format_mb->value.IsString() || (format = hashes::get_format_idx(format_mb->value.GetString())) == hashes::HFF_COUNT)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a hashes storage without a valid format", mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::path path_cfg = path_mb->value.GetString();
|
||||
auto path = path_cfg.is_absolute() ? path_cfg : (mod_path / path_cfg);
|
||||
|
||||
return hashes::load_file(path, format);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is load data member with an unknown type '{}'", mod_name, type_val));
|
||||
@ -568,6 +785,184 @@ namespace mods {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_cache_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 cache member without a valid type", mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto name = member.FindMember("name");
|
||||
|
||||
if (name == member.MemberEnd() || !name->value.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a cache member without a valid name", mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* name_val = name->value.GetString();
|
||||
const char* type_val = type->value.GetString();
|
||||
|
||||
xassets::BGCacheTypes bgtype = xassets::BG_Cache_GetTypeIndex(type_val);
|
||||
|
||||
if (!bgtype)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a cache member with a bad type: {}", mod_name, type_val));
|
||||
return false;
|
||||
}
|
||||
|
||||
cache_entry tmp{};
|
||||
|
||||
tmp.name.hash = fnv1a::generate_hash_pattern(name_val);
|
||||
tmp.type = bgtype;
|
||||
|
||||
auto hook_map = member.FindMember("map");
|
||||
auto hook_mode = member.FindMember("mode");
|
||||
auto hook_gametype = member.FindMember("gametype");
|
||||
|
||||
if (hook_map != member.MemberEnd())
|
||||
{
|
||||
if (hook_map->value.IsArray())
|
||||
{
|
||||
auto data_array = hook_map->value.GetArray();
|
||||
|
||||
for (rapidjson::Value& hookmember : data_array)
|
||||
{
|
||||
if (!hookmember.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is containing a cache member with a bad map hook", mod_name));
|
||||
continue;
|
||||
}
|
||||
tmp.hooks_map.insert(fnv1a::generate_hash(hookmember.GetString()));
|
||||
}
|
||||
}
|
||||
else if (hook_map->value.IsString())
|
||||
{
|
||||
tmp.hooks_map.insert(fnv1a::generate_hash(hook_map->value.GetString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is containing a cache member with a bad map hook", mod_name));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (hook_mode != member.MemberEnd())
|
||||
{
|
||||
if (hook_mode->value.IsArray())
|
||||
{
|
||||
auto data_array = hook_mode->value.GetArray();
|
||||
|
||||
for (rapidjson::Value& hookmember : data_array)
|
||||
{
|
||||
if (!hookmember.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is containing a cache member with a bad mode hook", mod_name));
|
||||
continue;
|
||||
}
|
||||
game::eModes loaded = game::Com_SessionMode_GetModeForAbbreviation(hookmember.GetString());
|
||||
if (loaded == game::eModes::MODE_COUNT)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is containing a cache member with a bad mode hook", mod_name));
|
||||
continue;
|
||||
}
|
||||
tmp.hooks_modes.insert(loaded);
|
||||
}
|
||||
}
|
||||
else if (hook_mode->value.IsString())
|
||||
{
|
||||
game::eModes loaded = game::Com_SessionMode_GetModeForAbbreviation(hook_mode->value.GetString());
|
||||
if (loaded == game::eModes::MODE_COUNT)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is containing a cache member with a bad mode hook", mod_name));
|
||||
return false;
|
||||
}
|
||||
tmp.hooks_modes.insert(loaded);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is containing a cache member with a bad mode hook", mod_name));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (hook_gametype != member.MemberEnd())
|
||||
{
|
||||
if (hook_gametype->value.IsArray())
|
||||
{
|
||||
auto data_array = hook_gametype->value.GetArray();
|
||||
|
||||
for (rapidjson::Value& hookmember : data_array)
|
||||
{
|
||||
if (!hookmember.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is containing a cache member with a bad gametype hook", mod_name));
|
||||
continue;
|
||||
}
|
||||
tmp.hooks_gametype.insert(fnv1a::generate_hash(hookmember.GetString()));
|
||||
}
|
||||
}
|
||||
else if (hook_gametype->value.IsString())
|
||||
{
|
||||
tmp.hooks_gametype.insert(fnv1a::generate_hash(hook_gametype->value.GetString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("mod {} is containing a cache member with a bad gametype hook", mod_name));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cache_entries.push_back(tmp);
|
||||
|
||||
return true;
|
||||
}
|
||||
bool read_redirect_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 redirect member without a valid type", mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto origin = member.FindMember("origin");
|
||||
|
||||
if (origin == member.MemberEnd() || !origin->value.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a redirect member without a valid origin", mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto target = member.FindMember("target");
|
||||
|
||||
if (target == member.MemberEnd() || !target->value.IsString())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a redirect member without a valid target", mod_name));
|
||||
return false;
|
||||
}
|
||||
|
||||
xassets::XAssetType assettype = xassets::DB_GetXAssetTypeIndex(type->value.GetString());
|
||||
|
||||
if (assettype == xassets::ASSET_TYPE_COUNT)
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a redirect member without a valid type: {}", mod_name, type->value.GetString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t from = fnv1a::generate_hash_pattern(origin->value.GetString());
|
||||
int64_t to = fnv1a::generate_hash_pattern(target->value.GetString());
|
||||
assets_redirects[assettype][from] = to;
|
||||
|
||||
logger::write(logger::LOG_TYPE_DEBUG, std::format("mod {}: loaded redirect {:x} -> {:x} ({})", mod_name, from, to, xassets::DB_GetXAssetTypeName(assettype)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool load_mods()
|
||||
{
|
||||
std::lock_guard lg{ load_mutex };
|
||||
@ -634,7 +1029,52 @@ namespace mods {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!read_data_entry(member, mod_name, mod_path))
|
||||
auto ignore_error_mb = member.FindMember("ignore_error");
|
||||
bool ignore_error = ignore_error_mb != member.MemberEnd() && ignore_error_mb->value.IsBool() && ignore_error_mb->value.GetBool();
|
||||
|
||||
if (!read_data_entry(member, mod_name, mod_path) && !ignore_error)
|
||||
{
|
||||
mod_errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto cache_member = info.FindMember("cache");
|
||||
|
||||
if (cache_member != info.MemberEnd() && cache_member->value.IsArray())
|
||||
{
|
||||
auto data_array = cache_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 cache member", mod_name));
|
||||
mod_errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!read_cache_entry(member, mod_name, mod_path))
|
||||
{
|
||||
mod_errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto redirect_member = info.FindMember("redirect");
|
||||
|
||||
if (redirect_member != info.MemberEnd() && redirect_member->value.IsArray())
|
||||
{
|
||||
auto redirect_array = redirect_member->value.GetArray();
|
||||
|
||||
for (rapidjson::Value& member : redirect_array)
|
||||
{
|
||||
if (!member.IsObject())
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_WARN, std::format("mod {} is containing a bad redirect member", mod_name));
|
||||
mod_errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!read_redirect_entry(member, mod_name, mod_path))
|
||||
{
|
||||
mod_errors++;
|
||||
}
|
||||
@ -675,10 +1115,25 @@ namespace mods {
|
||||
}
|
||||
|
||||
utilities::hook::detour db_find_xasset_header_hook;
|
||||
utilities::hook::detour db_does_xasset_exist_hook;
|
||||
utilities::hook::detour scr_gsc_obj_link_hook;
|
||||
utilities::hook::detour hksl_loadfile_hook;
|
||||
|
||||
void* db_find_xasset_header_stub(xassets::XAssetType type, game::BO4_AssetRef_t* name, bool errorIfMissing, int waitTime)
|
||||
{
|
||||
auto& redirect = storage.assets_redirects[type];
|
||||
|
||||
auto replaced = redirect.find(name->hash & 0x7FFFFFFFFFFFFFFF);
|
||||
|
||||
game::BO4_AssetRef_t redirected_name;
|
||||
if (replaced != redirect.end())
|
||||
{
|
||||
// replace xasset
|
||||
redirected_name.hash = replaced->second;
|
||||
redirected_name.null = 0;
|
||||
name = &redirected_name;
|
||||
}
|
||||
|
||||
void* header = storage.get_xasset(type, name->hash);
|
||||
|
||||
if (header)
|
||||
@ -689,6 +1144,31 @@ namespace mods {
|
||||
return db_find_xasset_header_hook.invoke<void*>(type, name, errorIfMissing, waitTime);
|
||||
}
|
||||
|
||||
bool db_does_xasset_exist_stub(xassets::XAssetType type, game::BO4_AssetRef_t* name)
|
||||
{
|
||||
auto& redirect = storage.assets_redirects[type];
|
||||
|
||||
auto replaced = redirect.find(name->hash & 0x7FFFFFFFFFFFFFFF);
|
||||
|
||||
game::BO4_AssetRef_t redirected_name;
|
||||
if (replaced != redirect.end())
|
||||
{
|
||||
// replace xasset
|
||||
redirected_name.hash = replaced->second;
|
||||
redirected_name.null = 0;
|
||||
name = &redirected_name;
|
||||
}
|
||||
|
||||
void* header = storage.get_xasset(type, name->hash);
|
||||
|
||||
if (header)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return db_does_xasset_exist_hook.invoke<bool>(type, name);
|
||||
}
|
||||
|
||||
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
|
||||
@ -719,24 +1199,77 @@ namespace mods {
|
||||
return scr_gsc_obj_link_hook.invoke<int>(inst, prime_obj, runScript);
|
||||
}
|
||||
|
||||
int hksl_loadfile_stub(game::lua_state* state, const char* filename)
|
||||
{
|
||||
|
||||
uint64_t hash{};
|
||||
if (!storage.lua_files.empty())
|
||||
{
|
||||
hash = fnv1a::generate_hash_pattern(filename);
|
||||
}
|
||||
|
||||
for (auto& lua : storage.lua_files)
|
||||
{
|
||||
// we need to use the hash because filename is x64:HASH or unhashed
|
||||
|
||||
if (lua.hooks.find(hash) != lua.hooks.end())
|
||||
{
|
||||
std::string name = std::format("x64:{:x}.lua", lua.noext_name);
|
||||
if (!game::Lua_CoD_LoadLuaFile(state, name.c_str()))
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("error when loading hook lua {} (pre)", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int load = hksl_loadfile_hook.invoke<int>(state, filename);
|
||||
|
||||
for (auto& lua : storage.lua_files)
|
||||
{
|
||||
// we need to use the hash because filename is x64:HASH or unhashed
|
||||
|
||||
if (lua.hooks_post.find(hash) != lua.hooks_post.end())
|
||||
{
|
||||
std::string name = std::format("x64:{:x}.lua", lua.noext_name);
|
||||
if (!game::Lua_CoD_LoadLuaFile(state, name.c_str()))
|
||||
{
|
||||
logger::write(logger::LOG_TYPE_ERROR, std::format("error when loading hook lua {} (post)", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return load;
|
||||
}
|
||||
|
||||
utilities::hook::detour bg_cache_sync_hook;
|
||||
|
||||
void bg_cache_sync_stub()
|
||||
{
|
||||
storage.sync_cache_entries();
|
||||
|
||||
xassets::Demo_AddBGCacheAndRegister(&storage.custom_cache, 0x16000);
|
||||
|
||||
// sync default
|
||||
bg_cache_sync_hook.invoke<void>();
|
||||
}
|
||||
|
||||
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);
|
||||
storage.load_mods();
|
||||
|
||||
// custom assets loading
|
||||
db_find_xasset_header_hook.create(xassets::DB_FindXAssetHeader.get(), db_find_xasset_header_stub);
|
||||
db_does_xasset_exist_hook.create(0x142EB6C90_g, db_does_xasset_exist_stub);
|
||||
scr_gsc_obj_link_hook.create(0x142748F10_g, scr_gsc_obj_link_stub);
|
||||
hksl_loadfile_hook.create(0x14375D6A0_g, hksl_loadfile_stub);
|
||||
bg_cache_sync_hook.create(0x1405CE0B0_g, bg_cache_sync_stub);
|
||||
|
||||
// register load mods command
|
||||
Cmd_AddCommand("reload_mods", load_mods_cmd);
|
||||
}
|
||||
|
||||
void pre_start() override
|
||||
{
|
||||
storage.load_mods();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,17 @@ namespace game
|
||||
typedef const char* (__fastcall* Com_GetBuildVersion_t)();
|
||||
Com_GetBuildVersion_t Com_GetBuildVersion = (Com_GetBuildVersion_t)0x142892F40_g;
|
||||
|
||||
|
||||
scoped_critical_section::scoped_critical_section(int32_t s, scoped_critical_section_type type) : _s(0), _hasOwnership(false), _isScopedRelease(false), _next(nullptr)
|
||||
{
|
||||
game::ScopedCriticalSectionConstructor(this, s, type);
|
||||
}
|
||||
|
||||
scoped_critical_section::~scoped_critical_section()
|
||||
{
|
||||
game::ScopedCriticalSectionDestructor(this);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
void verify_game_version()
|
||||
|
@ -81,7 +81,7 @@ namespace game
|
||||
int32_t include_offset;
|
||||
uint16_t string_count;
|
||||
uint16_t exports_count;
|
||||
int32_t ukn20;
|
||||
int32_t start_data;
|
||||
int32_t string_offset;
|
||||
int16_t imports_count;
|
||||
uint16_t fixup_count;
|
||||
@ -93,12 +93,12 @@ namespace game
|
||||
int32_t fixup_offset;
|
||||
int32_t globalvar_offset;
|
||||
int32_t script_size;
|
||||
int32_t ukn4c_offset;
|
||||
int32_t requires_implements_offset;
|
||||
int32_t ukn50;
|
||||
int32_t ukn54;
|
||||
int32_t data_length;
|
||||
uint16_t include_count;
|
||||
byte ukn5a;
|
||||
byte ukn4c_count;
|
||||
byte requires_implements_count;
|
||||
|
||||
inline GSC_EXPORT_ITEM* get_exports()
|
||||
{
|
||||
@ -183,7 +183,8 @@ namespace game
|
||||
TYPE_REMOVED_THREAD = 0x19,
|
||||
TYPE_FREE = 0x1a,
|
||||
TYPE_THREAD_LIST = 0x1b,
|
||||
TYPE_ENT_LIST = 0x1c
|
||||
TYPE_ENT_LIST = 0x1c,
|
||||
TYPE_COUNT
|
||||
};
|
||||
|
||||
|
||||
@ -222,6 +223,39 @@ namespace game
|
||||
ScrVarType_t type;
|
||||
};
|
||||
|
||||
struct ScrVar_t_Info
|
||||
{
|
||||
uint32_t nameType : 3;
|
||||
uint32_t flags : 5;
|
||||
uint32_t refCount : 24;
|
||||
};
|
||||
|
||||
struct ScrVar_t
|
||||
{
|
||||
ScrVarNameIndex_t nameIndex;
|
||||
ScrVar_t_Info _anon_0;
|
||||
ScrVarIndex_t nextSibling;
|
||||
ScrVarIndex_t prevSibling;
|
||||
ScrVarIndex_t parentId;
|
||||
ScrVarIndex_t nameSearchHashList;
|
||||
uint32_t pad0;
|
||||
};
|
||||
|
||||
union ScrVarObjectInfo1_t
|
||||
{
|
||||
uint64_t object_o;
|
||||
unsigned int size;
|
||||
ScrVarIndex_t nextEntId;
|
||||
ScrVarIndex_t self;
|
||||
ScrVarIndex_t free;
|
||||
};
|
||||
|
||||
union ScrVarObjectInfo2_t
|
||||
{
|
||||
uint32_t object_w;
|
||||
ScrVarIndex_t stackId;
|
||||
};
|
||||
|
||||
struct function_stack_t
|
||||
{
|
||||
byte* pos;
|
||||
@ -232,6 +266,11 @@ namespace game
|
||||
uint16_t profileInfoCount;
|
||||
};
|
||||
|
||||
struct function_frame_t
|
||||
{
|
||||
function_stack_t fs;
|
||||
};
|
||||
|
||||
struct ScrVmContext_t
|
||||
{
|
||||
ScrVarIndex_t fieldValueId;
|
||||
@ -242,6 +281,48 @@ namespace game
|
||||
|
||||
typedef void (*VM_OP_FUNC)(scriptInstance_t, function_stack_t*, ScrVmContext_t*, bool*);
|
||||
|
||||
|
||||
struct BO4_scrVarGlob
|
||||
{
|
||||
ScrVarIndex_t* scriptNameSearchHashList;
|
||||
ScrVar_t* scriptVariables;
|
||||
ScrVarObjectInfo1_t* scriptVariablesObjectInfo1;
|
||||
ScrVarObjectInfo2_t* scriptVariablesObjectInfo2;
|
||||
ScrVarValue_t* scriptValues;
|
||||
};
|
||||
|
||||
struct BO4_scrVmPub
|
||||
{
|
||||
void* unk0;
|
||||
void* unk8;
|
||||
void* executionQueueHeap; // HunkUser
|
||||
void* timeByValueQueue; // VmExecutionQueueData_t
|
||||
void* timeByThreadQueue[1024]; // VmExecutionQueue_t
|
||||
void* frameByValueQueue; // VmExecutionQueueData_t
|
||||
void* frameByThreadQueue[1024]; // VmExecutionQueue_t
|
||||
void* timeoutByValueList; // VmExecutionQueueData_t
|
||||
void* timeoutByThreadList[1024]; // VmExecutionQueue_t
|
||||
void* notifyByObjectQueue[1024]; // VmExecutionNotifyQueue_t
|
||||
void* notifyByThreadQueue[1024]; // VmExecutionNotifyQueue_t
|
||||
void* endonByObjectList[1024]; // VmExecutionNotifyQueue_t
|
||||
void* endonByThreadList[1024]; // VmExecutionNotifyQueue_t
|
||||
ScrVarIndex_t* localVars;
|
||||
ScrVarValue_t* maxstack;
|
||||
function_frame_t* function_frame;
|
||||
ScrVarValue_t* top;
|
||||
function_frame_t function_frame_start[64];
|
||||
ScrVarValue_t stack[2048];
|
||||
uint32_t time;
|
||||
uint32_t frame;
|
||||
int function_count;
|
||||
int callNesting;
|
||||
unsigned int inparamcount;
|
||||
bool showError;
|
||||
bool systemInitialized;
|
||||
bool vmInitialized;
|
||||
bool isShutdown;
|
||||
};
|
||||
|
||||
struct objFileInfo_t
|
||||
{
|
||||
GSC_OBJ* activeVersion;
|
||||
@ -655,6 +736,135 @@ namespace game
|
||||
MODE_FIRST = 0x0,
|
||||
};
|
||||
|
||||
enum consoleLabel_e : int32_t
|
||||
{
|
||||
CON_LABEL_TEMP = 0x0,
|
||||
CON_LABEL_GFX = 0x2,
|
||||
CON_LABEL_TASKMGR2 = 0x3,
|
||||
CON_LABEL_LIVE = 0x4,
|
||||
CON_LABEL_LIVE_XBOX = 0x5,
|
||||
CON_LABEL_LIVE_PS4 = 0x6,
|
||||
CON_LABEL_MATCHMAKING = 0x7,
|
||||
CON_LABEL_DEMONWARE = 0x8,
|
||||
CON_LABEL_LEADERBOARDS = 0x9,
|
||||
CON_LABEL_LOBBY = 0x0A,
|
||||
CON_LABEL_LOBBYHOST = 0x0B,
|
||||
CON_LABEL_LOBBYCLIENT = 0x0C,
|
||||
CON_LABEL_LOBBYVM = 0x0D,
|
||||
CON_LABEL_MIGRATION = 0x0E,
|
||||
CON_LABEL_IG_MIGRATION_Host = 0x0F,
|
||||
CON_LABEL_IG_MIGRATION_Client = 0x10,
|
||||
CON_LABEL_SCRIPTER = 0x11,
|
||||
CON_LABEL_VM = 0x12,
|
||||
CON_LABEL_DVAR = 0x13,
|
||||
CON_LABEL_TOOL = 0x14,
|
||||
CON_LABEL_ANIM = 0x15,
|
||||
CON_LABEL_NETCHAN = 0x16,
|
||||
CON_LABEL_BGCACHE = 0x17,
|
||||
CON_LABEL_PM = 0x18,
|
||||
CON_LABEL_MAPSWITCH = 0x19,
|
||||
CON_LABEL_AI = 0x1A,
|
||||
CON_LABEL_GADGET = 0x1B,
|
||||
CON_LABEL_SOUND = 0x1C,
|
||||
CON_LABEL_SNAPSHOT = 0x1D,
|
||||
CON_LABEL_PLAYGO = 0x1E,
|
||||
CON_LABEL_LUI = 0x1F,
|
||||
CON_LABEL_LUA = 0x20,
|
||||
CON_LABEL_VOIP = 0x21,
|
||||
CON_LABEL_DEMO = 0x22,
|
||||
CON_LABEL_DB = 0x23,
|
||||
CON_LABEL_HTTP = 0x24,
|
||||
CON_LABEL_DCACHE = 0x25,
|
||||
CON_LABEL_MEM = 0x26,
|
||||
CON_LABEL_CINEMATIC = 0x27,
|
||||
CON_LABEL_DDL = 0x28,
|
||||
CON_LABEL_STORAGE = 0x29,
|
||||
CON_LABEL_STEAM = 0x2A,
|
||||
CON_LABEL_CHKPTSAVE = 0x2B,
|
||||
CON_LABEL_DLOG = 0x2C,
|
||||
CON_LABEL_FILESHARE = 0x2D,
|
||||
CON_LABEL_LPC = 0x2E,
|
||||
CON_LABEL_MARKETING = 0x2F,
|
||||
CON_LABEL_STORE = 0x30,
|
||||
CON_LABEL_TESTING = 0x31,
|
||||
CON_LABEL_LOOT = 0x32,
|
||||
CON_LABEL_MATCHRECORDER = 0x33,
|
||||
CON_LABEL_EXCHANGE = 0x34,
|
||||
CON_LABEL_SCRIPTERROR = 0x35,
|
||||
CON_LABEL_PLAYOFTHEMATCH = 0x36,
|
||||
CON_LABEL_FILESYS = 0x37,
|
||||
CON_LABEL_JSON = 0x38,
|
||||
CON_LABEL_CUSTOMGAMES = 0x39,
|
||||
CON_LABEL_GAMEPLAY = 0x3A,
|
||||
CON_LABEL_STREAM = 0x3B,
|
||||
CON_LABEL_XPAK = 0x3C,
|
||||
CON_LABEL_AE = 0x3D,
|
||||
CON_LABEL_STRINGTABLE = 0x3E,
|
||||
CON_LABEL_COUNT = 0x3F
|
||||
};
|
||||
|
||||
enum scoped_critical_section_type : int32_t
|
||||
{
|
||||
SCOPED_CRITSECT_NORMAL = 0x0,
|
||||
SCOPED_CRITSECT_DISABLED = 0x1,
|
||||
SCOPED_CRITSECT_RELEASE = 0x2,
|
||||
SCOPED_CRITSECT_TRY = 0x3,
|
||||
};
|
||||
|
||||
class scoped_critical_section
|
||||
{
|
||||
int32_t _s;
|
||||
bool _hasOwnership;
|
||||
bool _isScopedRelease;
|
||||
scoped_critical_section* _next;
|
||||
public:
|
||||
scoped_critical_section(int32_t s, scoped_critical_section_type type);
|
||||
~scoped_critical_section();
|
||||
};
|
||||
|
||||
struct hks_global {};
|
||||
struct hks_callstack
|
||||
{
|
||||
void* m_records; // hks::CallStack::ActivationRecord*
|
||||
void* m_lastrecord; // hks::CallStack::ActivationRecord*
|
||||
void* m_current; // hks::CallStack::ActivationRecord*
|
||||
const void* m_current_lua_pc; // const hksInstruction*
|
||||
const void* m_hook_return_addr; // const hksInstruction*
|
||||
int32_t m_hook_level;
|
||||
};
|
||||
struct lua_state;
|
||||
struct hks_object
|
||||
{
|
||||
uint32_t t;
|
||||
union {
|
||||
void* ptr;
|
||||
float number;
|
||||
int32_t boolean;
|
||||
uint32_t native;
|
||||
lua_state* thread;
|
||||
} v;
|
||||
};
|
||||
struct hks_api_stack
|
||||
{
|
||||
hks_object* top;
|
||||
hks_object* base;
|
||||
hks_object* alloc_top;
|
||||
hks_object* bottom;
|
||||
};
|
||||
|
||||
struct lua_state
|
||||
{
|
||||
// hks::GenericChunkHeader
|
||||
size_t m_flags;
|
||||
// hks::ChunkHeader
|
||||
void* m_next;
|
||||
|
||||
hks_global* m_global;
|
||||
hks_callstack m_callStack;
|
||||
hks_api_stack m_apistack;
|
||||
|
||||
// ...
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// SYMBOLS //
|
||||
@ -691,6 +901,10 @@ namespace game
|
||||
// Main Functions
|
||||
WEAK symbol<void(const char* file, int line, int code, const char* fmt, ...)> Com_Error_{ 0x14288B410_g };
|
||||
|
||||
// mutex
|
||||
WEAK symbol<void(scoped_critical_section* sec, int32_t s, scoped_critical_section_type type)> ScopedCriticalSectionConstructor{ 0x14289E3C0_g };
|
||||
WEAK symbol<void(scoped_critical_section* sec)> ScopedCriticalSectionDestructor{ 0x14289E440_g };
|
||||
|
||||
// CMD
|
||||
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{ 0x143CDE880_g };
|
||||
|
||||
@ -716,6 +930,8 @@ namespace game
|
||||
WEAK symbol<bool()> Com_IsInGame{ 0x14288FDB0_g };
|
||||
WEAK symbol<bool()> Com_IsRunningUILevel{ 0x14288FDF0_g };
|
||||
WEAK symbol<eModes()> Com_SessionMode_GetMode{ 0x14289EFF0_g };
|
||||
WEAK symbol<eModes(const char* str)> Com_SessionMode_GetModeForAbbreviation{ 0x14289F000_g };
|
||||
WEAK symbol<const char*(eModes mode)> Com_SessionMode_GetAbbreviationForMode{0x14289EC70_g};
|
||||
|
||||
WEAK symbol<int> keyCatchers{ 0x148A53F84_g };
|
||||
WEAK symbol<PlayerKeyState> playerKeys{ 0x148A3EF80_g };
|
||||
@ -737,11 +953,20 @@ namespace game
|
||||
WEAK symbol<int64_t(scriptInstance_t inst, unsigned int index)> ScrVm_GetInt{ 0x142773B50_g };
|
||||
WEAK symbol<const char*(scriptInstance_t inst, unsigned int index)> ScrVm_GetString{ 0x142774840_g };
|
||||
WEAK symbol<void(scriptInstance_t inst, unsigned int index, vec3_t* vector)> ScrVm_GetVector{ 0x142774E40_g };
|
||||
WEAK symbol<int32_t(scriptInstance_t inst, unsigned int index)> ScrVm_GetConstString{ 0x142772E10_g };
|
||||
WEAK symbol<ScrVarIndex_t(scriptInstance_t inst, unsigned int index)> ScrVm_GetConstString{ 0x142772E10_g };
|
||||
WEAK symbol<uint32_t(scriptInstance_t inst)> ScrVm_GetNumParam{ 0x142774440_g };
|
||||
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<uint32_t(scriptInstance_t inst)> ScrVm_AddStruct{ 0x14276EF00_g };
|
||||
WEAK symbol<void(scriptInstance_t inst, uint32_t structId, uint32_t name)> ScrVm_SetStructField{ 0x142778450_g };
|
||||
WEAK symbol<void(scriptInstance_t inst)> ScrVm_AddToArray{ 0x14276F1C0_g };
|
||||
WEAK symbol<void(scriptInstance_t inst, BO4_AssetRef_t* name)> ScrVm_AddToArrayStringIndexed{ 0x14276F230_g };
|
||||
WEAK symbol<void(scriptInstance_t inst, vec3_t* vec)> ScrVm_AddVector{ 0x14276F490_g };
|
||||
WEAK symbol<void(scriptInstance_t inst)> ScrVar_PushArray{ 0x142775CF0_g };
|
||||
WEAK symbol<const char* (ScrVarIndex_t index)> ScrStr_ConvertToString{ 0x142759030_g };
|
||||
WEAK symbol<ScrVarIndex_t(scriptInstance_t inst, ScrVarIndex_t parentId, ScrVarNameIndex_t index)> ScrVar_NewVariableByIndex{ 0x142760440_g };
|
||||
WEAK symbol<void(scriptInstance_t inst, ScrVarIndex_t id, ScrVarValue_t* value)> ScrVar_SetValue{ 0x1427616B0_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 };
|
||||
@ -749,13 +974,26 @@ namespace game
|
||||
|
||||
WEAK symbol<void(uint64_t code, scriptInstance_t inst, char* unused, bool terminal)> ScrVm_Error{ 0x142770330_g };
|
||||
WEAK symbol<BO4_scrVarPub> scrVarPub{ 0x148307880_g };
|
||||
WEAK symbol<BO4_scrVarGlob> scrVarGlob{ 0x148307830_g };
|
||||
WEAK symbol<BO4_scrVmPub> scrVmPub{ 0x148307AA0_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 };
|
||||
|
||||
|
||||
// lua functions
|
||||
WEAK symbol<bool(lua_state* luaVM, const char* file)> Lua_CoD_LoadLuaFile{ 0x143962DF0_g };
|
||||
WEAK symbol<void(int code, const char* error, lua_state* s)> Lua_CoD_LuaStateManager_Error{ 0x14398A860_g };
|
||||
WEAK symbol<const char*(lua_state* luaVM, hks_object* obj, size_t* len)> hks_obj_tolstring{ 0x143755730_g };
|
||||
WEAK symbol<float(lua_state* luaVM, const hks_object* obj)> hks_obj_tonumber{ 0x143755A90_g };
|
||||
|
||||
// console labels
|
||||
WEAK symbol<const char*> builtinLabels{ 0x144F11530_g };
|
||||
// gsc types
|
||||
WEAK symbol<const char*> var_typename{ 0x144EED240_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 }; \
|
||||
@ -769,4 +1007,12 @@ namespace game
|
||||
|
||||
#define Com_Error(code, fmt, ...) \
|
||||
Com_Error_(__FILE__, __LINE__, code, fmt, ##__VA_ARGS__)
|
||||
|
||||
class scoped_critical_section_guard_lock
|
||||
{
|
||||
|
||||
|
||||
|
||||
|
||||
};
|
||||
}
|
185
source/proxy-dll/definitions/game_runtime_errors.cpp
Normal file
185
source/proxy-dll/definitions/game_runtime_errors.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
#include <std_include.hpp>
|
||||
#include "definitions/game.hpp"
|
||||
#include "definitions/xassets.hpp"
|
||||
#include "game_runtime_errors.hpp"
|
||||
|
||||
|
||||
namespace game::runtime_errors
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<uint64_t, const char*> errors =
|
||||
{
|
||||
{ 1045192683, "Scr_RandomFloatRange's second parameter must be greater than the first." },
|
||||
{ 1047729873, "exitlevel already called" },
|
||||
{ 104978404, "cannot cast type to canon" },
|
||||
{ 1072585573, "Raw file is not a file of the right type" },
|
||||
{ 1088278929, "Raw file is not a file of the right type" },
|
||||
{ 1099145600, "Can't find asset" },
|
||||
{ 1132507782, "bad opcode" },
|
||||
{ 1137123674, "GScr_LUINotifyEvent: Expected Istrings, hash or integers only" },
|
||||
{ 1252503459, "caller is not an entity" },
|
||||
{ 1273214009, "CScr_PlayFX: invalid effect" },
|
||||
{ 1333649786, "IsMature can only be called on a player." },
|
||||
{ 1364331101, "Object must be an array" },
|
||||
{ 1377489376, "Gesture key can't have the higher bit set" },
|
||||
{ 1385570291, "assert fail (with message)" },
|
||||
{ 1402557361, "bad opcode" },
|
||||
{ 1403832952, "Attempt to register ClientField failed. Client Field set either already contains a field, or a hash collision has occurred." },
|
||||
{ 1412130119, "parameter can't be cast to a hash" },
|
||||
{ 1427704235, "expected struct type to add value pair" },
|
||||
{ 1480037573, "Invalid canonical name hash" },
|
||||
{ 1480821566, "Error registering client field. Attempted field size is not acceptable bit number range 1->32" },
|
||||
{ 1517473035, "Value out of range. Allowed values: 0 to 2" },
|
||||
{ 1609894461, "bad entity" },
|
||||
{ 1670707254, "linking error" },
|
||||
{ 1850691545, "Debug Break" },
|
||||
{ 1895566756, "dvar is not a 3d vector, but GetDvarVector3D has been called on it" },
|
||||
{ 1909233687, "Optional argument must be a vector type" },
|
||||
{ 1915758537, "RegisterClientField can only accept bit ranges between 1 and 32." },
|
||||
{ 1957162421, "Can't find bgCache entry" },
|
||||
{ 1999906612, "type is not a integer or float" },
|
||||
{ 2078816051, "not a valid name for a clientfield set." },
|
||||
{ 209668787, "RandomInt parm must be positive integer." },
|
||||
{ 2116335949, "function called with too many parameters" },
|
||||
{ 219569925, "hasperk() can only be called on local players" },
|
||||
{ 219686544, "object is not an array index" },
|
||||
{ 2253722136, "CamAnimScripted can only be called on a player." },
|
||||
{ 2269096660, "vector scale expecting vector" },
|
||||
{ 2279633554, "SV_SetConfigstring: bad index" },
|
||||
{ 2331090760, "stack does not have the pool" },
|
||||
{ 2344222932, "assert fail" },
|
||||
{ 2355618801, "Gesture table key can't have the higher bit set" },
|
||||
{ 2408700928, "not a valid name for a clientfield set." },
|
||||
{ 2448966512, "string too long" },
|
||||
{ 245612264, "foreach should be used with an array" },
|
||||
{ 247375020, "Attempting to register client field in client field set using bits, but system is out of space." },
|
||||
{ 2479879368, "RegisterClientField can only accept bit ranges between 1 and 32." },
|
||||
{ 249068885, "Failed to allocate from state pool" },
|
||||
{ 2517242050, "parameter does not exist" },
|
||||
{ 2532286589, "error message" },
|
||||
{ 2538360978, "not a function pointer" },
|
||||
{ 2572009355, "vector scale expecting vector" },
|
||||
{ 2606724305, "parameter does not exist" },
|
||||
{ 2626909173, "exceeded maximum number of script strings" },
|
||||
{ 2681972741, "bad clientfield for name" },
|
||||
{ 2687742442, "Forced script exception." },
|
||||
{ 269518924, "Ammo count must not be negative" },
|
||||
{ 2706458388, "Trying to get a local client index for a client that is not a local client." },
|
||||
{ 2734491973, "Error need at least one argument for LUINotifyEvent." },
|
||||
{ 2737681163, "assert fail (with message)" },
|
||||
{ 2751867714, "self isn't a field object" },
|
||||
{ 2792722947, "RegisterClientField can only accept 5 parameters" },
|
||||
{ 280703902, "parameter does not exist" },
|
||||
{ 2838301872, "Gesture table key can't have the higher bit set" },
|
||||
{ 2855209542, "Cannot call IncrementClientField on a non-'counter' type clientfield." },
|
||||
{ 2940210599, "Invalid Version Handling. Grab Bat !!!" },
|
||||
{ 3015158315, "getperks() can only be called on local players" },
|
||||
{ 3016026156, "Can't find asset" },
|
||||
{ 3030895505, "Whitelist failure for title" },
|
||||
{ 3059411687, "Argument and parameter count mismatch for LUINotifyEvent" },
|
||||
{ 3086288875, "playFx called with (0 0 0) forward direction" },
|
||||
{ 3122940489, "caller is not an entity" },
|
||||
{ 312545010, "not a vector" },
|
||||
{ 3143575744, "parameter does not exist" },
|
||||
{ 317100267, "unmatching types" },
|
||||
{ 3189465155, "Invalid bgCache type" },
|
||||
{ 3221522156, "Failed to alloc client field" },
|
||||
{ 3222417139, "size cannot be applied to type" },
|
||||
{ 3251676101, "Could not load raw file" },
|
||||
{ 3255107847, "LUINotifyEvent: entity must be a player entity" },
|
||||
{ 3288551912, "expected array type to add value pair" },
|
||||
{ 3385008561, "cannot set field on type" },
|
||||
{ 3459949409, "Failed to alloc client field - MAX_CLIENTFIELD_FIELDS_IN_SET=512 exceeded." },
|
||||
{ 3523382186, "ScrEvent map is full, unable to register new event" },
|
||||
{ 3524483616, "Ammo count must not be negative" },
|
||||
{ 3592841213, "cannot directly set the origin on AI. Use the teleport command instead." },
|
||||
{ 359760836, "G_Spawn: no free entities" },
|
||||
{ 3654063291, "ScrEvent map is full, unable to register new event" },
|
||||
{ 3679846953, "LUINotifyEvent: entity must be a player entity" },
|
||||
{ 3699844858, "parameter does not exist" },
|
||||
{ 3761634992, "not a pointer" },
|
||||
{ 3894031202, "Can't find gamedata/playeranim/playeranimtypes.txt" },
|
||||
{ 3967909977, "Trying to get version of a demo when the demo system is idle." },
|
||||
{ 3990130335, "player getperks(<localClientNum>): localClientNum out of range" },
|
||||
{ 4039057166, "LUINotifyEvent: entity must be a player entity" },
|
||||
{ 4047738848, "Invalid opcode (Recovery)" },
|
||||
{ 409067247, "No clientfield named found in set." },
|
||||
{ 4100218247, "Error sending LUI notify event: LUI event name is not precached." },
|
||||
{ 4103906837, "Entity is not an item." },
|
||||
{ 4104994143, "can't allocate more script variables" },
|
||||
{ 4106063796, "key value provided for array is not valid" },
|
||||
{ 4163774148, "Optional argument must be a float or integer type" },
|
||||
{ 4178589916, "Model was not cached by the linker." },
|
||||
{ 4196473479, "parameter does not exist" },
|
||||
{ 4213634562, "precacheLeaderboards must be called before any wait statements in the gametype or level script" },
|
||||
{ 4220857104, "Cannot call IncrementClientField on a 'counter' type clientfield on the frame it is spawned, since newEnts on the clientside will not process 'counter' type clientfields." },
|
||||
{ 467754466, "Error registering client field. bCallbacksFor0WhenNew (CF_CALLBACK_ZERO_ON_NEW_ENT) is disallowed for CF_SCRIPTMOVERFIELDS." },
|
||||
{ 512306404, "not a function pointer" },
|
||||
{ 57350207, "Unknown perk" },
|
||||
{ 580674660, "Unknown ent type in GScr_GetFieldSetForEntityType." },
|
||||
{ 619241173, "Failed to allocate from element pool" },
|
||||
{ 647662103, "parameter can't be cast to a string" },
|
||||
{ 657813230, "Error registering client field. bCallbacksFor0WhenNew (CF_CALLBACK_ZERO_ON_NEW_ENT) is disallowed for counter type clientfields. Due to it's treatment of the old and new val as a ring buffer, the counter type is not valid on a new snapshot, new ent, or demojump" },
|
||||
{ 665902298, "Parameter must be an array" },
|
||||
{ 71894325, "CamAnimScripted or ExtraCamAnimScripted can only be called on a player." },
|
||||
{ 732489269, "Non-player entity passed to UploadStats()" },
|
||||
{ 744499668, "too many vehicles" },
|
||||
{ 750896894, "Invalid bgCache type" },
|
||||
{ 753495682, "RandomIntRange's second parameter must be greater than the first." },
|
||||
{ 754846421, "invalid vehicle spawn origin" },
|
||||
{ 829015102, "var isn't a field object" },
|
||||
{ 876169112, "key value provided for struct is not valid" },
|
||||
{ 887228744, "origin being set to NAN." },
|
||||
{ 904544970, "Attempt to register Client Field post finalization of Client Field registration period failed." },
|
||||
{ 941828720, "exitlevel already called" },
|
||||
{ 946363963, "Invalid opcode" },
|
||||
{ 949934674, "Invalid Version Handling. Grab Bat !!!" },
|
||||
{ 952690413, "parameter can't be cast to a boolean" },
|
||||
{ 962032109, "Entity does not exist." },
|
||||
{ 968521323, "player hasperk(<localClientNum>, <perk>): localClientNum out of range" },
|
||||
{ 4088624643, "Can't find asset" },
|
||||
{ game::runtime_errors::custom_error_id, "Shield Error" }
|
||||
};
|
||||
}
|
||||
|
||||
const char* get_error_message(uint64_t code)
|
||||
{
|
||||
auto it = errors.find(code);
|
||||
// known error
|
||||
if (it != errors.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// read from the csv
|
||||
static game::BO4_AssetRef_t custom_errors_file = game::AssetRef(fnv1a::generate_hash("gamedata/shield/custom_errors.csv"));
|
||||
|
||||
xassets::stringtable_header* table = xassets::DB_FindXAssetHeader(xassets::ASSET_TYPE_STRINGTABLE, &custom_errors_file, false, -1).stringtable;
|
||||
|
||||
if (!table || !table->columns_count || table->columns_count < 2)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < table->rows_count; i++)
|
||||
{
|
||||
auto* rows = &table->values[i * table->columns_count];
|
||||
|
||||
if (rows[0].type != xassets::STC_TYPE_INT || rows[1].type != xassets::STC_TYPE_STRING)
|
||||
{
|
||||
continue; // bad types
|
||||
}
|
||||
|
||||
if (rows[0].value.hash_value == code)
|
||||
{
|
||||
return rows[1].value.string_value;
|
||||
}
|
||||
}
|
||||
|
||||
// unknown
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
7
source/proxy-dll/definitions/game_runtime_errors.hpp
Normal file
7
source/proxy-dll/definitions/game_runtime_errors.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace game::runtime_errors
|
||||
{
|
||||
constexpr uint64_t custom_error_id = 0x42693201;
|
||||
const char* get_error_message(uint64_t code);
|
||||
}
|
@ -1,19 +1,35 @@
|
||||
#include <std_include.hpp>
|
||||
#include "variables.hpp"
|
||||
#include "component/hashes.hpp"
|
||||
|
||||
namespace fnv1a
|
||||
{
|
||||
uint64_t generate_hash(const char* string)
|
||||
uint64_t generate_hash(const char* string, uint64_t start)
|
||||
{
|
||||
uint64_t Result = 0xCBF29CE484222325;
|
||||
uint64_t res = start;
|
||||
|
||||
for (uint64_t i = 0; i < strlen(string); i++)
|
||||
for (const char* c = string; *c; c++)
|
||||
{
|
||||
Result ^= string[i];
|
||||
Result *= 0x100000001B3;
|
||||
if (*c == '\\')
|
||||
{
|
||||
res ^= '/';
|
||||
}
|
||||
else
|
||||
{
|
||||
res ^= tolower(*c);
|
||||
}
|
||||
|
||||
res *= 0x100000001B3;
|
||||
}
|
||||
|
||||
return (Result & 0x7FFFFFFFFFFFFFFF);
|
||||
uint64_t val = res & 0x7FFFFFFFFFFFFFFF;
|
||||
|
||||
if (start == 0xCBF29CE484222325)
|
||||
{
|
||||
hashes::add_hash(val, string);
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
uint64_t generate_hash_pattern(const char* string)
|
||||
@ -21,13 +37,29 @@ namespace fnv1a
|
||||
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);
|
||||
if (!v.rfind("hash_", 0)) return std::strtoull(&string[5], nullptr, 16) & 0x7FFFFFFFFFFFFFFF;
|
||||
if (!v.rfind("file_", 0)) return std::strtoull(&string[5], nullptr, 16) & 0x7FFFFFFFFFFFFFFF;
|
||||
if (!v.rfind("script_", 0)) return std::strtoull(&string[7], nullptr, 16) & 0x7FFFFFFFFFFFFFFF;
|
||||
|
||||
// lua notation x64:123
|
||||
if (!v.rfind("x64:", 0)) return std::strtoull(&string[4], nullptr, 16);
|
||||
|
||||
if (!v.rfind("x64:", 0))
|
||||
{
|
||||
if (v.length() <= 0x18 && v.ends_with(".lua"))
|
||||
{
|
||||
// x64:123456789abcdf.lua
|
||||
//
|
||||
// extract hash value
|
||||
char tmpbuffer[0x17] = {};
|
||||
|
||||
memcpy(tmpbuffer, string + 4, v.length() - 8);
|
||||
|
||||
// gen the hash and add .lua add the end
|
||||
return generate_hash(".lua", std::strtoull(&string[4], nullptr, 16) & 0x7FFFFFFFFFFFFFFF);
|
||||
}
|
||||
|
||||
return std::strtoull(&string[4], nullptr, 16) & 0x7FFFFFFFFFFFFFFF;
|
||||
}
|
||||
|
||||
// unknown, use hashed value
|
||||
return generate_hash(string);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace fnv1a
|
||||
{
|
||||
uint64_t generate_hash(const char* string);
|
||||
uint64_t generate_hash(const char* string, uint64_t start = 0xCBF29CE484222325);
|
||||
|
||||
uint64_t generate_hash_pattern(const char* string);
|
||||
}
|
||||
|
91
source/proxy-dll/definitions/xassets.cpp
Normal file
91
source/proxy-dll/definitions/xassets.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
#include <std_include.hpp>
|
||||
#include "xassets.hpp"
|
||||
|
||||
namespace xassets
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const char* cache_names[]
|
||||
{
|
||||
"invalid",
|
||||
"vehicle",
|
||||
"model",
|
||||
"aitype",
|
||||
"character",
|
||||
"xmodelalias",
|
||||
"weapon",
|
||||
"gesture",
|
||||
"gesturetable",
|
||||
"zbarrier",
|
||||
"rumble",
|
||||
"shellshock",
|
||||
"statuseffect",
|
||||
"xcam",
|
||||
"destructible",
|
||||
"streamerhint",
|
||||
"flowgraph",
|
||||
"xanim",
|
||||
"sanim",
|
||||
"scriptbundle",
|
||||
"talent",
|
||||
"statusicon",
|
||||
"locationselector",
|
||||
"menu",
|
||||
"material",
|
||||
"string",
|
||||
"eventstring",
|
||||
"moviefile",
|
||||
"objective",
|
||||
"fx",
|
||||
"lui_menu_data",
|
||||
"lui_elem",
|
||||
"lui_elem_uid",
|
||||
"radiant_exploder",
|
||||
"soundalias",
|
||||
"client_fx",
|
||||
"client_tagfxset",
|
||||
"client_lui_elem",
|
||||
"client_lui_elem_uid",
|
||||
"requires_implements"
|
||||
};
|
||||
}
|
||||
|
||||
BGCacheTypes BG_Cache_GetTypeIndex(const char* name)
|
||||
{
|
||||
//BG_Cache_GetTypeIndexInternal base isn't available after unpack
|
||||
for (size_t i = 0; i < BGCacheTypes::BG_CACHE_TYPE_COUNT; i++)
|
||||
{
|
||||
if (!_strcmpi(name, cache_names[i]))
|
||||
{
|
||||
return (BGCacheTypes)i;
|
||||
}
|
||||
}
|
||||
return BG_CACHE_TYPE_INVALID;
|
||||
}
|
||||
|
||||
const char* BG_Cache_GetTypeName(BGCacheTypes type)
|
||||
{
|
||||
return cache_names[type];
|
||||
}
|
||||
|
||||
XAssetType DB_GetXAssetTypeIndex(const char* name)
|
||||
{
|
||||
for (size_t i = 0; i < XAssetType::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
if (!_strcmpi(name, s_XAssetTypeInfo[i].name))
|
||||
{
|
||||
return (XAssetType)i;
|
||||
}
|
||||
}
|
||||
return XAssetType::ASSET_TYPE_COUNT;
|
||||
}
|
||||
|
||||
const char* DB_GetXAssetTypeName(XAssetType type)
|
||||
{
|
||||
if (type >= ASSET_TYPE_COUNT)
|
||||
{
|
||||
return "invalid";
|
||||
}
|
||||
return s_XAssetTypeInfo[type].name;
|
||||
}
|
||||
}
|
@ -29,6 +29,11 @@ namespace xassets
|
||||
byte* buffer{};
|
||||
};
|
||||
|
||||
struct localize_entry_header {
|
||||
const char* string = "";
|
||||
uint64_t name{};
|
||||
uint64_t namepad{};
|
||||
};
|
||||
|
||||
union stringtable_cell_value
|
||||
{
|
||||
@ -80,6 +85,8 @@ namespace xassets
|
||||
lua_file_header* lua_file;
|
||||
scriptparsetree_header* scriptparsetree;
|
||||
stringtable_header* stringtable;
|
||||
localize_entry_header* localize;
|
||||
void* ptr;
|
||||
};
|
||||
|
||||
enum XAssetType : byte
|
||||
@ -257,4 +264,83 @@ namespace xassets
|
||||
ASSET_TYPE_REPORT = 0xA9,
|
||||
ASSET_TYPE_FULL_COUNT = 0xAA,
|
||||
};
|
||||
|
||||
enum BGCacheTypes : byte
|
||||
{
|
||||
BG_CACHE_TYPE_INVALID = 0,
|
||||
BG_CACHE_TYPE_VEHICLE = 1,
|
||||
BG_CACHE_TYPE_MODEL = 2,
|
||||
BG_CACHE_TYPE_AITYPE = 3,
|
||||
BG_CACHE_TYPE_CHARACTER = 4,
|
||||
BG_CACHE_TYPE_XMODELALIAS = 5,
|
||||
BG_CACHE_TYPE_WEAPON = 6,
|
||||
BG_CACHE_TYPE_GESTURE = 7,
|
||||
BG_CACHE_TYPE_GESTURETABLE = 8,
|
||||
BG_CACHE_TYPE_ZBARRIER = 9,
|
||||
BG_CACHE_TYPE_RUMBLE = 10,
|
||||
BG_CACHE_TYPE_SHELLSHOCK = 11,
|
||||
BG_CACHE_TYPE_STATUSEFFECT = 12,
|
||||
BG_CACHE_TYPE_XCAM = 13,
|
||||
BG_CACHE_TYPE_DESTRUCTIBLE = 14,
|
||||
BG_CACHE_TYPE_STREAMERHINT = 15,
|
||||
BG_CACHE_TYPE_FLOWGRAPH = 16,
|
||||
BG_CACHE_TYPE_XANIM = 17,
|
||||
BG_CACHE_TYPE_SANIM = 18,
|
||||
BG_CACHE_TYPE_SCRIPTBUNDLE = 19,
|
||||
BG_CACHE_TYPE_TALENT = 20,
|
||||
BG_CACHE_TYPE_STATUSICON = 21,
|
||||
BG_CACHE_TYPE_LOCATIONSELECTOR = 22,
|
||||
BG_CACHE_TYPE_MENU = 23,
|
||||
BG_CACHE_TYPE_MATERIAL = 24,
|
||||
BG_CACHE_TYPE_STRING = 25,
|
||||
BG_CACHE_TYPE_EVENTSTRING = 26,
|
||||
BG_CACHE_TYPE_MOVIEFILE = 27,
|
||||
BG_CACHE_TYPE_OBJECTIVE = 28,
|
||||
BG_CACHE_TYPE_FX = 29,
|
||||
BG_CACHE_TYPE_LUI_MENU_DATA = 30,
|
||||
BG_CACHE_TYPE_LUI_ELEM = 31,
|
||||
BG_CACHE_TYPE_LUI_ELEM_UID = 32,
|
||||
BG_CACHE_TYPE_RADIANT_EXPLODER = 33,
|
||||
BG_CACHE_TYPE_SOUNDALIAS = 34,
|
||||
BG_CACHE_TYPE_CLIENT_FX = 35,
|
||||
BG_CACHE_TYPE_CLIENT_TAGFXSET = 36,
|
||||
BG_CACHE_TYPE_CLIENT_LUI_ELEM = 37,
|
||||
BG_CACHE_TYPE_CLIENT_LUI_ELEM_UID = 38,
|
||||
BG_CACHE_TYPE_REQUIRES_IMPLEMENTS = 39,
|
||||
BG_CACHE_TYPE_COUNT
|
||||
};
|
||||
|
||||
struct bg_cache_info_def
|
||||
{
|
||||
BGCacheTypes type;
|
||||
game::BO4_AssetRef_t name;
|
||||
uint64_t asset;
|
||||
};
|
||||
|
||||
struct bg_cache_info
|
||||
{
|
||||
game::BO4_AssetRef_t name{};
|
||||
bg_cache_info_def* def{};
|
||||
int defCount{};
|
||||
};
|
||||
|
||||
struct xasset_type_info {
|
||||
const char* name;
|
||||
uint64_t unk8;
|
||||
uint64_t unk10;
|
||||
game::BO4_AssetRef_t* (*get_name)(game::BO4_AssetRef_t* name, const void* header);
|
||||
void(*set_name)(void* header, game::BO4_AssetRef_t* name);
|
||||
};
|
||||
|
||||
WEAK game::symbol<xasset_type_info> s_XAssetTypeInfo{ 0x14498BB00_g };
|
||||
|
||||
WEAK game::symbol<void(bg_cache_info* cache, int32_t flags)> Demo_AddBGCacheAndRegister{ 0x1405CF5A0_g };
|
||||
WEAK game::symbol<int(BGCacheTypes type, game::BO4_AssetRef_t* name)> BG_Cache_RegisterAndGet{ 0x1405CEC20_g };
|
||||
WEAK game::symbol<BGCacheTypes(const char* name)> BG_Cache_GetTypeIndexInternal{ 0x1405CDBD0_g };
|
||||
BGCacheTypes BG_Cache_GetTypeIndex(const char* name);
|
||||
const char* BG_Cache_GetTypeName(BGCacheTypes type);
|
||||
XAssetType DB_GetXAssetTypeIndex(const char* name);
|
||||
const char* DB_GetXAssetTypeName(XAssetType type);
|
||||
|
||||
WEAK game::symbol<xasset_header(XAssetType type, game::BO4_AssetRef_t* name, bool errorIfMissing, int waittime)> DB_FindXAssetHeader{ 0x142EB75B0_g };
|
||||
}
|
Loading…
Reference in New Issue
Block a user