Organize scripts and add TWS plugins with HTTP timeouts
Vendor pinned T5 and T6 plugin sources under the new plugins layout. Add native HTTP deadlines, status and timing diagnostics, telemetry integration, and build/test tooling.
This commit is contained in:
@@ -0,0 +1,585 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/structs.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "gsc.hpp"
|
||||
#include "json.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::unordered_map<std::string, function_t> functions;
|
||||
std::unordered_map<std::string, function_t> methods;
|
||||
|
||||
std::unordered_map<std::string, void*> function_wraps;
|
||||
std::unordered_map<std::string, void*> method_wraps;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour player_get_method_hook;
|
||||
utils::hook::detour scr_get_common_function_hook;
|
||||
utils::hook::detour scr_error_internal_hook;
|
||||
utils::hook::detour scr_post_load_scripts_hook;
|
||||
|
||||
auto field_offset_start = 0xA000;
|
||||
std::unordered_map<unsigned int, entity_field_t> custom_fields[class_count];
|
||||
|
||||
std::string current_namespace;
|
||||
|
||||
std::unordered_map<std::string, scripting::script_value> world;
|
||||
|
||||
std::string get_full_name(const std::string& name)
|
||||
{
|
||||
if (current_namespace.empty())
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return std::format("{}::{}", current_namespace, name);
|
||||
}
|
||||
|
||||
void* find_function(const std::string& name)
|
||||
{
|
||||
const auto full_name = get_full_name(name);
|
||||
const auto iter = function_wraps.find(full_name);
|
||||
if (iter == function_wraps.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void* find_method(const std::string& name)
|
||||
{
|
||||
const auto full_name = get_full_name(name);
|
||||
const auto iter = method_wraps.find(full_name);
|
||||
if (iter == method_wraps.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
builtin_function scr_get_common_function(const char** name, int* type, int* min_args, int* max_args)
|
||||
{
|
||||
const auto func = reinterpret_cast<builtin_function>(find_function(*name));
|
||||
|
||||
if (func == nullptr)
|
||||
{
|
||||
return scr_get_common_function_hook.invoke<builtin_function>(name, type, min_args, max_args);
|
||||
}
|
||||
|
||||
*type = 1;
|
||||
*min_args = 0;
|
||||
*max_args = 512;
|
||||
|
||||
return func;
|
||||
}
|
||||
|
||||
builtin_method player_get_method(const char** name, int* min_args, int* max_args)
|
||||
{
|
||||
const auto method = reinterpret_cast<builtin_method>(find_method(*name));
|
||||
|
||||
if (method == nullptr)
|
||||
{
|
||||
return player_get_method_hook.invoke<builtin_method>(name, min_args, max_args);
|
||||
}
|
||||
|
||||
*min_args = 0;
|
||||
*max_args = 512;
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
const gsc::entity_field_t* find_field(unsigned int classnum, unsigned int offset)
|
||||
{
|
||||
const auto& class_map = custom_fields[classnum];
|
||||
const auto field_iter = class_map.find(offset);
|
||||
if (field_iter == class_map.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &field_iter->second;
|
||||
}
|
||||
|
||||
utils::hook::detour scr_get_object_field_hook;
|
||||
void scr_get_object_field_stub(unsigned int classnum, int entnum, unsigned int offset)
|
||||
{
|
||||
const auto field = find_field(classnum, offset);
|
||||
if (field == nullptr)
|
||||
{
|
||||
return scr_get_object_field_hook.invoke<void>(classnum, entnum, offset);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto result = field->getter(entnum);
|
||||
return_value(result);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("******* script runtime error *******\n");
|
||||
printf("while getting field \"%s\": %s\n", field->name.data(), e.what());
|
||||
printf("************************************\n");
|
||||
}
|
||||
}
|
||||
|
||||
utils::hook::detour scr_set_object_field_hook;
|
||||
int scr_set_object_field_stub(unsigned int classnum, int entnum, unsigned int offset)
|
||||
{
|
||||
const auto field = find_field(classnum, offset);
|
||||
if (field == nullptr)
|
||||
{
|
||||
return scr_set_object_field_hook.invoke<int>(classnum, entnum, offset);
|
||||
}
|
||||
|
||||
const auto args = get_arguments();
|
||||
|
||||
try
|
||||
{
|
||||
field->setter(entnum, args[0]);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("******* script runtime error *******\n");
|
||||
printf("while setting field \"%s\": %s\n", field->name.data(), e.what());
|
||||
printf("************************************\n");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void scr_post_load_scripts_stub()
|
||||
{
|
||||
for (auto i = 0; i < class_id_t::class_count; i++)
|
||||
{
|
||||
const auto& class_map = custom_fields[i];
|
||||
for (const auto& [offset, field] : class_map)
|
||||
{
|
||||
game::Scr_AddClassField(game::SCRIPTINSTANCE_SERVER, i, field.name.data(), offset);
|
||||
}
|
||||
}
|
||||
|
||||
return scr_post_load_scripts_hook.invoke<void>();
|
||||
}
|
||||
|
||||
__declspec(naked) void scr_error_internal_stub_1_mp()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, 0
|
||||
mov edx, eax
|
||||
imul edx, 0x54
|
||||
|
||||
push 0x8F3F65
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void scr_error_internal_stub_1_zm()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, 0
|
||||
mov edx, eax
|
||||
imul edx, 0x54
|
||||
|
||||
push 0x8F2CC5
|
||||
retn
|
||||
}
|
||||
}
|
||||
|
||||
void gsc_obj_resolve_stub_1(game::GSC_OBJ* obj, game::GSC_IMPORT_ITEM* item)
|
||||
{
|
||||
const auto name = &obj->magic[item->name];
|
||||
current_namespace = &obj->magic[item->name_space];
|
||||
|
||||
if (current_namespace[0] == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto is_function = find_function(name);
|
||||
const auto is_method = !is_function && find_method(name);
|
||||
|
||||
if (is_function || is_method)
|
||||
{
|
||||
item->name_space += static_cast<unsigned short>(current_namespace.size());
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void gsc_obj_resolve_stub_mp()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
push edi
|
||||
push esi
|
||||
call gsc_obj_resolve_stub_1
|
||||
pop esi
|
||||
pop edi
|
||||
popad
|
||||
|
||||
movzx eax, byte ptr[edi + 7]
|
||||
and eax, 0xF
|
||||
dec eax
|
||||
|
||||
push 0x6CD7F1
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
__declspec(naked) void gsc_obj_resolve_stub_zm()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
pushad
|
||||
push edi
|
||||
push esi
|
||||
call gsc_obj_resolve_stub_1
|
||||
pop esi
|
||||
pop edi
|
||||
popad
|
||||
|
||||
movzx eax, byte ptr[edi + 7]
|
||||
and eax, 0xF
|
||||
dec eax
|
||||
|
||||
push 0x4F0441
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void call_function(const function_t& function, const std::string& name)
|
||||
{
|
||||
const auto args = get_arguments();
|
||||
|
||||
try
|
||||
{
|
||||
const auto value = function(args);
|
||||
return_value(value);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("******* script runtime error *******\n");
|
||||
printf("in call to builtin function \"%s\": %s\n", name.data(), e.what());
|
||||
printf("************************************\n");
|
||||
}
|
||||
}
|
||||
|
||||
void call_method(const function_t& method, const std::string& name, const game::scr_entref_t entref)
|
||||
{
|
||||
const auto args = get_arguments();
|
||||
|
||||
try
|
||||
{
|
||||
const scripting::entity entity = game::Scr_GetEntityId(
|
||||
game::SCRIPTINSTANCE_SERVER, entref.entnum, entref.classnum, 0);
|
||||
|
||||
std::vector<scripting::script_value> next_args{};
|
||||
next_args.push_back(entity);
|
||||
for (const auto& arg : args)
|
||||
{
|
||||
next_args.push_back(arg);
|
||||
}
|
||||
|
||||
const auto value = method(next_args);
|
||||
return_value(value);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("******* script runtime error *******\n");
|
||||
printf("in call to builtin method \"%s\": %s\n", name.data(), e.what());
|
||||
printf("************************************\n");
|
||||
}
|
||||
}
|
||||
|
||||
namespace field
|
||||
{
|
||||
void add(const class_id_t classnum, const std::string& name, const field_getter_t getter, const field_setter_t& setter)
|
||||
{
|
||||
const auto offset = field_offset_start++;
|
||||
auto& class_map = custom_fields[classnum];
|
||||
|
||||
entity_field_t field{};
|
||||
field.name = name;
|
||||
field.getter = getter;
|
||||
field.setter = setter;
|
||||
class_map.insert(std::make_pair(offset, field));
|
||||
}
|
||||
}
|
||||
|
||||
void return_value(const scripting::script_value& value)
|
||||
{
|
||||
if (game::scr_VmPub->outparamcount)
|
||||
{
|
||||
game::Scr_ClearOutParams(game::SCRIPTINSTANCE_SERVER);
|
||||
}
|
||||
|
||||
scripting::push_value(value);
|
||||
}
|
||||
|
||||
std::vector<scripting::script_value> get_arguments()
|
||||
{
|
||||
std::vector<scripting::script_value> args;
|
||||
|
||||
for (auto i = 0; static_cast<unsigned int>(i) < game::scr_VmPub->outparamcount; i++)
|
||||
{
|
||||
const auto value = game::scr_VmPub->top[-i];
|
||||
args.push_back(value);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
std::string find_builtin_name(void* function)
|
||||
{
|
||||
for (auto i = scripting::function_map.begin(); i != scripting::function_map.end(); ++i)
|
||||
{
|
||||
if (i->second.actionFunc == function)
|
||||
{
|
||||
return i->second.actionString;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = function_wraps.begin(); i != function_wraps.end(); ++i)
|
||||
{
|
||||
if (i->second == function)
|
||||
{
|
||||
return i->first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string find_builtin_method_name(void* function)
|
||||
{
|
||||
for (auto i = scripting::method_map.begin(); i != scripting::method_map.end(); ++i)
|
||||
{
|
||||
if (i->second.actionFunc == function)
|
||||
{
|
||||
return i->second.actionString;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = method_wraps.begin(); i != method_wraps.end(); ++i)
|
||||
{
|
||||
if (i->second == function)
|
||||
{
|
||||
return i->first;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
||||
{
|
||||
utils::hook::jump(SELECT(0x6CD7E9, 0x4F0439), SELECT(gsc_obj_resolve_stub_mp, gsc_obj_resolve_stub_zm));
|
||||
|
||||
scr_get_common_function_hook.create(SELECT(0x4B57B0, 0x4AD040), scr_get_common_function);
|
||||
player_get_method_hook.create(SELECT(0x432480, 0x6F2DB0), player_get_method);
|
||||
|
||||
scr_get_object_field_hook.create(SELECT(0x573160, 0x4224E0), scr_get_object_field_stub);
|
||||
scr_set_object_field_hook.create(SELECT(0x5B9820, 0x43F2A0), scr_set_object_field_stub);
|
||||
scr_post_load_scripts_hook.create(SELECT(0x6B75B0, 0x492440), scr_post_load_scripts_stub);
|
||||
|
||||
utils::hook::jump(SELECT(0x8F3F60, 0x8F2CC0), SELECT(scr_error_internal_stub_1_mp, scr_error_internal_stub_1_zm));
|
||||
|
||||
field::add(class_id_t::class_entity, "eflags",
|
||||
[](unsigned int entnum) -> scripting::script_value
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
return entity->flags;
|
||||
},
|
||||
[](unsigned int entnum, const scripting::script_value& value)
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
entity->flags = value.as<int>();
|
||||
}
|
||||
);
|
||||
|
||||
field::add(class_id_t::class_entity, "eflags2",
|
||||
[](unsigned int entnum) -> scripting::script_value
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
return entity->eFlags2;
|
||||
},
|
||||
[](unsigned int entnum, const scripting::script_value& value)
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
entity->eFlags2 = value.as<int>();
|
||||
}
|
||||
);
|
||||
|
||||
field::add(class_id_t::class_entity, "clientflags",
|
||||
[](unsigned int entnum) -> scripting::script_value
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
return entity->client->flags;
|
||||
},
|
||||
[](unsigned int entnum, const scripting::script_value& value)
|
||||
{
|
||||
const auto entity = &game::g_entities[entnum];
|
||||
entity->client->flags = value.as<int>();
|
||||
}
|
||||
);
|
||||
|
||||
field::add(class_id_t::class_entity, "address",
|
||||
[](unsigned int entnum) -> scripting::script_value
|
||||
{
|
||||
if (entnum >= 18)
|
||||
{
|
||||
throw std::runtime_error("not a player entity");
|
||||
}
|
||||
|
||||
const auto clients = *game::svs_clients;
|
||||
const auto client = &clients[entnum];
|
||||
const auto address = client->header.netchan.remoteAddress.ip;
|
||||
|
||||
const std::string address_str = utils::string::va("%i.%i.%i.%i",
|
||||
address[0],
|
||||
address[1],
|
||||
address[2],
|
||||
address[3]
|
||||
);
|
||||
|
||||
return address_str;
|
||||
},
|
||||
[](unsigned int, const scripting::script_value&)
|
||||
{
|
||||
throw std::runtime_error("cannot set address");
|
||||
}
|
||||
);
|
||||
|
||||
function::add("getfunctionargcount", [](const scripting::function& function)
|
||||
{
|
||||
const auto pos = function.get_pos();
|
||||
if (*pos != 0x17) // OP_SafeCreateLocalVariables
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<int>(pos[1]);
|
||||
});
|
||||
|
||||
function::add("arrayremovekey", [](const scripting::array& array, const std::string& key)
|
||||
{
|
||||
array.erase(key);
|
||||
});
|
||||
|
||||
function::add("xor", [](const int a, const int b)
|
||||
{
|
||||
return a ^ b;
|
||||
});
|
||||
|
||||
function::add("not", [](const int a)
|
||||
{
|
||||
return ~a;
|
||||
});
|
||||
|
||||
function::add("and", [](const int a, const int b)
|
||||
{
|
||||
return a & b;
|
||||
});
|
||||
|
||||
function::add("or", [](const int a, const int b)
|
||||
{
|
||||
return a | b;
|
||||
});
|
||||
|
||||
function::add("structget", [](const scripting::object& obj, const std::string& key)
|
||||
{
|
||||
return obj[key];
|
||||
});
|
||||
|
||||
function::add("structset", [](const scripting::object& obj, const std::string& key,
|
||||
const scripting::script_value& value)
|
||||
{
|
||||
obj[key] = value;
|
||||
});
|
||||
|
||||
function::add("struct::get", [](const scripting::object& obj, const std::string& key)
|
||||
{
|
||||
return obj[key];
|
||||
});
|
||||
|
||||
function::add("struct::set", [](const scripting::object& obj, const std::string& key,
|
||||
const scripting::script_value& value)
|
||||
{
|
||||
obj[key] = value;
|
||||
});
|
||||
|
||||
function::add("structremove", [](const scripting::object& obj, const std::string& key)
|
||||
{
|
||||
obj.erase(key);
|
||||
});
|
||||
|
||||
function::add("getstructkeys", [](const scripting::object& obj)
|
||||
{
|
||||
return obj.get_keys();
|
||||
});
|
||||
|
||||
function::add("isfunctionptr", [](const scripting::script_value& value)
|
||||
{
|
||||
return value.is<scripting::function>();
|
||||
});
|
||||
|
||||
function::add("isentity", [](const scripting::script_value& value)
|
||||
{
|
||||
const auto& raw = value.get_raw();
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[raw.u.uintValue].w.type & 0x7F;
|
||||
return raw.type == game::SCRIPT_OBJECT && type == game::SCRIPT_ENTITY;
|
||||
});
|
||||
|
||||
function::add("isstruct", [](const scripting::script_value& value)
|
||||
{
|
||||
return value.is<scripting::object>();
|
||||
});
|
||||
|
||||
function::add("typeof", [](const scripting::script_value& value)
|
||||
{
|
||||
return value.type_name();
|
||||
});
|
||||
|
||||
method::add("get", [](const scripting::entity& entity, const std::string& field)
|
||||
{
|
||||
return entity.get(field);
|
||||
});
|
||||
|
||||
method::add("set", [](const scripting::entity& entity, const std::string& field,
|
||||
const scripting::script_value& value)
|
||||
{
|
||||
entity.set(field, value);
|
||||
});
|
||||
|
||||
function::add("worldget", [](const std::string& key)
|
||||
{
|
||||
return world[key];
|
||||
});
|
||||
|
||||
function::add("worldset", [](const std::string& key, const scripting::script_value& value)
|
||||
{
|
||||
world[key] = value;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::component)
|
||||
Reference in New Issue
Block a user