maint: fix a deps header

This commit is contained in:
Diavolo 2022-12-12 14:59:31 +01:00
parent b022f56658
commit e1c658fff1
No known key found for this signature in database
GPG Key ID: FA77F074E98D98A5
11 changed files with 340 additions and 15 deletions

View File

@ -11,18 +11,8 @@ namespace gsc
return compiler; return compiler;
} }
std::unique_ptr<xsk::gsc::decompiler> decompiler()
{
return std::make_unique<xsk::gsc::iw5::decompiler>();
}
std::unique_ptr<xsk::gsc::assembler> assembler() std::unique_ptr<xsk::gsc::assembler> assembler()
{ {
return std::make_unique<xsk::gsc::iw5::assembler>(); return std::make_unique<xsk::gsc::iw5::assembler>();
} }
std::unique_ptr<xsk::gsc::disassembler> disassembler()
{
return std::make_unique<xsk::gsc::iw5::disassembler>();
}
} }

View File

@ -3,7 +3,5 @@
namespace gsc namespace gsc
{ {
std::unique_ptr<xsk::gsc::compiler> compiler(); std::unique_ptr<xsk::gsc::compiler> compiler();
std::unique_ptr<xsk::gsc::decompiler> decompiler();
std::unique_ptr<xsk::gsc::assembler> assembler(); std::unique_ptr<xsk::gsc::assembler> assembler();
std::unique_ptr<xsk::gsc::disassembler> disassembler();
} }

View File

@ -13,6 +13,7 @@ namespace game
DB_LoadXAssets_t DB_LoadXAssets; DB_LoadXAssets_t DB_LoadXAssets;
DB_FindXAssetHeader_t DB_FindXAssetHeader; DB_FindXAssetHeader_t DB_FindXAssetHeader;
DB_IsXAssetDefault_t DB_IsXAssetDefault;
Dvar_RegisterBool_t Dvar_RegisterBool; Dvar_RegisterBool_t Dvar_RegisterBool;
Dvar_RegisterString_t Dvar_RegisterString; Dvar_RegisterString_t Dvar_RegisterString;
@ -46,6 +47,12 @@ namespace game
Sys_ShowConsole_t Sys_ShowConsole; Sys_ShowConsole_t Sys_ShowConsole;
Sys_Error_t Sys_Error; Sys_Error_t Sys_Error;
Sys_Milliseconds_t Sys_Milliseconds; Sys_Milliseconds_t Sys_Milliseconds;
Sys_Sleep_t Sys_Sleep;
PMem_AllocFromSource_NoDebug_t PMem_AllocFromSource_NoDebug;
Hunk_AllocateTempMemoryHighInternal_t Hunk_AllocateTempMemoryHighInternal;
Hunk_FreeTempMemory_t Hunk_FreeTempMemory;
VM_Notify_t VM_Notify; VM_Notify_t VM_Notify;
@ -78,6 +85,7 @@ namespace game
Com_Quit_f_t Com_Quit_f; Com_Quit_f_t Com_Quit_f;
FS_Printf_t FS_Printf; FS_Printf_t FS_Printf;
FS_ReadFile_t FS_ReadFile;
player_die_t player_die; player_die_t player_die;
@ -127,6 +135,8 @@ namespace game
int* dvarCount; int* dvarCount;
dvar_t** sortedDvars; dvar_t** sortedDvars;
FastCriticalSection* db_hashCritSect;
int Vec4Compare(const float* a, const float* b) int Vec4Compare(const float* a, const float* b)
{ {
return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3]; return a[0] == b[0] && a[1] == b[1] && a[2] == b[2] && a[3] == b[3];
@ -528,6 +538,18 @@ namespace game
return id == *threadId[THREAD_CONTEXT_SERVER]; return id == *threadId[THREAD_CONTEXT_SERVER];
} }
void Sys_LockRead(FastCriticalSection* critSect)
{
InterlockedIncrement(&critSect->readCount);
while (critSect->writeCount) Sys_Sleep(0);
}
void Sys_UnlockRead(FastCriticalSection* critSect)
{
assert(critSect->readCount > 0);
InterlockedDecrement(&critSect->readCount);
}
void FS_FCloseFile(int h) void FS_FCloseFile(int h)
{ {
reinterpret_cast<void(*)(int)>(SELECT_VALUE(0x415160, 0x5AF170))(h); reinterpret_cast<void(*)(int)>(SELECT_VALUE(0x415160, 0x5AF170))(h);
@ -606,6 +628,38 @@ namespace game
{ {
assert(*fs_searchpaths); assert(*fs_searchpaths);
} }
XAssetEntry* db_find_x_asset_entry(XAssetType type_, const char* name)
{
static DWORD func = SELECT_VALUE(0x585400, 0x4CA450);
XAssetEntry* result{};
__asm
{
pushad
push name
mov edi, type_
call func
add esp, 0x4
mov result, eax
popad
}
return result;
}
XAssetEntry* DB_FindXAssetEntry(XAssetType type, const char* name)
{
return db_find_x_asset_entry(type, name);
}
int DB_XAssetExists(XAssetType type, const char* name)
{
Sys_LockRead(db_hashCritSect);
auto* asset_entry = DB_FindXAssetEntry(type, name);
Sys_UnlockRead(db_hashCritSect);
return asset_entry != nullptr;
}
} }
launcher::mode mode = launcher::mode::none; launcher::mode mode = launcher::mode::none;
@ -642,6 +696,7 @@ namespace game
native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020)); native::DB_LoadXAssets = native::DB_LoadXAssets_t(SELECT_VALUE(0x48A8E0, 0x4CD020));
native::DB_FindXAssetHeader = native::DB_FindXAssetHeader_t(SELECT_VALUE(0x4FF000, 0x4CA620)); native::DB_FindXAssetHeader = native::DB_FindXAssetHeader_t(SELECT_VALUE(0x4FF000, 0x4CA620));
native::DB_IsXAssetDefault = native::DB_IsXAssetDefault_t(SELECT_VALUE(0x4868E0, 0x4CA800));
native::Dvar_RegisterBool = native::Dvar_RegisterBool_t(SELECT_VALUE(0x4914D0, 0x5BE9F0)); native::Dvar_RegisterBool = native::Dvar_RegisterBool_t(SELECT_VALUE(0x4914D0, 0x5BE9F0));
native::Dvar_RegisterString = native::Dvar_RegisterString_t(SELECT_VALUE(0x5197F0, 0x5BEC90)); native::Dvar_RegisterString = native::Dvar_RegisterString_t(SELECT_VALUE(0x5197F0, 0x5BEC90));
@ -676,6 +731,12 @@ namespace game
native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590)); native::Sys_ShowConsole = native::Sys_ShowConsole_t(SELECT_VALUE(0x470AF0, 0x5CF590));
native::Sys_Error = native::Sys_Error_t(SELECT_VALUE(0x490D90, 0x5CC3B0)); native::Sys_Error = native::Sys_Error_t(SELECT_VALUE(0x490D90, 0x5CC3B0));
native::Sys_Milliseconds = native::Sys_Milliseconds_t(SELECT_VALUE(0x4A1610, 0x5CE740)); native::Sys_Milliseconds = native::Sys_Milliseconds_t(SELECT_VALUE(0x4A1610, 0x5CE740));
native::Sys_Sleep = native::Sys_Sleep_t(SELECT_VALUE(0x438600, 0x55F460));
native::PMem_AllocFromSource_NoDebug = native::PMem_AllocFromSource_NoDebug_t(SELECT_VALUE(0x449E50, 0x5C15C0));
native::Hunk_AllocateTempMemoryHighInternal = native::Hunk_AllocateTempMemoryHighInternal_t(SELECT_VALUE(0x517870, 0x5B6C60));
native::Hunk_FreeTempMemory = native::Hunk_FreeTempMemory_t(SELECT_VALUE(0x434A40, 0x5B6F90));
native::VM_Notify = native::VM_Notify_t(SELECT_VALUE(0x610200, 0x569720)); native::VM_Notify = native::VM_Notify_t(SELECT_VALUE(0x610200, 0x569720));
@ -716,6 +777,7 @@ namespace game
native::Com_Quit_f = native::Com_Quit_f_t(SELECT_VALUE(0x4F48B0, 0x5556B0)); native::Com_Quit_f = native::Com_Quit_f_t(SELECT_VALUE(0x4F48B0, 0x5556B0));
native::FS_Printf = native::FS_Printf_t(SELECT_VALUE(0x421E90, 0x5AF7C0)); native::FS_Printf = native::FS_Printf_t(SELECT_VALUE(0x421E90, 0x5AF7C0));
native::FS_ReadFile = native::FS_ReadFile_t(SELECT_VALUE(0x4D8DF0, 0x5B1FB0));
native::player_die = native::player_die_t(SELECT_VALUE(0x0, 0x503460)); native::player_die = native::player_die_t(SELECT_VALUE(0x0, 0x503460));
@ -770,5 +832,7 @@ namespace game
native::dvarCount = reinterpret_cast<int*>(SELECT_VALUE(0x1C42398, 0x59CCDD8)); native::dvarCount = reinterpret_cast<int*>(SELECT_VALUE(0x1C42398, 0x59CCDD8));
native::sortedDvars = reinterpret_cast<native::dvar_t**>(SELECT_VALUE(0x1C423C0, 0x59CCE00)); native::sortedDvars = reinterpret_cast<native::dvar_t**>(SELECT_VALUE(0x1C423C0, 0x59CCE00));
native::db_hashCritSect = reinterpret_cast<native::FastCriticalSection*>(SELECT_VALUE(0xFA9E7C, 0x18596E4));
} }
} }

View File

@ -29,6 +29,9 @@ namespace game
typedef XAssetHeader (*DB_FindXAssetHeader_t)(XAssetType type, const char* name, int allowCreateDefault); typedef XAssetHeader (*DB_FindXAssetHeader_t)(XAssetType type, const char* name, int allowCreateDefault);
extern DB_FindXAssetHeader_t DB_FindXAssetHeader; extern DB_FindXAssetHeader_t DB_FindXAssetHeader;
typedef int (*DB_IsXAssetDefault_t)(XAssetType type, const char* name);
extern DB_IsXAssetDefault_t DB_IsXAssetDefault;
typedef const dvar_t* (*Dvar_RegisterBool_t)(const char* dvarName, bool value, typedef const dvar_t* (*Dvar_RegisterBool_t)(const char* dvarName, bool value,
unsigned __int16 flags, const char* description); unsigned __int16 flags, const char* description);
extern Dvar_RegisterBool_t Dvar_RegisterBool; extern Dvar_RegisterBool_t Dvar_RegisterBool;
@ -97,6 +100,18 @@ namespace game
typedef int (*Sys_Milliseconds_t)(); typedef int (*Sys_Milliseconds_t)();
extern Sys_Milliseconds_t Sys_Milliseconds; extern Sys_Milliseconds_t Sys_Milliseconds;
typedef void (*Sys_Sleep_t)(int msec);
extern Sys_Sleep_t Sys_Sleep;
typedef void* (*PMem_AllocFromSource_NoDebug_t)(unsigned int size, unsigned int alignment, unsigned int type, int source);
extern PMem_AllocFromSource_NoDebug_t PMem_AllocFromSource_NoDebug;
typedef void* (*Hunk_AllocateTempMemoryHighInternal_t)(unsigned int size);
extern Hunk_AllocateTempMemoryHighInternal_t Hunk_AllocateTempMemoryHighInternal;
typedef void (*Hunk_FreeTempMemory_t)(void* buf);
extern Hunk_FreeTempMemory_t Hunk_FreeTempMemory;
typedef void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top); typedef void (*VM_Notify_t)(unsigned int notifyListOwnerId, unsigned int stringValue, VariableValue* top);
extern VM_Notify_t VM_Notify; extern VM_Notify_t VM_Notify;
@ -158,6 +173,9 @@ namespace game
typedef void (*FS_Printf_t)(int h, const char* fmt, ...); typedef void (*FS_Printf_t)(int h, const char* fmt, ...);
extern FS_Printf_t FS_Printf; extern FS_Printf_t FS_Printf;
typedef int (*FS_ReadFile_t)(const char* qpath, char** buffer);
extern FS_ReadFile_t FS_ReadFile;
typedef void (*player_die_t)(gentity_s* self, const gentity_s* inflictor, gentity_s* attacker, int damage, int meansOfDeath, const Weapon* iWeapon, bool isAlternate, const float* vDir, const hitLocation_t hitLoc, int psTimeOffset); typedef void (*player_die_t)(gentity_s* self, const gentity_s* inflictor, gentity_s* attacker, int damage, int meansOfDeath, const Weapon* iWeapon, bool isAlternate, const float* vDir, const hitLocation_t hitLoc, int psTimeOffset);
extern player_die_t player_die; extern player_die_t player_die;
@ -210,6 +228,8 @@ namespace game
extern int* dvarCount; extern int* dvarCount;
extern dvar_t** sortedDvars; extern dvar_t** sortedDvars;
extern FastCriticalSection* db_hashCritSect;
// Global Definitions & Functions // Global Definitions & Functions
constexpr auto JUMP_LAND_SLOWDOWN_TIME = 1800; constexpr auto JUMP_LAND_SLOWDOWN_TIME = 1800;
@ -309,6 +329,9 @@ namespace game
int FS_FOpenFileReadForThread(const char* filename, int* file, FsThread thread); int FS_FOpenFileReadForThread(const char* filename, int* file, FsThread thread);
int FS_CreatePath(char* OSPath); int FS_CreatePath(char* OSPath);
void FS_CheckFileSystemStarted(); void FS_CheckFileSystemStarted();
XAssetEntry* DB_FindXAssetEntry(XAssetType type, const char* name);
int DB_XAssetExists(XAssetType type, const char* name);
} }
bool is_mp(); bool is_mp();

View File

@ -389,10 +389,48 @@ namespace game
unsigned char* bytecode; unsigned char* bytecode;
}; };
struct RawFile
{
const char* name;
int compressedLen;
int len;
const char* buffer;
};
union XAssetHeader union XAssetHeader
{ {
void* data; void* data;
ScriptFile* scriptfile; ScriptFile* scriptfile;
RawFile* rawfile;
};
struct XAsset
{
XAssetType type;
XAssetHeader header;
};
struct XAssetEntry
{
XAsset asset;
char zoneIndex;
volatile char inuseMask;
bool printedMissingAsset;
unsigned __int16 nextHash;
unsigned __int16 nextOverride;
};
struct TempPriority
{
void* threadHandle;
int oldPriority;
};
struct FastCriticalSection
{
volatile long readCount;
volatile long writeCount;
TempPriority tempPriority;
}; };
enum errorParm_t enum errorParm_t
@ -437,6 +475,14 @@ namespace game
LOCMSG_NOERR, LOCMSG_NOERR,
}; };
enum PMem_Source
{
PMEM_SOURCE_EXTERNAL,
PMEM_SOURCE_DATABASE,
PMEM_SOURCE_MOVIE,
PMEM_SOURCE_SCRIPT,
};
struct cmd_function_t struct cmd_function_t
{ {
cmd_function_t* next; cmd_function_t* next;

View File

@ -51,7 +51,7 @@ namespace gsc
} }
else else
{ {
unknown_function_error = std::format("unknown script '{}'", scripting::current_file); unknown_function_error = std::format("unknown script '{}'.gsc", scripting::current_file);
} }
} }

View File

@ -1,12 +1,180 @@
#include <std_include.hpp> #include <std_include.hpp>
#include <loader/module_loader.hpp>
#include "game/game.hpp" #include "game/game.hpp"
#include "script_loading.hpp" #include "script_loading.hpp"
#include "module/log_file.hpp"
#include "module/scripting.hpp"
#include <utils/hook.hpp>
#include <utils/memory.hpp>
#include <xsk/gsc/types.hpp>
#include <xsk/gsc/interfaces/compiler.hpp>
#include <xsk/gsc/interfaces/assembler.hpp>
#include <xsk/utils/compression.hpp>
#include <xsk/resolver.hpp>
#include <interface.hpp>
namespace gsc namespace gsc
{ {
namespace
{
auto compiler = ::gsc::compiler();
auto assembler = ::gsc::assembler();
utils::memory::allocator script_file_allocator;
std::unordered_map<std::string, game::native::ScriptFile*> loaded_scripts;
void clear()
{
loaded_scripts.clear();
script_file_allocator.clear();
}
bool read_script_file(const std::string& name, std::string* data)
{
char* buffer{};
const auto file_len = game::native::FS_ReadFile(name.data(), &buffer);
if (file_len > 0 && buffer)
{
data->append(buffer, file_len);
game::native::Hunk_FreeTempMemory(buffer);
return true;
}
return false;
}
game::native::ScriptFile* load_custom_script(const char* file_name, const std::string& real_name)
{
if (const auto itr = loaded_scripts.find(real_name); itr != loaded_scripts.end())
{
return itr->second;
}
std::string source_buffer;
if (!read_script_file(real_name + ".gsc", &source_buffer))
{
return nullptr;
}
std::vector<std::uint8_t> data;
data.assign(source_buffer.begin(), source_buffer.end());
try
{
compiler->compile(real_name, data);
}
catch (const std::exception& ex)
{
log_file::info("*********** script compile error *************\n");
log_file::info("failed to compile '%s':\n%s", real_name.data(), ex.what());
log_file::info("**********************************************\n");
return nullptr;
}
auto assembly = compiler->output();
try
{
assembler->assemble(real_name, assembly);
}
catch (const std::exception& ex)
{
log_file::info("*********** script compile error *************\n");
log_file::info("failed to assemble '%s':\n%s", real_name.data(), ex.what());
log_file::info("**********************************************\n");
return nullptr;
}
const auto script_file_ptr = static_cast<game::native::ScriptFile*>(script_file_allocator.allocate(sizeof(game::native::ScriptFile)));
script_file_ptr->name = file_name;
const auto stack = assembler->output_stack();
script_file_ptr->len = static_cast<int>(stack.size());
const auto script = assembler->output_script();
script_file_ptr->bytecodeLen = static_cast<int>(script.size());
const auto compressed = xsk::utils::zlib::compress(stack);
const auto stack_size = compressed.size();
const auto byte_code_size = script.size() + 1;
script_file_ptr->buffer = static_cast<char*>(game::native::Hunk_AllocateTempMemoryHighInternal(stack_size));
std::memcpy(const_cast<char*>(script_file_ptr->buffer), compressed.data(), compressed.size());
script_file_ptr->bytecode = static_cast<std::uint8_t*>(game::native::PMem_AllocFromSource_NoDebug(byte_code_size, 4, 0, game::native::PMEM_SOURCE_SCRIPT));
std::memcpy(script_file_ptr->bytecode, script.data(), script.size());
script_file_ptr->compressedLen = static_cast<int>(compressed.size());
loaded_scripts[real_name] = script_file_ptr;
return script_file_ptr;
}
std::string get_script_file_name(const std::string& name)
{
const auto id = xsk::gsc::iw5::resolver::token_id(name);
if (!id)
{
return name;
}
return std::to_string(id);
}
int db_is_x_asset_default(game::native::XAssetType type, const char* name)
{
if (loaded_scripts.contains(name))
{
return 0;
}
return game::native::DB_IsXAssetDefault(type, name);
}
}
game::native::ScriptFile* find_script(game::native::XAssetType type, const char* name, int allow_create_default) game::native::ScriptFile* find_script(game::native::XAssetType type, const char* name, int allow_create_default)
{ {
std::string real_name = name;
const auto id = static_cast<std::uint16_t>(std::strtol(name, nullptr, 10));
if (id)
{
real_name = xsk::gsc::iw5::resolver::token_name(id);
}
auto* script = load_custom_script(name, real_name);
if (script)
{
return script;
}
return game::native::DB_FindXAssetHeader(type, name, allow_create_default).scriptfile; return game::native::DB_FindXAssetHeader(type, name, allow_create_default).scriptfile;
} }
class script_loading final : public module
{
public:
void post_load() override
{
// ProcessScript
utils::hook(SELECT_VALUE(0x44685E, 0x56B13E), find_script, HOOK_CALL).install()->quick();
utils::hook(SELECT_VALUE(0x446868, 0x56B148), db_is_x_asset_default, HOOK_CALL).install()->quick();
scripting::on_shutdown([](int free_scripts) -> void
{
if (free_scripts)
{
xsk::gsc::iw5::resolver::cleanup();
clear();
} }
});
}
};
}
REGISTER_MODULE(gsc::script_loading)

View File

@ -37,7 +37,7 @@ private:
utils::hook::set<std::uint8_t>(0x663B5A, 0xEB); utils::hook::set<std::uint8_t>(0x663B5A, 0xEB);
utils::hook::set<std::uint8_t>(0x663C54, 0xEB); utils::hook::set<std::uint8_t>(0x663C54, 0xEB);
// archive "name" dvar // Archive "name" dvar
utils::hook::set<std::uint8_t>(0x4296F9, game::native::DVAR_ARCHIVE); utils::hook::set<std::uint8_t>(0x4296F9, game::native::DVAR_ARCHIVE);
utils::hook(0x44C640, &live_get_local_client_name_stub, HOOK_JUMP).install()->quick(); utils::hook(0x44C640, &live_get_local_client_name_stub, HOOK_JUMP).install()->quick();
@ -48,10 +48,17 @@ private:
// Note: on SP the max value is already 1000 // Note: on SP the max value is already 1000
utils::hook(0x55411F, &dvar_register_com_max_fps, HOOK_CALL).install()->quick(); utils::hook(0x55411F, &dvar_register_com_max_fps, HOOK_CALL).install()->quick();
// archive "name" dvar // Archive "name" dvar
utils::hook::set<std::uint32_t>(0x492C82, game::native::DVAR_USERINFO | game::native::DVAR_ARCHIVE); utils::hook::set<std::uint32_t>(0x492C82, game::native::DVAR_USERINFO | game::native::DVAR_ARCHIVE);
utils::hook(0x5C9980, &live_get_local_client_name_stub, HOOK_JUMP).install()->quick(); utils::hook(0x5C9980, &live_get_local_client_name_stub, HOOK_JUMP).install()->quick();
// Unpure client detected
utils::hook::set<std::uint8_t>(0x57228C, 0xEB);
// Allow any IWD file to be loaded
utils::hook::nop(0x5B090F, 6);
utils::hook::nop(0x5B092C, 6);
} }
void patch_dedi() const void patch_dedi() const

View File

@ -24,6 +24,8 @@ namespace scripting
std::uint32_t current_file_id = 0; std::uint32_t current_file_id = 0;
std::string current_script_file; std::string current_script_file;
std::vector<std::function<void(int)>> shutdown_callbacks;
std::string get_token(unsigned int id) std::string get_token(unsigned int id)
{ {
return find_token(id); return find_token(id);
@ -93,6 +95,22 @@ namespace scripting
utils::hook::invoke<void>(SELECT_VALUE(0x446850, 0x56B130), filename); utils::hook::invoke<void>(SELECT_VALUE(0x446850, 0x56B130), filename);
} }
void g_shutdown_game_stub(int free_scripts)
{
utils::hook::invoke<void>(SELECT_VALUE(0x528A90, 0x50C100), free_scripts);
if (free_scripts)
{
script_function_table_sort.clear();
script_function_table.clear();
}
for (const auto& callback : shutdown_callbacks)
{
callback(free_scripts);
}
}
} }
std::string get_token(unsigned int id) std::string get_token(unsigned int id)
@ -100,6 +118,11 @@ namespace scripting
return find_token(id); return find_token(id);
} }
void on_shutdown(const std::function<void(int)>& callback)
{
shutdown_callbacks.push_back(callback);
}
class scripting_class final : public module class scripting_class final : public module
{ {
public: public:
@ -131,6 +154,9 @@ namespace scripting
utils::hook(SELECT_VALUE(0x44690A, 0x56B1EA), &scr_set_thread_position, HOOK_CALL).install()->quick(); utils::hook(SELECT_VALUE(0x44690A, 0x56B1EA), &scr_set_thread_position, HOOK_CALL).install()->quick();
utils::hook(SELECT_VALUE(0x4232A8, 0x561748), &process_script, HOOK_CALL).install()->quick(); utils::hook(SELECT_VALUE(0x4232A8, 0x561748), &process_script, HOOK_CALL).install()->quick();
utils::hook(SELECT_VALUE(0x651E1B, 0x573C82), &g_shutdown_game_stub, HOOK_CALL).install()->quick();
utils::hook(SELECT_VALUE(0x651ECC, 0x573D3A), &g_shutdown_game_stub, HOOK_CALL).install()->quick();
} }
void pre_destroy() override void pre_destroy() override

View File

@ -9,4 +9,6 @@ namespace scripting
extern std::string current_file; extern std::string current_file;
std::string get_token(unsigned int id); std::string get_token(unsigned int id);
void on_shutdown(const std::function<void(int)>& callback);
} }

View File

@ -42,6 +42,7 @@
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <filesystem> #include <filesystem>
#include <format>
#include <fstream> #include <fstream>
#include <map> #include <map>
#include <mutex> #include <mutex>