This commit is contained in:
Diavolo 2023-02-21 01:17:04 +01:00
parent 3b52e11a45
commit 9be09b5873
No known key found for this signature in database
GPG Key ID: FA77F074E98D98A5
6 changed files with 251 additions and 8 deletions

View File

@ -62,6 +62,7 @@ namespace game
Scr_GetFunctionHandle_t Scr_GetFunctionHandle;
Scr_ExecThread_t Scr_ExecThread;
Scr_FreeThread_t Scr_FreeThread;
Scr_RegisterFunction_t Scr_RegisterFunction;
GetObjectType_t GetObjectType;
@ -788,6 +789,7 @@ namespace game
native::Scr_GetFunctionHandle = native::Scr_GetFunctionHandle_t(SELECT_VALUE(0x51DD50, 0x5618A0));
native::Scr_ExecThread = native::Scr_ExecThread_t(SELECT_VALUE(0x4FC590, 0x56E240));
native::Scr_FreeThread = native::Scr_FreeThread_t(SELECT_VALUE(0x51FD90, 0x569E20));
native::Scr_RegisterFunction = native::Scr_RegisterFunction_t(SELECT_VALUE(0x49E190, 0x561520));
native::GetObjectType = native::GetObjectType_t(SELECT_VALUE(0x4D8FE0, 0x565C60));

View File

@ -140,6 +140,9 @@ namespace game
typedef void (*Scr_FreeThread_t)(unsigned short handle);
extern Scr_FreeThread_t Scr_FreeThread;
typedef void (*Scr_RegisterFunction_t)(void* func, int type, unsigned int name);
extern Scr_RegisterFunction_t Scr_RegisterFunction;
typedef unsigned int(*GetObjectType_t)(unsigned int id);
extern GetObjectType_t GetObjectType;

View File

@ -1,6 +1,8 @@
#include <std_include.hpp>
#include "game/game.hpp"
#include "module/gsc/script_extension.hpp"
#include "functions.hpp"
#include <utils/string.hpp>
@ -67,10 +69,10 @@ namespace scripting
game::native::BuiltinFunction get_function_by_index(const std::uint32_t index)
{
static const auto function_table = SELECT_VALUE(0x186C68C, 0x1D6EB34);
static const auto method_table = SELECT_VALUE(0x184CDB0, 0x1D4F258);
auto** function_table = gsc::func_table;
auto** method_table = gsc::meth_table;
if (index < 0x1C7)
if (index < gsc::scr_func_max_id)
{
return reinterpret_cast<game::native::BuiltinFunction*>(function_table)[index - 1];
}

View File

@ -2,14 +2,141 @@
#include <loader/module_loader.hpp>
#include "game/game.hpp"
#include "script_extension.hpp"
#include "script_error.hpp"
#include "module/console.hpp"
#include "module/scripting.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <gsc_interface.hpp>
namespace gsc
{
std::uint16_t scr_func_max_id = 0x1C7;
std::uint16_t scr_meth_max_id = 0x830C;
void* func_table[500];
void* meth_table[900];
namespace
{
struct builtin_function
{
std::string name;
std::uint16_t id; // actual 'name'
void(*actionFunc)();
};
struct builtin_method
{
std::string name;
std::uint16_t id;
void(*actionFunc)(game::native::scr_entref_t);
};
utils::hook::detour scr_register_function_hook;
std::vector<builtin_function> custom_functions;
std::vector<builtin_method> custom_methods;
std::unordered_map<const char*, const char*> replaced_funcs;
const char* vm_execute_code_pos = nullptr;
DWORD vm_execute_addr;
DWORD vm_execute_func_addr;
DWORD vm_execute_meth_addr;
void scr_register_function_stub(void* func, [[maybe_unused]] int type, unsigned int name)
{
assert(name);
if ((name - 1) < scr_func_max_id)
{
func_table[name] = func;
}
else
{
meth_table[name] = func;
}
}
unsigned int scr_get_function_stub(char** p_name, int* type)
{
std::memset(func_table, 0, sizeof(func_table));
for (const auto& func : custom_functions)
{
scr_register_function_stub(reinterpret_cast<void*>(func.actionFunc), 0, func.id);
}
return utils::hook::invoke<unsigned int>(SELECT_VALUE(0x4CAC00, 0x527750), p_name, type); // Scr_GetFunction
}
unsigned int scr_get_method_stub(const char** p_name, int* type)
{
std::memset(meth_table, 0, sizeof(meth_table));
for (const auto& meth : custom_methods)
{
scr_register_function_stub(reinterpret_cast<void*>(meth.actionFunc), 0, meth.id);
}
return utils::hook::invoke<unsigned int>(SELECT_VALUE(0x51A4F0, 0x5277C0), p_name, type); // Scr_GetMethod
}
const char* get_replaced_func_pos(const char* pos)
{
if (const auto itr = replaced_funcs.find(pos); itr != replaced_funcs.end())
{
return itr->second;
}
return pos;
}
__declspec(naked) void vm_execute_func_stub()
{
__asm
{
mov eax, dword ptr ds:[func_table + eax * 4]
jmp vm_execute_func_addr
}
}
__declspec(naked) void vm_execute_meth_stub()
{
__asm
{
mov edx, dword ptr ds:[meth_table + ecx * 4]
jmp vm_execute_meth_addr
}
}
__declspec(naked) void vm_execute_stub()
{
__asm
{
pushad
push esi
call get_replaced_func_pos;
mov vm_execute_code_pos, eax
pop esi
popad
mov esi, vm_execute_code_pos
jmp vm_execute_addr
}
}
void scr_print()
{
for (std::size_t i = 0; i < game::native::Scr_GetNumParam(); ++i)
@ -29,13 +156,112 @@ namespace gsc
}
}
void register_function(const std::string& name, const game::native::BuiltinFunction& func)
{
const auto lowered_name = utils::string::to_lower(name);
constexpr auto is_duplicate = [](const std::string s)
{
return [s](const builtin_function& f) { return s == f.name; };
};
if (std::ranges::any_of(custom_functions, is_duplicate(lowered_name)))
{
console::error("Function '%s' is already registered!", name.data());
return;
}
++scr_func_max_id;
custom_functions.push_back({ name, scr_func_max_id, func });
cxt->func_add(name, scr_func_max_id);
}
void register_method(const std::string& name, const game::native::BuiltinMethod& meth)
{
const auto lowered_name = utils::string::to_lower(name);
constexpr auto is_duplicate = [](const std::string s)
{
return [s](const builtin_method& m) { return s == m.name; };
};
if (std::ranges::any_of(custom_methods, is_duplicate(lowered_name)))
{
console::error("Method '%s' already registered!", name.data());
return;
}
++scr_meth_max_id;
custom_methods.push_back({ name, scr_meth_max_id, meth });
cxt->meth_add(name, scr_meth_max_id);
}
const char* get_code_pos(int index)
{
if (static_cast<unsigned int>(index) >= game::native::scr_VmPub->outparamcount)
{
scr_error("Scr_GetCodePos: index is out of range");
return "";
}
const auto* value = &game::native::scr_VmPub->top[-index];
if (value->type != game::native::VAR_FUNCTION)
{
scr_error("Scr_GetCodePos requires a function as parameter");
return "";
}
return value->u.codePosValue;
}
class script_extension final : public module
{
public:
void post_load() override
{
utils::hook(SELECT_VALUE(0x4B4F87, 0x561F27), scr_get_function_stub, HOOK_CALL).install()->quick();
utils::hook(SELECT_VALUE(0x4B4F92, 0x561F32), scr_get_method_stub, HOOK_CALL).install()->quick();
utils::hook(SELECT_VALUE(0x611C5B, 0x56C8EB), vm_execute_func_stub, HOOK_JUMP).install()->quick();
utils::hook::nop(SELECT_VALUE(0x611C5B + 5, 0x56C8EB + 5), 2);
utils::hook(SELECT_VALUE(0x611F4C, 0x56CBDC), vm_execute_meth_stub, HOOK_JUMP).install()->quick();
utils::hook::nop(SELECT_VALUE(0x611F4C + 5, 0x56CBDC + 5), 2);
vm_execute_func_addr = SELECT_VALUE(0x611C62, 0x56C8F2);
vm_execute_meth_addr = SELECT_VALUE(0x611F53, 0x56CBE3);
scr_register_function_hook.create(SELECT_VALUE(0x49E190, 0x561520), scr_register_function_stub);
utils::hook::set<game::native::BuiltinFunction>(SELECT_VALUE(0x92B8DC, 0x8ABDC4), scr_print);
utils::hook::set<game::native::BuiltinFunction>(SELECT_VALUE(0x92B8E8, 0x8ABDD0), scr_print_ln);
vm_execute_addr = SELECT_VALUE(0x610A90, 0x56B720);
utils::hook(SELECT_VALUE(0x611EF1, 0x56CB81), vm_execute_stub, HOOK_JUMP).install()->quick();
scripting::on_shutdown([](int free_scripts) -> void
{
if (free_scripts)
{
replaced_funcs.clear();
}
});
add_gsc_functions();
}
static void add_gsc_functions()
{
register_function("replacefunc", []
{
if (scr_get_type(0) != game::native::VAR_FUNCTION || scr_get_type(1) != game::native::VAR_FUNCTION)
{
scr_error("ReplaceFunc requires two functions pointers");
}
replaced_funcs[get_code_pos(0)] = get_code_pos(1);
});
}
};
}

View File

@ -0,0 +1,12 @@
#pragma once
namespace gsc
{
extern void* func_table[500];
extern void* meth_table[900];
extern std::uint16_t scr_func_max_id;
void register_function(const std::string& name, const game::native::BuiltinFunction& func);
void register_method(const std::string& name, const game::native::BuiltinMethod& meth);
}

View File

@ -16,8 +16,6 @@
namespace gsc
{
std::uint16_t scr_func_max_id = 0x1C7;
namespace
{
utils::memory::allocator script_file_allocator;
@ -69,7 +67,7 @@ namespace gsc
std::vector<std::uint8_t> stack_data;
stack_data.assign(decompressed_stack.begin(), decompressed_stack.end());
return {{script_file->bytecode, static_cast<std::uint32_t>(script_file->bytecodeLen)} , stack_data};
return { { script_file->bytecode, static_cast<std::uint32_t>(script_file->bytecodeLen) } , stack_data };
}
game::native::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name)
@ -100,7 +98,7 @@ namespace gsc
const auto script_file_ptr = static_cast<game::native::ScriptFile*>(script_file_allocator.allocate(sizeof(game::native::ScriptFile)));
script_file_ptr->name = file_name;
const auto compressed_stack = utils::compression::zlib::compress({reinterpret_cast<const char*>(output_script.second.data), output_script.second.size});
const auto compressed_stack = utils::compression::zlib::compress( {reinterpret_cast<const char*>(output_script.second.data), output_script.second.size} );
const auto byte_code_size = output_script.first.size + 1;
script_file_ptr->len = static_cast<int>(output_script.second.size);
@ -269,7 +267,7 @@ namespace gsc
std::vector<std::uint8_t> script_data;
script_data.assign(file_buffer.begin(), file_buffer.end());
return {{}, script_data};
return { {}, script_data };
});
utils::hook::invoke<void>(SELECT_VALUE(0x4B4EE0, 0x561E80));