commit
4730970b4a
72
src/client/component/game_event.cpp
Normal file
72
src/client/component/game_event.cpp
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
#include <game/game.hpp>
|
||||||
|
|
||||||
|
#include "game_event.hpp"
|
||||||
|
|
||||||
|
#include <utils/concurrency.hpp>
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
|
||||||
|
namespace game_event
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
using event_task = std::vector<std::function<void()>>;
|
||||||
|
utils::concurrency::container<event_task> g_init_game_tasks;
|
||||||
|
utils::concurrency::container<event_task> g_shutdown_game_tasks;
|
||||||
|
|
||||||
|
void rope_init_ropes_stub()
|
||||||
|
{
|
||||||
|
g_init_game_tasks.access([](event_task& tasks)
|
||||||
|
{
|
||||||
|
for (const auto& func : tasks)
|
||||||
|
{
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
game::Rope_InitRopes();
|
||||||
|
}
|
||||||
|
|
||||||
|
void mantle_shutdown_anims_stub()
|
||||||
|
{
|
||||||
|
g_shutdown_game_tasks.access([](event_task& tasks)
|
||||||
|
{
|
||||||
|
for (const auto& func : tasks)
|
||||||
|
{
|
||||||
|
func();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
game::Mantle_ShutdownAnims();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_g_init_game(const std::function<void()>& callback)
|
||||||
|
{
|
||||||
|
g_init_game_tasks.access([&callback](event_task& tasks)
|
||||||
|
{
|
||||||
|
tasks.emplace_back(callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_g_shutdown_game(const std::function<void()>& callback)
|
||||||
|
{
|
||||||
|
g_shutdown_game_tasks.access([&callback](event_task& tasks)
|
||||||
|
{
|
||||||
|
tasks.emplace_back(callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public generic_component
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
utils::hook::call(game::select(0x1419DD6EC, 0x1402ABC1B), rope_init_ropes_stub);
|
||||||
|
utils::hook::call(game::select(0x141A02AAD, 0x1402ADF1D), mantle_shutdown_anims_stub);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(game_event::component)
|
7
src/client/component/game_event.hpp
Normal file
7
src/client/component/game_event.hpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace game_event
|
||||||
|
{
|
||||||
|
void on_g_init_game(const std::function<void()>& callback);
|
||||||
|
void on_g_shutdown_game(const std::function<void()>& callback);
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include "scheduler.hpp"
|
#include "game_event.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
@ -17,6 +17,7 @@ namespace script
|
|||||||
utils::hook::detour db_find_x_asset_header_hook;
|
utils::hook::detour db_find_x_asset_header_hook;
|
||||||
utils::hook::detour gscr_get_bgb_remaining_hook;
|
utils::hook::detour gscr_get_bgb_remaining_hook;
|
||||||
|
|
||||||
|
utils::memory::allocator allocator;
|
||||||
std::unordered_map<std::string, game::RawFile*> loaded_scripts;
|
std::unordered_map<std::string, game::RawFile*> loaded_scripts;
|
||||||
|
|
||||||
game::RawFile* get_loaded_script(const std::string& name)
|
game::RawFile* get_loaded_script(const std::string& name)
|
||||||
@ -30,10 +31,8 @@ namespace script
|
|||||||
printf("Loading GSC script '%s'\n", name.data());
|
printf("Loading GSC script '%s'\n", name.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_script(std::string& name, const std::string& data)
|
void load_script(std::string& name, const std::string& data, const bool is_custom)
|
||||||
{
|
{
|
||||||
auto& allocator = *utils::memory::get_allocator();
|
|
||||||
|
|
||||||
const auto appdata_path = (game::get_appdata_path() / "data/").generic_string();
|
const auto appdata_path = (game::get_appdata_path() / "data/").generic_string();
|
||||||
const auto host_path = (utils::nt::library{}.get_folder() / "boiii/").generic_string();
|
const auto host_path = (utils::nt::library{}.get_folder() / "boiii/").generic_string();
|
||||||
|
|
||||||
@ -49,15 +48,39 @@ namespace script
|
|||||||
name.erase(i, host_path.length());
|
name.erase(i, host_path.length());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto base_name = name;
|
||||||
|
if (!utils::string::ends_with(name, ".gsc"))
|
||||||
|
{
|
||||||
|
printf("GSC script '%s' failed to load due to invalid suffix.\n", name.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_custom)
|
||||||
|
{
|
||||||
|
// .gsc suffix will be added back by Scr_LoadScript
|
||||||
|
base_name = name.substr(0, name.size() - 4);
|
||||||
|
if (base_name.empty())
|
||||||
|
{
|
||||||
|
printf("GSC script '%s' failed to load due to invalid name.\n", name.data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto* raw_file = allocator.allocate<game::RawFile>();
|
auto* raw_file = allocator.allocate<game::RawFile>();
|
||||||
|
// use script name with .gsc suffix for DB_FindXAssetHeader hook
|
||||||
raw_file->name = allocator.duplicate_string(name);
|
raw_file->name = allocator.duplicate_string(name);
|
||||||
raw_file->buffer = allocator.duplicate_string(data);
|
raw_file->buffer = allocator.duplicate_string(data);
|
||||||
raw_file->len = static_cast<int>(data.length());
|
raw_file->len = static_cast<int>(data.length());
|
||||||
|
|
||||||
loaded_scripts[name] = raw_file;
|
loaded_scripts[name] = raw_file;
|
||||||
|
|
||||||
|
if (is_custom)
|
||||||
|
{
|
||||||
|
game::Scr_LoadScript(game::SCRIPTINSTANCE_SERVER, base_name.data());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_scripts_folder(const std::string& script_dir)
|
void load_scripts_folder(const std::string& script_dir, const bool is_custom)
|
||||||
{
|
{
|
||||||
if (!utils::io::directory_exists(script_dir))
|
if (!utils::io::directory_exists(script_dir))
|
||||||
{
|
{
|
||||||
@ -66,31 +89,45 @@ namespace script
|
|||||||
|
|
||||||
const auto scripts = utils::io::list_files(script_dir);
|
const auto scripts = utils::io::list_files(script_dir);
|
||||||
|
|
||||||
|
std::error_code e;
|
||||||
for (const auto& script : scripts)
|
for (const auto& script : scripts)
|
||||||
{
|
{
|
||||||
std::string data;
|
std::string data;
|
||||||
auto script_file = script.generic_string();
|
auto script_file = script.generic_string();
|
||||||
if (!std::filesystem::is_directory(script) && utils::io::read_file(script_file, &data))
|
if (!std::filesystem::is_directory(script, e) && utils::io::read_file(script_file, &data))
|
||||||
{
|
{
|
||||||
if (data.size() >= sizeof(GSC_MAGIC) && !std::memcmp(data.data(), &GSC_MAGIC, sizeof(GSC_MAGIC)))
|
if (data.size() >= sizeof(GSC_MAGIC) && !std::memcmp(data.data(), &GSC_MAGIC, sizeof(GSC_MAGIC)))
|
||||||
{
|
{
|
||||||
print_loading_script(script_file);
|
print_loading_script(script_file);
|
||||||
load_script(script_file, data);
|
load_script(script_file, data, is_custom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (std::filesystem::is_directory(script))
|
|
||||||
|
// Do not traverse directories for custom scripts.
|
||||||
|
// TODO: Add game type specific scripts. custom-scripts/cp, custom-scripts/mp and /custom-scripts/zm
|
||||||
|
if (std::filesystem::is_directory(script, e) && !is_custom)
|
||||||
{
|
{
|
||||||
load_scripts_folder(script_file);
|
load_scripts_folder(script_file, is_custom);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_scripts()
|
void load_scripts()
|
||||||
{
|
{
|
||||||
loaded_scripts = {};
|
|
||||||
const utils::nt::library host{};
|
const utils::nt::library host{};
|
||||||
load_scripts_folder((game::get_appdata_path() / "data/scripts").string());
|
|
||||||
load_scripts_folder((host.get_folder() / "boiii/scripts").string());
|
const auto data_folder = game::get_appdata_path() / "data";
|
||||||
|
const auto boiii_folder = host.get_folder() / "boiii";
|
||||||
|
|
||||||
|
// scripts folder is for overriding stock scripts the game uses
|
||||||
|
load_scripts_folder((data_folder / "scripts").string(), false);
|
||||||
|
load_scripts_folder((boiii_folder / "scripts").string(), false);
|
||||||
|
|
||||||
|
// custom_scripts is for loading completely custom scripts the game doesn't use
|
||||||
|
load_scripts_folder((data_folder / "custom_scripts").string(), true);
|
||||||
|
load_scripts_folder((boiii_folder / "custom_scripts").string(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
game::RawFile* db_find_x_asset_header_stub(const game::XAssetType type, const char* name,
|
game::RawFile* db_find_x_asset_header_stub(const game::XAssetType type, const char* name,
|
||||||
@ -108,16 +145,37 @@ namespace script
|
|||||||
auto* script = get_loaded_script(name);
|
auto* script = get_loaded_script(name);
|
||||||
if (script)
|
if (script)
|
||||||
{
|
{
|
||||||
// Copy over the checksum of the original script
|
|
||||||
utils::hook::copy(const_cast<char*>(script->buffer + 0x8), asset_header->buffer + 0x8, 4);
|
|
||||||
|
|
||||||
return script;
|
return script;
|
||||||
}
|
}
|
||||||
|
|
||||||
return asset_header;
|
return asset_header;
|
||||||
}
|
}
|
||||||
|
|
||||||
void gscr_get_bgb_remaining_stub(game::scriptInstance_t inst, void* entref)
|
void clear_script_memory()
|
||||||
|
{
|
||||||
|
loaded_scripts.clear();
|
||||||
|
allocator.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_gametype_script_stub()
|
||||||
|
{
|
||||||
|
if (!game::Com_IsInGame() || game::Com_IsRunningUILevel())
|
||||||
|
{
|
||||||
|
game::GScr_LoadGametypeScript();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
game::GScr_LoadGametypeScript();
|
||||||
|
load_scripts();
|
||||||
|
}
|
||||||
|
|
||||||
|
int server_script_checksum_stub()
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scr_loot_get_item_quantity_stub([[maybe_unused]] game::scriptInstance_t inst,
|
||||||
|
[[maybe_unused]] game::scr_entref_t entref)
|
||||||
{
|
{
|
||||||
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 255);
|
game::Scr_AddInt(game::SCRIPTINSTANCE_SERVER, 255);
|
||||||
}
|
}
|
||||||
@ -127,17 +185,21 @@ namespace script
|
|||||||
{
|
{
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
if (game::is_server())
|
// Return custom or overrided scripts if found
|
||||||
{
|
|
||||||
load_scripts();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
scheduler::once(load_scripts, scheduler::pipeline::main);
|
|
||||||
}
|
|
||||||
|
|
||||||
db_find_x_asset_header_hook.create(game::select(0x141420ED0, 0x1401D5FB0), db_find_x_asset_header_stub);
|
db_find_x_asset_header_hook.create(game::select(0x141420ED0, 0x1401D5FB0), db_find_x_asset_header_stub);
|
||||||
gscr_get_bgb_remaining_hook.create(game::select(0x141A8CAB0, 0x1402D2310), gscr_get_bgb_remaining_stub);
|
|
||||||
|
// Free our scripts when the game ends
|
||||||
|
game_event::on_g_shutdown_game(clear_script_memory);
|
||||||
|
|
||||||
|
// Load our scripts when the gametype script is loaded
|
||||||
|
utils::hook::call(game::select(0x141AAF37C, 0x1402D8C7F), load_gametype_script_stub);
|
||||||
|
|
||||||
|
// Force GSC checksums to be valid
|
||||||
|
utils::hook::call(game::select(0x1408F2E5D, 0x1400E2D22), server_script_checksum_stub);
|
||||||
|
|
||||||
|
// Workaround for "Out of X" gobblegum
|
||||||
|
gscr_get_bgb_remaining_hook.create(game::select(0x141A8CAB0, 0x1402D2310),
|
||||||
|
scr_loot_get_item_quantity_stub);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -10,7 +10,7 @@ namespace game
|
|||||||
|
|
||||||
enum ControllerIndex_t
|
enum ControllerIndex_t
|
||||||
{
|
{
|
||||||
INVALID_CONTROLLER_PORT = 0xFFFFFFFF,
|
INVALID_CONTROLLER_PORT = -1,
|
||||||
CONTROLLER_INDEX_FIRST = 0x0,
|
CONTROLLER_INDEX_FIRST = 0x0,
|
||||||
CONTROLLER_INDEX_0 = 0x0,
|
CONTROLLER_INDEX_0 = 0x0,
|
||||||
CONTROLLER_INDEX_1 = 0x1,
|
CONTROLLER_INDEX_1 = 0x1,
|
||||||
@ -19,6 +19,18 @@ namespace game
|
|||||||
CONTROLLER_INDEX_COUNT = 0x4,
|
CONTROLLER_INDEX_COUNT = 0x4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum LocalClientNum_t
|
||||||
|
{
|
||||||
|
INVALID_LOCAL_CLIENT = -1,
|
||||||
|
LOCAL_CLIENT_0 = 0x0,
|
||||||
|
LOCAL_CLIENT_FIRST = 0x0,
|
||||||
|
LOCAL_CLIENT_KEYBOARD_AND_MOUSE = 0x0,
|
||||||
|
LOCAL_CLIENT_1 = 0x1,
|
||||||
|
LOCAL_CLIENT_2 = 0x2,
|
||||||
|
LOCAL_CLIENT_3 = 0x3,
|
||||||
|
LOCAL_CLIENT_COUNT = 0x4,
|
||||||
|
};
|
||||||
|
|
||||||
enum eGameModes
|
enum eGameModes
|
||||||
{
|
{
|
||||||
MODE_GAME_MATCHMAKING_PLAYLIST = 0x0,
|
MODE_GAME_MATCHMAKING_PLAYLIST = 0x0,
|
||||||
@ -910,7 +922,7 @@ namespace game
|
|||||||
|
|
||||||
enum LobbyNetworkMode
|
enum LobbyNetworkMode
|
||||||
{
|
{
|
||||||
LOBBY_NETWORKMODE_INVALID = 0xFFFFFFFF,
|
LOBBY_NETWORKMODE_INVALID = -1,
|
||||||
LOBBY_NETWORKMODE_LOCAL = 0x0,
|
LOBBY_NETWORKMODE_LOCAL = 0x0,
|
||||||
LOBBY_NETWORKMODE_LAN = 0x1,
|
LOBBY_NETWORKMODE_LAN = 0x1,
|
||||||
LOBBY_NETWORKMODE_LIVE = 0x2,
|
LOBBY_NETWORKMODE_LIVE = 0x2,
|
||||||
@ -919,7 +931,7 @@ namespace game
|
|||||||
|
|
||||||
enum LobbyMainMode
|
enum LobbyMainMode
|
||||||
{
|
{
|
||||||
LOBBY_MAINMODE_INVALID = 0xFFFFFFFF,
|
LOBBY_MAINMODE_INVALID = -1,
|
||||||
LOBBY_MAINMODE_CP = 0x0,
|
LOBBY_MAINMODE_CP = 0x0,
|
||||||
LOBBY_MAINMODE_MP = 0x1,
|
LOBBY_MAINMODE_MP = 0x1,
|
||||||
LOBBY_MAINMODE_ZM = 0x2,
|
LOBBY_MAINMODE_ZM = 0x2,
|
||||||
@ -1141,8 +1153,8 @@ namespace game
|
|||||||
|
|
||||||
enum HksObjectType
|
enum HksObjectType
|
||||||
{
|
{
|
||||||
TANY = 0xFFFFFFFE,
|
TANY = -1,
|
||||||
TNONE = 0xFFFFFFFF,
|
TNONE = -1,
|
||||||
TNIL = 0x0,
|
TNIL = 0x0,
|
||||||
TBOOLEAN = 0x1,
|
TBOOLEAN = 0x1,
|
||||||
TLIGHTUSERDATA = 0x2,
|
TLIGHTUSERDATA = 0x2,
|
||||||
@ -1636,6 +1648,44 @@ namespace game
|
|||||||
static_assert(sizeof(client_s_cl) == 0xE5170);
|
static_assert(sizeof(client_s_cl) == 0xE5170);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
union Weapon
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
uint64_t weaponIdx : 9;
|
||||||
|
uint64_t attachment1 : 6;
|
||||||
|
uint64_t attachment2 : 6;
|
||||||
|
uint64_t attachment3 : 6;
|
||||||
|
uint64_t attachment4 : 6;
|
||||||
|
uint64_t attachment5 : 6;
|
||||||
|
uint64_t attachment6 : 6;
|
||||||
|
uint64_t attachment7 : 6;
|
||||||
|
uint64_t attachment8 : 6;
|
||||||
|
uint64_t padding : 7;
|
||||||
|
} _anon_0;
|
||||||
|
uint64_t weaponData;
|
||||||
|
};
|
||||||
|
|
||||||
|
union EntRefUnion
|
||||||
|
{
|
||||||
|
int32_t entnum;
|
||||||
|
uint32_t hudElemIndex;
|
||||||
|
uint32_t pathNodeIndex;
|
||||||
|
short vehicleNodeIndex;
|
||||||
|
unsigned short absDynEntIndex;
|
||||||
|
Weapon weapon;
|
||||||
|
uint64_t val;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct scr_entref_t
|
||||||
|
{
|
||||||
|
EntRefUnion u;
|
||||||
|
unsigned short classnum;
|
||||||
|
LocalClientNum_t client;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(scr_entref_t) == 0x10);
|
||||||
|
|
||||||
enum scriptInstance_t
|
enum scriptInstance_t
|
||||||
{
|
{
|
||||||
SCRIPTINSTANCE_SERVER = 0x0,
|
SCRIPTINSTANCE_SERVER = 0x0,
|
||||||
|
@ -40,7 +40,8 @@ namespace game
|
|||||||
WEAK symbol<unsigned int(const char* settingName, bool getDefault)> Com_GametypeSettings_GetUInt{
|
WEAK symbol<unsigned int(const char* settingName, bool getDefault)> Com_GametypeSettings_GetUInt{
|
||||||
0x1420F4E00, 0x1404FE5C0
|
0x1420F4E00, 0x1404FE5C0
|
||||||
};
|
};
|
||||||
WEAK symbol<bool()> Com_IsRunningUILevel{0x142148350};
|
WEAK symbol<bool()> Com_IsRunningUILevel{0x142148350, 0x140504BD0};
|
||||||
|
WEAK symbol<bool()> Com_IsInGame{0x1421482C0, 0x140504B90};
|
||||||
WEAK symbol<void(int localClientNum, eModes fromMode, eModes toMode, uint32_t flags)> Com_SwitchMode{
|
WEAK symbol<void(int localClientNum, eModes fromMode, eModes toMode, uint32_t flags)> Com_SwitchMode{
|
||||||
0x14214A4D0
|
0x14214A4D0
|
||||||
};
|
};
|
||||||
@ -112,6 +113,15 @@ namespace game
|
|||||||
WEAK symbol<TLSData*()> Sys_GetTLS{0x1421837B0, 0x140525EB0};
|
WEAK symbol<TLSData*()> Sys_GetTLS{0x1421837B0, 0x140525EB0};
|
||||||
WEAK symbol<TLSData*()> Sys_IsDatabaseReady{0x142183A60};
|
WEAK symbol<TLSData*()> Sys_IsDatabaseReady{0x142183A60};
|
||||||
|
|
||||||
|
// Rope
|
||||||
|
WEAK symbol<void()> Rope_InitRopes{0x1420D8D00, 0x1404E4300};
|
||||||
|
|
||||||
|
// Glass
|
||||||
|
WEAK symbol<void()> GlassSv_Shutdown{0x1425AA7A0, 0x14065BCC0};
|
||||||
|
|
||||||
|
// Mantle
|
||||||
|
WEAK symbol<void()> Mantle_ShutdownAnims{0x142678C80, 0x1406A1B50};
|
||||||
|
|
||||||
// Unnamed
|
// Unnamed
|
||||||
WEAK symbol<const char*(const char* name)> CopyString{0x1422AC220, 0x14056BD70};
|
WEAK symbol<const char*(const char* name)> CopyString{0x1422AC220, 0x14056BD70};
|
||||||
|
|
||||||
@ -184,6 +194,7 @@ namespace game
|
|||||||
0x0, 0x1402F5FF0
|
0x0, 0x1402F5FF0
|
||||||
};
|
};
|
||||||
WEAK symbol<unsigned int(scriptInstance_t inst)> Scr_GetNumParam{0x0, 0x140171320};
|
WEAK symbol<unsigned int(scriptInstance_t inst)> Scr_GetNumParam{0x0, 0x140171320};
|
||||||
|
WEAK symbol<unsigned int(scriptInstance_t inst, const char* filename)> Scr_LoadScript{0x1412C83F0, 0x140156610};
|
||||||
|
|
||||||
WEAK symbol<void(const char* name, const char* key, unsigned int playbackFlags, float volume, void* callbackInfo,
|
WEAK symbol<void(const char* name, const char* key, unsigned int playbackFlags, float volume, void* callbackInfo,
|
||||||
int id)> Cinematic_StartPlayback{0x1412BE3A0};
|
int id)> Cinematic_StartPlayback{0x1412BE3A0};
|
||||||
@ -232,6 +243,8 @@ namespace game
|
|||||||
0x1422E9410, 0x1405811E0
|
0x1422E9410, 0x1405811E0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WEAK symbol<void()> GScr_LoadGametypeScript{0x141AAD850, 0x1402D7140};
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
WEAK symbol<cmd_function_s> cmd_functions{0x15689DF58, 0x14946F860};
|
WEAK symbol<cmd_function_s> cmd_functions{0x15689DF58, 0x14946F860};
|
||||||
WEAK symbol<CmdArgs> sv_cmd_args{0x15689AE30, 0x14944C740};
|
WEAK symbol<CmdArgs> sv_cmd_args{0x15689AE30, 0x14944C740};
|
||||||
|
Loading…
Reference in New Issue
Block a user