commit
81c1b8fd99
4
.gitmodules
vendored
4
.gitmodules
vendored
@ -31,3 +31,7 @@
|
||||
[submodule "deps/rapidjson"]
|
||||
path = deps/rapidjson
|
||||
url = https://github.com/Tencent/rapidjson.git
|
||||
[submodule "deps/gsc-tool"]
|
||||
path = deps/gsc-tool
|
||||
url = https://github.com/Joelrau/gsc-tool.git
|
||||
branch = iw7
|
||||
|
1
deps/gsc-tool
vendored
Submodule
1
deps/gsc-tool
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 4761bfbadeba5f4fd04714b837aef93b85977ec2
|
62
deps/premake/gsc-tool.lua
vendored
Normal file
62
deps/premake/gsc-tool.lua
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
gsc_tool = {
|
||||
source = path.join(dependencies.basePath, "gsc-tool")
|
||||
}
|
||||
|
||||
function gsc_tool.import()
|
||||
links {"xsk-gsc-iw7", "xsk-gsc-utils"}
|
||||
gsc_tool.includes()
|
||||
end
|
||||
|
||||
function gsc_tool.includes()
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
function gsc_tool.project()
|
||||
project "xsk-gsc-utils"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
warnings "Off"
|
||||
|
||||
files {
|
||||
path.join(gsc_tool.source, "include/xsk/utils/*.hpp"),
|
||||
path.join(gsc_tool.source, "src/utils/*.cpp")
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "include")
|
||||
}
|
||||
|
||||
zlib.includes()
|
||||
|
||||
project "xsk-gsc-iw7"
|
||||
kind "StaticLib"
|
||||
language "C++"
|
||||
warnings "Off"
|
||||
|
||||
filter "action:vs*"
|
||||
buildoptions "/Zc:__cplusplus"
|
||||
filter {}
|
||||
|
||||
files {
|
||||
path.join(gsc_tool.source, "include/xsk/stdinc.hpp"),
|
||||
|
||||
path.join(gsc_tool.source, "include/xsk/gsc/engine/iw7.hpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/iw7.cpp"),
|
||||
|
||||
path.join(gsc_tool.source, "src/gsc/engine/iw7_code.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/iw7_func.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/iw7_meth.cpp"),
|
||||
path.join(gsc_tool.source, "src/gsc/engine/iw7_token.cpp"), path.join(gsc_tool.source, "src/gsc/*.cpp"),
|
||||
|
||||
path.join(gsc_tool.source, "src/gsc/common/*.cpp"),
|
||||
path.join(gsc_tool.source, "include/xsk/gsc/common/*.hpp")
|
||||
}
|
||||
|
||||
includedirs {
|
||||
path.join(gsc_tool.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
table.insert(dependencies, gsc_tool)
|
@ -322,6 +322,15 @@ links {"common"}
|
||||
|
||||
prebuildcommands {"pushd %{_MAIN_SCRIPT_DIR}", "tools\\premake5 generate-buildinfo", "popd"}
|
||||
|
||||
COMPUTER_NAME = os.getenv('COMPUTERNAME')
|
||||
if COMPUTER_NAME == "JOEL-PC" then
|
||||
debugdir "D:\\Games\\PC\\IW7"
|
||||
debugcommand "D:\\Games\\PC\\IW7\\$(TargetName)$(TargetExt)"
|
||||
postbuildcommands {
|
||||
"copy /y \"$(OutDir)$(TargetName)$(TargetExt)\" \"D:\\Games\\PC\\IW7\\$(TargetName)$(TargetExt)\"",
|
||||
}
|
||||
end
|
||||
|
||||
if _OPTIONS["copy-to"] then
|
||||
postbuildcommands {"copy /y \"$(TargetPath)\" \"" .. _OPTIONS["copy-to"] .. "\""}
|
||||
end
|
||||
|
@ -10,8 +10,12 @@
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include "integrity.hpp"
|
||||
#include "breakpoints.hpp"
|
||||
#include "illegal_instructions.hpp"
|
||||
|
||||
#define PRECOMPUTED_INTEGRITY_CHECKS
|
||||
#define PRECOMPUTED_BREAKPOINTS
|
||||
#define PRECOMPUTED_ILLEGAL_INSTRUCTIONS
|
||||
|
||||
#define ProcessDebugPort 7
|
||||
#define ProcessDebugObjectHandle 30
|
||||
@ -19,85 +23,8 @@
|
||||
|
||||
namespace arxan
|
||||
{
|
||||
namespace
|
||||
namespace integrity
|
||||
{
|
||||
utils::hook::detour nt_close_hook;
|
||||
utils::hook::detour nt_query_information_process_hook;
|
||||
|
||||
HANDLE process_id_to_handle(const DWORD pid)
|
||||
{
|
||||
return reinterpret_cast<HANDLE>(static_cast<DWORD64>(pid));
|
||||
}
|
||||
|
||||
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
|
||||
const PVOID info,
|
||||
const ULONG info_length, const PULONG ret_length)
|
||||
{
|
||||
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.get_original());
|
||||
const auto status = orig(handle, info_class, info, info_length, ret_length);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (info_class == ProcessBasicInformation)
|
||||
{
|
||||
static DWORD explorer_pid = 0;
|
||||
if (!explorer_pid)
|
||||
{
|
||||
auto* const shell_window = GetShellWindow();
|
||||
GetWindowThreadProcessId(shell_window, &explorer_pid);
|
||||
}
|
||||
|
||||
// InheritedFromUniqueProcessId
|
||||
static_cast<PPROCESS_BASIC_INFORMATION>(info)->Reserved3 = PVOID(DWORD64(explorer_pid));
|
||||
}
|
||||
else if (info_class == ProcessDebugObjectHandle)
|
||||
{
|
||||
*static_cast<HANDLE*>(info) = nullptr;
|
||||
|
||||
return 0xC0000353;
|
||||
}
|
||||
else if (info_class == ProcessDebugPort)
|
||||
{
|
||||
*static_cast<HANDLE*>(info) = nullptr;
|
||||
}
|
||||
else if (info_class == ProcessDebugFlags)
|
||||
{
|
||||
*static_cast<ULONG*>(info) = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
|
||||
{
|
||||
char info[16];
|
||||
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0 && size_t(handle) != 0x12345)
|
||||
{
|
||||
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
|
||||
return orig(handle);
|
||||
}
|
||||
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void hide_being_debugged()
|
||||
{
|
||||
auto* const peb = PPEB(__readgsqword(0x60));
|
||||
peb->BeingDebugged = false;
|
||||
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70;
|
||||
}
|
||||
|
||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
|
||||
{
|
||||
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
const std::vector<std::pair<uint8_t*, size_t>>& get_text_sections()
|
||||
{
|
||||
static const std::vector<std::pair<uint8_t*, size_t>> text = []
|
||||
@ -179,7 +106,7 @@ namespace arxan
|
||||
uint32_t adjust_integrity_checksum(const uint64_t return_address, uint8_t* stack_frame,
|
||||
const uint32_t current_checksum)
|
||||
{
|
||||
[[maybe_unused]]const auto handler_address = return_address - 5;
|
||||
[[maybe_unused]] const auto handler_address = return_address - 5;
|
||||
const auto* context = search_handler_context(stack_frame, current_checksum);
|
||||
|
||||
if (!context)
|
||||
@ -320,9 +247,6 @@ namespace arxan
|
||||
assert(game::base_address == 0x140000000);
|
||||
search_and_patch_integrity_checks_precomputed();
|
||||
#else
|
||||
// There seem to be 670 results.
|
||||
// Searching them is quite slow.
|
||||
// Maybe precomputing that might be better?
|
||||
const auto intact_results = "89 04 8A 83 45 ? FF"_sig;
|
||||
const auto split_results = "89 04 8A E9"_sig;
|
||||
|
||||
@ -338,14 +262,405 @@ namespace arxan
|
||||
#endif
|
||||
}
|
||||
}
|
||||
using namespace integrity;
|
||||
|
||||
namespace anti_debug
|
||||
{
|
||||
utils::hook::detour nt_close_hook;
|
||||
utils::hook::detour nt_query_information_process_hook;
|
||||
|
||||
NTSTATUS WINAPI nt_query_information_process_stub(const HANDLE handle, const PROCESSINFOCLASS info_class,
|
||||
const PVOID info,
|
||||
const ULONG info_length, const PULONG ret_length)
|
||||
{
|
||||
auto* orig = static_cast<decltype(NtQueryInformationProcess)*>(nt_query_information_process_hook.get_original());
|
||||
auto status = orig(handle, info_class, info, info_length, ret_length);
|
||||
|
||||
if (NT_SUCCESS(status))
|
||||
{
|
||||
if (info_class == ProcessDebugObjectHandle)
|
||||
{
|
||||
*static_cast<HANDLE*>(info) = nullptr;
|
||||
return static_cast<LONG>(0xC0000353);
|
||||
}
|
||||
else if (info_class == ProcessDebugPort)
|
||||
{
|
||||
*static_cast<HANDLE*>(info) = nullptr;
|
||||
}
|
||||
else if (info_class == ProcessDebugFlags)
|
||||
{
|
||||
*static_cast<ULONG*>(info) = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NTSTATUS NTAPI nt_close_stub(const HANDLE handle)
|
||||
{
|
||||
char info[16];
|
||||
if (NtQueryObject(handle, OBJECT_INFORMATION_CLASS(4), &info, 2, nullptr) >= 0 && size_t(handle) != 0x12345)
|
||||
{
|
||||
auto* orig = static_cast<decltype(NtClose)*>(nt_close_hook.get_original());
|
||||
return orig(handle);
|
||||
}
|
||||
|
||||
return STATUS_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
void hide_being_debugged()
|
||||
{
|
||||
auto* const peb = PPEB(__readgsqword(0x60));
|
||||
peb->BeingDebugged = false;
|
||||
*reinterpret_cast<PDWORD>(LPSTR(peb) + 0xBC) &= ~0x70; // NtGlobalFlag
|
||||
}
|
||||
|
||||
void remove_hardware_breakpoints()
|
||||
{
|
||||
CONTEXT context;
|
||||
ZeroMemory(&context, sizeof(context));
|
||||
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
|
||||
auto* const thread = GetCurrentThread();
|
||||
GetThreadContext(thread, &context);
|
||||
|
||||
context.Dr0 = 0;
|
||||
context.Dr1 = 0;
|
||||
context.Dr2 = 0;
|
||||
context.Dr3 = 0;
|
||||
context.Dr6 = 0;
|
||||
context.Dr7 = 0;
|
||||
|
||||
SetThreadContext(thread, &context);
|
||||
}
|
||||
|
||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS info)
|
||||
{
|
||||
if (info->ExceptionRecord->ExceptionCode == STATUS_INVALID_HANDLE)
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
BOOL WINAPI set_thread_context_stub(const HANDLE thread, CONTEXT* context)
|
||||
{
|
||||
if (context->ContextFlags == CONTEXT_DEBUG_REGISTERS)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return SetThreadContext(thread, context);
|
||||
}
|
||||
|
||||
enum dbg_funcs_e
|
||||
{
|
||||
DbgBreakPoint,
|
||||
DbgUserBreakPoint,
|
||||
DbgUiConnectToDbg,
|
||||
DbgUiContinue,
|
||||
DbgUiConvertStateChangeStructure,
|
||||
DbgUiDebugActiveProcess,
|
||||
DbgUiGetThreadDebugObject,
|
||||
DbgUiIssueRemoteBreakin,
|
||||
DbgUiRemoteBreakin,
|
||||
DbgUiSetThreadDebugObject,
|
||||
DbgUiStopDebugging,
|
||||
DbgUiWaitStateChange,
|
||||
DbgPrintReturnControlC,
|
||||
DbgPrompt,
|
||||
DBG_FUNCS_COUNT,
|
||||
};
|
||||
const char* dbg_funcs_names[] =
|
||||
{
|
||||
"DbgBreakPoint",
|
||||
"DbgUserBreakPoint",
|
||||
"DbgUiConnectToDbg",
|
||||
"DbgUiContinue",
|
||||
"DbgUiConvertStateChangeStructure",
|
||||
"DbgUiDebugActiveProcess",
|
||||
"DbgUiGetThreadDebugObject",
|
||||
"DbgUiIssueRemoteBreakin",
|
||||
"DbgUiRemoteBreakin",
|
||||
"DbgUiSetThreadDebugObject",
|
||||
"DbgUiStopDebugging",
|
||||
"DbgUiWaitStateChange",
|
||||
"DbgPrintReturnControlC",
|
||||
"DbgPrompt",
|
||||
};
|
||||
struct dbg_func_bytes_s
|
||||
{
|
||||
std::uint8_t buffer[15];
|
||||
};
|
||||
dbg_func_bytes_s dbg_func_bytes[DBG_FUNCS_COUNT];
|
||||
void* dbg_func_procs[DBG_FUNCS_COUNT]{};
|
||||
|
||||
void store_debug_functions()
|
||||
{
|
||||
const utils::nt::library ntdll("ntdll.dll");
|
||||
|
||||
for (auto i = 0; i < DBG_FUNCS_COUNT; i++)
|
||||
{
|
||||
dbg_func_procs[i] = ntdll.get_proc<void*>(dbg_funcs_names[i]);
|
||||
memcpy(dbg_func_bytes[i].buffer, dbg_func_procs[i], sizeof(dbg_func_bytes[i].buffer));
|
||||
}
|
||||
}
|
||||
|
||||
void restore_debug_functions()
|
||||
{
|
||||
for (auto i = 0; i < DBG_FUNCS_COUNT; i++)
|
||||
{
|
||||
utils::hook::copy(dbg_func_procs[i], dbg_func_bytes[i].buffer, sizeof(dbg_func_bytes[i].buffer));
|
||||
}
|
||||
}
|
||||
|
||||
namespace exceptions
|
||||
{
|
||||
std::unordered_map<PVOID, void*> handle_handler;
|
||||
|
||||
void fake_exception(void* address, _CONTEXT* fake_context, DWORD exception)
|
||||
{
|
||||
_EXCEPTION_POINTERS fake_info{};
|
||||
_EXCEPTION_RECORD fake_record{};
|
||||
fake_info.ExceptionRecord = &fake_record;
|
||||
fake_info.ContextRecord = fake_context;
|
||||
|
||||
fake_record.ExceptionAddress = reinterpret_cast<void*>(reinterpret_cast<std::uint64_t>(address) + 3);
|
||||
fake_record.ExceptionCode = exception;
|
||||
|
||||
for (auto handler : handle_handler)
|
||||
{
|
||||
if (handler.second)
|
||||
{
|
||||
auto result = utils::hook::invoke<LONG>(handler.second, &fake_info);
|
||||
if (result)
|
||||
{
|
||||
memset(fake_context, 0, sizeof(_CONTEXT));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void patch_int2d_trap(void* address)
|
||||
{
|
||||
const auto game_address = reinterpret_cast<std::uint64_t>(address);
|
||||
|
||||
const auto jump_target = utils::hook::extract<void*>(reinterpret_cast<void*>(game_address + 3));
|
||||
|
||||
_CONTEXT* fake_context = new _CONTEXT{};
|
||||
const auto stub = utils::hook::assemble([address, jump_target, fake_context](utils::hook::assembler& a)
|
||||
{
|
||||
a.push(rcx);
|
||||
a.mov(rcx, fake_context);
|
||||
a.call_aligned(RtlCaptureContext);
|
||||
a.pop(rcx);
|
||||
|
||||
a.pushad64();
|
||||
a.mov(rcx, address);
|
||||
a.mov(rdx, fake_context);
|
||||
a.mov(r8, EXCEPTION_BREAKPOINT);
|
||||
a.call_aligned(fake_exception);
|
||||
a.popad64();
|
||||
|
||||
a.jmp(jump_target);
|
||||
});
|
||||
|
||||
utils::hook::nop(game_address, 7);
|
||||
utils::hook::jump(game_address, stub, false);
|
||||
}
|
||||
|
||||
#ifdef PRECOMPUTED_BREAKPOINTS
|
||||
void patch_breakpoints_precomputed()
|
||||
{
|
||||
for (const auto i : int2d_breakpoint_addresses)
|
||||
{
|
||||
patch_int2d_trap(reinterpret_cast<void*>(i));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void patch_breakpoints()
|
||||
{
|
||||
static bool once = false;
|
||||
if (once)
|
||||
{
|
||||
return;
|
||||
}
|
||||
once = true;
|
||||
|
||||
#ifdef PRECOMPUTED_BREAKPOINTS
|
||||
assert(game::base_address == 0x140000000);
|
||||
patch_breakpoints_precomputed();
|
||||
#else
|
||||
const auto int2d_results = utils::hook::signature("CD 2D E9 ? ? ? ?", game_module::get_game_module()).process();
|
||||
for (auto* i : int2d_results)
|
||||
{
|
||||
patch_int2d_trap(i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void patch_illegal_instruction_intact(void* address)
|
||||
{
|
||||
const auto game_address = reinterpret_cast<std::uint64_t>(address);
|
||||
|
||||
const auto jump_target = game_address + 6;
|
||||
|
||||
const auto a1 = *reinterpret_cast<std::uint8_t*>(game_address + 3);
|
||||
|
||||
_CONTEXT* fake_context = new _CONTEXT{};
|
||||
const auto stub = utils::hook::assemble([address, jump_target, fake_context, a1](utils::hook::assembler& a)
|
||||
{
|
||||
a.lea(rax, ptr(rbp, a1));
|
||||
|
||||
a.push(rcx);
|
||||
a.mov(rcx, fake_context);
|
||||
a.call_aligned(RtlCaptureContext);
|
||||
a.pop(rcx);
|
||||
|
||||
a.pushad64();
|
||||
a.mov(rcx, address);
|
||||
a.mov(rdx, fake_context);
|
||||
a.mov(r8, EXCEPTION_ILLEGAL_INSTRUCTION);
|
||||
a.call_aligned(fake_exception);
|
||||
a.popad64();
|
||||
|
||||
a.jmp(jump_target);
|
||||
});
|
||||
|
||||
utils::hook::nop(game_address, 6);
|
||||
utils::hook::jump(game_address, stub, false);
|
||||
}
|
||||
|
||||
void patch_illegal_instruction_split(void* address)
|
||||
{
|
||||
const auto game_address = reinterpret_cast<std::uint64_t>(address);
|
||||
|
||||
const auto jump_target = utils::hook::extract<void*>(reinterpret_cast<void*>(game_address + 5));
|
||||
|
||||
if (*reinterpret_cast<std::uint16_t*>(jump_target) != 0x0B0F) // illegal instruction
|
||||
{
|
||||
return; // false positive
|
||||
}
|
||||
|
||||
const auto a1 = *reinterpret_cast<std::uint8_t*>(game_address + 3);
|
||||
|
||||
_CONTEXT* fake_context = new _CONTEXT{};
|
||||
const auto stub = utils::hook::assemble([address, jump_target, fake_context, a1](utils::hook::assembler& a)
|
||||
{
|
||||
a.lea(rax, ptr(rbp, a1));
|
||||
|
||||
a.push(rcx);
|
||||
a.mov(rcx, fake_context);
|
||||
a.call_aligned(RtlCaptureContext);
|
||||
a.pop(rcx);
|
||||
|
||||
a.pushad64();
|
||||
a.mov(rcx, address);
|
||||
a.mov(rdx, fake_context);
|
||||
a.mov(r8, EXCEPTION_ILLEGAL_INSTRUCTION);
|
||||
a.call_aligned(fake_exception);
|
||||
a.popad64();
|
||||
|
||||
a.jmp(jump_target);
|
||||
});
|
||||
|
||||
utils::hook::nop(game_address, 9);
|
||||
utils::hook::nop(jump_target, 2);
|
||||
utils::hook::jump(game_address, stub, false);
|
||||
}
|
||||
|
||||
#ifdef PRECOMPUTED_ILLEGAL_INSTRUCTIONS
|
||||
void patch_illegal_instructions_precomputed()
|
||||
{
|
||||
for (const auto i : illegal_instructions_intact)
|
||||
{
|
||||
patch_illegal_instruction_intact(reinterpret_cast<void*>(i));
|
||||
}
|
||||
|
||||
for (const auto i : illegal_instructions_split)
|
||||
{
|
||||
patch_illegal_instruction_split(reinterpret_cast<void*>(i));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void patch_illegal_instructions()
|
||||
{
|
||||
static bool once = false;
|
||||
if (once)
|
||||
{
|
||||
return;
|
||||
}
|
||||
once = true;
|
||||
|
||||
#ifdef PRECOMPUTED_ILLEGAL_INSTRUCTIONS
|
||||
assert(game::base_address == 0x140000000);
|
||||
patch_illegal_instructions_precomputed();
|
||||
#else
|
||||
const auto intact_results = utils::hook::signature("48 8D 45 ? 0F 0B", game_module::get_game_module()).process();
|
||||
for (auto* i : intact_results)
|
||||
{
|
||||
patch_illegal_instruction_intact(i);
|
||||
}
|
||||
|
||||
const auto split_results = utils::hook::signature("48 8D 45 ? E9 ? ? ?", game_module::get_game_module()).process();
|
||||
for (auto* i : split_results)
|
||||
{
|
||||
patch_illegal_instruction_split(i);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
PVOID WINAPI add_vectored_exception_handler_stub(ULONG first, PVECTORED_EXCEPTION_HANDLER handler)
|
||||
{
|
||||
exceptions::patch_breakpoints();
|
||||
exceptions::patch_illegal_instructions();
|
||||
|
||||
auto handle = AddVectoredExceptionHandler(first, handler);
|
||||
handle_handler[handle] = handler;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
ULONG WINAPI remove_vectored_exception_handler_stub(PVOID handle)
|
||||
{
|
||||
handle_handler[handle] = nullptr;
|
||||
return RemoveVectoredExceptionHandler(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
using namespace anti_debug;
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (function == "SetThreadContext")
|
||||
{
|
||||
return set_thread_context_stub;
|
||||
}
|
||||
else if (function == "AddVectoredExceptionHandler")
|
||||
{
|
||||
return exceptions::add_vectored_exception_handler_stub;
|
||||
}
|
||||
else if (function == "RemoveVectoredExceptionHandler")
|
||||
{
|
||||
return exceptions::remove_vectored_exception_handler_stub;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
remove_hardware_breakpoints();
|
||||
hide_being_debugged();
|
||||
scheduler::loop(hide_being_debugged, scheduler::pipeline::async);
|
||||
store_debug_functions();
|
||||
|
||||
const utils::nt::library ntdll("ntdll.dll");
|
||||
nt_close_hook.create(ntdll.get_proc<void*>("NtClose"), nt_close_stub);
|
||||
@ -359,7 +674,9 @@ namespace arxan
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
remove_hardware_breakpoints();
|
||||
search_and_patch_integrity_checks();
|
||||
restore_debug_functions();
|
||||
}
|
||||
|
||||
component_priority priority() override
|
||||
|
594
src/client/component/arxan/breakpoints.hpp
Normal file
594
src/client/component/arxan/breakpoints.hpp
Normal file
@ -0,0 +1,594 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr uint64_t int2d_breakpoint_addresses[] =
|
||||
{
|
||||
0x14004E569,
|
||||
0x140069F29,
|
||||
0x1400EFEA9,
|
||||
0x1400F0FC9,
|
||||
0x1403CE6D9,
|
||||
0x1404ECC09,
|
||||
0x1404F2539,
|
||||
0x140524519,
|
||||
0x140A50D69,
|
||||
0x140BFA4D9,
|
||||
0x140C1E049,
|
||||
0x140C29999,
|
||||
0x140C92BBE,
|
||||
0x140CC0EF9,
|
||||
0x140CEFFA9,
|
||||
0x140CF0D79,
|
||||
0x140CF241F,
|
||||
0x140CF6A91,
|
||||
0x140DB85A9,
|
||||
0x140F138C9,
|
||||
0x140F238E9,
|
||||
0x140F24A89,
|
||||
0x140F31AA9,
|
||||
0x140F3D1A9,
|
||||
0x140F58709,
|
||||
0x1411049E9,
|
||||
0x14117ADD0,
|
||||
0x141216534,
|
||||
0x14123991E,
|
||||
0x14123B17E,
|
||||
0x14124DA95,
|
||||
0x14125842F,
|
||||
0x1412632D5,
|
||||
0x141267CBF,
|
||||
0x1412697E4,
|
||||
0x141286872,
|
||||
0x141293C7F,
|
||||
0x1412990AB,
|
||||
0x14129A49E,
|
||||
0x141303D54,
|
||||
0x14131B345,
|
||||
0x1413204BE,
|
||||
0x141320571,
|
||||
0x14132211F,
|
||||
0x141379479,
|
||||
0x141398E09,
|
||||
0x1413B20E9,
|
||||
0x14B2E246A,
|
||||
0x14B2E5CA6,
|
||||
0x14B2E6C7D,
|
||||
0x14B2EAF29,
|
||||
0x14B2F7DA3,
|
||||
0x14B2F9D2E,
|
||||
0x14B34FE91,
|
||||
0x14B361E9D,
|
||||
0x14B36397C,
|
||||
0x14B36A32D,
|
||||
0x14B378CA7,
|
||||
0x14B37B194,
|
||||
0x14B382645,
|
||||
0x14B3888A3,
|
||||
0x14B39618C,
|
||||
0x14B39CD0C,
|
||||
0x14B3A1ECE,
|
||||
0x14B3A994B,
|
||||
0x14B3AC9FF,
|
||||
0x14B3AE79D,
|
||||
0x14B3AF313,
|
||||
0x14B3B16CF,
|
||||
0x14B3B65C1,
|
||||
0x14B3DA767,
|
||||
0x14B3DC6B1,
|
||||
0x14B3DD7E8,
|
||||
0x14B3E340F,
|
||||
0x14B3E8680,
|
||||
0x14B3ED8F6,
|
||||
0x14B3F590B,
|
||||
0x14B3F5A29,
|
||||
0x14B3F68EB,
|
||||
0x14B3FCB09,
|
||||
0x14B400C56,
|
||||
0x14B412247,
|
||||
0x14B4170F2,
|
||||
0x14B4222E3,
|
||||
0x14B426354,
|
||||
0x14B434EB1,
|
||||
0x14B438BC3,
|
||||
0x14B439E43,
|
||||
0x14B43BABC,
|
||||
0x14B443781,
|
||||
0x14B452BE4,
|
||||
0x14B455B40,
|
||||
0x14B45AAF3,
|
||||
0x14B45CFC6,
|
||||
0x14B46708E,
|
||||
0x14B46B471,
|
||||
0x14B46DFE6,
|
||||
0x14B47218E,
|
||||
0x14B47502B,
|
||||
0x14B47FF6B,
|
||||
0x14B489B5D,
|
||||
0x14B4901F5,
|
||||
0x14B493ED2,
|
||||
0x14B497074,
|
||||
0x14B499ADC,
|
||||
0x14B49BB2A,
|
||||
0x14B49F189,
|
||||
0x14B49F2AE,
|
||||
0x14B49FBC0,
|
||||
0x14B4A5193,
|
||||
0x14B4A9530,
|
||||
0x14B4AAA35,
|
||||
0x14B4AABC4,
|
||||
0x14B4AD6E8,
|
||||
0x14B4B3B31,
|
||||
0x14B4B7B3D,
|
||||
0x14B4B845B,
|
||||
0x14B4BB7E6,
|
||||
0x14B4BCCD6,
|
||||
0x14B4C1AF6,
|
||||
0x14B4C4AEA,
|
||||
0x14B4C5B91,
|
||||
0x14B4CCA65,
|
||||
0x14B4D0F9C,
|
||||
0x14B4D44D0,
|
||||
0x14B4D7423,
|
||||
0x14B5262AD,
|
||||
0x14B52655E,
|
||||
0x14B528DF2,
|
||||
0x14B52BC8B,
|
||||
0x14B53142D,
|
||||
0x14B538901,
|
||||
0x14B54520F,
|
||||
0x14B5462B2,
|
||||
0x14B5466A6,
|
||||
0x14B54CF35,
|
||||
0x14B54D44B,
|
||||
0x14B550353,
|
||||
0x14B555F0C,
|
||||
0x14B55678C,
|
||||
0x14B55F494,
|
||||
0x14B563618,
|
||||
0x14B564B29,
|
||||
0x14B565CB9,
|
||||
0x14B566DF3,
|
||||
0x14B58085C,
|
||||
0x14B58FA48,
|
||||
0x14B59229B,
|
||||
0x14B5944E4,
|
||||
0x14B5A2E29,
|
||||
0x14B5A3269,
|
||||
0x14B5B05BF,
|
||||
0x14B5B3D1B,
|
||||
0x14B5BE2D9,
|
||||
0x14B5C0A85,
|
||||
0x14B5C32CF,
|
||||
0x14B5C50F2,
|
||||
0x14B5C67CD,
|
||||
0x14B5C92EB,
|
||||
0x14B5C9717,
|
||||
0x14B5CE3A6,
|
||||
0x14B5CEEE3,
|
||||
0x14B626123,
|
||||
0x14B626553,
|
||||
0x14B633A3E,
|
||||
0x14B63DF91,
|
||||
0x14B648C4A,
|
||||
0x14B64D55C,
|
||||
0x14B64E48A,
|
||||
0x14B6539B4,
|
||||
0x14B666AC3,
|
||||
0x14B66AD46,
|
||||
0x14B66C669,
|
||||
0x14B66CE05,
|
||||
0x14B672AD5,
|
||||
0x14B67EDC1,
|
||||
0x14B68048C,
|
||||
0x14B681119,
|
||||
0x14B683347,
|
||||
0x14B68691B,
|
||||
0x14B6888BA,
|
||||
0x14B691973,
|
||||
0x14B6945F4,
|
||||
0x14B7348B6,
|
||||
0x14B73666A,
|
||||
0x14B7398B2,
|
||||
0x14B73E42F,
|
||||
0x14B741BA5,
|
||||
0x14B742DE8,
|
||||
0x14B745CFB,
|
||||
0x14B74827A,
|
||||
0x14B74A230,
|
||||
0x14B75124E,
|
||||
0x14B751691,
|
||||
0x14B760F53,
|
||||
0x14B77032D,
|
||||
0x14B7706AE,
|
||||
0x14B778A13,
|
||||
0x14B7794AF,
|
||||
0x14B77BBFB,
|
||||
0x14B785745,
|
||||
0x14B788D32,
|
||||
0x14B78DE47,
|
||||
0x14B7910C9,
|
||||
0x14B795294,
|
||||
0x14B7A0DAB,
|
||||
0x14B7A3B42,
|
||||
0x14B7AC060,
|
||||
0x14B7B5DA8,
|
||||
0x14B7B64E5,
|
||||
0x14B7BC9BF,
|
||||
0x14B7BDB7E,
|
||||
0x14B7BE77E,
|
||||
0x14B7C5313,
|
||||
0x14B7C8071,
|
||||
0x14B7CA6E4,
|
||||
0x14B7D3385,
|
||||
0x14B7D43DD,
|
||||
0x14B7D4C57,
|
||||
0x14B7DD2AF,
|
||||
0x14B7DE8C4,
|
||||
0x14B7DED66,
|
||||
0x14B7EE457,
|
||||
0x14B7F5322,
|
||||
0x14B7F6837,
|
||||
0x14B7FDD66,
|
||||
0x14B7FDFEE,
|
||||
0x14B805299,
|
||||
0x14B8052A0,
|
||||
0x14B807827,
|
||||
0x14B80A30B,
|
||||
0x14B810D85,
|
||||
0x14B81282F,
|
||||
0x14B818622,
|
||||
0x14B81C166,
|
||||
0x14B820555,
|
||||
0x14B8288BE,
|
||||
0x14B86E751,
|
||||
0x14B8760A6,
|
||||
0x14B87D6FD,
|
||||
0x14B8857AA,
|
||||
0x14B889598,
|
||||
0x14B894D05,
|
||||
0x14B895BB9,
|
||||
0x14B895BDA,
|
||||
0x14B8967F2,
|
||||
0x14B8E5641,
|
||||
0x14B8E61D8,
|
||||
0x14B8E6D08,
|
||||
0x14B8E9516,
|
||||
0x14B8E9784,
|
||||
0x14B900442,
|
||||
0x14B903DA2,
|
||||
0x14B9518C3,
|
||||
0x14B959CA1,
|
||||
0x14B95DE5B,
|
||||
0x14B9609C3,
|
||||
0x14B963A1C,
|
||||
0x14B9B86AE,
|
||||
0x14B9C0892,
|
||||
0x14B9C94CA,
|
||||
0x14B9CAAB3,
|
||||
0x14B9CD2F7,
|
||||
0x14B9CE417,
|
||||
0x14B9CFD20,
|
||||
0x14B9DD84E,
|
||||
0x14B9E31E7,
|
||||
0x14B9E57B1,
|
||||
0x14B9F1CB9,
|
||||
0x14B9FFF1C,
|
||||
0x14BA001F0,
|
||||
0x14BA037EA,
|
||||
0x14BA0787E,
|
||||
0x14BA08886,
|
||||
0x14BA10AEA,
|
||||
0x14BA18A56,
|
||||
0x14BA2368D,
|
||||
0x14BA2454B,
|
||||
0x14BA27998,
|
||||
0x14BA281CE,
|
||||
0x14BA2A34D,
|
||||
0x14BA2A5FA,
|
||||
0x14BA394BE,
|
||||
0x14BA402BC,
|
||||
0x14BA46451,
|
||||
0x14BA4F657,
|
||||
0x14BA50C3C,
|
||||
0x14BA57A6A,
|
||||
0x14BA5D5A0,
|
||||
0x14BA6A7B2,
|
||||
0x14BA76FA1,
|
||||
0x14BA77D4E,
|
||||
0x14BA7AA42,
|
||||
0x14BA7AA8A,
|
||||
0x14BA8640B,
|
||||
0x14BA8E903,
|
||||
0x14BA97CE7,
|
||||
0x14BA98908,
|
||||
0x14BAA1B64,
|
||||
0x14BAA3F78,
|
||||
0x14BAA4F8A,
|
||||
0x14BAA7DC2,
|
||||
0x14BAB299E,
|
||||
0x14BAB3DCD,
|
||||
0x14BAC35D6,
|
||||
0x14BACBEB4,
|
||||
0x14BACDE69,
|
||||
0x14BAD7967,
|
||||
0x14BADA669,
|
||||
0x14BAF6257,
|
||||
0x14BAFA589,
|
||||
0x14BAFB6BF,
|
||||
0x14BAFF6C5,
|
||||
0x14BB007C0,
|
||||
0x14BB039F2,
|
||||
0x14BB04882,
|
||||
0x14BB09346,
|
||||
0x14BB15868,
|
||||
0x14BB1CA96,
|
||||
0x14BB1E48A,
|
||||
0x14BB1FC5A,
|
||||
0x14BB21979,
|
||||
0x14BB21BDE,
|
||||
0x14BB242AF,
|
||||
0x14BB24927,
|
||||
0x14BB2C7B0,
|
||||
0x14BB30EF4,
|
||||
0x14BB37799,
|
||||
0x14BB3AA05,
|
||||
0x14BB423B2,
|
||||
0x14BB42F13,
|
||||
0x14BB44F93,
|
||||
0x14BB45129,
|
||||
0x14BB48C26,
|
||||
0x14BB4BD8D,
|
||||
0x14BB4C498,
|
||||
0x14BB4D00D,
|
||||
0x14BB4FC00,
|
||||
0x14BB50E8D,
|
||||
0x14BB57FFB,
|
||||
0x14BB60E63,
|
||||
0x14BB64D83,
|
||||
0x14BB67396,
|
||||
0x14BB69F17,
|
||||
0x14BB6ACCF,
|
||||
0x14BB70B4D,
|
||||
0x14BB714FE,
|
||||
0x14BB7859D,
|
||||
0x14BB82EFF,
|
||||
0x14BB86019,
|
||||
0x14BB88749,
|
||||
0x14BB89919,
|
||||
0x14BB8D7CF,
|
||||
0x14BC0B6BF,
|
||||
0x14BC0D671,
|
||||
0x14BC34710,
|
||||
0x14BC35765,
|
||||
0x14BC38700,
|
||||
0x14BC3941F,
|
||||
0x14BC3A326,
|
||||
0x14BC3B873,
|
||||
0x14BC3CD6F,
|
||||
0x14BC3CE22,
|
||||
0x14BC3F705,
|
||||
0x14BC4C130,
|
||||
0x14BC4CDE6,
|
||||
0x14BC54F57,
|
||||
0x14BC56C46,
|
||||
0x14BC5CB4A,
|
||||
0x14BC656DB,
|
||||
0x14BC657BA,
|
||||
0x14BC68F18,
|
||||
0x14BCB6BFA,
|
||||
0x14BCBC601,
|
||||
0x14BCBCBCA,
|
||||
0x14BCBD596,
|
||||
0x14BCC86C1,
|
||||
0x14BCCC17D,
|
||||
0x14BCD2059,
|
||||
0x14BCD6A36,
|
||||
0x14BCD9291,
|
||||
0x14BCDDFD8,
|
||||
0x14BCE5FC2,
|
||||
0x14BCE7E55,
|
||||
0x14BCE8BE4,
|
||||
0x14BCE9ACF,
|
||||
0x14BCF105A,
|
||||
0x14BCF12D1,
|
||||
0x14BCFA39E,
|
||||
0x14BCFAEB5,
|
||||
0x14BCFB339,
|
||||
0x14BCFB394,
|
||||
0x14BD04A53,
|
||||
0x14BD0C09B,
|
||||
0x14BD0F968,
|
||||
0x14BD7184A,
|
||||
0x14BDB8CD4,
|
||||
0x14BDBB690,
|
||||
0x14BDBC17B,
|
||||
0x14BDC1732,
|
||||
0x14BDC27BF,
|
||||
0x14BDC3213,
|
||||
0x14BDC3A6A,
|
||||
0x14BDD1829,
|
||||
0x14BDD3732,
|
||||
0x14BDD9F03,
|
||||
0x14BDDA3E0,
|
||||
0x14BDDD6C6,
|
||||
0x14BDE6A8D,
|
||||
0x14BDE7DD3,
|
||||
0x14BDE92BA,
|
||||
0x14BDEA25C,
|
||||
0x14BDF0034,
|
||||
0x14BDFAEED,
|
||||
0x14BDFC26C,
|
||||
0x14BE00996,
|
||||
0x14BE00F6C,
|
||||
0x14BE02A7C,
|
||||
0x14BE083B2,
|
||||
0x14BE11768,
|
||||
0x14BE18E54,
|
||||
0x14BE1F482,
|
||||
0x14BE21D97,
|
||||
0x14BE3098D,
|
||||
0x14BE5EB08,
|
||||
0x14BE64E8C,
|
||||
0x14BE65AF8,
|
||||
0x14BE68B8D,
|
||||
0x14BE6EB30,
|
||||
0x14BE7045A,
|
||||
0x14BE7433D,
|
||||
0x14BE7AF38,
|
||||
0x14BE7F377,
|
||||
0x14BE89D93,
|
||||
0x14BE9BC71,
|
||||
0x14BE9E2B4,
|
||||
0x14BEA5374,
|
||||
0x14BEA6F65,
|
||||
0x14BEAE8B0,
|
||||
0x14BEB8C2A,
|
||||
0x14BEB9450,
|
||||
0x14BEC31BD,
|
||||
0x14BEC58BE,
|
||||
0x14BECAC7E,
|
||||
0x14BED17B6,
|
||||
0x14BED1BFB,
|
||||
0x14BED54A0,
|
||||
0x14BEDBC7B,
|
||||
0x14BEE5679,
|
||||
0x14BEE9824,
|
||||
0x14BEEAA42,
|
||||
0x14BEEACC5,
|
||||
0x14BEEC39B,
|
||||
0x14BEEC3E0,
|
||||
0x14BEF214D,
|
||||
0x14BEF5CEC,
|
||||
0x14BF000AB,
|
||||
0x14BF08BE3,
|
||||
0x14BF19973,
|
||||
0x14BF19BA9,
|
||||
0x14BF2A59B,
|
||||
0x14BF2F52A,
|
||||
0x14BF30011,
|
||||
0x14BF32866,
|
||||
0x14BF35803,
|
||||
0x14BF39362,
|
||||
0x14BF3F92F,
|
||||
0x14BF438DD,
|
||||
0x14BF43DB9,
|
||||
0x14BF49549,
|
||||
0x14BF4BB7C,
|
||||
0x14BF4C8A8,
|
||||
0x14BF5287A,
|
||||
0x14BF5A0A4,
|
||||
0x14BF5B25B,
|
||||
0x14BF5EF0E,
|
||||
0x14BF73CDB,
|
||||
0x14BF784AA,
|
||||
0x14BF7C581,
|
||||
0x14BF7DB1A,
|
||||
0x14BF82743,
|
||||
0x14BF84963,
|
||||
0x14BF8B19D,
|
||||
0x14BF8D2CF,
|
||||
0x14BF91994,
|
||||
0x14BF950CE,
|
||||
0x14BFA5FA8,
|
||||
0x14BFA68E6,
|
||||
0x14BFB30E3,
|
||||
0x14BFB4F69,
|
||||
0x14BFB553D,
|
||||
0x14BFB840E,
|
||||
0x14BFB9D7E,
|
||||
0x14BFBEB33,
|
||||
0x14BFC5E0B,
|
||||
0x14BFC6017,
|
||||
0x14BFC70DE,
|
||||
0x14BFC8FE9,
|
||||
0x14BFD4EDD,
|
||||
0x14BFD8230,
|
||||
0x14BFDCEE5,
|
||||
0x14BFDE795,
|
||||
0x14BFDEB06,
|
||||
0x14BFDF4FE,
|
||||
0x14C006DA8,
|
||||
0x14C00990A,
|
||||
0x14C016FA5,
|
||||
0x14C01980C,
|
||||
0x14C0698CB,
|
||||
0x14C071FFA,
|
||||
0x14C078EC8,
|
||||
0x14C07BBC7,
|
||||
0x14C07D3EB,
|
||||
0x14C07E695,
|
||||
0x14C081960,
|
||||
0x14C089ED2,
|
||||
0x14C09F709,
|
||||
0x14C0A80E7,
|
||||
0x14C0AA53F,
|
||||
0x14C0AB4AC,
|
||||
0x14C0AE170,
|
||||
0x14C0AEB55,
|
||||
0x14C0B0093,
|
||||
0x14C0B09AC,
|
||||
0x14C0BB106,
|
||||
0x14C0BE1CC,
|
||||
0x14C0C5EE4,
|
||||
0x14C0C63E8,
|
||||
0x14C0D2894,
|
||||
0x14C0D85DD,
|
||||
0x14C0D926E,
|
||||
0x14C0E82C0,
|
||||
0x14C0EDDD2,
|
||||
0x14C0F7CB3,
|
||||
0x14C0F934E,
|
||||
0x14C0FA838,
|
||||
0x14C0FAF00,
|
||||
0x14C0FD876,
|
||||
0x14C101924,
|
||||
0x14C104713,
|
||||
0x14C1051C2,
|
||||
0x14C1074D9,
|
||||
0x14C10863A,
|
||||
0x14C10A51B,
|
||||
0x14C11092F,
|
||||
0x14C170C7A,
|
||||
0x14C1737F8,
|
||||
0x14C17A37B,
|
||||
0x14C18332C,
|
||||
0x14C1E0E9E,
|
||||
0x14C1E2A39,
|
||||
0x14C1E3E7C,
|
||||
0x14C1E67C0,
|
||||
0x14C1E9AA8,
|
||||
0x14C1EBA01,
|
||||
0x14C1EE31F,
|
||||
0x14C1F18E4,
|
||||
0x14C1F3B80,
|
||||
0x14C1F5E6E,
|
||||
0x14C1F5F75,
|
||||
0x14C1FAE4D,
|
||||
0x14C1FC26A,
|
||||
0x14C1FC625,
|
||||
0x14C1FD553,
|
||||
0x14C200AE5,
|
||||
0x14C202042,
|
||||
0x14C20701C,
|
||||
0x14C20E0CF,
|
||||
0x14C20F131,
|
||||
0x14C231030,
|
||||
0x14C23C17C,
|
||||
0x14C23E7D8,
|
||||
0x14C23FBBD,
|
||||
0x14C2415E9,
|
||||
0x14C2440CC,
|
||||
0x14C24AFA0,
|
||||
0x14C24B0B1,
|
||||
0x14C24B99B,
|
||||
0x14C250711,
|
||||
0x14C252ACF,
|
||||
0x14C25C0D5,
|
||||
0x14C265061,
|
||||
0x14C26A289,
|
||||
0x14C27695B,
|
||||
0x14C277EBA,
|
||||
0x14C27B2F5,
|
||||
0x14C27B803,
|
||||
};
|
597
src/client/component/arxan/illegal_instructions.hpp
Normal file
597
src/client/component/arxan/illegal_instructions.hpp
Normal file
@ -0,0 +1,597 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr uint64_t illegal_instructions_intact[] =
|
||||
{
|
||||
0x14123DEDC,
|
||||
0x141261C7F,
|
||||
0x141277D40,
|
||||
0x1414020BD,
|
||||
0x14140F61E,
|
||||
0x14B2B6065,
|
||||
0x14B2B9225,
|
||||
0x14B2C38FC,
|
||||
0x14B2C5FA2,
|
||||
0x14B2CA78B,
|
||||
0x14B2CF482,
|
||||
0x14B2D653C,
|
||||
0x14B2D8CF0,
|
||||
0x14B2DA6D2,
|
||||
0x14B2EB8FB,
|
||||
0x14B2ECBEE,
|
||||
0x14B2F376B,
|
||||
0x14B2F6306,
|
||||
0x14B2F88D3,
|
||||
0x14B2FDF3A,
|
||||
0x14B3508DC,
|
||||
0x14B351B0F,
|
||||
0x14B356014,
|
||||
0x14B3573EF,
|
||||
0x14B35CACF,
|
||||
0x14B35E76E,
|
||||
0x14B36757B,
|
||||
0x14B36AEA0,
|
||||
0x14B36E087,
|
||||
0x14B372670,
|
||||
0x14B372744,
|
||||
0x14B375729,
|
||||
0x14B3790EE,
|
||||
0x14B379F34,
|
||||
0x14B37CBC3,
|
||||
0x14B37E46C,
|
||||
0x14B3832F7,
|
||||
0x14B389DE5,
|
||||
0x14B390B46,
|
||||
0x14B39A032,
|
||||
0x14B39C874,
|
||||
0x14B3A7879,
|
||||
0x14B3A9BCC,
|
||||
0x14B3AC660,
|
||||
0x14B3AE8A8,
|
||||
0x14B3AF005,
|
||||
0x14B3C02E1,
|
||||
0x14B3C2DC7,
|
||||
0x14B3DAD67,
|
||||
0x14B3DD92D,
|
||||
0x14B3E49E1,
|
||||
0x14B3EBB18,
|
||||
0x14B3F156E,
|
||||
0x14B3F3B2E,
|
||||
0x14B3F6A1F,
|
||||
0x14B3FC8EE,
|
||||
0x14B3FE5C0,
|
||||
0x14B3FFE0C,
|
||||
0x14B405116,
|
||||
0x14B40CD15,
|
||||
0x14B40F8BA,
|
||||
0x14B4136C4,
|
||||
0x14B41539C,
|
||||
0x14B41B882,
|
||||
0x14B41C7D5,
|
||||
0x14B424303,
|
||||
0x14B428436,
|
||||
0x14B42B8DC,
|
||||
0x14B42DD0B,
|
||||
0x14B42E1CA,
|
||||
0x14B43A6D8,
|
||||
0x14B43CB43,
|
||||
0x14B4439FA,
|
||||
0x14B44510E,
|
||||
0x14B44528D,
|
||||
0x14B44E9E5,
|
||||
0x14B454C97,
|
||||
0x14B455538,
|
||||
0x14B475359,
|
||||
0x14B47C92C,
|
||||
0x14B488696,
|
||||
0x14B489298,
|
||||
0x14B489F2E,
|
||||
0x14B48AF9D,
|
||||
0x14B48B5B8,
|
||||
0x14B48F441,
|
||||
0x14B48FC17,
|
||||
0x14B493289,
|
||||
0x14B49581A,
|
||||
0x14B4991B9,
|
||||
0x14B4A0737,
|
||||
0x14B4A6FBB,
|
||||
0x14B4A7476,
|
||||
0x14B4ACE09,
|
||||
0x14B4B1A6A,
|
||||
0x14B4C0500,
|
||||
0x14B4C3A73,
|
||||
0x14B4C8DD0,
|
||||
0x14B4D9639,
|
||||
0x14B529163,
|
||||
0x14B52EB48,
|
||||
0x14B52EF82,
|
||||
0x14B53B5E3,
|
||||
0x14B53C40E,
|
||||
0x14B54466A,
|
||||
0x14B554BBD,
|
||||
0x14B583C3A,
|
||||
0x14B587749,
|
||||
0x14B587E86,
|
||||
0x14B588BFD,
|
||||
0x14B58EE98,
|
||||
0x14B58FCC1,
|
||||
0x14B59023F,
|
||||
0x14B5924FB,
|
||||
0x14B595DFC,
|
||||
0x14B596B2A,
|
||||
0x14B596E49,
|
||||
0x14B5A0B4A,
|
||||
0x14B5A235A,
|
||||
0x14B5A5D52,
|
||||
0x14B5AF2AA,
|
||||
0x14B5B0446,
|
||||
0x14B5C8698,
|
||||
0x14B5CD955,
|
||||
0x14B62639F,
|
||||
0x14B62F321,
|
||||
0x14B6339FC,
|
||||
0x14B63D66C,
|
||||
0x14B641A18,
|
||||
0x14B642607,
|
||||
0x14B64D09C,
|
||||
0x14B655A50,
|
||||
0x14B65A426,
|
||||
0x14B65D7D2,
|
||||
0x14B6666C4,
|
||||
0x14B66757B,
|
||||
0x14B66C177,
|
||||
0x14B676E80,
|
||||
0x14B6779A8,
|
||||
0x14B67DEA9,
|
||||
0x14B68C499,
|
||||
0x14B68F544,
|
||||
0x14B6E8817,
|
||||
0x14B7373C5,
|
||||
0x14B75DEB9,
|
||||
0x14B7630CC,
|
||||
0x14B769313,
|
||||
0x14B772EBD,
|
||||
0x14B77A2FE,
|
||||
0x14B7858E4,
|
||||
0x14B7878C2,
|
||||
0x14B787DD7,
|
||||
0x14B791100,
|
||||
0x14B791552,
|
||||
0x14B792E99,
|
||||
0x14B7A0DEF,
|
||||
0x14B7B8930,
|
||||
0x14B7CA91A,
|
||||
0x14B7CAAD8,
|
||||
0x14B7D1BBA,
|
||||
0x14B7DE6FD,
|
||||
0x14B7E06B3,
|
||||
0x14B7E0F97,
|
||||
0x14B7E19D9,
|
||||
0x14B7E7A48,
|
||||
0x14B7E8BC1,
|
||||
0x14B7E9741,
|
||||
0x14B7EBD12,
|
||||
0x14B807B6B,
|
||||
0x14B80C398,
|
||||
0x14B80C3C1,
|
||||
0x14B80F98A,
|
||||
0x14B8104BA,
|
||||
0x14B81287C,
|
||||
0x14B818115,
|
||||
0x14B818710,
|
||||
0x14B81DFBE,
|
||||
0x14B81E350,
|
||||
0x14B81F381,
|
||||
0x14B825255,
|
||||
0x14B829DD8,
|
||||
0x14B8754DF,
|
||||
0x14B885BE9,
|
||||
0x14B885F36,
|
||||
0x14B8896FC,
|
||||
0x14B88E976,
|
||||
0x14B8958FA,
|
||||
0x14B8EAF6C,
|
||||
0x14B8FEB01,
|
||||
0x14B9007C0,
|
||||
0x14B95AACD,
|
||||
0x14B95E17B,
|
||||
0x14B95EC92,
|
||||
0x14B960817,
|
||||
0x14B9636B4,
|
||||
0x14B9696F0,
|
||||
0x14B9B4551,
|
||||
0x14B9B82BE,
|
||||
0x14B9B8F4D,
|
||||
0x14B9BB089,
|
||||
0x14B9CB1DB,
|
||||
0x14B9D5DED,
|
||||
0x14B9DB649,
|
||||
0x14B9E60CD,
|
||||
0x14B9E6C9B,
|
||||
0x14B9E9928,
|
||||
0x14B9EFD44,
|
||||
0x14BA019AD,
|
||||
0x14BA01D70,
|
||||
0x14BA058BC,
|
||||
0x14BA09EC5,
|
||||
0x14BA11B00,
|
||||
0x14BA17BDC,
|
||||
0x14BA196A3,
|
||||
0x14BA1988A,
|
||||
0x14BA35064,
|
||||
0x14BA35B31,
|
||||
0x14BA36204,
|
||||
0x14BA3F03C,
|
||||
0x14BA47016,
|
||||
0x14BA4F49B,
|
||||
0x14BA50611,
|
||||
0x14BA64BB9,
|
||||
0x14BA64F6A,
|
||||
0x14BA6A610,
|
||||
0x14BA6A814,
|
||||
0x14BA6C8D3,
|
||||
0x14BA72980,
|
||||
0x14BA73097,
|
||||
0x14BA74956,
|
||||
0x14BA7B5F3,
|
||||
0x14BA7DE77,
|
||||
0x14BA81C1F,
|
||||
0x14BA85CC8,
|
||||
0x14BA91111,
|
||||
0x14BA941CF,
|
||||
0x14BA9B7EF,
|
||||
0x14BAB13A3,
|
||||
0x14BAB660C,
|
||||
0x14BAC4E16,
|
||||
0x14BAC662B,
|
||||
0x14BACCD50,
|
||||
0x14BAD3D3B,
|
||||
0x14BAD6528,
|
||||
0x14BAE6240,
|
||||
0x14BAE6C5C,
|
||||
0x14BAE782D,
|
||||
0x14BAE8DB7,
|
||||
0x14BAECE69,
|
||||
0x14BAEFD3C,
|
||||
0x14BAF3A44,
|
||||
0x14BB017BF,
|
||||
0x14BB06F08,
|
||||
0x14BB18982,
|
||||
0x14BB1A7EE,
|
||||
0x14BB1B1C7,
|
||||
0x14BB1F475,
|
||||
0x14BB25D17,
|
||||
0x14BB27982,
|
||||
0x14BB38329,
|
||||
0x14BB3FA72,
|
||||
0x14BB43CBB,
|
||||
0x14BB46A23,
|
||||
0x14BB5391B,
|
||||
0x14BB581D4,
|
||||
0x14BB5FBBB,
|
||||
0x14BB61611,
|
||||
0x14BB63931,
|
||||
0x14BB700CE,
|
||||
0x14BB82A27,
|
||||
0x14BB82C0A,
|
||||
0x14BB89003,
|
||||
0x14BB893CD,
|
||||
0x14BC03B2D,
|
||||
0x14BC041E8,
|
||||
0x14BC05287,
|
||||
0x14BC086B4,
|
||||
0x14BC15749,
|
||||
0x14BC1E58A,
|
||||
0x14BC3EC23,
|
||||
0x14BC436EF,
|
||||
0x14BC48195,
|
||||
0x14BC56497,
|
||||
0x14BC5755D,
|
||||
0x14BC57903,
|
||||
0x14BC5A62E,
|
||||
0x14BC5C62F,
|
||||
0x14BC66A22,
|
||||
0x14BCB7AE9,
|
||||
0x14BCBF793,
|
||||
0x14BCCAD84,
|
||||
0x14BCDA738,
|
||||
0x14BCEB884,
|
||||
0x14BCEF45F,
|
||||
0x14BCF3834,
|
||||
0x14BD016D1,
|
||||
0x14BD057AC,
|
||||
0x14BD05859,
|
||||
0x14BDB7EB7,
|
||||
0x14BDBA1DB,
|
||||
0x14BDBBA36,
|
||||
0x14BDBE93F,
|
||||
0x14BDC19E4,
|
||||
0x14BDC3E21,
|
||||
0x14BDC7BBF,
|
||||
0x14BDCBE79,
|
||||
0x14BDCF71D,
|
||||
0x14BDD3CBD,
|
||||
0x14BDD5AF5,
|
||||
0x14BDDAA78,
|
||||
0x14BDDB1F5,
|
||||
0x14BDE0244,
|
||||
0x14BDE6255,
|
||||
0x14BDF1556,
|
||||
0x14BDF4A3D,
|
||||
0x14BDF608A,
|
||||
0x14BDFB68F,
|
||||
0x14BDFFE41,
|
||||
0x14BE07690,
|
||||
0x14BE0B269,
|
||||
0x14BE0D169,
|
||||
0x14BE1193F,
|
||||
0x14BE11F1D,
|
||||
0x14BE18C23,
|
||||
0x14BE24E2A,
|
||||
0x14BE2896A,
|
||||
0x14BE2A7CF,
|
||||
0x14BE2B1CB,
|
||||
0x14BE2E4F7,
|
||||
0x14BE32D39,
|
||||
0x14BE351F2,
|
||||
0x14BE60CDD,
|
||||
0x14BE63E75,
|
||||
0x14BE6EF30,
|
||||
0x14BE71DB2,
|
||||
0x14BE7AE7A,
|
||||
0x14BE7E465,
|
||||
0x14BE8024B,
|
||||
0x14BE84254,
|
||||
0x14BE8D471,
|
||||
0x14BE90644,
|
||||
0x14BE94ACA,
|
||||
0x14BE95A75,
|
||||
0x14BEA14E0,
|
||||
0x14BEA5728,
|
||||
0x14BEA5792,
|
||||
0x14BEAAAAF,
|
||||
0x14BEAD940,
|
||||
0x14BEB407B,
|
||||
0x14BEB71BF,
|
||||
0x14BEC88F7,
|
||||
0x14BECF014,
|
||||
0x14BED66AB,
|
||||
0x14BEE4E49,
|
||||
0x14BEE712B,
|
||||
0x14BEE8115,
|
||||
0x14BEECA23,
|
||||
0x14BEF822E,
|
||||
0x14BEFE92E,
|
||||
0x14BF032D0,
|
||||
0x14BF086A4,
|
||||
0x14BF0D790,
|
||||
0x14BF1084D,
|
||||
0x14BF27B4B,
|
||||
0x14BF2BE6F,
|
||||
0x14BF2ECCD,
|
||||
0x14BF33217,
|
||||
0x14BF3C027,
|
||||
0x14BF3E0CA,
|
||||
0x14BF3F3C5,
|
||||
0x14BF40872,
|
||||
0x14BF40EA6,
|
||||
0x14BF411F3,
|
||||
0x14BF51243,
|
||||
0x14BF541A5,
|
||||
0x14BF58A0A,
|
||||
0x14BF5A721,
|
||||
0x14BF5F9E0,
|
||||
0x14BF7AC17,
|
||||
0x14BF7BDBB,
|
||||
0x14BF7DBB4,
|
||||
0x14BF806B0,
|
||||
0x14BF86481,
|
||||
0x14BF8B563,
|
||||
0x14BF8CE7E,
|
||||
0x14BF98757,
|
||||
0x14BFA3C60,
|
||||
0x14BFAEEBA,
|
||||
0x14BFB0E9D,
|
||||
0x14BFB59DF,
|
||||
0x14BFBDB2F,
|
||||
0x14BFBF43D,
|
||||
0x14BFC2764,
|
||||
0x14BFD674C,
|
||||
0x14BFE320E,
|
||||
0x14BFE4D2B,
|
||||
0x14BFE9D7F,
|
||||
0x14C006433,
|
||||
0x14C007B55,
|
||||
0x14C008A99,
|
||||
0x14C0092EC,
|
||||
0x14C00943D,
|
||||
0x14C00B792,
|
||||
0x14C012837,
|
||||
0x14C012AC3,
|
||||
0x14C01675E,
|
||||
0x14C0683E1,
|
||||
0x14C06AEED,
|
||||
0x14C07BED8,
|
||||
0x14C07FE7D,
|
||||
0x14C084D2D,
|
||||
0x14C088436,
|
||||
0x14C09466C,
|
||||
0x14C0AE879,
|
||||
0x14C0B4C36,
|
||||
0x14C0BC243,
|
||||
0x14C0C18D6,
|
||||
0x14C0CC8FC,
|
||||
0x14C0D4EF1,
|
||||
0x14C0EC01A,
|
||||
0x14C0ED2AD,
|
||||
0x14C0EDE92,
|
||||
0x14C0EE6A3,
|
||||
0x14C0F5DA6,
|
||||
0x14C0F654C,
|
||||
0x14C1030BF,
|
||||
0x14C17320D,
|
||||
0x14C177DC3,
|
||||
0x14C1850A3,
|
||||
0x14C185BA9,
|
||||
0x14C186C27,
|
||||
0x14C188C99,
|
||||
0x14C18CE23,
|
||||
0x14C1D51B5,
|
||||
0x14C1D6246,
|
||||
0x14C1DCB9A,
|
||||
0x14C1E3B52,
|
||||
0x14C1E9C40,
|
||||
0x14C1ED862,
|
||||
0x14C1F4B92,
|
||||
0x14C1F6575,
|
||||
0x14C20357C,
|
||||
0x14C20B745,
|
||||
0x14C216876,
|
||||
0x14C21ACF7,
|
||||
0x14C21EF99,
|
||||
0x14C2368F0,
|
||||
0x14C237C1B,
|
||||
0x14C2395B1,
|
||||
0x14C23ECD9,
|
||||
0x14C23EEEA,
|
||||
0x14C24F487,
|
||||
0x14C25172C,
|
||||
0x14C255A80,
|
||||
0x14C25E1BB,
|
||||
0x14C25FA75,
|
||||
0x14C263109,
|
||||
0x14C265E7E,
|
||||
0x14C269700,
|
||||
0x14C2723B5,
|
||||
0x14C275977,
|
||||
0x14C279104,
|
||||
0x14C27964C,
|
||||
};
|
||||
constexpr uint64_t illegal_instructions_split[] =
|
||||
{
|
||||
0x1412522EB,
|
||||
0x14B2C77C4,
|
||||
0x14B2D62AD,
|
||||
0x14B2E71D4,
|
||||
0x14B2E7713,
|
||||
0x14B2F5C52,
|
||||
0x14B35F6C1,
|
||||
0x14B361A7F,
|
||||
0x14B367251,
|
||||
0x14B39AB42,
|
||||
0x14B3ACABD,
|
||||
0x14B3F7EAF,
|
||||
0x14B406441,
|
||||
0x14B4196E5,
|
||||
0x14B41D19F,
|
||||
0x14B4322A5,
|
||||
0x14B43ACFB,
|
||||
0x14B441015,
|
||||
0x14B445DFF,
|
||||
0x14B462EFE,
|
||||
0x14B47FA01,
|
||||
0x14B49BF69,
|
||||
0x14B49D7D3,
|
||||
0x14B4B31CE,
|
||||
0x14B4C8EB8,
|
||||
0x14B4CC306,
|
||||
0x14B543258,
|
||||
0x14B57B9A3,
|
||||
0x14B597BED,
|
||||
0x14B5A4691,
|
||||
0x14B5C07B5,
|
||||
0x14B5D51CC,
|
||||
0x14B642ADA,
|
||||
0x14B651DE6,
|
||||
0x14B656F57,
|
||||
0x14B68A4D4,
|
||||
0x14B6E48EA,
|
||||
0x14B73FCF4,
|
||||
0x14B747FEC,
|
||||
0x14B767C87,
|
||||
0x14B76C67B,
|
||||
0x14B7718EB,
|
||||
0x14B792301,
|
||||
0x14B7B8912,
|
||||
0x14B7BF5EE,
|
||||
0x14B7E6951,
|
||||
0x14B7F603C,
|
||||
0x14B804F0C,
|
||||
0x14B818EE5,
|
||||
0x14B86D440,
|
||||
0x14B87F411,
|
||||
0x14B884B33,
|
||||
0x14B8F01F1,
|
||||
0x14B90065F,
|
||||
0x14B961290,
|
||||
0x14B9B1CCE,
|
||||
0x14B9C8637,
|
||||
0x14B9D5D04,
|
||||
0x14BA08255,
|
||||
0x14BA1C344,
|
||||
0x14BA2C491,
|
||||
0x14BA628A5,
|
||||
0x14BAA45B9,
|
||||
0x14BAAFFC1,
|
||||
0x14BAC036C,
|
||||
0x14BAC09AB,
|
||||
0x14BAC7C55,
|
||||
0x14BAD7547,
|
||||
0x14BAF3B1E,
|
||||
0x14BB692E3,
|
||||
0x14BB7AB76,
|
||||
0x14BB800A1,
|
||||
0x14BC0930A,
|
||||
0x14BC34164,
|
||||
0x14BC5965F,
|
||||
0x14BC6152C,
|
||||
0x14BCBD74A,
|
||||
0x14BCC4BCB,
|
||||
0x14BCC7D79,
|
||||
0x14BCC7D8B,
|
||||
0x14BCD6F46,
|
||||
0x14BCF3B77,
|
||||
0x14BCF49BF,
|
||||
0x14BD05004,
|
||||
0x14BDC7020,
|
||||
0x14BDC7C68,
|
||||
0x14BDD4DF5,
|
||||
0x14BE09E85,
|
||||
0x14BE17031,
|
||||
0x14BE2C8A7,
|
||||
0x14BE530BB,
|
||||
0x14BE806DF,
|
||||
0x14BE8D36C,
|
||||
0x14BEBDBB6,
|
||||
0x14BECB9D6,
|
||||
0x14BED4F0F,
|
||||
0x14BEEC558,
|
||||
0x14BEFB5D8,
|
||||
0x14BF2FC57,
|
||||
0x14BF56768,
|
||||
0x14BF58CCA,
|
||||
0x14BF7E2ED,
|
||||
0x14BF88EC2,
|
||||
0x14BFBECE9,
|
||||
0x14BFC9530,
|
||||
0x14BFCBF54,
|
||||
0x14C098550,
|
||||
0x14C098F31,
|
||||
0x14C0A22BD,
|
||||
0x14C0BCDBD,
|
||||
0x14C0C80D2,
|
||||
0x14C173C79,
|
||||
0x14C1D498D,
|
||||
0x14C1F19C9,
|
||||
0x14C1F3A1F,
|
||||
0x14C1F6D2B,
|
||||
0x14C1F7093,
|
||||
0x14C235640,
|
||||
0x14C241065,
|
||||
0x14C244C7B,
|
||||
0x14C24EDC6,
|
||||
0x14C2615B0,
|
||||
0x14C262DC0,
|
||||
0x14C26EA5A,
|
||||
};
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
|
||||
#include "console/console.hpp"
|
||||
#include "game_console.hpp"
|
||||
@ -194,34 +195,105 @@ namespace command
|
||||
return 0;
|
||||
}
|
||||
|
||||
void client_println(int client_num, const std::string& text)
|
||||
void cmd_give(const int client_num, const std::vector<std::string>& params)
|
||||
{
|
||||
if (game::Com_GameMode_GetActiveGameMode() == game::GAME_MODE_SP)
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game::CG_Utils_GameMessage(client_num, text.data(), 0); // why is nothing printed?
|
||||
game::shared::client_println(client_num, "You did not specify a weapon name");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto& arg = params[1];
|
||||
const auto player = scripting::entity({ static_cast<uint16_t>(client_num), 0 });
|
||||
|
||||
if (arg == "ammo")
|
||||
{
|
||||
const auto weapon = player.call("getcurrentweapon").as<std::string>();
|
||||
player.call("givemaxammo", { weapon });
|
||||
}
|
||||
else if (arg == "allammo")
|
||||
{
|
||||
const auto weapons = player.call("getweaponslistall").as<scripting::array>();
|
||||
for (auto i = 0; i < weapons.size(); i++)
|
||||
{
|
||||
player.call("givemaxammo", { weapons[i] });
|
||||
}
|
||||
}
|
||||
else if (arg == "health")
|
||||
{
|
||||
if (params.size() > 2)
|
||||
{
|
||||
const auto amount = atoi(params[2].data());
|
||||
const auto health = player.get("health").as<int>();
|
||||
player.set("health", { health + amount });
|
||||
}
|
||||
else
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"%s\"", text.data()));
|
||||
const auto amount = atoi(game::Dvar_FindVar("scr_player_maxhealth")->current.string);
|
||||
player.set("health", { amount });
|
||||
}
|
||||
}
|
||||
else if (arg == "all")
|
||||
{
|
||||
const auto type = game::XAssetType::ASSET_TYPE_WEAPON;
|
||||
game::DB_EnumXAssets(type, [&player, type](const game::XAssetHeader header)
|
||||
{
|
||||
const auto asset = game::XAsset{ type, header };
|
||||
const auto asset_name = game::DB_GetXAssetName(&asset);
|
||||
|
||||
player.call("giveweapon", { asset_name });
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
player.call("giveweapon", { arg });
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
bool cheats_ok(int client_num)
|
||||
void cmd_drop_weapon(int client_num)
|
||||
{
|
||||
if (game::Com_GameMode_GetActiveGameMode() == game::GAME_MODE_SP)
|
||||
try
|
||||
{
|
||||
return true;
|
||||
const auto player = scripting::entity({ static_cast<uint16_t>(client_num), 0 });
|
||||
const auto weapon = player.call("getcurrentweapon");
|
||||
player.call("dropitem", { weapon });
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
const auto sv_cheats = game::Dvar_FindVar("sv_cheats");
|
||||
if (!sv_cheats || !sv_cheats->current.enabled)
|
||||
void cmd_take(int client_num, const std::vector<std::string>& params)
|
||||
{
|
||||
client_println(client_num, "GAME_CHEATSNOTENABLED");
|
||||
return false;
|
||||
if (params.size() < 2)
|
||||
{
|
||||
game::shared::client_println(client_num, "You did not specify a weapon name");
|
||||
return;
|
||||
}
|
||||
|
||||
return true;
|
||||
const auto& weapon = params[1];
|
||||
|
||||
try
|
||||
{
|
||||
const auto player = scripting::entity({ static_cast<uint16_t>(client_num), 0 });
|
||||
if (weapon == "all"s)
|
||||
{
|
||||
player.call("takeallweapons");
|
||||
}
|
||||
else
|
||||
{
|
||||
player.call("takeweapon", { weapon });
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -416,13 +488,13 @@ namespace command
|
||||
|
||||
add_sv("god", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(client_num))
|
||||
if (!game::shared::cheats_ok(client_num, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::g_entities[client_num].flags ^= 1;
|
||||
client_println(client_num,
|
||||
game::shared::client_println(client_num,
|
||||
game::g_entities[client_num].flags & 1
|
||||
? "GAME_GODMODE_ON"
|
||||
: "GAME_GODMODE_OFF");
|
||||
@ -430,13 +502,13 @@ namespace command
|
||||
|
||||
add_sv("demigod", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(client_num))
|
||||
if (!game::shared::cheats_ok(client_num, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::g_entities[client_num].flags ^= 2;
|
||||
client_println(client_num,
|
||||
game::shared::client_println(client_num,
|
||||
game::g_entities[client_num].flags & 2
|
||||
? "GAME_DEMI_GODMODE_ON"
|
||||
: "GAME_DEMI_GODMODE_OFF");
|
||||
@ -444,13 +516,13 @@ namespace command
|
||||
|
||||
add_sv("notarget", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(client_num))
|
||||
if (!game::shared::cheats_ok(client_num, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::g_entities[client_num].flags ^= 4;
|
||||
client_println(client_num,
|
||||
game::shared::client_println(client_num,
|
||||
game::g_entities[client_num].flags & 4
|
||||
? "GAME_NOTARGETON"
|
||||
: "GAME_NOTARGETOFF");
|
||||
@ -458,13 +530,13 @@ namespace command
|
||||
|
||||
add_sv("noclip", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(client_num))
|
||||
if (!game::shared::cheats_ok(client_num, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::g_entities[client_num].client->flags ^= 1;
|
||||
client_println(client_num,
|
||||
game::shared::client_println(client_num,
|
||||
game::g_entities[client_num].client->flags & 1
|
||||
? "GAME_NOCLIPON"
|
||||
: "GAME_NOCLIPOFF");
|
||||
@ -472,17 +544,47 @@ namespace command
|
||||
|
||||
add_sv("ufo", [](const int client_num, const params_sv&)
|
||||
{
|
||||
if (!cheats_ok(client_num))
|
||||
if (!game::shared::cheats_ok(client_num, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::g_entities[client_num].client->flags ^= 2;
|
||||
client_println(client_num,
|
||||
game::shared::client_println(client_num,
|
||||
game::g_entities[client_num].client->flags & 2
|
||||
? "GAME_UFOON"
|
||||
: "GAME_UFOOFF");
|
||||
});
|
||||
|
||||
add_sv("give", [](const int client_num, const params_sv& params)
|
||||
{
|
||||
if (!game::shared::cheats_ok(client_num, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_give(client_num, params.get_all());
|
||||
});
|
||||
|
||||
add_sv("dropweapon", [](const int client_num, const params_sv& params)
|
||||
{
|
||||
if (!game::shared::cheats_ok(client_num, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_drop_weapon(client_num);
|
||||
});
|
||||
|
||||
add_sv("take", [](const int client_num, const params_sv& params)
|
||||
{
|
||||
if (!game::shared::cheats_ok(client_num, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_take(client_num, params.get_all());
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -341,6 +341,7 @@ namespace terminal
|
||||
public:
|
||||
component()
|
||||
{
|
||||
if(!game::environment::is_dedi())
|
||||
ShowWindow(GetConsoleWindow(), SW_HIDE);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "command.hpp"
|
||||
#include "console/console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "filesystem.hpp"
|
||||
|
||||
#include <utils/json.hpp>
|
||||
|
||||
@ -111,8 +112,6 @@ namespace dedicated
|
||||
}
|
||||
};
|
||||
|
||||
command::execute("onlinegame 1", true);
|
||||
command::execute("xblive_privatematch 1", true);
|
||||
initialize_gamemode();
|
||||
}
|
||||
|
||||
@ -121,7 +120,7 @@ namespace dedicated
|
||||
nlohmann::json get_snd_alias_length_data(const char* mapname, const std::string& game_mode = "")
|
||||
{
|
||||
const auto path = "sounddata/"s + game_mode + "/"s + mapname + ".json"s;
|
||||
const auto buffer = utils::io::read_file(path);
|
||||
const auto buffer = filesystem::read_file(path);
|
||||
if (!buffer.empty())
|
||||
{
|
||||
try
|
||||
@ -153,6 +152,7 @@ namespace dedicated
|
||||
}
|
||||
else
|
||||
{
|
||||
//console::error("[SND]: failed to find sound length soundalias \"%s\"\n", alias);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@ -233,8 +233,6 @@ namespace dedicated
|
||||
utils::hook::nop(0x34296F_b, 2); // ^
|
||||
utils::hook::set<uint8_t>(0xE08360_b, 0xC3); // don't shutdown renderer
|
||||
|
||||
utils::hook::set<uint8_t>(0xC5A200_b, 0xC3); // disable host migration
|
||||
|
||||
// SOUND patches
|
||||
//utils::hook::nop(0xC93213_b, 5); // snd stream thread
|
||||
//utils::hook::set<uint8_t>(0xC93206_b, 0); // snd_active
|
||||
|
@ -69,8 +69,7 @@ namespace dvar_cheats
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto sv_cheats = game::Dvar_FindVar("sv_cheats");
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_CHEAT) && ((sv_cheats && !sv_cheats->current.enabled) && *game::isCheatOverride))
|
||||
if ((dvar->flags & game::DvarFlags::DVAR_FLAG_CHEAT) && (!game::shared::cheats_ok() && *game::isCheatOverride))
|
||||
{
|
||||
//#ifdef DEBUG
|
||||
console::error("%s is cheat protected\n", dvars::dvar_get_name(dvar).data());
|
||||
|
@ -10,6 +10,9 @@
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
//#define XFILE_DEBUG
|
||||
|
||||
@ -33,6 +36,7 @@ namespace fastfiles
|
||||
utils::hook::detour db_init_load_x_file_hook;
|
||||
utils::hook::detour db_load_x_zone_hook;
|
||||
utils::hook::detour db_find_xasset_header_hook;
|
||||
utils::hook::detour db_add_xasset_hook;
|
||||
|
||||
void db_try_load_x_file_internal_stub(const char* zone_name, const unsigned int zone_flags,
|
||||
const bool is_base_map, const bool was_paused, const int failure_mode)
|
||||
@ -56,6 +60,28 @@ namespace fastfiles
|
||||
return db_load_x_zone_hook.invoke<void>(parent_name, zone_flags, is_base_map, failure_mode);
|
||||
}
|
||||
|
||||
game::dvar_t* g_dump_scripts;
|
||||
void dump_gsc_script(const std::string& name, game::XAssetHeader header)
|
||||
{
|
||||
if (!g_dump_scripts->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
buffer.append(header.scriptfile->name, strlen(header.scriptfile->name) + 1);
|
||||
buffer.append(reinterpret_cast<char*>(&header.scriptfile->compressedLen), 4);
|
||||
buffer.append(reinterpret_cast<char*>(&header.scriptfile->len), 4);
|
||||
buffer.append(reinterpret_cast<char*>(&header.scriptfile->bytecodeLen), 4);
|
||||
buffer.append(header.scriptfile->buffer, header.scriptfile->compressedLen);
|
||||
buffer.append(header.scriptfile->bytecode, header.scriptfile->bytecodeLen);
|
||||
|
||||
const auto out_name = utils::string::va("gsc_dump/%s.gscbin", name.data());
|
||||
utils::io::write_file(out_name, buffer);
|
||||
|
||||
console::info("Dumped %s\n", out_name);
|
||||
}
|
||||
|
||||
game::XAssetHeader db_find_xasset_header_stub(game::XAssetType type, const char* name, const int allow_create_default)
|
||||
{
|
||||
auto result = db_find_xasset_header_hook.invoke<game::XAssetHeader>(type, name, allow_create_default);
|
||||
@ -65,10 +91,76 @@ namespace fastfiles
|
||||
game::g_assetNames[static_cast<unsigned int>(type)],
|
||||
name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
game::XAssetHeader db_add_xasset_stub(game::XAssetType type, game::XAssetHeader* header_ptr)
|
||||
{
|
||||
auto header = *header_ptr;
|
||||
|
||||
if (type == game::ASSET_TYPE_SCRIPTFILE && header.scriptfile)
|
||||
{
|
||||
dump_gsc_script(header.scriptfile->name ? header.scriptfile->name : "__unnamed__", header);
|
||||
}
|
||||
|
||||
auto result = db_add_xasset_hook.invoke<game::XAssetHeader>(type, header_ptr);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
namespace zone_loading
|
||||
{
|
||||
utils::hook::detour db_is_patch_hook;
|
||||
|
||||
bool check_missing_content_func(const char* zone_name)
|
||||
{
|
||||
const char* lang_code = game::SEH_GetCurrentLanguageCode();
|
||||
char buffer[0x100]{ 0 };
|
||||
const auto len = sprintf_s(buffer, "%s_", lang_code);
|
||||
|
||||
if (!strncmp(zone_name, buffer, len))
|
||||
{
|
||||
printf("Tried to load missing language zone: %s\n", zone_name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool db_is_patch_stub(const char* name)
|
||||
{
|
||||
if (db_is_patch_hook.invoke<bool>(name)) return true;
|
||||
if (check_missing_content_func(name)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void skip_extra_zones_stub(utils::hook::assembler& a)
|
||||
{
|
||||
const auto skip = a.newLabel();
|
||||
const auto original = a.newLabel();
|
||||
|
||||
//a.pushad64();
|
||||
a.test(edi, game::DB_ZONE_CUSTOM); // allocFlags
|
||||
a.jnz(skip);
|
||||
|
||||
a.bind(original);
|
||||
//a.popad64();
|
||||
a.call(0x3BC450_b); // strnicmp_ffotd
|
||||
a.mov(r12d, edi);
|
||||
a.mov(rdx, 0x1467970_b); // "patch_"
|
||||
a.jmp(0x3BA9C0_b);
|
||||
|
||||
a.bind(skip);
|
||||
//a.popad64();
|
||||
a.mov(r12d, game::DB_ZONE_CUSTOM);
|
||||
a.not_(r12d);
|
||||
a.and_(edi, r12d);
|
||||
a.jmp(0x3BAC06_b);
|
||||
}
|
||||
}
|
||||
using namespace zone_loading;
|
||||
|
||||
bool exists(const std::string& zone)
|
||||
{
|
||||
const auto is_localized = game::DB_IsLocalized(zone.data());
|
||||
@ -97,6 +189,45 @@ namespace fastfiles
|
||||
#endif
|
||||
|
||||
db_find_xasset_header_hook.create(game::DB_FindXAssetHeader, db_find_xasset_header_stub);
|
||||
db_add_xasset_hook.create(0xA76520_b, db_add_xasset_stub);
|
||||
|
||||
g_dump_scripts = game::Dvar_RegisterBool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts");
|
||||
|
||||
// Don't fatal on certain missing zones
|
||||
db_is_patch_hook.create(0x3BC580_b, db_is_patch_stub);
|
||||
// Don't load extra zones with loadzone
|
||||
utils::hook::nop(0x3BA9B1_b, 15);
|
||||
utils::hook::jump(0x3BA9B1_b, utils::hook::assemble(skip_extra_zones_stub), true);
|
||||
|
||||
// Allow loading of unsigned fastfiles
|
||||
utils::hook::set<uint8_t>(0x9E8CAE_b, 0xEB); // DB_InflateInit
|
||||
|
||||
// Skip signature validation
|
||||
utils::hook::set(0x9E6390_b, 0xC301B0); // signature
|
||||
|
||||
command::add("loadzone", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("usage: loadzone <zone>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* name = params.get(1);
|
||||
|
||||
if (!fastfiles::exists(name))
|
||||
{
|
||||
console::warn("loadzone: zone %s could not be found!\n", name);
|
||||
return;
|
||||
}
|
||||
|
||||
game::XZoneInfo info{};
|
||||
info.name = name;
|
||||
info.allocFlags = game::DB_ZONE_GAME;
|
||||
info.allocFlags |= game::DB_ZONE_CUSTOM; // skip extra zones with this flag
|
||||
|
||||
game::DB_LoadXAssets(&info, 1, game::DBSyncMode::DB_LOAD_ASYNC);
|
||||
});
|
||||
|
||||
command::add("listassetpool", [](const command::params& params)
|
||||
{
|
||||
|
@ -2,42 +2,225 @@
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "filesystem.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "component/dvars.hpp"
|
||||
#include "component/console/console.hpp"
|
||||
|
||||
#include "dvars.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace game::filesystem
|
||||
namespace filesystem
|
||||
{
|
||||
file::file(std::string name)
|
||||
: name_(std::move(name))
|
||||
namespace
|
||||
{
|
||||
char* buffer{};
|
||||
const auto size = game::FS_ReadFile(this->name_.data(), &buffer);
|
||||
utils::hook::detour fs_startup_hook;
|
||||
|
||||
if (size >= 0 && buffer)
|
||||
bool initialized = false;
|
||||
|
||||
std::deque<std::filesystem::path>& get_search_paths_internal()
|
||||
{
|
||||
this->valid_ = true;
|
||||
this->buffer_.append(buffer, size);
|
||||
game::FS_FreeFile(buffer);
|
||||
static std::deque<std::filesystem::path> search_paths{};
|
||||
return search_paths;
|
||||
}
|
||||
|
||||
void fs_display_path()
|
||||
{
|
||||
console::info("Current language: %s\n", game::SEH_GetLanguageName(*reinterpret_cast<int*>(0x74C6420_b)));
|
||||
console::info("Current search paths:\n");
|
||||
|
||||
if (game::fs_searchpaths.get())
|
||||
{
|
||||
for (auto i = game::fs_searchpaths.get()->next; i; i = i->next)
|
||||
{
|
||||
console::info("%s/%s\n", i->dir->path, i->dir->gamedir);
|
||||
}
|
||||
}
|
||||
|
||||
bool file::exists() const
|
||||
for (auto path : filesystem::get_search_paths())
|
||||
{
|
||||
return this->valid_;
|
||||
console::info("%s\n", path.data());
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& file::get_buffer() const
|
||||
void fs_startup_stub(const char* name)
|
||||
{
|
||||
return this->buffer_;
|
||||
console::info("----- FS_Startup -----\n");
|
||||
|
||||
initialized = true;
|
||||
|
||||
filesystem::register_path(L".");
|
||||
filesystem::register_path(L"iw7-mod");
|
||||
filesystem::register_path(L"devraw_shared");
|
||||
filesystem::register_path(L"devraw");
|
||||
filesystem::register_path(L"raw_shared");
|
||||
filesystem::register_path(L"raw");
|
||||
filesystem::register_path(L"main_shared");
|
||||
filesystem::register_path(L"main");
|
||||
|
||||
fs_startup_hook.invoke<void>(name);
|
||||
|
||||
fs_display_path();
|
||||
console::info("----------------------\n");
|
||||
}
|
||||
|
||||
const std::string& file::get_name() const
|
||||
std::vector<std::filesystem::path> get_paths(const std::filesystem::path& path)
|
||||
{
|
||||
return this->name_;
|
||||
std::vector<std::filesystem::path> paths{};
|
||||
paths.push_back(path);
|
||||
return paths;
|
||||
}
|
||||
|
||||
bool can_insert_path(const std::filesystem::path& path)
|
||||
{
|
||||
for (const auto& path_ : get_search_paths_internal())
|
||||
{
|
||||
if (path_ == path)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* sys_default_install_path_stub()
|
||||
{
|
||||
static auto current_path = std::filesystem::current_path().string();
|
||||
return current_path.data();
|
||||
}
|
||||
}
|
||||
|
||||
std::string read_file(const std::string& path)
|
||||
{
|
||||
for (const auto& search_path : get_search_paths_internal())
|
||||
{
|
||||
const auto path_ = search_path / path;
|
||||
if (utils::io::file_exists(path_.generic_string()))
|
||||
{
|
||||
return utils::io::read_file(path_.generic_string());
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool read_file(const std::string& path, std::string* data, std::string* real_path)
|
||||
{
|
||||
for (const auto& search_path : get_search_paths_internal())
|
||||
{
|
||||
const auto path_ = search_path / path;
|
||||
if (utils::io::read_file(path_.generic_string(), data))
|
||||
{
|
||||
if (real_path != nullptr)
|
||||
{
|
||||
*real_path = path_.generic_string();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool find_file(const std::string& path, std::string* real_path)
|
||||
{
|
||||
for (const auto& search_path : get_search_paths_internal())
|
||||
{
|
||||
const auto path_ = search_path / path;
|
||||
if (utils::io::file_exists(path_.generic_string()))
|
||||
{
|
||||
*real_path = path_.generic_string();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool exists(const std::string& path)
|
||||
{
|
||||
for (const auto& search_path : get_search_paths_internal())
|
||||
{
|
||||
const auto path_ = search_path / path;
|
||||
if (utils::io::file_exists(path_.generic_string()))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void register_path(const std::filesystem::path& path)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto paths = get_paths(path);
|
||||
for (const auto& path_ : paths)
|
||||
{
|
||||
if (can_insert_path(path_))
|
||||
{
|
||||
console::debug("[FS] Registering path '%s'\n", path_.generic_string().data());
|
||||
get_search_paths_internal().push_front(path_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void unregister_path(const std::filesystem::path& path)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto paths = get_paths(path);
|
||||
for (const auto& path_ : paths)
|
||||
{
|
||||
auto& search_paths = get_search_paths_internal();
|
||||
for (auto i = search_paths.begin(); i != search_paths.end();)
|
||||
{
|
||||
if (*i == path_)
|
||||
{
|
||||
console::debug("[FS] Unregistering path '%s'\n", path_.generic_string().data());
|
||||
i = search_paths.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> get_search_paths()
|
||||
{
|
||||
std::vector<std::string> paths{};
|
||||
|
||||
for (const auto& path : get_search_paths_internal())
|
||||
{
|
||||
paths.push_back(path.generic_string());
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_search_paths_rev()
|
||||
{
|
||||
std::vector<std::string> paths{};
|
||||
const auto& search_paths = get_search_paths_internal();
|
||||
|
||||
for (auto i = search_paths.rbegin(); i != search_paths.rend(); ++i)
|
||||
{
|
||||
paths.push_back(i->generic_string());
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
@ -45,9 +228,14 @@ namespace game::filesystem
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
dvars::override::register_string("fs_basegame", "iw7-mod", 2048);
|
||||
fs_startup_hook.create(0xCDD800_b, fs_startup_stub);
|
||||
|
||||
utils::hook::jump(0xCFE5E0_b, sys_default_install_path_stub);
|
||||
|
||||
// fs_game flags
|
||||
utils::hook::set<uint32_t>(0xCDD415_b, 0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(game::filesystem::component)
|
||||
REGISTER_COMPONENT(filesystem::component)
|
||||
|
@ -1,19 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
namespace game::filesystem
|
||||
namespace filesystem
|
||||
{
|
||||
class file
|
||||
{
|
||||
public:
|
||||
file(std::string name);
|
||||
std::string read_file(const std::string& path);
|
||||
bool read_file(const std::string& path, std::string* data, std::string* real_path = nullptr);
|
||||
bool find_file(const std::string& path, std::string* real_path);
|
||||
bool exists(const std::string& path);
|
||||
|
||||
[[nodiscard]] bool exists() const;
|
||||
[[nodiscard]] const std::string& get_buffer() const;
|
||||
[[nodiscard]] const std::string& get_name() const;
|
||||
void register_path(const std::filesystem::path& path);
|
||||
void unregister_path(const std::filesystem::path& path);
|
||||
|
||||
private:
|
||||
bool valid_ = false;
|
||||
std::string name_;
|
||||
std::string buffer_;
|
||||
};
|
||||
std::vector<std::string> get_search_paths();
|
||||
std::vector<std::string> get_search_paths_rev();
|
||||
}
|
328
src/client/component/gsc/script_error.cpp
Normal file
328
src/client/component/gsc/script_error.cpp
Normal file
@ -0,0 +1,328 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "script_extension.hpp"
|
||||
#include "script_error.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
using namespace utils::string;
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour scr_emit_function_hook;
|
||||
|
||||
std::uint32_t current_filename = 0;
|
||||
|
||||
std::string unknown_function_error;
|
||||
|
||||
std::array<const char*, 27> var_typename =
|
||||
{
|
||||
"undefined",
|
||||
"object",
|
||||
"string",
|
||||
"localized string",
|
||||
"vector",
|
||||
"float",
|
||||
"int",
|
||||
"codepos",
|
||||
"precodepos",
|
||||
"function",
|
||||
"builtin function",
|
||||
"builtin method",
|
||||
"stack",
|
||||
"animation",
|
||||
"pre animation",
|
||||
"thread",
|
||||
"thread",
|
||||
"thread",
|
||||
"thread",
|
||||
"struct",
|
||||
"removed entity",
|
||||
"entity",
|
||||
"array",
|
||||
"removed thread",
|
||||
"<free>",
|
||||
"thread list",
|
||||
"endon list",
|
||||
};
|
||||
|
||||
void scr_emit_function_stub(std::uint32_t filename, std::uint32_t thread_name, char* code_pos)
|
||||
{
|
||||
current_filename = filename;
|
||||
scr_emit_function_hook.invoke<void>(filename, thread_name, code_pos);
|
||||
}
|
||||
|
||||
std::string get_filename_name()
|
||||
{
|
||||
const auto filename_str = game::SL_ConvertToString(static_cast<game::scr_string_t>(current_filename));
|
||||
const auto id = std::atoi(filename_str);
|
||||
if (!id)
|
||||
{
|
||||
return filename_str;
|
||||
}
|
||||
|
||||
return scripting::get_token(id);
|
||||
}
|
||||
|
||||
void get_unknown_function_error(const char* code_pos)
|
||||
{
|
||||
const auto function = find_function(code_pos);
|
||||
if (function.has_value())
|
||||
{
|
||||
const auto& pos = function.value();
|
||||
unknown_function_error = std::format(
|
||||
"while processing function '{}' in script '{}':\nunknown script '{}'", pos.first, pos.second, scripting::current_file
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
unknown_function_error = std::format("unknown script '{}'", scripting::current_file);
|
||||
}
|
||||
}
|
||||
|
||||
void get_unknown_function_error(std::uint32_t thread_name)
|
||||
{
|
||||
const auto filename = get_filename_name();
|
||||
const auto name = scripting::get_token(thread_name);
|
||||
|
||||
unknown_function_error = std::format(
|
||||
"while processing script '{}':\nunknown function '{}::{}'", scripting::current_file, filename, name
|
||||
);
|
||||
}
|
||||
|
||||
void compile_error_stub(const char* code_pos, [[maybe_unused]] const char* msg)
|
||||
{
|
||||
get_unknown_function_error(code_pos);
|
||||
game::Com_Error(game::ERR_SCRIPT_DROP, "script link error\n%s", unknown_function_error.data());
|
||||
}
|
||||
|
||||
std::uint32_t find_variable_stub(std::uint32_t parent_id, std::uint32_t thread_name)
|
||||
{
|
||||
const auto res = game::FindVariable(parent_id, thread_name);
|
||||
if (!res)
|
||||
{
|
||||
get_unknown_function_error(thread_name);
|
||||
game::Com_Error(game::ERR_SCRIPT_DROP, "script link error\n%s", unknown_function_error.data());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
unsigned int scr_get_object(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_POINTER)
|
||||
{
|
||||
return value->u.pointerValue;
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not an object", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int scr_get_const_string(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (game::Scr_CastString(value))
|
||||
{
|
||||
assert(value->type == game::VAR_STRING);
|
||||
return value->u.stringValue;
|
||||
}
|
||||
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int scr_get_const_istring(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_ISTRING)
|
||||
{
|
||||
return value->u.stringValue;
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not a localized string", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void scr_validate_localized_string_ref(int parm_index, const char* token, int token_len)
|
||||
{
|
||||
assert(token);
|
||||
assert(token_len >= 0);
|
||||
|
||||
if (token_len < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto char_iter = 0; char_iter < token_len; ++char_iter)
|
||||
{
|
||||
if (!std::isalnum(static_cast<unsigned char>(token[char_iter])) && token[char_iter] != '_')
|
||||
{
|
||||
scr_error(va("Illegal localized string reference: %s must contain only alpha-numeric characters and underscores", token));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scr_get_vector(unsigned int index, float* vector_value)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_VECTOR)
|
||||
{
|
||||
std::memcpy(vector_value, value->u.vectorValue, sizeof(std::float_t[3]));
|
||||
return;
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not a vector", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
}
|
||||
|
||||
int scr_get_int(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_INTEGER)
|
||||
{
|
||||
return value->u.intValue;
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not an int", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
float scr_get_float(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
auto* value = game::scr_VmPub->top - index;
|
||||
if (value->type == game::VAR_FLOAT)
|
||||
{
|
||||
return value->u.floatValue;
|
||||
}
|
||||
|
||||
if (value->type == game::VAR_INTEGER)
|
||||
{
|
||||
return static_cast<float>(value->u.intValue);
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not a float", var_typename[value->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
int scr_get_pointer_type(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
if ((game::scr_VmPub->top - index)->type == game::VAR_POINTER)
|
||||
{
|
||||
return static_cast<int>(game::GetObjectType((game::scr_VmPub->top - index)->u.uintValue));
|
||||
}
|
||||
|
||||
scr_error(va("Type %s is not an object", var_typename[(game::scr_VmPub->top - index)->type]));
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int scr_get_type(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
return (game::scr_VmPub->top - index)->type;
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* scr_get_type_name(unsigned int index)
|
||||
{
|
||||
if (index < game::scr_VmPub->outparamcount)
|
||||
{
|
||||
return var_typename[(game::scr_VmPub->top - index)->type];
|
||||
}
|
||||
|
||||
scr_error(va("Parameter %u does not exist", index + 1));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> find_function(const char* pos)
|
||||
{
|
||||
for (const auto& file : scripting::script_function_table_sort)
|
||||
{
|
||||
for (auto i = file.second.begin(); i != file.second.end() && std::next(i) != file.second.end(); ++i)
|
||||
{
|
||||
const auto next = std::next(i);
|
||||
if (pos >= i->second && pos < next->second)
|
||||
{
|
||||
return {std::make_pair(i->first, file.first)};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
class error final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
scr_emit_function_hook.create(0xBFCF90_b, &scr_emit_function_stub);
|
||||
|
||||
utils::hook::call(0xBFCF3A_b, compile_error_stub); // CompileError (LinkFile)
|
||||
utils::hook::call(0xBFCF86_b, compile_error_stub); // ^
|
||||
utils::hook::call(0xBFD06F_b, find_variable_stub); // Scr_EmitFunction
|
||||
|
||||
// Restore basic error messages for commonly used scr functions
|
||||
utils::hook::jump(0xC0BA10_b, scr_get_object);
|
||||
utils::hook::jump(0xC0B4C0_b, scr_get_const_string);
|
||||
utils::hook::jump(0xC0B270_b, scr_get_const_istring);
|
||||
utils::hook::jump(0xB52210_b, scr_validate_localized_string_ref);
|
||||
utils::hook::jump(0xC0BF40_b, scr_get_vector);
|
||||
utils::hook::jump(0xC0B950_b, scr_get_int);
|
||||
utils::hook::jump(0xC0B7E0_b, scr_get_float);
|
||||
|
||||
utils::hook::jump(0xC0BC00_b, scr_get_pointer_type);
|
||||
utils::hook::jump(0xC0BDE0_b, scr_get_type);
|
||||
utils::hook::jump(0xC0BE50_b, scr_get_type_name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::error)
|
6
src/client/component/gsc/script_error.hpp
Normal file
6
src/client/component/gsc/script_error.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
|
||||
}
|
536
src/client/component/gsc/script_extension.cpp
Normal file
536
src/client/component/gsc/script_extension.cpp
Normal file
@ -0,0 +1,536 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "component/command.hpp"
|
||||
#include "component/console/console.hpp"
|
||||
#include "component/dvars.hpp"
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
#include "script_error.hpp"
|
||||
#include "script_extension.hpp"
|
||||
#include "script_loading.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::uint16_t function_id_start = 806;
|
||||
std::uint16_t method_id_start = 1483 + 0x8000;
|
||||
|
||||
constexpr size_t func_table_count = 0x1000;
|
||||
constexpr size_t meth_table_count = 0x1000;
|
||||
|
||||
builtin_function func_table[func_table_count]{};
|
||||
builtin_method meth_table[meth_table_count]{};
|
||||
|
||||
const game::dvar_t* developer_script = nullptr;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::uint16_t, script_function> functions;
|
||||
std::unordered_map<std::uint16_t, script_method> methods;
|
||||
|
||||
bool force_error_print = false;
|
||||
std::optional<std::string> gsc_error_msg;
|
||||
|
||||
std::unordered_map<const char*, const char*> vm_execute_hooks;
|
||||
const char* target_function = nullptr;
|
||||
|
||||
function_args get_arguments()
|
||||
{
|
||||
std::vector<scripting::script_value> args;
|
||||
|
||||
for (auto i = 0; static_cast<std::uint32_t>(i) < game::scr_VmPub->outparamcount; ++i)
|
||||
{
|
||||
const auto value = game::scr_VmPub->top[-i];
|
||||
args.push_back(value);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
void return_value(const scripting::script_value& value)
|
||||
{
|
||||
if (game::scr_VmPub->outparamcount)
|
||||
{
|
||||
game::Scr_ClearOutParams();
|
||||
}
|
||||
|
||||
scripting::push_value(value);
|
||||
}
|
||||
|
||||
std::uint16_t get_function_id()
|
||||
{
|
||||
const auto pos = game::scr_function_stack->pos;
|
||||
return *reinterpret_cast<std::uint16_t*>(
|
||||
reinterpret_cast<size_t>(pos - 2));
|
||||
}
|
||||
|
||||
void nullstub_func() {}
|
||||
void nullstub_meth(game::scr_entref_t) {}
|
||||
|
||||
void execute_custom_function(const std::uint16_t id)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto& function = functions[id];
|
||||
const auto result = function(get_arguments());
|
||||
const auto type = result.get_raw().type;
|
||||
|
||||
if (type)
|
||||
{
|
||||
return_value(result);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
scr_error(ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void vm_call_builtin_function_internal()
|
||||
{
|
||||
const std::uint16_t function_id = get_function_id();
|
||||
const auto custom_function_id = static_cast<std::uint16_t>(function_id); // cast for gsc-tool & our custom func map
|
||||
const auto custom = functions.contains(custom_function_id);
|
||||
if (custom)
|
||||
{
|
||||
execute_custom_function(custom_function_id);
|
||||
return;
|
||||
}
|
||||
|
||||
builtin_function func = func_table[function_id - 1]; // game does this for the stock func table
|
||||
if (func == nullptr)
|
||||
{
|
||||
scr_error(utils::string::va("builtin function \"%s\" doesn't exist", gsc_ctx->func_name(function_id).data()), true);
|
||||
return;
|
||||
}
|
||||
|
||||
func();
|
||||
}
|
||||
|
||||
void execute_custom_method(const std::uint16_t id, game::scr_entref_t ent_ref)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto& method = methods[id];
|
||||
const auto result = method(ent_ref, get_arguments());
|
||||
const auto type = result.get_raw().type;
|
||||
|
||||
if (type)
|
||||
{
|
||||
return_value(result);
|
||||
}
|
||||
}
|
||||
catch (const std::exception& ex)
|
||||
{
|
||||
scr_error(ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
void builtin_call_error(const std::string& error)
|
||||
{
|
||||
const auto function_id = get_function_id();
|
||||
|
||||
if (function_id > func_table_count)
|
||||
{
|
||||
console::warn("in call to builtin method \"%s\"%s", gsc_ctx->meth_name(function_id).data(), error.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("in call to builtin function \"%s\"%s", gsc_ctx->func_name(function_id).data(), error.data());
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::string> get_opcode_name(const std::uint8_t opcode)
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto index = gsc_ctx->opcode_enum(opcode);
|
||||
return { gsc_ctx->opcode_name(index) };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void print_callstack()
|
||||
{
|
||||
for (auto frame = game::scr_VmPub->function_frame; frame != game::scr_VmPub->function_frame_start; --frame)
|
||||
{
|
||||
const auto pos = frame == game::scr_VmPub->function_frame ? game::scr_function_stack->pos : frame->fs.pos;
|
||||
const auto function = find_function(frame->fs.pos);
|
||||
|
||||
if (function.has_value())
|
||||
{
|
||||
console::warn("\tat function \"%s\" in file \"%s.gsc\"\n", function.value().first.data(), function.value().second.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("\tat unknown location %p\n", pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void vm_error_internal()
|
||||
{
|
||||
const bool dev_script = developer_script ? developer_script->current.enabled : false;
|
||||
|
||||
if (!dev_script && !force_error_print)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
console::warn("*********** script runtime error *************\n");
|
||||
|
||||
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x6B22940_b);
|
||||
const std::string error_str = gsc_error_msg.has_value()
|
||||
? utils::string::va(": %s", gsc_error_msg.value().data())
|
||||
: "";
|
||||
|
||||
if ((opcode_id >= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltin0) && opcode_id <= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltin))
|
||||
|| (opcode_id >= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltinMethod0) && opcode_id <= gsc_ctx->opcode_id(xsk::gsc::opcode::OP_CallBuiltinMethod)))
|
||||
{
|
||||
builtin_call_error(error_str);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto opcode = get_opcode_name(opcode_id);
|
||||
if (opcode.has_value())
|
||||
{
|
||||
console::warn("while processing instruction %s%s\n", opcode.value().data(), error_str.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
console::warn("while processing instruction 0x%X%s\n", opcode_id, error_str.data());
|
||||
}
|
||||
}
|
||||
|
||||
force_error_print = false;
|
||||
gsc_error_msg = {};
|
||||
|
||||
print_callstack();
|
||||
console::warn("**********************************************\n");
|
||||
}
|
||||
|
||||
void vm_error_stub(__int64 mark_pos)
|
||||
{
|
||||
vm_error_internal();
|
||||
|
||||
if (!game::CL_IsGameClientActive(0))
|
||||
{
|
||||
//return game::Com_Error(game::errorParm::ERR_SCRIPT_DROP, gsc_error_msg.has_value() ? gsc_error_msg.value().data() : "Fatal script error");
|
||||
}
|
||||
|
||||
utils::hook::invoke<void>(0x510C80_b, mark_pos);
|
||||
}
|
||||
|
||||
void print(const function_args& args)
|
||||
{
|
||||
std::string buffer{};
|
||||
|
||||
for (auto i = 0u; i < args.size(); ++i)
|
||||
{
|
||||
const auto str = args[i].to_string();
|
||||
buffer.append(str);
|
||||
buffer.append("\t");
|
||||
}
|
||||
console::info("%s\n", buffer.data());
|
||||
}
|
||||
|
||||
scripting::script_value typeof(const function_args& args)
|
||||
{
|
||||
return args[0].type_name();
|
||||
}
|
||||
|
||||
bool get_replaced_pos(const char* pos)
|
||||
{
|
||||
if (vm_execute_hooks.contains(pos))
|
||||
{
|
||||
target_function = vm_execute_hooks[pos];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void vm_execute_stub(utils::hook::assembler& a)
|
||||
{
|
||||
const auto replace = a.newLabel();
|
||||
const auto end = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
|
||||
a.mov(rcx, rsi);
|
||||
a.call_aligned(get_replaced_pos);
|
||||
|
||||
a.cmp(al, 0);
|
||||
a.jne(replace);
|
||||
|
||||
a.popad64();
|
||||
a.jmp(end);
|
||||
|
||||
a.bind(end);
|
||||
|
||||
a.movzx(r15d, byte_ptr(rsi));
|
||||
a.inc(rsi);
|
||||
a.mov(dword_ptr(rbp, 0x94), r15d);
|
||||
|
||||
a.jmp(0xC0D0B2_b);
|
||||
|
||||
a.bind(replace);
|
||||
|
||||
a.popad64();
|
||||
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&target_function)));
|
||||
a.mov(rsi, rax);
|
||||
a.jmp(end);
|
||||
}
|
||||
}
|
||||
|
||||
void scr_error(const char* error, const bool force_print)
|
||||
{
|
||||
force_error_print = force_print;
|
||||
gsc_error_msg = error;
|
||||
|
||||
game::Scr_ErrorInternal();
|
||||
}
|
||||
|
||||
namespace function
|
||||
{
|
||||
void add(const std::string& name, script_function function)
|
||||
{
|
||||
if (gsc_ctx->func_exists(name))
|
||||
{
|
||||
const auto id = gsc_ctx->func_id(name);
|
||||
functions[id] = function;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto id = ++function_id_start;
|
||||
gsc_ctx->func_add(name, id);
|
||||
functions[id] = function;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace method
|
||||
{
|
||||
void add(const std::string& name, script_method method)
|
||||
{
|
||||
if (gsc_ctx->meth_exists(name))
|
||||
{
|
||||
const auto id = gsc_ctx->meth_id(name);
|
||||
methods[id] = method;
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto id = ++method_id_start;
|
||||
gsc_ctx->meth_add(name, static_cast<std::uint16_t>(id));
|
||||
methods[id] = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function_args::function_args(std::vector<scripting::script_value> values)
|
||||
: values_(values)
|
||||
{
|
||||
}
|
||||
|
||||
std::uint32_t function_args::size() const
|
||||
{
|
||||
return static_cast<std::uint32_t>(this->values_.size());
|
||||
}
|
||||
|
||||
std::vector<scripting::script_value> function_args::get_raw() const
|
||||
{
|
||||
return this->values_;
|
||||
}
|
||||
|
||||
scripting::value_wrap function_args::get(const int index) const
|
||||
{
|
||||
if (index >= this->values_.size())
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("parameter %d does not exist", index));
|
||||
}
|
||||
|
||||
return {this->values_[index], index};
|
||||
}
|
||||
|
||||
void vm_call_builtin_method_internal(game::scr_entref_t ent_ref, int function_id)
|
||||
{
|
||||
const auto custom_function_id = static_cast<std::uint16_t>(function_id); // cast for gsc-tool & our custom method map
|
||||
const auto custom = methods.contains(custom_function_id);
|
||||
if (custom)
|
||||
{
|
||||
execute_custom_method(custom_function_id, ent_ref);
|
||||
return;
|
||||
}
|
||||
|
||||
builtin_method meth = meth_table[function_id - 0x8000];
|
||||
if (meth == nullptr)
|
||||
{
|
||||
scr_error(utils::string::va("builtin method \"%s\" doesn't exist", gsc_ctx->meth_name(custom_function_id).data()), true);
|
||||
return;
|
||||
}
|
||||
|
||||
meth(ent_ref);
|
||||
}
|
||||
|
||||
void vm_call_builtin_method_stub(utils::hook::assembler& a)
|
||||
{
|
||||
a.pushad64();
|
||||
a.push(rdx);
|
||||
a.push(ecx);
|
||||
a.mov(rdx, rdi); // function id is stored in rdi
|
||||
a.mov(ecx, ebx); // ent ref is stored in ebx
|
||||
a.call_aligned(vm_call_builtin_method_internal);
|
||||
a.pop(rdx);
|
||||
a.pop(ecx);
|
||||
a.popad64();
|
||||
|
||||
a.jmp(0xC0E8F9_b);
|
||||
}
|
||||
|
||||
class extension final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
developer_script = game::Dvar_RegisterBool("developer_script", true, 0, "Enable developer script comments"); // enable by default for now
|
||||
|
||||
utils::hook::set<uint32_t>(0xBFD16B_b + 1, func_table_count); // change builtin func count
|
||||
utils::hook::set<uint32_t>(0xBFD172_b + 4, static_cast<uint32_t>(reverse_b((&func_table))));
|
||||
utils::hook::inject(0xBFD5A1_b + 3, &func_table);
|
||||
utils::hook::set<uint32_t>(0xBFD595_b + 2, sizeof(func_table));
|
||||
utils::hook::nop(0xC0E5CE_b, 7);
|
||||
utils::hook::call(0xC0E5CE_b, vm_call_builtin_function_internal);
|
||||
|
||||
utils::hook::set<uint32_t>(0xBFD182_b + 4, static_cast<uint32_t>(reverse_b((&meth_table))));
|
||||
utils::hook::inject(0xBFD5AF_b + 3, &meth_table);
|
||||
utils::hook::set<uint32_t>(0xBFD5B6_b + 2, sizeof(meth_table));
|
||||
utils::hook::nop(0xC0E8EB_b, 14); // nop the lea & call at the end of call_builtin_method
|
||||
utils::hook::jump(0xC0E8EB_b, utils::hook::assemble(vm_call_builtin_method_stub), true);
|
||||
|
||||
utils::hook::call(0xC0F8C1_b, vm_error_stub); // LargeLocalResetToMark
|
||||
|
||||
utils::hook::jump(0xC0D0A4_b, utils::hook::assemble(vm_execute_stub), true);
|
||||
|
||||
/*
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
function::add("isusingmatchrulesdata", [](const function_args& args)
|
||||
{
|
||||
// return 0 so the game doesn't override the cfg
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
*/
|
||||
|
||||
function::add("print", [](const function_args& args)
|
||||
{
|
||||
print(args);
|
||||
return scripting::script_value{};
|
||||
});
|
||||
|
||||
function::add("println", [](const function_args& args)
|
||||
{
|
||||
print(args);
|
||||
return scripting::script_value{};
|
||||
});
|
||||
|
||||
function::add("assert", [](const function_args& args)
|
||||
{
|
||||
const auto expr = args[0].as<int>();
|
||||
if (!expr)
|
||||
{
|
||||
throw std::runtime_error("assert fail");
|
||||
}
|
||||
|
||||
return scripting::script_value{};
|
||||
});
|
||||
|
||||
function::add("assertex", [](const function_args& args)
|
||||
{
|
||||
const auto expr = args[0].as<int>();
|
||||
if (!expr)
|
||||
{
|
||||
const auto error = args[1].as<std::string>();
|
||||
throw std::runtime_error(error);
|
||||
}
|
||||
|
||||
return scripting::script_value{};
|
||||
});
|
||||
|
||||
function::add("getfunction", [](const function_args& args)
|
||||
{
|
||||
const auto filename = args[0].as<std::string>();
|
||||
const auto function = args[1].as<std::string>();
|
||||
|
||||
if (!scripting::script_function_table[filename].contains(function))
|
||||
{
|
||||
throw std::runtime_error("function not found");
|
||||
}
|
||||
|
||||
return scripting::function{scripting::script_function_table[filename][function]};
|
||||
});
|
||||
|
||||
function::add("replacefunc", [](const function_args& args)
|
||||
{
|
||||
const auto what = args[0].get_raw();
|
||||
const auto with = args[1].get_raw();
|
||||
|
||||
if (what.type != game::VAR_FUNCTION || with.type != game::VAR_FUNCTION)
|
||||
{
|
||||
throw std::runtime_error("replacefunc: parameter 1 must be a function");
|
||||
}
|
||||
|
||||
vm_execute_hooks[what.u.codePosValue] = with.u.codePosValue;
|
||||
|
||||
return scripting::script_value{};
|
||||
});
|
||||
|
||||
function::add("toupper", [](const function_args& args)
|
||||
{
|
||||
const auto string = args[0].as<std::string>();
|
||||
return utils::string::to_upper(string);
|
||||
});
|
||||
|
||||
/*
|
||||
function::add("logprint", [](const function_args& args)
|
||||
{
|
||||
std::string buffer{};
|
||||
|
||||
for (auto i = 0u; i < args.size(); ++i)
|
||||
{
|
||||
const auto string = args[i].as<std::string>();
|
||||
buffer.append(string);
|
||||
}
|
||||
|
||||
game::G_LogPrintf("%s", buffer.data());
|
||||
|
||||
return scripting::script_value{};
|
||||
});
|
||||
*/
|
||||
|
||||
function::add("executecommand", [](const function_args& args)
|
||||
{
|
||||
command::execute(args[0].as<std::string>(), false);
|
||||
|
||||
return scripting::script_value{};
|
||||
});
|
||||
|
||||
function::add("typeof", typeof);
|
||||
function::add("type", typeof);
|
||||
|
||||
method::add("test_method", [](game::scr_entref_t ent_ref, const function_args& args)
|
||||
{
|
||||
print(args);
|
||||
return scripting::script_value{};
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::extension)
|
48
src/client/component/gsc/script_extension.hpp
Normal file
48
src/client/component/gsc/script_extension.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "game/scripting/array.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "game/scripting/function.hpp"
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
class function_args
|
||||
{
|
||||
public:
|
||||
function_args(std::vector<scripting::script_value>);
|
||||
|
||||
unsigned int size() const;
|
||||
std::vector<scripting::script_value> get_raw() const;
|
||||
scripting::value_wrap get(const int index) const;
|
||||
|
||||
scripting::value_wrap operator[](const int index) const
|
||||
{
|
||||
return this->get(index);
|
||||
}
|
||||
private:
|
||||
std::vector<scripting::script_value> values_;
|
||||
};
|
||||
|
||||
using builtin_function = void(*)();
|
||||
using builtin_method = void(*)(game::scr_entref_t);
|
||||
|
||||
using script_function = std::function<scripting::script_value(const function_args&)>;
|
||||
using script_method = std::function<scripting::script_value(const game::scr_entref_t, const function_args&)>;
|
||||
|
||||
extern builtin_function func_table[0x1000];
|
||||
extern builtin_method meth_table[0x1000];
|
||||
|
||||
extern const game::dvar_t* developer_script;
|
||||
|
||||
void scr_error(const char* error, const bool force_print = false);
|
||||
|
||||
namespace function
|
||||
{
|
||||
void add(const std::string& name, script_function function);
|
||||
}
|
||||
|
||||
namespace method
|
||||
{
|
||||
void add(const std::string& name, script_method function);
|
||||
}
|
||||
}
|
471
src/client/component/gsc/script_loading.cpp
Normal file
471
src/client/component/gsc/script_loading.cpp
Normal file
@ -0,0 +1,471 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "component/console/console.hpp"
|
||||
#include "component/fastfiles.hpp"
|
||||
#include "component/filesystem.hpp"
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
#include "script_extension.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "script_loading.hpp"
|
||||
|
||||
#include <utils/compression.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::unique_ptr<xsk::gsc::iw7::context> gsc_ctx = std::make_unique<xsk::gsc::iw7::context>();;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour scr_begin_load_scripts_hook;
|
||||
utils::hook::detour scr_end_load_scripts_hook;
|
||||
|
||||
std::unordered_map<std::string, std::uint32_t> main_handles;
|
||||
std::unordered_map<std::string, std::uint32_t> init_handles;
|
||||
|
||||
utils::memory::allocator scriptfile_allocator;
|
||||
std::unordered_map<std::string, game::ScriptFile*> loaded_scripts;
|
||||
|
||||
char* script_mem_buf = nullptr;
|
||||
std::uint64_t script_mem_buf_size = 0;
|
||||
|
||||
struct
|
||||
{
|
||||
char* buf = nullptr;
|
||||
char* pos = nullptr;
|
||||
const std::uint64_t size = 0x100000i64;
|
||||
} script_memory;
|
||||
|
||||
char* allocate_buffer(size_t size)
|
||||
{
|
||||
if (script_memory.buf == nullptr)
|
||||
{
|
||||
script_memory.buf = script_mem_buf;
|
||||
script_memory.pos = script_memory.buf;
|
||||
}
|
||||
|
||||
if (script_memory.pos + size > script_memory.buf + script_memory.size)
|
||||
{
|
||||
game::Com_Error(game::ERR_FATAL, "Out of custom script memory");
|
||||
}
|
||||
|
||||
const auto pos = script_memory.pos;
|
||||
script_memory.pos += size;
|
||||
return pos;
|
||||
}
|
||||
|
||||
void free_script_memory()
|
||||
{
|
||||
if (script_memory.buf != nullptr)
|
||||
{
|
||||
memset(script_memory.buf, 0, reinterpret_cast<size_t>(script_memory.pos) - reinterpret_cast<size_t>(script_memory.buf));
|
||||
script_memory.buf = nullptr;
|
||||
script_memory.pos = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
main_handles.clear();
|
||||
init_handles.clear();
|
||||
loaded_scripts.clear();
|
||||
scriptfile_allocator.clear();
|
||||
free_script_memory();
|
||||
}
|
||||
|
||||
bool read_raw_script_file(const std::string& name, std::string* data)
|
||||
{
|
||||
if (filesystem::read_file(name, data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto* name_str = name.data();
|
||||
if (game::DB_XAssetExists(game::ASSET_TYPE_RAWFILE, name_str) &&
|
||||
!game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, name_str))
|
||||
{
|
||||
const auto asset = game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, name_str, false);
|
||||
const auto len = game::DB_GetRawFileLen(asset.rawfile);
|
||||
data->resize(len);
|
||||
game::DB_GetRawBuffer(asset.rawfile, data->data(), len);
|
||||
if (len > 0)
|
||||
{
|
||||
data->pop_back();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
game::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name)
|
||||
{
|
||||
if (const auto itr = loaded_scripts.find(file_name); itr != loaded_scripts.end())
|
||||
{
|
||||
return itr->second;
|
||||
}
|
||||
|
||||
if (game::Com_FrontEnd_IsInFrontEnd())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string source_buffer{};
|
||||
if (!read_raw_script_file(real_name + ".gsc", &source_buffer) || source_buffer.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// filter out "GSC rawfiles" that were used for development usage and are not meant for us.
|
||||
// each "GSC rawfile" has a ScriptFile counterpart to be used instead
|
||||
if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, file_name) &&
|
||||
!game::DB_IsXAssetDefault(game::ASSET_TYPE_SCRIPTFILE, file_name))
|
||||
{
|
||||
if (real_name.starts_with(utils::string::va("scripts/%s/maps/", game::Com_GameMode_GetActiveGameModeStr()))
|
||||
&& (real_name.ends_with("_fx") || real_name.ends_with("_sound")))
|
||||
{
|
||||
console::debug("Refusing to compile rawfile '%s'\n", real_name.data());
|
||||
return game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, file_name, false).scriptfile;
|
||||
}
|
||||
}
|
||||
|
||||
console::debug("Loading custom gsc '%s.gsc'", real_name.data());
|
||||
|
||||
try
|
||||
{
|
||||
auto& compiler = gsc_ctx->compiler();
|
||||
auto& assembler = gsc_ctx->assembler();
|
||||
|
||||
std::vector<std::uint8_t> data;
|
||||
data.assign(source_buffer.begin(), source_buffer.end());
|
||||
|
||||
const auto assembly_ptr = compiler.compile(real_name, data);
|
||||
const auto output_script = assembler.assemble(*assembly_ptr);
|
||||
|
||||
const auto bytecode = output_script.first;
|
||||
const auto stack = output_script.second;
|
||||
|
||||
const auto script_file_ptr = static_cast<game::ScriptFile*>(scriptfile_allocator.allocate(sizeof(game::ScriptFile)));
|
||||
script_file_ptr->name = file_name;
|
||||
|
||||
script_file_ptr->len = static_cast<int>(stack.size);
|
||||
script_file_ptr->bytecodeLen = static_cast<int>(bytecode.size);
|
||||
|
||||
const auto stack_size = static_cast<std::uint32_t>(stack.size + 1);
|
||||
const auto byte_code_size = static_cast<std::uint32_t>(bytecode.size + 1);
|
||||
|
||||
script_file_ptr->buffer = static_cast<char*>(scriptfile_allocator.allocate(stack_size));
|
||||
std::memcpy(const_cast<char*>(script_file_ptr->buffer), stack.data, stack.size);
|
||||
|
||||
script_file_ptr->bytecode = allocate_buffer(byte_code_size);
|
||||
std::memcpy(script_file_ptr->bytecode, bytecode.data, bytecode.size);
|
||||
|
||||
script_file_ptr->compressedLen = 0;
|
||||
|
||||
loaded_scripts[file_name] = script_file_ptr;
|
||||
|
||||
console::debug("Loaded custom gsc '%s.gsc'", real_name.data());
|
||||
|
||||
return script_file_ptr;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("*********** script compile error *************\n");
|
||||
console::error("failed to compile '%s':\n%s", real_name.data(), e.what());
|
||||
console::error("**********************************************\n");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_raw_script_file_name(const std::string& name)
|
||||
{
|
||||
if (name.ends_with(".gsh"))
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return name + ".gsc";
|
||||
}
|
||||
|
||||
std::string get_script_file_name(const std::string& name)
|
||||
{
|
||||
const auto id = gsc_ctx->token_id(name);
|
||||
if (!id)
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
return std::to_string(id);
|
||||
}
|
||||
|
||||
std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>> read_compiled_script_file(const std::string& name, const std::string& real_name)
|
||||
{
|
||||
const auto* script_file = game::DB_FindXAssetHeader(game::ASSET_TYPE_SCRIPTFILE, name.data(), false).scriptfile;
|
||||
if (script_file == nullptr)
|
||||
{
|
||||
throw std::runtime_error(std::format("Could not load scriptfile '{}'", real_name));
|
||||
}
|
||||
|
||||
console::debug("Decompiling scriptfile '%s'\n", real_name.data());
|
||||
|
||||
const auto len = script_file->compressedLen;
|
||||
const std::string stack{script_file->buffer, static_cast<std::uint32_t>(len)};
|
||||
|
||||
const auto decompressed_stack = utils::compression::zlib::decompress(stack);
|
||||
|
||||
std::vector<std::uint8_t> stack_data;
|
||||
stack_data.assign(decompressed_stack.begin(), decompressed_stack.end());
|
||||
|
||||
return {{reinterpret_cast<std::uint8_t*>(script_file->bytecode), static_cast<std::uint32_t>(script_file->bytecodeLen)}, stack_data};
|
||||
}
|
||||
|
||||
void load_script(const std::string& name)
|
||||
{
|
||||
if (!game::Scr_LoadScript(name.data()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto main_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("main"));
|
||||
const auto init_handle = game::Scr_GetFunctionHandle(name.data(), gsc_ctx->token_id("init"));
|
||||
|
||||
if (main_handle)
|
||||
{
|
||||
console::debug("Loaded '%s::main'\n", name.data());
|
||||
main_handles[name] = main_handle;
|
||||
}
|
||||
|
||||
if (init_handle)
|
||||
{
|
||||
console::debug("Loaded '%s::init'\n", name.data());
|
||||
init_handles[name] = init_handle;
|
||||
}
|
||||
}
|
||||
|
||||
void load_scripts(const std::filesystem::path& root_dir, const std::filesystem::path& subfolder)
|
||||
{
|
||||
std::filesystem::path script_dir = root_dir / subfolder;
|
||||
if (!utils::io::directory_exists(script_dir.generic_string()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scripts = utils::io::list_files(script_dir.generic_string());
|
||||
for (const auto& script : scripts)
|
||||
{
|
||||
if (!script.ends_with(".gsc"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::filesystem::path path(script);
|
||||
const auto relative = path.lexically_relative(root_dir).generic_string();
|
||||
const auto base_name = relative.substr(0, relative.size() - 4);
|
||||
|
||||
load_script(base_name);
|
||||
}
|
||||
}
|
||||
|
||||
void load_scripts()
|
||||
{
|
||||
if (!game::Com_FrontEnd_IsInFrontEnd())
|
||||
{
|
||||
for (const auto& path : filesystem::get_search_paths())
|
||||
{
|
||||
load_scripts(path, "scripts/"); // meant to override stock GSC
|
||||
load_scripts(path, "custom_scripts/"); // for no issues, use custom_scripts/
|
||||
load_scripts(path, "custom_scripts/"s + game::Com_GameMode_GetActiveGameModeStr() + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_compiler()
|
||||
{
|
||||
const bool dev_script = developer_script ? developer_script->current.enabled : false;
|
||||
const auto comp_mode = dev_script ?
|
||||
xsk::gsc::build::dev :
|
||||
xsk::gsc::build::prod;
|
||||
|
||||
gsc_ctx->init(comp_mode, [](const std::string& include_name)
|
||||
-> std::pair<xsk::gsc::buffer, std::vector<std::uint8_t>>
|
||||
{
|
||||
const auto real_name = get_raw_script_file_name(include_name);
|
||||
|
||||
std::string file_buffer;
|
||||
if (!read_raw_script_file(real_name, &file_buffer) || file_buffer.empty())
|
||||
{
|
||||
const auto name = get_script_file_name(include_name);
|
||||
if (game::DB_XAssetExists(game::ASSET_TYPE_SCRIPTFILE, name.data()))
|
||||
{
|
||||
return read_compiled_script_file(name, real_name);
|
||||
}
|
||||
|
||||
throw std::runtime_error(std::format("Could not load gsc file '{}'", real_name));
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> script_data;
|
||||
script_data.assign(file_buffer.begin(), file_buffer.end());
|
||||
|
||||
return { {}, script_data };
|
||||
});
|
||||
}
|
||||
|
||||
void scr_begin_load_scripts_stub(bool a1)
|
||||
{
|
||||
// start the compiler
|
||||
init_compiler();
|
||||
|
||||
scr_begin_load_scripts_hook.invoke<void>(a1);
|
||||
|
||||
// load scripts
|
||||
load_scripts();
|
||||
}
|
||||
|
||||
void scr_end_load_scripts_stub(const char* a1)
|
||||
{
|
||||
// cleanup the compiler
|
||||
gsc_ctx->cleanup();
|
||||
|
||||
scr_end_load_scripts_hook.invoke<void>(a1);
|
||||
}
|
||||
|
||||
utils::hook::detour g_load_structs_hook;
|
||||
void g_load_structs_stub(float a1)
|
||||
{
|
||||
for (auto& function_handle : main_handles)
|
||||
{
|
||||
console::debug("Executing '%s::main'\n", function_handle.first.data());
|
||||
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
|
||||
}
|
||||
|
||||
g_load_structs_hook.invoke<void>(a1);
|
||||
}
|
||||
|
||||
utils::hook::detour scr_load_level_hook;
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
for (auto& function_handle : init_handles)
|
||||
{
|
||||
console::debug("Executing '%s::init'\n", function_handle.first.data());
|
||||
game::RemoveRefToObject(game::Scr_ExecThread(function_handle.second, 0));
|
||||
}
|
||||
|
||||
scr_load_level_hook.invoke<void>();
|
||||
}
|
||||
|
||||
int db_is_x_asset_default(game::XAssetType type, const char* name)
|
||||
{
|
||||
if (loaded_scripts.contains(name))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return game::DB_IsXAssetDefault(type, name);
|
||||
}
|
||||
|
||||
void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size)
|
||||
{
|
||||
if (rawfile->len > 0 && rawfile->compressedLen == 0)
|
||||
{
|
||||
std::memset(buf, 0, size);
|
||||
std::memcpy(buf, rawfile->buffer, std::min(rawfile->len, size));
|
||||
return;
|
||||
}
|
||||
|
||||
game::DB_GetRawBuffer(rawfile, buf, size);
|
||||
}
|
||||
|
||||
// donetsk developers will paste this in days
|
||||
utils::hook::detour db_alloc_x_zone_memory_internal_hook;
|
||||
void db_alloc_x_zone_memory_internal_stub(unsigned __int64* blockSize, const char* filename, game::XZoneMemory* zoneMem, unsigned int type)
|
||||
{
|
||||
bool patch = false; // ugly fix for script memory allocation
|
||||
if (!_stricmp(filename, "code_post_gfx") && type == 2)
|
||||
{
|
||||
patch = true;
|
||||
console::debug("patching memory for '%s'\n", filename);
|
||||
}
|
||||
|
||||
if (patch)
|
||||
{
|
||||
blockSize[game::XFILE_BLOCK_SCRIPT] += script_memory.size;
|
||||
}
|
||||
|
||||
db_alloc_x_zone_memory_internal_hook.invoke<void>(blockSize, filename, zoneMem, type);
|
||||
|
||||
if (patch)
|
||||
{
|
||||
blockSize[game::XFILE_BLOCK_SCRIPT] -= script_memory.size;
|
||||
script_mem_buf = zoneMem->blocks[game::XFILE_BLOCK_SCRIPT].alloc + blockSize[game::XFILE_BLOCK_SCRIPT];
|
||||
script_mem_buf_size = script_memory.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default)
|
||||
{
|
||||
std::string real_name = name;
|
||||
const auto id = static_cast<std::uint16_t>(std::atoi(name));
|
||||
if (id)
|
||||
{
|
||||
real_name = gsc_ctx->token_name(id);
|
||||
}
|
||||
|
||||
auto* script = load_custom_script(name, real_name);
|
||||
if (script)
|
||||
{
|
||||
return script;
|
||||
}
|
||||
|
||||
return game::DB_FindXAssetHeader(type, name, allow_create_default).scriptfile;
|
||||
}
|
||||
|
||||
class loading final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Allocate script memory (PMem doesn't work)
|
||||
db_alloc_x_zone_memory_internal_hook.create(0xA75450_b, db_alloc_x_zone_memory_internal_stub);
|
||||
|
||||
// Increase allocated script memory
|
||||
utils::hook::set<uint32_t>(0xA75B5C_b + 1, 0x480000 + static_cast<std::uint32_t>(script_memory.size));
|
||||
utils::hook::set<uint32_t>(0xA75BAA_b + 4, 0x480 + (static_cast<std::uint32_t>(script_memory.size) >> 12));
|
||||
utils::hook::set<uint32_t>(0xA75BBE_b + 6, 0x480 + (static_cast<std::uint32_t>(script_memory.size) >> 12));
|
||||
|
||||
// Load our scripts with an uncompressed stack
|
||||
utils::hook::call(0xC09DA7_b, db_get_raw_buffer_stub);
|
||||
|
||||
// Compiler start and cleanup, also loads scripts
|
||||
scr_begin_load_scripts_hook.create(0xBFD500_b, scr_begin_load_scripts_stub);
|
||||
scr_end_load_scripts_hook.create(0xBFD630_b, scr_end_load_scripts_stub);
|
||||
|
||||
// ProcessScript: hook xasset functions to return our own custom scripts
|
||||
utils::hook::call(0xC09D37_b, find_script);
|
||||
utils::hook::call(0xC09D47_b, db_is_x_asset_default);
|
||||
|
||||
// execute main handle
|
||||
g_load_structs_hook.create(0x409FB0_b, g_load_structs_stub);
|
||||
|
||||
// execute init handle
|
||||
scr_load_level_hook.create(0xB51B40_b, scr_load_level_stub);
|
||||
|
||||
scripting::on_shutdown([](bool free_scripts, bool post_shutdown)
|
||||
{
|
||||
if (free_scripts && post_shutdown)
|
||||
{
|
||||
clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::loading)
|
9
src/client/component/gsc/script_loading.hpp
Normal file
9
src/client/component/gsc/script_loading.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <xsk/gsc/engine/iw7.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
extern std::unique_ptr<xsk::gsc::iw7::context> gsc_ctx;
|
||||
|
||||
game::ScriptFile* find_script(game::XAssetType type, const char* name, int allow_create_default);
|
||||
}
|
@ -7,6 +7,8 @@
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "version.h"
|
||||
|
||||
namespace logger
|
||||
{
|
||||
namespace
|
||||
@ -77,6 +79,27 @@ namespace logger
|
||||
vsnprintf(buffer, buffer_length, msg, va);
|
||||
console::warn(buffer);
|
||||
}
|
||||
|
||||
void com_init_pre()
|
||||
{
|
||||
console::info("%s %s build %s %s\n", "IW7", VERSION, "win64", __DATE__);
|
||||
|
||||
console::info("--- Common Initialization ---\n");
|
||||
}
|
||||
|
||||
void com_init_post()
|
||||
{
|
||||
console::info("--- Common Initialization Complete ---\n");
|
||||
|
||||
console::info("Working directory: %s\n", game::Sys_Cwd());
|
||||
}
|
||||
|
||||
void com_init_stub(void* a1)
|
||||
{
|
||||
com_init_pre();
|
||||
utils::hook::invoke<void>(0xB8EF90_b, a1);
|
||||
com_init_post();
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
@ -93,6 +116,8 @@ namespace logger
|
||||
// Com_Printf
|
||||
utils::hook::jump(0x343080_b, print_info);
|
||||
|
||||
utils::hook::call(0xD4D8D8_b, com_init_stub);
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
// R_WarnOncePerFrame
|
||||
|
@ -198,10 +198,16 @@ namespace patches
|
||||
|
||||
char* db_read_raw_file_stub(const char* filename, char* buf, const int size)
|
||||
{
|
||||
const auto file = game::filesystem::file(filename);
|
||||
if (file.exists())
|
||||
std::string file_name = filename;
|
||||
if (file_name.find(".cfg") == std::string::npos)
|
||||
{
|
||||
snprintf(buf, size, "%s\n", file.get_buffer().data());
|
||||
file_name.append(".cfg");
|
||||
}
|
||||
|
||||
std::string buffer{};
|
||||
if (filesystem::read_file(file_name, &buffer))
|
||||
{
|
||||
snprintf(buf, size, "%s\n", buffer.data());
|
||||
return buf;
|
||||
}
|
||||
|
||||
@ -301,6 +307,9 @@ namespace patches
|
||||
|
||||
// block changing name in-game
|
||||
utils::hook::set<uint8_t>(0xC4DF90_b, 0xC3);
|
||||
|
||||
// disable host migration
|
||||
utils::hook::set<uint8_t>(0xC5A200_b, 0xC3);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
39
src/client/component/ranked.cpp
Normal file
39
src/client/component/ranked.cpp
Normal file
@ -0,0 +1,39 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "component/dvars.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
|
||||
namespace ranked
|
||||
{
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// This must be registered as 'true' to avoid crash when starting a private match
|
||||
dvars::override::register_bool("xblive_privatematch", true, game::DVAR_FLAG_REPLICATED);
|
||||
|
||||
if (game::environment::is_dedi() && !utils::flags::has_flag("unranked"))
|
||||
{
|
||||
dvars::override::register_bool("xblive_privatematch", false, game::DVAR_FLAG_REPLICATED | game::DVAR_FLAG_WRITE); // DVAR_FLAG_REPLICATED needed?
|
||||
|
||||
game::Dvar_RegisterBool("onlinegame", true, game::DVAR_FLAG_READ, "Current game is an online game with stats, custom classes, unlocks");
|
||||
|
||||
// Fix sessionteam always returning none (SV_ClientMP_HasAssignedTeam_Internal)
|
||||
utils::hook::set(0xC50BC0_b, 0xC300B0);
|
||||
}
|
||||
}
|
||||
|
||||
component_priority priority() override
|
||||
{
|
||||
return component_priority::ranked;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(ranked::component)
|
285
src/client/component/scripting.cpp
Normal file
285
src/client/component/scripting.cpp
Normal file
@ -0,0 +1,285 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "component/gsc/script_extension.hpp"
|
||||
#include "component/gsc/script_loading.hpp"
|
||||
#include "component/scheduler.hpp"
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
#include "component/console/console.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "game/scripting/event.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
|
||||
std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
|
||||
|
||||
std::string current_file;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour vm_notify_hook;
|
||||
|
||||
utils::hook::detour scr_add_class_field_hook;
|
||||
|
||||
utils::hook::detour scr_set_thread_position_hook;
|
||||
utils::hook::detour process_script_hook;
|
||||
|
||||
utils::hook::detour sl_get_canonical_string_hook;
|
||||
|
||||
std::string current_script_file;
|
||||
unsigned int current_file_id{};
|
||||
|
||||
std::vector<std::function<void(bool, bool)>> shutdown_callbacks;
|
||||
|
||||
std::unordered_map<unsigned int, std::string> canonical_string_table;
|
||||
|
||||
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
|
||||
game::VariableValue* top)
|
||||
{
|
||||
if (!game::Com_FrontEnd_IsInFrontEnd())
|
||||
{
|
||||
const auto* string = game::SL_ConvertToString(string_value);
|
||||
if (string)
|
||||
{
|
||||
event e{};
|
||||
e.name = string;
|
||||
e.entity = notify_list_owner_id;
|
||||
|
||||
for (auto* value = top; value->type != game::VAR_PRECODEPOS; --value)
|
||||
{
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
|
||||
}
|
||||
|
||||
void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t name, unsigned int canonical_string, unsigned int offset)
|
||||
{
|
||||
const auto name_str = game::SL_ConvertToString(name);
|
||||
|
||||
if (fields_table[classnum].find(name_str) == fields_table[classnum].end())
|
||||
{
|
||||
fields_table[classnum][name_str] = offset;
|
||||
}
|
||||
|
||||
scr_add_class_field_hook.invoke<void>(classnum, name, canonical_string, offset);
|
||||
}
|
||||
|
||||
void process_script_stub(const char* filename)
|
||||
{
|
||||
current_script_file = filename;
|
||||
|
||||
const auto file_id = atoi(filename);
|
||||
if (file_id)
|
||||
{
|
||||
current_file_id = static_cast<std::uint16_t>(file_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
current_file_id = 0;
|
||||
current_file = filename;
|
||||
}
|
||||
|
||||
process_script_hook.invoke<void>(filename);
|
||||
}
|
||||
|
||||
void add_function_sort(unsigned int id, const char* pos)
|
||||
{
|
||||
std::string filename = current_file;
|
||||
if (current_file_id)
|
||||
{
|
||||
filename = scripting::get_token(current_file_id);
|
||||
}
|
||||
|
||||
if (!script_function_table_sort.contains(filename))
|
||||
{
|
||||
const auto script = gsc::find_script(game::ASSET_TYPE_SCRIPTFILE, current_script_file.data(), false);
|
||||
if (script)
|
||||
{
|
||||
const auto end = &script->bytecode[script->bytecodeLen];
|
||||
script_function_table_sort[filename].emplace_back("__end__", end);
|
||||
}
|
||||
}
|
||||
|
||||
const auto name = scripting::get_token(id);
|
||||
auto& itr = script_function_table_sort[filename];
|
||||
itr.insert(itr.end() - 1, {name, pos});
|
||||
}
|
||||
|
||||
void add_function(const std::string& file, unsigned int id, const char* pos)
|
||||
{
|
||||
const auto name = get_token(id);
|
||||
script_function_table[file][name] = pos;
|
||||
script_function_table_rev[pos] = {file, name};
|
||||
}
|
||||
|
||||
void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos)
|
||||
{
|
||||
add_function_sort(thread_name, code_pos);
|
||||
|
||||
if (current_file_id)
|
||||
{
|
||||
const auto name = get_token(current_file_id);
|
||||
add_function(name, thread_name, code_pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
add_function(current_file, thread_name, code_pos);
|
||||
}
|
||||
|
||||
scr_set_thread_position_hook.invoke<void>(thread_name, code_pos);
|
||||
}
|
||||
|
||||
unsigned int sl_get_canonical_string_stub(const char* str)
|
||||
{
|
||||
const auto result = sl_get_canonical_string_hook.invoke<unsigned int>(str);
|
||||
canonical_string_table[result] = str;
|
||||
return result;
|
||||
}
|
||||
|
||||
void shutdown_game_pre(const int free_scripts)
|
||||
{
|
||||
if (free_scripts)
|
||||
{
|
||||
script_function_table_sort.clear();
|
||||
script_function_table.clear();
|
||||
script_function_table_rev.clear();
|
||||
canonical_string_table.clear();
|
||||
}
|
||||
|
||||
for (const auto& callback : shutdown_callbacks)
|
||||
{
|
||||
callback(free_scripts, false);
|
||||
}
|
||||
|
||||
scripting::notify(*game::levelEntityId, "shutdownGame_called", { 1 });
|
||||
}
|
||||
|
||||
void shutdown_game_post(const int free_scripts)
|
||||
{
|
||||
for (const auto& callback : shutdown_callbacks)
|
||||
{
|
||||
callback(free_scripts, true);
|
||||
}
|
||||
}
|
||||
|
||||
namespace mp
|
||||
{
|
||||
utils::hook::detour sv_initgame_vm_hook;
|
||||
utils::hook::detour sv_shutdowngame_vm_hook;
|
||||
|
||||
void sv_initgame_vm_stub(game::sv::SvServerInitSettings* init_settings)
|
||||
{
|
||||
if (!game::Com_FrontEnd_IsInFrontEnd())
|
||||
{
|
||||
console::info("------- Game Initialization -------\n");
|
||||
console::info("gamename: %s\n", "IW7");
|
||||
console::info("gamedate: %s\n", __DATE__);
|
||||
|
||||
//G_LogPrintf("------------------------------------------------------------\n");
|
||||
//G_LogPrintf("InitGame: %s\n", serverinfo);
|
||||
}
|
||||
|
||||
sv_initgame_vm_hook.invoke<void>(init_settings);
|
||||
|
||||
if (!game::Com_FrontEnd_IsInFrontEnd())
|
||||
{
|
||||
console::info("-----------------------------------\n");
|
||||
}
|
||||
}
|
||||
|
||||
void sv_shutdowngame_vm_stub(int full_clear, int a2)
|
||||
{
|
||||
if (!game::Com_FrontEnd_IsInFrontEnd())
|
||||
{
|
||||
console::info("==== ShutdownGame (%d) ====\n", full_clear);
|
||||
|
||||
//G_LogPrintf("ShutdownGame:\n");
|
||||
//G_LogPrintf("------------------------------------------------------------\n");
|
||||
}
|
||||
|
||||
shutdown_game_pre(full_clear);
|
||||
sv_shutdowngame_vm_hook.invoke<void>(full_clear, a2);
|
||||
shutdown_game_post(full_clear);
|
||||
}
|
||||
}
|
||||
|
||||
namespace sp
|
||||
{
|
||||
utils::hook::detour sv_initgame_vm_hook;
|
||||
utils::hook::detour sv_shutdowngame_vm_hook;
|
||||
|
||||
void sv_initgame_vm_stub(int random_seed, int restart, int* savegame, void** save, int load_scripts)
|
||||
{
|
||||
sv_initgame_vm_hook.invoke<void>(random_seed, restart, savegame, save, load_scripts);
|
||||
}
|
||||
|
||||
void sv_shutdowngame_vm_stub(int full_clear, int a2)
|
||||
{
|
||||
shutdown_game_pre(full_clear);
|
||||
sv_shutdowngame_vm_hook.invoke<void>(full_clear, a2);
|
||||
shutdown_game_post(full_clear);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_token(unsigned int id)
|
||||
{
|
||||
if (canonical_string_table.find(id) != canonical_string_table.end())
|
||||
{
|
||||
return canonical_string_table[id];
|
||||
}
|
||||
|
||||
return scripting::find_token(id);
|
||||
}
|
||||
|
||||
void on_shutdown(const std::function<void(bool, bool)>& callback)
|
||||
{
|
||||
shutdown_callbacks.push_back(callback);
|
||||
}
|
||||
|
||||
std::optional<std::string> get_canonical_string(const unsigned int id)
|
||||
{
|
||||
if (canonical_string_table.find(id) == canonical_string_table.end())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return {canonical_string_table[id]};
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
vm_notify_hook.create(0xC10460_b, vm_notify_stub);
|
||||
|
||||
scr_add_class_field_hook.create(0xC061F0_b, scr_add_class_field_stub);
|
||||
|
||||
scr_set_thread_position_hook.create(0xBFD190_b, scr_set_thread_position_stub);
|
||||
process_script_hook.create(0xC09D20_b, process_script_stub);
|
||||
sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub);
|
||||
|
||||
mp::sv_initgame_vm_hook.create(0xBA3428D_b, mp::sv_initgame_vm_stub);
|
||||
sp::sv_initgame_vm_hook.create(0xBED4A96_b, sp::sv_initgame_vm_stub);
|
||||
mp::sv_shutdowngame_vm_hook.create(0xBB36D86_b, mp::sv_shutdowngame_vm_stub);
|
||||
sp::sv_shutdowngame_vm_hook.create(0x12159B6_b, sp::sv_shutdowngame_vm_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scripting::component)
|
16
src/client/component/scripting.hpp
Normal file
16
src/client/component/scripting.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
extern std::unordered_map<std::string, std::vector<std::pair<std::string, const char*>>> script_function_table_sort;
|
||||
extern std::unordered_map<const char*, std::pair<std::string, std::string>> script_function_table_rev;
|
||||
|
||||
extern std::string current_file;
|
||||
|
||||
void on_shutdown(const std::function<void(bool, bool)>& callback);
|
||||
std::optional<std::string> get_canonical_string(const unsigned int id);
|
||||
std::string get_token(unsigned int id);
|
||||
}
|
@ -256,7 +256,7 @@ namespace stats
|
||||
console::info("%s\n", value.stringPtr);
|
||||
break;
|
||||
case game::DDL_ENUM_TYPE:
|
||||
console::info("%s\n", game::DDL::DDL_Lookup_GetEnumString(state, value.intValue));
|
||||
console::info("%s\n", game::DDL_Lookup_GetEnumString(state, value.intValue));
|
||||
break;
|
||||
default:
|
||||
console::info("Unknown type (%d).\n", type);
|
||||
|
2002
src/client/game/database.hpp
Normal file
2002
src/client/game/database.hpp
Normal file
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
#include "game.hpp"
|
||||
|
||||
#include <utils/flags.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace game
|
||||
{
|
||||
@ -22,6 +23,40 @@ namespace game
|
||||
}
|
||||
}
|
||||
|
||||
namespace shared
|
||||
{
|
||||
void client_println(int client_num, const std::string& text)
|
||||
{
|
||||
if (game::Com_GameMode_GetActiveGameMode() == game::GAME_MODE_SP)
|
||||
{
|
||||
game::CG_Utils_GameMessage(client_num, text.data(), 0); // why is nothing printed?
|
||||
}
|
||||
else
|
||||
{
|
||||
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||
utils::string::va("f \"%s\"", text.data()));
|
||||
}
|
||||
}
|
||||
|
||||
bool cheats_ok(int client_num, bool print)
|
||||
{
|
||||
if (game::Com_GameMode_GetActiveGameMode() == game::GAME_MODE_SP)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto sv_cheats = game::Dvar_FindVar("sv_cheats");
|
||||
if (!sv_cheats || !sv_cheats->current.enabled)
|
||||
{
|
||||
if(print)
|
||||
client_println(client_num, "GAME_CHEATSNOTENABLED");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int Cmd_Argc()
|
||||
{
|
||||
return cmd_args->argc[cmd_args->nesting];
|
||||
@ -180,3 +215,18 @@ namespace game
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t operator"" _b(const size_t ptr)
|
||||
{
|
||||
return game::base_address + ptr;
|
||||
}
|
||||
|
||||
size_t reverse_b(const size_t ptr)
|
||||
{
|
||||
return ptr - game::base_address;
|
||||
}
|
||||
|
||||
size_t reverse_b(const void* ptr)
|
||||
{
|
||||
return reverse_b(reinterpret_cast<size_t>(ptr));
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include "structs.hpp"
|
||||
|
||||
#define PROTOCOL 1
|
||||
|
||||
namespace game
|
||||
{
|
||||
extern uint64_t base_address;
|
||||
@ -40,6 +42,12 @@ namespace game
|
||||
bool is_dedi();
|
||||
}
|
||||
|
||||
namespace shared
|
||||
{
|
||||
void client_println(int client_num, const std::string& text);
|
||||
bool cheats_ok(int client_num = 0, bool print = false);
|
||||
}
|
||||
|
||||
int Cmd_Argc();
|
||||
const char* Cmd_Argv(int index);
|
||||
|
||||
@ -58,9 +66,8 @@ namespace game
|
||||
void SV_CmdsMP_RequestMapRestart(bool loadScripts, bool migrate);
|
||||
}
|
||||
|
||||
inline uintptr_t operator"" _b(const uintptr_t ptr)
|
||||
{
|
||||
return game::base_address + ptr;
|
||||
}
|
||||
size_t operator"" _b(const size_t ptr);
|
||||
size_t reverse_b(const size_t ptr);
|
||||
size_t reverse_b(const void* ptr);
|
||||
|
||||
#include "symbols.hpp"
|
||||
|
335
src/client/game/scripting/array.cpp
Normal file
335
src/client/game/scripting/array.cpp
Normal file
@ -0,0 +1,335 @@
|
||||
#include <std_include.hpp>
|
||||
#include "array.hpp"
|
||||
#include "script_value.hpp"
|
||||
#include "execution.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
array_value::array_value(unsigned int parent_id, unsigned int id)
|
||||
: id_(id)
|
||||
, parent_id_(parent_id)
|
||||
{
|
||||
if (!this->id_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto value = game::scr_VarGlob->childVariableValue[this->id_ + 0xFA00 * (this->parent_id_ & 3)];
|
||||
game::VariableValue variable;
|
||||
variable.u = value.u.u;
|
||||
variable.type = value.type;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
void array_value::operator=(const script_value& _value)
|
||||
{
|
||||
if (!this->id_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto value = _value.get_raw();
|
||||
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[this->id_ + 0xFA00 * (this->parent_id_ & 3)];
|
||||
game::AddRefToValue(value.type, value.u);
|
||||
game::RemoveRefToValue(variable->type, variable->u.u);
|
||||
|
||||
variable->type = (char)value.type;
|
||||
variable->u.u = value.u;
|
||||
|
||||
this->value_ = value;
|
||||
}
|
||||
|
||||
array::array(const unsigned int id)
|
||||
: id_(id)
|
||||
{
|
||||
this->add();
|
||||
}
|
||||
|
||||
array::array(const array& other) : array(other.id_)
|
||||
{
|
||||
}
|
||||
|
||||
array::array(array&& other) noexcept
|
||||
{
|
||||
this->id_ = other.id_;
|
||||
other.id_ = 0;
|
||||
}
|
||||
|
||||
array::array()
|
||||
{
|
||||
this->id_ = make_array();
|
||||
}
|
||||
|
||||
array::array(std::vector<script_value> values)
|
||||
{
|
||||
this->id_ = make_array();
|
||||
|
||||
for (const auto& value : values)
|
||||
{
|
||||
this->push(value);
|
||||
}
|
||||
}
|
||||
|
||||
array::array(std::unordered_map<std::string, script_value> values)
|
||||
{
|
||||
this->id_ = make_array();
|
||||
|
||||
for (const auto& value : values)
|
||||
{
|
||||
this->set(value.first, value.second);
|
||||
}
|
||||
}
|
||||
|
||||
array::~array()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
array& array::operator=(const array& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->id_ = other.id_;
|
||||
this->add();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
array& array::operator=(array&& other) noexcept
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->id_ = other.id_;
|
||||
other.id_ = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void array::add() const
|
||||
{
|
||||
if (this->id_)
|
||||
{
|
||||
game::AddRefToValue(game::VAR_POINTER, {static_cast<int>(this->id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void array::release() const
|
||||
{
|
||||
if (this->id_)
|
||||
{
|
||||
game::RemoveRefToValue(game::VAR_POINTER, {static_cast<int>(this->id_)});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<script_value> array::get_keys() const
|
||||
{
|
||||
std::vector<script_value> result;
|
||||
|
||||
const auto offset = 0xFA00 * (this->id_ & 3);
|
||||
auto current = game::scr_VarGlob->objectVariableChildren[this->id_].firstChild;
|
||||
|
||||
for (auto i = offset + current; current; i = offset + current)
|
||||
{
|
||||
const auto var = game::scr_VarGlob->childVariableValue[i];
|
||||
|
||||
if (var.type == game::VAR_UNDEFINED)
|
||||
{
|
||||
current = var.nextSibling;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto string_value = (game::scr_string_t)((unsigned __int8)var.name_lo + (var.k.keys.name_hi << 8));
|
||||
const auto* str = game::SL_ConvertToString(string_value);
|
||||
|
||||
script_value key;
|
||||
if (string_value < 0x40000 && str)
|
||||
{
|
||||
key = str;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = (string_value - 0x800000) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
result.push_back(key);
|
||||
|
||||
current = var.nextSibling;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int array::size() const
|
||||
{
|
||||
return static_cast<int>(game::scr_VarGlob->objectVariableValue[this->id_].u.f.next);
|
||||
}
|
||||
|
||||
unsigned int array::push(script_value value) const
|
||||
{
|
||||
this->set(this->size(), value);
|
||||
return this->size();
|
||||
}
|
||||
|
||||
void array::erase(const unsigned int index) const
|
||||
{
|
||||
const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF);
|
||||
if (variable_id)
|
||||
{
|
||||
game::RemoveVariableValue(this->id_, variable_id);
|
||||
}
|
||||
}
|
||||
|
||||
void array::erase(const std::string& key) const
|
||||
{
|
||||
const auto string_value = game::SL_GetString(key.data(), 0);
|
||||
const auto variable_id = game::FindVariable(this->id_, string_value);
|
||||
if (variable_id)
|
||||
{
|
||||
game::RemoveVariableValue(this->id_, variable_id);
|
||||
}
|
||||
}
|
||||
|
||||
script_value array::pop() const
|
||||
{
|
||||
const auto value = this->get(this->size() - 1);
|
||||
this->erase(this->size() - 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
script_value array::get(const script_value& key) const
|
||||
{
|
||||
if (key.is<int>())
|
||||
{
|
||||
return this->get(key.as<int>());
|
||||
}
|
||||
else
|
||||
{
|
||||
return this->get(key.as<std::string>());
|
||||
}
|
||||
}
|
||||
|
||||
script_value array::get(const std::string& key) const
|
||||
{
|
||||
const auto string_value = game::SL_GetString(key.data(), 0);
|
||||
const auto variable_id = game::FindVariable(this->id_, string_value);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = game::scr_VarGlob->childVariableValue[variable_id + 0xFA00 * (this->id_ & 3)];
|
||||
game::VariableValue variable;
|
||||
variable.u = value.u.u;
|
||||
variable.type = value.type;
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
script_value array::get(const unsigned int index) const
|
||||
{
|
||||
const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto value = game::scr_VarGlob->childVariableValue[variable_id + 0xFA00 * (this->id_ & 3)];
|
||||
game::VariableValue variable;
|
||||
variable.u = value.u.u;
|
||||
variable.type = value.type;
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
void array::set(const script_value& key, const script_value& value) const
|
||||
{
|
||||
if (key.is<int>())
|
||||
{
|
||||
this->set(key.as<int>(), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->set(key.as<std::string>(), value);
|
||||
}
|
||||
}
|
||||
|
||||
void array::set(const std::string& key, const script_value& _value) const
|
||||
{
|
||||
const auto value = _value.get_raw();
|
||||
const auto variable_id = this->get_value_id(key);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + 0xFA00 * (this->id_ & 3)];
|
||||
|
||||
game::AddRefToValue(value.type, value.u);
|
||||
game::RemoveRefToValue(variable->type, variable->u.u);
|
||||
|
||||
variable->type = (char)value.type;
|
||||
variable->u.u = value.u;
|
||||
}
|
||||
|
||||
void array::set(const unsigned int index, const script_value& _value) const
|
||||
{
|
||||
const auto value = _value.get_raw();
|
||||
const auto variable_id = this->get_value_id(index);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + 0xFA00 * (this->id_ & 3)];
|
||||
|
||||
game::AddRefToValue(value.type, value.u);
|
||||
game::RemoveRefToValue(variable->type, variable->u.u);
|
||||
|
||||
variable->type = (char)value.type;
|
||||
variable->u.u = value.u;
|
||||
}
|
||||
|
||||
unsigned int array::get_entity_id() const
|
||||
{
|
||||
return this->id_;
|
||||
}
|
||||
|
||||
unsigned int array::get_value_id(const std::string& key) const
|
||||
{
|
||||
const auto string_value = game::SL_GetString(key.data(), 0);
|
||||
const auto variable_id = game::FindVariable(this->id_, string_value);
|
||||
|
||||
if (!variable_id)
|
||||
{
|
||||
return game::GetNewVariable(this->id_, string_value);
|
||||
}
|
||||
|
||||
return variable_id;
|
||||
}
|
||||
|
||||
unsigned int array::get_value_id(const unsigned int index) const
|
||||
{
|
||||
const auto variable_id = game::FindVariable(this->id_, (index - 0x800000) & 0xFFFFFF);
|
||||
if (!variable_id)
|
||||
{
|
||||
return game::GetNewArrayVariable(this->id_, index);
|
||||
}
|
||||
|
||||
return variable_id;
|
||||
}
|
||||
|
||||
entity array::get_raw() const
|
||||
{
|
||||
return entity(this->id_);
|
||||
}
|
||||
}
|
89
src/client/game/scripting/array.hpp
Normal file
89
src/client/game/scripting/array.hpp
Normal file
@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class array_value : public script_value
|
||||
{
|
||||
public:
|
||||
array_value(unsigned int, unsigned int);
|
||||
void operator=(const script_value&);
|
||||
private:
|
||||
unsigned int id_;
|
||||
unsigned int parent_id_;
|
||||
};
|
||||
|
||||
class array final
|
||||
{
|
||||
public:
|
||||
array();
|
||||
array(const unsigned int);
|
||||
|
||||
array(std::vector<script_value>);
|
||||
array(std::unordered_map<std::string, script_value>);
|
||||
|
||||
array(const array& other);
|
||||
array(array&& other) noexcept;
|
||||
|
||||
~array();
|
||||
|
||||
array& operator=(const array& other);
|
||||
array& operator=(array&& other) noexcept;
|
||||
|
||||
std::vector<script_value> get_keys() const;
|
||||
int size() const;
|
||||
|
||||
unsigned int push(script_value) const;
|
||||
void erase(const unsigned int) const;
|
||||
void erase(const std::string&) const;
|
||||
script_value pop() const;
|
||||
|
||||
script_value get(const script_value&) const;
|
||||
script_value get(const std::string&) const;
|
||||
script_value get(const unsigned int) const;
|
||||
|
||||
void set(const script_value&, const script_value&) const;
|
||||
void set(const std::string&, const script_value&) const;
|
||||
void set(const unsigned int, const script_value&) const;
|
||||
|
||||
unsigned int get_entity_id() const;
|
||||
|
||||
unsigned int get_value_id(const std::string&) const;
|
||||
unsigned int get_value_id(const unsigned int) const;
|
||||
|
||||
entity get_raw() const;
|
||||
|
||||
array_value operator[](const int index) const
|
||||
{
|
||||
return {this->id_, this->get_value_id(index)};
|
||||
}
|
||||
|
||||
array_value operator[](const std::string& key) const
|
||||
{
|
||||
return {this->id_, this->get_value_id(key)};
|
||||
}
|
||||
|
||||
template <typename I = int, typename S = std::string>
|
||||
array_value operator[](const script_value& key) const
|
||||
{
|
||||
if (key.is<I>())
|
||||
{
|
||||
return { this->id_, this->get_value_id(key.as<I>()) };
|
||||
}
|
||||
|
||||
if (key.is<S>())
|
||||
{
|
||||
return { this->id_, this->get_value_id(key.as<S>()) };
|
||||
}
|
||||
|
||||
throw std::runtime_error("Invalid key type");
|
||||
}
|
||||
|
||||
private:
|
||||
void add() const;
|
||||
void release() const;
|
||||
|
||||
unsigned int id_;
|
||||
};
|
||||
}
|
120
src/client/game/scripting/entity.cpp
Normal file
120
src/client/game/scripting/entity.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#include <std_include.hpp>
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
#include "execution.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
entity::entity()
|
||||
: entity(0)
|
||||
{
|
||||
}
|
||||
|
||||
entity::entity(const entity& other) : entity(other.entity_id_)
|
||||
{
|
||||
}
|
||||
|
||||
entity::entity(entity&& other) noexcept
|
||||
{
|
||||
this->entity_id_ = other.entity_id_;
|
||||
other.entity_id_ = 0;
|
||||
}
|
||||
|
||||
entity::entity(const unsigned int entity_id)
|
||||
: entity_id_(entity_id)
|
||||
{
|
||||
this->add();
|
||||
}
|
||||
|
||||
entity::entity(game::scr_entref_t entref)
|
||||
: entity(game::FindEntityId(entref.entnum, entref.classnum))
|
||||
{
|
||||
}
|
||||
|
||||
entity::~entity()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
entity& entity::operator=(const entity& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->entity_id_ = other.entity_id_;
|
||||
this->add();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
entity& entity::operator=(entity&& other) noexcept
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->entity_id_ = other.entity_id_;
|
||||
other.entity_id_ = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
unsigned int entity::get_entity_id() const
|
||||
{
|
||||
return this->entity_id_;
|
||||
}
|
||||
|
||||
game::scr_entref_t entity::get_entity_reference() const
|
||||
{
|
||||
if (!this->entity_id_)
|
||||
{
|
||||
const auto not_null = static_cast<uint16_t>(~0ui16);
|
||||
return game::scr_entref_t{not_null, not_null};
|
||||
}
|
||||
|
||||
return game::Scr_GetEntityIdRef(this->get_entity_id());
|
||||
}
|
||||
|
||||
bool entity::operator==(const entity& other) const noexcept
|
||||
{
|
||||
return this->get_entity_id() == other.get_entity_id();
|
||||
}
|
||||
|
||||
bool entity::operator!=(const entity& other) const noexcept
|
||||
{
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
void entity::add() const
|
||||
{
|
||||
if (this->entity_id_)
|
||||
{
|
||||
game::AddRefToValue(game::VAR_POINTER, {static_cast<int>(this->entity_id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void entity::release() const
|
||||
{
|
||||
if (this->entity_id_)
|
||||
{
|
||||
game::RemoveRefToValue(game::VAR_POINTER, {static_cast<int>(this->entity_id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void entity::set(const std::string& field, const script_value& value) const
|
||||
{
|
||||
set_entity_field(*this, field, value);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value entity::get<script_value>(const std::string& field) const
|
||||
{
|
||||
return get_entity_field(*this, field);
|
||||
}
|
||||
|
||||
script_value entity::call(const std::string& name, const std::vector<script_value>& arguments) const
|
||||
{
|
||||
return call_function(name, *this, arguments);
|
||||
}
|
||||
}
|
50
src/client/game/scripting/entity.hpp
Normal file
50
src/client/game/scripting/entity.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class entity final
|
||||
{
|
||||
public:
|
||||
entity();
|
||||
entity(unsigned int entity_id);
|
||||
entity(game::scr_entref_t entref);
|
||||
|
||||
entity(const entity& other);
|
||||
entity(entity&& other) noexcept;
|
||||
|
||||
~entity();
|
||||
|
||||
entity& operator=(const entity& other);
|
||||
entity& operator=(entity&& other) noexcept;
|
||||
|
||||
void set(const std::string& field, const script_value& value) const;
|
||||
|
||||
template <typename T = script_value>
|
||||
T get(const std::string& field) const;
|
||||
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments = {}) const;
|
||||
|
||||
unsigned int get_entity_id() const;
|
||||
game::scr_entref_t get_entity_reference() const;
|
||||
|
||||
bool operator ==(const entity& other) const noexcept;
|
||||
bool operator !=(const entity& other) const noexcept;
|
||||
|
||||
private:
|
||||
unsigned int entity_id_;
|
||||
|
||||
void add() const;
|
||||
void release() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
script_value entity::get(const std::string& field) const;
|
||||
|
||||
template <typename T>
|
||||
T entity::get(const std::string& field) const
|
||||
{
|
||||
return this->get<script_value>(field).as<T>();
|
||||
}
|
||||
}
|
13
src/client/game/scripting/event.hpp
Normal file
13
src/client/game/scripting/event.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "script_value.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
struct event
|
||||
{
|
||||
std::string name;
|
||||
entity entity{};
|
||||
std::vector<script_value> arguments;
|
||||
};
|
||||
}
|
251
src/client/game/scripting/execution.cpp
Normal file
251
src/client/game/scripting/execution.cpp
Normal file
@ -0,0 +1,251 @@
|
||||
#include <std_include.hpp>
|
||||
#include "execution.hpp"
|
||||
#include "safe_execution.hpp"
|
||||
#include "stack_isolation.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::VariableValue* allocate_argument()
|
||||
{
|
||||
game::VariableValue* value_ptr = ++game::scr_VmPub->top;
|
||||
++game::scr_VmPub->inparamcount;
|
||||
return value_ptr;
|
||||
}
|
||||
|
||||
int get_field_id(const int classnum, const std::string& field)
|
||||
{
|
||||
if (scripting::fields_table[classnum].find(field) != scripting::fields_table[classnum].end())
|
||||
{
|
||||
return scripting::fields_table[classnum][field];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
script_value get_return_value()
|
||||
{
|
||||
if (game::scr_VmPub->inparamcount == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
game::Scr_ClearOutParams();
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]);
|
||||
}
|
||||
}
|
||||
|
||||
void push_value(const script_value& value)
|
||||
{
|
||||
auto* value_ptr = allocate_argument();
|
||||
*value_ptr = value.get_raw();
|
||||
|
||||
game::AddRefToValue(value_ptr->type, value_ptr->u);
|
||||
}
|
||||
|
||||
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments)
|
||||
{
|
||||
stack_isolation _;
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
const auto event_id = game::SL_GetString(event.data(), 0);
|
||||
game::Scr_NotifyId(entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount);
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
|
||||
const auto is_method_call = *reinterpret_cast<const int*>(&entref) != -1;
|
||||
const auto function = find_function(name, !is_method_call);
|
||||
if (function == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Unknown "s + (is_method_call ? "method" : "function") + " '" + name + "'");
|
||||
}
|
||||
|
||||
stack_isolation _;
|
||||
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::call(function, entref))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Error executing "s + (is_method_call ? "method" : "function") + " '" + name + "'");
|
||||
}
|
||||
|
||||
return get_return_value();
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, entity(), arguments);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, arguments);
|
||||
}
|
||||
|
||||
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
stack_isolation _;
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
game::AddRefToObject(id);
|
||||
|
||||
const auto local_id = game::AllocThread(id);
|
||||
const auto result = game::VM_Execute(local_id, pos, static_cast<std::uint32_t>(arguments.size()));
|
||||
game::RemoveRefToObject(result);
|
||||
|
||||
return get_return_value();
|
||||
}
|
||||
|
||||
const char* get_function_pos(const std::string& filename, const std::string& function)
|
||||
{
|
||||
if (!script_function_table.contains(filename))
|
||||
{
|
||||
throw std::runtime_error("File '" + filename + "' not found");
|
||||
}
|
||||
|
||||
const auto& functions = script_function_table[filename];
|
||||
if (!functions.contains(function))
|
||||
{
|
||||
throw std::runtime_error("Function '" + function + "' in file '" + filename + "' not found");
|
||||
}
|
||||
|
||||
return functions.at(function);
|
||||
}
|
||||
|
||||
script_value call_script_function(const entity& entity, const std::string& filename,
|
||||
const std::string& function, const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto pos = get_function_pos(filename, function);
|
||||
return exec_ent_thread(entity, pos, arguments);
|
||||
}
|
||||
|
||||
void set_entity_field(const entity& entity, const std::string& field, const script_value& value)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const int id = get_field_id(entref.classnum, field);
|
||||
|
||||
if (id != -1)
|
||||
{
|
||||
stack_isolation _;
|
||||
push_value(value);
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::set_entity_field(entref, id))
|
||||
{
|
||||
throw std::runtime_error("Failed to set value for field '" + field + "'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
set_object_variable(entity.get_entity_id(), field, value);
|
||||
}
|
||||
}
|
||||
|
||||
script_value get_entity_field(const entity& entity, const std::string& field)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const auto id = get_field_id(entref.classnum, field);
|
||||
|
||||
if (id != -1)
|
||||
{
|
||||
stack_isolation _;
|
||||
|
||||
game::VariableValue value{};
|
||||
if (!safe_execution::get_entity_field(entref, id, &value))
|
||||
{
|
||||
throw std::runtime_error("Failed to get value for field '" + field + "'");
|
||||
}
|
||||
|
||||
const auto _0 = gsl::finally([value]
|
||||
{
|
||||
game::RemoveRefToValue(value.type, value.u);
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return get_object_variable(entity.get_entity_id(), field);
|
||||
}
|
||||
|
||||
unsigned int make_array()
|
||||
{
|
||||
unsigned int index = 0;
|
||||
const auto variable = game::AllocVariable(&index);
|
||||
variable->w.type = game::VAR_ARRAY;
|
||||
variable->u.f.prev = 0;
|
||||
variable->u.f.next = 0;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value)
|
||||
{
|
||||
const auto offset = 0xFA00 * (parent_id & 3);
|
||||
const auto variable_id = game::GetVariable(parent_id, id);
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
|
||||
const auto& raw_value = value.get_raw();
|
||||
|
||||
game::AddRefToValue(raw_value.type, raw_value.u);
|
||||
game::RemoveRefToValue(variable->type, variable->u.u);
|
||||
|
||||
variable->type = static_cast<char>(raw_value.type);
|
||||
variable->u.u = raw_value.u;
|
||||
}
|
||||
|
||||
void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value)
|
||||
{
|
||||
const auto id = scripting::find_token_id(name);
|
||||
set_object_variable(parent_id, id, value);
|
||||
}
|
||||
|
||||
script_value get_object_variable(const unsigned int parent_id, const unsigned int id)
|
||||
{
|
||||
const auto offset = 0xFA00 * (parent_id & 3);
|
||||
const auto variable_id = game::FindVariable(parent_id, id);
|
||||
if (!variable_id)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
|
||||
game::VariableValue value{};
|
||||
value.type = static_cast<int>(variable->type);
|
||||
value.u = variable->u.u;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
script_value get_object_variable(const unsigned int parent_id, const std::string& name)
|
||||
{
|
||||
const auto id = scripting::find_token_id(name);
|
||||
return get_object_variable(parent_id, id);
|
||||
}
|
||||
}
|
47
src/client/game/scripting/execution.hpp
Normal file
47
src/client/game/scripting/execution.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "entity.hpp"
|
||||
#include "array.hpp"
|
||||
#include "function.hpp"
|
||||
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
void push_value(const script_value& value);
|
||||
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments);
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T = script_value>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments = {});
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call<script_value>(name, arguments).as<T>();
|
||||
}
|
||||
|
||||
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments);
|
||||
const char* get_function_pos(const std::string& filename, const std::string& function);
|
||||
script_value call_script_function(const entity& entity, const std::string& filename,
|
||||
const std::string& function, const std::vector<script_value>& arguments);
|
||||
|
||||
void set_entity_field(const entity& entity, const std::string& field, const script_value& value);
|
||||
script_value get_entity_field(const entity& entity, const std::string& field);
|
||||
|
||||
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments);
|
||||
|
||||
unsigned int make_array();
|
||||
|
||||
script_value get_object_variable(const unsigned int parent_id, const unsigned int id);
|
||||
script_value get_object_variable(const unsigned int parent_id, const std::string& name);
|
||||
|
||||
void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value);
|
||||
void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value);
|
||||
}
|
44
src/client/game/scripting/function.cpp
Normal file
44
src/client/game/scripting/function.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "game/scripting/function.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
function::function(const char* pos)
|
||||
: pos_(pos)
|
||||
{
|
||||
}
|
||||
|
||||
script_value function::get_raw() const
|
||||
{
|
||||
game::VariableValue value;
|
||||
value.type = game::VAR_FUNCTION;
|
||||
value.u.codePosValue = this->pos_;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
const char* function::get_pos() const
|
||||
{
|
||||
return this->pos_;
|
||||
}
|
||||
|
||||
std::string function::get_name() const
|
||||
{
|
||||
if (scripting::script_function_table_rev.contains(this->pos_))
|
||||
{
|
||||
const auto& func = scripting::script_function_table_rev[this->pos_];
|
||||
return utils::string::va("%s::%s", func.first.data(), func.second.data());
|
||||
}
|
||||
|
||||
return "unknown function";
|
||||
}
|
||||
|
||||
script_value function::call(const entity& self, std::vector<script_value> arguments) const
|
||||
{
|
||||
return exec_ent_thread(self, this->pos_, arguments);
|
||||
}
|
||||
}
|
35
src/client/game/scripting/function.hpp
Normal file
35
src/client/game/scripting/function.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class function
|
||||
{
|
||||
public:
|
||||
function(const char*);
|
||||
|
||||
script_value get_raw() const;
|
||||
const char* get_pos() const;
|
||||
std::string get_name() const;
|
||||
|
||||
script_value call(const entity& self, std::vector<script_value> arguments) const;
|
||||
|
||||
script_value operator()(const entity& self, std::vector<script_value> arguments) const
|
||||
{
|
||||
return this->call(self, arguments);
|
||||
}
|
||||
|
||||
script_value operator()(std::vector<script_value> arguments) const
|
||||
{
|
||||
return this->call(*game::levelEntityId, arguments);
|
||||
}
|
||||
|
||||
script_value operator()() const
|
||||
{
|
||||
return this->call(*game::levelEntityId, {});
|
||||
}
|
||||
private:
|
||||
const char* pos_;
|
||||
};
|
||||
}
|
108
src/client/game/scripting/functions.cpp
Normal file
108
src/client/game/scripting/functions.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
#include <std_include.hpp>
|
||||
#include "functions.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include "component/gsc/script_extension.hpp"
|
||||
#include "component/gsc/script_loading.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
int find_function_index(const std::string& name, [[maybe_unused]] const bool prefer_global)
|
||||
{
|
||||
const auto target = utils::string::to_lower(name);
|
||||
auto const& first = gsc::gsc_ctx->func_map();
|
||||
auto const& second = gsc::gsc_ctx->meth_map();
|
||||
|
||||
if (!prefer_global)
|
||||
{
|
||||
if (const auto itr = second.find(name); itr != second.end())
|
||||
{
|
||||
return static_cast<int>(itr->second);
|
||||
}
|
||||
|
||||
if (const auto itr = first.find(name); itr != first.end())
|
||||
{
|
||||
return static_cast<int>(itr->second);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto itr = first.find(name); itr != first.end())
|
||||
{
|
||||
return static_cast<int>(itr->second);
|
||||
}
|
||||
|
||||
if (const auto itr = second.find(name); itr != second.end())
|
||||
{
|
||||
return static_cast<int>(itr->second);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t parse_token_id(const std::string& name)
|
||||
{
|
||||
if (name.starts_with("_ID"))
|
||||
{
|
||||
return static_cast<std::uint32_t>(std::strtol(name.substr(3).data(), nullptr, 10));
|
||||
}
|
||||
|
||||
if (name.starts_with("_id_"))
|
||||
{
|
||||
return static_cast<std::uint32_t>(std::strtol(name.substr(4).data(), nullptr, 16));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string find_token(std::uint32_t id)
|
||||
{
|
||||
return gsc::gsc_ctx->token_name(id);
|
||||
}
|
||||
|
||||
std::string find_token_single(std::uint32_t id)
|
||||
{
|
||||
return gsc::gsc_ctx->token_name(id);
|
||||
}
|
||||
|
||||
unsigned int find_token_id(const std::string& name)
|
||||
{
|
||||
const auto id = gsc::gsc_ctx->token_id(name);
|
||||
if (id)
|
||||
{
|
||||
return id;
|
||||
}
|
||||
|
||||
const auto parsed_id = parse_token_id(name);
|
||||
if (parsed_id)
|
||||
{
|
||||
return parsed_id;
|
||||
}
|
||||
|
||||
return game::SL_GetCanonicalString(name.data());
|
||||
}
|
||||
|
||||
script_function get_function_by_index(const std::uint32_t index)
|
||||
{
|
||||
static const auto function_table = &gsc::func_table;
|
||||
static const auto method_table = &gsc::meth_table;
|
||||
|
||||
if (index < 0x1000)
|
||||
{
|
||||
return reinterpret_cast<script_function*>(function_table)[index - 1];
|
||||
}
|
||||
|
||||
return reinterpret_cast<script_function*>(method_table)[index - 0x8000];
|
||||
}
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto index = find_function_index(name, prefer_global);
|
||||
if (index < 0) return nullptr;
|
||||
|
||||
return get_function_by_index(index);
|
||||
}
|
||||
}
|
14
src/client/game/scripting/functions.hpp
Normal file
14
src/client/game/scripting/functions.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
using script_function = void(*)(game::scr_entref_t);
|
||||
|
||||
std::string find_token(std::uint32_t id);
|
||||
std::string find_token_single(std::uint32_t id);
|
||||
unsigned int find_token_id(const std::string& name);
|
||||
|
||||
script_function get_function_by_index(std::uint32_t index);
|
||||
script_function find_function(const std::string& name, const bool prefer_global);
|
||||
}
|
72
src/client/game/scripting/safe_execution.cpp
Normal file
72
src/client/game/scripting/safe_execution.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include <std_include.hpp>
|
||||
#include "safe_execution.hpp"
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4611)
|
||||
|
||||
namespace scripting::safe_execution
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool execute_with_seh(const script_function function, const game::scr_entref_t& entref)
|
||||
{
|
||||
__try
|
||||
{
|
||||
function(entref);
|
||||
return true;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool call(const script_function function, const game::scr_entref_t& entref)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result = execute_with_seh(function, entref);
|
||||
*game::g_script_error_level -= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool set_entity_field(const game::scr_entref_t& entref, const int offset)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
game::Scr_SetObjectField(entref.classnum, entref.entnum, offset);
|
||||
|
||||
*game::g_script_error_level -= 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_entity_field(const game::scr_entref_t& entref, const int offset, game::VariableValue* value)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
value->type = game::VAR_UNDEFINED;
|
||||
value->u.intValue = 0;
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
game::GetEntityFieldValue(value, entref.classnum, entref.entnum, offset);
|
||||
|
||||
*game::g_script_error_level -= 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
10
src/client/game/scripting/safe_execution.hpp
Normal file
10
src/client/game/scripting/safe_execution.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "functions.hpp"
|
||||
|
||||
namespace scripting::safe_execution
|
||||
{
|
||||
bool call(script_function function, const game::scr_entref_t& entref);
|
||||
|
||||
bool set_entity_field(const game::scr_entref_t& entref, int offset);
|
||||
bool get_entity_field(const game::scr_entref_t& entref, int offset, game::VariableValue* value);
|
||||
}
|
356
src/client/game/scripting/script_value.cpp
Normal file
356
src/client/game/scripting/script_value.cpp
Normal file
@ -0,0 +1,356 @@
|
||||
#include <std_include.hpp>
|
||||
#include "script_value.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "array.hpp"
|
||||
#include "function.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
/***************************************************************
|
||||
* Constructors
|
||||
**************************************************************/
|
||||
|
||||
script_value::script_value(const game::VariableValue& value)
|
||||
: value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const value_wrap& value)
|
||||
: value_(value.get_raw())
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const int value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::VAR_INTEGER;
|
||||
variable.u.intValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const unsigned int value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::VAR_INTEGER;
|
||||
variable.u.uintValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const bool value)
|
||||
: script_value(static_cast<unsigned>(value))
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const float value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::VAR_FLOAT;
|
||||
variable.u.floatValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const double value)
|
||||
: script_value(static_cast<float>(value))
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const char* value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::VAR_STRING;
|
||||
variable.u.stringValue = game::SL_GetString(value, 0);
|
||||
|
||||
const auto _ = gsl::finally([&variable]()
|
||||
{
|
||||
game::RemoveRefToValue(variable.type, variable.u);
|
||||
});
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const std::string& value)
|
||||
: script_value(value.data())
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const entity& value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::VAR_POINTER;
|
||||
variable.u.pointerValue = value.get_entity_id();
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const array& value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::VAR_POINTER;
|
||||
variable.u.pointerValue = value.get_entity_id();
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const function& value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::VAR_FUNCTION;
|
||||
variable.u.codePosValue = value.get_pos();
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const vector& value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::VAR_VECTOR;
|
||||
variable.u.vectorValue = game::Scr_AllocVector(value);
|
||||
|
||||
const auto _ = gsl::finally([&variable]()
|
||||
{
|
||||
game::RemoveRefToValue(variable.type, variable.u);
|
||||
});
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Integer
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<int>() const
|
||||
{
|
||||
return this->get_raw().type == game::VAR_INTEGER;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<unsigned int>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<bool>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
int script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.intValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
unsigned int script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.uintValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.uintValue != 0;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Float
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<float>() const
|
||||
{
|
||||
return this->get_raw().type == game::VAR_FLOAT;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<double>() const
|
||||
{
|
||||
return this->is<float>();
|
||||
}
|
||||
|
||||
template <>
|
||||
float script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.floatValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
double script_value::get() const
|
||||
{
|
||||
return static_cast<double>(this->get_raw().u.floatValue);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* String
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<const char*>() const
|
||||
{
|
||||
return this->get_raw().type == game::VAR_STRING;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::string>() const
|
||||
{
|
||||
return this->is<const char*>();
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* script_value::get() const
|
||||
{
|
||||
return game::SL_ConvertToString(static_cast<game::scr_string_t>(this->get_raw().u.stringValue));
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string script_value::get() const
|
||||
{
|
||||
return this->get<const char*>();
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Array
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<array>() const
|
||||
{
|
||||
if (this->get_raw().type != game::VAR_POINTER)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto id = this->get_raw().u.uintValue;
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
|
||||
|
||||
return type == game::VAR_ARRAY;
|
||||
}
|
||||
|
||||
template <>
|
||||
array script_value::get() const
|
||||
{
|
||||
return array(this->get_raw().u.uintValue);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Struct
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::map<std::string, script_value>>() const
|
||||
{
|
||||
if (this->get_raw().type != game::VAR_POINTER)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto id = this->get_raw().u.uintValue;
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
|
||||
|
||||
return type == game::VAR_OBJECT;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Function
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<function>() const
|
||||
{
|
||||
return this->get_raw().type == game::VAR_FUNCTION;
|
||||
}
|
||||
|
||||
template <>
|
||||
function script_value::get() const
|
||||
{
|
||||
return function(this->get_raw().u.codePosValue);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Entity
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<entity>() const
|
||||
{
|
||||
return this->get_raw().type == game::VAR_POINTER;
|
||||
}
|
||||
|
||||
template <>
|
||||
entity script_value::get() const
|
||||
{
|
||||
return entity(this->get_raw().u.pointerValue);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Vector
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<vector>() const
|
||||
{
|
||||
return this->get_raw().type == game::VAR_VECTOR;
|
||||
}
|
||||
|
||||
template <>
|
||||
vector script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.vectorValue;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
const game::VariableValue& script_value::get_raw() const
|
||||
{
|
||||
return this->value_.get();
|
||||
}
|
||||
|
||||
value_wrap::value_wrap(const scripting::script_value& value, int argument_index)
|
||||
: value_(value)
|
||||
, argument_index_(argument_index)
|
||||
{
|
||||
}
|
||||
|
||||
std::string script_value::to_string() const
|
||||
{
|
||||
if (this->is<int>())
|
||||
{
|
||||
return utils::string::va("%i", this->as<int>());
|
||||
}
|
||||
|
||||
if (this->is<float>())
|
||||
{
|
||||
return utils::string::va("%f", this->as<float>());
|
||||
}
|
||||
|
||||
if (this->is<std::string>())
|
||||
{
|
||||
return this->as<std::string>();
|
||||
}
|
||||
|
||||
if (this->is<vector>())
|
||||
{
|
||||
const auto vec = this->as<vector>();
|
||||
return utils::string::va("(%g, %g, %g)",
|
||||
vec.get_x(),
|
||||
vec.get_y(),
|
||||
vec.get_z()
|
||||
);
|
||||
}
|
||||
|
||||
if (this->is<function>())
|
||||
{
|
||||
const auto func = this->as<function>();
|
||||
return utils::string::va("[[ %s ]]", func.get_name().data());
|
||||
}
|
||||
|
||||
return this->type_name();
|
||||
}
|
||||
}
|
223
src/client/game/scripting/script_value.hpp
Normal file
223
src/client/game/scripting/script_value.hpp
Normal file
@ -0,0 +1,223 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "variable_value.hpp"
|
||||
#include "vector.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class entity;
|
||||
class array;
|
||||
class function;
|
||||
class value_wrap;
|
||||
|
||||
namespace
|
||||
{
|
||||
std::array<const char*, 27> var_typename =
|
||||
{
|
||||
"undefined",
|
||||
"object",
|
||||
"string",
|
||||
"localized string",
|
||||
"vector",
|
||||
"float",
|
||||
"int",
|
||||
"codepos",
|
||||
"precodepos",
|
||||
"function",
|
||||
"builtin function",
|
||||
"builtin method",
|
||||
"stack",
|
||||
"animation",
|
||||
"pre animation",
|
||||
"thread",
|
||||
"thread",
|
||||
"thread",
|
||||
"thread",
|
||||
"struct",
|
||||
"removed entity",
|
||||
"entity",
|
||||
"array",
|
||||
"removed thread",
|
||||
"<free>",
|
||||
"thread list",
|
||||
"endon list",
|
||||
};
|
||||
|
||||
std::string get_typename(const game::VariableValue& value)
|
||||
{
|
||||
if (value.type == game::VAR_POINTER)
|
||||
{
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[value.u.uintValue].w.type;
|
||||
return var_typename[type];
|
||||
}
|
||||
else
|
||||
{
|
||||
return var_typename[value.type];
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename A = array>
|
||||
std::string get_c_typename()
|
||||
{
|
||||
auto& info = typeid(T);
|
||||
|
||||
if (info == typeid(std::string))
|
||||
{
|
||||
return "string";
|
||||
}
|
||||
|
||||
if (info == typeid(const char*))
|
||||
{
|
||||
return "string";
|
||||
}
|
||||
|
||||
if (info == typeid(entity))
|
||||
{
|
||||
return "entity";
|
||||
}
|
||||
|
||||
if (info == typeid(array))
|
||||
{
|
||||
return "array";
|
||||
}
|
||||
|
||||
if (info == typeid(function))
|
||||
{
|
||||
return "function";
|
||||
}
|
||||
|
||||
if (info == typeid(vector))
|
||||
{
|
||||
return "vector";
|
||||
}
|
||||
|
||||
return info.name();
|
||||
}
|
||||
}
|
||||
|
||||
class script_value
|
||||
{
|
||||
public:
|
||||
script_value() = default;
|
||||
script_value(const game::VariableValue& value);
|
||||
script_value(const value_wrap& value);
|
||||
|
||||
script_value(int value);
|
||||
script_value(unsigned int value);
|
||||
script_value(bool value);
|
||||
|
||||
script_value(float value);
|
||||
script_value(double value);
|
||||
|
||||
script_value(const char* value);
|
||||
script_value(const std::string& value);
|
||||
|
||||
script_value(const entity& value);
|
||||
script_value(const array& value);
|
||||
|
||||
script_value(const function& value);
|
||||
|
||||
script_value(const vector& value);
|
||||
|
||||
std::string type_name() const
|
||||
{
|
||||
return get_typename(this->get_raw());
|
||||
}
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
template <typename T>
|
||||
bool is() const;
|
||||
|
||||
template <typename T>
|
||||
T as() const
|
||||
{
|
||||
if (!this->is<T>())
|
||||
{
|
||||
const auto type = get_typename(this->get_raw());
|
||||
const auto c_type = get_c_typename<T>();
|
||||
throw std::runtime_error(std::format("has type '{}' but should be '{}'", type, c_type));
|
||||
}
|
||||
|
||||
return get<T>();
|
||||
}
|
||||
|
||||
template <typename T, typename I = int>
|
||||
T* as_ptr()
|
||||
{
|
||||
const auto value = this->as<I>();
|
||||
|
||||
if (!value)
|
||||
{
|
||||
throw std::runtime_error("is null");
|
||||
}
|
||||
|
||||
return reinterpret_cast<T*>(value);
|
||||
}
|
||||
|
||||
const game::VariableValue& get_raw() const;
|
||||
|
||||
variable_value value_{};
|
||||
private:
|
||||
template <typename T>
|
||||
T get() const;
|
||||
};
|
||||
|
||||
class value_wrap
|
||||
{
|
||||
public:
|
||||
value_wrap(const scripting::script_value& value, int argument_index);
|
||||
|
||||
std::string to_string() const
|
||||
{
|
||||
return this->value_.to_string();
|
||||
}
|
||||
|
||||
std::string type_name() const
|
||||
{
|
||||
return this->value_.type_name();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T as() const
|
||||
{
|
||||
try
|
||||
{
|
||||
return this->value_.as<T>();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("parameter %d %s", this->argument_index_, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename I = int>
|
||||
T* as_ptr()
|
||||
{
|
||||
try
|
||||
{
|
||||
return this->value_.as_ptr<T>();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error(utils::string::va("parameter %d %s", this->argument_index_, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T is() const
|
||||
{
|
||||
return this->value_.is<T>();
|
||||
}
|
||||
|
||||
const game::VariableValue& get_raw() const
|
||||
{
|
||||
return this->value_.get_raw();
|
||||
}
|
||||
|
||||
int argument_index_{};
|
||||
scripting::script_value value_;
|
||||
};
|
||||
}
|
27
src/client/game/scripting/stack_isolation.cpp
Normal file
27
src/client/game/scripting/stack_isolation.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <std_include.hpp>
|
||||
#include "stack_isolation.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
stack_isolation::stack_isolation()
|
||||
{
|
||||
this->in_param_count_ = game::scr_VmPub->inparamcount;
|
||||
this->out_param_count_ = game::scr_VmPub->outparamcount;
|
||||
this->top_ = game::scr_VmPub->top;
|
||||
this->max_stack_ = game::scr_VmPub->maxstack;
|
||||
|
||||
game::scr_VmPub->top = this->stack_;
|
||||
game::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1];
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
game::scr_VmPub->outparamcount = 0;
|
||||
}
|
||||
|
||||
stack_isolation::~stack_isolation()
|
||||
{
|
||||
game::Scr_ClearOutParams();
|
||||
game::scr_VmPub->inparamcount = this->in_param_count_;
|
||||
game::scr_VmPub->outparamcount = this->out_param_count_;
|
||||
game::scr_VmPub->top = this->top_;
|
||||
game::scr_VmPub->maxstack = this->max_stack_;
|
||||
}
|
||||
}
|
25
src/client/game/scripting/stack_isolation.hpp
Normal file
25
src/client/game/scripting/stack_isolation.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class stack_isolation final
|
||||
{
|
||||
public:
|
||||
stack_isolation();
|
||||
~stack_isolation();
|
||||
|
||||
stack_isolation(stack_isolation&&) = delete;
|
||||
stack_isolation(const stack_isolation&) = delete;
|
||||
stack_isolation& operator=(stack_isolation&&) = delete;
|
||||
stack_isolation& operator=(const stack_isolation&) = delete;
|
||||
|
||||
private:
|
||||
game::VariableValue stack_[512]{};
|
||||
|
||||
game::VariableValue* max_stack_;
|
||||
game::VariableValue* top_;
|
||||
unsigned int in_param_count_;
|
||||
unsigned int out_param_count_;
|
||||
};
|
||||
}
|
68
src/client/game/scripting/variable_value.cpp
Normal file
68
src/client/game/scripting/variable_value.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include <std_include.hpp>
|
||||
#include "variable_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
variable_value::variable_value(const game::VariableValue& value)
|
||||
{
|
||||
this->assign(value);
|
||||
}
|
||||
|
||||
variable_value::variable_value(const variable_value& other) noexcept
|
||||
{
|
||||
this->operator=(other);
|
||||
}
|
||||
|
||||
variable_value::variable_value(variable_value&& other) noexcept
|
||||
{
|
||||
this->operator=(std::move(other));
|
||||
}
|
||||
|
||||
variable_value& variable_value::operator=(const variable_value& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->release();
|
||||
this->assign(other.value_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
variable_value& variable_value::operator=(variable_value&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->release();
|
||||
this->value_ = other.value_;
|
||||
other.value_.type = game::VAR_UNDEFINED;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
variable_value::~variable_value()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
const game::VariableValue& variable_value::get() const
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
void variable_value::assign(const game::VariableValue& value)
|
||||
{
|
||||
this->value_ = value;
|
||||
game::AddRefToValue(this->value_.type, this->value_.u);
|
||||
}
|
||||
|
||||
void variable_value::release()
|
||||
{
|
||||
if (this->value_.type != game::VAR_UNDEFINED)
|
||||
{
|
||||
game::RemoveRefToValue(this->value_.type, this->value_.u);
|
||||
this->value_.type = game::VAR_UNDEFINED;
|
||||
}
|
||||
}
|
||||
}
|
27
src/client/game/scripting/variable_value.hpp
Normal file
27
src/client/game/scripting/variable_value.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class variable_value
|
||||
{
|
||||
public:
|
||||
variable_value() = default;
|
||||
variable_value(const game::VariableValue& value);
|
||||
variable_value(const variable_value& other) noexcept;
|
||||
variable_value(variable_value&& other) noexcept;
|
||||
|
||||
variable_value& operator=(const variable_value& other) noexcept;
|
||||
variable_value& operator=(variable_value&& other) noexcept;
|
||||
|
||||
~variable_value();
|
||||
|
||||
const game::VariableValue& get() const;
|
||||
|
||||
private:
|
||||
void assign(const game::VariableValue& value);
|
||||
void release();
|
||||
|
||||
game::VariableValue value_{{0}, game::VAR_UNDEFINED};
|
||||
};
|
||||
}
|
85
src/client/game/scripting/vector.cpp
Normal file
85
src/client/game/scripting/vector.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include <std_include.hpp>
|
||||
#include "vector.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
vector::vector(const float* value)
|
||||
{
|
||||
for (auto i = 0; i < 3; ++i)
|
||||
{
|
||||
this->value_[i] = value[i];
|
||||
}
|
||||
}
|
||||
|
||||
vector::vector(const game::vec3_t& value)
|
||||
: vector(&value[0])
|
||||
{
|
||||
}
|
||||
|
||||
vector::vector(const float x, const float y, const float z)
|
||||
{
|
||||
this->value_[0] = x;
|
||||
this->value_[1] = y;
|
||||
this->value_[2] = z;
|
||||
}
|
||||
|
||||
vector::operator game::vec3_t&()
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
vector::operator const game::vec3_t&() const
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
game::vec_t& vector::operator[](const size_t i)
|
||||
{
|
||||
if (i >= 3)
|
||||
{
|
||||
throw std::runtime_error("Out of bounds.");
|
||||
}
|
||||
|
||||
return this->value_[i];
|
||||
}
|
||||
|
||||
const game::vec_t& vector::operator[](const size_t i) const
|
||||
{
|
||||
if (i >= 3)
|
||||
{
|
||||
throw std::runtime_error("Out of bounds.");
|
||||
}
|
||||
|
||||
return this->value_[i];
|
||||
}
|
||||
|
||||
float vector::get_x() const
|
||||
{
|
||||
return this->operator[](0);
|
||||
}
|
||||
|
||||
float vector::get_y() const
|
||||
{
|
||||
return this->operator[](1);
|
||||
}
|
||||
|
||||
float vector::get_z() const
|
||||
{
|
||||
return this->operator[](2);
|
||||
}
|
||||
|
||||
void vector::set_x(const float value)
|
||||
{
|
||||
this->operator[](0) = value;
|
||||
}
|
||||
|
||||
void vector::set_y(const float value)
|
||||
{
|
||||
this->operator[](1) = value;
|
||||
}
|
||||
|
||||
void vector::set_z(const float value)
|
||||
{
|
||||
this->operator[](2) = value;
|
||||
}
|
||||
}
|
31
src/client/game/scripting/vector.hpp
Normal file
31
src/client/game/scripting/vector.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class vector final
|
||||
{
|
||||
public:
|
||||
vector() = default;
|
||||
vector(const float* value);
|
||||
vector(const game::vec3_t& value);
|
||||
vector(float x, float y, float z);
|
||||
|
||||
operator game::vec3_t&();
|
||||
operator const game::vec3_t&() const;
|
||||
|
||||
game::vec_t& operator[](size_t i);
|
||||
const game::vec_t& operator[](size_t i) const;
|
||||
|
||||
float get_x() const;
|
||||
float get_y() const;
|
||||
float get_z() const;
|
||||
|
||||
void set_x(float value);
|
||||
void set_y(float value);
|
||||
void set_z(float value);
|
||||
|
||||
private:
|
||||
game::vec3_t value_{0};
|
||||
};
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -8,6 +8,16 @@ namespace game
|
||||
* Functions
|
||||
**************************************************************/
|
||||
|
||||
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{ 0xC04360 };
|
||||
WEAK symbol<void(int type, VariableUnion u)> RemoveRefToValue{ 0xC05DB0 };
|
||||
WEAK symbol<void(unsigned int id)> AddRefToObject{ 0xC04350 };
|
||||
WEAK symbol<void(unsigned int id)> RemoveRefToObject{ 0xC05CA0 };
|
||||
WEAK symbol<unsigned int(unsigned int id)> AllocThread{ 0xC04580 };
|
||||
WEAK symbol<ObjectVariableValue* (unsigned int* id)> AllocVariable{ 0xC04650 };
|
||||
WEAK symbol<unsigned int(const char* filename)> Scr_LoadScript{ 0xBFD900 };
|
||||
WEAK symbol<unsigned int(const char* filename, unsigned int handle)> Scr_GetFunctionHandle{ 0xBFD780 };
|
||||
WEAK symbol<unsigned int(int handle, int num_param)> Scr_ExecThread{ 0xC0ACD0 };
|
||||
|
||||
WEAK symbol<float()> BG_GetGravity{ 0x68DD0 };
|
||||
|
||||
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{ 0xB8D830 };
|
||||
@ -52,18 +62,26 @@ namespace game
|
||||
WEAK symbol<void(const char* text_in)> Cmd_TokenizeString{ 0xB7D850 };
|
||||
WEAK symbol<void()> Cmd_EndTokenizeString{ 0xB7CC90 };
|
||||
|
||||
WEAK symbol<__int64(void* stream, int flush)> db_inflate{ 0xE77380 };
|
||||
WEAK symbol<__int64(void* stream, const char* version, int stream_size)> db_inflateInit_{ 0xE77980 };
|
||||
WEAK symbol<__int64(void* stream)> db_inflateEnd{ 0xE777F0 };
|
||||
|
||||
WEAK symbol<void(void* stream, void* memory, int size)> DB_Zip_InitThreadMemory{ 0xE78290 };
|
||||
WEAK symbol<void(void* stream)> DB_Zip_ShutdownThreadMemory{ 0xE782D0 };
|
||||
|
||||
WEAK symbol<void(XZoneInfo* zoneInfo, unsigned int zoneCount, char syncMode)> DB_LoadXAssets{ 0xA78630 };
|
||||
WEAK symbol<int(XAssetType type, const char* name)> DB_XAssetExists{ 0xA7C3A0 };
|
||||
WEAK symbol<int(const RawFile* rawfile, char* buf, int size)> DB_GetRawBuffer{ 0xA77AB0 };
|
||||
|
||||
WEAK symbol<const char* (XAssetType type, XAssetHeader header)> DB_GetXAssetHeaderName{ 0x9E5BA0 };
|
||||
WEAK symbol<bool(std::int32_t, void(__cdecl*)(XAssetHeader, void*), const void*)> DB_EnumXAssets_FastFile{ 0xA76CE0 };
|
||||
WEAK symbol<bool(XAssetType type, const char* name)> DB_IsXAssetDefault{ 0xA780D0 };
|
||||
WEAK symbol<XAssetHeader(XAssetType type, const char* name, int allowCreateDefault)> DB_FindXAssetHeader{ 0xA76E00 };
|
||||
WEAK symbol<bool(const char* zoneName)> DB_IsLocalized{ 0x3BC500 };
|
||||
WEAK symbol<char* (const char* filename, char* buf, int size)> DB_ReadRawFile{ 0xA79E30 };
|
||||
WEAK symbol<int(const RawFile* rawfile)> DB_GetRawFileLen{ 0xF20AF0 };
|
||||
|
||||
namespace DDL
|
||||
{
|
||||
WEAK symbol<const char*(const DDLState* state, int enumValue)> DDL_Lookup_GetEnumString{ 0x30430 };
|
||||
}
|
||||
|
||||
WEAK symbol<const char* (const DDLState* state, int enumValue)> DDL_Lookup_GetEnumString{ 0x30430 };
|
||||
WEAK symbol<bool(const DDLState* state)> DDL_StateIsLeaf{ 0x2E3C0 };
|
||||
WEAK symbol<DDLType(const DDLState* state)> DDL_GetType{ 0x2E5A0 };
|
||||
WEAK symbol<DDLValue(const DDLState* state, const DDLContext* ddlContext)> DDL_GetValue{ 0x2F5E0 };
|
||||
@ -104,7 +122,20 @@ namespace game
|
||||
WEAK symbol<void(char* buffer)> FS_FreeFile{ 0xCDE1F0 };
|
||||
WEAK symbol<void(int h, const char* fmt, ...)> FS_Printf{ 0xCDD1C0 };
|
||||
|
||||
WEAK symbol<unsigned int(unsigned int)> GetObjectType{ 0xC059E0 };
|
||||
WEAK symbol<unsigned int(unsigned int, unsigned int)> GetVariable{ 0xC05A90 };
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewVariable{ 0xC05660 };
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewArrayVariable{ 0xC054E0 };
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindVariable{ 0xC05100 };
|
||||
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> FindEntityId{ 0xC05000 };
|
||||
WEAK symbol<void(unsigned int parentId, unsigned int index)> RemoveVariableValue{ 0xC05E90 };
|
||||
WEAK symbol<void(VariableValue* result, unsigned int classnum,
|
||||
int entnum, int offset)> GetEntityFieldValue{ 0xC09CC0 };
|
||||
|
||||
WEAK symbol<int(int clientNum)> G_MainMP_GetClientScore{ 0xB20550 };
|
||||
WEAK symbol<void* (void*, const char* name)> G_GetWeaponForName { 0x733D40 };
|
||||
WEAK symbol<int (void*, const void* weapon, int dualWield, int startInAltMode, int usedBefore)> G_GivePlayerWeapon{ 0x733D40 };
|
||||
WEAK symbol<int(void*, const void* weapon, const bool isAlternate, int hadWeapon)> G_InitializeAmmo{ 0x733D40 };
|
||||
|
||||
WEAK symbol<char* (char* string)> I_CleanStr{ 0xCFACC0 };
|
||||
|
||||
@ -147,6 +178,8 @@ namespace game
|
||||
#define R_AddCmdDrawTextWithCursor(TXT, MC, F, UNK, X, Y, XS, YS, R, C, S, CP, CC) \
|
||||
IW7_AddBaseDrawTextCmd(TXT, MC, F, game::R_GetFontHeight(F), X, Y, XS, YS, R, C, CP, CC, game::R_DrawSomething(S), 0, 0, 0, 0)
|
||||
|
||||
WEAK symbol<char* ()> Sys_Cwd{ 0xCFE5A0 };
|
||||
|
||||
WEAK symbol<int()> Sys_Milliseconds{ 0xD58110 };
|
||||
|
||||
WEAK symbol<HANDLE(Sys_Folder folder, const char* baseFilename)> Sys_CreateFile{ 0xCFDF50 };
|
||||
@ -154,14 +187,40 @@ namespace game
|
||||
WEAK symbol<int(int length, void const* data, const netadr_s* to)> Sys_SendPacket{ 0xD57DE0 };
|
||||
WEAK symbol<int(netadr_s* net_from, msg_t* net_message)> Sys_GetPacket{ 0xD57D50 };
|
||||
|
||||
WEAK symbol<bool()> Sys_IsDatabaseThread{ 0xBB7B30 };
|
||||
|
||||
WEAK symbol<const char* ()> SEH_GetCurrentLanguageCode{ 0xCBAF50 };
|
||||
WEAK symbol<char* ()> SEH_GetCurrentLanguageName{ 0xCBB090 };
|
||||
WEAK symbol<char* (int code)> SEH_GetLanguageName{ 0xCBB140 };
|
||||
|
||||
WEAK symbol<void(const char* name, int allocDir)> PMem_BeginAlloc{ 0xCF0E10 };
|
||||
WEAK symbol<void(const char* name, int allocDir)> PMem_EndAlloc{ 0xCF1070 };
|
||||
WEAK symbol<char* (const size_t size, unsigned int alignment, int type, int source)> PMem_AllocFromSource_NoDebug{ 0xCF0A90 };
|
||||
WEAK symbol<void(const char* name, int allocDir)> PMem_Free{ 0xCF10D0 };
|
||||
|
||||
WEAK symbol<unsigned int(unsigned int localId, const char* pos,
|
||||
unsigned int paramcount)> VM_Execute{ 0xC0CDB0 };
|
||||
|
||||
WEAK symbol<void(unsigned int id, scr_string_t stringValue,
|
||||
unsigned int paramcount)> Scr_NotifyId{ 0xC0C2B0 };
|
||||
WEAK symbol<const float* (const float* v)> Scr_AllocVector{ 0xC06960 };
|
||||
WEAK symbol<void(int)> Scr_AddInt{ 0xC0A580 };
|
||||
WEAK symbol<bool(VariableValue* value)> Scr_CastString{ 0xC06AE0 };
|
||||
WEAK symbol<void()> Scr_ClearOutParams{ 0xC0ABC0 };
|
||||
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{ 0xC09050 };
|
||||
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{ 0x40B6E0 };
|
||||
WEAK symbol<int()> Scr_GetInt{ 0xC0B950 };
|
||||
WEAK symbol<void()> Scr_ErrorInternal{ 0xC0AC30 };
|
||||
|
||||
WEAK symbol<ScreenPlacement* ()> ScrPlace_GetViewPlacement{ 0x9E4090 };
|
||||
|
||||
WEAK symbol<const char* (const StringTable* table, const int comparisonColumn, const char* value, const int valueColumn)> StringTable_Lookup{ 0xCE7950 };
|
||||
WEAK symbol<const char* (const StringTable* table, const int row, const int column)> StringTable_GetColumnValueForRow{ 0xCE78E0 };
|
||||
|
||||
WEAK symbol<scr_string_t(const char* str, unsigned int user)> SL_GetString{ 0xC037E0 };
|
||||
WEAK symbol<const char*(scr_string_t stringValue)> SL_ConvertToString{ 0xC03300 };
|
||||
WEAK symbol<unsigned int(const char* str)> SL_GetCanonicalString{ 0xBFD340 };
|
||||
|
||||
WEAK symbol<void(const char* string)> SV_Cmd_TokenizeString{ 0xB7DD00 };
|
||||
WEAK symbol<void()> SV_Cmd_EndTokenizedString{ 0xB7DCC0 };
|
||||
WEAK symbol<void(const char* map, const char* gameType, int clientCount, int agentCount, bool hardcore,
|
||||
@ -177,14 +236,23 @@ namespace game
|
||||
WEAK symbol<bool()> SV_Loaded{ 0xC114C0 };
|
||||
WEAK symbol<bool(const char* name)> SV_MapExists{ 0xCDB620 };
|
||||
WEAK symbol<bool(int clientNum)> SV_BotIsBot{ 0xC3BC90 };
|
||||
WEAK symbol<void* (int num)> SV_GetPlayerstateForClientNum{ 0xC123A0 };
|
||||
|
||||
WEAK symbol<void(int)> SND_StopSounds{ 0xCA06E0 };
|
||||
WEAK symbol<void(const char*)> SND_SetMusicState{ 0xC9E110 };
|
||||
|
||||
WEAK symbol<void* (jmp_buf* Buf, int Value)> longjmp{ 0x12C0758 };
|
||||
WEAK symbol<int(jmp_buf* Buf)> _setjmp{ 0x1423110 };
|
||||
|
||||
/***************************************************************
|
||||
* Variables
|
||||
**************************************************************/
|
||||
|
||||
WEAK symbol<int> g_script_error_level{ 0x6B16298 };
|
||||
WEAK symbol<jmp_buf> g_script_error{ 0x6B162A0 };
|
||||
|
||||
WEAK symbol<unsigned int> levelEntityId{ 0x665A120 };
|
||||
|
||||
WEAK symbol<CmdArgs> sv_cmd_args{ 0x5D65C20 };
|
||||
WEAK symbol<CmdArgs> cmd_args{ 0x5D65B70 };
|
||||
WEAK symbol<cmd_function_s*> cmd_functions{ 0x5D65CC8 };
|
||||
@ -216,4 +284,21 @@ namespace game
|
||||
WEAK symbol<SOCKET> query_socket{ 0x779FDC8 };
|
||||
|
||||
WEAK symbol<DWORD> threadIds{ 0x602BAB0 };
|
||||
|
||||
WEAK symbol<scrVarGlob_t> scr_VarGlob{ 0x6691180 };
|
||||
WEAK symbol<scrVmPub_t> scr_VmPub{ 0x6B183B0 };
|
||||
WEAK symbol<function_stack_t> scr_function_stack{ 0x6B22908 };
|
||||
|
||||
WEAK symbol<PhysicalMemory> g_mem{ 0x7685560 };
|
||||
WEAK symbol<PhysicalMemory> g_scriptmem{ 0x7685FC0 };
|
||||
|
||||
WEAK symbol<searchpath_s> fs_searchpaths{ 0x756DEE0 };
|
||||
|
||||
WEAK symbol<DB_LoadData> g_load{ 0x52A8010 };
|
||||
WEAK symbol<int> g_authLoad_isSecure{ 0x529DD90 };
|
||||
WEAK symbol<DB_ReadStream> db_stream{ 0x52A8050 };
|
||||
WEAK symbol<db_z_stream_s> db_zip_stream{ 0x529DD30 };
|
||||
WEAK symbol<char*> db_zip_memory{ 0x525B500 };
|
||||
|
||||
WEAK symbol<unsigned __int64> g_streamPos{ 0x5687E30 };
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ enum class component_priority
|
||||
{
|
||||
min = 0,
|
||||
dvars,
|
||||
ranked,
|
||||
steam_proxy,
|
||||
arxan,
|
||||
};
|
||||
|
@ -25,11 +25,25 @@ public:
|
||||
|
||||
installer(const std::string& component_name)
|
||||
{
|
||||
auto last = component_name.find("::component");
|
||||
if (last == std::string::npos)
|
||||
{
|
||||
last = component_name.find("::error");
|
||||
}
|
||||
if (last == std::string::npos)
|
||||
{
|
||||
last = component_name.find("::extension");
|
||||
}
|
||||
if (last == std::string::npos)
|
||||
{
|
||||
last = component_name.find("::loading");
|
||||
}
|
||||
|
||||
register_component(
|
||||
std::make_unique<T>(),
|
||||
std::string(
|
||||
component_name.begin(),
|
||||
component_name.begin() + component_name.find("::component")
|
||||
component_name.begin() + last
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -107,7 +107,9 @@ void limit_parallel_dll_loading()
|
||||
|
||||
int main()
|
||||
{
|
||||
if (!game::environment::is_dedi())
|
||||
ShowWindow(GetConsoleWindow(), SW_HIDE);
|
||||
|
||||
console::init();
|
||||
|
||||
FARPROC entry_point;
|
||||
|
@ -15,27 +15,45 @@
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
Mem seg_ptr(const SReg& segment, const uint64_t off)
|
||||
{
|
||||
auto mem = ptr_abs(off);
|
||||
mem.setSegment(segment);
|
||||
return mem;
|
||||
}
|
||||
|
||||
namespace utils::hook
|
||||
{
|
||||
namespace
|
||||
{
|
||||
uint8_t* allocate_somewhere_near(const void* base_address, const size_t size)
|
||||
size_t get_allocation_granularity()
|
||||
{
|
||||
size_t offset = 0;
|
||||
SYSTEM_INFO info{};
|
||||
GetSystemInfo(&info);
|
||||
|
||||
return info.dwAllocationGranularity;
|
||||
}
|
||||
|
||||
uint8_t* allocate_somewhere_near(const void* base_address, const size_t granularity, const size_t size)
|
||||
{
|
||||
size_t target_address = reinterpret_cast<size_t>(base_address) - (1ull << 31);
|
||||
target_address &= ~(granularity - 1);
|
||||
|
||||
while (true)
|
||||
{
|
||||
offset += size;
|
||||
auto* target_address = static_cast<const uint8_t*>(base_address) - offset;
|
||||
if (is_relatively_far(base_address, target_address))
|
||||
target_address += granularity;
|
||||
|
||||
auto* target_ptr = reinterpret_cast<uint8_t*>(target_address);
|
||||
if (is_relatively_far(base_address, target_ptr))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto res = VirtualAlloc(const_cast<uint8_t*>(target_address), size, MEM_RESERVE | MEM_COMMIT,
|
||||
const auto res = VirtualAlloc(target_ptr, size, MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_EXECUTE_READWRITE);
|
||||
if (res)
|
||||
{
|
||||
if (is_relatively_far(base_address, target_address))
|
||||
if (is_relatively_far(base_address, target_ptr))
|
||||
{
|
||||
VirtualFree(res, 0, MEM_RELEASE);
|
||||
return nullptr;
|
||||
@ -54,8 +72,10 @@ namespace utils::hook
|
||||
memory(const void* ptr)
|
||||
: memory()
|
||||
{
|
||||
this->length_ = 0x1000;
|
||||
this->buffer_ = allocate_somewhere_near(ptr, this->length_);
|
||||
static const auto allocation_granularity = get_allocation_granularity();
|
||||
this->length_ = allocation_granularity;
|
||||
|
||||
this->buffer_ = allocate_somewhere_near(ptr, allocation_granularity, this->length_);
|
||||
if (!this->buffer_)
|
||||
{
|
||||
throw std::runtime_error("Failed to allocate");
|
||||
@ -144,28 +164,6 @@ namespace utils::hook
|
||||
});
|
||||
}
|
||||
|
||||
concurrency::container<std::map<const void*, uint8_t>>& get_original_data_map()
|
||||
{
|
||||
static concurrency::container<std::map<const void*, uint8_t>> og_data{};
|
||||
return og_data;
|
||||
}
|
||||
|
||||
void store_original_data(const void* /*data*/, size_t /*length*/)
|
||||
{
|
||||
/*get_original_data_map().access([data, length](std::map<const void*, uint8_t>& og_map)
|
||||
{
|
||||
const auto data_ptr = static_cast<const uint8_t*>(data);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
{
|
||||
const auto pos = data_ptr + i;
|
||||
if (!og_map.contains(pos))
|
||||
{
|
||||
og_map[pos] = *pos;
|
||||
}
|
||||
}
|
||||
});*/
|
||||
}
|
||||
|
||||
void* initialize_min_hook()
|
||||
{
|
||||
static class min_hook_init
|
||||
@ -199,12 +197,26 @@ namespace utils::hook
|
||||
this->push(rsi);
|
||||
this->push(rdi);
|
||||
|
||||
this->sub(rsp, 0x40);
|
||||
this->push(r8);
|
||||
this->push(r9);
|
||||
this->push(r10);
|
||||
this->push(r11);
|
||||
this->push(r12);
|
||||
this->push(r13);
|
||||
this->push(r14);
|
||||
this->push(r15);
|
||||
}
|
||||
|
||||
void assembler::popad64()
|
||||
{
|
||||
this->add(rsp, 0x40);
|
||||
this->pop(r15);
|
||||
this->pop(r14);
|
||||
this->pop(r13);
|
||||
this->pop(r12);
|
||||
this->pop(r11);
|
||||
this->pop(r10);
|
||||
this->pop(r9);
|
||||
this->pop(r8);
|
||||
|
||||
this->pop(rdi);
|
||||
this->pop(rsi);
|
||||
@ -250,12 +262,12 @@ namespace utils::hook
|
||||
|
||||
asmjit::Error assembler::call(void* target)
|
||||
{
|
||||
return Assembler::call(size_t(target));
|
||||
return Assembler::call(reinterpret_cast<size_t>(target));
|
||||
}
|
||||
|
||||
asmjit::Error assembler::jmp(void* target)
|
||||
{
|
||||
return Assembler::jmp(size_t(target));
|
||||
return Assembler::jmp(reinterpret_cast<size_t>(target));
|
||||
}
|
||||
|
||||
detour::detour()
|
||||
@ -299,7 +311,6 @@ namespace utils::hook
|
||||
{
|
||||
this->clear();
|
||||
this->place_ = place;
|
||||
store_original_data(place, 14);
|
||||
|
||||
if (MH_CreateHook(this->place_, target, &this->original_) != MH_OK)
|
||||
{
|
||||
@ -332,6 +343,11 @@ namespace utils::hook
|
||||
this->moved_data_ = move_hook(this->place_);
|
||||
}
|
||||
|
||||
void* detour::get_place() const
|
||||
{
|
||||
return this->place_;
|
||||
}
|
||||
|
||||
void* detour::get_original() const
|
||||
{
|
||||
return this->original_;
|
||||
@ -345,15 +361,14 @@ namespace utils::hook
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<std::pair<void*, void*>> iat(const nt::library& library, const std::string& target_library, const std::string& process, void* stub)
|
||||
std::optional<std::pair<void*, void*>> iat(const nt::library& library, const std::string& target_library,
|
||||
const std::string& process, void* stub)
|
||||
{
|
||||
if (!library.is_valid()) return {};
|
||||
|
||||
auto* const ptr = library.get_iat_entry(target_library, process);
|
||||
if (!ptr) return {};
|
||||
|
||||
store_original_data(ptr, sizeof(*ptr));
|
||||
|
||||
DWORD protect;
|
||||
VirtualProtect(ptr, sizeof(*ptr), PAGE_EXECUTE_READWRITE, &protect);
|
||||
|
||||
@ -365,8 +380,6 @@ namespace utils::hook
|
||||
|
||||
void nop(void* place, const size_t length)
|
||||
{
|
||||
store_original_data(place, length);
|
||||
|
||||
DWORD old_protect{};
|
||||
VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect);
|
||||
|
||||
@ -383,8 +396,6 @@ namespace utils::hook
|
||||
|
||||
void copy(void* place, const void* data, const size_t length)
|
||||
{
|
||||
store_original_data(place, length);
|
||||
|
||||
DWORD old_protect{};
|
||||
VirtualProtect(place, length, PAGE_EXECUTE_READWRITE, &old_protect);
|
||||
|
||||
@ -411,9 +422,14 @@ namespace utils::hook
|
||||
|
||||
bool is_relatively_far(const void* pointer, const void* data, const int offset)
|
||||
{
|
||||
const int64_t diff = size_t(data) - (size_t(pointer) + offset);
|
||||
const auto small_diff = int32_t(diff);
|
||||
return diff != int64_t(small_diff);
|
||||
return is_relatively_far(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data), offset);
|
||||
}
|
||||
|
||||
bool is_relatively_far(const size_t pointer, const size_t data, const int offset)
|
||||
{
|
||||
const auto diff = static_cast<int64_t>(data - (pointer + offset));
|
||||
const auto small_diff = static_cast<int32_t>(diff);
|
||||
return diff != static_cast<int64_t>(small_diff);
|
||||
}
|
||||
|
||||
void call(void* pointer, void* data)
|
||||
@ -433,9 +449,10 @@ namespace utils::hook
|
||||
|
||||
uint8_t copy_data[5];
|
||||
copy_data[0] = 0xE8;
|
||||
*reinterpret_cast<int32_t*>(©_data[1]) = int32_t(size_t(data) - (size_t(pointer) + 5));
|
||||
*reinterpret_cast<int32_t*>(©_data[1]) = static_cast<int32_t>(reinterpret_cast<size_t>(data) - (
|
||||
reinterpret_cast<size_t>(pointer) + 5));
|
||||
|
||||
auto* patch_pointer = PBYTE(pointer);
|
||||
auto* patch_pointer = static_cast<PBYTE>(pointer);
|
||||
copy(patch_pointer, copy_data, sizeof(copy_data));
|
||||
}
|
||||
|
||||
@ -524,24 +541,35 @@ namespace utils::hook
|
||||
asm_function(a);
|
||||
|
||||
void* result = nullptr;
|
||||
runtime.add(&result, &code);
|
||||
auto err_result = runtime.add(&result, &code);
|
||||
|
||||
if (err_result != asmjit::ErrorCode::kErrorOk)
|
||||
{
|
||||
printf("ASMJIT ERROR: %s\n", asmjit::DebugUtils::errorAsString(err_result));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void inject(void* pointer, const void* data)
|
||||
void inject(size_t pointer, size_t data)
|
||||
{
|
||||
if (is_relatively_far(pointer, data, 4))
|
||||
{
|
||||
throw std::runtime_error("Too far away to create 32bit relative branch");
|
||||
}
|
||||
|
||||
set<int32_t>(pointer, int32_t(size_t(data) - (size_t(pointer) + 4)));
|
||||
set<int32_t>(
|
||||
pointer, static_cast<int32_t>(data - (pointer + 4)));
|
||||
}
|
||||
|
||||
void inject(void* pointer, const void* data)
|
||||
{
|
||||
return inject(reinterpret_cast<size_t>(pointer), reinterpret_cast<size_t>(data));
|
||||
}
|
||||
|
||||
void inject(const size_t pointer, const void* data)
|
||||
{
|
||||
return inject(reinterpret_cast<void*>(pointer), data);
|
||||
return inject(pointer, reinterpret_cast<size_t>(data));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> move_hook(void* pointer)
|
||||
@ -589,26 +617,4 @@ namespace utils::hook
|
||||
|
||||
return extract<void*>(data + 1);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> query_original_data(const void* data, const size_t length)
|
||||
{
|
||||
std::vector<uint8_t> og_data{};
|
||||
og_data.resize(length);
|
||||
memcpy(og_data.data(), data, length);
|
||||
|
||||
get_original_data_map().access([data, length, &og_data](const std::map<const void*, uint8_t>& og_map)
|
||||
{
|
||||
auto* ptr = static_cast<const uint8_t*>(data);
|
||||
for (size_t i = 0; i < length; ++i)
|
||||
{
|
||||
auto entry = og_map.find(ptr + i);
|
||||
if (entry != og_map.end())
|
||||
{
|
||||
og_data[i] = entry->second;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return og_data;
|
||||
}
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
#include "signature.hpp"
|
||||
#include "memory.hpp"
|
||||
|
||||
#include <asmjit/core/jitruntime.h>
|
||||
#include <asmjit/x86/x86assembler.h>
|
||||
|
||||
using namespace asmjit::x86;
|
||||
|
||||
Mem seg_ptr(const SReg& segment, const uint64_t off);
|
||||
|
||||
namespace utils::hook
|
||||
{
|
||||
namespace detail
|
||||
@ -125,6 +128,8 @@ namespace utils::hook
|
||||
|
||||
void move();
|
||||
|
||||
void* get_place() const;
|
||||
|
||||
template <typename T>
|
||||
T* get() const
|
||||
{
|
||||
@ -147,7 +152,8 @@ namespace utils::hook
|
||||
void un_move();
|
||||
};
|
||||
|
||||
std::optional<std::pair<void*, void*>> iat(const nt::library& library, const std::string& target_library, const std::string& process, void* stub);
|
||||
std::optional<std::pair<void*, void*>> iat(const nt::library& library, const std::string& target_library,
|
||||
const std::string& process, void* stub);
|
||||
|
||||
void nop(void* place, size_t length);
|
||||
void nop(size_t place, size_t length);
|
||||
@ -159,6 +165,7 @@ namespace utils::hook
|
||||
void copy_string(size_t place, const char* str);
|
||||
|
||||
bool is_relatively_far(const void* pointer, const void* data, int offset = 5);
|
||||
bool is_relatively_far(size_t pointer, size_t data, int offset = 5);
|
||||
|
||||
void call(void* pointer, void* data);
|
||||
void call(size_t pointer, void* data);
|
||||
@ -172,6 +179,7 @@ namespace utils::hook
|
||||
|
||||
void inject(void* pointer, const void* data);
|
||||
void inject(size_t pointer, const void* data);
|
||||
void inject(size_t pointer, size_t data);
|
||||
|
||||
std::vector<uint8_t> move_hook(void* pointer);
|
||||
std::vector<uint8_t> move_hook(size_t pointer);
|
||||
@ -210,5 +218,51 @@ namespace utils::hook
|
||||
return static_cast<T(*)(Args ...)>(func)(args...);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> query_original_data(const void* data, size_t length);
|
||||
template <size_t Base>
|
||||
void* allocate_far_jump()
|
||||
{
|
||||
constexpr auto alloc_size = 0x1000;
|
||||
constexpr auto far_jmp_size = 0xC;
|
||||
|
||||
const auto alloc_jump_table = []
|
||||
{
|
||||
return reinterpret_cast<char*>(
|
||||
memory::allocate_near(Base, alloc_size, PAGE_EXECUTE_READWRITE));
|
||||
};
|
||||
|
||||
static auto jump_table = alloc_jump_table();
|
||||
static auto current_pos = jump_table;
|
||||
|
||||
if (current_pos + far_jmp_size >= jump_table + alloc_size)
|
||||
{
|
||||
jump_table = alloc_jump_table();
|
||||
current_pos = jump_table;
|
||||
}
|
||||
|
||||
const auto ptr = current_pos;
|
||||
current_pos += far_jmp_size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
template <size_t Base, typename T>
|
||||
void* create_far_jump(const T dest)
|
||||
{
|
||||
static std::unordered_map<void*, void*> allocated_jumps;
|
||||
if (const auto iter = allocated_jumps.find(reinterpret_cast<void*>(dest)); iter != allocated_jumps.end())
|
||||
{
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
const auto pos = allocate_far_jump<Base>();
|
||||
jump(pos, dest, true);
|
||||
allocated_jumps.insert(std::make_pair(dest, pos));
|
||||
return pos;
|
||||
}
|
||||
|
||||
template <size_t Base, typename T>
|
||||
void far_jump(const size_t address, const T dest)
|
||||
{
|
||||
const auto pos = create_far_jump<Base>(dest);
|
||||
jump(address, pos, false);
|
||||
}
|
||||
}
|
@ -166,6 +166,33 @@ namespace utils
|
||||
return false;
|
||||
}
|
||||
|
||||
void* memory::allocate_near(const size_t address, const size_t size, const std::uint32_t protect)
|
||||
{
|
||||
SYSTEM_INFO system_info{};
|
||||
GetSystemInfo(&system_info);
|
||||
|
||||
const auto page_size = system_info.dwPageSize;
|
||||
const auto aligned_size = size + (~size & (page_size - 1));
|
||||
auto current_address = address;
|
||||
|
||||
while (true)
|
||||
{
|
||||
current_address -= page_size;
|
||||
|
||||
if (current_address <= reinterpret_cast<size_t>(system_info.lpMinimumApplicationAddress))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto result = VirtualAlloc(reinterpret_cast<void*>(current_address), aligned_size, MEM_RESERVE | MEM_COMMIT, protect);
|
||||
if (result != nullptr)
|
||||
{
|
||||
std::memset(result, 0, aligned_size);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memory::allocator* memory::get_allocator()
|
||||
{
|
||||
return &memory::mem_allocator_;
|
||||
|
@ -69,6 +69,8 @@ namespace utils
|
||||
static bool is_bad_code_ptr(const void* ptr);
|
||||
static bool is_rdata_ptr(void* ptr);
|
||||
|
||||
static void* allocate_near(const size_t address, const size_t size, const std::uint32_t protect);
|
||||
|
||||
static allocator* get_allocator();
|
||||
|
||||
private:
|
||||
|
2
update_quak_zt.bat
Normal file
2
update_quak_zt.bat
Normal file
@ -0,0 +1,2 @@
|
||||
cd deps/gsc-tool
|
||||
git pull origin iw7
|
Loading…
Reference in New Issue
Block a user