forked from alterware/iw6-mod
Compare commits
1 Commits
demo_syste
...
v0.0.1
Author | SHA1 | Date | |
---|---|---|---|
45907301ee
|
2
deps/GSL
vendored
2
deps/GSL
vendored
Submodule deps/GSL updated: f1a494cfd2...272463043e
2
deps/WinToast
vendored
2
deps/WinToast
vendored
Submodule deps/WinToast updated: 821c4818ad...a78ce469b4
2
deps/asmjit
vendored
2
deps/asmjit
vendored
Submodule deps/asmjit updated: bfa0bf690c...cfc9f813cc
2
deps/gsc-tool
vendored
2
deps/gsc-tool
vendored
Submodule deps/gsc-tool updated: b8e30e6334...9ff3b871ef
2
deps/libtomcrypt
vendored
2
deps/libtomcrypt
vendored
Submodule deps/libtomcrypt updated: 7e863d2142...c900951dab
2
deps/libtommath
vendored
2
deps/libtommath
vendored
Submodule deps/libtommath updated: 8314bde5e5...5809141a3a
2
deps/minhook
vendored
2
deps/minhook
vendored
Submodule deps/minhook updated: f5485b8454...c1a7c3843b
2
deps/rapidjson
vendored
2
deps/rapidjson
vendored
Submodule deps/rapidjson updated: 6089180ecb...d621dc9e9c
2
deps/zlib
vendored
2
deps/zlib
vendored
Submodule deps/zlib updated: 9f0f2d4f9f...ef24c4c750
35
src/client/component/asset_restrict.cpp
Normal file
35
src/client/component/asset_restrict.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include <loader/component_loader.hpp>
|
||||||
|
#include "game/game.hpp"
|
||||||
|
|
||||||
|
#include <utils/memory.hpp>
|
||||||
|
|
||||||
|
namespace asset_restrict
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void reallocate_asset_pool(game::XAssetType type, const int size)
|
||||||
|
{
|
||||||
|
const auto asset_size = game::DB_GetXAssetTypeSize(type);
|
||||||
|
const auto new_size = size * game::g_poolSize[type];
|
||||||
|
|
||||||
|
const auto new_allocation_size = static_cast<std::size_t>(new_size * asset_size);
|
||||||
|
|
||||||
|
const game::XAssetHeader pool_entry = { .data = utils::memory::allocate(new_allocation_size) };
|
||||||
|
game::DB_XAssetPool[type] = pool_entry.data;
|
||||||
|
game::g_poolSize[type] = new_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
reallocate_asset_pool(game::ASSET_TYPE_XANIMPARTS, 2);
|
||||||
|
reallocate_asset_pool(game::ASSET_TYPE_ATTACHMENT, 2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(asset_restrict::component)
|
@ -8,11 +8,13 @@
|
|||||||
#include "console.hpp"
|
#include "console.hpp"
|
||||||
#include "network.hpp"
|
#include "network.hpp"
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
|
||||||
#include <utils/string.hpp>
|
|
||||||
#include <utils/smbios.hpp>
|
|
||||||
#include <utils/info_string.hpp>
|
|
||||||
#include <utils/cryptography.hpp>
|
#include <utils/cryptography.hpp>
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/info_string.hpp>
|
||||||
|
#include <utils/io.hpp>
|
||||||
|
#include <utils/properties.hpp>
|
||||||
|
#include <utils/smbios.hpp>
|
||||||
|
#include <utils/string.hpp>
|
||||||
|
|
||||||
namespace auth
|
namespace auth
|
||||||
{
|
{
|
||||||
@ -31,13 +33,26 @@ namespace auth
|
|||||||
|
|
||||||
std::string get_hw_profile_guid()
|
std::string get_hw_profile_guid()
|
||||||
{
|
{
|
||||||
|
auto hw_profile_path = (utils::properties::get_appdata_path() / "iw6-guid.dat").generic_string();
|
||||||
|
if (utils::io::file_exists(hw_profile_path))
|
||||||
|
{
|
||||||
|
std::string hw_profile_info;
|
||||||
|
if (utils::io::read_file(hw_profile_path, &hw_profile_info) && !hw_profile_info.empty())
|
||||||
|
{
|
||||||
|
return hw_profile_info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HW_PROFILE_INFO info;
|
HW_PROFILE_INFO info;
|
||||||
if (!GetCurrentHwProfileA(&info))
|
if (!GetCurrentHwProfileA(&info))
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::string{ info.szHwProfileGuid, sizeof(info.szHwProfileGuid) };
|
auto hw_profile_info = std::string{ info.szHwProfileGuid, sizeof(info.szHwProfileGuid) };
|
||||||
|
utils::io::write_file(hw_profile_path, hw_profile_info);
|
||||||
|
|
||||||
|
return hw_profile_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string get_protected_data()
|
std::string get_protected_data()
|
||||||
@ -82,7 +97,7 @@ namespace auth
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
int send_connect_data_stub(game::netsrc_t sock, game::netadr_t* adr, const char* format, const int len)
|
int send_connect_data_stub(game::netsrc_t sock, game::netadr_s* adr, const char* format, const int len)
|
||||||
{
|
{
|
||||||
std::string connect_string(format, len);
|
std::string connect_string(format, len);
|
||||||
game::SV_Cmd_TokenizeString(connect_string.data());
|
game::SV_Cmd_TokenizeString(connect_string.data());
|
||||||
@ -116,7 +131,7 @@ namespace auth
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void direct_connect(game::netadr_t* from, game::msg_t* msg)
|
void direct_connect(game::netadr_s* from, game::msg_t* msg)
|
||||||
{
|
{
|
||||||
const auto offset = sizeof("connect") + 4;
|
const auto offset = sizeof("connect") + 4;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ namespace bots
|
|||||||
|
|
||||||
bool can_add()
|
bool can_add()
|
||||||
{
|
{
|
||||||
return party::get_client_count() < game::mp::svs->clientCount;
|
return party::get_client_count() < *game::mp::svs_clientCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
void bot_team_join(const int entity_num)
|
void bot_team_join(const int entity_num)
|
||||||
@ -29,14 +29,14 @@ namespace bots
|
|||||||
// schedule the select team call
|
// schedule the select team call
|
||||||
scheduler::once([entity_num]()
|
scheduler::once([entity_num]()
|
||||||
{
|
{
|
||||||
game::SV_ExecuteClientCommand(&game::mp::svs->clients[entity_num],
|
game::SV_ExecuteClientCommand(&game::mp::svs_clients[entity_num],
|
||||||
utils::string::va("lui 68 2 %i", *game::mp::sv_serverId_value),
|
utils::string::va("lui 68 2 %i", *game::mp::sv_serverId_value),
|
||||||
false);
|
false);
|
||||||
|
|
||||||
// scheduler the select class call
|
// scheduler the select class call
|
||||||
scheduler::once([entity_num]()
|
scheduler::once([entity_num]()
|
||||||
{
|
{
|
||||||
game::SV_ExecuteClientCommand(&game::mp::svs->clients[entity_num],
|
game::SV_ExecuteClientCommand(&game::mp::svs_clients[entity_num],
|
||||||
utils::string::va("lui 5 %i %i", (rand() % 5) + 10,
|
utils::string::va("lui 5 %i %i", (rand() % 5) + 10,
|
||||||
*game::mp::sv_serverId_value), false);
|
*game::mp::sv_serverId_value), false);
|
||||||
}, scheduler::pipeline::server, 1s);
|
}, scheduler::pipeline::server, 1s);
|
||||||
@ -144,7 +144,7 @@ namespace bots
|
|||||||
{
|
{
|
||||||
bot_names_received = false;
|
bot_names_received = false;
|
||||||
|
|
||||||
game::netadr_t master{};
|
game::netadr_s master{};
|
||||||
if (server_list::get_master_server(master))
|
if (server_list::get_master_server(master))
|
||||||
{
|
{
|
||||||
console::info("Getting bots...\n");
|
console::info("Getting bots...\n");
|
||||||
@ -175,7 +175,7 @@ namespace bots
|
|||||||
num_bots = std::strtoul(params.get(1), nullptr, 10);
|
num_bots = std::strtoul(params.get(1), nullptr, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
num_bots = std::min(num_bots, static_cast<std::size_t>(game::mp::svs->clientCount));
|
num_bots = std::min(num_bots, static_cast<std::size_t>(*game::mp::svs_clientCount));
|
||||||
|
|
||||||
console::info("Spawning %zu %s\n", num_bots, (num_bots == 1 ? "bot" : "bots"));
|
console::info("Spawning %zu %s\n", num_bots, (num_bots == 1 ? "bot" : "bots"));
|
||||||
|
|
||||||
@ -198,9 +198,9 @@ namespace bots
|
|||||||
parse_bot_names_from_file();
|
parse_bot_names_from_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
network::on("getbotsResponse", [](const game::netadr_t& target, const std::string& data)
|
network::on("getbotsResponse", [](const game::netadr_s& target, const std::string& data)
|
||||||
{
|
{
|
||||||
game::netadr_t master{};
|
game::netadr_s master{};
|
||||||
if (server_list::get_master_server(master) && !bot_names_received && target == master)
|
if (server_list::get_master_server(master) && !bot_names_received && target == master)
|
||||||
{
|
{
|
||||||
bot_names = utils::string::split(data, '\n');
|
bot_names = utils::string::split(data, '\n');
|
||||||
|
@ -41,7 +41,8 @@ namespace branding
|
|||||||
localized_strings::override("LUA_MENU_LEGAL_COPYRIGHT", "iw6-mod: " VERSION " by AlterWare.\n");
|
localized_strings::override("LUA_MENU_LEGAL_COPYRIGHT", "iw6-mod: " VERSION " by AlterWare.\n");
|
||||||
|
|
||||||
utils::hook::call(SELECT_VALUE(0x1403BDABA, 0x140414424), dvar_set_string_stub);
|
utils::hook::call(SELECT_VALUE(0x1403BDABA, 0x140414424), dvar_set_string_stub);
|
||||||
ui_get_formatted_build_number_hook.create(game::LiveStorage_FetchFFotD, ui_get_formatted_build_number_stub);
|
ui_get_formatted_build_number_hook.create(
|
||||||
|
SELECT_VALUE(0x140415FD0, 0x1404D7C00), ui_get_formatted_build_number_stub);
|
||||||
|
|
||||||
scheduler::loop([]()
|
scheduler::loop([]()
|
||||||
{
|
{
|
||||||
|
@ -86,28 +86,18 @@ namespace colors
|
|||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t get_client_name_stub(const int local_client_num, const int index, char* name_buf,
|
size_t get_client_name_stub(const int local_client_num, const int index, char* buf, const int size,
|
||||||
const int max_name_size, char* clan_buf, const int max_clan_size)
|
const size_t unk, const size_t unk2)
|
||||||
{
|
{
|
||||||
// CL_GetClientNameAndClanTag (wrapper for CL_GetClientNameAndClanTagColorize)
|
// CL_GetClientName (CL_GetClientNameAndClantag?)
|
||||||
const auto result = reinterpret_cast<size_t(*)(int, int, char*, int, char*, int)>(0x1402CF790)(
|
const auto result = reinterpret_cast<size_t(*)(int, int, char*, int, size_t, size_t)>(0x1402CF790)(
|
||||||
local_client_num, index, name_buf, max_name_size, clan_buf, max_clan_size);
|
local_client_num, index, buf, size, unk, unk2);
|
||||||
|
|
||||||
utils::string::strip(name_buf, name_buf, static_cast<size_t>(max_name_size));
|
utils::string::strip(buf, buf, size);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int clean_agent_name_stub(char* out, int max_size, const char* fmt, const char* in)
|
|
||||||
{
|
|
||||||
// format agent overhead name like [%s]
|
|
||||||
const auto length = sprintf_s(out, max_size, fmt, in);
|
|
||||||
|
|
||||||
utils::string::strip(in, out, std::min(static_cast<size_t>(length), static_cast<size_t>(max_size)));
|
|
||||||
|
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rb_lookup_color_stub(const char index, DWORD* color)
|
void rb_lookup_color_stub(const char index, DWORD* color)
|
||||||
{
|
{
|
||||||
*color = RGB(255, 255, 255);
|
*color = RGB(255, 255, 255);
|
||||||
@ -153,9 +143,6 @@ namespace colors
|
|||||||
// don't apply colors to overhead names
|
// don't apply colors to overhead names
|
||||||
utils::hook::call(0x14025CE79, get_client_name_stub);
|
utils::hook::call(0x14025CE79, get_client_name_stub);
|
||||||
|
|
||||||
// don't apply colors to overhead names of agents (like dogs or juggernauts)
|
|
||||||
utils::hook::call(0x1402CF760, clean_agent_name_stub);
|
|
||||||
|
|
||||||
// patch I_CleanStr
|
// patch I_CleanStr
|
||||||
utils::hook::jump(0x1404F63C0, i_clean_str_stub);
|
utils::hook::jump(0x1404F63C0, i_clean_str_stub);
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ namespace command
|
|||||||
|
|
||||||
void add_raw(const char* name, void (*callback)())
|
void add_raw(const char* name, void (*callback)())
|
||||||
{
|
{
|
||||||
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_t>());
|
game::Cmd_AddCommandInternal(name, callback, utils::memory::get_allocator()->allocate<game::cmd_function_s>());
|
||||||
}
|
}
|
||||||
|
|
||||||
void add(const char* name, const std::function<void(const params&)>& callback)
|
void add(const char* name, const std::function<void(const params&)>& callback)
|
||||||
@ -232,7 +232,10 @@ namespace command
|
|||||||
|
|
||||||
void execute(std::string command, const bool sync)
|
void execute(std::string command, const bool sync)
|
||||||
{
|
{
|
||||||
command += "\n";
|
if (!command.ends_with('\n'))
|
||||||
|
{
|
||||||
|
command += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
if (sync)
|
if (sync)
|
||||||
{
|
{
|
||||||
@ -318,7 +321,7 @@ namespace command
|
|||||||
}
|
}
|
||||||
|
|
||||||
console::info("================================ COMMAND DUMP =====================================\n");
|
console::info("================================ COMMAND DUMP =====================================\n");
|
||||||
game::cmd_function_t* cmd = *game::cmd_functions;
|
game::cmd_function_s* cmd = *game::cmd_functions;
|
||||||
auto i = 0;
|
auto i = 0;
|
||||||
while (cmd)
|
while (cmd)
|
||||||
{
|
{
|
||||||
@ -553,12 +556,12 @@ namespace command
|
|||||||
{
|
{
|
||||||
if (!dvars::sv_cheats->current.enabled)
|
if (!dvars::sv_cheats->current.enabled)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::mp::g_entities[client_num].flags ^= game::FL_GODMODE;
|
game::mp::g_entities[client_num].flags ^= game::FL_GODMODE;
|
||||||
game::SV_GameSendServerCommand(client_num, 1,
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||||
utils::string::va("f \"godmode %s\"",
|
utils::string::va("f \"godmode %s\"",
|
||||||
game::mp::g_entities[client_num].flags & game::FL_GODMODE
|
game::mp::g_entities[client_num].flags & game::FL_GODMODE
|
||||||
? "^2on"
|
? "^2on"
|
||||||
@ -569,12 +572,12 @@ namespace command
|
|||||||
{
|
{
|
||||||
if (!dvars::sv_cheats->current.enabled)
|
if (!dvars::sv_cheats->current.enabled)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::mp::g_entities[client_num].flags ^= game::FL_NOTARGET;
|
game::mp::g_entities[client_num].flags ^= game::FL_NOTARGET;
|
||||||
game::SV_GameSendServerCommand(client_num, 1,
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||||
utils::string::va("f \"notarget %s\"",
|
utils::string::va("f \"notarget %s\"",
|
||||||
game::mp::g_entities[client_num].flags & game::FL_NOTARGET
|
game::mp::g_entities[client_num].flags & game::FL_NOTARGET
|
||||||
? "^2on"
|
? "^2on"
|
||||||
@ -585,12 +588,12 @@ namespace command
|
|||||||
{
|
{
|
||||||
if (!dvars::sv_cheats->current.enabled)
|
if (!dvars::sv_cheats->current.enabled)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::mp::g_entities[client_num].client->flags ^= 1;
|
game::mp::g_entities[client_num].client->flags ^= 1;
|
||||||
game::SV_GameSendServerCommand(client_num, 1,
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||||
utils::string::va("f \"noclip %s\"",
|
utils::string::va("f \"noclip %s\"",
|
||||||
game::mp::g_entities[client_num].client->flags & 1
|
game::mp::g_entities[client_num].client->flags & 1
|
||||||
? "^2on"
|
? "^2on"
|
||||||
@ -601,12 +604,12 @@ namespace command
|
|||||||
{
|
{
|
||||||
if (!dvars::sv_cheats->current.enabled)
|
if (!dvars::sv_cheats->current.enabled)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::mp::g_entities[client_num].client->flags ^= 2;
|
game::mp::g_entities[client_num].client->flags ^= 2;
|
||||||
game::SV_GameSendServerCommand(client_num, 1,
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||||
utils::string::va("f \"ufo %s\"",
|
utils::string::va("f \"ufo %s\"",
|
||||||
game::mp::g_entities[client_num].client->flags & 2
|
game::mp::g_entities[client_num].client->flags & 2
|
||||||
? "^2on"
|
? "^2on"
|
||||||
@ -617,13 +620,13 @@ namespace command
|
|||||||
{
|
{
|
||||||
if (!dvars::sv_cheats->current.enabled)
|
if (!dvars::sv_cheats->current.enabled)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.size() < 4)
|
if (params.size() < 4)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1,
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||||
"f \"You did not specify the correct number of coordinates\"");
|
"f \"You did not specify the correct number of coordinates\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -637,13 +640,13 @@ namespace command
|
|||||||
{
|
{
|
||||||
if (!dvars::sv_cheats->current.enabled)
|
if (!dvars::sv_cheats->current.enabled)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.size() < 4)
|
if (params.size() < 4)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1,
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE,
|
||||||
"f \"You did not specify the correct number of coordinates\"");
|
"f \"You did not specify the correct number of coordinates\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -657,13 +660,13 @@ namespace command
|
|||||||
{
|
{
|
||||||
if (!dvars::sv_cheats->current.enabled)
|
if (!dvars::sv_cheats->current.enabled)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.size() < 2)
|
if (params.size() < 2)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"You did not specify a weapon name\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -680,13 +683,13 @@ namespace command
|
|||||||
{
|
{
|
||||||
if (!dvars::sv_cheats->current.enabled)
|
if (!dvars::sv_cheats->current.enabled)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"Cheats are not enabled on this server\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"Cheats are not enabled on this server\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.size() < 2)
|
if (params.size() < 2)
|
||||||
{
|
{
|
||||||
game::SV_GameSendServerCommand(client_num, 1, "f \"You did not specify a weapon name\"");
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_RELIABLE, "f \"You did not specify a weapon name\"");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ namespace dedicated
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::netadr_t target{};
|
game::netadr_s target{};
|
||||||
if (server_list::get_master_server(target))
|
if (server_list::get_master_server(target))
|
||||||
{
|
{
|
||||||
network::send(target, "heartbeat", "IW6");
|
network::send(target, "heartbeat", "IW6");
|
||||||
|
@ -25,28 +25,28 @@ namespace dedicated_info
|
|||||||
|
|
||||||
scheduler::loop([]()
|
scheduler::loop([]()
|
||||||
{
|
{
|
||||||
auto* sv_running = game::Dvar_FindVar("sv_running");
|
const auto* sv_running = game::Dvar_FindVar("sv_running");
|
||||||
if (!sv_running || !sv_running->current.enabled)
|
if (!sv_running || !sv_running->current.enabled)
|
||||||
{
|
{
|
||||||
console::set_title("iw6-mod Dedicated Server");
|
console::set_title("iw6-mod Dedicated Server");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* const sv_hostname = game::Dvar_FindVar("sv_hostname");
|
const auto* sv_hostname = game::Dvar_FindVar("sv_hostname");
|
||||||
auto* const sv_maxclients = game::Dvar_FindVar("sv_maxclients");
|
const auto* sv_maxclients = game::Dvar_FindVar("sv_maxclients");
|
||||||
auto* const mapname = game::Dvar_FindVar("mapname");
|
const auto* mapname = game::Dvar_FindVar("mapname");
|
||||||
|
|
||||||
auto client_count = 0;
|
auto client_count = 0;
|
||||||
auto bot_count = 0;
|
auto bot_count = 0;
|
||||||
|
|
||||||
for (auto i = 0; i < sv_maxclients->current.integer; i++)
|
for (auto i = 0; i < sv_maxclients->current.integer; ++i)
|
||||||
{
|
{
|
||||||
auto* client = &game::mp::svs->clients[i];
|
auto* client = &game::mp::svs_clients[i];
|
||||||
auto* self = &game::mp::g_entities[i];
|
auto* self = &game::mp::g_entities[i];
|
||||||
|
|
||||||
if (client->header.state > game::CS_FREE && self && self->client)
|
if (client->header.state > game::CS_FREE && self && self->client)
|
||||||
{
|
{
|
||||||
client_count++;
|
++client_count;
|
||||||
if (game::SV_BotIsBot(i))
|
if (game::SV_BotIsBot(i))
|
||||||
{
|
{
|
||||||
++bot_count;
|
++bot_count;
|
||||||
@ -59,7 +59,7 @@ namespace dedicated_info
|
|||||||
utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(),
|
utils::string::strip(sv_hostname->current.string, cleaned_hostname.data(),
|
||||||
cleaned_hostname.size() + 1);
|
cleaned_hostname.size() + 1);
|
||||||
|
|
||||||
console::set_title(utils::string::va("%s on %s [%d/%d] (%d)", cleaned_hostname.c_str(),
|
console::set_title(utils::string::va("%s on %s [%d/%d] (%d)", cleaned_hostname.data(),
|
||||||
mapname->current.string, client_count,
|
mapname->current.string, client_count,
|
||||||
sv_maxclients->current.integer, bot_count));
|
sv_maxclients->current.integer, bot_count));
|
||||||
}, scheduler::pipeline::main, 1s);
|
}, scheduler::pipeline::main, 1s);
|
||||||
|
@ -1,946 +0,0 @@
|
|||||||
#include "std_include.hpp"
|
|
||||||
#include "loader/component_loader.hpp"
|
|
||||||
|
|
||||||
#include "demo_playback.hpp"
|
|
||||||
#include "demo_utils.hpp"
|
|
||||||
|
|
||||||
#include "command.hpp"
|
|
||||||
#include "console.hpp"
|
|
||||||
#include "scheduler.hpp"
|
|
||||||
#include "utils/command_line.hpp"
|
|
||||||
#include "utils/hook.hpp"
|
|
||||||
|
|
||||||
using namespace demo_utils;
|
|
||||||
|
|
||||||
namespace // demo class
|
|
||||||
{
|
|
||||||
class demo_playback_t
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
demo_playback_t();
|
|
||||||
|
|
||||||
[[nodiscard]] bool is_demo_playing() const;
|
|
||||||
[[nodiscard]] bool is_demo_repeating() const;
|
|
||||||
[[nodiscard]] const std::optional<gamestate_t>& get_gamestate() const;
|
|
||||||
[[nodiscard]] bool rewind_demo(std::optional<std::uint32_t> msec);
|
|
||||||
[[nodiscard]] bool start(std::filesystem::path& path, bool repeat);
|
|
||||||
[[nodiscard]] std::optional<std::reference_wrapper<std::filesystem::path>> restart();
|
|
||||||
template <typename Func, typename... Args> auto use_predicted_data(Func func, Args&&... args);
|
|
||||||
template <typename Func, typename... Args> auto handle_fast_forward(Func func, Args&&... args);
|
|
||||||
|
|
||||||
void parse_demo_wrapper();
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
private:
|
|
||||||
void repeat();
|
|
||||||
|
|
||||||
bool repeat_{};
|
|
||||||
bool skip_timeout_{};
|
|
||||||
bool retain_timescale_{};
|
|
||||||
std::size_t post_map_header_file_offset_{};
|
|
||||||
std::size_t pred_data_index_{};
|
|
||||||
std::array<predicted_data_t, 256> pred_data_{};
|
|
||||||
std::optional<std::int32_t> rewind_snap_svr_time_;
|
|
||||||
std::optional<gamestate_t> gs_;
|
|
||||||
std::vector<std::uint8_t> buffer_;
|
|
||||||
std::filesystem::path path_;
|
|
||||||
std::ifstream file_;
|
|
||||||
} demo;
|
|
||||||
|
|
||||||
demo_playback_t::demo_playback_t()
|
|
||||||
{
|
|
||||||
buffer_.reserve(MAX_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_playback_t::is_demo_playing() const
|
|
||||||
{
|
|
||||||
return file_.good() && file_.is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_playback_t::is_demo_repeating() const
|
|
||||||
{
|
|
||||||
return repeat_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::optional<gamestate_t>& demo_playback_t::get_gamestate() const
|
|
||||||
{
|
|
||||||
return gs_;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Func, typename ...Args>
|
|
||||||
auto demo_playback_t::use_predicted_data(Func func, Args&& ...args)
|
|
||||||
{
|
|
||||||
return func(pred_data_, pred_data_index_, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Func, typename ...Args>
|
|
||||||
auto demo_playback_t::handle_fast_forward(Func func, Args&& ...args)
|
|
||||||
{
|
|
||||||
return func(rewind_snap_svr_time_, skip_timeout_, std::forward<Args>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_playback_t::rewind_demo(std::optional<std::uint32_t> msec)
|
|
||||||
{
|
|
||||||
if (!is_demo_playing())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// trigger demo end-of-file which will repeat the demo
|
|
||||||
repeat_ = true;
|
|
||||||
file_.seekg(0, std::ios::end);
|
|
||||||
|
|
||||||
if (msec)
|
|
||||||
{
|
|
||||||
retain_timescale_ = true;
|
|
||||||
|
|
||||||
const auto snap_svr_time = game::mp::cl->snap.serverTime;
|
|
||||||
if (const auto delta_time = snap_svr_time - static_cast<std::int32_t>(*msec) - 50; delta_time > 0)
|
|
||||||
{
|
|
||||||
rewind_snap_svr_time_ = delta_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// workaround to get rewinding working without delay at low timescales
|
|
||||||
fast_forward_demo(1000);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_playback_t::start(std::filesystem::path& path, bool repeat)
|
|
||||||
{
|
|
||||||
// check if the file can be opened before taking any action with the demo file stream
|
|
||||||
std::ifstream file(path, std::ios::binary);
|
|
||||||
|
|
||||||
const auto can_open = file.good() && file.is_open();
|
|
||||||
if (!can_open)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
stop();
|
|
||||||
|
|
||||||
file_ = std::move(file);
|
|
||||||
assert(file_.good() && file_.is_open());
|
|
||||||
|
|
||||||
// (re)set data
|
|
||||||
repeat_ = repeat;
|
|
||||||
gs_.reset();
|
|
||||||
path_ = std::move(path);
|
|
||||||
get_persistent_data() = {};
|
|
||||||
|
|
||||||
if (auto* demo_timescale = game::Dvar_FindVar("demotimescale"); demo_timescale)
|
|
||||||
{
|
|
||||||
demo_timescale->current.value = 1.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game::Live_SyncOnlineDataFlags(0))
|
|
||||||
{
|
|
||||||
console::info("the demo should start to load in a few seconds\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::reference_wrapper<std::filesystem::path>> demo_playback_t::restart()
|
|
||||||
{
|
|
||||||
if (!start(path_, repeat_))
|
|
||||||
{
|
|
||||||
return path_;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_playback_t::parse_demo_wrapper()
|
|
||||||
{
|
|
||||||
if (!parse_demo(file_, buffer_, post_map_header_file_offset_, pred_data_, pred_data_index_, gs_))
|
|
||||||
{
|
|
||||||
if (is_demo_repeating() && *game::mp::connstate != game::CA_DISCONNECTED)
|
|
||||||
{ // don't repeat demo if it was stopped manually
|
|
||||||
repeat();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(*game::mp::connstate != game::CA_DISCONNECTED || !is_demo_playing());
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_playback_t::stop()
|
|
||||||
{
|
|
||||||
skip_timeout_ = {};
|
|
||||||
retain_timescale_ = {};
|
|
||||||
post_map_header_file_offset_ = {};
|
|
||||||
pred_data_index_ = {};
|
|
||||||
pred_data_.fill({});
|
|
||||||
rewind_snap_svr_time_ = {};
|
|
||||||
gs_.reset();
|
|
||||||
buffer_.clear();
|
|
||||||
file_.clear();
|
|
||||||
file_.close();
|
|
||||||
|
|
||||||
// ensure that sv_cheats is always disabled after demo playback
|
|
||||||
if (auto* sv_cheats = game::Dvar_FindVar("sv_cheats"); sv_cheats)
|
|
||||||
{
|
|
||||||
sv_cheats->current.enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_playback_t::repeat()
|
|
||||||
{
|
|
||||||
assert(post_map_header_file_offset_ < 256);
|
|
||||||
|
|
||||||
if (auto& connstate = *game::mp::connstate; connstate == game::CA_ACTIVE)
|
|
||||||
{
|
|
||||||
// prevent old snapshots from being processed; would mess up the server command sequences
|
|
||||||
connstate = game::CA_PRIMED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset game data
|
|
||||||
game::mp::clc->serverCommandSequence = {};
|
|
||||||
game::mp::clc->lastExecutedServerCommand = {};
|
|
||||||
game::mp::cgs->serverCommandSequence = {};
|
|
||||||
get_persistent_data() = {};
|
|
||||||
|
|
||||||
if (auto* demo_timescale = game::Dvar_FindVar("demotimescale"); demo_timescale)
|
|
||||||
{
|
|
||||||
if (!retain_timescale_)
|
|
||||||
{
|
|
||||||
demo_timescale->current.value = 1.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset class data
|
|
||||||
skip_timeout_ = {};
|
|
||||||
retain_timescale_ = {};
|
|
||||||
pred_data_index_ = {};
|
|
||||||
|
|
||||||
file_.clear();
|
|
||||||
file_.seekg(post_map_header_file_offset_, std::ios::beg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace // hooks
|
|
||||||
{
|
|
||||||
utils::hook::detour CG_UpdateOmnvars_hook;
|
|
||||||
utils::hook::detour CG_VisionSetStartLerp_To_hook;
|
|
||||||
utils::hook::detour CL_GetPredictedPlayerInformationForServerTime_hook;
|
|
||||||
utils::hook::detour CL_GetPredictedVehicleForServerTime_hook;
|
|
||||||
utils::hook::detour CL_SetCGameTime_hook;
|
|
||||||
utils::hook::detour CL_WritePacket_hook;
|
|
||||||
utils::hook::detour GetRemoteEyeValues_hook;
|
|
||||||
utils::hook::detour LUI_IntermissionBegan_hook;
|
|
||||||
utils::hook::detour VehicleCam_UpdatePlayerControlCam_hook;
|
|
||||||
|
|
||||||
game::dvar_t* demo_vision;
|
|
||||||
|
|
||||||
static constexpr std::array VISION_SETS{
|
|
||||||
"disabled", "no vision", "default",
|
|
||||||
|
|
||||||
"ac130", "ac130_enhanced_mp", "ac130_inverted", "aftermath", "aftermath_glow", "aftermath_post", "black_bw",
|
|
||||||
"cheat_bw", "coup_sunblind", "default_night", "default_night_mp", "end_game", "missilecam", "mpintro",
|
|
||||||
"mpnuke", "mpnuke_aftermath", "mpoutro", "near_death", "near_death_mp", "nuke_global_flash", "thermal_mp", "thermal_snowlevel_mp",
|
|
||||||
|
|
||||||
"aftermath_map", static_cast<const char*>(nullptr)
|
|
||||||
};
|
|
||||||
static_assert(
|
|
||||||
std::string_view(VISION_SETS[0]) == "disabled" &&
|
|
||||||
std::string_view(VISION_SETS[1]) == "no vision" &&
|
|
||||||
std::string_view(VISION_SETS[2]) == "default" &&
|
|
||||||
std::string_view(VISION_SETS[VISION_SETS.size() - 2]) == "aftermath_map" &&
|
|
||||||
VISION_SETS.back() == nullptr // enum dvars require a nullptr as last value
|
|
||||||
);
|
|
||||||
|
|
||||||
bool handle_vision_override(game::mp::ClientVisionSetData& cvs_data, std::int32_t index, const char* vision_name, std::int32_t duration, std::int32_t time)
|
|
||||||
{
|
|
||||||
if (demo.is_demo_playing())
|
|
||||||
{
|
|
||||||
const auto vision_set = static_cast<std::size_t>((demo_vision) ? demo_vision->current.integer : 0);
|
|
||||||
|
|
||||||
if (vision_set == 1)
|
|
||||||
{
|
|
||||||
// enable 'post apply' vision
|
|
||||||
cvs_data.visionSetLerpData[index].style = 1;
|
|
||||||
cvs_data.postApplyLerpDest = 1.0f;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else if (vision_set >= 2 && vision_set + 2 <= VISION_SETS.size())
|
|
||||||
{
|
|
||||||
// enable 'post apply' vision
|
|
||||||
cvs_data.visionSetLerpData[index].style = 0;
|
|
||||||
cvs_data.postApplyLerpDest = 1.0f;
|
|
||||||
|
|
||||||
if (vision_set + 2 == VISION_SETS.size())
|
|
||||||
{
|
|
||||||
if (const auto mapname = get_mapname(false); mapname.empty())
|
|
||||||
{
|
|
||||||
return CG_VisionSetStartLerp_To_hook.invoke<bool>(&cvs_data, index, "aftermath_post", duration, time);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// a small number of maps have a vision called 'aftermath_mp_mapname'
|
|
||||||
return CG_VisionSetStartLerp_To_hook.invoke<bool>(&cvs_data, index, std::format("aftermath_{}", mapname).c_str(), duration, time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return CG_VisionSetStartLerp_To_hook.invoke<bool>(&cvs_data, index, VISION_SETS[vision_set], duration, time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return CG_VisionSetStartLerp_To_hook.invoke<bool>(&cvs_data, index, vision_name, duration, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_vision()
|
|
||||||
{
|
|
||||||
if (demo_vision && demo_vision->modified)
|
|
||||||
{
|
|
||||||
const auto vision_set = demo_vision->current.integer;
|
|
||||||
|
|
||||||
for (auto i = 0; i < game::CS_VISIONSET_COUNT; ++i)
|
|
||||||
{
|
|
||||||
const std::string_view vision_name = (!vision_set)
|
|
||||||
? game::mp::cg->cvsData.visionName[i]
|
|
||||||
: "";
|
|
||||||
|
|
||||||
handle_vision_override(game::mp::cg->cvsData, i, vision_name.data(), 1, game::mp::cl->serverTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!vision_set)
|
|
||||||
{
|
|
||||||
// disable 'post apply' vision if the game hasn't used it yet
|
|
||||||
if (game::mp::cg->cvsData.visionName[5][0] == '\0')
|
|
||||||
{
|
|
||||||
game::mp::cg->cvsData.postApplyLerpDest = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (vision_set == 1)
|
|
||||||
{
|
|
||||||
std::memset(&game::mp::cg->cvsData.visionSetCurrent, 0, sizeof(game::mp::cg->cvsData.visionSetCurrent));
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset modified flag to avoid doing the same work each frame
|
|
||||||
demo_vision->modified = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_fast_forward(auto old_connstate)
|
|
||||||
{
|
|
||||||
if (const auto new_connstate = *game::mp::connstate; new_connstate == game::CA_ACTIVE)
|
|
||||||
{
|
|
||||||
const auto server_time = game::mp::cl->serverTime;
|
|
||||||
const auto snap_svr_time = game::mp::cl->snap.serverTime;
|
|
||||||
|
|
||||||
demo.handle_fast_forward([old_connstate, server_time, snap_svr_time](
|
|
||||||
std::optional<std::int32_t>& rewind_snap_svr_time, bool& skip_timeout)
|
|
||||||
{
|
|
||||||
if (old_connstate == game::CA_PRIMED)
|
|
||||||
{
|
|
||||||
if (rewind_snap_svr_time)
|
|
||||||
{
|
|
||||||
// handle fast forward after rewinding
|
|
||||||
if (*rewind_snap_svr_time > snap_svr_time)
|
|
||||||
{
|
|
||||||
fast_forward_demo(*rewind_snap_svr_time - snap_svr_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
rewind_snap_svr_time = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (old_connstate == game::CA_ACTIVE && !skip_timeout)
|
|
||||||
{
|
|
||||||
if (const auto delta_time = snap_svr_time - server_time; delta_time >= 1000)
|
|
||||||
{
|
|
||||||
// skip timeout between first and second snapshots (for user demos)
|
|
||||||
fast_forward_demo(delta_time);
|
|
||||||
skip_timeout = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_demo_patches()
|
|
||||||
{
|
|
||||||
// set cg_t->demoType to DEMO_TYPE_CLIENT to disable mouse input and avoid frame interpolation issues
|
|
||||||
game::mp::cg->demoType = game::mp::DEMO_TYPE_CLIENT;
|
|
||||||
|
|
||||||
// ensure that sv_cheats is always enabled during demo playback
|
|
||||||
if (auto* sv_cheats = game::Dvar_FindVar("sv_cheats"); sv_cheats)
|
|
||||||
{
|
|
||||||
sv_cheats->current.enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sometimes the hud isn't updated after a weapon change; force update the weapon name and ammo (clip) when changing weapons
|
|
||||||
// various other Call of Duty games are also affected by this bug
|
|
||||||
if (game::mp::cg->weaponSelect.data != static_cast<std::size_t>(game::mp::cg->ps.weapon))
|
|
||||||
{
|
|
||||||
game::CG_SelectWeapon(0, game::mp::cg->ps.weapon, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handle_demo_reading(const std::int32_t client_num)
|
|
||||||
{
|
|
||||||
if (game::Live_SyncOnlineDataFlags(0) || !demo.is_demo_playing())
|
|
||||||
{
|
|
||||||
// set demoType to default value
|
|
||||||
// normally the game does this unconditionally, but that code has been removed
|
|
||||||
game::mp::cg->demoType = game::mp::DEMO_TYPE_NONE;
|
|
||||||
|
|
||||||
CL_SetCGameTime_hook.invoke<void>(client_num);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto old_connstate = *game::mp::connstate;
|
|
||||||
if (old_connstate == game::CA_ACTIVE)
|
|
||||||
{
|
|
||||||
// reset cl->newSnapshots to prevent CL_SetCGameTime from making a call to CL_AdjustTimeDelta,
|
|
||||||
// which interferes with the timescale
|
|
||||||
game::mp::cl->newSnapshots = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CL_SetCGameTime_hook.invoke<void>(client_num);
|
|
||||||
|
|
||||||
handle_fast_forward(old_connstate);
|
|
||||||
handle_vision();
|
|
||||||
handle_demo_patches();
|
|
||||||
|
|
||||||
demo.parse_demo_wrapper();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void write_packet(const std::int32_t client_num)
|
|
||||||
{
|
|
||||||
if (!demo.is_demo_playing())
|
|
||||||
{
|
|
||||||
CL_WritePacket_hook.invoke<void>(client_num);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_predicted_player_data(const game::mp::clientActive_t& cl, std::int32_t ps_time, game::mp::playerState_t& ps)
|
|
||||||
{
|
|
||||||
if (!demo.is_demo_playing())
|
|
||||||
{
|
|
||||||
return CL_GetPredictedPlayerInformationForServerTime_hook.invoke<bool>(&cl, ps_time, &ps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// hide regular hud when showing special hud for killstreak reward
|
|
||||||
// must not unset this 15th bit when dead to avoid a crash in CG_SimulateBulletFire
|
|
||||||
ps.otherFlags = (ps.pm_type == 7 || ps.remoteControlEnt != 2047 || ps.vehicleState.entity != 2047)
|
|
||||||
? ps.otherFlags | 0x4000
|
|
||||||
: ps.otherFlags & ~0x4000;
|
|
||||||
|
|
||||||
demo.use_predicted_data([&ps, ps_time](const std::array<predicted_data_t, 256>& pred_data, std::size_t pred_data_index)
|
|
||||||
{
|
|
||||||
static constexpr auto pred_data_size = std::tuple_size_v<std::remove_reference_t<decltype(pred_data)>>;
|
|
||||||
|
|
||||||
// find most recent predicted player data
|
|
||||||
for (std::size_t count = 0, index = pred_data_index - 1; count < pred_data_size; ++count, --index)
|
|
||||||
{
|
|
||||||
const auto& p_data = pred_data[index % pred_data_size];
|
|
||||||
if (p_data.cad.serverTime <= ps_time && ps_time - p_data.cad.serverTime < 500)
|
|
||||||
{
|
|
||||||
std::memcpy(&ps.origin[0], &p_data.cad.origin[0], sizeof(ps.origin));
|
|
||||||
std::memcpy(&ps.velocity[0], &p_data.cad.velocity[0], sizeof(ps.velocity));
|
|
||||||
std::memcpy(&ps.viewangles[0], &p_data.viewangles[0], sizeof(ps.viewangles));
|
|
||||||
ps.bobCycle = p_data.cad.bobCycle;
|
|
||||||
ps.movementDir = p_data.cad.movementDir;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// must return true otherwise branch is taken that undoes the work of this function
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get_predicted_vehicle_data(const game::mp::clientActive_t& cl, std::int32_t ps_time, game::PlayerVehicleState& ps_vehicle)
|
|
||||||
{
|
|
||||||
if (!demo.is_demo_playing())
|
|
||||||
{
|
|
||||||
return CL_GetPredictedVehicleForServerTime_hook.invoke<bool>(&cl, ps_time, &ps_vehicle);
|
|
||||||
}
|
|
||||||
|
|
||||||
demo.use_predicted_data([&ps_vehicle, ps_time](const std::array<predicted_data_t, 256>& pred_data, std::size_t pred_data_index)
|
|
||||||
{
|
|
||||||
static constexpr auto pred_data_size = std::tuple_size_v<std::remove_reference_t<decltype(pred_data)>>;
|
|
||||||
|
|
||||||
// find most recent predicted vehicle data
|
|
||||||
for (std::size_t count = 0, index = pred_data_index - 1; count < pred_data_size; ++count, --index)
|
|
||||||
{
|
|
||||||
const auto& p_data = pred_data[index % pred_data_size];
|
|
||||||
if (p_data.cad.serverTime <= ps_time && ps_time - p_data.cad.serverTime < 500)
|
|
||||||
{
|
|
||||||
const auto org_entity = ps_vehicle.entity;
|
|
||||||
ps_vehicle = p_data.cad.playerVehStateClientArchive;
|
|
||||||
ps_vehicle.entity = org_entity;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// must return true otherwise branch is taken that undoes the work of this function
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool show_intermission_menu(const std::int32_t client_num, void* lua_vm)
|
|
||||||
{
|
|
||||||
// it appeared easier to prevent the intermission menu from being shown instead of closing or hiding it
|
|
||||||
|
|
||||||
if (!demo.is_demo_playing() || !demo.is_demo_repeating())
|
|
||||||
{
|
|
||||||
return LUI_IntermissionBegan_hook.invoke<bool>(client_num, lua_vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
// rewind demo and don't display intermission menu for repeated demos
|
|
||||||
static_cast<void>(demo.rewind_demo(std::nullopt));
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void control_ui_element(const std::int32_t client_num, const game::mp::snapshot_t& old_snap, game::mp::snapshot_t& new_snap)
|
|
||||||
{
|
|
||||||
// it appeared easier to prevent these menus from being shown instead of closing or hiding them
|
|
||||||
|
|
||||||
if (demo.is_demo_playing())
|
|
||||||
{
|
|
||||||
// skip team selection menu (and loadout selection menu)
|
|
||||||
assert(std::string_view(*reinterpret_cast<const char**>(game::Omnvar_GetDef(103))) == "ui_options_menu");
|
|
||||||
new_snap.omnvars[103] = {};
|
|
||||||
|
|
||||||
if (demo.is_demo_repeating())
|
|
||||||
{
|
|
||||||
// skip extinction mode end-of-game score / stats menu
|
|
||||||
assert(std::string_view(*reinterpret_cast<const char**>(game::Omnvar_GetDef(194))) == "ui_alien_show_eog_score");
|
|
||||||
new_snap.omnvars[194] = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CG_UpdateOmnvars_hook.invoke<void>(client_num, &old_snap, &new_snap);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::int32_t update_gamestate_config_strings(game::msg_t& msg)
|
|
||||||
{
|
|
||||||
if (demo.is_demo_playing())
|
|
||||||
{
|
|
||||||
// update command sequences and overwrite config strings if that data is available
|
|
||||||
if (const auto& gs = demo.get_gamestate(); gs)
|
|
||||||
{
|
|
||||||
game::mp::cls->gameState = gs->data;
|
|
||||||
game::mp::clc->serverCommandSequence = gs->svr_cmd_seq;
|
|
||||||
game::mp::clc->lastExecutedServerCommand = gs->svr_cmd_seq;
|
|
||||||
game::mp::cgs->serverCommandSequence = gs->svr_cmd_seq;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(is_gamestate_valid(game::mp::cls->gameState, false));
|
|
||||||
|
|
||||||
game::mp::clc->lastExecutedServerCommand = game::mp::clc->serverCommandSequence;
|
|
||||||
game::mp::cgs->serverCommandSequence = game::mp::clc->serverCommandSequence;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// call MSG_ReadLong that this hook replaces
|
|
||||||
return game::MSG_ReadLong(&msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ignore_mouse_input_killstreak_reward_1(const std::int32_t client_num, game::mp::cg_t& cg, game::vec3_t& out_view_origin, game::vec3_t& out_view_angles)
|
|
||||||
{
|
|
||||||
auto& in_killcam = cg.inKillCam;
|
|
||||||
|
|
||||||
if (!demo.is_demo_playing() || in_killcam)
|
|
||||||
{
|
|
||||||
VehicleCam_UpdatePlayerControlCam_hook.invoke<void>(client_num, &cg, &out_view_origin, &out_view_angles);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// required for e.g. the Gryphon killstreak reward
|
|
||||||
in_killcam = 1;
|
|
||||||
VehicleCam_UpdatePlayerControlCam_hook.invoke<void>(client_num, &cg, &out_view_origin, &out_view_angles);
|
|
||||||
in_killcam = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ignore_mouse_input_killstreak_reward_2(const std::int32_t client_num)
|
|
||||||
{
|
|
||||||
auto& other_flags = game::mp::cg->ps.otherFlags;
|
|
||||||
|
|
||||||
if (!demo.is_demo_playing() || !(other_flags & 0x4000))
|
|
||||||
{
|
|
||||||
return GetRemoteEyeValues_hook.invoke<bool>(client_num);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// required for e.g. the Trinity Rocket killstreak reward
|
|
||||||
other_flags &= ~0x4000;
|
|
||||||
const auto result = GetRemoteEyeValues_hook.invoke<bool>(client_num);
|
|
||||||
other_flags |= 0x4000;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace // command execution
|
|
||||||
{
|
|
||||||
bool execute_playback_command_internal(std::filesystem::path& file_path, bool repeat, bool print_error)
|
|
||||||
{
|
|
||||||
if (demo.start(file_path, repeat))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (print_error)
|
|
||||||
{
|
|
||||||
console::error("could not play demo %s\n", file_path.string().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool execute_playback_command_internal(std::string_view file_name, bool repeat)
|
|
||||||
{
|
|
||||||
const auto fs_basepath = get_base_path(false);
|
|
||||||
if (fs_basepath.empty())
|
|
||||||
{
|
|
||||||
console::error("could not find fs_basepath\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto mod = get_mod_directory();
|
|
||||||
|
|
||||||
auto file_path1 = std::filesystem::path(fs_basepath) / std::format("demos/client/{2}/user/{0}{1}", file_name, DEMO_EXTENSION, mod);
|
|
||||||
if (execute_playback_command_internal(file_path1, repeat, false))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto file_path2 = std::filesystem::path(fs_basepath) / std::format("demos/client/{2}/auto/{0}{1}", file_name, DEMO_EXTENSION, mod);
|
|
||||||
if (execute_playback_command_internal(file_path2, repeat, false))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto file_path3 = std::filesystem::path(fs_basepath) / std::format("demos/client/{0}{1}", file_name, DEMO_EXTENSION);
|
|
||||||
if (execute_playback_command_internal(file_path3, repeat, false))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto file_path4 = std::filesystem::path(fs_basepath) / std::format("demos/server/{2}/{0}{1}", file_name, DEMO_EXTENSION, mod);
|
|
||||||
if (execute_playback_command_internal(file_path4, repeat, false))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto file_path5 = std::filesystem::path(fs_basepath) / std::format("demos/server/{0}{1}", file_name, DEMO_EXTENSION);
|
|
||||||
if (execute_playback_command_internal(file_path5, repeat, false))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto file_path6 = std::filesystem::path(fs_basepath) / std::format("demos/{0}{1}", file_name, DEMO_EXTENSION);
|
|
||||||
if (execute_playback_command_internal(file_path6, repeat, false))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
console::error("could not play demo %s\n", file_path1.string().c_str());
|
|
||||||
console::error("could not play demo %s\n", file_path2.string().c_str());
|
|
||||||
console::error("could not play demo %s\n", file_path3.string().c_str());
|
|
||||||
console::error("could not play demo %s\n", file_path4.string().c_str());
|
|
||||||
console::error("could not play demo %s\n", file_path5.string().c_str());
|
|
||||||
console::error("could not play demo %s\n", file_path6.string().c_str());
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <bool force_playback>
|
|
||||||
bool execute_playback_command(const command::params& params)
|
|
||||||
{
|
|
||||||
struct args_t
|
|
||||||
{
|
|
||||||
bool fullpath{};
|
|
||||||
bool repeat{};
|
|
||||||
};
|
|
||||||
|
|
||||||
auto parsed_arguments = [¶ms]() -> std::optional<args_t>
|
|
||||||
{
|
|
||||||
if (params.size() == 3)
|
|
||||||
{
|
|
||||||
if (std::string_view(params.get(2)) == "fullpath")
|
|
||||||
{
|
|
||||||
return args_t{ true, false };
|
|
||||||
}
|
|
||||||
else if (std::string_view(params.get(2)) == "repeat")
|
|
||||||
{
|
|
||||||
return args_t{ false, true };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (params.size() == 4)
|
|
||||||
{
|
|
||||||
if (std::string_view(params.get(2)) == "fullpath" && std::string_view(params.get(3)) == "repeat")
|
|
||||||
{
|
|
||||||
return args_t{ true, true };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return args_t{ false, false };
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
if (params.size() < 2 || params.size() > 4 || !parsed_arguments)
|
|
||||||
{
|
|
||||||
console::info("usage: demoplay <file_name>, fullpath(optional), repeat(optional)\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if constexpr (!force_playback)
|
|
||||||
{
|
|
||||||
if (!demo.is_demo_playing() && game::CL_IsCgameInitialized())
|
|
||||||
{
|
|
||||||
console::warn("could not play demo; please disconnect first\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parsed_arguments->fullpath)
|
|
||||||
{
|
|
||||||
return execute_playback_command_internal(params.get(1), parsed_arguments->repeat);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
auto file_path = std::filesystem::path(params.get(1));
|
|
||||||
return execute_playback_command_internal(file_path, parsed_arguments->repeat, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void execute_playback_instant_play_command(const command::params& params)
|
|
||||||
{
|
|
||||||
// the demo recording code (demo_recording_t::shutdown_watcher) should take care of properly closing any recording demo files!
|
|
||||||
|
|
||||||
const auto demo_playing = demo.is_demo_playing();
|
|
||||||
if (execute_playback_command<true>(params))
|
|
||||||
{
|
|
||||||
if (auto* sv_running = game::Dvar_FindVar("sv_running"); sv_running && sv_running->current.enabled)
|
|
||||||
{
|
|
||||||
// demo cannot play if local server is still considered to be running
|
|
||||||
sv_running->current.enabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!demo_playing)
|
|
||||||
{
|
|
||||||
if (auto& connstate = *game::mp::connstate; connstate == game::CA_ACTIVE)
|
|
||||||
{
|
|
||||||
// prevent old snapshots from being processed,
|
|
||||||
// would mess up the server command sequences and server times
|
|
||||||
connstate = game::CA_PRIMED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void execute_playback_replay_command(const command::params& params)
|
|
||||||
{
|
|
||||||
if (params.size() != 1)
|
|
||||||
{
|
|
||||||
console::info("usage: demoreplay\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto path = demo.restart(); path)
|
|
||||||
{
|
|
||||||
console::error("could not play demo %s\n", path->get().string().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void execute_playback_rewind_command(const command::params& params)
|
|
||||||
{
|
|
||||||
if (params.size() < 1 || params.size() > 2)
|
|
||||||
{
|
|
||||||
console::info("usage: demorewind <milliseconds>(optional)\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.size() == 2)
|
|
||||||
{
|
|
||||||
const auto msec = std::atoi(params.get(1));
|
|
||||||
if (msec <= 0)
|
|
||||||
{
|
|
||||||
console::error("demorewind invalid value %s\n", params.get(1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!demo.rewind_demo(msec))
|
|
||||||
{
|
|
||||||
console::error("demo must be playing to rewind\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!demo.rewind_demo(std::nullopt))
|
|
||||||
{
|
|
||||||
console::error("demo must be playing to rewind\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void execute_playback_fast_forward_command(const command::params& params)
|
|
||||||
{
|
|
||||||
if (params.size() != 2)
|
|
||||||
{
|
|
||||||
console::info("usage: demoforward <milliseconds>\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto msec = std::atoi(params.get(1));
|
|
||||||
if (msec <= 0)
|
|
||||||
{
|
|
||||||
console::error("demoforward invalid value %s\n", params.get(1));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (demo.is_demo_playing())
|
|
||||||
{
|
|
||||||
fast_forward_demo(static_cast<std::uint32_t>(msec));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console::error("demo must be playing to fast forward\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace demo_playback
|
|
||||||
{
|
|
||||||
bool playing()
|
|
||||||
{
|
|
||||||
return demo.is_demo_playing();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool startplay(std::string_view file_name, bool repeat)
|
|
||||||
{
|
|
||||||
return execute_playback_command_internal(file_name, repeat);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool startplay(std::filesystem::path& file_name, bool repeat)
|
|
||||||
{
|
|
||||||
return execute_playback_command_internal(file_name, repeat, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopplay()
|
|
||||||
{
|
|
||||||
demo.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
class component final : public component_interface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void post_unpack() override
|
|
||||||
{
|
|
||||||
if (!game::environment::is_mp())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check run-time address assertions
|
|
||||||
check_address_assertions();
|
|
||||||
|
|
||||||
// primary hook for demo playback; read demo data after server time has been updated
|
|
||||||
CL_SetCGameTime_hook.create(game::CL_SetCGameTime, handle_demo_reading);
|
|
||||||
|
|
||||||
// don't write packets when playing a demo
|
|
||||||
CL_WritePacket_hook.create(game::CL_WritePacket, write_packet);
|
|
||||||
|
|
||||||
// replace functions to get predicted data, because they skip the viewangles
|
|
||||||
CL_GetPredictedPlayerInformationForServerTime_hook.create(
|
|
||||||
game::CL_GetPredictedPlayerInformationForServerTime, get_predicted_player_data);
|
|
||||||
CL_GetPredictedVehicleForServerTime_hook.create(
|
|
||||||
game::CL_GetPredictedVehicleForServerTime, get_predicted_vehicle_data);
|
|
||||||
|
|
||||||
// ignore mouse input when updating the camera for certain killstreak rewards
|
|
||||||
VehicleCam_UpdatePlayerControlCam_hook.create(
|
|
||||||
game::VehicleCam_UpdatePlayerControlCam, ignore_mouse_input_killstreak_reward_1);
|
|
||||||
GetRemoteEyeValues_hook.create(
|
|
||||||
game::GetRemoteEyeValues, ignore_mouse_input_killstreak_reward_2);
|
|
||||||
|
|
||||||
// optional skip of intermission menu
|
|
||||||
LUI_IntermissionBegan_hook.create(game::LUI_IntermissionBegan, show_intermission_menu);
|
|
||||||
|
|
||||||
// don't open any menu when demo playback has started
|
|
||||||
CG_UpdateOmnvars_hook.create(game::CG_UpdateOmnvars, control_ui_element);
|
|
||||||
|
|
||||||
// allow user to override visual settings
|
|
||||||
CG_VisionSetStartLerp_To_hook.create(0x1402A3200, handle_vision_override);
|
|
||||||
demo_vision = game::Dvar_RegisterEnum(
|
|
||||||
"demovision", const_cast<const char**>(VISION_SETS.data()), 0, game::DVAR_FLAG_NONE, "Override vision set for demos");
|
|
||||||
|
|
||||||
// update the gamestate configs strings with 'live' data from the demo (in CL_ParseGamestate)
|
|
||||||
utils::hook::call(0x1402CCD56, update_gamestate_config_strings);
|
|
||||||
|
|
||||||
// don't let the game set cg_t->demoType in CG_DrawActiveFrame,
|
|
||||||
// otherwise demo playback will have mouse input and suffer from frame interpolation issues
|
|
||||||
utils::hook::nop(0x14029981C, 6);
|
|
||||||
|
|
||||||
// replace thread local storage lookup for cg.bgs in function CG_EmissiveBlendCommand with hardcoded address
|
|
||||||
// when CG_ExecuteNewServerCommands is called manually (e.g. from demo_utils::process_server_commands),
|
|
||||||
// this address in tls may be a nullptr and the game could crash here (in extinction mode at least)
|
|
||||||
static constexpr auto patch_address = 0x14028844B;
|
|
||||||
static constexpr std::array<std::uint8_t, 7> patch{ 0x48, 0x8D, 0x1D, 0x68, 0xBC, 0x59, 0x01 };
|
|
||||||
|
|
||||||
utils::hook::nop(patch_address, 0xE);
|
|
||||||
utils::hook::nop(patch_address + 0x1C, 4);
|
|
||||||
utils::hook::nop(patch_address + 0x22, 4);
|
|
||||||
utils::hook::copy(patch_address + 0xE, patch.data(), patch.size()); // lea rbx, [0x1418240C8] (point RBX to cg.bgs)
|
|
||||||
assert(std::bit_cast<std::size_t>(&game::mp::cg->bgs) ==
|
|
||||||
patch_address + 0xE + patch.size() + *reinterpret_cast<const std::uint32_t*>(patch_address + 0x11));
|
|
||||||
|
|
||||||
// add console command support to play demos
|
|
||||||
command::add("demoplay", execute_playback_command<false>);
|
|
||||||
|
|
||||||
// add experimental command console support to play demos,
|
|
||||||
// skip disconnecting from local or online server and skip map (re)loading
|
|
||||||
command::add("demoinstantplay", execute_playback_instant_play_command);
|
|
||||||
|
|
||||||
// replay most recent demo (if any)
|
|
||||||
command::add("demoreplay", execute_playback_replay_command);
|
|
||||||
|
|
||||||
// rewind demo (by N msec)
|
|
||||||
command::add("demorewind", execute_playback_rewind_command);
|
|
||||||
|
|
||||||
// fast forward demo by N msec
|
|
||||||
command::add("demoforward", execute_playback_fast_forward_command);
|
|
||||||
|
|
||||||
// add command line support to play demos; this makes it possible to double click on a demo file to play it
|
|
||||||
const auto& args = utils::command_line::get_args();
|
|
||||||
for (const auto& arg : args)
|
|
||||||
{
|
|
||||||
if (arg.extension() == DEMO_EXTENSION)
|
|
||||||
{
|
|
||||||
scheduler::on_game_initialized([cmd = std::format(R"(demoplay "{}" fullpath repeat)", arg.string())]() mutable
|
|
||||||
{
|
|
||||||
command::execute(std::move(cmd), true);
|
|
||||||
}, scheduler::main);
|
|
||||||
|
|
||||||
console::info("the demo should start to load in a few seconds\n");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_COMPONENT(demo_playback::component)
|
|
@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace demo_playback
|
|
||||||
{
|
|
||||||
[[nodiscard]] bool playing();
|
|
||||||
[[nodiscard]] bool startplay(std::string_view file_name, bool repeat = true);
|
|
||||||
[[nodiscard]] bool startplay(std::filesystem::path& file_path, bool repeat = true);
|
|
||||||
[[nodiscard]] void stopplay();
|
|
||||||
}
|
|
@ -1,746 +0,0 @@
|
|||||||
#include "std_include.hpp"
|
|
||||||
#include "loader/component_loader.hpp"
|
|
||||||
|
|
||||||
#include "demo_playback.hpp"
|
|
||||||
#include "demo_recording.hpp"
|
|
||||||
#include "demo_utils.hpp"
|
|
||||||
|
|
||||||
#include "command.hpp"
|
|
||||||
#include "console.hpp"
|
|
||||||
#include "game/game.hpp"
|
|
||||||
#include "scheduler.hpp"
|
|
||||||
#include "utils/hook.hpp"
|
|
||||||
|
|
||||||
using namespace demo_utils;
|
|
||||||
|
|
||||||
namespace // demo class
|
|
||||||
{
|
|
||||||
game::dvar_t* demo_autorecord;
|
|
||||||
|
|
||||||
class demo_recording_t
|
|
||||||
{
|
|
||||||
static constexpr auto NON_DELTA_SNAPSHOT_COUNT = 4;
|
|
||||||
|
|
||||||
struct stored_times_t
|
|
||||||
{
|
|
||||||
std::int32_t flush_time{};
|
|
||||||
std::int32_t predicted_data_time{};
|
|
||||||
std::int32_t cur_svr_time{};
|
|
||||||
std::int32_t first_svr_time_user_file{};
|
|
||||||
std::int32_t first_svr_time_auto_file{};
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
demo_recording_t() = default;
|
|
||||||
~demo_recording_t();
|
|
||||||
demo_recording_t(const demo_recording_t&) = delete;
|
|
||||||
demo_recording_t(demo_recording_t&&) noexcept = delete;
|
|
||||||
demo_recording_t& operator=(const demo_recording_t&) = delete;
|
|
||||||
demo_recording_t& operator=(demo_recording_t&&) noexcept = delete;
|
|
||||||
|
|
||||||
[[nodiscard]] std::uint32_t get_demo_count() const;
|
|
||||||
[[nodiscard]] std::int32_t get_demo_length() const;
|
|
||||||
[[nodiscard]] bool is_recording() const;
|
|
||||||
[[nodiscard]] bool non_delta_data_requested() const;
|
|
||||||
[[nodiscard]] bool start(const std::filesystem::path& user_path, const std::filesystem::path& auto_path);
|
|
||||||
[[nodiscard]] bool stop();
|
|
||||||
|
|
||||||
void shutdown_watcher();
|
|
||||||
void process_network_data(auto old_connstate, auto new_connstate, std::span<const std::uint8_t> network_data);
|
|
||||||
void process_predicted_data();
|
|
||||||
|
|
||||||
private:
|
|
||||||
[[nodiscard]] bool is_auto_recording() const;
|
|
||||||
[[nodiscard]] bool non_delta_data_request_acknowledged();
|
|
||||||
[[nodiscard]] bool start_user_file(const std::filesystem::path& path);
|
|
||||||
[[nodiscard]] bool start_auto_file(const std::filesystem::path& path);
|
|
||||||
[[nodiscard]] static bool stop_internal(std::ofstream& file, std::int32_t first_svr_time, std::int32_t last_svr_time);
|
|
||||||
|
|
||||||
void flush_filestreams();
|
|
||||||
void check_auto_recording();
|
|
||||||
void process_old_data();
|
|
||||||
void process_new_gamestate(std::span<const std::uint8_t> network_data);
|
|
||||||
void process_first_snapshot(std::span<const std::uint8_t> network_data);
|
|
||||||
void process_special_network_data(auto old_connstate, auto new_connstate, std::span<const std::uint8_t> network_data);
|
|
||||||
void process_helo_pilot_turret_fire();
|
|
||||||
|
|
||||||
std::uint32_t demo_count_{};
|
|
||||||
std::uint32_t non_delta_count_{};
|
|
||||||
stored_times_t times_;
|
|
||||||
buffer_t crit_data_;
|
|
||||||
std::ofstream user_file_;
|
|
||||||
buffer_t auto_buffer_;
|
|
||||||
std::ofstream auto_file_;
|
|
||||||
} demo;
|
|
||||||
|
|
||||||
demo_recording_t::~demo_recording_t()
|
|
||||||
{
|
|
||||||
process_old_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint32_t demo_recording_t::get_demo_count() const
|
|
||||||
{
|
|
||||||
return demo_count_;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::int32_t demo_recording_t::get_demo_length() const
|
|
||||||
{
|
|
||||||
return (times_.cur_svr_time - times_.first_svr_time_user_file) / 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::is_recording() const
|
|
||||||
{
|
|
||||||
return user_file_.good() && user_file_.is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::is_auto_recording() const
|
|
||||||
{
|
|
||||||
return auto_file_.good() && auto_file_.is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::non_delta_data_requested() const
|
|
||||||
{
|
|
||||||
return non_delta_count_ > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::non_delta_data_request_acknowledged()
|
|
||||||
{
|
|
||||||
if (non_delta_data_requested())
|
|
||||||
{
|
|
||||||
const auto delta_num = game::mp::cl->snap.deltaNum;
|
|
||||||
if (static constexpr auto no_delta = -1; delta_num == no_delta)
|
|
||||||
{
|
|
||||||
--non_delta_count_;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::start_user_file(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
const auto crit_data = crit_data_.get();
|
|
||||||
if (!crit_data.size())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ofstream file(path, std::ios::binary);
|
|
||||||
if (!file.good() || !file.is_open())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
write_general_header(file);
|
|
||||||
|
|
||||||
// store mod name so if there's mod support the mod can be loaded
|
|
||||||
write_mod_header(file);
|
|
||||||
|
|
||||||
// store map name and game type so that the map can be preloaded
|
|
||||||
if (!write_map_header(file))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// store the current gamestate config strings so that the original strings are overwritten if they were modified
|
|
||||||
if (!write_gamestate_data(file))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// required for demos (and live matches) to function at all
|
|
||||||
file.write(reinterpret_cast<const char*>(crit_data.data()), crit_data.size());
|
|
||||||
|
|
||||||
// set the non-delta snapshot count, the demo won't be valid otherwise
|
|
||||||
non_delta_count_ = NON_DELTA_SNAPSHOT_COUNT;
|
|
||||||
|
|
||||||
// store first server time so that demo length can be tracked
|
|
||||||
times_.first_svr_time_user_file = game::mp::cl->snap.serverTime;
|
|
||||||
|
|
||||||
// increase demo count for the text render function to work properly
|
|
||||||
++demo_count_;
|
|
||||||
|
|
||||||
user_file_ = std::move(file);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::start_auto_file(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
std::ofstream file(path, std::ios::binary);
|
|
||||||
if (!file.good() || !file.is_open())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto buffer = auto_buffer_.get();
|
|
||||||
file.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
|
|
||||||
|
|
||||||
auto_file_ = std::move(file);
|
|
||||||
auto_buffer_.clear();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::start(const std::filesystem::path& user_path, const std::filesystem::path& auto_path)
|
|
||||||
{
|
|
||||||
if (!is_recording())
|
|
||||||
{
|
|
||||||
if (!is_auto_recording() && !start_auto_file(auto_path))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!start_user_file(user_path))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::stop_internal(std::ofstream& file, std::int32_t first_svr_time, std::int32_t last_svr_time)
|
|
||||||
{
|
|
||||||
write_general_footer(file, first_svr_time, last_svr_time);
|
|
||||||
write_end_of_file(file);
|
|
||||||
|
|
||||||
file.flush();
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
return file.good() && !file.is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording_t::stop()
|
|
||||||
{
|
|
||||||
return is_recording() && stop_internal(user_file_, times_.first_svr_time_user_file, times_.cur_svr_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::process_old_data()
|
|
||||||
{
|
|
||||||
if (is_recording() && !stop())
|
|
||||||
{
|
|
||||||
console::error("could not stop user demo\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_auto_recording() && !stop_internal(auto_file_, times_.first_svr_time_auto_file, times_.cur_svr_time))
|
|
||||||
{
|
|
||||||
console::error("could not close auto demo\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
demo_count_ = {};
|
|
||||||
non_delta_count_ = {};
|
|
||||||
times_ = {};
|
|
||||||
crit_data_.clear();
|
|
||||||
auto_buffer_.clear();
|
|
||||||
assert(!is_recording());
|
|
||||||
assert(!is_auto_recording());
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::shutdown_watcher()
|
|
||||||
{
|
|
||||||
auto execute_on_disconnect = [this, cgame_init = false]() mutable
|
|
||||||
{
|
|
||||||
if (demo_playback::playing())
|
|
||||||
{
|
|
||||||
if (is_recording() || is_auto_recording())
|
|
||||||
{
|
|
||||||
// this is only relevant when playing back demos without disconnecting from the server (instant play)
|
|
||||||
cgame_init = false;
|
|
||||||
process_old_data();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cgame_init && game::CL_IsCgameInitialized())
|
|
||||||
{
|
|
||||||
cgame_init = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cgame_init && !game::CL_IsCgameInitialized())
|
|
||||||
{
|
|
||||||
cgame_init = false;
|
|
||||||
process_old_data();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
scheduler::loop(execute_on_disconnect, scheduler::main);
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::flush_filestreams()
|
|
||||||
{
|
|
||||||
const auto real_time = game::mp::cls->realtime;
|
|
||||||
if (real_time >= times_.flush_time + 15'000)
|
|
||||||
{
|
|
||||||
times_.flush_time = real_time;
|
|
||||||
|
|
||||||
if (is_recording())
|
|
||||||
{
|
|
||||||
user_file_.flush();
|
|
||||||
}
|
|
||||||
if (is_auto_recording())
|
|
||||||
{
|
|
||||||
auto_file_.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::check_auto_recording()
|
|
||||||
{
|
|
||||||
if (!is_auto_recording() && times_.first_svr_time_auto_file && demo_autorecord && demo_autorecord->current.enabled)
|
|
||||||
{
|
|
||||||
assert(!game::Live_SyncOnlineDataFlags(0) && game::CL_IsCgameInitialized());
|
|
||||||
|
|
||||||
const auto opt_auto_dir = create_directory_auto_demo();
|
|
||||||
if (!opt_auto_dir)
|
|
||||||
{
|
|
||||||
console::error("could not create demo auto directory\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto opt_auto_path = create_path_auto_demo(*opt_auto_dir);
|
|
||||||
if (!opt_auto_path)
|
|
||||||
{
|
|
||||||
console::error("could not create demo auto file\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!start_auto_file(*opt_auto_path))
|
|
||||||
{
|
|
||||||
// turn auto recording off to prevent spamming the console with errors
|
|
||||||
demo_autorecord->current.enabled = false;
|
|
||||||
|
|
||||||
console::error("could not create demo auto file %s\n", opt_auto_path->string().c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::process_new_gamestate(std::span<const std::uint8_t> network_data)
|
|
||||||
{
|
|
||||||
process_old_data();
|
|
||||||
|
|
||||||
// store gamestate for user file
|
|
||||||
write_network_data(crit_data_, network_data);
|
|
||||||
|
|
||||||
// reserve memory (low size if auto recording is enabled, because then the demo writes almost directly to disk)
|
|
||||||
const auto reserve_size = (demo_autorecord && demo_autorecord->current.enabled) ? 8192 : 1024 * 1024;
|
|
||||||
auto_buffer_.reserve_memory(reserve_size);
|
|
||||||
|
|
||||||
// write headers to auto buffer (ahead of the gamestate itself)
|
|
||||||
write_general_header(auto_buffer_);
|
|
||||||
write_mod_header(auto_buffer_);
|
|
||||||
write_map_header(auto_buffer_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::process_first_snapshot(std::span<const std::uint8_t> network_data)
|
|
||||||
{
|
|
||||||
assert(!is_recording() && crit_data_.size()
|
|
||||||
&& !times_.cur_svr_time && !times_.first_svr_time_user_file && !times_.first_svr_time_auto_file);
|
|
||||||
|
|
||||||
// store first snapshot for user file
|
|
||||||
write_network_data(crit_data_, network_data);
|
|
||||||
|
|
||||||
times_.first_svr_time_auto_file = game::mp::cl->snap.serverTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::process_special_network_data(auto old_connstate, auto new_connstate, std::span<const std::uint8_t> network_data)
|
|
||||||
{
|
|
||||||
assert(new_connstate >= game::CA_PRIMED);
|
|
||||||
|
|
||||||
if (old_connstate < game::CA_LOADING)
|
|
||||||
{
|
|
||||||
assert(!game::mp::cl->snap.valid && !game::mp::cl->snap.serverTime);
|
|
||||||
|
|
||||||
process_new_gamestate(network_data);
|
|
||||||
}
|
|
||||||
else if (old_connstate >= game::CA_PRIMED)
|
|
||||||
{
|
|
||||||
if (game::mp::cl->snap.valid && game::mp::cl->snap.serverTime && !times_.cur_svr_time)
|
|
||||||
{
|
|
||||||
process_first_snapshot(network_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
times_.cur_svr_time = game::mp::cl->snap.serverTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::process_network_data(auto old_connstate, auto new_connstate, std::span<const std::uint8_t> network_data)
|
|
||||||
{
|
|
||||||
if (new_connstate < game::CA_PRIMED)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
process_special_network_data(old_connstate, new_connstate, network_data);
|
|
||||||
check_auto_recording();
|
|
||||||
|
|
||||||
if (is_recording())
|
|
||||||
{
|
|
||||||
if (!non_delta_data_requested() || non_delta_data_request_acknowledged())
|
|
||||||
{
|
|
||||||
write_network_data(user_file_, network_data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_auto_recording())
|
|
||||||
{
|
|
||||||
write_network_data(auto_file_, network_data);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_network_data(auto_buffer_, network_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
flush_filestreams();
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::process_helo_pilot_turret_fire()
|
|
||||||
{
|
|
||||||
const auto turret_fire = cpy_cast<std::uint8_t>(reinterpret_cast<const std::uint8_t*>(0x1419A9394));
|
|
||||||
if (turret_fire > 0)
|
|
||||||
{
|
|
||||||
if (is_recording())
|
|
||||||
{
|
|
||||||
write_helo_pilot_turret_fire(user_file_, turret_fire);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_auto_recording())
|
|
||||||
{
|
|
||||||
write_helo_pilot_turret_fire(auto_file_, turret_fire);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_helo_pilot_turret_fire(auto_buffer_, turret_fire);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void demo_recording_t::process_predicted_data()
|
|
||||||
{
|
|
||||||
process_helo_pilot_turret_fire();
|
|
||||||
|
|
||||||
if (is_recording())
|
|
||||||
{
|
|
||||||
// only one predicted data packet is needed per (1000 / sv_fps) msec
|
|
||||||
// the best way to limit the amount of predicted data is to attempt to predict the next playerState_t::commandTime
|
|
||||||
// because during demo playback the playerState_t::commandTime determines which predicted data is used
|
|
||||||
|
|
||||||
const auto& cl = *game::mp::cl;
|
|
||||||
const auto cmd_number = static_cast<std::size_t>(cl.cmdNumber);
|
|
||||||
const auto pred_svr_time_delta = cl.cmds[cmd_number % std::size(cl.cmds)].serverTime
|
|
||||||
- cl.cmds[(cmd_number - 1) % std::size(cl.cmds)].serverTime;
|
|
||||||
|
|
||||||
const auto pred_svr_time = cl.cgamePredictedDataServerTime;
|
|
||||||
const auto svr_time_delta = std::clamp(cl.snap.serverTime - cl.oldSnapServerTime, 50, 100);
|
|
||||||
|
|
||||||
const auto cur_cmd_time = cl.snap.ps.commandTime;
|
|
||||||
const auto next_cmd_time = cur_cmd_time + svr_time_delta;
|
|
||||||
const auto threshold = (3 * pred_svr_time_delta) / 2;
|
|
||||||
|
|
||||||
if (pred_svr_time + threshold >= next_cmd_time && next_cmd_time + threshold >= pred_svr_time)
|
|
||||||
{
|
|
||||||
times_.predicted_data_time = cl.serverTime;
|
|
||||||
write_predicted_data(user_file_);
|
|
||||||
}
|
|
||||||
else if (cl.serverTime >= times_.predicted_data_time + svr_time_delta - 5)
|
|
||||||
{
|
|
||||||
times_.predicted_data_time = cl.serverTime;
|
|
||||||
write_predicted_data(user_file_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_auto_recording())
|
|
||||||
{
|
|
||||||
write_predicted_data(auto_file_);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
write_predicted_data(auto_buffer_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace // hooks
|
|
||||||
{
|
|
||||||
utils::hook::detour CL_ParseServerMessage_hook;
|
|
||||||
|
|
||||||
void capture_data(const std::int32_t client_num, game::msg_t& msg)
|
|
||||||
{
|
|
||||||
const auto old_connstate = *game::mp::connstate;
|
|
||||||
CL_ParseServerMessage_hook.invoke(client_num, &msg);
|
|
||||||
const auto new_connstate = *game::mp::connstate;
|
|
||||||
|
|
||||||
if (demo_playback::playing())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new_connstate < game::CA_DISCONNECTED || new_connstate > game::CA_ACTIVE)
|
|
||||||
{
|
|
||||||
console::warn("invalid connection state after processing data in CL_PacketEvent\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!msg.data || msg.overflowed || static_cast<std::size_t>(msg.cursize) > MAX_SIZE)
|
|
||||||
{
|
|
||||||
if (demo.is_recording())
|
|
||||||
{
|
|
||||||
console::warn("invalid data in CL_PacketEvent\n");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
demo.process_network_data(old_connstate, new_connstate,
|
|
||||||
std::span<const std::uint8_t>(reinterpret_cast<const std::uint8_t*>(msg.data), msg.cursize));
|
|
||||||
}
|
|
||||||
|
|
||||||
void capture_predicted_data(game::msg_t& msg, std::int32_t value, std::uint32_t bits)
|
|
||||||
{
|
|
||||||
if (!demo_playback::playing())
|
|
||||||
{
|
|
||||||
demo.process_predicted_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
// call MSG_WriteBits that this hook replaces
|
|
||||||
game::MSG_WriteBits(&msg, value, bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
void request_non_delta_data(game::msg_t& msg, std::int32_t value, std::uint32_t bits)
|
|
||||||
{
|
|
||||||
if (!demo_playback::playing() && demo.non_delta_data_requested())
|
|
||||||
{
|
|
||||||
// request a non-delta snapshot from the server
|
|
||||||
// to have a valid start for a new (user) demo
|
|
||||||
value = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// call MSG_WriteBits that this hook replaces
|
|
||||||
game::MSG_WriteBits(&msg, value, bits);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace // command execution
|
|
||||||
{
|
|
||||||
void add_recording_text_to_hud(const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
const auto* font = game::R_RegisterFont("fonts/normalfont");
|
|
||||||
if (!font)
|
|
||||||
{
|
|
||||||
console::error("could not register font\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto* view_placement = game::ScrPlace_GetViewPlacement();
|
|
||||||
if (!view_placement)
|
|
||||||
{
|
|
||||||
console::error("could not find view placement\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto create_resources = [&path, font, view_placement]()
|
|
||||||
{
|
|
||||||
struct resources_t
|
|
||||||
{
|
|
||||||
const game::Font_t* font{};
|
|
||||||
float y{};
|
|
||||||
std::int32_t demo_length{};
|
|
||||||
std::uint32_t demo_count{};
|
|
||||||
std::uint32_t org_text_size{};
|
|
||||||
std::string text;
|
|
||||||
};
|
|
||||||
|
|
||||||
resources_t res{
|
|
||||||
.font = font,
|
|
||||||
.y = view_placement->realViewportSize[1],
|
|
||||||
.demo_length = demo.get_demo_length(),
|
|
||||||
.demo_count = demo.get_demo_count(),
|
|
||||||
.text = "recording: " + path.stem().string()
|
|
||||||
};
|
|
||||||
|
|
||||||
res.org_text_size = static_cast<std::uint32_t>(res.text.size());
|
|
||||||
res.text.reserve(res.text.size() + 12);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
scheduler::schedule([res = create_resources()]() mutable
|
|
||||||
{
|
|
||||||
if (!demo.is_recording() || res.demo_count != demo.get_demo_count() || !game::CL_IsCgameInitialized())
|
|
||||||
{
|
|
||||||
return scheduler::cond_end;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.demo_length != demo.get_demo_length())
|
|
||||||
{
|
|
||||||
res.demo_length = demo.get_demo_length();
|
|
||||||
|
|
||||||
// resize to original size and append demo length in seconds
|
|
||||||
res.text.resize(res.org_text_size);
|
|
||||||
res.text += std::format(" ({} sec)", res.demo_length);
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr std::array<float, 4> color_red{ 178.0f / 255.0f, 34.0f / 255.0f, 34.0f / 255.0f, 1.0f };
|
|
||||||
|
|
||||||
// draw in bottom left corner
|
|
||||||
game::R_AddCmdDrawText(res.text.c_str(), std::numeric_limits<std::int32_t>::max(),
|
|
||||||
res.font, 10.0f, res.y - 5.0f, 0.75f, 0.75f, 0.0f, color_red.data(), 0);
|
|
||||||
|
|
||||||
return scheduler::cond_continue;
|
|
||||||
}, scheduler::renderer);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool execute_demo_start_command_internal(std::string_view file_name, bool overwrite)
|
|
||||||
{
|
|
||||||
if (game::Live_SyncOnlineDataFlags(0) || !game::CL_IsCgameInitialized() || *game::mp::connstate != game::CA_ACTIVE)
|
|
||||||
{
|
|
||||||
console::warn("must be in a match to record a demo\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (demo.is_recording())
|
|
||||||
{
|
|
||||||
console::warn("must stop demo recording before starting to record\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (demo_playback::playing())
|
|
||||||
{
|
|
||||||
console::warn("cannot record a demo during demo playback\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto opt_user_dir = create_directory_user_demo();
|
|
||||||
if (!opt_user_dir)
|
|
||||||
{
|
|
||||||
console::error("could not create demo user directory\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto opt_auto_dir = create_directory_auto_demo();
|
|
||||||
if (!opt_auto_dir)
|
|
||||||
{
|
|
||||||
console::error("could not create demo auto directory\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto opt_user_path = (file_name.empty())
|
|
||||||
? create_path_user_demo(*opt_user_dir)
|
|
||||||
: create_path_user_demo(*opt_user_dir, file_name, overwrite);
|
|
||||||
if (!opt_user_path)
|
|
||||||
{
|
|
||||||
console::error("could not create demo user file\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto opt_auto_path = create_path_auto_demo(*opt_auto_dir);
|
|
||||||
if (!opt_auto_path)
|
|
||||||
{
|
|
||||||
console::error("could not create demo auto file\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!demo.start(*opt_user_path, *opt_auto_path))
|
|
||||||
{
|
|
||||||
console::error("could not create demo user file %s\n", opt_user_path->string().c_str());
|
|
||||||
console::error("could not create demo auto file %s\n", opt_auto_path->string().c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
add_recording_text_to_hud(*opt_user_path);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void execute_demo_start_command(const command::params& params)
|
|
||||||
{
|
|
||||||
if (params.size() < 1 || params.size() > 3 || (params.size() == 3 && std::string_view(params.get(2)) != "overwrite"))
|
|
||||||
{
|
|
||||||
console::info("usage: demostart <file_name>(optional), overwrite(optional)\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(params.size() != 3 || (params.size() == 3 && std::string_view(params.get(2)) == "overwrite"));
|
|
||||||
execute_demo_start_command_internal((params.size() >= 2) ? params.get(1) : std::string_view{}, (params.size() == 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
void execute_demo_stop_command(const command::params& params)
|
|
||||||
{
|
|
||||||
if (params.size() != 1)
|
|
||||||
{
|
|
||||||
console::info("usage: demostop\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (demo.is_recording() && !demo.stop())
|
|
||||||
{
|
|
||||||
console::error("could not stop demo\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace demo_recording
|
|
||||||
{
|
|
||||||
bool recording()
|
|
||||||
{
|
|
||||||
return demo.is_recording();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool startrecord(std::string_view file_name, bool overwrite)
|
|
||||||
{
|
|
||||||
return execute_demo_start_command_internal(file_name, overwrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool demo_recording::stoprecord()
|
|
||||||
{
|
|
||||||
return demo.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
class component final : public component_interface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void post_unpack() override
|
|
||||||
{
|
|
||||||
if (!game::environment::is_mp())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check run-time address assertions
|
|
||||||
check_address_assertions();
|
|
||||||
|
|
||||||
// capture incoming packets
|
|
||||||
CL_ParseServerMessage_hook.create(game::CL_ParseServerMessage, capture_data);
|
|
||||||
|
|
||||||
// capture client predicted data (in CL_WritePacket)
|
|
||||||
utils::hook::call(0x1402C21B9, capture_predicted_data);
|
|
||||||
|
|
||||||
// request the server for no delta data (in CL_WritePacket)
|
|
||||||
utils::hook::call(0x1402C2026, request_non_delta_data);
|
|
||||||
|
|
||||||
// add support to auto record
|
|
||||||
demo_autorecord = game::Dvar_RegisterBool(
|
|
||||||
"demoautorecord", false, game::DVAR_FLAG_NONE, "Automatically start recording a demo.");
|
|
||||||
|
|
||||||
// add console command support to record demos
|
|
||||||
command::add("demostart", execute_demo_start_command);
|
|
||||||
command::add("demostop", execute_demo_stop_command);
|
|
||||||
|
|
||||||
scheduler::on_game_initialized([]()
|
|
||||||
{
|
|
||||||
demo.shutdown_watcher();
|
|
||||||
|
|
||||||
// check if demo directories exist / could be created
|
|
||||||
if (!can_create_demo_directories())
|
|
||||||
{
|
|
||||||
console::error("could not create demo directories\n");
|
|
||||||
}
|
|
||||||
}, scheduler::main);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_COMPONENT(demo_recording::component)
|
|
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace demo_recording
|
|
||||||
{
|
|
||||||
[[nodiscard]] bool recording();
|
|
||||||
[[nodiscard]] bool startrecord(std::string_view file_name = {}, bool overwrite = {});
|
|
||||||
[[nodiscard]] bool stoprecord();
|
|
||||||
}
|
|
@ -1,664 +0,0 @@
|
|||||||
#include "std_include.hpp"
|
|
||||||
#include "loader/component_loader.hpp"
|
|
||||||
|
|
||||||
#include "demo_sv_recording.hpp"
|
|
||||||
#include "demo_utils.hpp"
|
|
||||||
|
|
||||||
#include "command.hpp"
|
|
||||||
#include "console.hpp"
|
|
||||||
#include "scheduler.hpp"
|
|
||||||
#include "utils/hook.hpp"
|
|
||||||
#include "utils/string.hpp"
|
|
||||||
|
|
||||||
using namespace demo_utils;
|
|
||||||
|
|
||||||
namespace // checked client num class
|
|
||||||
{
|
|
||||||
inline constexpr std::size_t SV_MAX_CLIENTS =
|
|
||||||
sizeof(game::mp::serverStatic_t::clients) / sizeof(game::mp::serverStatic_t::clients[0]);
|
|
||||||
|
|
||||||
class sv_client_num_t
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
explicit sv_client_num_t(std::size_t client_num);
|
|
||||||
explicit sv_client_num_t(std::string_view client_id);
|
|
||||||
|
|
||||||
[[nodiscard]] bool valid() const;
|
|
||||||
[[nodiscard]] std::uint8_t value() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
[[nodiscard]] static std::optional<std::uint8_t> check_client_num(std::size_t client_num);
|
|
||||||
[[nodiscard]] static std::optional<std::uint8_t> parse_client_id(std::string_view str);
|
|
||||||
|
|
||||||
const std::optional<std::uint8_t> client_num_;
|
|
||||||
};
|
|
||||||
|
|
||||||
sv_client_num_t::sv_client_num_t(std::size_t client_num)
|
|
||||||
: client_num_(check_client_num(client_num))
|
|
||||||
{
|
|
||||||
assert(valid());
|
|
||||||
}
|
|
||||||
|
|
||||||
sv_client_num_t::sv_client_num_t(std::string_view client_id)
|
|
||||||
: client_num_(parse_client_id(client_id))
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool sv_client_num_t::valid() const
|
|
||||||
{
|
|
||||||
return client_num_.has_value();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::uint8_t sv_client_num_t::value() const
|
|
||||||
{
|
|
||||||
assert(valid());
|
|
||||||
|
|
||||||
return client_num_.value();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::uint8_t> sv_client_num_t::check_client_num(std::size_t client_num)
|
|
||||||
{
|
|
||||||
if (client_num >= SV_MAX_CLIENTS)
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (client_num >= static_cast<std::size_t>(game::mp::svs->clientCount))
|
|
||||||
{
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(game::mp::svs->clientCount == game::Dvar_FindVar("sv_maxclients")->current.integer);
|
|
||||||
assert(client_num == static_cast<std::size_t>(game::mp::svs->clients[client_num].gentity->s.number));
|
|
||||||
|
|
||||||
return static_cast<std::uint8_t>(client_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::uint8_t> sv_client_num_t::parse_client_id(std::string_view str)
|
|
||||||
{
|
|
||||||
if (str.starts_with("pid"))
|
|
||||||
{
|
|
||||||
const auto pid = std::string_view(str.begin() + 3, str.end());
|
|
||||||
const auto all_digits = std::all_of(pid.begin(), pid.end(), [](unsigned char c)
|
|
||||||
{
|
|
||||||
return std::isdigit(c);
|
|
||||||
});
|
|
||||||
|
|
||||||
auto value = std::numeric_limits<std::size_t>::max();
|
|
||||||
const auto ec = std::from_chars(pid.data(), pid.data() + pid.size(), value).ec;
|
|
||||||
|
|
||||||
if (all_digits && ec == std::errc{})
|
|
||||||
{
|
|
||||||
const auto client_num = check_client_num(value);
|
|
||||||
if (client_num)
|
|
||||||
{
|
|
||||||
return client_num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& cl : game::mp::svs->clients)
|
|
||||||
{
|
|
||||||
if (cl.header.state != game::CS_ACTIVE || str != utils::string::strip(cl.name, true))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto client_num = check_client_num(cl.gentity->s.number);
|
|
||||||
if (client_num)
|
|
||||||
{
|
|
||||||
return client_num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace // server demos class
|
|
||||||
{
|
|
||||||
game::dvar_t* sv_demos;
|
|
||||||
game::dvar_t* sv_demo_autorecord;
|
|
||||||
|
|
||||||
bool sv_execute_demo_start_command_internal(sv_client_num_t client_num, std::string_view file_name, bool overwrite);
|
|
||||||
|
|
||||||
class server_rotation_t
|
|
||||||
{
|
|
||||||
struct data_t
|
|
||||||
{
|
|
||||||
bool svr_restart_bit{};
|
|
||||||
std::size_t map_hash{};
|
|
||||||
std::size_t gametype_hash{};
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
[[nodiscard]] bool has_changed()
|
|
||||||
{
|
|
||||||
const auto svr_restart_bit = static_cast<bool>(game::mp::svs->snapFlagServerBit & 4);
|
|
||||||
const auto has_value = data_.has_value();
|
|
||||||
|
|
||||||
if (!has_value || data_->svr_restart_bit != svr_restart_bit)
|
|
||||||
{
|
|
||||||
const auto dvar_mapname = get_mapname(false);
|
|
||||||
const auto dvar_gametype = get_gametype(false);
|
|
||||||
|
|
||||||
if (dvar_mapname.empty() || dvar_gametype.empty())
|
|
||||||
{
|
|
||||||
console::error("the server demo recording code may not function properly if dvars mapname or g_gametype are not available\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto map_hash = std::hash<std::string_view>()(dvar_mapname);
|
|
||||||
const auto gametype_hash = std::hash<std::string_view>()(dvar_gametype);
|
|
||||||
|
|
||||||
if (!has_value || (data_->map_hash != map_hash || data_->gametype_hash != gametype_hash))
|
|
||||||
{
|
|
||||||
data_.emplace(data_t{
|
|
||||||
.svr_restart_bit = svr_restart_bit,
|
|
||||||
.map_hash = map_hash,
|
|
||||||
.gametype_hash = gametype_hash
|
|
||||||
});
|
|
||||||
|
|
||||||
return has_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// server has not rotated despite restart bit change
|
|
||||||
data_->svr_restart_bit = svr_restart_bit;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::optional<data_t> data_;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct client_data_t
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
struct stored_times_t
|
|
||||||
{
|
|
||||||
std::int32_t first_svr_time{};
|
|
||||||
std::int32_t cur_svr_time{};
|
|
||||||
std::optional<std::int32_t> last_conn_time;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
|
||||||
[[nodiscard]] bool is_buffer_active() const
|
|
||||||
{
|
|
||||||
return buffer_active;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool is_recording() const
|
|
||||||
{
|
|
||||||
return file_active;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool buffer_active{};
|
|
||||||
bool file_active{};
|
|
||||||
stored_times_t times;
|
|
||||||
buffer_t buffer;
|
|
||||||
std::ofstream file;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct sv_demo_recordings_t
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
sv_demo_recordings_t() = default;
|
|
||||||
~sv_demo_recordings_t();
|
|
||||||
sv_demo_recordings_t(const sv_demo_recordings_t&) = delete;
|
|
||||||
sv_demo_recordings_t(sv_demo_recordings_t&&) noexcept = delete;
|
|
||||||
sv_demo_recordings_t& operator=(const sv_demo_recordings_t&) = delete;
|
|
||||||
sv_demo_recordings_t& operator=(sv_demo_recordings_t&&) noexcept = delete;
|
|
||||||
|
|
||||||
[[nodiscard]] bool is_recording(sv_client_num_t client_num) const;
|
|
||||||
[[nodiscard]] bool start(sv_client_num_t client_num, const std::filesystem::path& path);
|
|
||||||
[[nodiscard]] bool stop(sv_client_num_t client_num);
|
|
||||||
|
|
||||||
void write_data(sv_client_num_t client_num, const game::mp::client_t& cl,
|
|
||||||
std::span<const std::uint8_t> network_data, bool client_loading);
|
|
||||||
void shutdown_watcher();
|
|
||||||
|
|
||||||
private:
|
|
||||||
[[nodiscard]] const client_data_t& get_client(sv_client_num_t client_num) const;
|
|
||||||
[[nodiscard]] client_data_t& get_client(sv_client_num_t client_num);
|
|
||||||
|
|
||||||
bool stop_internal(client_data_t& client, bool reset_client);
|
|
||||||
bool update_state(sv_client_num_t client_num, std::int32_t last_conn_time, bool client_loading);
|
|
||||||
void process_old_data();
|
|
||||||
|
|
||||||
server_rotation_t svr_rotation_data_;
|
|
||||||
std::array<client_data_t, SV_MAX_CLIENTS> client_data_{};
|
|
||||||
} demos;
|
|
||||||
|
|
||||||
sv_demo_recordings_t::~sv_demo_recordings_t()
|
|
||||||
{
|
|
||||||
process_old_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
const client_data_t& sv_demo_recordings_t::get_client(sv_client_num_t client_num) const
|
|
||||||
{
|
|
||||||
assert(client_num.valid());
|
|
||||||
|
|
||||||
return client_data_[client_num.value()];
|
|
||||||
}
|
|
||||||
|
|
||||||
client_data_t& sv_demo_recordings_t::get_client(sv_client_num_t client_num)
|
|
||||||
{
|
|
||||||
assert(client_num.valid());
|
|
||||||
|
|
||||||
return client_data_[client_num.value()];
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sv_demo_recordings_t::is_recording(sv_client_num_t client_num) const
|
|
||||||
{
|
|
||||||
return client_num.valid() && get_client(client_num).is_recording();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sv_demo_recordings_t::start(sv_client_num_t client_num, const std::filesystem::path& path)
|
|
||||||
{
|
|
||||||
if (!client_num.valid())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& client = get_client(client_num);
|
|
||||||
|
|
||||||
if (client.is_buffer_active())
|
|
||||||
{
|
|
||||||
if (!client.is_recording())
|
|
||||||
{
|
|
||||||
std::ofstream file(path, std::ios::binary);
|
|
||||||
|
|
||||||
if (file.good() && file.is_open())
|
|
||||||
{
|
|
||||||
const auto buffer = client.buffer.get();
|
|
||||||
|
|
||||||
client.file = std::move(file);
|
|
||||||
client.file.write(reinterpret_cast<const char*>(buffer.data()), buffer.size());
|
|
||||||
client.file.flush();
|
|
||||||
client.file_active = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console::error("client pid %d cannot be recorded at this time; sv_demos was possibly enabled too late!\n", client_num.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sv_demo_recordings_t::stop_internal(client_data_t& client, bool reset_client)
|
|
||||||
{
|
|
||||||
if (client.is_recording())
|
|
||||||
{
|
|
||||||
write_general_footer(client.file, client.times.first_svr_time, client.times.cur_svr_time);
|
|
||||||
write_end_of_file(client.file);
|
|
||||||
|
|
||||||
client.file.flush();
|
|
||||||
client.file.close();
|
|
||||||
client.file_active = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reset_client)
|
|
||||||
{
|
|
||||||
client.buffer_active = {};
|
|
||||||
client.times = {};
|
|
||||||
client.buffer.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.file.good() && !client.file.is_open();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sv_demo_recordings_t::stop(sv_client_num_t client_num)
|
|
||||||
{
|
|
||||||
return client_num.valid() && stop_internal(get_client(client_num), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void sv_demo_recordings_t::process_old_data()
|
|
||||||
{
|
|
||||||
for (auto& client : client_data_)
|
|
||||||
{
|
|
||||||
stop_internal(client, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
svr_rotation_data_ = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void sv_demo_recordings_t::shutdown_watcher()
|
|
||||||
{
|
|
||||||
auto execute_on_shutdown = [this, sv_loaded = false]() mutable
|
|
||||||
{
|
|
||||||
if (!sv_loaded && game::SV_Loaded())
|
|
||||||
{
|
|
||||||
sv_loaded = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sv_loaded && !game::SV_Loaded())
|
|
||||||
{
|
|
||||||
sv_loaded = false;
|
|
||||||
process_old_data();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
scheduler::loop(execute_on_shutdown, scheduler::main);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sv_demo_recordings_t::update_state(sv_client_num_t client_num, std::int32_t last_conn_time, bool client_loading)
|
|
||||||
{
|
|
||||||
// handle server map change: close files and reset data for all clients; should ignore fast restarts
|
|
||||||
if (svr_rotation_data_.has_changed())
|
|
||||||
{
|
|
||||||
process_old_data();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& client = get_client(client_num);
|
|
||||||
if (!client_loading && !client.is_buffer_active())
|
|
||||||
{
|
|
||||||
// sv_demos was possibly enabled too late for this player for this match!
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle client last connect time change: close file and reset data
|
|
||||||
if (!client.times.last_conn_time || *client.times.last_conn_time != last_conn_time)
|
|
||||||
{
|
|
||||||
if (client.times.last_conn_time)
|
|
||||||
{
|
|
||||||
stop_internal(client, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.times.last_conn_time = last_conn_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
// write general, mod and map headers to the buffer once
|
|
||||||
if (!client.is_buffer_active())
|
|
||||||
{
|
|
||||||
// reserve memory (low size if auto recording is enabled, because then the demo writes almost directly to disk)
|
|
||||||
const auto reserve_size = (sv_demo_autorecord && sv_demo_autorecord->current.enabled) ? 8192 : 512 * 1024;
|
|
||||||
client.buffer.reserve_memory(reserve_size);
|
|
||||||
|
|
||||||
sv_write_general_header(client.buffer, game::mp::svs->clients[client_num.value()].name);
|
|
||||||
write_mod_header(client.buffer);
|
|
||||||
|
|
||||||
if (write_map_header(client.buffer))
|
|
||||||
{
|
|
||||||
client.buffer_active = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// store first and current server times to keep track of demo length
|
|
||||||
client.times.cur_svr_time = game::mp::svs->time;
|
|
||||||
if (!client_loading && !client.times.first_svr_time && client.times.cur_svr_time)
|
|
||||||
{
|
|
||||||
client.times.first_svr_time = client.times.cur_svr_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if not already recording, start demo for client when auto recording is enabled
|
|
||||||
if (!client.is_recording() && sv_demo_autorecord && sv_demo_autorecord->current.enabled)
|
|
||||||
{
|
|
||||||
if (!sv_execute_demo_start_command_internal(client_num, std::string_view{}, false))
|
|
||||||
{
|
|
||||||
console::error("failed to start demo automatically for client num %d\n", client_num.value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sv_demo_recordings_t::write_data(sv_client_num_t client_num,
|
|
||||||
const game::mp::client_t& cl, std::span<const std::uint8_t> network_data, bool client_loading)
|
|
||||||
{
|
|
||||||
if (!client_num.valid() || !update_state(client_num, cl.lastConnectTime, client_loading))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto write_data_internal = [&cl, network_data, client_loading](auto& output)
|
|
||||||
{
|
|
||||||
const auto send_msg_count = static_cast<std::size_t>(cl.header.sendMessageCount);
|
|
||||||
const auto svr_msg_sequence = static_cast<std::size_t>(cl.header.netchan.outgoingSequence);
|
|
||||||
|
|
||||||
if (!client_loading)
|
|
||||||
{
|
|
||||||
sv_write_predicted_data(output, cl.gentity->client->ps, send_msg_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
write_id_and_size(output, 4 + network_data.size(), demo_data_id::network_data);
|
|
||||||
output.write(reinterpret_cast<const char*>(&svr_msg_sequence), 4);
|
|
||||||
output.write(reinterpret_cast<const char*>(network_data.data()), network_data.size());
|
|
||||||
};
|
|
||||||
|
|
||||||
auto& client = get_client(client_num);
|
|
||||||
|
|
||||||
if (client.is_buffer_active())
|
|
||||||
{
|
|
||||||
write_data_internal(client.buffer);
|
|
||||||
}
|
|
||||||
if (client.is_recording())
|
|
||||||
{
|
|
||||||
write_data_internal(client.file);
|
|
||||||
|
|
||||||
if (static_cast<std::size_t>(cl.header.sendMessageCount) % 512 == 0)
|
|
||||||
{
|
|
||||||
client.file.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace // hooks
|
|
||||||
{
|
|
||||||
utils::hook::detour SV_Netchan_Transmit_hook;
|
|
||||||
|
|
||||||
bool sv_capture_data(game::mp::client_t& cl, const char* data, std::int32_t length)
|
|
||||||
{
|
|
||||||
const auto valid_player = cl.gentity && cl.gentity->client && cl.testClient == game::TC_NONE
|
|
||||||
&& (cl.header.state == game::CS_CLIENTLOADING || cl.header.state == game::CS_ACTIVE);
|
|
||||||
|
|
||||||
if (sv_demos && sv_demos->current.enabled && valid_player)
|
|
||||||
{
|
|
||||||
const auto client_num = sv_client_num_t(static_cast<std::size_t>(cl.gentity->s.number));
|
|
||||||
|
|
||||||
if (!client_num.valid())
|
|
||||||
{
|
|
||||||
console::error("invalid client num %d\n", cl.gentity->s.number);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const auto client_loading = (cl.header.state == game::CS_CLIENTLOADING);
|
|
||||||
const auto size = static_cast<std::size_t>(length);
|
|
||||||
|
|
||||||
if ((!client_loading || cl.gamestateMessageNum == cl.header.netchan.outgoingSequence) && size < MAX_SIZE)
|
|
||||||
{
|
|
||||||
const std::span<const std::uint8_t> network_data(reinterpret_cast<const std::uint8_t*>(data), size);
|
|
||||||
demos.write_data(client_num, cl, network_data, client_loading);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console::warn("invalid data for client num %d, message count %d, message size %zu\n",
|
|
||||||
client_num.value(), cl.header.sendMessageCount, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SV_Netchan_Transmit_hook.invoke<bool>(&cl, data, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace // command execution
|
|
||||||
{
|
|
||||||
bool sv_execute_demo_start_command_internal(sv_client_num_t client_num, std::string_view file_name, bool overwrite)
|
|
||||||
{
|
|
||||||
if (game::Live_SyncOnlineDataFlags(0))
|
|
||||||
{
|
|
||||||
console::error("server must be initialized to record a demo\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto* sv_running = game::Dvar_FindVar("sv_running"); !sv_running || !sv_running->current.enabled)
|
|
||||||
{
|
|
||||||
console::error("server must be online to record a demo\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sv_demos || !sv_demos->current.enabled)
|
|
||||||
{
|
|
||||||
console::error("cannot record a demo with sv_demos disabled\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!client_num.valid())
|
|
||||||
{
|
|
||||||
console::error("invalid client num\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto state = game::mp::svs->clients[client_num.value()].header.state;
|
|
||||||
if (state != game::CS_CLIENTLOADING && state != game::CS_ACTIVE)
|
|
||||||
{
|
|
||||||
console::error("client needs to be fully connected\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto client_type = game::mp::svs->clients[client_num.value()].testClient;
|
|
||||||
if (client_type != game::TC_NONE)
|
|
||||||
{
|
|
||||||
console::error("can only record actual players\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (demos.is_recording(client_num))
|
|
||||||
{
|
|
||||||
console::error("already recording client\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto opt_dir_path = sv_create_demo_directory();
|
|
||||||
if (!opt_dir_path)
|
|
||||||
{
|
|
||||||
console::error("could not create demo directory\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& cl = game::mp::svs->clients[client_num.value()];
|
|
||||||
const auto empty_guid = (cl.playerGuid[0] == '\0');
|
|
||||||
if (empty_guid)
|
|
||||||
{
|
|
||||||
console::warn("player guid appears empty\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto opt_server_path = (file_name.empty())
|
|
||||||
? sv_create_path_server_demo(*opt_dir_path, (!empty_guid) ? cl.playerGuid : utils::string::strip(cl.name, true))
|
|
||||||
: sv_create_path_server_demo(*opt_dir_path, file_name, overwrite);
|
|
||||||
|
|
||||||
if (!opt_server_path)
|
|
||||||
{
|
|
||||||
console::error("could not create demo file\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!demos.start(client_num, *opt_server_path))
|
|
||||||
{
|
|
||||||
console::error("could not create demo file %s\n", opt_server_path->string().c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void sv_execute_demo_start_command(const command::params& params)
|
|
||||||
{
|
|
||||||
if (params.size() < 2 || params.size() > 4 || (params.size() == 4 && std::string_view(params.get(3)) != "overwrite"))
|
|
||||||
{
|
|
||||||
console::info("usage: sv_demostart <client_num | player_name>, <file_name>(optional), overwrite(optional)\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(params.size() != 4 || (params.size() == 4 && std::string_view(params.get(3)) == "overwrite"));
|
|
||||||
sv_execute_demo_start_command_internal(
|
|
||||||
sv_client_num_t(params.get(1)), (params.size() >= 3) ? params.get(2) : std::string_view{}, (params.size() == 4));
|
|
||||||
}
|
|
||||||
|
|
||||||
void sv_execute_demo_stop_command(const command::params& params)
|
|
||||||
{
|
|
||||||
if (params.size() != 2)
|
|
||||||
{
|
|
||||||
console::info("usage: sv_demostop <client_num | player_name>\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sv_client_num_t client_num(params.get(1));
|
|
||||||
if (!client_num.valid())
|
|
||||||
{
|
|
||||||
console::error("invalid client num %s\n", params.get(1));
|
|
||||||
}
|
|
||||||
else if (!demos.stop(client_num))
|
|
||||||
{
|
|
||||||
console::error("could not stop demo\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace demo_sv_recording
|
|
||||||
{
|
|
||||||
bool sv_recording(std::size_t client_num)
|
|
||||||
{
|
|
||||||
return demos.is_recording(sv_client_num_t(client_num));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sv_startrecord(std::size_t client_num, std::string_view file_name, bool overwrite)
|
|
||||||
{
|
|
||||||
return sv_execute_demo_start_command_internal(sv_client_num_t(client_num), file_name, overwrite);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool sv_stoprecord(std::size_t client_num)
|
|
||||||
{
|
|
||||||
return demos.stop(sv_client_num_t(client_num));
|
|
||||||
}
|
|
||||||
|
|
||||||
class component final : public component_interface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void post_unpack() override
|
|
||||||
{
|
|
||||||
if (!game::environment::is_dedi())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check run-time address assertions
|
|
||||||
check_address_assertions();
|
|
||||||
|
|
||||||
// capture outgoing packets to client
|
|
||||||
SV_Netchan_Transmit_hook.create(game::SV_Netchan_Transmit, sv_capture_data);
|
|
||||||
|
|
||||||
// execute server demo code based on this value; if it's is enabled mid-match,
|
|
||||||
// then the demos recorded during that match (if any) are likely corrupt!
|
|
||||||
sv_demos = game::Dvar_RegisterBool(
|
|
||||||
"sv_demos", false, game::DVAR_FLAG_NONE, "Enable server demos");
|
|
||||||
|
|
||||||
// add support to auto record all players
|
|
||||||
sv_demo_autorecord = game::Dvar_RegisterBool(
|
|
||||||
"sv_demoautorecord", false, game::DVAR_FLAG_NONE,
|
|
||||||
"Automatically start recording a demo for each connected client.");
|
|
||||||
|
|
||||||
// add console command support to record server demos
|
|
||||||
command::add("sv_demostart", sv_execute_demo_start_command);
|
|
||||||
command::add("sv_demostop", sv_execute_demo_stop_command);
|
|
||||||
|
|
||||||
scheduler::on_game_initialized([]()
|
|
||||||
{
|
|
||||||
demos.shutdown_watcher();
|
|
||||||
|
|
||||||
// check if demo directory exists / could be created
|
|
||||||
if (!sv_can_create_demo_directory())
|
|
||||||
{
|
|
||||||
console::error("could not create demo directory\n");
|
|
||||||
}
|
|
||||||
}, scheduler::main);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_COMPONENT(demo_sv_recording::component)
|
|
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace demo_sv_recording
|
|
||||||
{
|
|
||||||
[[nodiscard]] bool sv_recording(std::size_t client_num);
|
|
||||||
[[nodiscard]] bool sv_startrecord(std::size_t client_num, std::string_view file_name = {}, bool overwrite = {});
|
|
||||||
[[nodiscard]] bool sv_stoprecord(std::size_t client_num);
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
#include "std_include.hpp"
|
|
||||||
#include "loader/component_loader.hpp"
|
|
||||||
|
|
||||||
#include "demo_playback.hpp"
|
|
||||||
|
|
||||||
#include "fps.hpp"
|
|
||||||
#include "game/game.hpp"
|
|
||||||
#include "utils/hook.hpp"
|
|
||||||
#include "utils/string.hpp"
|
|
||||||
|
|
||||||
namespace demo_timescale
|
|
||||||
{
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
utils::hook::detour Com_TimeScaleMsec_hook;
|
|
||||||
|
|
||||||
game::dvar_t* demo_timescale;
|
|
||||||
|
|
||||||
void generate_pattern(std::array<std::uint8_t, 1000>& pattern, std::size_t ones_count, std::size_t zeros_count)
|
|
||||||
{
|
|
||||||
assert(ones_count + zeros_count == pattern.size());
|
|
||||||
|
|
||||||
for (std::size_t i = 0, zeros = 1, ones = 1; i < pattern.size(); ++i)
|
|
||||||
{
|
|
||||||
if (ones * zeros_count < zeros * ones_count)
|
|
||||||
{
|
|
||||||
++ones;
|
|
||||||
pattern[i] = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
++zeros;
|
|
||||||
pattern[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(std::accumulate(pattern.begin(), pattern.end(), 0) > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void calculate_pattern(std::array<std::uint8_t, 1000>& pattern, float fps, float timescale)
|
|
||||||
{
|
|
||||||
// Com_TimeScaleMsec is called once per frame, so the number of calls it takes to advance 1000 ms
|
|
||||||
// can be calculated by using the following formula: fps / timescale
|
|
||||||
|
|
||||||
// example: 500 fps and 0.01 timescale -> 500 / 0.01 = 50'000
|
|
||||||
// a pattern needs to be generated where 1000 * 1ms and 49'000 * 0ms are interleaved,
|
|
||||||
// and fit in an array of size 1000
|
|
||||||
|
|
||||||
const auto call_count_per_sec = static_cast<std::size_t>(std::clamp(fps / timescale, 1000.0f, 1'000'000.0f));
|
|
||||||
const auto ones_count = static_cast<std::size_t>(pattern.size() / (call_count_per_sec / static_cast<float>(pattern.size())));
|
|
||||||
const auto zeros_count = pattern.size() - ones_count;
|
|
||||||
|
|
||||||
generate_pattern(pattern, ones_count, zeros_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::int32_t Com_TimeScaleMsec_stub(std::int32_t msec)
|
|
||||||
{
|
|
||||||
if (!demo_playback::playing() || !demo_timescale || demo_timescale->current.value == 1.0f)
|
|
||||||
{
|
|
||||||
return Com_TimeScaleMsec_hook.invoke<std::int32_t>(msec);
|
|
||||||
}
|
|
||||||
if (demo_timescale->current.value == 0.0f)
|
|
||||||
{
|
|
||||||
return 0; // pause game
|
|
||||||
}
|
|
||||||
|
|
||||||
// the code below generates a pattern of interleaved 0s and 1s based on calculated avg fps
|
|
||||||
// a new pattern is generated every 1000 frames, or after timescale or maxfps changes
|
|
||||||
// the pattern determines the speed at which the game advances
|
|
||||||
|
|
||||||
const auto timescale = demo_timescale->current.value;
|
|
||||||
const auto avg_fps = fps::get_avg_fps();
|
|
||||||
const auto frame_time = timescale * 1000.0f / avg_fps;
|
|
||||||
const auto* com_maxfps = game::Dvar_FindVar("com_maxfps");
|
|
||||||
const auto maxfps = (com_maxfps) ? com_maxfps->current.integer : static_cast<std::int32_t>(avg_fps);
|
|
||||||
|
|
||||||
static auto last_timescale = timescale;
|
|
||||||
static auto last_maxfps = maxfps;
|
|
||||||
static std::size_t pattern_index;
|
|
||||||
static std::array<std::uint8_t, 1000> pattern;
|
|
||||||
|
|
||||||
if (last_timescale != timescale || last_maxfps != maxfps)
|
|
||||||
{
|
|
||||||
last_timescale = timescale;
|
|
||||||
last_maxfps = maxfps;
|
|
||||||
|
|
||||||
// update pattern using the maxfps instead of avg fps for now
|
|
||||||
calculate_pattern(pattern, static_cast<float>(maxfps), timescale);
|
|
||||||
|
|
||||||
// update the pattern again in the near future when the average fps is more accurate
|
|
||||||
pattern_index = 95 * pattern.size() / 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame_time > 1.0f)
|
|
||||||
{
|
|
||||||
const auto i_frame_time = static_cast<std::int32_t>(frame_time);
|
|
||||||
const auto ones_count = static_cast<std::size_t>(frame_time * 1000 - i_frame_time * 1000);
|
|
||||||
if (ones_count <= 1)
|
|
||||||
{
|
|
||||||
return i_frame_time;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pattern_index % pattern.size() == 0)
|
|
||||||
{
|
|
||||||
const auto zeros_count = pattern.size() - ones_count;
|
|
||||||
generate_pattern(pattern, ones_count, zeros_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
return i_frame_time + pattern[pattern_index++ % pattern.size()];
|
|
||||||
}
|
|
||||||
else if (pattern_index % pattern.size() == 0)
|
|
||||||
{
|
|
||||||
calculate_pattern(pattern, avg_fps, timescale);
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance (1ms) or pause (0ms) based on the pattern
|
|
||||||
return pattern[pattern_index++ % pattern.size()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class component final : public component_interface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
void post_unpack() override
|
|
||||||
{
|
|
||||||
if (!game::environment::is_mp())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// add timescale support for demo playback
|
|
||||||
Com_TimeScaleMsec_hook.create(game::Com_TimeScaleMsec, Com_TimeScaleMsec_stub);
|
|
||||||
demo_timescale = game::Dvar_RegisterFloat(
|
|
||||||
"demotimescale", 1.0f, 0.0f, 1000.0f, game::DVAR_FLAG_NONE, "Set playback speed for demos");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
REGISTER_COMPONENT(demo_timescale::component)
|
|
File diff suppressed because it is too large
Load Diff
@ -1,217 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "game/game.hpp"
|
|
||||||
|
|
||||||
namespace demo_utils
|
|
||||||
{
|
|
||||||
namespace globals
|
|
||||||
{
|
|
||||||
inline constexpr std::string_view DEMO_CODE_VERSION = "v0.0.1";
|
|
||||||
inline constexpr std::string_view DEMO_EXTENSION = ".dm_iw6";
|
|
||||||
inline constexpr std::size_t MAX_SIZE = 131'072;
|
|
||||||
}
|
|
||||||
using namespace globals;
|
|
||||||
|
|
||||||
namespace types
|
|
||||||
{
|
|
||||||
enum struct demo_data_id : std::uint8_t
|
|
||||||
{
|
|
||||||
mod_header = 0,
|
|
||||||
map_header = 1,
|
|
||||||
network_data = 2,
|
|
||||||
predicted_data = 3,
|
|
||||||
update_gamestate_data = 4,
|
|
||||||
gen_header = 5,
|
|
||||||
gen_footer = 6,
|
|
||||||
|
|
||||||
eof = 31,
|
|
||||||
|
|
||||||
unused_flag_1 = 0b0010'0000,
|
|
||||||
unused_flag_2 = 0b0100'0000,
|
|
||||||
one_byte_flag = 0b1000'0000,
|
|
||||||
|
|
||||||
flags = (unused_flag_1 | unused_flag_2 | one_byte_flag)
|
|
||||||
};
|
|
||||||
|
|
||||||
demo_data_id operator|(demo_data_id lhs, demo_data_id rhs);
|
|
||||||
demo_data_id operator&(demo_data_id lhs, demo_data_id rhs);
|
|
||||||
demo_data_id operator~(demo_data_id value);
|
|
||||||
|
|
||||||
enum struct predicted_data_id : std::uint8_t
|
|
||||||
{
|
|
||||||
player = 0,
|
|
||||||
vehicle_gryphon = 1,
|
|
||||||
vehicle_helo_pilot = 2,
|
|
||||||
vehicle_odin = 3,
|
|
||||||
vehicle_unknown = 4,
|
|
||||||
vehicle_helo_pilot_turret_fire = 5
|
|
||||||
};
|
|
||||||
|
|
||||||
struct persistent_data_t
|
|
||||||
{
|
|
||||||
// ksr -> killstreak reward
|
|
||||||
std::int32_t cur_kill_count{};
|
|
||||||
std::int32_t req_kill_count_next_ksr{};
|
|
||||||
std::int32_t unknown1{ 1 };
|
|
||||||
std::int32_t active_ksr_count{};
|
|
||||||
std::int32_t unknown2{ -1 };
|
|
||||||
std::int32_t active_ksr_flags{};
|
|
||||||
std::int32_t unknown3{};
|
|
||||||
std::int32_t first_ksr_icon{};
|
|
||||||
std::int32_t second_ksr_icon{};
|
|
||||||
std::int32_t third_ksr_icon{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct packed_gamestate_sizes
|
|
||||||
{
|
|
||||||
std::uint64_t string_data : 20{};
|
|
||||||
std::uint64_t string_offsets : 16{};
|
|
||||||
std::uint64_t unused : 27{};
|
|
||||||
std::uint64_t compressed : 1{};
|
|
||||||
};
|
|
||||||
static_assert(sizeof(packed_gamestate_sizes) == 8);
|
|
||||||
|
|
||||||
struct predicted_data_t
|
|
||||||
{
|
|
||||||
game::vec3_t viewangles{};
|
|
||||||
game::ClientArchiveData cad{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct gamestate_t
|
|
||||||
{
|
|
||||||
std::uint32_t svr_cmd_seq{};
|
|
||||||
game::gameState_t data{};
|
|
||||||
};
|
|
||||||
|
|
||||||
class buffer_t
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
[[nodiscard]] std::size_t size() const;
|
|
||||||
[[nodiscard]] std::span<const std::uint8_t> get() const;
|
|
||||||
void reserve_memory(std::size_t size);
|
|
||||||
void write(const char* src, std::size_t size);
|
|
||||||
void clear();
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::uint8_t> buffer_;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
using namespace types;
|
|
||||||
|
|
||||||
namespace gen_utilities
|
|
||||||
{
|
|
||||||
std::string_view get_dvar_string(std::string_view dvar_name, bool valid_string);
|
|
||||||
std::string_view get_mod_directory();
|
|
||||||
std::string_view get_base_path(bool default_string);
|
|
||||||
std::string_view get_mapname(bool default_string);
|
|
||||||
std::string get_shortened_mapname_lowercase(bool default_string);
|
|
||||||
std::string get_mapname_lowercase(bool default_string);
|
|
||||||
std::string_view get_gametype(bool default_string);
|
|
||||||
std::string get_gametype_lowercase(bool default_string);
|
|
||||||
//std::string get_datetime();
|
|
||||||
}
|
|
||||||
using namespace gen_utilities;
|
|
||||||
|
|
||||||
namespace file_directory
|
|
||||||
{
|
|
||||||
std::optional<std::filesystem::path> create_directory_user_demo();
|
|
||||||
std::optional<std::filesystem::path> create_directory_auto_demo();
|
|
||||||
bool can_create_demo_directories();
|
|
||||||
std::optional<std::filesystem::path> create_path_user_demo(const std::filesystem::path& dir_path);
|
|
||||||
std::optional<std::filesystem::path> create_path_user_demo(
|
|
||||||
const std::filesystem::path& dir_path, std::string_view demo_name, bool overwrite);
|
|
||||||
std::optional<std::filesystem::path> create_path_auto_demo(const std::filesystem::path& dir_path);
|
|
||||||
}
|
|
||||||
using namespace file_directory;
|
|
||||||
|
|
||||||
namespace file_directory_server
|
|
||||||
{
|
|
||||||
std::optional<std::filesystem::path> sv_create_demo_directory();
|
|
||||||
bool sv_can_create_demo_directory();
|
|
||||||
std::optional<std::filesystem::path> sv_create_path_server_demo(
|
|
||||||
const std::filesystem::path& dir_path, std::string_view client_id);
|
|
||||||
std::optional<std::filesystem::path> sv_create_path_server_demo(
|
|
||||||
const std::filesystem::path& dir_path, std::string_view demo_name, bool overwrite);
|
|
||||||
}
|
|
||||||
using namespace file_directory_server;
|
|
||||||
|
|
||||||
namespace serialization
|
|
||||||
{
|
|
||||||
void write_id_and_size(buffer_t& output, std::size_t size, demo_data_id id);
|
|
||||||
void write_id_and_size(std::ofstream& output, std::size_t size, demo_data_id id);
|
|
||||||
|
|
||||||
void write_network_data(buffer_t& output, std::span<const std::uint8_t> network_data);
|
|
||||||
void write_network_data(std::ofstream& output, std::span<const std::uint8_t> network_data);
|
|
||||||
|
|
||||||
//void write_predicted_player_data(buffer_t& output, const game::ClientArchiveData& cad,
|
|
||||||
//const game::vec3_t& viewangles, std::uint8_t cad_index, predicted_data_id id);
|
|
||||||
//void write_predicted_player_data(std::ofstream& output, const game::ClientArchiveData& cad,
|
|
||||||
//const game::vec3_t& viewangles, std::uint8_t cad_index, predicted_data_id id);
|
|
||||||
|
|
||||||
//void write_predicted_vehicle_data(buffer_t& output, const game::ClientArchiveData& cad,
|
|
||||||
//const game::vec3_t& viewangles, std::uint8_t cad_index, predicted_data_id id);
|
|
||||||
//void write_predicted_vehicle_data(std::ofstream& output, const game::ClientArchiveData& cad,
|
|
||||||
//const game::vec3_t& viewangles, std::uint8_t cad_index, predicted_data_id id);
|
|
||||||
|
|
||||||
void write_helo_pilot_turret_fire(buffer_t& output, std::uint8_t fire_count);
|
|
||||||
void write_helo_pilot_turret_fire(std::ofstream& output, std::uint8_t fire_count);
|
|
||||||
|
|
||||||
void write_mod_header(buffer_t& output);
|
|
||||||
void write_mod_header(std::ofstream& output);
|
|
||||||
|
|
||||||
bool write_map_header(buffer_t& output);
|
|
||||||
bool write_map_header(std::ofstream& output);
|
|
||||||
|
|
||||||
bool write_gamestate_data(buffer_t& output);
|
|
||||||
bool write_gamestate_data(std::ofstream& output);
|
|
||||||
|
|
||||||
void write_predicted_data(buffer_t& output);
|
|
||||||
void write_predicted_data(std::ofstream& output);
|
|
||||||
|
|
||||||
void write_general_header(buffer_t& output);
|
|
||||||
void write_general_header(std::ofstream& output);
|
|
||||||
|
|
||||||
//void write_general_footer(buffer_t& output, std::int32_t first_svr_time, std::int32_t last_svr_time);
|
|
||||||
void write_general_footer(std::ofstream& output, std::int32_t first_svr_time, std::int32_t last_svr_time);
|
|
||||||
|
|
||||||
//void write_end_of_file(buffer_t& output);
|
|
||||||
void write_end_of_file(std::ofstream& output);
|
|
||||||
}
|
|
||||||
using namespace serialization;
|
|
||||||
|
|
||||||
namespace serialization_server
|
|
||||||
{
|
|
||||||
void sv_write_predicted_data(buffer_t& output, const game::mp::playerState_t& ps, std::size_t send_msg_count);
|
|
||||||
void sv_write_predicted_data(std::ofstream& output, const game::mp::playerState_t& ps, std::size_t send_msg_count);
|
|
||||||
|
|
||||||
void sv_write_general_header(buffer_t& output, std::string_view player_name);
|
|
||||||
void sv_write_general_header(std::ofstream& output, std::string_view player_name);
|
|
||||||
}
|
|
||||||
using namespace serialization_server;
|
|
||||||
|
|
||||||
namespace deserialization
|
|
||||||
{
|
|
||||||
bool is_gamestate_valid(game::gameState_t& gs, bool sanitize);
|
|
||||||
bool parse_demo(std::ifstream& file, std::vector<std::uint8_t>& buffer,
|
|
||||||
std::size_t& post_map_header_file_offset, std::array<predicted_data_t, 256>& pred_data,
|
|
||||||
std::size_t& pred_data_index, std::optional<gamestate_t>& gs);
|
|
||||||
}
|
|
||||||
using namespace deserialization;
|
|
||||||
|
|
||||||
namespace misc
|
|
||||||
{
|
|
||||||
template <typename To, typename From>
|
|
||||||
requires (std::is_trivially_copyable_v<From>&& std::is_trivially_copyable_v<To>)
|
|
||||||
[[nodiscard]] To cpy_cast(const From* src)
|
|
||||||
{
|
|
||||||
To dst;
|
|
||||||
std::memcpy(&dst, src, sizeof(To));
|
|
||||||
return dst;
|
|
||||||
}
|
|
||||||
|
|
||||||
persistent_data_t& get_persistent_data();
|
|
||||||
void fast_forward_demo(std::uint32_t msec);
|
|
||||||
void check_address_assertions();
|
|
||||||
}
|
|
||||||
using namespace misc;
|
|
||||||
}
|
|
@ -54,8 +54,8 @@ namespace discord
|
|||||||
|
|
||||||
discord_presence.details = utils::string::va("%s on %s", gametype, map);
|
discord_presence.details = utils::string::va("%s on %s", gametype, map);
|
||||||
|
|
||||||
discord_presence.partySize = game::mp::cg->snap != nullptr
|
discord_presence.partySize = game::mp::cgArray->snap != nullptr
|
||||||
? game::mp::cg->snap->numClients
|
? game::mp::cgArray->snap->numClients
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
if (game::Dvar_GetBool("xblive_privatematch"))
|
if (game::Dvar_GetBool("xblive_privatematch"))
|
||||||
@ -65,13 +65,13 @@ namespace discord
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto* host_name = game::mp::cgs->szHostName;
|
auto* host_name = reinterpret_cast<char*>(0x14187EBC4);
|
||||||
utils::string::strip(host_name, host_name, std::strlen(host_name) + 1);
|
utils::string::strip(host_name, host_name, std::strlen(host_name) + 1);
|
||||||
|
|
||||||
discord_presence.state = host_name;
|
discord_presence.state = host_name;
|
||||||
discord_presence.partyMax = party::server_client_count();
|
discord_presence.partyMax = party::server_client_count();
|
||||||
|
|
||||||
std::hash<game::netadr_t> hash_fn;
|
std::hash<game::netadr_s> hash_fn;
|
||||||
static const auto nonce = utils::cryptography::random::get_integer();
|
static const auto nonce = utils::cryptography::random::get_integer();
|
||||||
|
|
||||||
const auto& address = party::get_target();
|
const auto& address = party::get_target();
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
#include "console.hpp"
|
#include "console.hpp"
|
||||||
#include "demo_playback.hpp"
|
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
@ -14,12 +13,6 @@ namespace dvar_cheats
|
|||||||
{
|
{
|
||||||
void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value)
|
void apply_sv_cheats(const game::dvar_t* dvar, const game::DvarSetSource source, game::dvar_value* value)
|
||||||
{
|
{
|
||||||
// return early because sv_cheats is enabled when playing back demos
|
|
||||||
if (demo_playback::playing())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dvar && dvar->name == "sv_cheats"s)
|
if (dvar && dvar->name == "sv_cheats"s)
|
||||||
{
|
{
|
||||||
// if dedi, do not allow internal to change value so servers can allow cheats if they want to
|
// if dedi, do not allow internal to change value so servers can allow cheats if they want to
|
||||||
@ -117,7 +110,7 @@ namespace dvar_cheats
|
|||||||
a.jmp(0x1404F0D74);
|
a.jmp(0x1404F0D74);
|
||||||
});
|
});
|
||||||
|
|
||||||
void cg_set_client_dvar_from_server(const int local_client_num, game::mp::cg_t* cg, const char* dvar_id, const char* value)
|
void cg_set_client_dvar_from_server(const int local_client_num, game::mp::cg_s* cg, const char* dvar_id, const char* value)
|
||||||
{
|
{
|
||||||
if (dvar_id == "cg_fov"s || dvar_id == "com_maxfps"s)
|
if (dvar_id == "cg_fov"s || dvar_id == "com_maxfps"s)
|
||||||
{
|
{
|
||||||
@ -143,7 +136,7 @@ namespace dvar_cheats
|
|||||||
const auto* dvar = game::Scr_GetString(0); // grab the original dvar again since it's never stored on stack
|
const auto* dvar = game::Scr_GetString(0); // grab the original dvar again since it's never stored on stack
|
||||||
const auto* command = utils::string::va("q %s \"%s\"", dvar, value);
|
const auto* command = utils::string::va("q %s \"%s\"", dvar, value);
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(entity_num, 1, command);
|
game::SV_GameSendServerCommand(entity_num, game::SV_CMD_RELIABLE, command);
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a)
|
const auto player_cmd_set_client_dvar = utils::hook::assemble([](utils::hook::assembler& a)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
#include "fastfiles.hpp"
|
|
||||||
#include "command.hpp"
|
#include "command.hpp"
|
||||||
#include "console.hpp"
|
#include "console.hpp"
|
||||||
|
#include "fastfiles.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include <utils/hook.hpp>
|
#include <utils/hook.hpp>
|
||||||
#include <utils/memory.hpp>
|
#include <utils/memory.hpp>
|
||||||
@ -22,7 +22,7 @@ namespace fastfiles
|
|||||||
void db_try_load_x_file_internal(const char* zone_name, const int zone_flags, const int is_base_map)
|
void db_try_load_x_file_internal(const char* zone_name, const int zone_flags, const int is_base_map)
|
||||||
{
|
{
|
||||||
console::info("Loading fastfile %s\n", zone_name);
|
console::info("Loading fastfile %s\n", zone_name);
|
||||||
return db_try_load_x_file_internal_hook.invoke<void>(zone_name, zone_flags, is_base_map);
|
db_try_load_x_file_internal_hook.invoke<void>(zone_name, zone_flags, is_base_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dump_gsc_script(const std::string& name, game::XAssetHeader header)
|
void dump_gsc_script(const std::string& name, game::XAssetHeader header)
|
||||||
@ -114,21 +114,9 @@ namespace fastfiles
|
|||||||
utils::hook::call(SELECT_VALUE(0x1402752DF, 0x140156350), p_mem_free_stub);
|
utils::hook::call(SELECT_VALUE(0x1402752DF, 0x140156350), p_mem_free_stub);
|
||||||
utils::hook::call(SELECT_VALUE(0x140276004, 0x140324259), p_mem_free_stub);
|
utils::hook::call(SELECT_VALUE(0x140276004, 0x140324259), p_mem_free_stub);
|
||||||
|
|
||||||
command::add("loadzone", [](const command::params& params)
|
// Allow loading of unsigned fastfiles
|
||||||
{
|
utils::hook::set<uint8_t>(0x1402FBF23, 0xEB); // DB_LoadXFile
|
||||||
if (params.size() < 2)
|
utils::hook::nop(0x1402FC445, 2); // DB_SetFileLoadCompressor
|
||||||
{
|
|
||||||
console::info("usage: loadzone <zone>\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
game::XZoneInfo info;
|
|
||||||
info.name = params.get(1);
|
|
||||||
info.allocFlags = 1;
|
|
||||||
info.freeFlags = 0;
|
|
||||||
|
|
||||||
game::DB_LoadXAssets(&info, 1, game::DBSyncMode::DB_LOAD_SYNC);
|
|
||||||
});
|
|
||||||
|
|
||||||
command::add("materiallist", [](const command::params& params)
|
command::add("materiallist", [](const command::params& params)
|
||||||
{
|
{
|
||||||
|
@ -86,6 +86,11 @@ namespace filesystem
|
|||||||
register_path("iw6");
|
register_path("iw6");
|
||||||
register_path(get_binary_directory() + "\\data");
|
register_path(get_binary_directory() + "\\data");
|
||||||
|
|
||||||
|
if (get_binary_directory() != std::filesystem::current_path())
|
||||||
|
{
|
||||||
|
register_path(std::filesystem::current_path() / "data");
|
||||||
|
}
|
||||||
|
|
||||||
// game's search paths
|
// game's search paths
|
||||||
register_path("devraw");
|
register_path("devraw");
|
||||||
register_path("devraw_shared");
|
register_path("devraw_shared");
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#include <std_include.hpp>
|
#include <std_include.hpp>
|
||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
#include "fps.hpp"
|
|
||||||
#include "scheduler.hpp"
|
#include "scheduler.hpp"
|
||||||
|
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
@ -75,23 +74,6 @@ namespace fps
|
|||||||
++data->index;
|
++data->index;
|
||||||
}
|
}
|
||||||
|
|
||||||
void enforce_fps_limit()
|
|
||||||
{
|
|
||||||
const auto* maxfps = game::Dvar_FindVar("com_maxfps");
|
|
||||||
const auto max = (maxfps) ? std::min(2 * maxfps->current.integer, maxfps->domain.integer.max + 250) : 1250;
|
|
||||||
const auto fps = static_cast<std::int32_t>(static_cast<float>(1000.0f /
|
|
||||||
static_cast<float>(cg_perf.average)) + 9.313225746154785e-10);
|
|
||||||
|
|
||||||
if (fps > max)
|
|
||||||
{
|
|
||||||
// workaround to limit fps
|
|
||||||
scheduler::once([]()
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
|
||||||
}, scheduler::pipeline::renderer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void perf_update()
|
void perf_update()
|
||||||
{
|
{
|
||||||
cg_perf.count = 32;
|
cg_perf.count = 32;
|
||||||
@ -102,7 +84,6 @@ namespace fps
|
|||||||
cg_perf.previous_ms = cg_perf.current_ms;
|
cg_perf.previous_ms = cg_perf.current_ms;
|
||||||
|
|
||||||
perf_calc_fps(&cg_perf, cg_perf.frame_ms);
|
perf_calc_fps(&cg_perf, cg_perf.frame_ms);
|
||||||
enforce_fps_limit();
|
|
||||||
|
|
||||||
utils::hook::invoke<void>(SELECT_VALUE(0x1405806E0, 0x140658E30));
|
utils::hook::invoke<void>(SELECT_VALUE(0x1405806E0, 0x140658E30));
|
||||||
}
|
}
|
||||||
@ -174,11 +155,6 @@ namespace fps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float get_avg_fps()
|
|
||||||
{
|
|
||||||
return 1000.0f / (cg_perf.average + std::numeric_limits<float>::epsilon());
|
|
||||||
}
|
|
||||||
|
|
||||||
class component final : public component_interface
|
class component final : public component_interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace fps
|
|
||||||
{
|
|
||||||
[[nodiscard]] float get_avg_fps();
|
|
||||||
}
|
|
@ -505,7 +505,7 @@ namespace game_console
|
|||||||
{
|
{
|
||||||
if (key == game::keyNum_t::K_F10)
|
if (key == game::keyNum_t::K_F10)
|
||||||
{
|
{
|
||||||
if(game::mp::svs->clients[localClientNum].header.state > game::CS_FREE)
|
if(game::mp::svs_clients[localClientNum].header.state > game::CS_FREE)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -674,7 +674,7 @@ namespace game_console
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
game::cmd_function_t* cmd = (*game::cmd_functions);
|
game::cmd_function_s* cmd = (*game::cmd_functions);
|
||||||
while (cmd)
|
while (cmd)
|
||||||
{
|
{
|
||||||
if (cmd->name)
|
if (cmd->name)
|
||||||
|
@ -102,9 +102,14 @@ namespace game_log
|
|||||||
g_log_printf("InitGame\n");
|
g_log_printf("InitGame\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
scripting::on_shutdown([](int free_scripts)
|
scripting::on_shutdown([](const int clear_scripts, const int post_shutdown) -> void
|
||||||
{
|
{
|
||||||
console::info("==== ShutdownGame (%d) ====\n", free_scripts);
|
if (post_shutdown)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console::info("==== ShutdownGame (%d) ====\n", clear_scripts);
|
||||||
|
|
||||||
g_log_printf("ShutdownGame:\n");
|
g_log_printf("ShutdownGame:\n");
|
||||||
g_log_printf("------------------------------------------------------------\n");
|
g_log_printf("------------------------------------------------------------\n");
|
||||||
|
@ -133,7 +133,7 @@ namespace gameplay
|
|||||||
return jump_height;
|
return jump_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
void jump_apply_slowdown_stub(game::mp::playerState_t* ps)
|
void jump_apply_slowdown_stub(game::mp::playerState_s* ps)
|
||||||
{
|
{
|
||||||
assert(ps->pm_flags & game::PMF_JUMPING);
|
assert(ps->pm_flags & game::PMF_JUMPING);
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ namespace gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float jump_get_slowdown_friction(game::mp::playerState_t* ps)
|
float jump_get_slowdown_friction(game::mp::playerState_s* ps)
|
||||||
{
|
{
|
||||||
assert(ps->pm_flags & game::PMF_JUMPING);
|
assert(ps->pm_flags & game::PMF_JUMPING);
|
||||||
assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME);
|
assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME);
|
||||||
@ -181,7 +181,7 @@ namespace gameplay
|
|||||||
return 2.5f;
|
return 2.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
float jump_reduce_friction_stub(game::mp::playerState_t* ps)
|
float jump_reduce_friction_stub(game::mp::playerState_s* ps)
|
||||||
{
|
{
|
||||||
float control;
|
float control;
|
||||||
|
|
||||||
@ -199,7 +199,7 @@ namespace gameplay
|
|||||||
return control;
|
return control;
|
||||||
}
|
}
|
||||||
|
|
||||||
float jump_get_land_factor(game::mp::playerState_t* ps)
|
float jump_get_land_factor(game::mp::playerState_s* ps)
|
||||||
{
|
{
|
||||||
assert(ps->pm_flags & game::PMF_JUMPING);
|
assert(ps->pm_flags & game::PMF_JUMPING);
|
||||||
assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME);
|
assert(ps->pm_time <= game::JUMP_LAND_SLOWDOWN_TIME);
|
||||||
@ -219,9 +219,9 @@ namespace gameplay
|
|||||||
|
|
||||||
void jump_start_stub(game::pmove_t* pm, game::pml_t* pml, float height)
|
void jump_start_stub(game::pmove_t* pm, game::pml_t* pml, float height)
|
||||||
{
|
{
|
||||||
static_assert(offsetof(game::mp::playerState_t, groundEntityNum) == 0x70);
|
static_assert(offsetof(game::mp::playerState_s, groundEntityNum) == 0x70);
|
||||||
static_assert(offsetof(game::mp::playerState_t, pm_time) == 0x8);
|
static_assert(offsetof(game::mp::playerState_s, pm_time) == 0x8);
|
||||||
static_assert(offsetof(game::mp::playerState_t, sprintState.sprintButtonUpRequired) == 0x240);
|
static_assert(offsetof(game::mp::playerState_s, sprintState.sprintButtonUpRequired) == 0x240);
|
||||||
static_assert(offsetof(game::pml_t, frametime) == 0x24);
|
static_assert(offsetof(game::pml_t, frametime) == 0x24);
|
||||||
static_assert(offsetof(game::pml_t, walking) == 0x2C);
|
static_assert(offsetof(game::pml_t, walking) == 0x2C);
|
||||||
static_assert(offsetof(game::pml_t, groundPlane) == 0x30);
|
static_assert(offsetof(game::pml_t, groundPlane) == 0x30);
|
||||||
@ -229,9 +229,9 @@ namespace gameplay
|
|||||||
|
|
||||||
float factor;
|
float factor;
|
||||||
float velocity_sqrd;
|
float velocity_sqrd;
|
||||||
game::mp::playerState_t* ps;
|
game::mp::playerState_s* ps;
|
||||||
|
|
||||||
ps = static_cast<game::mp::playerState_t*>(pm->ps);
|
ps = static_cast<game::mp::playerState_s*>(pm->ps);
|
||||||
|
|
||||||
assert(ps);
|
assert(ps);
|
||||||
|
|
||||||
@ -295,7 +295,7 @@ namespace gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void pm_weapon_use_ammo_stub(game::playerState_t* ps, game::Weapon weapon,
|
void pm_weapon_use_ammo_stub(game::playerState_s* ps, game::Weapon weapon,
|
||||||
bool is_alternate, int amount, game::PlayerHandIndex hand)
|
bool is_alternate, int amount, game::PlayerHandIndex hand)
|
||||||
{
|
{
|
||||||
if (!dvars::player_sustainAmmo->current.enabled)
|
if (!dvars::player_sustainAmmo->current.enabled)
|
||||||
@ -304,10 +304,10 @@ namespace gameplay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
game::mp::gentity_t* weapon_rocket_launcher_fire_stub(game::mp::gentity_t* ent, game::Weapon weapon, float spread, game::weaponParms* wp,
|
game::mp::gentity_s* weapon_rocket_launcher_fire_stub(game::mp::gentity_s* ent, game::Weapon weapon, float spread, game::weaponParms* wp,
|
||||||
const float* gun_vel, game::mp::missileFireParms* fire_parms, bool magic_bullet)
|
const float* gun_vel, game::mp::missileFireParms* fire_parms, bool magic_bullet)
|
||||||
{
|
{
|
||||||
auto* result = utils::hook::invoke<game::mp::gentity_t*>(0x1403DB8A0, ent, weapon, spread, wp, gun_vel, fire_parms, magic_bullet);
|
auto* result = utils::hook::invoke<game::mp::gentity_s*>(0x1403DB8A0, ent, weapon, spread, wp, gun_vel, fire_parms, magic_bullet);
|
||||||
|
|
||||||
if (ent->client != nullptr && wp->weapDef->inventoryType != game::WEAPINVENTORY_EXCLUSIVE)
|
if (ent->client != nullptr && wp->weapDef->inventoryType != game::WEAPINVENTORY_EXCLUSIVE)
|
||||||
{
|
{
|
||||||
|
@ -113,7 +113,6 @@ namespace gsc
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int scr_get_object(unsigned int index)
|
unsigned int scr_get_object(unsigned int index)
|
||||||
@ -284,28 +283,23 @@ namespace gsc
|
|||||||
public:
|
public:
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
if (game::environment::is_sp())
|
scr_emit_function_hook.create(SELECT_VALUE(0x1403D3350, 0x14042E150), &scr_emit_function_stub);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
scr_emit_function_hook.create(0x14042E150, &scr_emit_function_stub);
|
utils::hook::call(SELECT_VALUE(0x1403D32E4, 0x14042E0E4), compile_error_stub); // LinkFile
|
||||||
|
utils::hook::call(SELECT_VALUE(0x1403D3338, 0x14042E138), compile_error_stub); // LinkFile
|
||||||
utils::hook::call(0x14042E0E4, compile_error_stub); // LinkFile
|
utils::hook::call(SELECT_VALUE(0x1403D342A, 0x14042E22B), find_variable_stub); // Scr_EmitFunction
|
||||||
utils::hook::call(0x14042E138, compile_error_stub); // LinkFile
|
|
||||||
utils::hook::call(0x14042E22B, find_variable_stub); // Scr_EmitFunction
|
|
||||||
|
|
||||||
// Restore basic error messages to scr functions
|
// Restore basic error messages to scr functions
|
||||||
utils::hook::jump(0x140438ED0, scr_get_object);
|
utils::hook::jump(game::Scr_GetObject, scr_get_object);
|
||||||
utils::hook::jump(0x140438AD0, scr_get_const_string);
|
utils::hook::jump(game::Scr_GetConstString, scr_get_const_string);
|
||||||
utils::hook::jump(0x1404388B0, scr_get_const_istring);
|
utils::hook::jump(game::Scr_GetConstIString, scr_get_const_istring);
|
||||||
utils::hook::jump(0x1404393D0, scr_get_vector);
|
utils::hook::jump(game::Scr_GetVector, scr_get_vector);
|
||||||
utils::hook::jump(0x140438E10, scr_get_int);
|
utils::hook::jump(game::Scr_GetInt, scr_get_int);
|
||||||
utils::hook::jump(0x140438D60, scr_get_float);
|
utils::hook::jump(game::Scr_GetFloat, scr_get_float);
|
||||||
|
|
||||||
utils::hook::jump(0x1404390B0, scr_get_pointer_type);
|
utils::hook::jump(SELECT_VALUE(0x1403DE150, 0x1404390B0), scr_get_pointer_type);
|
||||||
utils::hook::jump(0x140439280, scr_get_type);
|
utils::hook::jump(SELECT_VALUE(0x1403DE320, 0x140439280), scr_get_type);
|
||||||
utils::hook::jump(0x1404392F0, scr_get_type_name);
|
utils::hook::jump(SELECT_VALUE(0x1403DE390, 0x1404392F0), scr_get_type_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pre_destroy() override
|
void pre_destroy() override
|
||||||
|
@ -37,9 +37,60 @@ namespace gsc
|
|||||||
bool force_error_print = false;
|
bool force_error_print = false;
|
||||||
std::optional<std::string> gsc_error_msg;
|
std::optional<std::string> gsc_error_msg;
|
||||||
|
|
||||||
|
std::vector<devmap_entry> devmap_entries{};
|
||||||
|
|
||||||
|
std::optional<devmap_entry> get_devmap_entry(const std::uint8_t* codepos)
|
||||||
|
{
|
||||||
|
const auto itr = std::ranges::find_if(devmap_entries, [codepos](const devmap_entry& entry) -> bool
|
||||||
|
{
|
||||||
|
return codepos >= entry.bytecode && codepos < entry.bytecode + entry.size;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (itr != devmap_entries.end())
|
||||||
|
{
|
||||||
|
return *itr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<std::uint16_t, std::uint16_t>> get_line_and_col_for_codepos(const std::uint8_t* codepos)
|
||||||
|
{
|
||||||
|
const auto entry = get_devmap_entry(codepos);
|
||||||
|
|
||||||
|
if (!entry.has_value())
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::pair<std::uint16_t, std::uint16_t>> best_line_info{};
|
||||||
|
std::uint32_t best_codepos = 0;
|
||||||
|
|
||||||
|
assert(codepos >= entry->bytecode);
|
||||||
|
const std::uint32_t codepos_offset = static_cast<std::uint32_t>(codepos - entry->bytecode);
|
||||||
|
|
||||||
|
for (const auto& instruction : entry->devmap)
|
||||||
|
{
|
||||||
|
if (instruction.codepos > codepos_offset)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best_line_info.has_value() && codepos_offset - instruction.codepos > codepos_offset - best_codepos)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
best_line_info = { { instruction.line, instruction.col } };
|
||||||
|
best_codepos = instruction.codepos;
|
||||||
|
}
|
||||||
|
|
||||||
|
return best_line_info;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int scr_get_function_stub(const char** p_name, int* type)
|
unsigned int scr_get_function_stub(const char** p_name, int* type)
|
||||||
{
|
{
|
||||||
const auto result = utils::hook::invoke<unsigned int>(0x1403CD9F0, p_name, type);
|
const auto result = game::Scr_GetFunction(p_name, type);
|
||||||
|
|
||||||
for (const auto& [name, func] : functions)
|
for (const auto& [name, func] : functions)
|
||||||
{
|
{
|
||||||
@ -120,27 +171,36 @@ namespace gsc
|
|||||||
const auto pos = frame == game::scr_VmPub->function_frame ? game::scr_function_stack->pos : frame->fs.pos;
|
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);
|
const auto function = find_function(frame->fs.pos);
|
||||||
|
|
||||||
|
const char* location;
|
||||||
if (function.has_value())
|
if (function.has_value())
|
||||||
{
|
{
|
||||||
console::warn("\tat function \"%s\" in file \"%s.gsc\"\n", function.value().first.data(), function.value().second.data());
|
location = utils::string::va("function \"%s\" in file \"%s\"", function.value().first.data(), function.value().second.data());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
console::warn("\tat unknown location %p\n", pos);
|
location = utils::string::va("unknown location %p", pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto line_info = get_line_and_col_for_codepos(reinterpret_cast<const std::uint8_t*>(pos));
|
||||||
|
if (line_info.has_value())
|
||||||
|
{
|
||||||
|
location = utils::string::va("%s line \"%d\" column \"%d\"", location, line_info->first, line_info->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
console::warn("\tat %s\n", location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void vm_error_stub(int mark_pos)
|
void vm_error_stub(const unsigned __int64 mark_pos)
|
||||||
{
|
{
|
||||||
if (!dvars::com_developer_script->current.enabled && !force_error_print)
|
if (!dvars::com_developer_script->current.enabled && !force_error_print)
|
||||||
{
|
{
|
||||||
utils::hook::invoke<void>(0x1404E4D00, mark_pos);
|
game::LargeLocalResetToMark(mark_pos);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console::warn("******* script runtime error ********\n");
|
console::warn("******* script runtime error ********\n");
|
||||||
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(0x144D57840);
|
const auto opcode_id = *reinterpret_cast<std::uint8_t*>(SELECT_VALUE(0x1455BE740, 0x144D57840));
|
||||||
|
|
||||||
const std::string error = gsc_error_msg.has_value() ? std::format(": {}", gsc_error_msg.value()) : std::string();
|
const std::string error = gsc_error_msg.has_value() ? std::format(": {}", gsc_error_msg.value()) : std::string();
|
||||||
|
|
||||||
@ -166,7 +226,7 @@ namespace gsc
|
|||||||
|
|
||||||
print_callstack();
|
print_callstack();
|
||||||
console::warn("************************************\n");
|
console::warn("************************************\n");
|
||||||
utils::hook::invoke<void>(0x1404E4D00, mark_pos);
|
game::LargeLocalResetToMark(mark_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void inc_in_param()
|
void inc_in_param()
|
||||||
@ -228,6 +288,11 @@ namespace gsc
|
|||||||
scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(0)));
|
scr_error(utils::string::va("Assert fail: %s", game::Scr_GetString(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void scr_cmd_is_dedicated_server()
|
||||||
|
{
|
||||||
|
game::Scr_AddInt(game::environment::is_dedi());
|
||||||
|
}
|
||||||
|
|
||||||
const char* get_code_pos(const int index)
|
const char* get_code_pos(const int index)
|
||||||
{
|
{
|
||||||
if (static_cast<unsigned int>(index) >= game::scr_VmPub->outparamcount)
|
if (static_cast<unsigned int>(index) >= game::scr_VmPub->outparamcount)
|
||||||
@ -248,6 +313,22 @@ namespace gsc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void add_devmap_entry(std::uint8_t* codepos, std::size_t size, const std::string& name, xsk::gsc::buffer devmap_buf)
|
||||||
|
{
|
||||||
|
std::vector<dev_map_instruction> devmap{};
|
||||||
|
const auto* devmap_ptr = reinterpret_cast<const dev_map*>(devmap_buf.data);
|
||||||
|
|
||||||
|
devmap.resize(devmap_ptr->num_instructions);
|
||||||
|
std::memcpy(devmap.data(), devmap_ptr->instructions, sizeof(dev_map_instruction) * devmap_ptr->num_instructions);
|
||||||
|
|
||||||
|
devmap_entries.emplace_back(codepos, size, name, std::move(devmap));
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_devmap()
|
||||||
|
{
|
||||||
|
devmap_entries.clear();
|
||||||
|
}
|
||||||
|
|
||||||
void add_function(const std::string& name, game::BuiltinFunction function)
|
void add_function(const std::string& name, game::BuiltinFunction function)
|
||||||
{
|
{
|
||||||
++function_id_start;
|
++function_id_start;
|
||||||
@ -271,27 +352,18 @@ namespace gsc
|
|||||||
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F468, 0x1409E6CE8), scr_print);
|
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F468, 0x1409E6CE8), scr_print);
|
||||||
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F480, 0x1409E6D00), scr_print_ln);
|
utils::hook::set<game::BuiltinFunction>(SELECT_VALUE(0x14086F480, 0x1409E6D00), scr_print_ln);
|
||||||
|
|
||||||
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D353C, 0x14042E33C), 0x1000); // Scr_RegisterFunction
|
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D353B + 1, 0x14042E33B + 1), 0x1000); // Scr_RegisterFunction
|
||||||
|
|
||||||
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D3542 + 4, 0x14042E342 + 4), RVA(&func_table)); // Scr_RegisterFunction
|
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403D3542 + 4, 0x14042E342 + 4), RVA(&func_table)); // Scr_RegisterFunction
|
||||||
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403E0BDD + 3, 0x14043BBBE + 3), RVA(&func_table)); // VM_Execute_0
|
// utils::hook::set<std::uint32_t>(SELECT_VALUE(0x1403E0BDD + 3, 0x14043BBBE + 3), RVA(&func_table)); // VM_Execute_0
|
||||||
utils::hook::inject(SELECT_VALUE(0x1403D38E4 + 3, 0x14042E734 + 3), &func_table); // Scr_BeginLoadScripts
|
utils::hook::inject(SELECT_VALUE(0x1403D38E4 + 3, 0x14042E734 + 3), &func_table); // Scr_BeginLoadScripts
|
||||||
|
|
||||||
if (game::environment::is_sp())
|
utils::hook::nop(SELECT_VALUE(0x1403E0BDD + 5, 0x14043BBBE + 5), 2);
|
||||||
{
|
utils::hook::call(SELECT_VALUE(0x1403E0BDD, 0x14043BBBE), vm_call_builtin_function);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
utils::hook::nop(0x14043BBBE + 5, 2);
|
utils::hook::call(SELECT_VALUE(0x1403D391F, 0x14042E76F), scr_get_function_stub);
|
||||||
utils::hook::call(0x14043BBBE, vm_call_builtin_function);
|
|
||||||
|
|
||||||
utils::hook::call(0x14043CEB1, vm_error_stub);
|
utils::hook::call(SELECT_VALUE(0x1403E1ED0, 0x14043CEB1), vm_error_stub);
|
||||||
|
|
||||||
utils::hook::call(0x14042E76F, scr_get_function_stub);
|
|
||||||
|
|
||||||
utils::hook::set<game::BuiltinFunction>(0x1409E6E38, assert_ex_cmd);
|
|
||||||
utils::hook::set<game::BuiltinFunction>(0x1409E6E50, assert_msg_cmd);
|
|
||||||
utils::hook::set<game::BuiltinFunction>(0x1409E6E20, assert_cmd);
|
|
||||||
|
|
||||||
add_function("replacefunc", []
|
add_function("replacefunc", []
|
||||||
{
|
{
|
||||||
@ -309,10 +381,16 @@ namespace gsc
|
|||||||
command::execute(cmd);
|
command::execute(cmd);
|
||||||
});
|
});
|
||||||
|
|
||||||
add_function("isdedicated", []
|
if (game::environment::is_sp())
|
||||||
{
|
{
|
||||||
game::Scr_AddInt(game::environment::is_dedi());
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
utils::hook::set<game::BuiltinFunction>(0x1409E6E38, assert_ex_cmd);
|
||||||
|
utils::hook::set<game::BuiltinFunction>(0x1409E6E50, assert_msg_cmd);
|
||||||
|
utils::hook::set<game::BuiltinFunction>(0x1409E6E20, assert_cmd);
|
||||||
|
|
||||||
|
utils::hook::set<game::BuiltinFunction>(0x1409E94D0, scr_cmd_is_dedicated_server);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,36 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <xsk/gsc/engine/iw6_pc.hpp>
|
||||||
|
|
||||||
namespace gsc
|
namespace gsc
|
||||||
{
|
{
|
||||||
extern void* func_table[0x1000];
|
extern void* func_table[0x1000];
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct dev_map_instruction
|
||||||
|
{
|
||||||
|
std::uint32_t codepos;
|
||||||
|
std::uint16_t line;
|
||||||
|
std::uint16_t col;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct dev_map
|
||||||
|
{
|
||||||
|
std::uint32_t num_instructions;
|
||||||
|
dev_map_instruction instructions[1];
|
||||||
|
};
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
struct devmap_entry
|
||||||
|
{
|
||||||
|
const std::uint8_t* bytecode;
|
||||||
|
std::size_t size;
|
||||||
|
std::string script_name;
|
||||||
|
std::vector<dev_map_instruction> devmap;
|
||||||
|
};
|
||||||
|
|
||||||
|
void add_devmap_entry(std::uint8_t*, std::size_t, const std::string&, xsk::gsc::buffer);
|
||||||
|
void clear_devmap();
|
||||||
|
|
||||||
void add_function(const std::string& name, game::BuiltinFunction function);
|
void add_function(const std::string& name, game::BuiltinFunction function);
|
||||||
|
|
||||||
void scr_error(const char* error);
|
void scr_error(const char* error);
|
||||||
|
@ -3,15 +3,16 @@
|
|||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
#include "game/dvars.hpp"
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
#include <utils/io.hpp>
|
|
||||||
#include <utils/hook.hpp>
|
|
||||||
#include <utils/memory.hpp>
|
|
||||||
#include <utils/compression.hpp>
|
#include <utils/compression.hpp>
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
#include <utils/io.hpp>
|
||||||
|
#include <utils/memory.hpp>
|
||||||
|
|
||||||
#include "component/filesystem.hpp"
|
|
||||||
#include "component/console.hpp"
|
#include "component/console.hpp"
|
||||||
|
#include "component/filesystem.hpp"
|
||||||
#include "component/scripting.hpp"
|
#include "component/scripting.hpp"
|
||||||
|
|
||||||
|
#include "script_extension.hpp"
|
||||||
#include "script_loading.hpp"
|
#include "script_loading.hpp"
|
||||||
|
|
||||||
namespace gsc
|
namespace gsc
|
||||||
@ -32,6 +33,7 @@ namespace gsc
|
|||||||
init_handles.clear();
|
init_handles.clear();
|
||||||
loaded_scripts.clear();
|
loaded_scripts.clear();
|
||||||
script_allocator.clear();
|
script_allocator.clear();
|
||||||
|
clear_devmap();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool read_raw_script_file(const std::string& name, std::string* data)
|
bool read_raw_script_file(const std::string& name, std::string* data)
|
||||||
@ -114,6 +116,12 @@ namespace gsc
|
|||||||
|
|
||||||
loaded_scripts[real_name] = script_file_ptr;
|
loaded_scripts[real_name] = script_file_ptr;
|
||||||
|
|
||||||
|
const auto devmap = std::get<2>(output_script);
|
||||||
|
if (devmap.size > 0 && (gsc_ctx->build() & xsk::gsc::build::dev_maps) != xsk::gsc::build::prod)
|
||||||
|
{
|
||||||
|
add_devmap_entry(script_file_ptr->bytecode, byte_code_size, real_name, devmap);
|
||||||
|
}
|
||||||
|
|
||||||
return script_file_ptr;
|
return script_file_ptr;
|
||||||
}
|
}
|
||||||
catch (const std::exception& ex)
|
catch (const std::exception& ex)
|
||||||
@ -180,13 +188,9 @@ namespace gsc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_scripts(const std::filesystem::path& root_dir)
|
void load_scripts_from_folder(const std::filesystem::path& root_dir, const std::filesystem::path& script_dir)
|
||||||
{
|
{
|
||||||
const std::filesystem::path script_dir = root_dir / "scripts";
|
console::info("Scanning directory '%s' for custom GSC scripts...\n", script_dir.generic_string().data());
|
||||||
if (!utils::io::directory_exists(script_dir.generic_string()))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto scripts = utils::io::list_files(script_dir.generic_string());
|
const auto scripts = utils::io::list_files(script_dir.generic_string());
|
||||||
for (const auto& script : scripts)
|
for (const auto& script : scripts)
|
||||||
@ -204,6 +208,44 @@ namespace gsc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void load_scripts(const std::filesystem::path& root_dir)
|
||||||
|
{
|
||||||
|
const auto load = [&root_dir](const std::filesystem::path& folder) -> void
|
||||||
|
{
|
||||||
|
const std::filesystem::path script_dir = root_dir / folder;
|
||||||
|
if (utils::io::directory_exists(script_dir.generic_string()))
|
||||||
|
{
|
||||||
|
load_scripts_from_folder(root_dir, script_dir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::filesystem::path base_dir = "scripts";
|
||||||
|
|
||||||
|
load(base_dir);
|
||||||
|
|
||||||
|
const auto* map_name = game::Dvar_FindVar("mapname");
|
||||||
|
|
||||||
|
if (game::environment::is_sp())
|
||||||
|
{
|
||||||
|
const std::filesystem::path game_folder = "sp";
|
||||||
|
|
||||||
|
load(base_dir / game_folder);
|
||||||
|
|
||||||
|
load(base_dir / game_folder / map_name->current.string);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const std::filesystem::path game_folder = "mp";
|
||||||
|
|
||||||
|
load(base_dir / game_folder);
|
||||||
|
|
||||||
|
load(base_dir / game_folder / map_name->current.string);
|
||||||
|
|
||||||
|
const auto* game_type = game::Dvar_FindVar("g_gametype");
|
||||||
|
load(base_dir / game_folder / game_type->current.string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int db_is_x_asset_default(game::XAssetType type, const char* name)
|
int db_is_x_asset_default(game::XAssetType type, const char* name)
|
||||||
{
|
{
|
||||||
if (loaded_scripts.contains(name))
|
if (loaded_scripts.contains(name))
|
||||||
@ -218,12 +260,22 @@ namespace gsc
|
|||||||
{
|
{
|
||||||
utils::hook::invoke<void>(0x1403CCB10);
|
utils::hook::invoke<void>(0x1403CCB10);
|
||||||
|
|
||||||
clear();
|
for (const auto& path : filesystem::get_search_paths())
|
||||||
|
{
|
||||||
|
load_scripts(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int g_scr_load_script_and_label_stub(game::ScriptFunctions* functions)
|
||||||
|
{
|
||||||
|
const auto result = utils::hook::invoke<int>(0x140349790, functions);
|
||||||
|
|
||||||
for (const auto& path : filesystem::get_search_paths())
|
for (const auto& path : filesystem::get_search_paths())
|
||||||
{
|
{
|
||||||
load_scripts(path);
|
load_scripts(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size)
|
void db_get_raw_buffer_stub(const game::RawFile* rawfile, char* buf, const int size)
|
||||||
@ -250,7 +302,26 @@ namespace gsc
|
|||||||
utils::hook::invoke<void>(0x1403D2CA0);
|
utils::hook::invoke<void>(0x1403D2CA0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void scr_load_level_stub()
|
void scr_load_level_singleplayer_stub()
|
||||||
|
{
|
||||||
|
for (auto& function_handle : main_handles)
|
||||||
|
{
|
||||||
|
console::info("Executing '%s::main'\n", function_handle.first.data());
|
||||||
|
const auto thread = game::Scr_ExecThread(static_cast<int>(function_handle.second), 0);
|
||||||
|
game::RemoveRefToObject(thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::hook::invoke<void>(0x1403401B0);
|
||||||
|
|
||||||
|
for (auto& function_handle : init_handles)
|
||||||
|
{
|
||||||
|
console::info("Executing '%s::init'\n", function_handle.first.data());
|
||||||
|
const auto thread = game::Scr_ExecThread(static_cast<int>(function_handle.second), 0);
|
||||||
|
game::RemoveRefToObject(thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void scr_load_level_multiplayer_stub()
|
||||||
{
|
{
|
||||||
utils::hook::invoke<void>(0x1403CDC60);
|
utils::hook::invoke<void>(0x1403CDC60);
|
||||||
|
|
||||||
@ -345,31 +416,35 @@ namespace gsc
|
|||||||
utils::hook::call(SELECT_VALUE(0x14032D1E0, 0x1403CCED9), scr_begin_load_scripts_stub); // GScr_LoadScripts
|
utils::hook::call(SELECT_VALUE(0x14032D1E0, 0x1403CCED9), scr_begin_load_scripts_stub); // GScr_LoadScripts
|
||||||
utils::hook::call(SELECT_VALUE(0x14032D345, 0x1403CD08D), scr_end_load_scripts_stub); // GScr_LoadScripts
|
utils::hook::call(SELECT_VALUE(0x14032D345, 0x1403CD08D), scr_end_load_scripts_stub); // GScr_LoadScripts
|
||||||
|
|
||||||
|
// ProcessScript
|
||||||
|
utils::hook::call(SELECT_VALUE(0x1403DC887, 0x1404378D7), find_script);
|
||||||
|
utils::hook::call(SELECT_VALUE(0x1403DC897, 0x1404378E7), db_is_x_asset_default);
|
||||||
|
|
||||||
dvars::com_developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
|
dvars::com_developer_script = game::Dvar_RegisterBool("developer_script", false, game::DVAR_FLAG_NONE, "Enable developer script comments");
|
||||||
|
|
||||||
if (game::environment::is_sp())
|
scripting::on_shutdown([](const int clear_scripts, const int post_shutdown) -> void
|
||||||
{
|
{
|
||||||
return;
|
if (clear_scripts && post_shutdown)
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessScript
|
|
||||||
utils::hook::call(0x1404378D7, find_script);
|
|
||||||
utils::hook::call(0x1404378E7, db_is_x_asset_default);
|
|
||||||
|
|
||||||
// GScr_LoadScripts
|
|
||||||
utils::hook::call(0x1403CD009, gscr_load_game_type_script_stub);
|
|
||||||
|
|
||||||
// Exec script handles
|
|
||||||
utils::hook::call(0x14039F64E, g_load_structs_stub);
|
|
||||||
utils::hook::call(0x14039F653, scr_load_level_stub);
|
|
||||||
|
|
||||||
scripting::on_shutdown([](int free_scripts)
|
|
||||||
{
|
|
||||||
if (free_scripts)
|
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (game::environment::is_sp())
|
||||||
|
{
|
||||||
|
utils::hook::call(0x14034996F, g_scr_load_script_and_label_stub);
|
||||||
|
|
||||||
|
utils::hook::call(0x140316591, scr_load_level_singleplayer_stub);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// GScr_LoadScripts
|
||||||
|
utils::hook::call(0x1403CD009, gscr_load_game_type_script_stub);
|
||||||
|
|
||||||
|
// Exec script handles
|
||||||
|
utils::hook::call(0x14039F64E, g_load_structs_stub);
|
||||||
|
utils::hook::call(0x14039F653, scr_load_level_multiplayer_stub);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -50,8 +50,8 @@ namespace input
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cl_char_event_hook.create(game::CL_CharEvent, cl_char_event_stub);
|
cl_char_event_hook.create(SELECT_VALUE(0x14023CE50, 0x1402C2AE0), cl_char_event_stub);
|
||||||
cl_key_event_hook.create(game::CL_KeyEvent, cl_key_event_stub);
|
cl_key_event_hook.create(SELECT_VALUE(0x14023D070, 0x1402C2CE0), cl_key_event_stub);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
157
src/client/component/mods.cpp
Normal file
157
src/client/component/mods.cpp
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
#include <std_include.hpp>
|
||||||
|
#include "loader/component_loader.hpp"
|
||||||
|
#include "game/game.hpp"
|
||||||
|
#include "game/dvars.hpp"
|
||||||
|
|
||||||
|
#include "mods.hpp"
|
||||||
|
|
||||||
|
#include <utils/hook.hpp>
|
||||||
|
|
||||||
|
namespace mods
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
utils::hook::detour sys_create_file_hook;
|
||||||
|
|
||||||
|
void db_build_os_path_from_source(const char* zone_name, game::FF_DIR source, unsigned int size, char* filename)
|
||||||
|
{
|
||||||
|
char user_map[MAX_PATH]{};
|
||||||
|
|
||||||
|
switch (source)
|
||||||
|
{
|
||||||
|
case game::FFD_DEFAULT:
|
||||||
|
(void)sprintf_s(filename, size, "%s\\%s.ff", std::filesystem::current_path().string().c_str(), zone_name);
|
||||||
|
break;
|
||||||
|
case game::FFD_MOD_DIR:
|
||||||
|
assert(mods::is_using_mods());
|
||||||
|
|
||||||
|
(void)sprintf_s(filename, size, "%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), (*dvars::fs_gameDirVar)->current.string, zone_name);
|
||||||
|
break;
|
||||||
|
case game::FFD_USER_MAP:
|
||||||
|
strncpy_s(user_map, zone_name, _TRUNCATE);
|
||||||
|
|
||||||
|
(void)sprintf_s(filename, size, "%s\\%s\\%s\\%s.ff", std::filesystem::current_path().string().c_str(), "usermaps", user_map, zone_name);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false && "inconceivable");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
game::Sys_File sys_create_file_stub(const char* dir, const char* filename)
|
||||||
|
{
|
||||||
|
auto result = sys_create_file_hook.invoke<game::Sys_File>(dir, filename);
|
||||||
|
|
||||||
|
if (result.handle != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_using_mods())
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .ff extension was added previously
|
||||||
|
if (!std::strcmp(filename, "mod.ff") && mods::db_mod_file_exists())
|
||||||
|
{
|
||||||
|
char file_path[MAX_PATH]{};
|
||||||
|
db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(file_path), file_path);
|
||||||
|
result.handle = game::Sys_OpenFileReliable(file_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void db_load_x_assets_stub(game::XZoneInfo* zone_info, unsigned int zone_count, game::DBSyncMode sync_mode)
|
||||||
|
{
|
||||||
|
std::vector<game::XZoneInfo> zones(zone_info, zone_info + zone_count);
|
||||||
|
|
||||||
|
if (db_mod_file_exists())
|
||||||
|
{
|
||||||
|
zones.emplace_back("mod", game::DB_ZONE_COMMON | game::DB_ZONE_CUSTOM, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
game::DB_LoadXAssets(zones.data(), static_cast<unsigned int>(zones.size()), sync_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto skip_extra_zones_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||||
|
{
|
||||||
|
const auto skip = a.newLabel();
|
||||||
|
const auto original = a.newLabel();
|
||||||
|
|
||||||
|
a.pushad64();
|
||||||
|
a.test(ebp, game::DB_ZONE_CUSTOM); // allocFlags
|
||||||
|
a.jnz(skip);
|
||||||
|
|
||||||
|
a.bind(original);
|
||||||
|
a.popad64();
|
||||||
|
a.mov(rdx, 0x140835F28);
|
||||||
|
a.mov(rcx, rsi);
|
||||||
|
a.call_aligned(strcmp);
|
||||||
|
a.jmp(0x1403217C0);
|
||||||
|
|
||||||
|
a.bind(skip);
|
||||||
|
a.popad64();
|
||||||
|
a.mov(r15d, 0x80);
|
||||||
|
a.not_(r15d);
|
||||||
|
a.and_(ebp, r15d);
|
||||||
|
a.jmp(0x1403217F6);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_using_mods()
|
||||||
|
{
|
||||||
|
return (*dvars::fs_gameDirVar) && *(*dvars::fs_gameDirVar)->current.string;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool db_mod_file_exists()
|
||||||
|
{
|
||||||
|
if (!*(*dvars::fs_gameDirVar)->current.string)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char filename[MAX_PATH]{};
|
||||||
|
db_build_os_path_from_source("mod", game::FFD_MOD_DIR, sizeof(filename), filename);
|
||||||
|
|
||||||
|
if (auto zone_file = game::Sys_OpenFileReliable(filename); zone_file != INVALID_HANDLE_VALUE)
|
||||||
|
{
|
||||||
|
::CloseHandle(zone_file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class component final : public component_interface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static_assert(sizeof(game::Sys_File) == 8);
|
||||||
|
|
||||||
|
void post_unpack() override
|
||||||
|
{
|
||||||
|
dvars::fs_gameDirVar = reinterpret_cast<game::dvar_t**>(SELECT_VALUE(0x145856D38, 0x147876000));
|
||||||
|
|
||||||
|
// Remove DVAR_INIT from fs_game
|
||||||
|
utils::hook::set<std::uint32_t>(SELECT_VALUE(0x14041C085 + 2, 0x1404DDA45 + 2), SELECT_VALUE(game::DVAR_FLAG_NONE, game::DVAR_FLAG_SERVERINFO));
|
||||||
|
|
||||||
|
if (game::environment::is_sp())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't load eng_ + patch_ with loadzone
|
||||||
|
utils::hook::nop(0x1403217B1, 15);
|
||||||
|
utils::hook::jump(0x1403217B1, skip_extra_zones_stub, true);
|
||||||
|
|
||||||
|
// Add custom zone paths
|
||||||
|
sys_create_file_hook.create(game::Sys_CreateFile, sys_create_file_stub);
|
||||||
|
|
||||||
|
// Load mod.ff
|
||||||
|
utils::hook::call(0x1405E7113, db_load_x_assets_stub); // R_LoadGraphicsAssets According to myself but I don't remember where I got it from
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
REGISTER_COMPONENT(mods::component)
|
7
src/client/component/mods.hpp
Normal file
7
src/client/component/mods.hpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace mods
|
||||||
|
{
|
||||||
|
bool is_using_mods();
|
||||||
|
bool db_mod_file_exists();
|
||||||
|
}
|
@ -20,7 +20,7 @@ namespace network
|
|||||||
return callbacks;
|
return callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handle_command(game::netadr_t* address, const char* command, game::msg_t* message)
|
bool handle_command(game::netadr_s* address, const char* command, game::msg_t* message)
|
||||||
{
|
{
|
||||||
const auto cmd_string = utils::string::to_lower(command);
|
const auto cmd_string = utils::string::to_lower(command);
|
||||||
auto& callbacks = get_callbacks();
|
auto& callbacks = get_callbacks();
|
||||||
@ -61,7 +61,7 @@ namespace network
|
|||||||
a.jmp(0x1402C64EE);
|
a.jmp(0x1402C64EE);
|
||||||
}
|
}
|
||||||
|
|
||||||
int net_compare_base_address(const game::netadr_t* a1, const game::netadr_t* a2)
|
int net_compare_base_address(const game::netadr_s* a1, const game::netadr_s* a2)
|
||||||
{
|
{
|
||||||
if (a1->type == a2->type)
|
if (a1->type == a2->type)
|
||||||
{
|
{
|
||||||
@ -83,12 +83,12 @@ namespace network
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int net_compare_address(const game::netadr_t* a1, const game::netadr_t* a2)
|
int net_compare_address(const game::netadr_s* a1, const game::netadr_s* a2)
|
||||||
{
|
{
|
||||||
return net_compare_base_address(a1, a2) && a1->port == a2->port;
|
return net_compare_base_address(a1, a2) && a1->port == a2->port;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reconnect_migrated_client(game::mp::client_t*, game::netadr_t* from, const int, const int, const char*, const char*, bool)
|
void reconnect_migrated_client(game::mp::client_t*, game::netadr_s* from, const int, const int, const char*, const char*, bool)
|
||||||
{
|
{
|
||||||
// This happens when a client tries to rejoin after being recently disconnected, OR by a duplicated guid
|
// This happens when a client tries to rejoin after being recently disconnected, OR by a duplicated guid
|
||||||
// We don't want this to do anything. It decides to crash seemingly randomly
|
// We don't want this to do anything. It decides to crash seemingly randomly
|
||||||
@ -107,14 +107,14 @@ namespace network
|
|||||||
get_callbacks()[utils::string::to_lower(command)] = callback;
|
get_callbacks()[utils::string::to_lower(command)] = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
int dw_send_to_stub(const int size, const char* src, game::netadr_t* addr)
|
int dw_send_to_stub(const int size, const char* src, game::netadr_s* addr)
|
||||||
{
|
{
|
||||||
sockaddr s = {};
|
sockaddr s = {};
|
||||||
game::NetadrToSockadr(addr, &s);
|
game::NetadrToSockadr(addr, &s);
|
||||||
return sendto(*game::query_socket, src, size, 0, &s, 16) >= 0;
|
return sendto(*game::query_socket, src, size, 0, &s, 16) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void send(const game::netadr_t& address, const std::string& command, const std::string& data, const char separator)
|
void send(const game::netadr_s& address, const std::string& command, const std::string& data, const char separator)
|
||||||
{
|
{
|
||||||
std::string packet = "\xFF\xFF\xFF\xFF";
|
std::string packet = "\xFF\xFF\xFF\xFF";
|
||||||
packet.append(command);
|
packet.append(command);
|
||||||
@ -124,7 +124,7 @@ namespace network
|
|||||||
send_data(address, packet);
|
send_data(address, packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_data(const game::netadr_t& address, const std::string& data)
|
void send_data(const game::netadr_s& address, const std::string& data)
|
||||||
{
|
{
|
||||||
if (address.type == game::NA_LOOPBACK)
|
if (address.type == game::NA_LOOPBACK)
|
||||||
{
|
{
|
||||||
@ -136,12 +136,12 @@ namespace network
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool are_addresses_equal(const game::netadr_t& a, const game::netadr_t& b)
|
bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b)
|
||||||
{
|
{
|
||||||
return net_compare_address(&a, &b);
|
return net_compare_address(&a, &b);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* net_adr_to_string(const game::netadr_t& a)
|
const char* net_adr_to_string(const game::netadr_s& a)
|
||||||
{
|
{
|
||||||
if (a.type == game::NA_LOOPBACK)
|
if (a.type == game::NA_LOOPBACK)
|
||||||
{
|
{
|
||||||
@ -286,7 +286,7 @@ namespace network
|
|||||||
if (!game::environment::is_dedi())
|
if (!game::environment::is_dedi())
|
||||||
{
|
{
|
||||||
// we need this on the client for RCon
|
// we need this on the client for RCon
|
||||||
on("print", [](const game::netadr_t& address, const std::string& message)
|
on("print", [](const game::netadr_s& address, const std::string& message)
|
||||||
{
|
{
|
||||||
if (address != party::get_target())
|
if (address != party::get_target())
|
||||||
{
|
{
|
||||||
|
@ -2,23 +2,23 @@
|
|||||||
|
|
||||||
namespace network
|
namespace network
|
||||||
{
|
{
|
||||||
using callback = std::function<void(const game::netadr_t&, const std::string&)>;
|
using callback = std::function<void(const game::netadr_s&, const std::string&)>;
|
||||||
|
|
||||||
void on(const std::string& command, const callback& callback);
|
void on(const std::string& command, const callback& callback);
|
||||||
void send(const game::netadr_t& address, const std::string& command, const std::string& data = {}, char separator = ' ');
|
void send(const game::netadr_s& address, const std::string& command, const std::string& data = {}, char separator = ' ');
|
||||||
void send_data(const game::netadr_t& address, const std::string& data);
|
void send_data(const game::netadr_s& address, const std::string& data);
|
||||||
|
|
||||||
bool are_addresses_equal(const game::netadr_t& a, const game::netadr_t& b);
|
bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b);
|
||||||
|
|
||||||
const char* net_adr_to_string(const game::netadr_t& a);
|
const char* net_adr_to_string(const game::netadr_s& a);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool operator==(const game::netadr_t& a, const game::netadr_t& b)
|
inline bool operator==(const game::netadr_s& a, const game::netadr_s& b)
|
||||||
{
|
{
|
||||||
return network::are_addresses_equal(a, b); //
|
return network::are_addresses_equal(a, b); //
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool operator!=(const game::netadr_t& a, const game::netadr_t& b)
|
inline bool operator!=(const game::netadr_s& a, const game::netadr_s& b)
|
||||||
{
|
{
|
||||||
return !(a == b); //
|
return !(a == b); //
|
||||||
}
|
}
|
||||||
@ -26,20 +26,20 @@ inline bool operator!=(const game::netadr_t& a, const game::netadr_t& b)
|
|||||||
namespace std
|
namespace std
|
||||||
{
|
{
|
||||||
template <>
|
template <>
|
||||||
struct equal_to<game::netadr_t>
|
struct equal_to<game::netadr_s>
|
||||||
{
|
{
|
||||||
using result_type = bool;
|
using result_type = bool;
|
||||||
|
|
||||||
bool operator()(const game::netadr_t& lhs, const game::netadr_t& rhs) const
|
bool operator()(const game::netadr_s& lhs, const game::netadr_s& rhs) const
|
||||||
{
|
{
|
||||||
return network::are_addresses_equal(lhs, rhs);
|
return network::are_addresses_equal(lhs, rhs);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct hash<game::netadr_t>
|
struct hash<game::netadr_s>
|
||||||
{
|
{
|
||||||
size_t operator()(const game::netadr_t& x) const noexcept
|
size_t operator()(const game::netadr_s& x) const noexcept
|
||||||
{
|
{
|
||||||
return hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(&x.ip[0])) ^ hash<uint16_t>()(x.port);
|
return hash<uint32_t>()(*reinterpret_cast<const uint32_t*>(&x.ip[0])) ^ hash<uint16_t>()(x.port);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ namespace notifies
|
|||||||
scheduler::once([params, message, client_num]
|
scheduler::once([params, message, client_num]
|
||||||
{
|
{
|
||||||
const auto* guid = game::SV_GetGuid(client_num);
|
const auto* guid = game::SV_GetGuid(client_num);
|
||||||
const auto* name = game::mp::svs->clients[client_num].name;
|
const auto* name = game::mp::svs_clients[client_num].name;
|
||||||
|
|
||||||
game_log::g_log_printf("%s;%s;%i;%s;%s\n",
|
game_log::g_log_printf("%s;%s;%i;%s;%s\n",
|
||||||
params.get(0),
|
params.get(0),
|
||||||
@ -126,7 +126,7 @@ namespace notifies
|
|||||||
a.lea(eax, dword_ptr(r15, -0x17));
|
a.lea(eax, dword_ptr(r15, -0x17));
|
||||||
a.mov(dword_ptr(rbp, 0x60), r15d);
|
a.mov(dword_ptr(rbp, 0x60), r15d);
|
||||||
|
|
||||||
a.jmp(0x14043A593);
|
a.jmp(SELECT_VALUE(0x1403DF5B3, 0x14043A593));
|
||||||
|
|
||||||
a.bind(replace);
|
a.bind(replace);
|
||||||
|
|
||||||
@ -174,22 +174,22 @@ namespace notifies
|
|||||||
public:
|
public:
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
|
utils::hook::jump(SELECT_VALUE(0x1403DF5A4, 0x14043A584), utils::hook::assemble(vm_execute_stub), true);
|
||||||
|
|
||||||
|
scripting::on_shutdown([](const int clear_scripts, const int post_shutdown) -> void
|
||||||
|
{
|
||||||
|
if (clear_scripts && post_shutdown)
|
||||||
|
{
|
||||||
|
vm_execute_hooks.clear();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (game::environment::is_sp())
|
if (game::environment::is_sp())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::hook::call(0x1404724DD, client_command_stub);
|
utils::hook::call(0x1404724DD, client_command_stub);
|
||||||
|
|
||||||
utils::hook::jump(0x14043A584, utils::hook::assemble(vm_execute_stub), true);
|
|
||||||
|
|
||||||
scripting::on_shutdown([](const bool free_scripts)
|
|
||||||
{
|
|
||||||
if (free_scripts)
|
|
||||||
{
|
|
||||||
vm_execute_hooks.clear();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ namespace party
|
|||||||
{
|
{
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
game::netadr_t host{};
|
game::netadr_s host{};
|
||||||
std::string challenge{};
|
std::string challenge{};
|
||||||
bool hostDefined{false};
|
bool hostDefined{false};
|
||||||
} connect_state;
|
} connect_state;
|
||||||
@ -37,7 +37,7 @@ namespace party
|
|||||||
|
|
||||||
int sv_maxclients;
|
int sv_maxclients;
|
||||||
|
|
||||||
void connect_to_party(const game::netadr_t& target, const std::string& mapname, const std::string& gametype)
|
void connect_to_party(const game::netadr_s& target, const std::string& mapname, const std::string& gametype)
|
||||||
{
|
{
|
||||||
if (game::environment::is_sp())
|
if (game::environment::is_sp())
|
||||||
{
|
{
|
||||||
@ -58,7 +58,7 @@ namespace party
|
|||||||
|
|
||||||
// CL_ConnectFromParty
|
// CL_ConnectFromParty
|
||||||
char session_info[0x100] = {};
|
char session_info[0x100] = {};
|
||||||
reinterpret_cast<void(*)(int, char*, const game::netadr_t*, const char*, const char*)>(0x1402C5700)(
|
reinterpret_cast<void(*)(int, char*, const game::netadr_s*, const char*, const char*)>(0x1402C5700)(
|
||||||
0, session_info, &target, mapname.data(), gametype.data());
|
0, session_info, &target, mapname.data(), gametype.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,12 +81,26 @@ namespace party
|
|||||||
a.mov(ecx, 2);
|
a.mov(ecx, 2);
|
||||||
a.jmp(0x1402C617D);
|
a.jmp(0x1402C617D);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
void connect_to_dummy_party(const std::string& mapname, const std::string& gametype)
|
utils::info_string get_info()
|
||||||
{
|
{
|
||||||
const game::netadr_t dummy_target{};
|
utils::info_string info;
|
||||||
connect_to_party(dummy_target, mapname, gametype);
|
|
||||||
|
info.set("gamename", "IW6");
|
||||||
|
info.set("hostname", dvars::get_string("sv_hostname"));
|
||||||
|
info.set("gametype", dvars::get_string("g_gametype"));
|
||||||
|
info.set("sv_motd", dvars::get_string("sv_motd"));
|
||||||
|
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
|
||||||
|
info.set("mapname", dvars::get_string("mapname"));
|
||||||
|
info.set("isPrivate", dvars::get_string("g_password").empty() ? "0" : "1");
|
||||||
|
info.set("clients", std::to_string(get_client_count()));
|
||||||
|
info.set("bots", std::to_string(get_bot_count()));
|
||||||
|
info.set("sv_maxclients", std::to_string(*game::mp::svs_clientCount));
|
||||||
|
info.set("protocol", std::to_string(PROTOCOL));
|
||||||
|
info.set("shortversion", SHORTVERSION);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void switch_gamemode_if_necessary(const std::string& gametype)
|
void switch_gamemode_if_necessary(const std::string& gametype)
|
||||||
@ -126,9 +140,9 @@ namespace party
|
|||||||
int get_client_count()
|
int get_client_count()
|
||||||
{
|
{
|
||||||
auto count = 0;
|
auto count = 0;
|
||||||
for (auto i = 0; i < game::mp::svs->clientCount; ++i)
|
for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
|
||||||
{
|
{
|
||||||
if (game::mp::svs->clients[i].header.state >= game::CS_CONNECTED)
|
if (game::mp::svs_clients[i].header.state >= game::CS_CONNECTED)
|
||||||
{
|
{
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
@ -140,10 +154,10 @@ namespace party
|
|||||||
int get_bot_count()
|
int get_bot_count()
|
||||||
{
|
{
|
||||||
auto count = 0;
|
auto count = 0;
|
||||||
for (auto i = 0; i < game::mp::svs->clientCount; ++i)
|
for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
|
||||||
{
|
{
|
||||||
if (game::mp::svs->clients[i].header.state >= game::CS_CONNECTED &&
|
if (game::mp::svs_clients[i].header.state >= game::CS_CONNECTED &&
|
||||||
game::mp::svs->clients[i].testClient != game::TC_NONE)
|
game::mp::svs_clients[i].testClient != game::TC_NONE)
|
||||||
{
|
{
|
||||||
++count;
|
++count;
|
||||||
}
|
}
|
||||||
@ -152,7 +166,7 @@ namespace party
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::netadr_t& get_target()
|
game::netadr_s& get_target()
|
||||||
{
|
{
|
||||||
return connect_state.host;
|
return connect_state.host;
|
||||||
}
|
}
|
||||||
@ -164,7 +178,7 @@ namespace party
|
|||||||
|
|
||||||
int get_client_num_from_name(const std::string& name)
|
int get_client_num_from_name(const std::string& name)
|
||||||
{
|
{
|
||||||
for (auto i = 0; !name.empty() && i < game::mp::svs->clientCount; ++i)
|
for (auto i = 0; !name.empty() && i < *game::mp::svs_clientCount; ++i)
|
||||||
{
|
{
|
||||||
if (game::mp::g_entities[i].client)
|
if (game::mp::g_entities[i].client)
|
||||||
{
|
{
|
||||||
@ -181,7 +195,7 @@ namespace party
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void connect(const game::netadr_t& target)
|
void connect(const game::netadr_s& target)
|
||||||
{
|
{
|
||||||
if (game::environment::is_sp())
|
if (game::environment::is_sp())
|
||||||
{
|
{
|
||||||
@ -258,7 +272,7 @@ namespace party
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::netadr_t target{};
|
game::netadr_s target{};
|
||||||
if (game::NET_StringToAdr(params[1], &target))
|
if (game::NET_StringToAdr(params[1], &target))
|
||||||
{
|
{
|
||||||
connect(target);
|
connect(target);
|
||||||
@ -289,7 +303,7 @@ namespace party
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto client_num = atoi(params.get(1));
|
const auto client_num = atoi(params.get(1));
|
||||||
if (client_num < 0 || client_num >= game::mp::svs->clientCount)
|
if (client_num < 0 || client_num >= *game::mp::svs_clientCount)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -326,7 +340,7 @@ namespace party
|
|||||||
const std::string name = params.get(1);
|
const std::string name = params.get(1);
|
||||||
if (name == "all"s)
|
if (name == "all"s)
|
||||||
{
|
{
|
||||||
for (auto i = 0; i < game::mp::svs->clientCount; ++i)
|
for (auto i = 0; i < *game::mp::svs_clientCount; ++i)
|
||||||
{
|
{
|
||||||
scheduler::once([i, reason]
|
scheduler::once([i, reason]
|
||||||
{
|
{
|
||||||
@ -337,7 +351,7 @@ namespace party
|
|||||||
}
|
}
|
||||||
|
|
||||||
const auto client_num = get_client_num_from_name(name);
|
const auto client_num = get_client_num_from_name(name);
|
||||||
if (client_num < 0 || client_num >= game::mp::svs->clientCount)
|
if (client_num < 0 || client_num >= *game::mp::svs_clientCount)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -366,7 +380,7 @@ namespace party
|
|||||||
const auto message = params.join(2);
|
const auto message = params.join(2);
|
||||||
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(client_num, 0, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
||||||
console::info("%s -> %i: %s\n", name, client_num, message.data());
|
console::info("%s -> %i: %s\n", name, client_num, message.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -380,7 +394,7 @@ namespace party
|
|||||||
const auto client_num = atoi(params.get(1));
|
const auto client_num = atoi(params.get(1));
|
||||||
const auto message = params.join(2);
|
const auto message = params.join(2);
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(client_num, 0, utils::string::va("%c \"%s\"", 84, message.data()));
|
game::SV_GameSendServerCommand(client_num, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data()));
|
||||||
console::info("%i: %s\n", client_num, message.data());
|
console::info("%i: %s\n", client_num, message.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -394,7 +408,7 @@ namespace party
|
|||||||
const auto message = params.join(1);
|
const auto message = params.join(1);
|
||||||
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
const auto* const name = game::Dvar_FindVar("sv_sayName")->current.string;
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s: %s\"", 84, name, message.data()));
|
||||||
console::info("%s: %s\n", name, message.data());
|
console::info("%s: %s\n", name, message.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -407,36 +421,67 @@ namespace party
|
|||||||
|
|
||||||
const auto message = params.join(1);
|
const auto message = params.join(1);
|
||||||
|
|
||||||
game::SV_GameSendServerCommand(-1, 0, utils::string::va("%c \"%s\"", 84, message.data()));
|
game::SV_GameSendServerCommand(-1, game::SV_CMD_CAN_IGNORE, utils::string::va("%c \"%s\"", 84, message.data()));
|
||||||
console::info("%s\n", message.data());
|
console::info("%s\n", message.data());
|
||||||
});
|
});
|
||||||
|
|
||||||
network::on("getInfo", [](const game::netadr_t& target, const std::string& data)
|
network::on("getInfo", [](const game::netadr_s& target, const std::string& data)
|
||||||
{
|
{
|
||||||
utils::info_string info;
|
utils::info_string info = get_info();
|
||||||
info.set("challenge", data);
|
info.set("challenge", data);
|
||||||
info.set("gamename", "IW6");
|
|
||||||
info.set("hostname", dvars::get_string("sv_hostname"));
|
|
||||||
info.set("gametype", dvars::get_string("g_gametype"));
|
|
||||||
info.set("sv_motd", dvars::get_string("sv_motd"));
|
|
||||||
info.set("xuid", utils::string::va("%llX", steam::SteamUser()->GetSteamID().bits));
|
|
||||||
info.set("mapname", dvars::get_string("mapname"));
|
|
||||||
info.set("isPrivate", dvars::get_string("g_password").empty() ? "0" : "1");
|
|
||||||
info.set("clients", std::to_string(get_client_count()));
|
|
||||||
info.set("bots", std::to_string(get_bot_count()));
|
|
||||||
info.set("sv_maxclients", std::to_string(game::mp::svs->clientCount));
|
|
||||||
info.set("protocol", std::to_string(PROTOCOL));
|
|
||||||
info.set("shortversion", SHORTVERSION);
|
|
||||||
|
|
||||||
network::send(target, "infoResponse", info.build(), '\n');
|
network::send(target, "infoResponse", info.build(), '\n');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
network::on("getStatus", [](const game::netadr_s& target, const std::string& data)
|
||||||
|
{
|
||||||
|
std::string player_list;
|
||||||
|
|
||||||
|
utils::info_string info = get_info();
|
||||||
|
info.set("challenge", data);
|
||||||
|
|
||||||
|
const auto* sv_running = game::Dvar_FindVar("sv_running");
|
||||||
|
if (!sv_running || !sv_running->current.enabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; ++i)
|
||||||
|
{
|
||||||
|
auto* client = &game::mp::svs_clients[i];
|
||||||
|
auto* self = &game::mp::g_entities[i];
|
||||||
|
|
||||||
|
if (client->header.state < game::CS_ACTIVE)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self || !self->client)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (game::SV_BotIsBot(i))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto score = game::G_GetClientScore(i);
|
||||||
|
const auto ping = game::mp::svs_clients[i].ping;
|
||||||
|
const std::string name = game::mp::svs_clients[i].name;
|
||||||
|
|
||||||
|
player_list.append(std::format("{} {} \"{}\"\n", score, ping, name));
|
||||||
|
}
|
||||||
|
|
||||||
|
network::send(target, "statusResponse", info.build() + "\n"s + player_list + "\n"s, '\n');
|
||||||
|
});
|
||||||
|
|
||||||
if (game::environment::is_dedi())
|
if (game::environment::is_dedi())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
network::on("infoResponse", [](const game::netadr_t& target, const std::string& data)
|
network::on("infoResponse", [](const game::netadr_s& target, const std::string& data)
|
||||||
{
|
{
|
||||||
const utils::info_string info(data);
|
const utils::info_string info(data);
|
||||||
server_list::handle_info_response(target, info);
|
server_list::handle_info_response(target, info);
|
||||||
|
@ -2,14 +2,12 @@
|
|||||||
|
|
||||||
namespace party
|
namespace party
|
||||||
{
|
{
|
||||||
void connect_to_dummy_party(const std::string& mapname, const std::string& gametype);
|
|
||||||
|
|
||||||
void switch_gamemode_if_necessary(const std::string& gametype);
|
void switch_gamemode_if_necessary(const std::string& gametype);
|
||||||
void perform_game_initialization();
|
void perform_game_initialization();
|
||||||
|
|
||||||
void reset_connect_state();
|
void reset_connect_state();
|
||||||
|
|
||||||
void connect(const game::netadr_t& target);
|
void connect(const game::netadr_s& target);
|
||||||
void map_restart();
|
void map_restart();
|
||||||
|
|
||||||
[[nodiscard]] int server_client_count();
|
[[nodiscard]] int server_client_count();
|
||||||
@ -17,5 +15,5 @@ namespace party
|
|||||||
[[nodiscard]] int get_client_count();
|
[[nodiscard]] int get_client_count();
|
||||||
[[nodiscard]] int get_bot_count();
|
[[nodiscard]] int get_bot_count();
|
||||||
|
|
||||||
[[nodiscard]] game::netadr_t& get_target();
|
[[nodiscard]] game::netadr_s& get_target();
|
||||||
}
|
}
|
||||||
|
@ -150,7 +150,7 @@ namespace patches
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::Font_t* get_chat_font_handle()
|
game::Font_s* get_chat_font_handle()
|
||||||
{
|
{
|
||||||
return game::R_RegisterFont("fonts/bigFont");
|
return game::R_RegisterFont("fonts/bigFont");
|
||||||
}
|
}
|
||||||
@ -187,11 +187,11 @@ namespace patches
|
|||||||
}
|
}
|
||||||
|
|
||||||
utils::hook::detour cmd_lui_notify_server_hook;
|
utils::hook::detour cmd_lui_notify_server_hook;
|
||||||
void cmd_lui_notify_server_stub(game::mp::gentity_t* ent)
|
void cmd_lui_notify_server_stub(game::mp::gentity_s* ent)
|
||||||
{
|
{
|
||||||
command::params_sv params{};
|
command::params_sv params{};
|
||||||
const auto menu_id = atoi(params.get(1));
|
const auto menu_id = atoi(params.get(1));
|
||||||
const auto client = &game::mp::svs->clients[ent->s.clientNum];
|
const auto client = &game::mp::svs_clients[ent->s.clientNum];
|
||||||
|
|
||||||
// 9 => "end_game"
|
// 9 => "end_game"
|
||||||
if (menu_id == 9 && client->header.netchan.remoteAddress.type != game::NA_LOOPBACK)
|
if (menu_id == 9 && client->header.netchan.remoteAddress.type != game::NA_LOOPBACK)
|
||||||
|
@ -14,11 +14,11 @@ namespace rcon
|
|||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
bool is_redirecting_ = false;
|
bool is_redirecting_ = false;
|
||||||
game::netadr_t redirect_target_ = {};
|
game::netadr_s redirect_target_ = {};
|
||||||
std::string redirect_buffer = {};
|
std::string redirect_buffer = {};
|
||||||
std::recursive_mutex redirect_lock;
|
std::recursive_mutex redirect_lock;
|
||||||
|
|
||||||
void setup_redirect(const game::netadr_t& target)
|
void setup_redirect(const game::netadr_s& target)
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> $(redirect_lock);
|
std::lock_guard<std::recursive_mutex> $(redirect_lock);
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ namespace rcon
|
|||||||
|
|
||||||
for (int i = 0; i < sv_maxclients->current.integer; i++)
|
for (int i = 0; i < sv_maxclients->current.integer; i++)
|
||||||
{
|
{
|
||||||
const auto client = &game::mp::svs->clients[i];
|
const auto client = &game::mp::svs_clients[i];
|
||||||
auto self = &game::mp::g_entities[i];
|
auto self = &game::mp::g_entities[i];
|
||||||
|
|
||||||
char clean_name[32]{};
|
char clean_name[32]{};
|
||||||
@ -92,7 +92,7 @@ namespace rcon
|
|||||||
|
|
||||||
if (*reinterpret_cast<std::int32_t*>(0x1419E1AE0) >= 5) //clientUIActive.connectionState >= CA_CONNECTED
|
if (*reinterpret_cast<std::int32_t*>(0x1419E1AE0) >= 5) //clientUIActive.connectionState >= CA_CONNECTED
|
||||||
{
|
{
|
||||||
const auto target = *reinterpret_cast<game::netadr_t*>(0x141CB535C);
|
const auto target = *reinterpret_cast<game::netadr_s*>(0x141CB535C);
|
||||||
const auto buffer = password + " " + data;
|
const auto buffer = password + " " + data;
|
||||||
network::send(target, "rcon", buffer);
|
network::send(target, "rcon", buffer);
|
||||||
}
|
}
|
||||||
@ -170,7 +170,7 @@ namespace rcon
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
network::on("rcon", [](const game::netadr_t& addr, const std::string& data)
|
network::on("rcon", [](const game::netadr_s& addr, const std::string& data)
|
||||||
{
|
{
|
||||||
const auto pos = data.find_first_of(" ");
|
const auto pos = data.find_first_of(" ");
|
||||||
if (pos == std::string::npos)
|
if (pos == std::string::npos)
|
||||||
|
@ -34,7 +34,7 @@ namespace scripting
|
|||||||
|
|
||||||
std::unordered_map<unsigned int, std::string> canonical_string_table;
|
std::unordered_map<unsigned int, std::string> canonical_string_table;
|
||||||
|
|
||||||
std::vector<std::function<void(int)>> shutdown_callbacks;
|
std::vector<std::function<void(int, int)>> shutdown_callbacks;
|
||||||
std::vector<std::function<void()>> init_callbacks;
|
std::vector<std::function<void()>> init_callbacks;
|
||||||
|
|
||||||
void scr_load_level_stub()
|
void scr_load_level_stub()
|
||||||
@ -47,9 +47,9 @@ namespace scripting
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void g_shutdown_game_stub(const int free_scripts)
|
void g_shutdown_game_stub(const int clear_scripts)
|
||||||
{
|
{
|
||||||
if (free_scripts)
|
if (clear_scripts)
|
||||||
{
|
{
|
||||||
script_function_table_sort.clear();
|
script_function_table_sort.clear();
|
||||||
script_function_table.clear();
|
script_function_table.clear();
|
||||||
@ -59,10 +59,15 @@ namespace scripting
|
|||||||
|
|
||||||
for (const auto& callback : shutdown_callbacks)
|
for (const auto& callback : shutdown_callbacks)
|
||||||
{
|
{
|
||||||
callback(free_scripts);
|
callback(clear_scripts ,false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return g_shutdown_game_hook.invoke<void>(free_scripts);
|
g_shutdown_game_hook.invoke<void>(clear_scripts);
|
||||||
|
|
||||||
|
for (const auto& callback : shutdown_callbacks)
|
||||||
|
{
|
||||||
|
callback(clear_scripts, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_script_stub(const char* filename)
|
void process_script_stub(const char* filename)
|
||||||
@ -165,7 +170,7 @@ namespace scripting
|
|||||||
return find_token(id);
|
return find_token(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void on_shutdown(const std::function<void(int)>& callback)
|
void on_shutdown(const std::function<void(int, int)>& callback)
|
||||||
{
|
{
|
||||||
shutdown_callbacks.push_back(callback);
|
shutdown_callbacks.push_back(callback);
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ namespace scripting
|
|||||||
|
|
||||||
extern std::string current_file;
|
extern std::string current_file;
|
||||||
|
|
||||||
void on_shutdown(const std::function<void(int)>& callback);
|
void on_shutdown(const std::function<void(int, int)>& callback);
|
||||||
void on_init(const std::function<void()>& callback);
|
void on_init(const std::function<void()>& callback);
|
||||||
|
|
||||||
std::optional<std::string> get_canonical_string(unsigned int id);
|
std::optional<std::string> get_canonical_string(unsigned int id);
|
||||||
|
@ -33,14 +33,14 @@ namespace server_list
|
|||||||
std::string map_name;
|
std::string map_name;
|
||||||
std::string game_type;
|
std::string game_type;
|
||||||
char in_game;
|
char in_game;
|
||||||
game::netadr_t address;
|
game::netadr_s address;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
game::netadr_t address{};
|
game::netadr_s address{};
|
||||||
volatile bool requesting = false;
|
volatile bool requesting = false;
|
||||||
std::unordered_map<game::netadr_t, int> queued_servers{};
|
std::unordered_map<game::netadr_s, int> queued_servers{};
|
||||||
} master_state;
|
} master_state;
|
||||||
|
|
||||||
volatile bool update_server_list = false;
|
volatile bool update_server_list = false;
|
||||||
@ -307,12 +307,12 @@ namespace server_list
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get_master_server(game::netadr_t& address)
|
bool get_master_server(game::netadr_s& address)
|
||||||
{
|
{
|
||||||
return game::NET_StringToAdr("server.alterware.dev:20810", &address);
|
return game::NET_StringToAdr("server.alterware.dev:20810", &address);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handle_info_response(const game::netadr_t& address, const utils::info_string& info)
|
void handle_info_response(const game::netadr_s& address, const utils::info_string& info)
|
||||||
{
|
{
|
||||||
int start_time{};
|
int start_time{};
|
||||||
const auto now = game::Sys_Milliseconds();
|
const auto now = game::Sys_Milliseconds();
|
||||||
@ -436,7 +436,7 @@ namespace server_list
|
|||||||
|
|
||||||
scheduler::loop(do_frame_work, scheduler::pipeline::main);
|
scheduler::loop(do_frame_work, scheduler::pipeline::main);
|
||||||
|
|
||||||
network::on("getServersResponse", [](const game::netadr_t& target, const std::string& data)
|
network::on("getServersResponse", [](const game::netadr_s& target, const std::string& data)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> _(mutex);
|
std::lock_guard<std::mutex> _(mutex);
|
||||||
@ -469,7 +469,7 @@ namespace server_list
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
game::netadr_t address{};
|
game::netadr_s address{};
|
||||||
address.type = game::NA_IP;
|
address.type = game::NA_IP;
|
||||||
address.localNetID = game::NS_CLIENT1;
|
address.localNetID = game::NS_CLIENT1;
|
||||||
std::memcpy(&address.ip[0], data.data() + i + 0, 4);
|
std::memcpy(&address.ip[0], data.data() + i + 0, 4);
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
namespace server_list
|
namespace server_list
|
||||||
{
|
{
|
||||||
bool get_master_server(game::netadr_t& address);
|
bool get_master_server(game::netadr_s& address);
|
||||||
void handle_info_response(const game::netadr_t& address, const utils::info_string& info);
|
void handle_info_response(const game::netadr_s& address, const utils::info_string& info);
|
||||||
|
|
||||||
bool sl_key_event(int key, int down);
|
bool sl_key_event(int key, int down);
|
||||||
}
|
}
|
||||||
|
@ -50,8 +50,8 @@ namespace slowmotion
|
|||||||
|
|
||||||
for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; i++)
|
for (auto i = 0; i < game::Dvar_FindVar("sv_maxclients")->current.integer; i++)
|
||||||
{
|
{
|
||||||
auto* client = &game::mp::svs->clients[i];
|
auto* client = &game::mp::svs_clients[i];
|
||||||
client->nextSnapshotTime = game::mp::svs->time - 1;
|
client->nextSnapshotTime = *game::mp::serverTime - 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,7 +66,7 @@ namespace slowmotion
|
|||||||
utils::hook::jump(0x1403B4A10, scr_cmd_set_slow_motion);
|
utils::hook::jump(0x1403B4A10, scr_cmd_set_slow_motion);
|
||||||
|
|
||||||
// Detour used here instead of call hook because Com_TimeScaleMsec is called from arxan encrypted function
|
// Detour used here instead of call hook because Com_TimeScaleMsec is called from arxan encrypted function
|
||||||
com_timescale_msec_hook.create(game::Com_TimeScaleMsec, com_timescale_msec);
|
com_timescale_msec_hook.create(0x140415D50, com_timescale_msec);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,9 @@ namespace dvars
|
|||||||
game::dvar_t* cg_legacyCrashHandling = nullptr;
|
game::dvar_t* cg_legacyCrashHandling = nullptr;
|
||||||
|
|
||||||
game::dvar_t* com_developer_script = nullptr;
|
game::dvar_t* com_developer_script = nullptr;
|
||||||
game::dvar_t** com_developer;
|
game::dvar_t** com_developer = nullptr;
|
||||||
|
|
||||||
|
game::dvar_t** fs_gameDirVar = nullptr;
|
||||||
|
|
||||||
std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain)
|
std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain)
|
||||||
{
|
{
|
||||||
|
@ -47,6 +47,8 @@ namespace dvars
|
|||||||
extern game::dvar_t* com_developer_script;
|
extern game::dvar_t* com_developer_script;
|
||||||
extern game::dvar_t** com_developer;
|
extern game::dvar_t** com_developer;
|
||||||
|
|
||||||
|
extern game::dvar_t** fs_gameDirVar;
|
||||||
|
|
||||||
std::string dvar_get_vector_domain(int components, const game::dvar_limits& domain);
|
std::string dvar_get_vector_domain(int components, const game::dvar_limits& domain);
|
||||||
std::string dvar_get_domain(game::dvar_type type, const game::dvar_limits& domain);
|
std::string dvar_get_domain(game::dvar_type type, const game::dvar_limits& domain);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,13 @@ namespace game
|
|||||||
return sv_cmd_args->argv[sv_cmd_args->nesting][index];
|
return sv_cmd_args->argv[sv_cmd_args->nesting][index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HANDLE Sys_OpenFileReliable(const char* filename)
|
||||||
|
{
|
||||||
|
return ::CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, nullptr,
|
||||||
|
OPEN_EXISTING,
|
||||||
|
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
namespace environment
|
namespace environment
|
||||||
{
|
{
|
||||||
launcher::mode mode = launcher::mode::none;
|
launcher::mode mode = launcher::mode::none;
|
||||||
|
@ -63,6 +63,8 @@ namespace game
|
|||||||
[[nodiscard]] int SV_Cmd_Argc();
|
[[nodiscard]] int SV_Cmd_Argc();
|
||||||
[[nodiscard]] const char* SV_Cmd_Argv(int index);
|
[[nodiscard]] const char* SV_Cmd_Argv(int index);
|
||||||
|
|
||||||
|
[[nodiscard]] HANDLE Sys_OpenFileReliable(const char* filename);
|
||||||
|
|
||||||
[[nodiscard]] bool is_headless();
|
[[nodiscard]] bool is_headless();
|
||||||
void show_error(const std::string& text, const std::string& title = "Error");
|
void show_error(const std::string& text, const std::string& title = "Error");
|
||||||
|
|
||||||
|
@ -47,6 +47,12 @@ namespace game
|
|||||||
PMF_UNK3 = 0x4000,
|
PMF_UNK3 = 0x4000,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum svscmd_type
|
||||||
|
{
|
||||||
|
SV_CMD_CAN_IGNORE,
|
||||||
|
SV_CMD_RELIABLE,
|
||||||
|
};
|
||||||
|
|
||||||
enum XAssetType
|
enum XAssetType
|
||||||
{
|
{
|
||||||
ASSET_TYPE_PHYSPRESET = 0x0,
|
ASSET_TYPE_PHYSPRESET = 0x0,
|
||||||
@ -127,6 +133,19 @@ namespace game
|
|||||||
DB_LOAD_SYNC_SKIP_ALWAYS_LOADED = 0x5,
|
DB_LOAD_SYNC_SKIP_ALWAYS_LOADED = 0x5,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
DB_ZONE_COMMON = 0x1,
|
||||||
|
DB_ZONE_UI = 0x2,
|
||||||
|
DB_ZONE_GAME = 0x4,
|
||||||
|
DB_ZONE_LOAD = 0x8,
|
||||||
|
DB_ZONE_DEV = 0x10,
|
||||||
|
DB_ZONE_BASEMAP = 0x20,
|
||||||
|
DB_ZONE_TRANSIENT_POOL = 0x40,
|
||||||
|
DB_ZONE_TRANSIENT_MASK = 0x40,
|
||||||
|
DB_ZONE_CUSTOM = 0x80,
|
||||||
|
};
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
THREAD_CONTEXT_MAIN = 0x0,
|
THREAD_CONTEXT_MAIN = 0x0,
|
||||||
@ -181,7 +200,7 @@ namespace game
|
|||||||
|
|
||||||
static_assert(sizeof(db_internal_state) == 0x20);
|
static_assert(sizeof(db_internal_state) == 0x20);
|
||||||
|
|
||||||
struct db_z_stream_t
|
struct db_z_stream_s
|
||||||
{
|
{
|
||||||
unsigned char* next_in;
|
unsigned char* next_in;
|
||||||
unsigned int avail_in;
|
unsigned int avail_in;
|
||||||
@ -216,7 +235,7 @@ namespace game
|
|||||||
unsigned char* start_in;
|
unsigned char* start_in;
|
||||||
_OVERLAPPED overlapped;
|
_OVERLAPPED overlapped;
|
||||||
unsigned int readError;
|
unsigned int readError;
|
||||||
db_z_stream_t stream;
|
db_z_stream_s stream;
|
||||||
int readingResident;
|
int readingResident;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -792,22 +811,6 @@ namespace game
|
|||||||
|
|
||||||
enum ConfigString
|
enum ConfigString
|
||||||
{
|
{
|
||||||
CS_TIMESCALE = 0xA,
|
|
||||||
|
|
||||||
CS_VISIONSET_NORMAL = 0x49C,
|
|
||||||
CS_VISIONSET_NIGHT = 0x49D,
|
|
||||||
CS_VISIONSET_MISSILECAM = 0x49E,
|
|
||||||
CS_VISIONSET_THERMAL = 0x49F,
|
|
||||||
CS_VISIONSET_PAIN = 0x4A0,
|
|
||||||
CS_VISIONSET_POST_APPLY = 0x4A1,
|
|
||||||
|
|
||||||
CS_VISIONSET_FIRST = CS_VISIONSET_NORMAL,
|
|
||||||
CS_VISIONSET_LAST = CS_VISIONSET_POST_APPLY,
|
|
||||||
CS_VISIONSET_COUNT = CS_VISIONSET_POST_APPLY - CS_VISIONSET_NORMAL + 1
|
|
||||||
|
|
||||||
// many of the indices below are incorrect
|
|
||||||
|
|
||||||
/*
|
|
||||||
CS_FIRST = 0x0,
|
CS_FIRST = 0x0,
|
||||||
CS_SERVERINFO = 0x0,
|
CS_SERVERINFO = 0x0,
|
||||||
CS_SYSTEMINFO = 0x1,
|
CS_SYSTEMINFO = 0x1,
|
||||||
@ -879,8 +882,7 @@ namespace game
|
|||||||
CS_MP_ANIMS_LAST = 0xD21,
|
CS_MP_ANIMS_LAST = 0xD21,
|
||||||
CS_ITEMS = 0xD22,
|
CS_ITEMS = 0xD22,
|
||||||
CS_LEADERBOARDS = 0xD23,
|
CS_LEADERBOARDS = 0xD23,
|
||||||
MAX_CONFIGSTRINGS = 0xD24
|
MAX_CONFIGSTRINGS = 0xD24,
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum LiveClientDropType
|
enum LiveClientDropType
|
||||||
@ -1081,7 +1083,7 @@ namespace game
|
|||||||
float t1;
|
float t1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Font_t
|
struct Font_s
|
||||||
{
|
{
|
||||||
const char* fontName;
|
const char* fontName;
|
||||||
int pixelHeight;
|
int pixelHeight;
|
||||||
@ -1123,6 +1125,7 @@ namespace game
|
|||||||
DVAR_FLAG_LATCHED = 0x2,
|
DVAR_FLAG_LATCHED = 0x2,
|
||||||
DVAR_FLAG_CHEAT = 0x4,
|
DVAR_FLAG_CHEAT = 0x4,
|
||||||
DVAR_FLAG_REPLICATED = 0x8,
|
DVAR_FLAG_REPLICATED = 0x8,
|
||||||
|
DVAR_FLAG_SERVERINFO = 0x400,
|
||||||
DVAR_FLAG_WRITE = 0x800,
|
DVAR_FLAG_WRITE = 0x800,
|
||||||
DVAR_FLAG_READ = 0x2000,
|
DVAR_FLAG_READ = 0x2000,
|
||||||
};
|
};
|
||||||
@ -1199,9 +1202,9 @@ namespace game
|
|||||||
const char** argv[8];
|
const char** argv[8];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct cmd_function_t
|
struct cmd_function_s
|
||||||
{
|
{
|
||||||
cmd_function_t* next;
|
cmd_function_s* next;
|
||||||
const char* name;
|
const char* name;
|
||||||
void (__cdecl* function)();
|
void (__cdecl* function)();
|
||||||
};
|
};
|
||||||
@ -1252,7 +1255,7 @@ namespace game
|
|||||||
WEAPON_HAND_DEFAULT = 0x0,
|
WEAPON_HAND_DEFAULT = 0x0,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct usercmd_t
|
struct usercmd_s
|
||||||
{
|
{
|
||||||
int serverTime;
|
int serverTime;
|
||||||
unsigned int buttons;
|
unsigned int buttons;
|
||||||
@ -1306,16 +1309,6 @@ namespace game
|
|||||||
vec2_t corridorSpeeds;
|
vec2_t corridorSpeeds;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ClientArchiveData
|
|
||||||
{
|
|
||||||
int serverTime;
|
|
||||||
vec3_t origin;
|
|
||||||
vec3_t velocity;
|
|
||||||
int bobCycle;
|
|
||||||
int movementDir;
|
|
||||||
PlayerVehicleState playerVehStateClientArchive;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PlayerActiveWeaponState
|
struct PlayerActiveWeaponState
|
||||||
{
|
{
|
||||||
int weapAnim;
|
int weapAnim;
|
||||||
@ -1338,7 +1331,7 @@ namespace game
|
|||||||
bool hybridScope;
|
bool hybridScope;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct compressedAnimData_t
|
struct compressedAnimData_s
|
||||||
{
|
{
|
||||||
int flags;
|
int flags;
|
||||||
int animRate;
|
int animRate;
|
||||||
@ -1357,7 +1350,7 @@ namespace game
|
|||||||
int flags;
|
int flags;
|
||||||
int startTime;
|
int startTime;
|
||||||
float startPosition[3];
|
float startPosition[3];
|
||||||
compressedAnimData_t compressedAnimData;
|
compressedAnimData_s compressedAnimData;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SlideState
|
struct SlideState
|
||||||
@ -1366,7 +1359,7 @@ namespace game
|
|||||||
int noFricTime;
|
int noFricTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SprintState_t
|
struct SprintState_s
|
||||||
{
|
{
|
||||||
int sprintButtonUpRequired;
|
int sprintButtonUpRequired;
|
||||||
int sprintDelay;
|
int sprintDelay;
|
||||||
@ -1425,7 +1418,7 @@ namespace game
|
|||||||
NS_INVALID_NETSRC = 0x4,
|
NS_INVALID_NETSRC = 0x4,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct netadr_t
|
struct netadr_s
|
||||||
{
|
{
|
||||||
netadrtype_t type;
|
netadrtype_t type;
|
||||||
unsigned char ip[4];
|
unsigned char ip[4];
|
||||||
@ -1438,26 +1431,18 @@ namespace game
|
|||||||
{
|
{
|
||||||
int overflowed;
|
int overflowed;
|
||||||
int readOnly;
|
int readOnly;
|
||||||
const char* data;
|
char* data;
|
||||||
const char* splitData;
|
char* splitData;
|
||||||
int maxsize;
|
int maxsize;
|
||||||
int cursize;
|
int cursize;
|
||||||
int splitSize;
|
int splitSize;
|
||||||
int readcount;
|
int readcount;
|
||||||
int bit;
|
int bit;
|
||||||
int lastEntityRef;
|
int lastEntityRef;
|
||||||
|
netsrc_t targetLocalNetID;
|
||||||
int useZlib;
|
int useZlib;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct gameState_t
|
|
||||||
{
|
|
||||||
int stringOffsets[3524];
|
|
||||||
char stringData[131'072];
|
|
||||||
int dataCount;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(game::gameState_t) == 145'172);
|
|
||||||
|
|
||||||
struct HsvColor
|
struct HsvColor
|
||||||
{
|
{
|
||||||
unsigned char h;
|
unsigned char h;
|
||||||
@ -1498,7 +1483,7 @@ namespace game
|
|||||||
unsigned int rgba;
|
unsigned int rgba;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct hudelem_t
|
struct hudelem_s
|
||||||
{
|
{
|
||||||
he_type_t type;
|
he_type_t type;
|
||||||
float x;
|
float x;
|
||||||
@ -1544,9 +1529,9 @@ namespace game
|
|||||||
int flags;
|
int flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct game_hudelem_t
|
struct game_hudelem_s
|
||||||
{
|
{
|
||||||
hudelem_t elem;
|
hudelem_s elem;
|
||||||
int clientNum;
|
int clientNum;
|
||||||
int team;
|
int team;
|
||||||
int archived;
|
int archived;
|
||||||
@ -1588,6 +1573,13 @@ namespace game
|
|||||||
VAR_TOTAL_COUNT = 0x1B,
|
VAR_TOTAL_COUNT = 0x1B,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ScriptFunctions
|
||||||
|
{
|
||||||
|
int maxSize;
|
||||||
|
int count;
|
||||||
|
int* address;
|
||||||
|
};
|
||||||
|
|
||||||
struct VariableStackBuffer
|
struct VariableStackBuffer
|
||||||
{
|
{
|
||||||
const char* pos;
|
const char* pos;
|
||||||
@ -1974,7 +1966,7 @@ namespace game
|
|||||||
FxWorld *fxWorld;
|
FxWorld *fxWorld;
|
||||||
GfxWorld *gfxWorld;
|
GfxWorld *gfxWorld;
|
||||||
GfxLightDef *lightDef;*/
|
GfxLightDef *lightDef;*/
|
||||||
Font_t* font;
|
Font_s* font;
|
||||||
/*MenuList *menuList;
|
/*MenuList *menuList;
|
||||||
menuDef_t *menu;
|
menuDef_t *menu;
|
||||||
AnimationClass *animClass;
|
AnimationClass *animClass;
|
||||||
@ -1987,8 +1979,8 @@ namespace game
|
|||||||
SurfaceFxTable *surfaceFx;*/
|
SurfaceFxTable *surfaceFx;*/
|
||||||
RawFile* rawfile;
|
RawFile* rawfile;
|
||||||
ScriptFile* scriptfile;
|
ScriptFile* scriptfile;
|
||||||
/*StringTable *stringTable;
|
StringTable* stringTable;
|
||||||
LeaderboardDef *leaderboardDef;
|
/*LeaderboardDef *leaderboardDef;
|
||||||
StructuredDataDefSet *structuredDataDefSet;
|
StructuredDataDefSet *structuredDataDefSet;
|
||||||
TracerDef *tracerDef;
|
TracerDef *tracerDef;
|
||||||
VehicleDef *vehDef;
|
VehicleDef *vehDef;
|
||||||
@ -2050,24 +2042,36 @@ namespace game
|
|||||||
const void* weapCompleteDef;
|
const void* weapCompleteDef;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum FF_DIR
|
||||||
|
{
|
||||||
|
FFD_DEFAULT = 0x0,
|
||||||
|
FFD_MOD_DIR = 0x1,
|
||||||
|
FFD_USER_MAP = 0x2,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Sys_File
|
||||||
|
{
|
||||||
|
HANDLE handle;
|
||||||
|
};
|
||||||
|
|
||||||
namespace sp
|
namespace sp
|
||||||
{
|
{
|
||||||
// very shit structures for the moment cuz i cba mapping the whole thing out right now...
|
// very shit structures for the moment cuz i cba mapping the whole thing out right now...
|
||||||
struct gclient_t
|
struct gclient_s
|
||||||
{
|
{
|
||||||
char __0x00[0xB6DC];
|
char __0x00[0xB6DC];
|
||||||
int flags;
|
int flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct gentity_t
|
struct gentity_s
|
||||||
{
|
{
|
||||||
char __0x00[0x110];
|
char __0x00[0x110];
|
||||||
gclient_t* client;
|
gclient_s* client;
|
||||||
char __0x118[0x4C];
|
char __0x118[0x4C];
|
||||||
int flags;
|
int flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct playerState_t
|
struct playerState_s
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2084,7 +2088,7 @@ namespace game
|
|||||||
namespace mp
|
namespace mp
|
||||||
{
|
{
|
||||||
#pragma pack(push, 4)
|
#pragma pack(push, 4)
|
||||||
struct playerState_t
|
struct playerState_s
|
||||||
{
|
{
|
||||||
int commandTime;
|
int commandTime;
|
||||||
int pm_type;
|
int pm_type;
|
||||||
@ -2134,11 +2138,9 @@ namespace game
|
|||||||
int clientNum;
|
int clientNum;
|
||||||
int viewmodelIndex;
|
int viewmodelIndex;
|
||||||
vec3_t viewangles;
|
vec3_t viewangles;
|
||||||
char _0x190[0x58];
|
char _0x190[0xAC];
|
||||||
int viewlocked_entNum;
|
|
||||||
char _0x1EC[0x50];
|
|
||||||
int locationSelectionInfo;
|
int locationSelectionInfo;
|
||||||
SprintState_t sprintState;
|
SprintState_s sprintState;
|
||||||
float holdBreathScale;
|
float holdBreathScale;
|
||||||
int holdBreathTimer;
|
int holdBreathTimer;
|
||||||
float moveSpeedScaleMultiplier;
|
float moveSpeedScaleMultiplier;
|
||||||
@ -2168,12 +2170,15 @@ namespace game
|
|||||||
vec3_t weapLockedPos;
|
vec3_t weapLockedPos;
|
||||||
int weaponIdleTime;
|
int weaponIdleTime;
|
||||||
int lastStowedWeapon;
|
int lastStowedWeapon;
|
||||||
char __0x638[0x2CEC];
|
char __0x638[0x2CE8];
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(playerState_t) == 0x3328);
|
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
struct ClientCustomizationInfo
|
||||||
|
{
|
||||||
|
unsigned int modelIndex[3];
|
||||||
|
};
|
||||||
|
|
||||||
struct ScoreInfo
|
struct ScoreInfo
|
||||||
{
|
{
|
||||||
unsigned short ping;
|
unsigned short ping;
|
||||||
@ -2188,140 +2193,6 @@ namespace game
|
|||||||
unsigned short adrenaline;
|
unsigned short adrenaline;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ScoreboardInfo
|
|
||||||
{
|
|
||||||
int teamScores[2];
|
|
||||||
ScoreInfo clientScores[18];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct clSnapshot_t
|
|
||||||
{
|
|
||||||
mp::playerState_t ps;
|
|
||||||
ScoreboardInfo scores;
|
|
||||||
int valid;
|
|
||||||
int snapFlags;
|
|
||||||
int serverTime;
|
|
||||||
int messageNum;
|
|
||||||
int deltaNum;
|
|
||||||
int ping;
|
|
||||||
int cmdNum;
|
|
||||||
int numEntities;
|
|
||||||
int numClients;
|
|
||||||
int parseEntitiesIndex;
|
|
||||||
int parseClientsIndex;
|
|
||||||
int parseOmnvarsIdx;
|
|
||||||
int numAgents;
|
|
||||||
int parseAgentsIndex;
|
|
||||||
int scriptablesParseCount;
|
|
||||||
int scriptablesParseIndex;
|
|
||||||
int serverCommandNum;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(clSnapshot_t) == 0x34B8);
|
|
||||||
|
|
||||||
struct clientActive_t
|
|
||||||
{
|
|
||||||
char usingAds;
|
|
||||||
char padding1[3];
|
|
||||||
int timeoutcount;
|
|
||||||
clSnapshot_t snap;
|
|
||||||
int oldSnapServerTime;
|
|
||||||
int oldServerTime;
|
|
||||||
int extrapolatedSnapshot;
|
|
||||||
char alwaysFalse;
|
|
||||||
char padding2[3];
|
|
||||||
int serverTime;
|
|
||||||
int oldFrameServerTime;
|
|
||||||
int serverTimeDelta;
|
|
||||||
int newSnapshots;
|
|
||||||
int serverId;
|
|
||||||
char mapname[64];
|
|
||||||
int mapcrc;
|
|
||||||
int parseEntitiesIndex;
|
|
||||||
int parseClientsIndex;
|
|
||||||
int parseOmnvarsIdx;
|
|
||||||
int mouseDx[2];
|
|
||||||
int mouseDy[2];
|
|
||||||
int mouseIndex;
|
|
||||||
char stanceHeld;
|
|
||||||
char padding3[3];
|
|
||||||
int stance;
|
|
||||||
int stancePosition;
|
|
||||||
int stanceTime;
|
|
||||||
char wasSprinting;
|
|
||||||
char padding4[3];
|
|
||||||
int cgameUserCmdWeapon;
|
|
||||||
int cgameUserCmdOffHand;
|
|
||||||
int cgameUserCmdAlternate;
|
|
||||||
float cgameFOVSensitivityScale;
|
|
||||||
float cgameMaxPitchSpeed;
|
|
||||||
float cgameMaxYawSpeed;
|
|
||||||
vec3_t cgameKickAngles;
|
|
||||||
vec3_t cgameOrigin;
|
|
||||||
vec3_t cgameVelocity;
|
|
||||||
PlayerVehicleState cgamePlayerVehState;
|
|
||||||
int cgameBobCycle;
|
|
||||||
int cgameMovementDir;
|
|
||||||
int cgameExtraButtons;
|
|
||||||
int cgamePredictedDataServerTime;
|
|
||||||
vec3_t clViewangles;
|
|
||||||
char _361C[12];
|
|
||||||
usercmd_t cmds[128];
|
|
||||||
int cmdNumber;
|
|
||||||
ClientArchiveData clientArchive[256];
|
|
||||||
int clientArchiveIndex;
|
|
||||||
int packetBackupCount;
|
|
||||||
int packetBackupMask;
|
|
||||||
int parseEntitiesCount;
|
|
||||||
int parseClientsCount;
|
|
||||||
int parseOmnvarsCount;
|
|
||||||
// missing fields
|
|
||||||
};
|
|
||||||
|
|
||||||
struct clientStatic_t
|
|
||||||
{
|
|
||||||
int quit;
|
|
||||||
int hunkUsersStarted;
|
|
||||||
char servername[256];
|
|
||||||
int rendererStarted;
|
|
||||||
int soundStarted;
|
|
||||||
int inUpdateSound;
|
|
||||||
int uiStarted;
|
|
||||||
int frametime;
|
|
||||||
int frametime_base;
|
|
||||||
int realtime;
|
|
||||||
char _0x124[0x80DBC];
|
|
||||||
gameState_t gameState;
|
|
||||||
// missing fields
|
|
||||||
};
|
|
||||||
|
|
||||||
struct clientConnection_t
|
|
||||||
{
|
|
||||||
int qport;
|
|
||||||
int clientNum;
|
|
||||||
netadr_t serverAddress;
|
|
||||||
int connectLastSendTime;
|
|
||||||
int connectPacketCount;
|
|
||||||
char serverMessage[256];
|
|
||||||
int challenge;
|
|
||||||
int checksumFeed;
|
|
||||||
int reliableSequence;
|
|
||||||
int reliableAcknowledge;
|
|
||||||
char reliableCommands[128][1024];
|
|
||||||
int serverMessageSequence;
|
|
||||||
int serverCommandSequence;
|
|
||||||
int lastExecutedServerCommand;
|
|
||||||
char serverCommands[128][1024];
|
|
||||||
char isServerRestarting;
|
|
||||||
char padding[3];
|
|
||||||
// missing fields
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ClientCustomizationInfo
|
|
||||||
{
|
|
||||||
unsigned int modelIndex[3];
|
|
||||||
};
|
|
||||||
|
|
||||||
union OmnvarValue
|
union OmnvarValue
|
||||||
{
|
{
|
||||||
bool enabled;
|
bool enabled;
|
||||||
@ -2347,58 +2218,51 @@ namespace game
|
|||||||
TEAM_NUM_TEAMS = 0x4,
|
TEAM_NUM_TEAMS = 0x4,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct clientState_t
|
struct clientState_s
|
||||||
{
|
{
|
||||||
int clientIndex;
|
int clientIndex;
|
||||||
team_t team;
|
team_t team;
|
||||||
int modelindex;
|
int modelindex;
|
||||||
int dualWielding;
|
int dualWielding;
|
||||||
char _0x10[0x30];
|
char _0x10[0x30];
|
||||||
char name[16];
|
char name[0x10];
|
||||||
int rank;
|
int rank;
|
||||||
int prestige;
|
int prestige;
|
||||||
|
char _0x00[0x10];
|
||||||
int nameplateFriendlyIndex;
|
int nameplateFriendlyIndex;
|
||||||
int nameplateEnemyIndex;
|
int nameplateEnemyIndex;
|
||||||
unsigned int perks[2];
|
unsigned int perks[2];
|
||||||
char _0x68[0x40];
|
char _0x78[0x80];
|
||||||
char clanAbbrev[8];
|
|
||||||
char _0xB0[0x28];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(clientState_t) == 0xD8);
|
|
||||||
|
|
||||||
struct clientSession_t
|
struct clientSession_t
|
||||||
{
|
{
|
||||||
int sessionState;
|
int sessionState;
|
||||||
int forceSpectatorClient;
|
int forceSpectatorClient;
|
||||||
int killCamEntity;
|
int killCamEntity;
|
||||||
int isInKillcam;
|
int isInKillcam;
|
||||||
char _0x10[0x10];
|
char __0x332C[0x14];
|
||||||
usercmd_t cmd;
|
usercmd_s cmd;
|
||||||
usercmd_t oldcmd;
|
usercmd_s oldcmd;
|
||||||
int localClient;
|
int localClient;
|
||||||
char _0x9C[0x34];
|
char __0x33C4[0x24];
|
||||||
float moveSpeedScaleMultiplier;
|
float moveSpeedScaleMultiplier;
|
||||||
int viewmodelIndex;
|
int viewmodelIndex;
|
||||||
int noSpectate;
|
int noSpectate;
|
||||||
clientState_t cs;
|
clientState_s cs;
|
||||||
ScoreInfo scores;
|
ScoreInfo scores;
|
||||||
char _0x1E6[0x1C0];
|
char __pad[0x1B0];
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(clientSession_t) == 0x388);
|
struct gclient_s
|
||||||
|
|
||||||
struct gclient_t
|
|
||||||
{
|
{
|
||||||
playerState_t ps;
|
playerState_s ps;
|
||||||
clientSession_t sess;
|
clientSession_t sess;
|
||||||
int flags;
|
int flags;
|
||||||
int spectatorClient;
|
int spectatorClient;
|
||||||
char _0x36B8[0x3B0];
|
char __0x36B4[0x3B0];
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(gclient_t) == 0x3A68);
|
|
||||||
|
|
||||||
struct LerpEntityState
|
struct LerpEntityState
|
||||||
{
|
{
|
||||||
int entFlags;
|
int entFlags;
|
||||||
@ -2433,8 +2297,6 @@ namespace game
|
|||||||
char _0xD8[0x30];
|
char _0xD8[0x30];
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(EntityState) == 0x108);
|
|
||||||
|
|
||||||
struct EntityShared
|
struct EntityShared
|
||||||
{
|
{
|
||||||
char _0x108[0x38];
|
char _0x108[0x38];
|
||||||
@ -2443,18 +2305,11 @@ namespace game
|
|||||||
char _0x14C[0xC];
|
char _0x14C[0xC];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct centity_t
|
struct gentity_s
|
||||||
{
|
|
||||||
char _0x0[0x238];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(centity_t) == 0x238);
|
|
||||||
|
|
||||||
struct gentity_t
|
|
||||||
{
|
{
|
||||||
EntityState s;
|
EntityState s;
|
||||||
EntityShared r;
|
EntityShared r;
|
||||||
gclient_t* client;
|
gclient_s* client;
|
||||||
char _0x170[0x50];
|
char _0x170[0x50];
|
||||||
int flags;
|
int flags;
|
||||||
char _0x1C4[0x18];
|
char _0x1C4[0x18];
|
||||||
@ -2463,232 +2318,21 @@ namespace game
|
|||||||
char _0x1E4[0x10C];
|
char _0x1E4[0x10C];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct snapshot_t
|
struct snapshot_s
|
||||||
{
|
{
|
||||||
playerState_t ps;
|
char _0x0[0x3C78];
|
||||||
ScoreboardInfo scores;
|
|
||||||
OmnvarData omnvars[256];
|
|
||||||
int snapFlags;
|
|
||||||
int ping;
|
int ping;
|
||||||
int serverTime;
|
int serverTime;
|
||||||
int numEntities;
|
int numEntities;
|
||||||
int numClients;
|
int numClients;
|
||||||
// missing fields
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum DemoType
|
struct cg_s
|
||||||
{
|
{
|
||||||
DEMO_TYPE_NONE = 0x0,
|
playerState_s ps;
|
||||||
DEMO_TYPE_CLIENT = 0x1,
|
char _0x3324[0x64];
|
||||||
DEMO_TYPE_SERVER = 0x2
|
snapshot_s* snap;
|
||||||
};
|
snapshot_s* nextSnap; // theres alot after this cba rn
|
||||||
|
|
||||||
struct characterInfo_t
|
|
||||||
{
|
|
||||||
char _0x0[0x5D8];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(characterInfo_t) == 0x5D8);
|
|
||||||
|
|
||||||
struct clientInfo_t
|
|
||||||
{
|
|
||||||
char _0x0[0x70];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(clientInfo_t) == 0x70);
|
|
||||||
|
|
||||||
struct bgs_t
|
|
||||||
{
|
|
||||||
char _0x0[0x459F0];
|
|
||||||
characterInfo_t characterinfo[42];
|
|
||||||
clientInfo_t clientinfo[18];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(bgs_t) == 0x55740);
|
|
||||||
|
|
||||||
struct visionSetVarsBase_t
|
|
||||||
{
|
|
||||||
int filmEnable;
|
|
||||||
float filmBrightness;
|
|
||||||
float filmContrast;
|
|
||||||
float filmDesaturation;
|
|
||||||
float filmDesaturationDark;
|
|
||||||
float filmInvert;
|
|
||||||
vec3_t filmLightTint;
|
|
||||||
vec3_t filmMediumTint;
|
|
||||||
vec3_t filmDarkTint;
|
|
||||||
int charPrimaryUseTweaks;
|
|
||||||
float charPrimaryDiffuseScale;
|
|
||||||
float charPrimarySpecularScale;
|
|
||||||
vec3_t charLightAmbient;
|
|
||||||
int viewModelPrimaryUseTweaks;
|
|
||||||
float viewModelPrimaryDiffuseScale;
|
|
||||||
float viewModelPrimarySpecularScale;
|
|
||||||
vec3_t viewModelLightAmbient;
|
|
||||||
float primaryLightOverride;
|
|
||||||
int materialBloomHQEnable;
|
|
||||||
float materialBloomHQHaziness;
|
|
||||||
float materialBloomHQGamma;
|
|
||||||
float materialBloomHQDesaturation;
|
|
||||||
float materialBloomIntensity;
|
|
||||||
float materialBloomRadius;
|
|
||||||
float materialBloomPinch;
|
|
||||||
float materialBloomLuminanceCutoff;
|
|
||||||
float materialBloomDesaturation;
|
|
||||||
int volumeLightScatter;
|
|
||||||
float volumeLightScatterLinearAtten;
|
|
||||||
float volumeLightScatterQuadraticAtten;
|
|
||||||
float volumeLightScatterAngularAtten;
|
|
||||||
float volumeLightScatterDepthAttenNear;
|
|
||||||
float volumeLightScatterDepthAttenFar;
|
|
||||||
float volumeLightScatterBackgroundDistance;
|
|
||||||
vec3_t volumeLightScatterColor;
|
|
||||||
float ssaoStrength;
|
|
||||||
float ssaoPower;
|
|
||||||
float ssaoBlurRadius;
|
|
||||||
int glareEnable;
|
|
||||||
float glareNormalStart;
|
|
||||||
float glareNormalEnd;
|
|
||||||
float glareNormalPower;
|
|
||||||
float glareNormalAmount;
|
|
||||||
float glareMirrorStart;
|
|
||||||
float glareMirrorEnd;
|
|
||||||
float glareMirrorPower;
|
|
||||||
float glareMirrorAmount;
|
|
||||||
float rimLightPitch;
|
|
||||||
float rimLightHeading;
|
|
||||||
float rimLightDiffuseIntensity;
|
|
||||||
float rimLightSpecIntensity;
|
|
||||||
float rimLightBias;
|
|
||||||
float rimLightPower;
|
|
||||||
vec3_t rimLightColor;
|
|
||||||
int overrideExistingMask;
|
|
||||||
float unlitSurfaceHDRScalar;
|
|
||||||
float litSurfaceHDRScalar;
|
|
||||||
float hdrSkyIntensity;
|
|
||||||
vec3_t hdrSkyColorTint;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(visionSetVarsBase_t) == 0x12C);
|
|
||||||
|
|
||||||
struct visionSetVars_t
|
|
||||||
{
|
|
||||||
visionSetVarsBase_t baseclass_0;
|
|
||||||
void* _0x12C[3];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(visionSetVars_t) == 0x148);
|
|
||||||
|
|
||||||
struct interpolationVisionSetVars_t
|
|
||||||
{
|
|
||||||
visionSetVarsBase_t baseclass_0;
|
|
||||||
void* _0x12C[0x2F];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(interpolationVisionSetVars_t) == 0x2A8);
|
|
||||||
|
|
||||||
struct visionSetLerpData_t
|
|
||||||
{
|
|
||||||
int timeStart;
|
|
||||||
int timeDuration;
|
|
||||||
int style;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct GfxFog
|
|
||||||
{
|
|
||||||
char _0x0[0x38];
|
|
||||||
int startTime;
|
|
||||||
int finishTime;
|
|
||||||
int color;
|
|
||||||
float density;
|
|
||||||
int sunColor;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ClientVisionSetData
|
|
||||||
{
|
|
||||||
visionSetVars_t visionSetPreLoaded[4];
|
|
||||||
char visionSetPreLoadedName[4][64];
|
|
||||||
int visionSetPreLoadedUsedTime[4];
|
|
||||||
interpolationVisionSetVars_t visionSetFrom[6];
|
|
||||||
interpolationVisionSetVars_t visionSetTo[6];
|
|
||||||
interpolationVisionSetVars_t visionSetCurrent[6];
|
|
||||||
interpolationVisionSetVars_t visionSetPrevious;
|
|
||||||
visionSetLerpData_t visionSetLerpData[6];
|
|
||||||
char visionName[6][64];
|
|
||||||
char visionBlendActive[6];
|
|
||||||
char thermalVisionActive;
|
|
||||||
char remoteMissileCam;
|
|
||||||
char painVisionActive;
|
|
||||||
float painVisionLerp;
|
|
||||||
int postApplyLerpTimeStart;
|
|
||||||
int postApplyLerpTimeDuration;
|
|
||||||
float postApplyLerpStart;
|
|
||||||
float postApplyLerpDest;
|
|
||||||
float postApplyLerpCurrent;
|
|
||||||
GfxFog lastClientFog;
|
|
||||||
int scriptFogActive;
|
|
||||||
int scriptFogLerpStartTime;
|
|
||||||
int scriptFogLerpEndTime;
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(ClientVisionSetData) == 0x3AF0);
|
|
||||||
|
|
||||||
struct cpose_t
|
|
||||||
{
|
|
||||||
char _0x0[0x90];
|
|
||||||
};
|
|
||||||
|
|
||||||
static_assert(sizeof(cpose_t) == 0x90);
|
|
||||||
|
|
||||||
struct cg_t
|
|
||||||
{
|
|
||||||
playerState_t ps;
|
|
||||||
char _0x3324[0x38];
|
|
||||||
int clientNum;
|
|
||||||
int localClientNum;
|
|
||||||
DemoType demoType;
|
|
||||||
char _0x336C[0x1C];
|
|
||||||
snapshot_t* snap;
|
|
||||||
snapshot_t* nextSnap;
|
|
||||||
char _0x3398[0xA66F8];
|
|
||||||
vec3_t refdefViewAngles;
|
|
||||||
char _0xA9A9C[0x8CC4];
|
|
||||||
int clientSideVisionSetsEnabled;
|
|
||||||
int voiceTime;
|
|
||||||
Weapon weaponSelect;
|
|
||||||
char _0xB276C[0x2C10];
|
|
||||||
int inKillCam;
|
|
||||||
char _0xB5380[0x148];
|
|
||||||
bgs_t bgs;
|
|
||||||
cpose_t viewModelPose;
|
|
||||||
cpose_t viewModelPoseLeftHand;
|
|
||||||
ClientVisionSetData cvsData;
|
|
||||||
int stagedVisionStateTo;
|
|
||||||
int stagedVisionStateFrom;
|
|
||||||
int stagedVisionStateEndTime;
|
|
||||||
int stagedVisionStateStartTime;
|
|
||||||
// missing fields
|
|
||||||
};
|
|
||||||
|
|
||||||
struct cgs_t
|
|
||||||
{
|
|
||||||
int viewX;
|
|
||||||
int viewY;
|
|
||||||
int viewWidth;
|
|
||||||
int viewHeight;
|
|
||||||
float viewAspect;
|
|
||||||
float _0x14;
|
|
||||||
int serverCommandSequence;
|
|
||||||
int processedSnapshotNum;
|
|
||||||
int localServer;
|
|
||||||
char gametype[32];
|
|
||||||
char szHostName[256];
|
|
||||||
int hardcore;
|
|
||||||
int maxclients;
|
|
||||||
int privateClients;
|
|
||||||
char mapname[64];
|
|
||||||
int gameEndTime;
|
|
||||||
// missing fields
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct netProfilePacket_t
|
struct netProfilePacket_t
|
||||||
@ -2723,7 +2367,7 @@ namespace game
|
|||||||
netsrc_t sock;
|
netsrc_t sock;
|
||||||
int dropped;
|
int dropped;
|
||||||
int incomingSequence;
|
int incomingSequence;
|
||||||
netadr_t remoteAddress;
|
netadr_s remoteAddress;
|
||||||
int fragmentSequence;
|
int fragmentSequence;
|
||||||
int fragmentLength;
|
int fragmentLength;
|
||||||
char* fragmentBuffer;
|
char* fragmentBuffer;
|
||||||
@ -2738,7 +2382,7 @@ namespace game
|
|||||||
|
|
||||||
struct __declspec(align(8)) clientHeader_t
|
struct __declspec(align(8)) clientHeader_t
|
||||||
{
|
{
|
||||||
clientstate_t state;
|
int state;
|
||||||
int sendAsActive;
|
int sendAsActive;
|
||||||
int deltaMessage;
|
int deltaMessage;
|
||||||
int rateDelayed;
|
int rateDelayed;
|
||||||
@ -2772,11 +2416,8 @@ namespace game
|
|||||||
int reliableAcknowledge;
|
int reliableAcknowledge;
|
||||||
int reliableSent;
|
int reliableSent;
|
||||||
int messageAcknowledge;
|
int messageAcknowledge;
|
||||||
int largeCommandSequence;
|
char _0xC30[0x41238];
|
||||||
int gamestateMessageNum;
|
gentity_s* gentity;
|
||||||
int challenge;
|
|
||||||
char _0xC3C[0x4122C];
|
|
||||||
gentity_t* gentity;
|
|
||||||
char name[16];
|
char name[16];
|
||||||
int lastPacketTime;
|
int lastPacketTime;
|
||||||
int lastConnectTime;
|
int lastConnectTime;
|
||||||
@ -2798,8 +2439,6 @@ namespace game
|
|||||||
char _0x41E94[0x416A0];
|
char _0x41E94[0x416A0];
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(client_t) == 0x83570);
|
|
||||||
|
|
||||||
struct XZone
|
struct XZone
|
||||||
{
|
{
|
||||||
char name[64];
|
char name[64];
|
||||||
@ -2811,7 +2450,7 @@ namespace game
|
|||||||
|
|
||||||
struct missileFireParms
|
struct missileFireParms
|
||||||
{
|
{
|
||||||
gentity_t* target;
|
gentity_s* target;
|
||||||
float targetPosOrOffset[3];
|
float targetPosOrOffset[3];
|
||||||
float autoDetonateTime;
|
float autoDetonateTime;
|
||||||
bool lockon;
|
bool lockon;
|
||||||
@ -2819,23 +2458,14 @@ namespace game
|
|||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(missileFireParms) == 0x20);
|
static_assert(sizeof(missileFireParms) == 0x20);
|
||||||
|
|
||||||
struct serverStatic_t
|
|
||||||
{
|
|
||||||
char _0x0[0x1681D00];
|
|
||||||
int time;
|
|
||||||
int loopbackProcessStopTime;
|
|
||||||
int snapFlagServerBit;
|
|
||||||
int clientCount;
|
|
||||||
client_t clients[18];
|
|
||||||
// missing fields
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
union playerState_t
|
static_assert(sizeof(mp::client_t) == 0x83570);
|
||||||
|
|
||||||
|
union playerState_s
|
||||||
{
|
{
|
||||||
sp::playerState_t* sp;
|
sp::playerState_s* sp;
|
||||||
mp::playerState_t* mp;
|
mp::playerState_s* mp;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum GfxDrawSceneMethod
|
enum GfxDrawSceneMethod
|
||||||
@ -2866,7 +2496,7 @@ namespace game
|
|||||||
TECHNIQUE_NONE = 0x137,
|
TECHNIQUE_NONE = 0x137,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GfxDrawMethod_t
|
struct GfxDrawMethod_s
|
||||||
{
|
{
|
||||||
int drawScene;
|
int drawScene;
|
||||||
int baseTechType;
|
int baseTechType;
|
||||||
@ -2918,8 +2548,8 @@ namespace game
|
|||||||
struct pmove_t
|
struct pmove_t
|
||||||
{
|
{
|
||||||
void* ps;
|
void* ps;
|
||||||
usercmd_t cmd;
|
usercmd_s cmd;
|
||||||
usercmd_t oldcmd;
|
usercmd_s oldcmd;
|
||||||
int tracemask;
|
int tracemask;
|
||||||
int numtouch;
|
int numtouch;
|
||||||
int touchents[32];
|
int touchents[32];
|
||||||
|
@ -8,12 +8,12 @@ namespace game
|
|||||||
* Functions
|
* Functions
|
||||||
**************************************************************/
|
**************************************************************/
|
||||||
|
|
||||||
WEAK symbol<void(unsigned int id)> AddRefToObject{0, 0x1404326D0};
|
WEAK symbol<void(unsigned int id)> AddRefToObject{0x1403D7A10, 0x1404326D0};
|
||||||
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{0x1403D7740, 0x1404326E0};
|
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{0x1403D7740, 0x1404326E0};
|
||||||
WEAK symbol<unsigned int(unsigned int id)> AllocThread{0, 0x1404329B0};
|
WEAK symbol<unsigned int(unsigned int id)> AllocThread{0, 0x1404329B0};
|
||||||
WEAK symbol<ObjectVariableValue*(unsigned int* index)> AllocVariable{0x1403D7A70, 0x140432A10};
|
WEAK symbol<ObjectVariableValue*(unsigned int* index)> AllocVariable{0x1403D7A70, 0x140432A10};
|
||||||
WEAK symbol<void(int type, VariableUnion u)> RemoveRefToValue{0x1403D90F0, 0x1404340C0};
|
WEAK symbol<void(int type, VariableUnion u)> RemoveRefToValue{0x1403D90F0, 0x1404340C0};
|
||||||
WEAK symbol<void(unsigned int id)> RemoveRefToObject{0, 0x140433FB0};
|
WEAK symbol<void(unsigned int id)> RemoveRefToObject{0x1403D8FE0, 0x140433FB0};
|
||||||
WEAK symbol<void(unsigned int parentId, unsigned int index)> RemoveVariableValue{0x1403D91C0, 0x140434190};
|
WEAK symbol<void(unsigned int parentId, unsigned int index)> RemoveVariableValue{0x1403D91C0, 0x140434190};
|
||||||
|
|
||||||
WEAK symbol<void(void*, void*)> AimAssist_AddToTargetList{0, 0x140139D80};
|
WEAK symbol<void(void*, void*)> AimAssist_AddToTargetList{0, 0x140139D80};
|
||||||
@ -36,31 +36,15 @@ namespace game
|
|||||||
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x1403B3050, 0x1403F6B50};
|
WEAK symbol<void(int localClientNum, const char* text)> Cbuf_AddText{0x1403B3050, 0x1403F6B50};
|
||||||
WEAK symbol<void(int localClientNum, int controllerIndex, const char* buffer, void (int, int, const char*))> Cbuf_ExecuteBufferInternal{0x1403B3160, 0x1403F6C60};
|
WEAK symbol<void(int localClientNum, int controllerIndex, const char* buffer, void (int, int, const char*))> Cbuf_ExecuteBufferInternal{0x1403B3160, 0x1403F6C60};
|
||||||
|
|
||||||
WEAK symbol<void(int localClientNum, int latestSequence)> CG_ExecuteNewServerCommands{0, 0x140288500};
|
|
||||||
WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessage{0x1401F2E20, 0x140271320};
|
|
||||||
WEAK symbol<void*(const mp::playerState_t* ps)> CG_GetVehicleDef{0, 0x140229760};
|
|
||||||
WEAK symbol<void(int localClientNum, const mp::centity_t* cent, unsigned int event, unsigned int eventParm, bool isPlayerView)> CG_HandleTurretFire{0, 0x140269DF0};
|
|
||||||
WEAK symbol<void(int localClientNum, mp::cg_t* cg, const char* dvar, const char* value)> CG_SetClientDvarFromServer{0, 0x14028A2C0};
|
|
||||||
WEAK symbol<void(int localClientNum, const mp::snapshot_t* oldSnap, const mp::snapshot_t* newSnap)> CG_UpdateOmnvars{0, 0x14028E770};
|
|
||||||
|
|
||||||
WEAK symbol<void(int localClientNum, int key)> CL_CharEvent{0x14023CE50, 0x1402C2AE0};
|
|
||||||
WEAK symbol<bool(int localClientNum, int clientNum, char* buffer, int length)> CL_GetClientNameColorize{0, 0x1402CFA60};
|
|
||||||
WEAK symbol<bool(const mp::clientActive_t* cl, const int serverTime,
|
|
||||||
mp::playerState_t* to)> CL_GetPredictedPlayerInformationForServerTime{0, 0x1402CC710};
|
|
||||||
WEAK symbol<bool(const mp::clientActive_t* cl, const int serverTime,
|
|
||||||
PlayerVehicleState* predictedPlayerVehState)> CL_GetPredictedVehicleForServerTime{0, 0x1402CC7F0};
|
|
||||||
WEAK symbol<bool()> CL_IsCgameInitialized{0x140234DA0, 0x1402B9A70};
|
WEAK symbol<bool()> CL_IsCgameInitialized{0x140234DA0, 0x1402B9A70};
|
||||||
WEAK symbol<void(int localClientNum, int key, int down, unsigned int time)> CL_KeyEvent{0x14023D070, 0x1402C2CE0};
|
|
||||||
WEAK symbol<void(int localClientNum, msg_t*)> CL_ParseServerMessage{0, 0x1402CDFC0};
|
|
||||||
WEAK symbol<bool(int localClientNum, int weapon, int useAltMode)> CG_SelectWeapon{0, 0x1402AB310};
|
|
||||||
WEAK symbol<void(int localClientNum)> CL_SetCGameTime{0, 0x1402B9D80};
|
|
||||||
WEAK symbol<void(int localClientNum)> CL_WritePacket{0, 0x1402C1E70};
|
|
||||||
|
|
||||||
WEAK symbol<void(const char* cmdName, void (), cmd_function_t* allocedCmd)> Cmd_AddCommandInternal{0x1403B3570, 0x1403F7070};
|
WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessage{0x1401F2E20, 0x140271320};
|
||||||
|
WEAK symbol<void(int localClientNum, mp::cg_s* cg, const char* dvar, const char* value)> CG_SetClientDvarFromServer{0x0, 0x14028A2C0};
|
||||||
|
|
||||||
|
WEAK symbol<void(const char* cmdName, void (), cmd_function_s* allocedCmd)> Cmd_AddCommandInternal{0x1403B3570, 0x1403F7070};
|
||||||
WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x1403B3B10, 0x1403F7680};
|
WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)> Cmd_ExecuteSingleCommand{0x1403B3B10, 0x1403F7680};
|
||||||
WEAK symbol<int(int msec)> Com_TimeScaleMsec{0, 0x140415D50};
|
|
||||||
|
|
||||||
WEAK symbol<void(XAssetType type, void (__cdecl *func)(XAssetHeader, void*), void* inData, bool includeOverride)> DB_EnumXAssets_FastFile{0x140271F50, 0x14031EF90};
|
WEAK symbol<void (XAssetType type, void (__cdecl *func)(XAssetHeader, void*), void* inData, bool includeOverride)> DB_EnumXAssets_FastFile{0x140271F50, 0x14031EF90};
|
||||||
WEAK symbol<void(XAssetType type, void(__cdecl* func)(XAssetHeader, void*), const void* inData, bool includeOverride)> DB_EnumXAssets_Internal{0x140271FC0, 0x14031F000};
|
WEAK symbol<void(XAssetType type, void(__cdecl* func)(XAssetHeader, void*), const void* inData, bool includeOverride)> DB_EnumXAssets_Internal{0x140271FC0, 0x14031F000};
|
||||||
WEAK symbol<XAssetEntry*(XAssetType type, const char* name)> DB_FindXAssetEntry{0x140272230, 0x14031F2D0};
|
WEAK symbol<XAssetEntry*(XAssetType type, const char* name)> DB_FindXAssetEntry{0x140272230, 0x14031F2D0};
|
||||||
WEAK symbol<const char* (const XAsset* asset)> DB_GetXAssetName{0x14024FB10, 0x1402FB160};
|
WEAK symbol<const char* (const XAsset* asset)> DB_GetXAssetName{0x14024FB10, 0x1402FB160};
|
||||||
@ -74,7 +58,7 @@ namespace game
|
|||||||
WEAK symbol<void(const RawFile* rawfile, char* buf, int size)> DB_GetRawBuffer{0x140272D50, 0x14031FE50};
|
WEAK symbol<void(const RawFile* rawfile, char* buf, int size)> DB_GetRawBuffer{0x140272D50, 0x14031FE50};
|
||||||
WEAK symbol<int(const char* zoneName)> DB_IsLocalized{0x140273210, 0x140320360};
|
WEAK symbol<int(const char* zoneName)> DB_IsLocalized{0x140273210, 0x140320360};
|
||||||
|
|
||||||
WEAK symbol<void*(unsigned int size, unsigned int alignment, unsigned int type, PMem_Source source)> PMem_AllocFromSource_NoDebug{0x140430B80, 0x001404F46C0};
|
WEAK symbol<void*(unsigned int size, unsigned int alignment, unsigned int type, PMem_Source source)> PMem_AllocFromSource_NoDebug{0x140430B80, 0x1404F46C0};
|
||||||
WEAK symbol<void(const char* name, PMem_Direction allocDir)> PMem_Free{0x140430EC0 , 0x1404F4A30};
|
WEAK symbol<void(const char* name, PMem_Direction allocDir)> PMem_Free{0x140430EC0 , 0x1404F4A30};
|
||||||
|
|
||||||
WEAK symbol<void*(unsigned int size)> Hunk_AllocateTempMemoryHighInternal{0x140423C70, 0x1404E4E20};
|
WEAK symbol<void*(unsigned int size)> Hunk_AllocateTempMemoryHighInternal{0x140423C70, 0x1404E4E20};
|
||||||
@ -109,20 +93,19 @@ namespace game
|
|||||||
WEAK symbol<void(const char *path, const char *dir, int bLanguageDirectory, int iLanguage)> FS_AddGameDirectory{0x14041A120, 0x1404DC570};
|
WEAK symbol<void(const char *path, const char *dir, int bLanguageDirectory, int iLanguage)> FS_AddGameDirectory{0x14041A120, 0x1404DC570};
|
||||||
WEAK symbol<void(const char *path, const char *dir)> FS_AddLocalizedGameDirectory{0x14041A2F0, 0x1404DC760};
|
WEAK symbol<void(const char *path, const char *dir)> FS_AddLocalizedGameDirectory{0x14041A2F0, 0x1404DC760};
|
||||||
|
|
||||||
WEAK symbol<bool(int localClientNum)> GetRemoteEyeValues{0, 0x1402A0190};
|
|
||||||
|
|
||||||
WEAK symbol<Weapon(const char* pickupName, int model)> G_FindItem{0x140462490, 0x14021B7E0};
|
WEAK symbol<Weapon(const char* pickupName, int model)> G_FindItem{0x140462490, 0x14021B7E0};
|
||||||
WEAK symbol<int(playerState_t* ps, Weapon weapon, int dualWield, int startInAltMode, int usedBefore)> G_GivePlayerWeapon{0x140359E20, 0x1403DA5E0};
|
WEAK symbol<int(playerState_s* ps, Weapon weapon, int dualWield, int startInAltMode, int usedBefore)> G_GivePlayerWeapon{0x140359E20, 0x1403DA5E0};
|
||||||
WEAK symbol<Weapon(const char* name)> G_GetWeaponForName{0x140359890, 0x1403DA060};
|
WEAK symbol<Weapon(const char* name)> G_GetWeaponForName{0x140359890, 0x1403DA060};
|
||||||
WEAK symbol<void()> G_Glass_Update{0x14030E680, 0x140397450};
|
WEAK symbol<void()> G_Glass_Update{0x14030E680, 0x140397450};
|
||||||
WEAK symbol<void (playerState_t* ps, Weapon weapon, int hadWeapon)> G_InitializeAmmo{0x140311F00, 0x14039AEA0};
|
WEAK symbol<void (playerState_s* ps, Weapon weapon, int hadWeapon)> G_InitializeAmmo{0x140311F00, 0x14039AEA0};
|
||||||
WEAK symbol<void(int clientNum, Weapon weapon)> G_SelectWeapon{0x14035A200, 0x1403DA880};
|
WEAK symbol<void(int clientNum, Weapon weapon)> G_SelectWeapon{0x14035A200, 0x1403DA880};
|
||||||
WEAK symbol<int(playerState_t* ps, Weapon weapon)> G_TakePlayerWeapon{0x14035A350, 0x1403DA9C0};
|
WEAK symbol<int(playerState_s* ps, Weapon weapon)> G_TakePlayerWeapon{0x14035A350, 0x1403DA9C0};
|
||||||
WEAK symbol<unsigned int (const char* name, /*ConfigString*/ unsigned int start, unsigned int max, int create,
|
WEAK symbol<unsigned int (const char* name, /*ConfigString*/ unsigned int start, unsigned int max, int create,
|
||||||
const char* errormsg)> G_FindConfigstringIndex{0, 0x140161F90};
|
const char* errormsg)> G_FindConfigstringIndex{0x0, 0x140161F90};
|
||||||
WEAK symbol<int(int server_time)> G_RunFrame{0, 0x1403A05E0};
|
WEAK symbol<int(int server_time)> G_RunFrame{0x0, 0x1403A05E0};
|
||||||
|
WEAK symbol<int(int clientNun)> G_GetClientScore{0x0, 0x14039EF60};
|
||||||
|
|
||||||
WEAK symbol<game_hudelem_t*(int clientNum, int teamNum)> HudElem_Alloc{0, 0x1403997E0};
|
WEAK symbol<game_hudelem_s*(int clientNum, int teamNum)> HudElem_Alloc{0x0, 0x1403997E0};
|
||||||
|
|
||||||
WEAK symbol<char*(char* string)> I_CleanStr{0x140432460, 0x1404F63C0};
|
WEAK symbol<char*(char* string)> I_CleanStr{0x140432460, 0x1404F63C0};
|
||||||
|
|
||||||
@ -134,7 +117,6 @@ namespace game
|
|||||||
|
|
||||||
WEAK symbol<unsigned int (int)> Live_SyncOnlineDataFlags{0, 0x1405ABF70};
|
WEAK symbol<unsigned int (int)> Live_SyncOnlineDataFlags{0, 0x1405ABF70};
|
||||||
|
|
||||||
WEAK symbol<const char*()> LiveStorage_FetchFFotD{0x140415FD0, 0x1404D7C00};
|
|
||||||
WEAK symbol<bool(int controllerIndex, unsigned int name, int value, StatsGroup statsGroup)> LiveStorage_PlayerDataSetIntByName{0x1403B8C20, 0x140404730};
|
WEAK symbol<bool(int controllerIndex, unsigned int name, int value, StatsGroup statsGroup)> LiveStorage_PlayerDataSetIntByName{0x1403B8C20, 0x140404730};
|
||||||
WEAK symbol<bool(std::uint8_t* persistentData, const char* lookupString, int value, std::uint8_t* modifiedFlags, StatsGroup statsGroup)> LiveStorage_PlayerDataSetReservedInt{0x1403B8D00, 0x140404820};
|
WEAK symbol<bool(std::uint8_t* persistentData, const char* lookupString, int value, std::uint8_t* modifiedFlags, StatsGroup statsGroup)> LiveStorage_PlayerDataSetReservedInt{0x1403B8D00, 0x140404820};
|
||||||
WEAK symbol<std::int64_t(std::uint8_t* persistentData, const char* lookupString, const StatsGroup statsGroup)> LiveStorage_PlayerDataGetReservedInt{0x1403B84F0, 0x140403CF0};
|
WEAK symbol<std::int64_t(std::uint8_t* persistentData, const char* lookupString, const StatsGroup statsGroup)> LiveStorage_PlayerDataGetReservedInt{0x1403B84F0, 0x140403CF0};
|
||||||
@ -151,36 +133,31 @@ namespace game
|
|||||||
|
|
||||||
WEAK symbol<StructuredDataDef*(const char* filename, unsigned int maxSize)>StructuredDataDef_GetAsset{0, 0x1404E6560};
|
WEAK symbol<StructuredDataDef*(const char* filename, unsigned int maxSize)>StructuredDataDef_GetAsset{0, 0x1404E6560};
|
||||||
WEAK symbol<StringTable*(const char* fileName, const StringTable** tablePtr)>StringTable_GetAsset{0, 0x1404E6170};
|
WEAK symbol<StringTable*(const char* fileName, const StringTable** tablePtr)>StringTable_GetAsset{0, 0x1404E6170};
|
||||||
WEAK symbol<const char*(const StringTable* table, const int row, const int column)> StringTable_GetColumnValueForRow{0, 0x1404E61A0};
|
WEAK symbol<const char*(const StringTable* table, int row, int column)> StringTable_GetColumnValueForRow{0, 0x1404E61A0};
|
||||||
WEAK symbol<int(const StringTable* table, const int comparisonColumn, const char* value)> StringTable_LookupRowNumForValue{0, 0x1404E6260};
|
WEAK symbol<int(const StringTable* table, int comparisonColumn, const char* value)> StringTable_LookupRowNumForValue{0, 0x1404E6260};
|
||||||
|
WEAK symbol<int(const char* string)> StringTable_HashString{0x1404259A0, 0x1404E6320};
|
||||||
|
|
||||||
WEAK symbol<bool(int localClientNum, void* lua_vm)> LUI_IntermissionBegan{0, 0x1401CEB40};
|
|
||||||
WEAK symbol<void(int localClientNum, const char* menuName, int isPopup, int isModal, unsigned int isExclusive)> LUI_OpenMenu{0x1403FD460, 0x1404B3610};
|
WEAK symbol<void(int localClientNum, const char* menuName, int isPopup, int isModal, unsigned int isExclusive)> LUI_OpenMenu{0x1403FD460, 0x1404B3610};
|
||||||
// Made up name, replaced by ScopedCriticalSection on Black Ops 3
|
// Made up name, replaced by ScopedCriticalSection on Black Ops 3
|
||||||
WEAK symbol<void()> LUI_EnterCriticalSection{0x1401AE940, 0x1401CD040};
|
WEAK symbol<void()> LUI_EnterCriticalSection{0x1401AE940, 0x1401CD040};
|
||||||
WEAK symbol<void()> LUI_LeaveCriticalSection{0x1401B0AA0, 0x1401CF1A0};
|
WEAK symbol<void()> LUI_LeaveCriticalSection{0x1401B0AA0, 0x1401CF1A0};
|
||||||
|
|
||||||
WEAK symbol<bool(int localClientNum, const char* menuName)> Menu_IsMenuOpenAndVisible{0, 0x1404B38A0};
|
WEAK symbol<bool(int localClientNum, const char* menuName)> Menu_IsMenuOpenAndVisible{0x0, 0x1404B38A0};
|
||||||
|
|
||||||
WEAK symbol<Material*(const char* material)> Material_RegisterHandle{0x140523D90, 0x1405F0E20};
|
WEAK symbol<Material*(const char* material)> Material_RegisterHandle{0x140523D90, 0x1405F0E20};
|
||||||
|
|
||||||
WEAK symbol<int(msg_t*)> MSG_ReadLong{0, 0x1404181C0};
|
WEAK symbol<void(netsrc_t, netadr_s*, const char*)> NET_OutOfBandPrint{0, 0x14041D5C0};
|
||||||
WEAK symbol<void(msg_t*, int, unsigned int)> MSG_WriteBits{0, 0x140418740};
|
WEAK symbol<void(netsrc_t sock, int length, const void* data, const netadr_s* to)> NET_SendLoopPacket{0, 0x14041D780};
|
||||||
|
WEAK symbol<bool(const char* s, netadr_s* a)> NET_StringToAdr{0, 0x14041D870};
|
||||||
WEAK symbol<void(netsrc_t, netadr_t*, const char*)> NET_OutOfBandPrint{0, 0x14041D5C0};
|
WEAK symbol<void(netadr_s*, sockaddr*)> NetadrToSockadr{0, 0x1404E53D0};
|
||||||
WEAK symbol<void(netsrc_t sock, int length, const void* data, const netadr_t* to)> NET_SendLoopPacket{0, 0x14041D780};
|
|
||||||
WEAK symbol<bool(const char* s, netadr_t* a)> NET_StringToAdr{0, 0x14041D870};
|
|
||||||
WEAK symbol<void(netadr_t*, sockaddr*)> NetadrToSockadr{0, 0x1404E53D0};
|
|
||||||
|
|
||||||
WEAK symbol<void*(int)> Omnvar_GetDef{0, 0x1404F3E80};
|
|
||||||
|
|
||||||
WEAK symbol<void(float x, float y, float width, float height, float s0, float t0, float s1, float t1,
|
WEAK symbol<void(float x, float y, float width, float height, float s0, float t0, float s1, float t1,
|
||||||
float* color, Material* material)> R_AddCmdDrawStretchPic{0x140234460, 0x140600BE0};
|
float* color, Material* material)> R_AddCmdDrawStretchPic{0x140234460, 0x140600BE0};
|
||||||
WEAK symbol<void(const char*, int, const Font_t*, float, float, float, float, float, const float*, int)> R_AddCmdDrawText{0x140533E40, 0x140601070};
|
WEAK symbol<void(const char*, int, Font_s*, float, float, float, float, float, float*, int)> R_AddCmdDrawText{0x140533E40, 0x140601070};
|
||||||
WEAK symbol<void(const char*, int, const Font_t*, float, float, float, float, float, const float*, int, int, char)> R_AddCmdDrawTextWithCursor{0x140534170, 0x1406013A0};
|
WEAK symbol<void(const char*, int, Font_s*, float, float, float, float, float, const float*, int, int, char)> R_AddCmdDrawTextWithCursor{0x140534170, 0x1406013A0};
|
||||||
WEAK symbol<Font_t*(const char* font)> R_RegisterFont{0x1405130B0, 0x1405DFAC0};
|
WEAK symbol<Font_s*(const char* font)> R_RegisterFont{0x1405130B0, 0x1405DFAC0};
|
||||||
WEAK symbol<void()> R_SyncRenderThread{0x140535AF0, 0x140602D30};
|
WEAK symbol<void()> R_SyncRenderThread{0x140535AF0, 0x140602D30};
|
||||||
WEAK symbol<int(const char* text, int maxChars, Font_t* font)> R_TextWidth{0x140513390, 0x1405DFDB0};
|
WEAK symbol<int(const char* text, int maxChars, Font_s* font)> R_TextWidth{0x140513390, 0x1405DFDB0};
|
||||||
|
|
||||||
WEAK symbol<ScreenPlacement*()> ScrPlace_GetViewPlacement{0x14024D150, 0x1402F6D40};
|
WEAK symbol<ScreenPlacement*()> ScrPlace_GetViewPlacement{0x14024D150, 0x1402F6D40};
|
||||||
|
|
||||||
@ -188,30 +165,35 @@ namespace game
|
|||||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindVariable{0x1403D84F0, 0x1404334A0};
|
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindVariable{0x1403D84F0, 0x1404334A0};
|
||||||
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> FindEntityId{0, 0x1404333A0};
|
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> FindEntityId{0, 0x1404333A0};
|
||||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int id)> GetVariableName{0x1403D8E90, 0x140433E60};
|
WEAK symbol<unsigned int(unsigned int parentId, unsigned int id)> GetVariableName{0x1403D8E90, 0x140433E60};
|
||||||
WEAK symbol<void (VariableValue* result, unsigned int classnum, int entnum, int offset)> GetEntityFieldValue{0x1403DC810, 0x140437860};
|
WEAK symbol<void(VariableValue* result, unsigned int classnum, int entnum, int offset)> GetEntityFieldValue{0x1403DC810, 0x140437860};
|
||||||
WEAK symbol<const float*(const float* v)> Scr_AllocVector{0x1403D9AF0, 0x140434A10};
|
WEAK symbol<const float*(const float* v)> Scr_AllocVector{0x1403D9AF0, 0x140434A10};
|
||||||
WEAK symbol<const char*(unsigned int index)> Scr_GetString{0, 0x140439160};
|
WEAK symbol<const char*(unsigned int index)> Scr_GetString{0x1403DE200, 0x140439160};
|
||||||
WEAK symbol<void(int value)> Scr_AddInt{0, 0x140437E70};
|
WEAK symbol<unsigned int(unsigned int index)> Scr_GetConstString{0x1403DDAC0, 0x140438AD0};
|
||||||
WEAK symbol<void(const char* value)> Scr_AddString{0, 0x1404381D0};
|
WEAK symbol<unsigned int(unsigned int index)> Scr_GetConstIString{0x1403DD8A0, 0x1404388B0};
|
||||||
WEAK symbol<int(unsigned int index)> Scr_GetInt{0, 0x140438E10};
|
WEAK symbol<void(int value)> Scr_AddInt{0x0, 0x140437E70};
|
||||||
WEAK symbol<float(unsigned int index)> Scr_GetFloat{0, 0x140438D60};
|
WEAK symbol<void(const char* value)> Scr_AddString{0x0, 0x1404381D0};
|
||||||
|
WEAK symbol<int(unsigned int index)> Scr_GetInt{0x1403DDEB0, 0x140438E10};
|
||||||
|
WEAK symbol<float(unsigned int index)> Scr_GetFloat{0x1403DDD50, 0x140438D60};
|
||||||
|
WEAK symbol<void(unsigned int index, float* vectorValue)> Scr_GetVector{0x1403DE470, 0x1404393D0};
|
||||||
|
WEAK symbol<unsigned int(unsigned int index)> Scr_GetObject{0x1403DDF70, 0x140438ED0};
|
||||||
WEAK symbol<unsigned int()> Scr_GetNumParam{0x1403DDF60, 0x140438EC0};
|
WEAK symbol<unsigned int()> Scr_GetNumParam{0x1403DDF60, 0x140438EC0};
|
||||||
WEAK symbol<void()> Scr_ClearOutParams{0x1403DD500, 0x140438600};
|
WEAK symbol<void()> Scr_ClearOutParams{0x1403DD500, 0x140438600};
|
||||||
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x1403DBDC0, 0x140436E10};
|
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x1403DBDC0, 0x140436E10};
|
||||||
WEAK symbol<void(int entnum, unsigned int classnum)> Scr_AddEntityNum{0, 0x140437F60};
|
WEAK symbol<void(int entnum, unsigned int classnum)> Scr_AddEntityNum{0x0, 0x140437F60};
|
||||||
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x140350E70, 0x1403D3FE0};
|
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x140350E70, 0x1403D3FE0};
|
||||||
WEAK symbol<void(unsigned int id, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyId{0x1403DE730, 0x140439700};
|
WEAK symbol<void(unsigned int id, unsigned int stringValue, unsigned int paramcount)> Scr_NotifyId{0x1403DE730, 0x140439700};
|
||||||
WEAK symbol<void(unsigned int stringValue, unsigned int paramcount)> Scr_NotifyLevel{0, 0x1404397D0};
|
WEAK symbol<void(unsigned int stringValue, unsigned int paramcount)> Scr_NotifyLevel{0x0, 0x1404397D0};
|
||||||
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> Scr_GetEntityId{0, 0x140436D60};
|
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> Scr_GetEntityId{0x0, 0x140436D60};
|
||||||
WEAK symbol<bool(VariableValue* value)> Scr_CastString{0, 0x140434AC0};
|
WEAK symbol<bool(VariableValue* value)> Scr_CastString{0x1403D9BA0, 0x140434AC0};
|
||||||
|
|
||||||
WEAK symbol<unsigned __int16(int handle, unsigned int paramcount)> Scr_ExecThread{0x1403DD600, 0x1404386E0};
|
WEAK symbol<unsigned __int16(int handle, unsigned int paramcount)> Scr_ExecThread{0x1403DD600, 0x1404386E0};
|
||||||
WEAK symbol<unsigned int(const char* name)> Scr_LoadScript{0x1403D3C50, 0x14042EAA0};
|
WEAK symbol<unsigned int(const char* name)> Scr_LoadScript{0x1403D3C50, 0x14042EAA0};
|
||||||
WEAK symbol<unsigned int(const char* script, unsigned int name)> Scr_GetFunctionHandle{0x1403D3AD0 , 0x14042E920};
|
WEAK symbol<unsigned int(const char* script, unsigned int name)> Scr_GetFunctionHandle{0x1403D3AD0 , 0x14042E920};
|
||||||
WEAK symbol<unsigned int(void* func, int type, unsigned int name)> Scr_RegisterFunction{0x1403D3530, 0x14042E330};
|
WEAK symbol<unsigned int(void* func, int type, unsigned int name)> Scr_RegisterFunction{0x1403D3530, 0x14042E330};
|
||||||
WEAK symbol<void()> Scr_ErrorInternal{0, 0x140438660};
|
WEAK symbol<unsigned int(const char** pName, int* type)> Scr_GetFunction{0x14034A950, 0x1403CD9F0};
|
||||||
|
WEAK symbol<void()> Scr_ErrorInternal{0x1403DD580 , 0x140438660};
|
||||||
|
|
||||||
WEAK symbol<int(unsigned int)> GetObjectType{0, 0x140433CF0};
|
WEAK symbol<int(unsigned int)> GetObjectType{0x1403D8D30, 0x140433CF0};
|
||||||
|
|
||||||
WEAK symbol<unsigned int(unsigned int localId, const char* pos, unsigned int paramcount)> VM_Execute{0, 0x14043A280};
|
WEAK symbol<unsigned int(unsigned int localId, const char* pos, unsigned int paramcount)> VM_Execute{0, 0x14043A280};
|
||||||
|
|
||||||
@ -226,32 +208,31 @@ namespace game
|
|||||||
WEAK symbol<void(int arg, char* buffer, int bufferLength)> SV_Cmd_ArgvBuffer{0x1403B4560, 0x1403F80D0};
|
WEAK symbol<void(int arg, char* buffer, int bufferLength)> SV_Cmd_ArgvBuffer{0x1403B4560, 0x1403F80D0};
|
||||||
WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0, 0x1403F8150};
|
WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0, 0x1403F8150};
|
||||||
WEAK symbol<void()> SV_Cmd_EndTokenizedString{0, 0x1403F8110};
|
WEAK symbol<void()> SV_Cmd_EndTokenizedString{0, 0x1403F8110};
|
||||||
WEAK symbol<void()> SV_MatchEnd{0, 0x14047A090};
|
WEAK symbol<void()> SV_MatchEnd{0x0, 0x14047A090};
|
||||||
|
|
||||||
WEAK symbol<void(netadr_t* from)> SV_DirectConnect{0, 0x140471390};
|
WEAK symbol<void(netadr_s* from)> SV_DirectConnect{0, 0x140471390};
|
||||||
WEAK symbol<void(int, int, const char*)> SV_GameSendServerCommand{0x140490F40, 0x1404758C0};
|
|
||||||
WEAK symbol<bool()> SV_Loaded{0x140491820, 0x1404770C0};
|
WEAK symbol<bool()> SV_Loaded{0x140491820, 0x1404770C0};
|
||||||
WEAK symbol<void(int localClientNum, const char* map, bool mapIsPreloaded)> SV_StartMap{0, 0x140470170};
|
WEAK symbol<void(int localClientNum, const char* map, bool mapIsPreloaded)> SV_StartMap{0, 0x140470170};
|
||||||
WEAK symbol<void(int localClientNum, const char* map, bool mapIsPreloaded, bool migrate)> SV_StartMapForParty{0, 0x1404702F0};
|
WEAK symbol<void(int localClientNum, const char* map, bool mapIsPreloaded, bool migrate)> SV_StartMapForParty{0, 0x1404702F0};
|
||||||
|
|
||||||
WEAK symbol<mp::gentity_t*(const char*, unsigned int, unsigned int, unsigned int)> SV_AddBot{0, 0x140470920};
|
WEAK symbol<mp::gentity_s*(const char*, unsigned int, unsigned int, unsigned int)> SV_AddBot{0, 0x140470920};
|
||||||
WEAK symbol<bool(int clientNum)> SV_BotIsBot{0, 0x140461340};
|
WEAK symbol<bool(int clientNum)> SV_BotIsBot{0, 0x140461340};
|
||||||
WEAK symbol<const char*()> SV_BotGetRandomName{0, 0x140460B80};
|
WEAK symbol<const char*()> SV_BotGetRandomName{0, 0x140460B80};
|
||||||
WEAK symbol<void(mp::gentity_t*)> SV_SpawnTestClient{0, 0x1404740A0};
|
WEAK symbol<void(mp::gentity_s*)> SV_SpawnTestClient{0, 0x1404740A0};
|
||||||
|
|
||||||
WEAK symbol<void(mp::client_t*, const char*, int)> SV_ExecuteClientCommand{0, 0x140472430};
|
WEAK symbol<void(mp::client_t*, const char*, int)> SV_ExecuteClientCommand{0, 0x140472430};
|
||||||
WEAK symbol<void()> SV_FastRestart{0x14048B890, 0x14046F440};
|
WEAK symbol<void()> SV_FastRestart{0x14048B890, 0x14046F440};
|
||||||
WEAK symbol<playerState_t*(int num)> SV_GetPlayerstateForClientNum{0x140490F80, 0x140475A10};
|
WEAK symbol<playerState_s*(int num)> SV_GetPlayerstateForClientNum{0x140490F80, 0x140475A10};
|
||||||
WEAK symbol<const char*(int clientNum)> SV_GetGuid{0, 0x140475990};
|
WEAK symbol<const char*(int clientNum)> SV_GetGuid{0, 0x140475990};
|
||||||
WEAK symbol<void(int clientNum, const char* reason)> SV_KickClientNum{0, 0x14046F730};
|
WEAK symbol<void(int clientNum, const char* reason)> SV_KickClientNum{0, 0x14046F730};
|
||||||
WEAK symbol<bool(mp::client_t*, const char*, int)> SV_Netchan_Transmit{0, 0x14047CC60};
|
|
||||||
WEAK symbol<void(int index, const char* string)> SV_SetConfigstring{0, 0x140477450};
|
WEAK symbol<void(int index, const char* string)> SV_SetConfigstring{0, 0x140477450};
|
||||||
|
WEAK symbol<void(int clientNum, svscmd_type type, const char* text)> SV_GameSendServerCommand{0x140490F40, 0x1404758C0};
|
||||||
|
|
||||||
WEAK symbol<void(const char* error, ...)> Sys_Error{0x14043AC20, 0x1404FF510};
|
WEAK symbol<void(const char* error, ...)> Sys_Error{0x14043AC20, 0x1404FF510};
|
||||||
WEAK symbol<bool()> Sys_IsDatabaseReady2{0x1403C2D40, 0x140423920};
|
WEAK symbol<bool()> Sys_IsDatabaseReady2{0x1403C2D40, 0x140423920};
|
||||||
WEAK symbol<int()> Sys_Milliseconds{0x14043D2A0, 0x140501CA0};
|
WEAK symbol<int()> Sys_Milliseconds{0x14043D2A0, 0x140501CA0};
|
||||||
WEAK symbol<void()> Sys_ShowConsole{0x14043E650, 0x140503130};
|
WEAK symbol<void()> Sys_ShowConsole{0x14043E650, 0x140503130};
|
||||||
WEAK symbol<bool(int, void const*, const netadr_t*)> Sys_SendPacket{0x14043D000, 0x140501A00};
|
WEAK symbol<bool(int, void const*, const netadr_s*)> Sys_SendPacket{0x14043D000, 0x140501A00};
|
||||||
WEAK symbol<void*(int valueIndex)> Sys_GetValue{0x1403C2C30, 0x1404237D0};
|
WEAK symbol<void*(int valueIndex)> Sys_GetValue{0x1403C2C30, 0x1404237D0};
|
||||||
WEAK symbol<bool()> Sys_IsMainThread{0x1478FC470, 0x140423950};
|
WEAK symbol<bool()> Sys_IsMainThread{0x1478FC470, 0x140423950};
|
||||||
WEAK symbol<HANDLE(Sys_Folder folder, const char* baseFilename)> Sys_CreateFile{0x140434B10, 0x1404F8FD0};
|
WEAK symbol<HANDLE(Sys_Folder folder, const char* baseFilename)> Sys_CreateFile{0x140434B10, 0x1404F8FD0};
|
||||||
@ -263,9 +244,6 @@ namespace game
|
|||||||
WEAK symbol<const char*(const char*)> UI_LocalizeMapname{0, 0x1404B96D0};
|
WEAK symbol<const char*(const char*)> UI_LocalizeMapname{0, 0x1404B96D0};
|
||||||
WEAK symbol<const char*(const char*)> UI_LocalizeGametype{0, 0x1404B90F0};
|
WEAK symbol<const char*(const char*)> UI_LocalizeGametype{0, 0x1404B90F0};
|
||||||
|
|
||||||
WEAK symbol<void(int localClientNum, mp::cg_t* cg, vec3_t* outViewOrigin,
|
|
||||||
vec3_t* outViewAngles)> VehicleCam_UpdatePlayerControlCam{0, 0x140568CA0};
|
|
||||||
|
|
||||||
WEAK symbol<DWOnlineStatus(int)> dwGetLogOnStatus{0, 0x140589490};
|
WEAK symbol<DWOnlineStatus(int)> dwGetLogOnStatus{0, 0x140589490};
|
||||||
|
|
||||||
WEAK symbol<void(pmove_t* move, trace_t*, const float*, const float*,
|
WEAK symbol<void(pmove_t* move, trace_t*, const float*, const float*,
|
||||||
@ -273,7 +251,9 @@ namespace game
|
|||||||
WEAK symbol<void(const pmove_t* move, trace_t* trace, const float*,
|
WEAK symbol<void(const pmove_t* move, trace_t* trace, const float*,
|
||||||
const float*, const Bounds*, int, int)> PM_trace{0, 0x140225DB0};
|
const float*, const Bounds*, int, int)> PM_trace{0, 0x140225DB0};
|
||||||
|
|
||||||
WEAK symbol<void(void* ps)> Jump_ClearState{0, 0x140213120};
|
WEAK symbol<void(void* ps)> Jump_ClearState{0x0, 0x140213120};
|
||||||
|
|
||||||
|
WEAK symbol<void(unsigned __int64 markPos)> LargeLocalResetToMark{0x140423B50, 0x1404E4D00};
|
||||||
|
|
||||||
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x14062E030, 0x140738060};
|
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x14062E030, 0x140738060};
|
||||||
WEAK symbol<int (jmp_buf* Buf)> _setjmp{0x14062F030, 0x140739060};
|
WEAK symbol<int (jmp_buf* Buf)> _setjmp{0x14062F030, 0x140739060};
|
||||||
@ -286,7 +266,7 @@ namespace game
|
|||||||
|
|
||||||
WEAK symbol<CmdArgs> cmd_args{0x144CE7F70, 0x144518480};
|
WEAK symbol<CmdArgs> cmd_args{0x144CE7F70, 0x144518480};
|
||||||
WEAK symbol<CmdArgs> sv_cmd_args{0x144CE8020, 0x144518530};
|
WEAK symbol<CmdArgs> sv_cmd_args{0x144CE8020, 0x144518530};
|
||||||
WEAK symbol<cmd_function_t*> cmd_functions{0x144CE80C8, 0x1445185D8};
|
WEAK symbol<cmd_function_s*> cmd_functions{0x144CE80C8, 0x1445185D8};
|
||||||
|
|
||||||
WEAK symbol<int> dvarCount{0x1458CBA3C, 0x1478EADF4};
|
WEAK symbol<int> dvarCount{0x1458CBA3C, 0x1478EADF4};
|
||||||
WEAK symbol<dvar_t*> sortedDvars{0x1458CBA60, 0x1478EAE10};
|
WEAK symbol<dvar_t*> sortedDvars{0x1458CBA60, 0x1478EAE10};
|
||||||
@ -323,37 +303,36 @@ namespace game
|
|||||||
WEAK symbol<function_stack_t> scr_function_stack{0x1455BE708, 0x144D57808};
|
WEAK symbol<function_stack_t> scr_function_stack{0x1455BE708, 0x144D57808};
|
||||||
WEAK symbol<unsigned int> scr_levelEntityId{0x1452A9F30, 0x144A43020};
|
WEAK symbol<unsigned int> scr_levelEntityId{0x1452A9F30, 0x144A43020};
|
||||||
|
|
||||||
WEAK symbol<int> level_time{0, 0x1443F4B6C};
|
WEAK symbol<int> level_time{0x0, 0x1443F4B6C};
|
||||||
WEAK symbol<int> level_finished{0, 0x1443F6FAC};
|
WEAK symbol<int> level_finished{0x0, 0x1443F6FAC};
|
||||||
WEAK symbol<int> level_savepersist{0, 0x1443F5710};
|
WEAK symbol<int> level_savepersist{0x0, 0x1443F5710};
|
||||||
|
|
||||||
WEAK symbol<DWORD> threadIds{0x144DE6640, 0x1446B4960};
|
WEAK symbol<DWORD> threadIds{0x144DE6640, 0x1446B4960};
|
||||||
|
|
||||||
WEAK symbol<GfxDrawMethod_t> gfxDrawMethod{0x145F525A8, 0x1480350D8};
|
WEAK symbol<GfxDrawMethod_s> gfxDrawMethod{0x145F525A8, 0x1480350D8};
|
||||||
|
|
||||||
namespace sp
|
namespace sp
|
||||||
{
|
{
|
||||||
WEAK symbol<gentity_t> g_entities{0x143C91600, 0};
|
WEAK symbol<gentity_s> g_entities{0x143C91600, 0};
|
||||||
|
|
||||||
WEAK symbol<XZone> g_zones_0{0x1434892D8, 0};
|
WEAK symbol<XZone> g_zones_0{0x1434892D8, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace mp
|
namespace mp
|
||||||
{
|
{
|
||||||
WEAK symbol<cg_t> cg{0, 0x14176EC00};
|
WEAK symbol<cg_s> cgArray{0, 0x14176EC00};
|
||||||
WEAK symbol<cgs_t> cgs{0, 0x14187EB80};
|
|
||||||
WEAK symbol<clientActive_t> cl{0, 0x1419E1C70};
|
|
||||||
WEAK symbol<clientConnection_t> clc{0, 0x141CB5350};
|
|
||||||
WEAK symbol<clientStatic_t> cls{0, 0x141D1AB60};
|
|
||||||
WEAK symbol<serverStatic_t> svs{0, 0x144DF9580};
|
|
||||||
|
|
||||||
WEAK symbol<gentity_t> g_entities{0, 0x14427A0E0};
|
WEAK symbol<gentity_s> g_entities{0, 0x14427A0E0};
|
||||||
WEAK symbol<centity_t> centities{0, 0x141887350};
|
|
||||||
|
WEAK symbol<int> svs_clientCount{0, 0x14647B28C};
|
||||||
|
WEAK symbol<client_t> svs_clients{0, 0x14647B290};
|
||||||
|
|
||||||
|
WEAK symbol<std::uint32_t> sv_serverId_value{0, 0x144DF9478};
|
||||||
|
|
||||||
|
WEAK symbol<int> gameTime{0, 0x1443F4B6C};
|
||||||
|
WEAK symbol<int> serverTime{0, 0x14647B280};
|
||||||
|
|
||||||
WEAK symbol<XZone> g_zones_0{0, 0x143A46498};
|
WEAK symbol<XZone> g_zones_0{0, 0x143A46498};
|
||||||
WEAK symbol<connstate_t> connstate{0, 0x1419E1AE0};
|
|
||||||
WEAK symbol<int> gameTime{0, 0x1443F4B6C};
|
|
||||||
WEAK symbol<std::uint32_t> sv_serverId_value{0, 0x144DF9478};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace hks
|
namespace hks
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
#include "loader/component_loader.hpp"
|
#include "loader/component_loader.hpp"
|
||||||
#include "game/game.hpp"
|
#include "game/game.hpp"
|
||||||
|
|
||||||
#include "utils/command_line.hpp"
|
|
||||||
#include <utils/flags.hpp>
|
#include <utils/flags.hpp>
|
||||||
#include <utils/io.hpp>
|
#include <utils/io.hpp>
|
||||||
#include <utils/string.hpp>
|
#include <utils/string.hpp>
|
||||||
@ -168,23 +167,8 @@ FARPROC load_binary(const launcher::mode mode)
|
|||||||
std::string data;
|
std::string data;
|
||||||
if (!utils::io::read_file(binary, &data))
|
if (!utils::io::read_file(binary, &data))
|
||||||
{
|
{
|
||||||
// Check the first argument to see if the current directory needs to changed
|
throw std::runtime_error(
|
||||||
// Required when the game is used to open a file (like a demo file)
|
"Failed to read game binary! Please select the correct path in the launcher settings.");
|
||||||
const auto& args = utils::command_line::get_args();
|
|
||||||
if (!args.empty())
|
|
||||||
{
|
|
||||||
const auto& binary_dir = args.front();
|
|
||||||
if (binary_dir.filename().string().ends_with("iw6-mod.exe"))
|
|
||||||
{
|
|
||||||
std::filesystem::current_path(binary_dir.parent_path());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!utils::io::read_file(binary, &data))
|
|
||||||
{
|
|
||||||
throw std::runtime_error(
|
|
||||||
"Failed to read game binary! Please select the correct path in the launcher settings.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef INJECT_HOST_AS_LIB
|
#ifdef INJECT_HOST_AS_LIB
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
#define BINARY_PAYLOAD_SIZE 0x0B500000
|
#define BINARY_PAYLOAD_SIZE 0x0B500000
|
||||||
|
|
||||||
// Decide whether to load the game as lib or to inject it
|
// Decide whether to load the game as lib or to inject it
|
||||||
|
#if 1
|
||||||
#define INJECT_HOST_AS_LIB
|
#define INJECT_HOST_AS_LIB
|
||||||
|
#endif
|
||||||
|
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable: 4100)
|
#pragma warning(disable: 4100)
|
||||||
@ -46,10 +48,7 @@
|
|||||||
#undef min
|
#undef min
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <cassert>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <format>
|
#include <format>
|
||||||
@ -57,7 +56,6 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <numeric>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
#include "command_line.hpp"
|
|
||||||
#include "nt.hpp"
|
|
||||||
|
|
||||||
#include <shellapi.h>
|
|
||||||
#include <winrt/Windows.Foundation.h>
|
|
||||||
|
|
||||||
namespace utils::command_line
|
|
||||||
{
|
|
||||||
template <typename type, typename deleter>
|
|
||||||
constexpr std::unique_ptr<type, deleter> make_unique_ptr(type* ptr, deleter&& del)
|
|
||||||
{
|
|
||||||
return std::unique_ptr<type, deleter&&>(ptr, std::forward<deleter>(del));
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<std::filesystem::path>& get_args()
|
|
||||||
{
|
|
||||||
static const auto args = []()
|
|
||||||
{
|
|
||||||
auto argc = 0;
|
|
||||||
const auto cmd_line = winrt::check_pointer(::GetCommandLineW());
|
|
||||||
const auto up = make_unique_ptr(winrt::check_pointer(::CommandLineToArgvW(cmd_line, &argc)), ::LocalFree);
|
|
||||||
const auto argv = up.get();
|
|
||||||
|
|
||||||
std::vector<std::filesystem::path> vec(argc);
|
|
||||||
std::transform(argv, argv + argc, vec.begin(), [](const auto* path)
|
|
||||||
{
|
|
||||||
return std::filesystem::path(path);
|
|
||||||
});
|
|
||||||
|
|
||||||
return vec;
|
|
||||||
}();
|
|
||||||
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace utils::command_line
|
|
||||||
{
|
|
||||||
[[nodiscard]] const std::vector<std::filesystem::path>& get_args();
|
|
||||||
}
|
|
@ -109,86 +109,6 @@ namespace utils::compression
|
|||||||
result.resize(length);
|
result.resize(length);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::uint8_t> decompress(std::span<const std::uint8_t> input, std::size_t exp_output_size, double growth_rate)
|
|
||||||
{
|
|
||||||
auto decompress_internal = [](std::uint8_t* output, uLong* output_size, const std::uint8_t* input, uLong* input_size)
|
|
||||||
{
|
|
||||||
return uncompress2(
|
|
||||||
reinterpret_cast<Bytef*>(output), output_size,
|
|
||||||
reinterpret_cast<const Bytef*>(input), input_size
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto output_size = static_cast<uLong>(std::max(exp_output_size, std::size_t(256)));
|
|
||||||
std::vector<std::uint8_t> output(output_size);
|
|
||||||
|
|
||||||
auto input_size = static_cast<uLong>(input.size());
|
|
||||||
auto status = decompress_internal(output.data(), &output_size, input.data(), &input_size);
|
|
||||||
|
|
||||||
if (status == Z_BUF_ERROR)
|
|
||||||
{
|
|
||||||
const auto decompression_ratio = (input_size / static_cast<double>(output.size())) - 0.04;
|
|
||||||
output_size = static_cast<uLong>(std::max(input.size() / decompression_ratio, static_cast<double>(output_size)));
|
|
||||||
output.resize(output_size);
|
|
||||||
|
|
||||||
input_size = static_cast<uLong>(input.size());
|
|
||||||
status = decompress_internal(output.data(), &output_size, input.data(), &input_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (growth_rate = std::max(growth_rate, 1.0); status == Z_BUF_ERROR;)
|
|
||||||
{
|
|
||||||
output_size = 4 + static_cast<uLong>(growth_rate * output.size());
|
|
||||||
output.resize(output_size);
|
|
||||||
|
|
||||||
input_size = static_cast<uLong>(input.size());
|
|
||||||
status = decompress_internal(output.data(), &output_size, input.data(), &input_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status != Z_OK)
|
|
||||||
{
|
|
||||||
output.clear();
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.resize(output_size);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::uint8_t> compress(std::span<const std::uint8_t> input, std::size_t exp_output_size, double growth_rate)
|
|
||||||
{
|
|
||||||
auto compress_internal = [](std::uint8_t* output, uLong* output_size, const std::uint8_t* input, uLong input_size)
|
|
||||||
{
|
|
||||||
return compress2(
|
|
||||||
reinterpret_cast<Bytef*>(output), output_size,
|
|
||||||
reinterpret_cast<const Bytef*>(input), input_size,
|
|
||||||
Z_BEST_COMPRESSION
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto output_size = static_cast<uLong>(std::max(exp_output_size, std::size_t(256)));
|
|
||||||
std::vector<std::uint8_t> output(output_size);
|
|
||||||
|
|
||||||
const auto input_size = static_cast<uLong>(input.size());
|
|
||||||
auto status = compress_internal(output.data(), &output_size, input.data(), input_size);
|
|
||||||
|
|
||||||
for (growth_rate = std::max(growth_rate, 1.0); status == Z_BUF_ERROR;)
|
|
||||||
{
|
|
||||||
output_size = 4 + static_cast<uLong>(growth_rate * output.size());
|
|
||||||
output.resize(output_size);
|
|
||||||
|
|
||||||
status = compress_internal(output.data(), &output_size, input.data(), input_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status != Z_OK)
|
|
||||||
{
|
|
||||||
output.clear();
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
output.resize(output_size);
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace zip
|
namespace zip
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <span>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#define CHUNK 16384u
|
#define CHUNK 16384u
|
||||||
|
|
||||||
@ -13,9 +11,6 @@ namespace utils::compression
|
|||||||
{
|
{
|
||||||
std::string compress(const std::string& data);
|
std::string compress(const std::string& data);
|
||||||
std::string decompress(const std::string& data);
|
std::string decompress(const std::string& data);
|
||||||
|
|
||||||
std::vector<std::uint8_t> compress(std::span<const std::uint8_t> input, std::size_t exp_output_size, double growth_rate = 2.0);
|
|
||||||
std::vector<std::uint8_t> decompress(std::span<const std::uint8_t> input, std::size_t exp_output_size, double growth_rate = 2.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace zip
|
namespace zip
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
#include "string.hpp"
|
#include "string.hpp"
|
||||||
|
|
||||||
#include <MinHook.h>
|
#include <MinHook.h>
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
namespace utils::hook
|
namespace utils::hook
|
||||||
{
|
{
|
||||||
@ -280,7 +279,6 @@ namespace utils::hook
|
|||||||
void* result = nullptr;
|
void* result = nullptr;
|
||||||
runtime.add(&result, &code);
|
runtime.add(&result, &code);
|
||||||
|
|
||||||
assert(result);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
src/common/utils/properties.cpp
Normal file
25
src/common/utils/properties.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include "properties.hpp"
|
||||||
|
|
||||||
|
#include <gsl/gsl>
|
||||||
|
|
||||||
|
#include <ShlObj.h>
|
||||||
|
|
||||||
|
namespace utils::properties
|
||||||
|
{
|
||||||
|
std::filesystem::path get_appdata_path()
|
||||||
|
{
|
||||||
|
PWSTR path;
|
||||||
|
if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path)))
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Failed to read APPDATA path!");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto _ = gsl::finally([&path]
|
||||||
|
{
|
||||||
|
CoTaskMemFree(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
static auto appdata = std::filesystem::path(path) / "alterware";
|
||||||
|
return appdata;
|
||||||
|
}
|
||||||
|
}
|
7
src/common/utils/properties.hpp
Normal file
7
src/common/utils/properties.hpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
namespace utils::properties
|
||||||
|
{
|
||||||
|
std::filesystem::path get_appdata_path();
|
||||||
|
}
|
@ -115,13 +115,13 @@ namespace utils::string
|
|||||||
|
|
||||||
void strip(const char* in, char* out, size_t max)
|
void strip(const char* in, char* out, size_t max)
|
||||||
{
|
{
|
||||||
if (!in || !out || !max) return;
|
if (!in || !out) return;
|
||||||
|
|
||||||
max--;
|
max--;
|
||||||
auto current = 0u;
|
auto current = 0u;
|
||||||
while (*in != 0 && current < max)
|
while (*in != 0 && current < max)
|
||||||
{
|
{
|
||||||
const auto color_index = (static_cast<size_t>(*(in + 1) - 48)) >= 0xC ? 7 : (*(in + 1) - 48);
|
const auto color_index = (*(in + 1) - 48) >= 0xC ? 7 : (*(in + 1) - 48);
|
||||||
|
|
||||||
if (*in == '^' && (color_index != 7 || *(in + 1) == '7'))
|
if (*in == '^' && (color_index != 7 || *(in + 1) == '7'))
|
||||||
{
|
{
|
||||||
@ -140,32 +140,6 @@ namespace utils::string
|
|||||||
*out = '\0';
|
*out = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string strip(std::string_view sv, bool strip_default_color)
|
|
||||||
{
|
|
||||||
std::string in(sv);
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i + 1 < in.size();)
|
|
||||||
{
|
|
||||||
if (in[i] == '^' && static_cast<std::size_t>(in[i + 1] - '0') < 0xC)
|
|
||||||
{
|
|
||||||
if (in[i + 1] != '7' || strip_default_color)
|
|
||||||
{
|
|
||||||
in.erase(in.begin() + i, in.begin() + i + 2);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
i += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return in;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string convert(const std::wstring& wstr)
|
std::string convert(const std::wstring& wstr)
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "memory.hpp"
|
#include "memory.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <span>
|
|
||||||
|
|
||||||
template <class Type, size_t n>
|
template <class Type, size_t n>
|
||||||
constexpr auto ARRAY_COUNT(Type(&)[n]) { return n; }
|
constexpr auto ARRAY_COUNT(Type(&)[n]) { return n; }
|
||||||
@ -92,7 +91,6 @@ namespace utils::string
|
|||||||
std::string get_clipboard_data();
|
std::string get_clipboard_data();
|
||||||
|
|
||||||
void strip(const char* in, char* out, size_t max);
|
void strip(const char* in, char* out, size_t max);
|
||||||
std::string strip(std::string_view sv, bool strip_default_color = false);
|
|
||||||
|
|
||||||
std::string convert(const std::wstring& wstr);
|
std::string convert(const std::wstring& wstr);
|
||||||
std::wstring convert(const std::string& str);
|
std::wstring convert(const std::string& str);
|
||||||
|
Reference in New Issue
Block a user