mirror of
https://github.com/JezuzLizard/T4SP-Server-Plugin.git
synced 2025-09-03 15:17:26 +00:00
Add scheduler, add support for GSC method adding, command adding.
Some cleanup. Add exception handler.
This commit is contained in:
137
src/component/exception.cpp
Normal file
137
src/component/exception.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
#include <utils/compression.hpp>
|
||||
|
||||
#include <exception/minidump.hpp>
|
||||
|
||||
namespace exception
|
||||
{
|
||||
namespace
|
||||
{
|
||||
thread_local struct
|
||||
{
|
||||
DWORD code = 0;
|
||||
PVOID address = nullptr;
|
||||
} exception_data;
|
||||
|
||||
void show_mouse_cursor()
|
||||
{
|
||||
while (ShowCursor(TRUE) < 0);
|
||||
}
|
||||
|
||||
void display_error_dialog()
|
||||
{
|
||||
std::string error_str = utils::string::va("Fatal error (0x%08X) at 0x%p.\n"
|
||||
"A minidump has been written.\n\n",
|
||||
exception_data.code, exception_data.address);
|
||||
|
||||
error_str += "Make sure to update your graphics card drivers and install operating system updates!";
|
||||
|
||||
utils::thread::suspend_other_threads();
|
||||
show_mouse_cursor();
|
||||
MessageBoxA(nullptr, error_str.data(), "Plutonium T4 ERROR", MB_ICONERROR);
|
||||
TerminateProcess(GetCurrentProcess(), exception_data.code);
|
||||
}
|
||||
|
||||
void reset_state()
|
||||
{
|
||||
display_error_dialog();
|
||||
}
|
||||
|
||||
size_t get_reset_state_stub()
|
||||
{
|
||||
static auto* stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.sub(esp, 0x10);
|
||||
a.or_(esp, 0x8);
|
||||
a.jmp(reset_state);
|
||||
});
|
||||
|
||||
return reinterpret_cast<size_t>(stub);
|
||||
}
|
||||
|
||||
std::string generate_crash_info(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
std::string info{};
|
||||
const auto line = [&info](const std::string& text)
|
||||
{
|
||||
info.append(text);
|
||||
info.append("\r\n");
|
||||
};
|
||||
|
||||
line("Plutonium T4 Crash Dump");
|
||||
line("");
|
||||
line("Timestamp: "s + utils::string::get_timestamp());
|
||||
line(utils::string::va("Exception: 0x%08X", exceptioninfo->ExceptionRecord->ExceptionCode));
|
||||
line(utils::string::va("Address: 0x%lX", exceptioninfo->ExceptionRecord->ExceptionAddress));
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
OSVERSIONINFOEXA version_info;
|
||||
ZeroMemory(&version_info, sizeof(version_info));
|
||||
version_info.dwOSVersionInfoSize = sizeof(version_info);
|
||||
GetVersionExA(reinterpret_cast<LPOSVERSIONINFOA>(&version_info));
|
||||
#pragma warning(pop)
|
||||
|
||||
line(utils::string::va("OS Version: %u.%u", version_info.dwMajorVersion, version_info.dwMinorVersion));
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void write_minidump(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
const std::string crash_name = utils::string::va("minidumps/plutonium-t4-crash-%s.zip",
|
||||
utils::string::get_timestamp().data());
|
||||
|
||||
utils::compression::zip::archive zip_file{};
|
||||
zip_file.add("crash.dmp", create_minidump(exceptioninfo));
|
||||
zip_file.add("info.txt", generate_crash_info(exceptioninfo));
|
||||
zip_file.write(crash_name, "Plutonium T4 Crash Dump");
|
||||
}
|
||||
|
||||
bool is_harmless_error(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
const auto code = exceptioninfo->ExceptionRecord->ExceptionCode;
|
||||
return code == STATUS_INTEGER_OVERFLOW || code == STATUS_FLOAT_OVERFLOW || code == STATUS_SINGLE_STEP;
|
||||
}
|
||||
|
||||
LONG WINAPI exception_filter(const LPEXCEPTION_POINTERS exceptioninfo)
|
||||
{
|
||||
if (is_harmless_error(exceptioninfo))
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
write_minidump(exceptioninfo);
|
||||
|
||||
exception_data.code = exceptioninfo->ExceptionRecord->ExceptionCode;
|
||||
exception_data.address = exceptioninfo->ExceptionRecord->ExceptionAddress;
|
||||
exceptioninfo->ContextRecord->Eip = get_reset_state_stub();
|
||||
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER WINAPI set_unhandled_exception_filter_stub(LPTOP_LEVEL_EXCEPTION_FILTER)
|
||||
{
|
||||
// Don't register anything here...
|
||||
return &exception_filter;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
SetUnhandledExceptionFilter(exception_filter);
|
||||
utils::hook::jump(reinterpret_cast<uintptr_t>(&SetUnhandledExceptionFilter), set_unhandled_exception_filter_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(exception::component)
|
148
src/component/gsc.cpp
Normal file
148
src/component/gsc.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <json.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace gsc
|
||||
{
|
||||
std::unordered_map<unsigned int, std::pair<std::string, void*>> functions;
|
||||
|
||||
namespace
|
||||
{
|
||||
void* original_scr_get_gsc_funcs_jump_loc;
|
||||
|
||||
void* original_scr_get_method_funcs_call_loc;
|
||||
|
||||
void ebic_func()
|
||||
{
|
||||
game::Com_PrintF(game::CON_CHANNEL_SERVER, "Oof \n");
|
||||
}
|
||||
|
||||
game::BuiltinFunction plutonium_scr_get_gsc_funcs_stub(const char** pName, int* type)
|
||||
{
|
||||
//printf( "%s %d\n", * pName, * type);
|
||||
/*
|
||||
if (*pName == "isdefined"s)
|
||||
{
|
||||
return &ebic_func;
|
||||
}
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
game::BuiltinMethod plutonium_scr_get_gsc_methods_stub(const char** pName, int* type)
|
||||
{
|
||||
printf("%s %d\n", *pName, *type);
|
||||
/*
|
||||
if (*pName == "isdefined"s)
|
||||
{
|
||||
return &ebic_func;
|
||||
}
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
void __declspec(naked) original_scr_get_gsc_funcs_hook()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push eax;
|
||||
pushad;
|
||||
|
||||
lea eax, [esp + 0x24 + 0x2C - 0x1C];
|
||||
push eax;
|
||||
push edx;
|
||||
call plutonium_scr_get_gsc_funcs_stub;
|
||||
add esp, 8;
|
||||
|
||||
mov[esp + 0x20], eax;
|
||||
|
||||
popad;
|
||||
pop eax;
|
||||
|
||||
test eax, eax;
|
||||
|
||||
jnz og;
|
||||
|
||||
// pluto
|
||||
push original_scr_get_gsc_funcs_jump_loc;
|
||||
retn;
|
||||
|
||||
og:
|
||||
// retn
|
||||
add esp, 4;
|
||||
push 0x682DC8;
|
||||
retn;
|
||||
}
|
||||
}
|
||||
|
||||
void __declspec(naked) original_scr_get_method_funcs_hook()
|
||||
{
|
||||
__asm
|
||||
{
|
||||
push eax;
|
||||
pushad;
|
||||
|
||||
push edi;
|
||||
push esi;
|
||||
call plutonium_scr_get_gsc_methods_stub;
|
||||
add esp, 8;
|
||||
mov[esp + 0x20], eax; // move answer into eax when pop happens
|
||||
|
||||
popad;
|
||||
pop eax;
|
||||
|
||||
test eax, eax;
|
||||
jz pluto_code;
|
||||
|
||||
retn;
|
||||
|
||||
pluto_code:
|
||||
|
||||
push original_scr_get_method_funcs_call_loc;
|
||||
retn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int find_function(const std::string& name)
|
||||
{
|
||||
for (const auto& function : functions)
|
||||
{
|
||||
if (function.second.first == name)
|
||||
{
|
||||
return function.first;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace function
|
||||
{
|
||||
void add(const std::string& name, const void*& function)
|
||||
{
|
||||
const auto id = functions.size() + 1;
|
||||
//functions[id] = std::make_pair(name, function);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
original_scr_get_gsc_funcs_jump_loc = utils::hook::get_displacement_addr(0x682D99);
|
||||
original_scr_get_method_funcs_call_loc = utils::hook::get_displacement_addr(0x683043);
|
||||
utils::hook::jump(0x682D99, original_scr_get_gsc_funcs_hook);
|
||||
utils::hook::call(0x683043, original_scr_get_method_funcs_hook);
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(gsc::component)
|
||||
|
188
src/component/scheduler.cpp
Normal file
188
src/component/scheduler.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct task
|
||||
{
|
||||
std::function<bool()> handler{};
|
||||
std::chrono::milliseconds interval{};
|
||||
std::chrono::high_resolution_clock::time_point last_call{};
|
||||
};
|
||||
|
||||
using task_list = std::vector<task>;
|
||||
|
||||
class task_pipeline
|
||||
{
|
||||
public:
|
||||
void add(task&& task)
|
||||
{
|
||||
new_callbacks_.access([&task](task_list& tasks)
|
||||
{
|
||||
tasks.emplace_back(std::move(task));
|
||||
});
|
||||
}
|
||||
|
||||
void execute()
|
||||
{
|
||||
callbacks_.access([&](task_list& tasks)
|
||||
{
|
||||
this->merge_callbacks();
|
||||
|
||||
for (auto i = tasks.begin(); i != tasks.end();)
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto diff = now - i->last_call;
|
||||
|
||||
if (diff < i->interval)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
i->last_call = now;
|
||||
|
||||
const auto res = i->handler();
|
||||
if (res == cond_end)
|
||||
{
|
||||
i = tasks.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
utils::concurrency::container<task_list> new_callbacks_;
|
||||
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
|
||||
|
||||
void merge_callbacks()
|
||||
{
|
||||
callbacks_.access([&](task_list& tasks)
|
||||
{
|
||||
new_callbacks_.access([&](task_list& new_tasks)
|
||||
{
|
||||
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()), std::move_iterator<task_list::iterator>(new_tasks.end()));
|
||||
new_tasks = {};
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
std::thread thread;
|
||||
task_pipeline pipelines[pipeline::count];
|
||||
|
||||
void execute(const pipeline type)
|
||||
{
|
||||
assert(type >= 0 && type < pipeline::count);
|
||||
pipelines[type].execute();
|
||||
}
|
||||
|
||||
void execute_server()
|
||||
{
|
||||
execute(pipeline::server);
|
||||
}
|
||||
|
||||
utils::hook::detour com_init_hook;
|
||||
|
||||
std::vector<std::function<void()>> post_init_funcs;
|
||||
bool inited = false;
|
||||
|
||||
void on_post_init_hook()
|
||||
{
|
||||
if (inited)
|
||||
{
|
||||
return;
|
||||
}
|
||||
inited = true;
|
||||
for (const auto& func : post_init_funcs)
|
||||
{
|
||||
func();
|
||||
}
|
||||
}
|
||||
|
||||
void com_init_stub()
|
||||
{
|
||||
com_init_hook.invoke<void>();
|
||||
on_post_init_hook();
|
||||
}
|
||||
}
|
||||
|
||||
void schedule(const std::function<bool()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
assert(type >= 0 && type < pipeline::count);
|
||||
|
||||
task task;
|
||||
task.handler = callback;
|
||||
task.interval = delay;
|
||||
task.last_call = std::chrono::high_resolution_clock::now();
|
||||
|
||||
pipelines[type].add(std::move(task));
|
||||
}
|
||||
|
||||
void loop(const std::function<void()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return cond_continue;
|
||||
}, type, delay);
|
||||
}
|
||||
|
||||
void once(const std::function<void()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return cond_end;
|
||||
}, type, delay);
|
||||
}
|
||||
|
||||
void on_init(const std::function<void()>& callback)
|
||||
{
|
||||
if (inited)
|
||||
{
|
||||
callback();
|
||||
}
|
||||
else
|
||||
{
|
||||
post_init_funcs.push_back(callback);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
thread = std::thread([]()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
execute(pipeline::async);
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
});
|
||||
|
||||
com_init_hook.create(0x59D710, com_init_stub);
|
||||
|
||||
utils::hook::call(0x503B5D, execute_server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scheduler::component)
|
23
src/component/scheduler.hpp
Normal file
23
src/component/scheduler.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
enum pipeline
|
||||
{
|
||||
server,
|
||||
async,
|
||||
count,
|
||||
};
|
||||
|
||||
static const bool cond_continue = false;
|
||||
static const bool cond_end = true;
|
||||
|
||||
void schedule(const std::function<bool()>& callback, pipeline type = pipeline::server,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void loop(const std::function<void()>& callback, pipeline type = pipeline::server,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void once(const std::function<void()>& callback, pipeline type = pipeline::server,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
|
||||
void on_init(const std::function<void()>& callback);
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
#include <stdinc.hpp>
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "signatures.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace signatures
|
||||
{
|
||||
size_t load_image_size()
|
||||
{
|
||||
MODULEINFO info{};
|
||||
GetModuleInformation(GetCurrentProcess(),
|
||||
GetModuleHandle("plutonium-bootstrapper-win32.exe"), &info, sizeof(MODULEINFO));
|
||||
return info.SizeOfImage;
|
||||
}
|
||||
|
||||
size_t get_image_size()
|
||||
{
|
||||
static const auto image_size = load_image_size();
|
||||
return image_size;
|
||||
}
|
||||
|
||||
size_t find_string_ptr(const std::string& string)
|
||||
{
|
||||
const char* string_ptr = nullptr;
|
||||
std::string mask(string.size(), 'x');
|
||||
const auto base = reinterpret_cast<size_t>(GetModuleHandle("plutonium-bootstrapper-win32.exe"));
|
||||
utils::hook::signature signature(base, get_image_size() - base);
|
||||
|
||||
signature.add({
|
||||
string,
|
||||
mask,
|
||||
[&](char* address)
|
||||
{
|
||||
string_ptr = address;
|
||||
}
|
||||
});
|
||||
|
||||
signature.process();
|
||||
return reinterpret_cast<size_t>(string_ptr);
|
||||
}
|
||||
|
||||
size_t find_string_ref(const std::string& string)
|
||||
{
|
||||
char bytes[4] = {0};
|
||||
const auto string_ptr = find_string_ptr(string);
|
||||
memcpy(bytes, &string_ptr, sizeof(bytes));
|
||||
return find_string_ptr({bytes, 4});
|
||||
}
|
||||
|
||||
|
||||
bool process_printf()
|
||||
{
|
||||
const auto string_ref = find_string_ref("A critical exception occured!\n");
|
||||
if (!string_ref)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto offset = *reinterpret_cast<size_t*>(string_ref + 5);
|
||||
OutputDebugString(utils::string::va("%p\n", string_ref + 4 + 5 + offset));
|
||||
game::plutonium::printf.set(string_ref + 4 + 5 + offset);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool process()
|
||||
{
|
||||
return process_printf();
|
||||
}
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
namespace signatures
|
||||
{
|
||||
bool process();
|
||||
}
|
52
src/component/test.cpp
Normal file
52
src/component/test.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <stdinc.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace test
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::dvar_s* custom_dvar;
|
||||
game::dvar_s* custom_string_dvar;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
game::Cmd_AddCommand("testcmd", []()
|
||||
{
|
||||
if (game::Cmd_Argc() == 2)
|
||||
{
|
||||
printf("test %s\n", game::Cmd_Argv(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("test\n");
|
||||
}
|
||||
});
|
||||
|
||||
custom_dvar = game::Dvar_RegisterInt("testdvar1", 69, -69, 420, game::DVAR_FLAG_NONE, "This dvar is a dvar");
|
||||
|
||||
scheduler::on_init([]()
|
||||
{
|
||||
custom_string_dvar = game::Dvar_RegisterString("testdvar2", "This might be a dvar value", game::DVAR_FLAG_NONE, "This dvar is a dvar");
|
||||
printf("We initeded bois\n");
|
||||
});
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
//printf("Biggie Spam McCheese\n");
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(test::component)
|
Reference in New Issue
Block a user