client upload
This commit is contained in:
231
src/client/component/auth.cpp
Normal file
231
src/client/component/auth.cpp
Normal file
@ -0,0 +1,231 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "auth.hpp"
|
||||
#include "component/command.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 "game/game.hpp"
|
||||
#include "steam/steam.hpp"
|
||||
|
||||
namespace auth
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::string get_hdd_serial()
|
||||
{
|
||||
DWORD serial{};
|
||||
if (!GetVolumeInformationA("C:\\", nullptr, 0, &serial, nullptr, nullptr, nullptr, 0))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return utils::string::va("%08X", serial);
|
||||
}
|
||||
|
||||
std::string get_hw_profile_guid()
|
||||
{
|
||||
HW_PROFILE_INFO info;
|
||||
if (!GetCurrentHwProfileA(&info))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::string{ info.szHwProfileGuid, sizeof(info.szHwProfileGuid) };
|
||||
}
|
||||
|
||||
std::string get_protected_data()
|
||||
{
|
||||
std::string input = "X-Labs-S1x-Auth";
|
||||
|
||||
DATA_BLOB data_in{}, data_out{};
|
||||
data_in.pbData = reinterpret_cast<uint8_t*>(input.data());
|
||||
data_in.cbData = static_cast<DWORD>(input.size());
|
||||
if (CryptProtectData(&data_in, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_LOCAL_MACHINE, &data_out) != TRUE)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto size = std::min(data_out.cbData, 52ul);
|
||||
std::string result{ reinterpret_cast<char*>(data_out.pbData), size };
|
||||
LocalFree(data_out.pbData);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string get_key_entropy()
|
||||
{
|
||||
std::string entropy{};
|
||||
entropy.append(utils::smbios::get_uuid());
|
||||
entropy.append(get_hw_profile_guid());
|
||||
entropy.append(get_protected_data());
|
||||
entropy.append(get_hdd_serial());
|
||||
|
||||
if (entropy.empty())
|
||||
{
|
||||
entropy.resize(32);
|
||||
utils::cryptography::random::get_data(entropy.data(), entropy.size());
|
||||
}
|
||||
|
||||
return entropy;
|
||||
}
|
||||
|
||||
utils::cryptography::ecc::key& get_key()
|
||||
{
|
||||
static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy());
|
||||
return key;
|
||||
}
|
||||
|
||||
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);
|
||||
game::SV_Cmd_TokenizeString(connect_string.data());
|
||||
const auto _ = gsl::finally([]()
|
||||
{
|
||||
game::SV_Cmd_EndTokenizedString();
|
||||
});
|
||||
|
||||
const command::params_sv params;
|
||||
if (params.size() < 3)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const utils::info_string info_string{ std::string{params[2]} };
|
||||
const auto challenge = info_string.get("challenge");
|
||||
|
||||
connect_string.clear();
|
||||
connect_string.append(params[0]);
|
||||
connect_string.append(" ");
|
||||
connect_string.append(params[1]);
|
||||
connect_string.append(" ");
|
||||
connect_string.append("\"" + info_string.build() + "\"");
|
||||
|
||||
proto::network::connect_info info;
|
||||
info.set_publickey(get_key().get_public_key());
|
||||
info.set_signature(sign_message(get_key(), challenge));
|
||||
info.set_infostring(connect_string);
|
||||
|
||||
network::send(*adr, "connect", info.SerializeAsString());
|
||||
return true;
|
||||
}
|
||||
|
||||
void direct_connect(game::netadr_s* from, game::msg_t* msg)
|
||||
{
|
||||
const auto offset = sizeof("connect") + 4;
|
||||
|
||||
proto::network::connect_info info;
|
||||
if (!info.ParseFromArray(msg->data + offset, msg->cursize - offset))
|
||||
{
|
||||
network::send(*from, "error", "Invalid connect data!", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
game::SV_Cmd_EndTokenizedString();
|
||||
game::SV_Cmd_TokenizeString(info.infostring().data());
|
||||
|
||||
const command::params_sv params;
|
||||
if (params.size() < 3)
|
||||
{
|
||||
network::send(*from, "error", "Invalid connect string!", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
const utils::info_string info_string{ std::string{params[2]} };
|
||||
|
||||
const auto steam_id = info_string.get("xuid");
|
||||
const auto challenge = info_string.get("challenge");
|
||||
|
||||
if (steam_id.empty() || challenge.empty())
|
||||
{
|
||||
network::send(*from, "error", "Invalid connect data!", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
utils::cryptography::ecc::key key;
|
||||
key.set(info.publickey());
|
||||
|
||||
const auto xuid = strtoull(steam_id.data(), nullptr, 16);
|
||||
if (xuid != key.get_hash())
|
||||
{
|
||||
//MessageBoxA(nullptr, steam_id.data(), std::to_string(key.get_hash()).data(), 0);
|
||||
network::send(*from, "error",
|
||||
utils::string::va("XUID doesn't match the certificate: %llX != %llX", xuid, key.get_hash()), '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!key.is_valid() || !verify_message(key, challenge, info.signature()))
|
||||
{
|
||||
network::send(*from, "error", "Challenge signature was invalid!", '\n');
|
||||
return;
|
||||
}
|
||||
|
||||
game::SV_DirectConnect(from);
|
||||
}
|
||||
|
||||
void* get_direct_connect_stub()
|
||||
{
|
||||
return utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
a.lea(rcx, qword_ptr(rsp, 0x20));
|
||||
a.movaps(xmmword_ptr(rsp, 0x20), xmm0);
|
||||
|
||||
a.pushad64();
|
||||
a.mov(rdx, rdi);
|
||||
a.call_aligned(direct_connect);
|
||||
a.popad64();
|
||||
|
||||
a.jmp(0x140488CE2); // H1MP64(1.4)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t get_guid()
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return 0x110000100000000 | (::utils::cryptography::random::get_integer() & ~0x80000000);
|
||||
}
|
||||
|
||||
return get_key().get_hash();
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Patch steam id bit check
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
utils::hook::jump(0x1404267F0, 0x140426846); // S1SP
|
||||
utils::hook::jump(0x14042760F, 0x140427650); // S1SP
|
||||
utils::hook::jump(0x140427AB4, 0x140427B02); // S1SP
|
||||
}
|
||||
else
|
||||
{
|
||||
utils::hook::jump(0x140571E07, 0x140571E5A); // H1MP64[1.4]
|
||||
utils::hook::jump(0x14004B223, 0x14004B4F2); // H1MP64[1.4]
|
||||
utils::hook::jump(0x14004B4AD, 0x140009B48); // H1MP64[1.4]
|
||||
utils::hook::jump(0x140572F6F, 0x140572FB0); // H1MP64[1.4]
|
||||
utils::hook::jump(0x140573470, 0x1405734B6); // H1MP64[1.4]
|
||||
|
||||
utils::hook::jump(0x140488BC1, get_direct_connect_stub(), true); // H1MP64[1.4]
|
||||
utils::hook::call(0x140250ED2, send_connect_data_stub); // H1MP64[1.4]
|
||||
}
|
||||
|
||||
command::add("guid", []()
|
||||
{
|
||||
printf("Your guid: %llX\n", steam::SteamUser()->GetSteamID().bits);
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(auth::component)
|
6
src/client/component/auth.hpp
Normal file
6
src/client/component/auth.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace auth
|
||||
{
|
||||
uint64_t get_guid();
|
||||
}
|
60
src/client/component/branding.cpp
Normal file
60
src/client/component/branding.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "command.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace branding
|
||||
{
|
||||
game::dvar_t* dvar_draw2d;
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
//localized_strings::override("MENU_SP_CAMPAIGN", "COD DEVELOPER COMPANION");
|
||||
localized_strings::override("LUA_MENU_MULTIPLAYER_CAPS", "H1-Mod: MULTIPLAYER\n");
|
||||
localized_strings::override("MENU_MULTIPLAYER_CAPS", "H1-Mod: MULTIPLAYER");
|
||||
localized_strings::override("PLATFORM_UI_HEADER_PLAY_MP_CAPS", "H1-ONLINE");
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
if (!dvar_draw2d)
|
||||
{
|
||||
dvar_draw2d = game::Dvar_FindVar("cg_draw2d");
|
||||
}
|
||||
|
||||
if (dvar_draw2d && !dvar_draw2d->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto x = 4;
|
||||
const auto y = 4;
|
||||
const auto scale = 1.0f;
|
||||
float color[4] = { 1.0f, 0.5f, 0.0f, 1.0f };
|
||||
|
||||
const auto* text = "H1-Mod 1.4";
|
||||
|
||||
auto* font = game::R_RegisterFont("fonts/fira_mono_bold.ttf", 20);
|
||||
|
||||
if (!font) return;
|
||||
|
||||
game::R_AddCmdDrawText(text, 0x7FFFFFFF, font, static_cast<float>(x),
|
||||
y + static_cast<float>(font->pixelHeight) * scale,
|
||||
scale, scale, 0.0f, color, 0);
|
||||
|
||||
}, scheduler::pipeline::renderer);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(branding::component)
|
||||
|
||||
|
||||
// fonts/default.otf, fonts/defaultBold.otf, fonts/fira_mono_regular.ttf, fonts/fira_mono_bold.ttf
|
418
src/client/component/command.cpp
Normal file
418
src/client/component/command.cpp
Normal file
@ -0,0 +1,418 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "game_console.hpp"
|
||||
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
#include "utils/io.hpp"
|
||||
|
||||
namespace command
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour dvar_command_hook;
|
||||
|
||||
std::unordered_map<std::string, std::function<void(params&)>> handlers;
|
||||
|
||||
std::unordered_map<std::string, std::function<void(int, params_sv&)>> handlers_sv;
|
||||
|
||||
void main_handler()
|
||||
{
|
||||
params params = {};
|
||||
|
||||
const auto command = utils::string::to_lower(params[0]);
|
||||
if (handlers.find(command) != handlers.end())
|
||||
{
|
||||
handlers[command](params);
|
||||
}
|
||||
}
|
||||
|
||||
void client_command(const int client_num, void* a2)
|
||||
{
|
||||
params_sv params = {};
|
||||
|
||||
const auto command = utils::string::to_lower(params[0]);
|
||||
if (handlers_sv.find(command) != handlers_sv.end())
|
||||
{
|
||||
handlers_sv[command](client_num, params);
|
||||
}
|
||||
|
||||
dvar_command_hook.invoke<void>(client_num, a2);
|
||||
}
|
||||
|
||||
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride)
|
||||
{
|
||||
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
|
||||
{
|
||||
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
|
||||
cb(header);
|
||||
}), &callback, includeOverride);
|
||||
}
|
||||
|
||||
game::dvar_t* dvar_command_stub()
|
||||
{
|
||||
const params args;
|
||||
|
||||
if (args.size() <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto dvar = game::Dvar_FindVar(args[0]);
|
||||
|
||||
if (dvar)
|
||||
{
|
||||
if (args.size() == 1)
|
||||
{
|
||||
const auto current = game::Dvar_ValueToString(dvar, dvar->current, 0);
|
||||
const auto reset = game::Dvar_ValueToString(dvar, dvar->reset, 0);
|
||||
|
||||
game_console::print(game_console::con_type_info, "\"%s\" is: \"%s\" default: \"%s\" hash: %i",
|
||||
args[0], current, reset, dvar->name);
|
||||
|
||||
game_console::print(game_console::con_type_info, " %s\n",
|
||||
dvars::dvar_get_domain(dvar->type, dvar->domain).data());
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// char command[0x1000] = { 0 };
|
||||
// game::Dvar_GetCombinedString(command, 1);
|
||||
// game::Dvar_SetCommand(dvar->name, command);
|
||||
//}
|
||||
|
||||
return dvar;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
params::params()
|
||||
: nesting_(game::cmd_args->nesting)
|
||||
{
|
||||
}
|
||||
|
||||
int params::size() const
|
||||
{
|
||||
return game::cmd_args->argc[this->nesting_];
|
||||
}
|
||||
|
||||
const char* params::get(const int index) const
|
||||
{
|
||||
if (index >= this->size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return game::cmd_args->argv[this->nesting_][index];
|
||||
}
|
||||
|
||||
std::string params::join(const int index) const
|
||||
{
|
||||
std::string result = {};
|
||||
|
||||
for (auto i = index; i < this->size(); i++)
|
||||
{
|
||||
if (i > index) result.append(" ");
|
||||
result.append(this->get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
params_sv::params_sv()
|
||||
: nesting_(game::sv_cmd_args->nesting)
|
||||
{
|
||||
}
|
||||
|
||||
int params_sv::size() const
|
||||
{
|
||||
return game::sv_cmd_args->argc[this->nesting_];
|
||||
}
|
||||
|
||||
const char* params_sv::get(const int index) const
|
||||
{
|
||||
if (index >= this->size())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return game::sv_cmd_args->argv[this->nesting_][index];
|
||||
}
|
||||
|
||||
std::string params_sv::join(const int index) const
|
||||
{
|
||||
std::string result = {};
|
||||
|
||||
for (auto i = index; i < this->size(); i++)
|
||||
{
|
||||
if (i > index) result.append(" ");
|
||||
result.append(this->get(i));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void add_raw(const char* name, void (*callback)())
|
||||
{
|
||||
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)
|
||||
{
|
||||
const auto command = utils::string::to_lower(name);
|
||||
|
||||
if (handlers.find(command) == handlers.end())
|
||||
add_raw(name, main_handler);
|
||||
|
||||
handlers[command] = callback;
|
||||
}
|
||||
|
||||
void add(const char* name, const std::function<void()>& callback)
|
||||
{
|
||||
add(name, [callback](const params&)
|
||||
{
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
void add_sv(const char* name, std::function<void(int, const params_sv&)> callback)
|
||||
{
|
||||
// doing this so the sv command would show up in the console
|
||||
add_raw(name, nullptr);
|
||||
|
||||
const auto command = utils::string::to_lower(name);
|
||||
|
||||
if (handlers_sv.find(command) == handlers_sv.end())
|
||||
handlers_sv[command] = std::move(callback);
|
||||
}
|
||||
|
||||
void execute(std::string command, const bool sync)
|
||||
{
|
||||
command += "\n";
|
||||
|
||||
if (sync)
|
||||
{
|
||||
game::Cmd_ExecuteSingleCommand(0, 0, command.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
game::Cbuf_AddText(0, command.data());
|
||||
}
|
||||
}
|
||||
|
||||
void parse_command_line()
|
||||
{
|
||||
static auto parsed = false;
|
||||
if (parsed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
static std::string comand_line_buffer = GetCommandLineA();
|
||||
auto* command_line = comand_line_buffer.data();
|
||||
|
||||
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4);
|
||||
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0);
|
||||
|
||||
auto inq = false;
|
||||
com_console_lines[0] = command_line;
|
||||
com_num_console_lines = 0;
|
||||
|
||||
while (*command_line)
|
||||
{
|
||||
if (*command_line == '"')
|
||||
{
|
||||
inq = !inq;
|
||||
}
|
||||
// look for a + separating character
|
||||
// if commandLine came from a file, we might have real line seperators
|
||||
if ((*command_line == '+' && !inq) || *command_line == '\n' || *command_line == '\r')
|
||||
{
|
||||
if (com_num_console_lines == 0x20) // MAX_CONSOLE_LINES
|
||||
{
|
||||
break;
|
||||
}
|
||||
com_console_lines[com_num_console_lines] = command_line + 1;
|
||||
com_num_console_lines++;
|
||||
*command_line = '\0';
|
||||
}
|
||||
command_line++;
|
||||
}
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
void parse_commandline_stub()
|
||||
{
|
||||
parse_command_line();
|
||||
reinterpret_cast<void(*)()>(0x1400D8210)(); // mwr: test
|
||||
}
|
||||
|
||||
void read_startup_variable(const std::string& dvar)
|
||||
{
|
||||
// parse the commandline if it's not parsed
|
||||
parse_command_line();
|
||||
|
||||
auto& com_num_console_lines = *reinterpret_cast<int*>(0x142623FB4);
|
||||
auto* com_console_lines = reinterpret_cast<char**>(0x142623FC0);
|
||||
|
||||
for (int i = 0; i < com_num_console_lines; i++)
|
||||
{
|
||||
game::Cmd_TokenizeString(com_console_lines[i]);
|
||||
|
||||
// only +set dvar value
|
||||
if (game::Cmd_Argc() >= 3 && game::Cmd_Argv(0) == "set"s && game::Cmd_Argv(1) == dvar)
|
||||
{
|
||||
game::Dvar_SetCommand(game::Cmd_Argv(1), game::Cmd_Argv(2));
|
||||
}
|
||||
|
||||
game::Cmd_EndTokenizeString();
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
//utils::hook::jump(game::base_address + 0x000000, dvar_command_stub, true); // H1
|
||||
|
||||
static game::cmd_function_s cmd_test;
|
||||
|
||||
game::Cmd_AddCommandInternal("quit", game::Com_Quit_f, &cmd_test);
|
||||
/*game::Cmd_AddCommandInternal("connect", []() {
|
||||
|
||||
}, game::cmd_function_s);*/
|
||||
//add_raw("quit", main_handler);
|
||||
|
||||
//add("quit", game::Com_Quit_f);
|
||||
|
||||
//add("startmap", [](const params& params)
|
||||
//{
|
||||
// const auto map = params.get(1);
|
||||
|
||||
// const auto exists = utils::hook::invoke<bool>(game::base_address + 0x412B50, map, 0);
|
||||
|
||||
// if (!exists)
|
||||
// {
|
||||
// game_console::print(game_console::con_type_error, "map '%s' not found\n", map);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // SV_SpawnServer
|
||||
// utils::hook::invoke<void>(game::base_address + 0x6B3AA0, map, 0, 0, 0, 0);
|
||||
//});
|
||||
|
||||
/*add("say", [](const params& params)
|
||||
{
|
||||
chat::print(params.join(1));
|
||||
});*/
|
||||
|
||||
//add("listassetpool", [](const params& params)
|
||||
//{
|
||||
// if (params.size() < 2)
|
||||
// {
|
||||
// game_console::print(game_console::con_type_info, "listassetpool <poolnumber>: list all the assets in the specified pool\n");
|
||||
|
||||
// for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
|
||||
// {
|
||||
// game_console::print(game_console::con_type_info, "%d %s\n", i, game::g_assetNames[i]);
|
||||
// }
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// const auto type = static_cast<game::XAssetType>(atoi(params.get(1)));
|
||||
|
||||
// if (type < 0 || type >= game::XAssetType::ASSET_TYPE_COUNT)
|
||||
// {
|
||||
// game_console::print(game_console::con_type_info, "Invalid pool passed must be between [%d, %d]\n", 0, game::XAssetType::ASSET_TYPE_COUNT - 1);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// game_console::print(game_console::con_type_info, "Listing assets in pool %s\n", game::g_assetNames[type]);
|
||||
|
||||
// enum_assets(type, [type](const game::XAssetHeader header)
|
||||
// {
|
||||
// const auto asset = game::XAsset{type, header};
|
||||
// const auto* const asset_name = game::DB_GetXAssetName(&asset);
|
||||
// //const auto entry = game::DB_FindXAssetEntry(type, asset_name);
|
||||
// //TODO: display which zone the asset is from
|
||||
// game_console::print(game_console::con_type_info, "%s\n", asset_name);
|
||||
// }, true);
|
||||
// }
|
||||
//});
|
||||
|
||||
//add("commandDump", []()
|
||||
//{
|
||||
// printf("======== Start command dump =========\n");
|
||||
|
||||
// game::cmd_function_s* cmd = (*game::cmd_functions);
|
||||
|
||||
// while (cmd)
|
||||
// {
|
||||
// if (cmd->name)
|
||||
// {
|
||||
// game_console::print(game_console::con_type_info, "%s\n", cmd->name);
|
||||
// }
|
||||
|
||||
// cmd = cmd->next;
|
||||
// }
|
||||
|
||||
// printf("======== End command dump =========\n");
|
||||
//});
|
||||
|
||||
|
||||
/*add("god", []()
|
||||
{
|
||||
if (!game::SV_Loaded())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
game::mp::g_entities[0].flags ^= game::FL_GODMODE;
|
||||
game::CG_GameMessage(0, utils::string::va("godmode %s",
|
||||
game::g_entities[0].flags & game::FL_GODMODE
|
||||
? "^2on"
|
||||
: "^1off"));
|
||||
});*/
|
||||
|
||||
|
||||
|
||||
//add("notarget", []()
|
||||
//{
|
||||
// if (!game::SV_Loaded())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// game::g_entities[0].flags ^= game::FL_NOTARGET;
|
||||
// game::CG_GameMessage(0, utils::string::va("notarget %s",
|
||||
// game::g_entities[0].flags & game::FL_NOTARGET
|
||||
// ? "^2on"
|
||||
// : "^1off"));
|
||||
//});
|
||||
|
||||
//add("noclip", []()
|
||||
//{
|
||||
// if (!game::SV_Loaded())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// game::g_entities[0].client->flags ^= 1;
|
||||
// game::CG_GameMessage(0, utils::string::va("noclip %s",
|
||||
// game::g_entities[0].client->flags & 1
|
||||
// ? "^2on"
|
||||
// : "^1off"));
|
||||
//});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(command::component)
|
50
src/client/component/command.hpp
Normal file
50
src/client/component/command.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
namespace command
|
||||
{
|
||||
class params
|
||||
{
|
||||
public:
|
||||
params();
|
||||
|
||||
int size() const;
|
||||
const char* get(int index) const;
|
||||
std::string join(int index) const;
|
||||
|
||||
const char* operator[](const int index) const
|
||||
{
|
||||
return this->get(index); //
|
||||
}
|
||||
|
||||
private:
|
||||
int nesting_;
|
||||
};
|
||||
|
||||
class params_sv
|
||||
{
|
||||
public:
|
||||
params_sv();
|
||||
|
||||
int size() const;
|
||||
const char* get(int index) const;
|
||||
std::string join(int index) const;
|
||||
|
||||
const char* operator[](const int index) const
|
||||
{
|
||||
return this->get(index); //
|
||||
}
|
||||
|
||||
private:
|
||||
int nesting_;
|
||||
};
|
||||
|
||||
void read_startup_variable(const std::string& dvar);
|
||||
|
||||
void add_raw(const char* name, void (*callback)());
|
||||
void add(const char* name, const std::function<void(const params&)>& callback);
|
||||
void add(const char* name, const std::function<void()>& callback);
|
||||
|
||||
void add_sv(const char* name, std::function<void(int, const params_sv&)> callback);
|
||||
|
||||
void execute(std::string command, bool sync = false);
|
||||
}
|
265
src/client/component/console.cpp
Normal file
265
src/client/component/console.cpp
Normal file
@ -0,0 +1,265 @@
|
||||
#include <std_include.hpp>
|
||||
#include "console.hpp"
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game/game.hpp"
|
||||
#include "command.hpp"
|
||||
|
||||
#include <utils/thread.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace game_console
|
||||
{
|
||||
void print(int type, const std::string& data);
|
||||
}
|
||||
|
||||
namespace console
|
||||
{
|
||||
namespace
|
||||
{
|
||||
using message_queue = std::queue<std::string>;
|
||||
utils::concurrency::container<message_queue> messages;
|
||||
|
||||
void hide_console()
|
||||
{
|
||||
auto* const con_window = GetConsoleWindow();
|
||||
|
||||
DWORD process;
|
||||
GetWindowThreadProcessId(con_window, &process);
|
||||
|
||||
if (process == GetCurrentProcessId() || IsDebuggerPresent())
|
||||
{
|
||||
ShowWindow(con_window, SW_HIDE);
|
||||
}
|
||||
}
|
||||
|
||||
std::string format(va_list* ap, const char* message)
|
||||
{
|
||||
static thread_local char buffer[0x1000];
|
||||
|
||||
const auto count = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), message, *ap);
|
||||
|
||||
if (count < 0) return {};
|
||||
return { buffer, static_cast<size_t>(count) };
|
||||
}
|
||||
|
||||
void dispatch_message(const int type, const std::string& message)
|
||||
{
|
||||
/*if (rcon::message_redirect(message))
|
||||
{
|
||||
return;
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
void append_text(const char* text)
|
||||
{
|
||||
dispatch_message(con_type_info, text);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
component()
|
||||
{
|
||||
hide_console();
|
||||
|
||||
(void)_pipe(this->handles_, 1024, _O_TEXT);
|
||||
(void)_dup2(this->handles_[1], 1);
|
||||
(void)_dup2(this->handles_[1], 2);
|
||||
|
||||
//setvbuf(stdout, nullptr, _IONBF, 0);
|
||||
//setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
}
|
||||
|
||||
void post_start() override
|
||||
{
|
||||
this->terminate_runner_ = false;
|
||||
|
||||
this->console_runner_ = utils::thread::create_named_thread("Console IO", [this]
|
||||
{
|
||||
this->runner();
|
||||
});
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
this->terminate_runner_ = true;
|
||||
|
||||
printf("\r\n");
|
||||
_flushall();
|
||||
|
||||
if (this->console_runner_.joinable())
|
||||
{
|
||||
this->console_runner_.join();
|
||||
}
|
||||
|
||||
if (this->console_thread_.joinable())
|
||||
{
|
||||
this->console_thread_.join();
|
||||
}
|
||||
|
||||
_close(this->handles_[0]);
|
||||
_close(this->handles_[1]);
|
||||
|
||||
messages.access([&](message_queue& msgs)
|
||||
{
|
||||
msgs = {};
|
||||
});
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
// Redirect input (]command)
|
||||
utils::hook::jump(SELECT_VALUE(0x000000000, 0x1405141E0), append_text); // H1MP1.4
|
||||
|
||||
this->initialize();
|
||||
}
|
||||
|
||||
private:
|
||||
volatile bool console_initialized_ = false;
|
||||
volatile bool terminate_runner_ = false;
|
||||
|
||||
std::thread console_runner_;
|
||||
std::thread console_thread_;
|
||||
|
||||
int handles_[2]{};
|
||||
|
||||
void initialize()
|
||||
{
|
||||
this->console_thread_ = utils::thread::create_named_thread("Console", [this]()
|
||||
{
|
||||
if (game::environment::is_dedi() || !utils::flags::has_flag("noconsole"))
|
||||
{
|
||||
game::Sys_ShowConsole();
|
||||
}
|
||||
|
||||
if (!game::environment::is_dedi())
|
||||
{
|
||||
// Hide that shit
|
||||
ShowWindow(console::get_window(), SW_MINIMIZE);
|
||||
}
|
||||
|
||||
{
|
||||
messages.access([&](message_queue&)
|
||||
{
|
||||
this->console_initialized_ = true;
|
||||
});
|
||||
}
|
||||
|
||||
MSG msg;
|
||||
while (!this->terminate_runner_)
|
||||
{
|
||||
if (PeekMessageA(&msg, nullptr, NULL, NULL, PM_REMOVE))
|
||||
{
|
||||
if (msg.message == WM_QUIT)
|
||||
{
|
||||
command::execute("quit", false);
|
||||
break;
|
||||
}
|
||||
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->log_messages();
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void log_messages()
|
||||
{
|
||||
/*while*/
|
||||
if (this->console_initialized_ && !messages.get_raw().empty())
|
||||
{
|
||||
std::queue<std::string> message_queue_copy;
|
||||
|
||||
{
|
||||
messages.access([&](message_queue& msgs)
|
||||
{
|
||||
message_queue_copy = std::move(msgs);
|
||||
msgs = {};
|
||||
});
|
||||
}
|
||||
|
||||
while (!message_queue_copy.empty())
|
||||
{
|
||||
log_message(message_queue_copy.front());
|
||||
message_queue_copy.pop();
|
||||
}
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
static void log_message(const std::string& message)
|
||||
{
|
||||
OutputDebugStringA(message.data());
|
||||
game::Conbuf_AppendText(message.data()); //0x140513FF0
|
||||
FILE* pFile = fopen("debug.log", "a");
|
||||
fprintf(pFile, "%s\n", message.data());
|
||||
fclose(pFile);
|
||||
|
||||
}
|
||||
|
||||
void runner()
|
||||
{
|
||||
char buffer[1024];
|
||||
|
||||
while (!this->terminate_runner_ && this->handles_[0])
|
||||
{
|
||||
const auto len = _read(this->handles_[0], buffer, sizeof(buffer));
|
||||
if (len > 0)
|
||||
{
|
||||
dispatch_message(con_type_info, std::string(buffer, len));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::yield();
|
||||
}
|
||||
};
|
||||
|
||||
HWND get_window()
|
||||
{
|
||||
return *reinterpret_cast<HWND*>((SELECT_VALUE(0x000000000, 0x14DDFC2D0))); // H1MP1.4
|
||||
}
|
||||
|
||||
void set_title(std::string title)
|
||||
{
|
||||
SetWindowText(get_window(), title.data());
|
||||
}
|
||||
|
||||
void set_size(const int width, const int height)
|
||||
{
|
||||
RECT rect;
|
||||
GetWindowRect(get_window(), &rect);
|
||||
|
||||
SetWindowPos(get_window(), nullptr, rect.left, rect.top, width, height, 0);
|
||||
|
||||
auto* const logo_window = *reinterpret_cast<HWND*>(SELECT_VALUE(0x000000000, 0x14DDFC2E0)); // H1MP64(1.4)
|
||||
SetWindowPos(logo_window, nullptr, 5, 5, width - 25, 60, 0);
|
||||
}
|
||||
|
||||
void print(const int type, const char* fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
const auto result = format(&ap, fmt);
|
||||
va_end(ap);
|
||||
|
||||
dispatch_message(type, result);
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(console::component)
|
35
src/client/component/console.hpp
Normal file
35
src/client/component/console.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
namespace console
|
||||
{
|
||||
HWND get_window();
|
||||
void set_title(std::string title);
|
||||
void set_size(int width, int height);
|
||||
|
||||
enum console_type
|
||||
{
|
||||
con_type_error = 1,
|
||||
con_type_warning = 3,
|
||||
con_type_info = 7
|
||||
};
|
||||
|
||||
void print(int type, const char* fmt, ...);
|
||||
|
||||
template <typename... Args>
|
||||
void error(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_error, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void warn(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_warning, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void info(const char* fmt, Args&&... args)
|
||||
{
|
||||
print(con_type_info, fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
453
src/client/component/demonware.cpp
Normal file
453
src/client/component/demonware.cpp
Normal file
@ -0,0 +1,453 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/thread.hpp>
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/demonware/servers/lobby_server.hpp"
|
||||
#include "game/demonware/servers/auth3_server.hpp"
|
||||
#include "game/demonware/servers/stun_server.hpp"
|
||||
#include "game/demonware/servers/umbrella_server.hpp"
|
||||
#include "game/demonware/server_registry.hpp"
|
||||
#include <game/dvars.hpp>
|
||||
|
||||
#define TCP_BLOCKING true
|
||||
#define UDP_BLOCKING false
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
namespace
|
||||
{
|
||||
volatile bool exit_server;
|
||||
std::thread server_thread;
|
||||
utils::concurrency::container<std::unordered_map<SOCKET, bool>> blocking_sockets;
|
||||
utils::concurrency::container<std::unordered_map<SOCKET, tcp_server*>> socket_map;
|
||||
server_registry<tcp_server> tcp_servers;
|
||||
server_registry<udp_server> udp_servers;
|
||||
|
||||
tcp_server* find_server(const SOCKET socket)
|
||||
{
|
||||
return socket_map.access<tcp_server*>([&](const std::unordered_map<SOCKET, tcp_server*>& map) -> tcp_server*
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return entry->second;
|
||||
});
|
||||
}
|
||||
|
||||
bool socket_link(const SOCKET socket, const uint32_t address)
|
||||
{
|
||||
auto* server = tcp_servers.find(address);
|
||||
if (!server)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& map)
|
||||
{
|
||||
map[socket] = server;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void socket_unlink(const SOCKET socket)
|
||||
{
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry != map.end())
|
||||
{
|
||||
map.erase(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool is_socket_blocking(const SOCKET socket, const bool def)
|
||||
{
|
||||
return blocking_sockets.access<bool>([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return def;
|
||||
}
|
||||
|
||||
return entry->second;
|
||||
});
|
||||
}
|
||||
|
||||
void remove_blocking_socket(const SOCKET socket)
|
||||
{
|
||||
blocking_sockets.access([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry != map.end())
|
||||
{
|
||||
map.erase(entry);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void add_blocking_socket(const SOCKET socket, const bool block)
|
||||
{
|
||||
blocking_sockets.access([&](std::unordered_map<SOCKET, bool>& map)
|
||||
{
|
||||
map[socket] = block;
|
||||
});
|
||||
}
|
||||
|
||||
void server_main()
|
||||
{
|
||||
exit_server = false;
|
||||
|
||||
while (!exit_server)
|
||||
{
|
||||
tcp_servers.frame();
|
||||
udp_servers.frame();
|
||||
std::this_thread::sleep_for(50ms);
|
||||
}
|
||||
}
|
||||
|
||||
namespace io
|
||||
{
|
||||
hostent* gethostbyname_stub(const char* name)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[ network ]: [gethostbyname]: \"%s\"\n", name);
|
||||
#endif
|
||||
|
||||
base_server* server = tcp_servers.find(name);
|
||||
if (!server)
|
||||
{
|
||||
server = udp_servers.find(name);
|
||||
}
|
||||
|
||||
if (!server)
|
||||
{
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4996)
|
||||
return gethostbyname(name);
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
static thread_local in_addr address{};
|
||||
address.s_addr = server->get_address();
|
||||
|
||||
static thread_local in_addr* addr_list[2]{};
|
||||
addr_list[0] = &address;
|
||||
addr_list[1] = nullptr;
|
||||
|
||||
static thread_local hostent host{};
|
||||
host.h_name = const_cast<char*>(name);
|
||||
host.h_aliases = nullptr;
|
||||
host.h_addrtype = AF_INET;
|
||||
host.h_length = sizeof(in_addr);
|
||||
host.h_addr_list = reinterpret_cast<char**>(addr_list);
|
||||
|
||||
return &host;
|
||||
}
|
||||
|
||||
int connect_stub(const SOCKET s, const struct sockaddr* addr, const int len)
|
||||
{
|
||||
if (len == sizeof(sockaddr_in))
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(addr);
|
||||
if (socket_link(s, in_addr->sin_addr.s_addr)) return 0;
|
||||
}
|
||||
|
||||
return connect(s, addr, len);
|
||||
}
|
||||
|
||||
int closesocket_stub(const SOCKET s)
|
||||
{
|
||||
remove_blocking_socket(s);
|
||||
socket_unlink(s);
|
||||
|
||||
return closesocket(s);
|
||||
}
|
||||
|
||||
int send_stub(const SOCKET s, const char* buf, const int len, const int flags)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
server->handle_input(buf, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
return send(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int recv_stub(const SOCKET s, char* buf, const int len, const int flags)
|
||||
{
|
||||
auto* server = find_server(s);
|
||||
|
||||
if (server)
|
||||
{
|
||||
if (server->pending_data())
|
||||
{
|
||||
return static_cast<int>(server->handle_output(buf, len));
|
||||
}
|
||||
else
|
||||
{
|
||||
WSASetLastError(WSAEWOULDBLOCK);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return recv(s, buf, len, flags);
|
||||
}
|
||||
|
||||
int sendto_stub(const SOCKET s, const char* buf, const int len, const int flags, const sockaddr* to,
|
||||
const int tolen)
|
||||
{
|
||||
const auto* in_addr = reinterpret_cast<const sockaddr_in*>(to);
|
||||
auto* server = udp_servers.find(in_addr->sin_addr.s_addr);
|
||||
|
||||
if (server)
|
||||
{
|
||||
server->handle_input(buf, len, { s, to, tolen });
|
||||
return len;
|
||||
}
|
||||
|
||||
return sendto(s, buf, len, flags, to, tolen);
|
||||
}
|
||||
|
||||
int recvfrom_stub(const SOCKET s, char* buf, const int len, const int flags, struct sockaddr* from,
|
||||
int* fromlen)
|
||||
{
|
||||
// Not supported yet
|
||||
if (is_socket_blocking(s, UDP_BLOCKING))
|
||||
{
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
size_t result = 0;
|
||||
udp_servers.for_each([&](udp_server& server)
|
||||
{
|
||||
if (server.pending_data(s))
|
||||
{
|
||||
result = server.handle_output(
|
||||
s, buf, static_cast<size_t>(len), from, fromlen);
|
||||
}
|
||||
});
|
||||
|
||||
if (result)
|
||||
{
|
||||
return static_cast<int>(result);
|
||||
}
|
||||
|
||||
return recvfrom(s, buf, len, flags, from, fromlen);
|
||||
}
|
||||
|
||||
int select_stub(const int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds,
|
||||
struct timeval* timeout)
|
||||
{
|
||||
if (exit_server)
|
||||
{
|
||||
return select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
}
|
||||
|
||||
auto result = 0;
|
||||
std::vector<SOCKET> read_sockets;
|
||||
std::vector<SOCKET> write_sockets;
|
||||
|
||||
socket_map.access([&](std::unordered_map<SOCKET, tcp_server*>& sockets)
|
||||
{
|
||||
for (auto& s : sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, readfds))
|
||||
{
|
||||
if (s.second->pending_data())
|
||||
{
|
||||
read_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, readfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (writefds)
|
||||
{
|
||||
if (FD_ISSET(s.first, writefds))
|
||||
{
|
||||
write_sockets.push_back(s.first);
|
||||
FD_CLR(s.first, writefds);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptfds)
|
||||
{
|
||||
if (FD_ISSET(s.first, exceptfds))
|
||||
{
|
||||
FD_CLR(s.first, exceptfds);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if ((!readfds || readfds->fd_count == 0) && (!writefds || writefds->fd_count == 0))
|
||||
{
|
||||
timeout->tv_sec = 0;
|
||||
timeout->tv_usec = 0;
|
||||
}
|
||||
|
||||
result = select(nfds, readfds, writefds, exceptfds, timeout);
|
||||
if (result < 0) result = 0;
|
||||
|
||||
for (const auto& socket : read_sockets)
|
||||
{
|
||||
if (readfds)
|
||||
{
|
||||
FD_SET(socket, readfds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& socket : write_sockets)
|
||||
{
|
||||
if (writefds)
|
||||
{
|
||||
FD_SET(socket, writefds);
|
||||
result++;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int ioctlsocket_stub(const SOCKET s, const long cmd, u_long* argp)
|
||||
{
|
||||
if (static_cast<unsigned long>(cmd) == (FIONBIO))
|
||||
{
|
||||
add_blocking_socket(s, *argp == 0);
|
||||
}
|
||||
|
||||
return ioctlsocket(s, cmd, argp);
|
||||
}
|
||||
|
||||
BOOL internet_get_connected_state_stub(LPDWORD, DWORD)
|
||||
{
|
||||
// Allow offline play
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
void bd_logger_stub(const char* const function, const char* const msg, ...)
|
||||
{
|
||||
game::dvar_t* enabled;
|
||||
|
||||
enabled = dvars::register_bool("bd_logger_enabled", false, game::DVAR_FLAG_SAVED, true);
|
||||
|
||||
|
||||
//game::Dvar_RegisterBool("bd_logger_enabled", false, game::DVAR_FLAG_SAVED, "bdLogger")
|
||||
if (!enabled->current.enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
char buffer[2048];
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, msg);
|
||||
|
||||
vsnprintf_s(buffer, sizeof(buffer), _TRUNCATE, msg, ap);
|
||||
printf("%s: %s\n", function, buffer);
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
component()
|
||||
{
|
||||
udp_servers.create<stun_server>("phoenix.stun.us.demonware.net");
|
||||
udp_servers.create<stun_server>("phoenix.stun.eu.demonware.net");
|
||||
udp_servers.create<stun_server>("phoenix.stun.jp.demonware.net");
|
||||
udp_servers.create<stun_server>("phoenix.stun.au.demonware.net");
|
||||
|
||||
udp_servers.create<stun_server>("stun.us.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.eu.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.jp.demonware.net");
|
||||
udp_servers.create<stun_server>("stun.au.demonware.net");
|
||||
|
||||
tcp_servers.create<auth3_server>("mwr-pc-steam-auth3.prod.demonware.net");
|
||||
tcp_servers.create<lobby_server>("mwr-pc-steam-lobby.prod.demonware.net");
|
||||
tcp_servers.create<umbrella_server>("prod.umbrella.demonware.net");
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
server_thread = utils::thread::create_named_thread("Demonware", server_main);
|
||||
}
|
||||
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (library == "WS2_32.dll")
|
||||
{
|
||||
if (function == "#3") return io::closesocket_stub;
|
||||
if (function == "#4") return io::connect_stub;
|
||||
if (function == "#10") return io::ioctlsocket_stub;
|
||||
if (function == "#16") return io::recv_stub;
|
||||
if (function == "#17") return io::recvfrom_stub;
|
||||
if (function == "#18") return io::select_stub;
|
||||
if (function == "#19") return io::send_stub;
|
||||
if (function == "#20") return io::sendto_stub;
|
||||
if (function == "#52") return io::gethostbyname_stub;
|
||||
}
|
||||
|
||||
if (function == "InternetGetConnectedState")
|
||||
{
|
||||
return io::internet_get_connected_state_stub;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void post_unpack() override
|
||||
{
|
||||
utils::hook::jump(SELECT_VALUE(0, 0x1407400B0), bd_logger_stub); // H1MP64(1.4)
|
||||
|
||||
//singleplayer not supported so far.
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
utils::hook::set<uint8_t>(0x1405632E0, 0xC3); // bdAuthSteam
|
||||
utils::hook::set<uint8_t>(0x1402DF2C0, 0xC3); // dwNet
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::set<uint8_t>(0x140715039, 0x0); // CURLOPT_SSL_VERIFYPEER H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x140715025, 0xAF); // CURLOPT_SSL_VERIFYHOST H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x14095433C, 0x0); // HTTPS -> HTTP [MWR OK][S1X: 0x14088D0E8]
|
||||
|
||||
//HTTPS -> HTTP
|
||||
utils::hook::inject(0x14006DDA9, "http://prod.umbrella.demonware.net/v1.0/"); // ---> [H1MP1.4 - S1X: 0x14003852E]
|
||||
utils::hook::inject(0x14006E11C, "http://prod.umbrella.demonware.net/v1.0/"); // ---> [H1MP1.4 - S1X: 0x14003884F]
|
||||
utils::hook::inject(0x14006E2FB, "http://prod.umbrella.demonware.net/v1.0/"); // ---> [H1MP1.4 - S1X: 0x140038A07]
|
||||
|
||||
utils::hook::set<uint8_t>(0x14047F290, 0xC3); // SV_SendMatchData H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x140598990, 0xC3); // Live_CheckForFullDisconnect H1MP64(1.4)
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
exit_server = true;
|
||||
if (server_thread.joinable())
|
||||
{
|
||||
server_thread.join();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(demonware::component)
|
6
src/client/component/demonware.hpp
Normal file
6
src/client/component/demonware.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
|
||||
}
|
756
src/client/component/game_console.cpp
Normal file
756
src/client/component/game_console.cpp
Normal file
@ -0,0 +1,756 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "game_console.hpp"
|
||||
#include "command.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#define console_font game::R_RegisterFont("fonts/fira_mono_regular.ttf", 18)
|
||||
#define material_white game::Material_RegisterHandle("white")
|
||||
|
||||
namespace game_console
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct console_globals
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float left_x;
|
||||
float font_height;
|
||||
bool may_auto_complete;
|
||||
char auto_complete_choice[64];
|
||||
int info_line_count;
|
||||
};
|
||||
|
||||
struct ingame_console
|
||||
{
|
||||
char buffer[256];
|
||||
int cursor;
|
||||
int font_height;
|
||||
int visible_line_count;
|
||||
int visible_pixel_width;
|
||||
float screen_min[2]; //left & top
|
||||
float screen_max[2]; //right & bottom
|
||||
console_globals globals;
|
||||
bool output_visible;
|
||||
int display_line_offset;
|
||||
int line_count;
|
||||
std::deque<std::string> output;
|
||||
};
|
||||
|
||||
ingame_console con;
|
||||
|
||||
std::int32_t history_index = -1;
|
||||
std::deque<std::string> history;
|
||||
|
||||
std::string fixed_input;
|
||||
std::vector<std::string> matches;
|
||||
|
||||
float color_white[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
float color_title[4] = { 0.9f, 0.9f, 0.5f, 1.0f };
|
||||
|
||||
void clear()
|
||||
{
|
||||
strncpy_s(con.buffer, "", 256);
|
||||
con.cursor = 0;
|
||||
|
||||
fixed_input = "";
|
||||
matches.clear();
|
||||
}
|
||||
|
||||
void print(const std::string& data)
|
||||
{
|
||||
if (con.visible_line_count > 0 && con.display_line_offset == (con.output.size() - con.visible_line_count))
|
||||
{
|
||||
con.display_line_offset++;
|
||||
}
|
||||
|
||||
con.output.push_back(data);
|
||||
|
||||
printf("%s\n", data.data());
|
||||
|
||||
if (con.output.size() > 1024)
|
||||
{
|
||||
con.output.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
void toggle_console()
|
||||
{
|
||||
clear();
|
||||
|
||||
con.output_visible = false;
|
||||
*game::keyCatchers ^= 1;
|
||||
}
|
||||
|
||||
void toggle_console_output()
|
||||
{
|
||||
con.output_visible = con.output_visible == 0;
|
||||
}
|
||||
|
||||
void check_resize()
|
||||
{
|
||||
con.screen_min[0] = 6.0f;
|
||||
con.screen_min[1] = 6.0f;
|
||||
con.screen_max[0] = game::ScrPlace_GetViewPlacement()->realViewportSize[0] - 6.0f;
|
||||
con.screen_max[1] = game::ScrPlace_GetViewPlacement()->realViewportSize[1] - 6.0f;
|
||||
|
||||
if (console_font)
|
||||
{
|
||||
con.font_height = console_font->pixelHeight;
|
||||
con.visible_line_count = static_cast<int>((con.screen_max[1] - con.screen_min[1] - (con.font_height * 2)
|
||||
) -
|
||||
24.0f) / con.font_height;
|
||||
con.visible_pixel_width = static_cast<int>(((con.screen_max[0] - con.screen_min[0]) - 10.0f) - 18.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
con.font_height = 0;
|
||||
con.visible_line_count = 0;
|
||||
con.visible_pixel_width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_box(const float x, const float y, const float w, const float h, float* color)
|
||||
{
|
||||
game::vec4_t dark_color;
|
||||
|
||||
dark_color[0] = color[0] * 0.5f;
|
||||
dark_color[1] = color[1] * 0.5f;
|
||||
dark_color[2] = color[2] * 0.5f;
|
||||
dark_color[3] = color[3];
|
||||
|
||||
game::R_AddCmdDrawStretchPic(x, y, w, h, 0.0f, 0.0f, 0.0f, 0.0f, color, material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white);
|
||||
game::R_AddCmdDrawStretchPic((x + w) - 2.0f, y, 2.0f, h, 0.0f, 0.0f, 0.0f, 0.0f, dark_color,
|
||||
material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, y, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color, material_white);
|
||||
game::R_AddCmdDrawStretchPic(x, (y + h) - 2.0f, w, 2.0f, 0.0f, 0.0f, 0.0f, 0.0f, dark_color,
|
||||
material_white);
|
||||
}
|
||||
|
||||
void draw_input_box(const int lines, float* color)
|
||||
{
|
||||
draw_box(
|
||||
con.globals.x - 6.0f,
|
||||
con.globals.y - 6.0f,
|
||||
(con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]),
|
||||
(lines * con.globals.font_height) + 12.0f,
|
||||
color);
|
||||
}
|
||||
|
||||
void draw_input_text_and_over(const char* str, float* color)
|
||||
{
|
||||
game::R_AddCmdDrawText(str, 0x7FFFFFFF, console_font, con.globals.x,
|
||||
con.globals.y + con.globals.font_height, 1.0f,
|
||||
1.0f, 0.0f, color, 0);
|
||||
con.globals.x = game::R_TextWidth(str, 0, console_font) + con.globals.x + 6.0f;
|
||||
}
|
||||
|
||||
void draw_hint_box(const int lines, float* color, [[maybe_unused]] float offset_x = 0.0f,
|
||||
[[maybe_unused]] float offset_y = 0.0f)
|
||||
{
|
||||
const auto _h = lines * con.globals.font_height + 12.0f;
|
||||
const auto _y = con.globals.y - 3.0f + con.globals.font_height + 12.0f;
|
||||
const auto _w = (con.screen_max[0] - con.screen_min[0]) - ((con.globals.x - 6.0f) - con.screen_min[0]);
|
||||
|
||||
draw_box(con.globals.x - 6.0f, _y, _w, _h, color);
|
||||
}
|
||||
|
||||
void draw_hint_text(const int line, const char* text, float* color, const float offset = 0.0f)
|
||||
{
|
||||
const auto _y = con.globals.font_height + con.globals.y + (con.globals.font_height * (line + 1)) + 15.0f;
|
||||
|
||||
game::R_AddCmdDrawText(text, 0x7FFFFFFF, console_font, con.globals.x + offset, _y, 1.0f, 1.0f, 0.0f, color, 0);
|
||||
}
|
||||
|
||||
bool match_compare(const std::string& input, const std::string& text, const bool exact)
|
||||
{
|
||||
if (exact && text == input) return true;
|
||||
if (!exact && text.find(input) != std::string::npos) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact)
|
||||
{
|
||||
input = utils::string::to_lower(input);
|
||||
|
||||
for (const auto& dvar : dvars::dvar_list)
|
||||
{
|
||||
auto name = utils::string::to_lower(dvar);
|
||||
if (match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.push_back(dvar);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (suggestions.size() == 0 && game::Dvar_FindVar(input.data()))
|
||||
{
|
||||
suggestions.push_back(input.data());
|
||||
}
|
||||
|
||||
game::cmd_function_s* cmd = (*game::cmd_functions);
|
||||
while (cmd)
|
||||
{
|
||||
if (cmd->name)
|
||||
{
|
||||
std::string name = utils::string::to_lower(cmd->name);
|
||||
|
||||
if (match_compare(input, name, exact))
|
||||
{
|
||||
suggestions.push_back(cmd->name);
|
||||
}
|
||||
|
||||
if (exact && suggestions.size() > 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
cmd = cmd->next;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_input()
|
||||
{
|
||||
con.globals.font_height = static_cast<float>(console_font->pixelHeight);
|
||||
con.globals.x = con.screen_min[0] + 6.0f;
|
||||
con.globals.y = con.screen_min[1] + 6.0f;
|
||||
con.globals.left_x = con.screen_min[0] + 6.0f;
|
||||
|
||||
draw_input_box(1, dvars::con_inputBoxColor->current.vector);
|
||||
draw_input_text_and_over("H1-Mod >", color_title);
|
||||
|
||||
con.globals.left_x = con.globals.x;
|
||||
con.globals.auto_complete_choice[0] = 0;
|
||||
|
||||
|
||||
game::R_AddCmdDrawTextWithCursor(con.buffer, 0x7FFFFFFF, console_font, 18, con.globals.x,
|
||||
con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0, color_white, 0,
|
||||
con.cursor, '|');
|
||||
|
||||
//game::R_AddCmdDrawText(con.buffer, 0x7FFF, console_font, con.globals.x,
|
||||
// con.globals.y + con.globals.font_height, 1.0f, 1.0f, 0.0f, color_white, 0);
|
||||
|
||||
|
||||
// check if using a prefixed '/' or not
|
||||
const auto input = con.buffer[1] && (con.buffer[0] == '/' || con.buffer[0] == '\\')
|
||||
? std::string(con.buffer).substr(1)
|
||||
: std::string(con.buffer);
|
||||
|
||||
if (!input.length())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (input != fixed_input)
|
||||
{
|
||||
matches.clear();
|
||||
|
||||
if (input.find(" ") != std::string::npos)
|
||||
{
|
||||
find_matches(input.substr(0, input.find(" ")), matches, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
find_matches(input, matches, false);
|
||||
}
|
||||
|
||||
fixed_input = input;
|
||||
}
|
||||
|
||||
con.globals.may_auto_complete = false;
|
||||
if (matches.size() > 24)
|
||||
{
|
||||
draw_hint_box(1, dvars::con_inputHintBoxColor->current.vector);
|
||||
draw_hint_text(0, utils::string::va("%i matches (too many to show here)", matches.size()),
|
||||
dvars::con_inputDvarMatchColor->current.vector);
|
||||
}
|
||||
else if (matches.size() == 1)
|
||||
{
|
||||
auto* const dvar = game::Dvar_FindVar(matches[0].data());
|
||||
const auto line_count = dvar ? 2 : 1;
|
||||
|
||||
draw_hint_box(line_count, dvars::con_inputHintBoxColor->current.vector);
|
||||
draw_hint_text(0, matches[0].data(), dvar
|
||||
? dvars::con_inputDvarMatchColor->current.vector
|
||||
: dvars::con_inputCmdMatchColor->current.vector);
|
||||
|
||||
if (dvar)
|
||||
{
|
||||
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
|
||||
|
||||
draw_hint_text(0, game::Dvar_ValueToString(dvar, dvar->current, 0),
|
||||
dvars::con_inputDvarValueColor->current.vector, offset);
|
||||
draw_hint_text(1, " default", dvars::con_inputDvarInactiveValueColor->current.vector);
|
||||
draw_hint_text(1, game::Dvar_ValueToString(dvar, dvar->reset, 0),
|
||||
dvars::con_inputDvarInactiveValueColor->current.vector, offset);
|
||||
}
|
||||
|
||||
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64);
|
||||
con.globals.may_auto_complete = true;
|
||||
}
|
||||
else if (matches.size() > 1)
|
||||
{
|
||||
draw_hint_box(static_cast<int>(matches.size()), dvars::con_inputHintBoxColor->current.vector);
|
||||
|
||||
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
|
||||
|
||||
for (size_t i = 0; i < matches.size(); i++)
|
||||
{
|
||||
auto* const dvar = game::Dvar_FindVar(matches[i].data());
|
||||
|
||||
draw_hint_text(static_cast<int>(i), matches[i].data(),
|
||||
dvar
|
||||
? dvars::con_inputDvarMatchColor->current.vector
|
||||
: dvars::con_inputCmdMatchColor->current.vector);
|
||||
|
||||
if (dvar)
|
||||
{
|
||||
draw_hint_text(static_cast<int>(i), game::Dvar_ValueToString(dvar, dvar->current, 0),
|
||||
dvars::con_inputDvarValueColor->current.vector, offset);
|
||||
}
|
||||
}
|
||||
|
||||
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64);
|
||||
con.globals.may_auto_complete = true;
|
||||
}
|
||||
}
|
||||
|
||||
void draw_output_scrollbar(const float x, float y, const float width, const float height)
|
||||
{
|
||||
const auto _x = (x + width) - 10.0f;
|
||||
draw_box(_x, y, 10.0f, height, dvars::con_outputBarColor->current.vector);
|
||||
|
||||
auto _height = height;
|
||||
if (con.output.size() > con.visible_line_count)
|
||||
{
|
||||
const auto percentage = static_cast<float>(con.visible_line_count) / con.output.size();
|
||||
_height *= percentage;
|
||||
|
||||
const auto remainingSpace = height - _height;
|
||||
const auto percentageAbove = static_cast<float>(con.display_line_offset) / (con.output.size() - con.
|
||||
visible_line_count);
|
||||
|
||||
y = y + (remainingSpace * percentageAbove);
|
||||
}
|
||||
|
||||
draw_box(_x, y, 10.0f, _height, dvars::con_outputSliderColor->current.vector);
|
||||
}
|
||||
|
||||
void draw_output_text(const float x, float y)
|
||||
{
|
||||
const auto offset = con.output.size() >= con.visible_line_count
|
||||
? 0.0f
|
||||
: (con.font_height * (con.visible_line_count - con.output.size()));
|
||||
|
||||
for (auto i = 0; i < con.visible_line_count; i++)
|
||||
{
|
||||
y = console_font->pixelHeight + y;
|
||||
|
||||
const auto index = i + con.display_line_offset;
|
||||
if (index >= con.output.size())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
game::R_AddCmdDrawText(con.output.at(index).data(), 0x7FFF, console_font, x, y + offset, 1.0f, 1.0f,
|
||||
0.0f, color_white, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void draw_output_window()
|
||||
{
|
||||
draw_box(con.screen_min[0], con.screen_min[1] + 32.0f, con.screen_max[0] - con.screen_min[0],
|
||||
(con.screen_max[1] - con.screen_min[1]) - 32.0f, dvars::con_outputWindowColor->current.vector);
|
||||
|
||||
const auto x = con.screen_min[0] + 6.0f;
|
||||
const auto y = (con.screen_min[1] + 32.0f) + 6.0f;
|
||||
const auto width = (con.screen_max[0] - con.screen_min[0]) - 12.0f;
|
||||
const auto height = ((con.screen_max[1] - con.screen_min[1]) - 32.0f) - 12.0f;
|
||||
|
||||
game::R_AddCmdDrawText("H1-Mod 1.4", 0x7FFFFFFF, console_font, x,
|
||||
((height - 16.0f) + y) + console_font->pixelHeight, 1.0f, 1.0f, 0.0f, color_title, 0);
|
||||
|
||||
draw_output_scrollbar(x, y, width, height);
|
||||
draw_output_text(x, y);
|
||||
}
|
||||
|
||||
void draw_console()
|
||||
{
|
||||
check_resize();
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (!(*game::keyCatchers & 1))
|
||||
{
|
||||
con.output_visible = false;
|
||||
}
|
||||
|
||||
if (con.output_visible)
|
||||
{
|
||||
draw_output_window();
|
||||
}
|
||||
|
||||
draw_input();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void print(const int type, const char* fmt, ...)
|
||||
{
|
||||
char va_buffer[0x200] = { 0 };
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsprintf_s(va_buffer, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
const auto formatted = std::string(va_buffer);
|
||||
const auto lines = utils::string::split(formatted, '\n');
|
||||
|
||||
for (auto& line : lines)
|
||||
{
|
||||
print(type == con_type_info ? line : "^"s.append(std::to_string(type)).append(line));
|
||||
}
|
||||
}
|
||||
|
||||
bool console_char_event(const int localClientNum, const int key)
|
||||
{
|
||||
if (key == '`' || key == '~' || key == '|' || key == '\\')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key > 127)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (key == game::keyNum_t::K_TAB) // tab (auto complete)
|
||||
{
|
||||
if (con.globals.may_auto_complete)
|
||||
{
|
||||
const auto firstChar = con.buffer[0];
|
||||
|
||||
clear();
|
||||
|
||||
if (firstChar == '\\' || firstChar == '/')
|
||||
{
|
||||
con.buffer[0] = firstChar;
|
||||
con.buffer[1] = '\0';
|
||||
}
|
||||
|
||||
strncat_s(con.buffer, con.globals.auto_complete_choice, 64);
|
||||
con.cursor = static_cast<int>(std::string(con.buffer).length());
|
||||
|
||||
if (con.cursor != 254)
|
||||
{
|
||||
con.buffer[con.cursor++] = ' ';
|
||||
con.buffer[con.cursor] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (key == 'v' - 'a' + 1) // paste
|
||||
{
|
||||
const auto clipboard = utils::string::get_clipboard_data();
|
||||
if (clipboard.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < clipboard.length(); i++)
|
||||
{
|
||||
console_char_event(localClientNum, clipboard[i]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == 'c' - 'a' + 1) // clear
|
||||
{
|
||||
clear();
|
||||
con.line_count = 0;
|
||||
con.output.clear();
|
||||
history_index = -1;
|
||||
history.clear();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == 'h' - 'a' + 1) // backspace
|
||||
{
|
||||
if (con.cursor > 0)
|
||||
{
|
||||
memmove(con.buffer + con.cursor - 1, con.buffer + con.cursor,
|
||||
strlen(con.buffer) + 1 - con.cursor);
|
||||
con.cursor--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key < 32)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (con.cursor == 256 - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
memmove(con.buffer + con.cursor + 1, con.buffer + con.cursor, strlen(con.buffer) + 1 - con.cursor);
|
||||
con.buffer[con.cursor] = static_cast<char>(key);
|
||||
con.cursor++;
|
||||
|
||||
if (con.cursor == strlen(con.buffer) + 1)
|
||||
{
|
||||
con.buffer[con.cursor] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void execute(const char* cmd)
|
||||
{
|
||||
game::Cbuf_AddText(0, utils::string::va("%s \n", cmd));
|
||||
}
|
||||
|
||||
bool console_key_event(const int localClientNum, const int key, const int down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_GRAVE || key == game::keyNum_t::K_TILDE)
|
||||
{
|
||||
if (!down)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (game::playerKeys[localClientNum].keys[game::keyNum_t::K_SHIFT].down)
|
||||
{
|
||||
if (!(*game::keyCatchers & 1))
|
||||
toggle_console();
|
||||
|
||||
toggle_console_output();
|
||||
return false;
|
||||
}
|
||||
|
||||
toggle_console();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*game::keyCatchers & 1)
|
||||
{
|
||||
if (down)
|
||||
{
|
||||
if (key == game::keyNum_t::K_UPARROW)
|
||||
{
|
||||
if (++history_index >= history.size())
|
||||
{
|
||||
history_index = static_cast<int>(history.size()) - 1;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
strncpy_s(con.buffer, history.at(history_index).c_str(), 0x100);
|
||||
con.cursor = static_cast<int>(strlen(con.buffer));
|
||||
}
|
||||
}
|
||||
else if (key == game::keyNum_t::K_DOWNARROW)
|
||||
{
|
||||
if (--history_index < -1)
|
||||
{
|
||||
history_index = -1;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
strncpy_s(con.buffer, history.at(history_index).c_str(), 0x100);
|
||||
con.cursor = static_cast<int>(strlen(con.buffer));
|
||||
}
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_RIGHTARROW)
|
||||
{
|
||||
if (con.cursor < strlen(con.buffer))
|
||||
{
|
||||
con.cursor++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_LEFTARROW)
|
||||
{
|
||||
if (con.cursor > 0)
|
||||
{
|
||||
con.cursor--;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//scroll through output
|
||||
if (key == game::keyNum_t::K_MWHEELUP || key == game::keyNum_t::K_PGUP)
|
||||
{
|
||||
if (con.output.size() > con.visible_line_count && con.display_line_offset > 0)
|
||||
{
|
||||
con.display_line_offset--;
|
||||
}
|
||||
}
|
||||
else if (key == game::keyNum_t::K_MWHEELDOWN || key == game::keyNum_t::K_PGDN)
|
||||
{
|
||||
if (con.output.size() > con.visible_line_count && con.display_line_offset < (con.output.size() -
|
||||
con.
|
||||
visible_line_count))
|
||||
{
|
||||
con.display_line_offset++;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == game::keyNum_t::K_ENTER)
|
||||
{
|
||||
execute(fixed_input.data());
|
||||
|
||||
if (history_index != -1)
|
||||
{
|
||||
const auto itr = history.begin() + history_index;
|
||||
|
||||
if (*itr == con.buffer)
|
||||
{
|
||||
history.erase(history.begin() + history_index);
|
||||
}
|
||||
}
|
||||
|
||||
history.push_front(con.buffer);
|
||||
|
||||
print(""s.append(con.buffer));
|
||||
|
||||
if (history.size() > 10)
|
||||
{
|
||||
history.erase(history.begin() + 10);
|
||||
}
|
||||
|
||||
history_index = -1;
|
||||
|
||||
clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
scheduler::loop(draw_console, scheduler::pipeline::renderer);
|
||||
|
||||
con.cursor = 0;
|
||||
con.visible_line_count = 0;
|
||||
con.output_visible = false;
|
||||
con.display_line_offset = 0;
|
||||
con.line_count = 0;
|
||||
strncpy_s(con.buffer, "", 256);
|
||||
|
||||
con.globals.x = 0.0f;
|
||||
con.globals.y = 0.0f;
|
||||
con.globals.left_x = 0.0f;
|
||||
con.globals.font_height = 0.0f;
|
||||
con.globals.may_auto_complete = false;
|
||||
con.globals.info_line_count = 0;
|
||||
strncpy_s(con.globals.auto_complete_choice, "", 64);
|
||||
|
||||
// //add clear command
|
||||
//command::add("clear", [&]()
|
||||
//{
|
||||
//clear();
|
||||
//con.line_count = 0;
|
||||
//con.output.clear();
|
||||
//history_index = -1;
|
||||
//history.clear();
|
||||
//});
|
||||
|
||||
char a2[1] = {};
|
||||
|
||||
// add our dvars
|
||||
dvars::con_inputBoxColor = dvars::register_vec4(
|
||||
"con_inputBoxColor",
|
||||
0.2f, 0.2f, 0.2f, 0.9f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
|
||||
dvars::con_inputHintBoxColor = dvars::register_vec4(
|
||||
"con_inputHintBoxColor",
|
||||
0.3f, 0.3f, 0.3f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
|
||||
dvars::con_outputBarColor = dvars::register_vec4(
|
||||
"con_outputBarColor",
|
||||
0.5f, 0.5f, 0.5f, 0.6f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
|
||||
dvars::con_outputSliderColor = dvars::register_vec4(
|
||||
"con_outputSliderColor",
|
||||
0.9f, 0.9f, 0.5f, 1.00f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
|
||||
dvars::con_outputWindowColor = dvars::register_vec4(
|
||||
"con_outputWindowColor",
|
||||
0.25f, 0.25f, 0.25f, 0.85f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
|
||||
dvars::con_inputDvarMatchColor = dvars::register_vec4(
|
||||
"con_inputDvarMatchColor",
|
||||
1.0f, 1.0f, 0.8f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
|
||||
dvars::con_inputDvarValueColor = dvars::register_vec4(
|
||||
"con_inputDvarValueColor",
|
||||
1.0f, 1.0f, 0.8f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
|
||||
dvars::con_inputDvarInactiveValueColor = dvars::register_vec4(
|
||||
"con_inputDvarInactiveValueColor",
|
||||
0.8f, 0.8f, 0.8f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
|
||||
dvars::con_inputCmdMatchColor = dvars::register_vec4(
|
||||
"con_inputCmdMatchColor",
|
||||
0.80f, 0.80f, 1.0f, 1.0f,
|
||||
0.0f, 1.0f,
|
||||
game::DVAR_FLAG_SAVED);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(game_console::component)
|
18
src/client/component/game_console.hpp
Normal file
18
src/client/component/game_console.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
namespace game_console
|
||||
{
|
||||
enum console_type
|
||||
{
|
||||
con_type_error = 1,
|
||||
con_type_warning = 3,
|
||||
con_type_info = 7
|
||||
};
|
||||
|
||||
void print(int type, const char* fmt, ...);
|
||||
|
||||
bool console_char_event(int local_client_num, int key);
|
||||
bool console_key_event(int local_client_num, int key, int down);
|
||||
|
||||
void execute(const char* cmd);
|
||||
}
|
118
src/client/component/game_module.cpp
Normal file
118
src/client/component/game_module.cpp
Normal file
@ -0,0 +1,118 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "game_module.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace game_module
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour handle_a_hook;
|
||||
utils::hook::detour handle_w_hook;
|
||||
utils::hook::detour handle_ex_a_hook;
|
||||
utils::hook::detour handle_ex_w_hook;
|
||||
utils::hook::detour file_name_a_hook;
|
||||
utils::hook::detour file_name_w_hook;
|
||||
|
||||
HMODULE __stdcall get_module_handle_a(const LPCSTR module_name)
|
||||
{
|
||||
if (!module_name)
|
||||
{
|
||||
return get_game_module();
|
||||
}
|
||||
|
||||
return handle_a_hook.invoke<HMODULE>(module_name);
|
||||
}
|
||||
|
||||
HMODULE __stdcall get_module_handle_w(const LPWSTR module_name)
|
||||
{
|
||||
if (!module_name)
|
||||
{
|
||||
return get_game_module();
|
||||
}
|
||||
|
||||
return handle_w_hook.invoke<HMODULE>(module_name);
|
||||
}
|
||||
|
||||
BOOL __stdcall get_module_handle_ex_a(const DWORD flags, const LPCSTR module_name, HMODULE* hmodule)
|
||||
{
|
||||
if (!module_name)
|
||||
{
|
||||
*hmodule = get_game_module();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return handle_ex_a_hook.invoke<BOOL>(flags, module_name, hmodule);
|
||||
}
|
||||
|
||||
BOOL __stdcall get_module_handle_ex_w(const DWORD flags, const LPCWSTR module_name, HMODULE* hmodule)
|
||||
{
|
||||
if (!module_name)
|
||||
{
|
||||
*hmodule = get_game_module();
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return handle_ex_w_hook.invoke<BOOL>(flags, module_name, hmodule);
|
||||
}
|
||||
|
||||
DWORD __stdcall get_module_file_name_a(HMODULE hmodule, const LPSTR filename, const DWORD size)
|
||||
{
|
||||
if (!hmodule)
|
||||
{
|
||||
hmodule = get_game_module();
|
||||
}
|
||||
|
||||
return file_name_a_hook.invoke<DWORD>(hmodule, filename, size);
|
||||
}
|
||||
|
||||
DWORD __stdcall get_module_file_name_w(HMODULE hmodule, const LPWSTR filename, const DWORD size)
|
||||
{
|
||||
if (!hmodule)
|
||||
{
|
||||
hmodule = get_game_module();
|
||||
}
|
||||
|
||||
return file_name_w_hook.invoke<DWORD>(hmodule, filename, size);
|
||||
}
|
||||
|
||||
void hook_module_resolving()
|
||||
{
|
||||
handle_a_hook.create(&GetModuleHandleA, &get_module_handle_a);
|
||||
handle_w_hook.create(&GetModuleHandleW, &get_module_handle_w);
|
||||
handle_ex_w_hook.create(&GetModuleHandleExA, &get_module_handle_ex_a);
|
||||
handle_ex_w_hook.create(&GetModuleHandleExW, &get_module_handle_ex_w);
|
||||
file_name_a_hook.create(&GetModuleFileNameA, &get_module_file_name_a);
|
||||
file_name_w_hook.create(&GetModuleFileNameW, &get_module_file_name_w);
|
||||
}
|
||||
}
|
||||
|
||||
utils::nt::library get_game_module()
|
||||
{
|
||||
static utils::nt::library game{ HMODULE(0x140000000) };
|
||||
return game;
|
||||
}
|
||||
|
||||
utils::nt::library get_host_module()
|
||||
{
|
||||
static utils::nt::library host{};
|
||||
return host;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_start() override
|
||||
{
|
||||
get_host_module();
|
||||
}
|
||||
|
||||
void post_load() override
|
||||
{
|
||||
hook_module_resolving();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(game_module::component)
|
9
src/client/component/game_module.hpp
Normal file
9
src/client/component/game_module.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
namespace game_module
|
||||
{
|
||||
utils::nt::library get_game_module();
|
||||
utils::nt::library get_host_module();
|
||||
}
|
54
src/client/component/input.cpp
Normal file
54
src/client/component/input.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "game_console.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
namespace input
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour cl_char_event_hook;
|
||||
utils::hook::detour cl_key_event_hook;
|
||||
|
||||
void cl_char_event_stub(const int local_client_num, const int key)
|
||||
{
|
||||
if (!game_console::console_char_event(local_client_num, key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cl_char_event_hook.invoke<void>(local_client_num, key);
|
||||
}
|
||||
|
||||
void cl_key_event_stub(const int local_client_num, const int key, const int down)
|
||||
{
|
||||
if (!game_console::console_key_event(local_client_num, key, down))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cl_key_event_hook.invoke<void>(local_client_num, key, down);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cl_char_event_hook.create(SELECT_VALUE(0x000000000, 0x14024E810), cl_char_event_stub);
|
||||
cl_key_event_hook.create(SELECT_VALUE(0x000000000, 0x14024EA60), cl_key_event_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(input::component)
|
58
src/client/component/localized_strings.cpp
Normal file
58
src/client/component/localized_strings.cpp
Normal file
@ -0,0 +1,58 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "localized_strings.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace localized_strings
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour seh_string_ed_get_string_hook;
|
||||
|
||||
std::unordered_map<std::string, std::string>& get_localized_overrides()
|
||||
{
|
||||
static std::unordered_map<std::string, std::string> overrides;
|
||||
return overrides;
|
||||
}
|
||||
|
||||
std::mutex& get_synchronization_mutex()
|
||||
{
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
const char* seh_string_ed_get_string(const char* reference)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(get_synchronization_mutex());
|
||||
|
||||
auto& overrides = get_localized_overrides();
|
||||
const auto entry = overrides.find(reference);
|
||||
if (entry != overrides.end())
|
||||
{
|
||||
return utils::string::va("%s", entry->second.data());
|
||||
}
|
||||
|
||||
return seh_string_ed_get_string_hook.invoke<const char*>(reference);
|
||||
}
|
||||
}
|
||||
|
||||
void override(const std::string& key, const std::string& value)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(get_synchronization_mutex());
|
||||
get_localized_overrides()[key] = value;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
// Change some localized strings
|
||||
seh_string_ed_get_string_hook.create(SELECT_VALUE(0x000000000, 0x1404BB2A0), &seh_string_ed_get_string);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(localized_strings::component)
|
6
src/client/component/localized_strings.hpp
Normal file
6
src/client/component/localized_strings.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace localized_strings
|
||||
{
|
||||
void override(const std::string& key, const std::string& value);
|
||||
}
|
279
src/client/component/network.cpp
Normal file
279
src/client/component/network.cpp
Normal file
@ -0,0 +1,279 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "network.hpp"
|
||||
#include "game_console.hpp"
|
||||
#include "../game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <game/dvars.hpp>
|
||||
|
||||
namespace network
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, callback>& get_callbacks()
|
||||
{
|
||||
static std::unordered_map<std::string, callback> callbacks{};
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
bool handle_command(game::netadr_s* address, const char* command, game::msg_t* message)
|
||||
{
|
||||
const auto cmd_string = utils::string::to_lower(command);
|
||||
auto& callbacks = get_callbacks();
|
||||
const auto handler = callbacks.find(cmd_string);
|
||||
if (handler == callbacks.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto offset = cmd_string.size() + 5;
|
||||
const std::string_view data(message->data + offset, message->cursize - offset);
|
||||
|
||||
handler->second(*address, data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void handle_command_stub(utils::hook::assembler& a)
|
||||
{
|
||||
const auto return_unhandled = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
|
||||
a.mov(r8, rsi); // message
|
||||
a.mov(rdx, rdi); // command
|
||||
a.mov(rcx, r14); // netaddr
|
||||
|
||||
a.call_aligned(handle_command);
|
||||
|
||||
a.test(al, al);
|
||||
a.jz(return_unhandled);
|
||||
|
||||
// Command handled
|
||||
a.popad64();
|
||||
a.mov(al, 1);
|
||||
a.jmp(0x14020AA10);
|
||||
|
||||
a.bind(return_unhandled);
|
||||
a.popad64();
|
||||
a.jmp(0x14020A19A);
|
||||
}
|
||||
|
||||
int net_compare_base_address(const game::netadr_s* a1, const game::netadr_s* a2)
|
||||
{
|
||||
if (a1->type == a2->type)
|
||||
{
|
||||
switch (a1->type)
|
||||
{
|
||||
case game::netadrtype_t::NA_BOT:
|
||||
case game::netadrtype_t::NA_LOOPBACK:
|
||||
return a1->port == a2->port;
|
||||
|
||||
case game::netadrtype_t::NA_IP:
|
||||
return !memcmp(a1->ip, a2->ip, 4);
|
||||
case game::netadrtype_t::NA_BROADCAST:
|
||||
return true;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void reconnect_migratated_client(void*, 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
|
||||
// We don't want this to do anything. It decides to crash seemingly randomly
|
||||
// Rather than try and let the player in, just tell them they are a duplicate player and reject connection
|
||||
game::NET_OutOfBandPrint(game::NS_SERVER, from, "error\nYou are already connected to the server.");
|
||||
}
|
||||
}
|
||||
|
||||
void on(const std::string& command, const callback& callback)
|
||||
{
|
||||
get_callbacks()[utils::string::to_lower(command)] = callback;
|
||||
}
|
||||
|
||||
void dw_send_to_stub(const unsigned int size, const char* src, game::netadr_s* a3)
|
||||
{
|
||||
sockaddr s = {};
|
||||
game::NetadrToSockadr(a3, &s); //0x1404F62F0
|
||||
sendto(*game::query_socket, src, size - 2, 0, &s, 16);
|
||||
}
|
||||
|
||||
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";
|
||||
packet.append(command);
|
||||
packet.push_back(separator);
|
||||
packet.append(data);
|
||||
|
||||
send_data(address, packet);
|
||||
}
|
||||
|
||||
|
||||
void send_data(const game::netadr_s& address, const std::string& data)
|
||||
{
|
||||
if (address.type == game::NA_LOOPBACK)
|
||||
{
|
||||
game::NET_SendLoopPacket(game::NS_CLIENT1, static_cast<int>(data.size()), data.data(), &address);
|
||||
}
|
||||
else
|
||||
{
|
||||
game::Sys_SendPacket(static_cast<int>(data.size()), data.data(), &address);
|
||||
}
|
||||
}
|
||||
|
||||
bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b)
|
||||
{
|
||||
return net_compare_address(&a, &b);
|
||||
}
|
||||
|
||||
const char* net_adr_to_string(const game::netadr_s& a)
|
||||
{
|
||||
if (a.type == game::netadrtype_t::NA_LOOPBACK)
|
||||
{
|
||||
return "loopback";
|
||||
}
|
||||
|
||||
if (a.type == game::netadrtype_t::NA_BOT)
|
||||
{
|
||||
return "bot";
|
||||
}
|
||||
|
||||
if (a.type == game::netadrtype_t::NA_IP || a.type == game::netadrtype_t::NA_BROADCAST)
|
||||
{
|
||||
if (a.port)
|
||||
{
|
||||
return utils::string::va("%u.%u.%u.%u:%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3], htons(a.port));
|
||||
}
|
||||
|
||||
return utils::string::va("%u.%u.%u.%u", a.ip[0], a.ip[1], a.ip[2], a.ip[3]);
|
||||
}
|
||||
|
||||
return "bad";
|
||||
}
|
||||
|
||||
game::dvar_t* register_netport_stub(const char* dvarName, int value, int min, int max, unsigned int flags,
|
||||
const char* description)
|
||||
{
|
||||
auto dvar = dvars::register_int("net_port", 27016, 0, 0xFFFFu, game::DVAR_FLAG_LATCHED, "Network port");
|
||||
|
||||
// read net_port from command line
|
||||
command::read_startup_variable("net_port");
|
||||
|
||||
return dvar;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// redirect dw_sendto to raw socket
|
||||
//utils::hook::jump(0x1404D850A, reinterpret_cast<void*>(0x1404D849A));
|
||||
utils::hook::call(0x140513467, dw_send_to_stub); // H1MP64(1.4)
|
||||
utils::hook::jump(game::Sys_SendPacket, dw_send_to_stub);
|
||||
|
||||
// intercept command handling
|
||||
utils::hook::jump(0x140252327, utils::hook::assemble(handle_command_stub), true); // H1MP64(1.4)
|
||||
|
||||
// handle xuid without secure connection
|
||||
utils::hook::nop(0x140486AAF, 6); // H1MP64(1.4)
|
||||
|
||||
utils::hook::jump(0x140424F20, net_compare_address); // H1MP64(1.4)
|
||||
utils::hook::jump(0x140424F70, net_compare_base_address); // H1MP64(1.4)
|
||||
|
||||
// don't establish secure conenction
|
||||
utils::hook::set<uint8_t>(0x14027EA4D, 0xEB); // H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x14027EB1E, 0xEB); // H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x14027EF8D, 0xEB); // H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x14025081F, 0xEB); // H1MP64(1.4)
|
||||
|
||||
// ignore unregistered connection
|
||||
utils::hook::jump(0x140480F46, reinterpret_cast<void*>(0x140480EE5)); // H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x140480F3B, 0xEB); // H1MP64(1.4)
|
||||
|
||||
// disable xuid verification
|
||||
utils::hook::set<uint8_t>(0x14005B62D, 0xEB); // H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x14005B649, 0xEB); // H1MP64(1.4) NOT_SURE SHOULD JZ BUT LEA
|
||||
|
||||
// disable xuid verification
|
||||
utils::hook::nop(0x14048382C, 2); // H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x140483889, 0xEB); // H1MP64(1.4) NOT_SURE
|
||||
|
||||
// ignore configstring mismatch
|
||||
utils::hook::set<uint8_t>(0x1402591C9, 0xEB); // H1MP64(1.4)
|
||||
|
||||
// ignore dw handle in SV_PacketEvent
|
||||
utils::hook::set<uint8_t>(0x1404898E2, 0xEB); // H1MP64(1.4)
|
||||
utils::hook::call(0x1404898D6, &net_compare_address); // H1MP64(1.4)
|
||||
|
||||
// ignore dw handle in SV_FindClientByAddress
|
||||
utils::hook::set<uint8_t>(0x140488EFD, 0xEB); // H1MP64(1.4)
|
||||
utils::hook::call(0x140488EF1, &net_compare_address); // H1MP64(1.4)
|
||||
|
||||
// ignore dw handle in SV_DirectConnect
|
||||
utils::hook::set<uint8_t>(0x140480C58, 0xEB); // H1MP64(1.4)
|
||||
utils::hook::set<uint8_t>(0x140480CF2, 0xEB); // H1MP64(1.4) NOT_SURE
|
||||
utils::hook::call(0x140480C4B, &net_compare_address); // H1MP64(1.4)
|
||||
utils::hook::call(0x140480E62, &net_compare_address); // H1MP64(1.4)
|
||||
|
||||
// increase cl_maxpackets
|
||||
//dvars::override::Dvar_RegisterInt("cl_maxpackets", 1000, 1, 1000, 0x1);
|
||||
dvars::override::register_int("cl_maxpackets", 1000, 1, 1000, 0x1, true);
|
||||
|
||||
// ignore impure client
|
||||
utils::hook::jump(0x140481B58, reinterpret_cast<void*>(0x140481BEE)); // H1MP64(1.4)
|
||||
|
||||
// don't send checksum
|
||||
utils::hook::set<uint8_t>(0x140513433, 0); // H1MP64(1.4) mov: r8d, edi ; LEN
|
||||
|
||||
// don't read checksum
|
||||
utils::hook::jump(0x140513389, 0x14051339F); // H1MP64(1.4)
|
||||
|
||||
// don't try to reconnect client
|
||||
utils::hook::call(0x140480DFF, reconnect_migratated_client); // H1MP64(1.4)
|
||||
utils::hook::nop(0x140480DDB, 4); // H1MP64(1.4) this crashes when reconnecting for some reason
|
||||
|
||||
// allow server owner to modify net_port before the socket bind
|
||||
utils::hook::call(0x140512BE5, register_netport_stub); // H1MP64(1.4)
|
||||
utils::hook::call(0x140512D20, register_netport_stub); // H1MP64(1.4)
|
||||
|
||||
// ignore built in "print" oob command and add in our own
|
||||
utils::hook::set<uint8_t>(0x14025280E, 0xEB); // H1MP64(1.4)
|
||||
on("print", [](const game::netadr_s& addr, const std::string_view& data)
|
||||
{
|
||||
const std::string message{ data };
|
||||
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
printf("%s\n", message.data());
|
||||
}
|
||||
else
|
||||
{
|
||||
game_console::print(game_console::con_type_info, "%s\n", message.data());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(network::component)
|
48
src/client/component/network.hpp
Normal file
48
src/client/component/network.hpp
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace network
|
||||
{
|
||||
using callback = std::function<void(const game::netadr_s&, const std::string_view&)>;
|
||||
|
||||
void on(const std::string& command, const callback& callback);
|
||||
void send(const game::netadr_s& address, const std::string& command, const std::string& data = {}, char separator = ' ');
|
||||
void send_data(const game::netadr_s& address, const std::string& data);
|
||||
|
||||
bool are_addresses_equal(const game::netadr_s& a, const game::netadr_s& b);
|
||||
|
||||
const char* net_adr_to_string(const game::netadr_s& a);
|
||||
}
|
||||
|
||||
inline bool operator==(const game::netadr_s& a, const game::netadr_s& b)
|
||||
{
|
||||
return network::are_addresses_equal(a, b); //
|
||||
}
|
||||
|
||||
inline bool operator!=(const game::netadr_s& a, const game::netadr_s& b)
|
||||
{
|
||||
return !(a == b); //
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
template <>
|
||||
struct equal_to<game::netadr_s>
|
||||
{
|
||||
using result_type = bool;
|
||||
|
||||
bool operator()(const game::netadr_s& lhs, const game::netadr_s& rhs) const
|
||||
{
|
||||
return network::are_addresses_equal(lhs, rhs);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<game::netadr_s>
|
||||
{
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
83
src/client/component/redirect.cpp
Normal file
83
src/client/component/redirect.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include "game_module.hpp"
|
||||
|
||||
namespace redirect
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void launch_complementary_game(const bool singleplayer, const std::string& mode = "")
|
||||
{
|
||||
const auto self = game_module::get_host_module();
|
||||
|
||||
STARTUPINFOA startup_info;
|
||||
PROCESS_INFORMATION process_info;
|
||||
|
||||
ZeroMemory(&startup_info, sizeof(startup_info));
|
||||
ZeroMemory(&process_info, sizeof(process_info));
|
||||
startup_info.cb = sizeof(startup_info);
|
||||
|
||||
auto* arguments = const_cast<char*>(utils::string::va("%s%s%s", self.get_path().data(),
|
||||
(singleplayer ? " -singleplayer" : " -multiplayer"),
|
||||
(mode.empty() ? "" : (" +"s + mode).data())));
|
||||
CreateProcessA(self.get_path().data(), arguments, nullptr, nullptr, false, NULL, nullptr, nullptr,
|
||||
&startup_info, &process_info);
|
||||
|
||||
if (process_info.hThread && process_info.hThread != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(process_info.hThread);
|
||||
}
|
||||
|
||||
if (process_info.hProcess && process_info.hProcess != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(process_info.hProcess);
|
||||
}
|
||||
}
|
||||
|
||||
HINSTANCE shell_execute_a(const HWND hwnd, const LPCSTR operation, const LPCSTR file, const LPCSTR parameters,
|
||||
const LPCSTR directory, const INT show_cmd)
|
||||
{
|
||||
if (utils::string::starts_with(file, "steam://run/393080/"))
|
||||
{
|
||||
launch_complementary_game(true);
|
||||
return HINSTANCE(33);
|
||||
}
|
||||
else if (utils::string::starts_with(file, "steam://run/393100/"))
|
||||
{
|
||||
std::string mode(file);
|
||||
mode.erase(0, 20);
|
||||
if (!mode.empty())
|
||||
{
|
||||
mode = utils::string::replace(mode, "%2b", ""); // '+'
|
||||
mode = utils::string::replace(mode, "%2", " "); // ' '
|
||||
}
|
||||
|
||||
launch_complementary_game(false, mode);
|
||||
return HINSTANCE(33);
|
||||
}
|
||||
|
||||
return ShellExecuteA(hwnd, operation, file, parameters, directory, show_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void* load_import(const std::string& library, const std::string& function) override
|
||||
{
|
||||
if (library == "SHELL32.dll")
|
||||
{
|
||||
if (function == "ShellExecuteA")
|
||||
{
|
||||
return shell_execute_a;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(redirect::component)
|
170
src/client/component/scheduler.cpp
Normal file
170
src/client/component/scheduler.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct task
|
||||
{
|
||||
std::function<bool()> handler{};
|
||||
std::chrono::milliseconds interval{};
|
||||
std::chrono::high_resolution_clock::time_point last_call{};
|
||||
};
|
||||
|
||||
using task_list = std::vector<task>;
|
||||
|
||||
class task_pipeline
|
||||
{
|
||||
public:
|
||||
void add(task&& task)
|
||||
{
|
||||
new_callbacks_.access([&task](task_list& tasks)
|
||||
{
|
||||
tasks.emplace_back(std::move(task));
|
||||
});
|
||||
}
|
||||
|
||||
void execute()
|
||||
{
|
||||
callbacks_.access([&](task_list& tasks)
|
||||
{
|
||||
this->merge_callbacks();
|
||||
|
||||
for (auto i = tasks.begin(); i != tasks.end();)
|
||||
{
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const auto diff = now - i->last_call;
|
||||
|
||||
if (diff < i->interval)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
i->last_call = now;
|
||||
|
||||
const auto res = i->handler();
|
||||
if (res == cond_end)
|
||||
{
|
||||
i = tasks.erase(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
++i;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
utils::concurrency::container<task_list> new_callbacks_;
|
||||
utils::concurrency::container<task_list, std::recursive_mutex> callbacks_;
|
||||
|
||||
void merge_callbacks()
|
||||
{
|
||||
callbacks_.access([&](task_list& tasks)
|
||||
{
|
||||
new_callbacks_.access([&](task_list& new_tasks)
|
||||
{
|
||||
tasks.insert(tasks.end(), std::move_iterator<task_list::iterator>(new_tasks.begin()), std::move_iterator<task_list::iterator>(new_tasks.end()));
|
||||
new_tasks = {};
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
volatile bool kill = false;
|
||||
std::thread thread;
|
||||
task_pipeline pipelines[pipeline::count];
|
||||
utils::hook::detour r_end_frame_hook;
|
||||
utils::hook::detour g_run_frame_hook;
|
||||
utils::hook::detour main_frame_hook;
|
||||
|
||||
void execute(const pipeline type)
|
||||
{
|
||||
assert(type >= 0 && type < pipeline::count);
|
||||
pipelines[type].execute();
|
||||
}
|
||||
|
||||
void r_end_frame_stub()
|
||||
{
|
||||
execute(pipeline::renderer);
|
||||
r_end_frame_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void server_frame_stub()
|
||||
{
|
||||
g_run_frame_hook.invoke<void>();
|
||||
execute(pipeline::server);
|
||||
}
|
||||
|
||||
void main_frame_stub()
|
||||
{
|
||||
main_frame_hook.invoke<void>();
|
||||
execute(pipeline::main);
|
||||
}
|
||||
}
|
||||
|
||||
void schedule(const std::function<bool()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
assert(type >= 0 && type < pipeline::count);
|
||||
|
||||
task task;
|
||||
task.handler = callback;
|
||||
task.interval = delay;
|
||||
task.last_call = std::chrono::high_resolution_clock::now();
|
||||
|
||||
pipelines[type].add(std::move(task));
|
||||
}
|
||||
|
||||
void loop(const std::function<void()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return cond_continue;
|
||||
}, type, delay);
|
||||
}
|
||||
|
||||
void once(const std::function<void()>& callback, const pipeline type,
|
||||
const std::chrono::milliseconds delay)
|
||||
{
|
||||
schedule([callback]()
|
||||
{
|
||||
callback();
|
||||
return cond_end;
|
||||
}, type, delay);
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
//thread = std::thread([]()
|
||||
//{
|
||||
// while (!kill)
|
||||
// {
|
||||
// execute(pipeline::async);
|
||||
// std::this_thread::sleep_for(10ms);
|
||||
// }
|
||||
//});
|
||||
|
||||
r_end_frame_hook.create(0x1405FE470, scheduler::r_end_frame_stub); // H1MP64[1.4]
|
||||
g_run_frame_hook.create(0x1402772D0, scheduler::server_frame_stub); // H1MP64[1.4]
|
||||
main_frame_hook.create(0x1401CE8D0, scheduler::main_frame_stub); // H1MP64[1.4]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scheduler::component)
|
33
src/client/component/scheduler.hpp
Normal file
33
src/client/component/scheduler.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
namespace scheduler
|
||||
{
|
||||
enum pipeline
|
||||
{
|
||||
// Asynchronuous pipeline, disconnected from the game
|
||||
async = 0,
|
||||
|
||||
// The game's rendering pipeline
|
||||
renderer,
|
||||
|
||||
// The game's server thread
|
||||
server,
|
||||
|
||||
// The game's main thread
|
||||
main,
|
||||
|
||||
count,
|
||||
};
|
||||
|
||||
static const bool cond_continue = false;
|
||||
static const bool cond_end = true;
|
||||
|
||||
void schedule(const std::function<bool()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void loop(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void once(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
void on_game_initialized(const std::function<void()>& callback, pipeline type = pipeline::async,
|
||||
std::chrono::milliseconds delay = 0ms);
|
||||
}
|
188
src/client/component/steam_proxy.cpp
Normal file
188
src/client/component/steam_proxy.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "steam_proxy.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/flags.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/binary_resource.hpp>
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "steam/interface.hpp"
|
||||
#include "steam/steam.hpp"
|
||||
|
||||
namespace steam_proxy
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::binary_resource runner_file(RUNNER, "runner.exe");
|
||||
|
||||
bool is_disabled()
|
||||
{
|
||||
static const auto disabled = utils::flags::has_flag("nosteam");
|
||||
return disabled;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_load() override
|
||||
{
|
||||
if (game::environment::is_dedi() || is_disabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->load_client();
|
||||
this->clean_up_on_error();
|
||||
|
||||
#ifndef DEV_BUILD
|
||||
try
|
||||
{
|
||||
this->start_mod("\xF0\x9F\x90\xA4" " H1-Mod: "s + (game::environment::is_sp() ? "Singleplayer" : "Multiplayer"), game::environment::is_sp() ? 393080 : 393100);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
printf("Steam: %s\n", e.what());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void pre_destroy() override
|
||||
{
|
||||
if (this->steam_client_module_)
|
||||
{
|
||||
if (this->steam_pipe_)
|
||||
{
|
||||
if (this->global_user_)
|
||||
{
|
||||
this->steam_client_module_.invoke<void>("Steam_ReleaseUser", this->steam_pipe_,
|
||||
this->global_user_);
|
||||
}
|
||||
|
||||
this->steam_client_module_.invoke<bool>("Steam_BReleaseSteamPipe", this->steam_pipe_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const utils::nt::library& get_overlay_module() const
|
||||
{
|
||||
return steam_overlay_module_;
|
||||
}
|
||||
|
||||
private:
|
||||
utils::nt::library steam_client_module_{};
|
||||
utils::nt::library steam_overlay_module_{};
|
||||
|
||||
steam::interface client_engine_ {};
|
||||
steam::interface client_user_ {};
|
||||
steam::interface client_utils_ {};
|
||||
|
||||
void* steam_pipe_ = nullptr;
|
||||
void* global_user_ = nullptr;
|
||||
|
||||
void* load_client_engine() const
|
||||
{
|
||||
if (!this->steam_client_module_) return nullptr;
|
||||
|
||||
for (auto i = 1; i > 0; ++i)
|
||||
{
|
||||
std::string name = utils::string::va("CLIENTENGINE_INTERFACE_VERSION%03i", i);
|
||||
auto* const client_engine = this->steam_client_module_
|
||||
.invoke<void*>("CreateInterface", name.data(), nullptr);
|
||||
if (client_engine) return client_engine;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void load_client()
|
||||
{
|
||||
const std::filesystem::path steam_path = steam::SteamAPI_GetSteamInstallPath();
|
||||
if (steam_path.empty()) return;
|
||||
|
||||
utils::nt::library::load(steam_path / "tier0_s64.dll");
|
||||
utils::nt::library::load(steam_path / "vstdlib_s64.dll");
|
||||
this->steam_overlay_module_ = utils::nt::library::load(steam_path / "gameoverlayrenderer64.dll");
|
||||
this->steam_client_module_ = utils::nt::library::load(steam_path / "steamclient64.dll");
|
||||
if (!this->steam_client_module_) return;
|
||||
|
||||
this->client_engine_ = load_client_engine();
|
||||
if (!this->client_engine_) return;
|
||||
|
||||
this->steam_pipe_ = this->steam_client_module_.invoke<void*>("Steam_CreateSteamPipe");
|
||||
this->global_user_ = this->steam_client_module_.invoke<void*>(
|
||||
"Steam_ConnectToGlobalUser", this->steam_pipe_);
|
||||
this->client_user_ = this->client_engine_.invoke<void*>(8, this->steam_pipe_, this->global_user_);
|
||||
// GetIClientUser
|
||||
this->client_utils_ = this->client_engine_.invoke<void*>(14, this->steam_pipe_); // GetIClientUtils
|
||||
}
|
||||
|
||||
void start_mod(const std::string& title, size_t app_id)
|
||||
{
|
||||
if (!this->client_utils_ || !this->client_user_) return;
|
||||
|
||||
if (!this->client_user_.invoke<bool>("BIsSubscribedApp", app_id))
|
||||
{
|
||||
app_id = 480; // Spacewar
|
||||
}
|
||||
|
||||
this->client_utils_.invoke<void>("SetAppIDForCurrentPipe", app_id, false);
|
||||
|
||||
char our_directory[MAX_PATH] = { 0 };
|
||||
GetCurrentDirectoryA(sizeof(our_directory), our_directory);
|
||||
|
||||
const auto path = runner_file.get_extracted_file();
|
||||
const std::string cmdline = utils::string::va("\"%s\" -proc %d", path.data(), GetCurrentProcessId());
|
||||
|
||||
steam::game_id game_id;
|
||||
game_id.raw.type = 1; // k_EGameIDTypeGameMod
|
||||
game_id.raw.app_id = app_id & 0xFFFFFF;
|
||||
|
||||
const auto* mod_id = "H1-Mod";
|
||||
game_id.raw.mod_id = *reinterpret_cast<const unsigned int*>(mod_id) | 0x80000000;
|
||||
|
||||
this->client_user_.invoke<bool>("SpawnProcess", path.data(), cmdline.data(), our_directory,
|
||||
&game_id.bits, title.data(), 0, 0, 0);
|
||||
}
|
||||
|
||||
void clean_up_on_error()
|
||||
{
|
||||
scheduler::schedule([this]()
|
||||
{
|
||||
if (this->steam_client_module_
|
||||
&& this->steam_pipe_
|
||||
&& this->global_user_
|
||||
&& this->steam_client_module_.invoke<bool>("Steam_BConnected", this->global_user_,
|
||||
this->steam_pipe_)
|
||||
&& this->steam_client_module_.invoke<bool>("Steam_BLoggedOn", this->global_user_, this->steam_pipe_)
|
||||
)
|
||||
{
|
||||
return scheduler::cond_continue;
|
||||
}
|
||||
|
||||
this->client_engine_ = nullptr;
|
||||
this->client_user_ = nullptr;
|
||||
this->client_utils_ = nullptr;
|
||||
|
||||
this->steam_pipe_ = nullptr;
|
||||
this->global_user_ = nullptr;
|
||||
|
||||
this->steam_client_module_ = utils::nt::library{ nullptr };
|
||||
|
||||
return scheduler::cond_end;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const utils::nt::library& get_overlay_module()
|
||||
{
|
||||
// TODO: Find a better way to do this
|
||||
return component_loader::get<component>()->get_overlay_module();
|
||||
}
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(steam_proxy::component)
|
7
src/client/component/steam_proxy.hpp
Normal file
7
src/client/component/steam_proxy.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <utils/nt.hpp>
|
||||
|
||||
namespace steam_proxy
|
||||
{
|
||||
const utils::nt::library& get_overlay_module();
|
||||
}
|
182
src/client/game/demonware/bit_buffer.cpp
Normal file
182
src/client/game/demonware/bit_buffer.cpp
Normal file
@ -0,0 +1,182 @@
|
||||
#include <std_include.hpp>
|
||||
#include "bit_buffer.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bool bit_buffer::read_bytes(const unsigned int bytes, unsigned char* output)
|
||||
{
|
||||
return this->read(bytes * 8, output);
|
||||
}
|
||||
|
||||
bool bit_buffer::read_bool(bool* output)
|
||||
{
|
||||
if (!this->read_data_type(1))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this->read(1, output);
|
||||
}
|
||||
|
||||
bool bit_buffer::read_uint32(unsigned int* output)
|
||||
{
|
||||
if (!this->read_data_type(8))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return this->read(32, output);
|
||||
}
|
||||
|
||||
bool bit_buffer::read_data_type(const char expected)
|
||||
{
|
||||
char data_type = 0;
|
||||
|
||||
if (!this->use_data_types_) return true;
|
||||
if (this->read(5, &data_type))
|
||||
{
|
||||
return (data_type == expected);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bit_buffer::write_bytes(const unsigned int bytes, const char* data)
|
||||
{
|
||||
return this->write_bytes(bytes, reinterpret_cast<const unsigned char*>(data));
|
||||
}
|
||||
|
||||
bool bit_buffer::write_bytes(const unsigned int bytes, const unsigned char* data)
|
||||
{
|
||||
return this->write(bytes * 8, data);
|
||||
}
|
||||
|
||||
bool bit_buffer::write_bool(bool data)
|
||||
{
|
||||
if (this->write_data_type(1))
|
||||
{
|
||||
return this->write(1, &data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bit_buffer::write_int32(int data)
|
||||
{
|
||||
if (this->write_data_type(7))
|
||||
{
|
||||
return this->write(32, &data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bit_buffer::write_uint32(unsigned int data)
|
||||
{
|
||||
if (this->write_data_type(8))
|
||||
{
|
||||
return this->write(32, &data);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bit_buffer::write_data_type(char data)
|
||||
{
|
||||
if (!this->use_data_types_)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return this->write(5, &data);
|
||||
}
|
||||
|
||||
bool bit_buffer::read(unsigned int bits, void* output)
|
||||
{
|
||||
if (bits == 0) return false;
|
||||
if ((this->current_bit_ + bits) > (this->buffer_.size() * 8)) return false;
|
||||
|
||||
int cur_byte = this->current_bit_ >> 3;
|
||||
auto cur_out = 0;
|
||||
|
||||
const char* bytes = this->buffer_.data();
|
||||
const auto output_bytes = static_cast<unsigned char*>(output);
|
||||
|
||||
while (bits > 0)
|
||||
{
|
||||
const int min_bit = (bits < 8) ? bits : 8;
|
||||
const auto this_byte = bytes[cur_byte++] & 0xFF;
|
||||
const int remain = this->current_bit_ & 7;
|
||||
|
||||
if ((min_bit + remain) <= 8)
|
||||
{
|
||||
output_bytes[cur_out] = BYTE((0xFF >> (8 - min_bit)) & (this_byte >> remain));
|
||||
}
|
||||
else
|
||||
{
|
||||
output_bytes[cur_out] = BYTE(
|
||||
(0xFF >> (8 - min_bit)) & (bytes[cur_byte] << (8 - remain)) | (this_byte >> remain));
|
||||
}
|
||||
|
||||
cur_out++;
|
||||
this->current_bit_ += min_bit;
|
||||
bits -= min_bit;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool bit_buffer::write(const unsigned int bits, const void* data)
|
||||
{
|
||||
if (bits == 0) return false;
|
||||
this->buffer_.resize(this->buffer_.size() + (bits >> 3) + 1);
|
||||
|
||||
int bit = bits;
|
||||
const auto bytes = const_cast<char*>(this->buffer_.data());
|
||||
const auto* input_bytes = static_cast<const unsigned char*>(data);
|
||||
|
||||
while (bit > 0)
|
||||
{
|
||||
const int bit_pos = this->current_bit_ & 7;
|
||||
auto rem_bit = 8 - bit_pos;
|
||||
const auto this_write = (bit < rem_bit) ? bit : rem_bit;
|
||||
|
||||
const BYTE mask = ((0xFF >> rem_bit) | (0xFF << (bit_pos + this_write)));
|
||||
const int byte_pos = this->current_bit_ >> 3;
|
||||
|
||||
const BYTE temp_byte = (mask & bytes[byte_pos]);
|
||||
const BYTE this_bit = ((bits - bit) & 7);
|
||||
const auto this_byte = (bits - bit) >> 3;
|
||||
|
||||
auto this_data = input_bytes[this_byte];
|
||||
|
||||
const auto next_byte = (((bits - 1) >> 3) > this_byte) ? input_bytes[this_byte + 1] : 0;
|
||||
|
||||
this_data = BYTE((next_byte << (8 - this_bit)) | (this_data >> this_bit));
|
||||
|
||||
const BYTE out_byte = (~mask & (this_data << bit_pos) | temp_byte);
|
||||
bytes[byte_pos] = out_byte;
|
||||
|
||||
this->current_bit_ += this_write;
|
||||
bit -= this_write;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void bit_buffer::set_use_data_types(const bool use_data_types)
|
||||
{
|
||||
this->use_data_types_ = use_data_types;
|
||||
}
|
||||
|
||||
unsigned int bit_buffer::size() const
|
||||
{
|
||||
return this->current_bit_ / 8 + (this->current_bit_ % 8 ? 1 : 0);
|
||||
}
|
||||
|
||||
std::string& bit_buffer::get_buffer()
|
||||
{
|
||||
this->buffer_.resize(this->size());
|
||||
return this->buffer_;
|
||||
}
|
||||
}
|
40
src/client/game/demonware/bit_buffer.hpp
Normal file
40
src/client/game/demonware/bit_buffer.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bit_buffer final
|
||||
{
|
||||
public:
|
||||
bit_buffer() = default;
|
||||
|
||||
explicit bit_buffer(std::string buffer) : buffer_(std::move(buffer))
|
||||
{
|
||||
}
|
||||
|
||||
bool read_bytes(unsigned int bytes, unsigned char* output);
|
||||
bool read_bool(bool* output);
|
||||
bool read_uint32(unsigned int* output);
|
||||
bool read_data_type(char expected);
|
||||
|
||||
bool write_bytes(unsigned int bytes, const char* data);
|
||||
bool write_bytes(unsigned int bytes, const unsigned char* data);
|
||||
bool write_bool(bool data);
|
||||
bool write_int32(int data);
|
||||
bool write_uint32(unsigned int data);
|
||||
bool write_data_type(char data);
|
||||
|
||||
bool read(unsigned int bits, void* output);
|
||||
bool write(unsigned int bits, const void* data);
|
||||
|
||||
void set_use_data_types(bool use_data_types);
|
||||
|
||||
unsigned int size() const;
|
||||
|
||||
std::string& get_buffer();
|
||||
|
||||
private:
|
||||
std::string buffer_{};
|
||||
unsigned int current_bit_ = 0;
|
||||
bool use_data_types_ = true;
|
||||
};
|
||||
}
|
308
src/client/game/demonware/byte_buffer.cpp
Normal file
308
src/client/game/demonware/byte_buffer.cpp
Normal file
@ -0,0 +1,308 @@
|
||||
#include <std_include.hpp>
|
||||
#include "byte_buffer.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bool byte_buffer::read_byte(unsigned char* output)
|
||||
{
|
||||
if (!this->read_data_type(3)) return false;
|
||||
return this->read(1, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_bool(bool* output)
|
||||
{
|
||||
if (!this->read_data_type(1)) return false;
|
||||
return this->read(1, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_int16(short* output)
|
||||
{
|
||||
if (!this->read_data_type(5)) return false;
|
||||
return this->read(2, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_uint16(unsigned short* output)
|
||||
{
|
||||
if (!this->read_data_type(6)) return false;
|
||||
return this->read(2, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_int32(int* output)
|
||||
{
|
||||
if (!this->read_data_type(7)) return false;
|
||||
return this->read(4, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_uint32(unsigned int* output)
|
||||
{
|
||||
if (!this->read_data_type(8)) return false;
|
||||
return this->read(4, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_int64(__int64* output)
|
||||
{
|
||||
if (!this->read_data_type(9)) return false;
|
||||
return this->read(8, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_uint64(unsigned __int64* output)
|
||||
{
|
||||
if (!this->read_data_type(10)) return false;
|
||||
return this->read(8, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_float(float* output)
|
||||
{
|
||||
if (!this->read_data_type(13)) return false;
|
||||
return this->read(4, output);
|
||||
}
|
||||
|
||||
bool byte_buffer::read_string(std::string* output)
|
||||
{
|
||||
char* out_data;
|
||||
if (this->read_string(&out_data))
|
||||
{
|
||||
output->clear();
|
||||
output->append(out_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool byte_buffer::read_string(char** output)
|
||||
{
|
||||
if (!this->read_data_type(16)) return false;
|
||||
|
||||
*output = const_cast<char*>(this->buffer_.data()) + this->current_byte_;
|
||||
this->current_byte_ += strlen(*output) + 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool byte_buffer::read_string(char* output, const int length)
|
||||
{
|
||||
if (!this->read_data_type(16)) return false;
|
||||
|
||||
strcpy_s(output, length, const_cast<char*>(this->buffer_.data()) + this->current_byte_);
|
||||
this->current_byte_ += strlen(output) + 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool byte_buffer::read_blob(std::string* output)
|
||||
{
|
||||
char* out_data;
|
||||
int length;
|
||||
if (this->read_blob(&out_data, &length))
|
||||
{
|
||||
output->clear();
|
||||
output->append(out_data, length);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool byte_buffer::read_blob(char** output, int* length)
|
||||
{
|
||||
if (!this->read_data_type(0x13))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int size;
|
||||
this->read_uint32(&size);
|
||||
|
||||
*output = const_cast<char*>(this->buffer_.data()) + this->current_byte_;
|
||||
*length = static_cast<int>(size);
|
||||
|
||||
this->current_byte_ += size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool byte_buffer::read_data_type(const char expected)
|
||||
{
|
||||
if (!this->use_data_types_) return true;
|
||||
|
||||
char type;
|
||||
this->read(1, &type);
|
||||
return type == expected;
|
||||
}
|
||||
|
||||
bool byte_buffer::read_array_header(const unsigned char expected, unsigned int* element_count,
|
||||
unsigned int* element_size)
|
||||
{
|
||||
if (element_count) *element_count = 0;
|
||||
if (element_size) *element_size = 0;
|
||||
|
||||
if (!this->read_data_type(expected + 100)) return false;
|
||||
|
||||
uint32_t array_size, el_count;
|
||||
if (!this->read_uint32(&array_size)) return false;
|
||||
|
||||
this->set_use_data_types(false);
|
||||
this->read_uint32(&el_count);
|
||||
this->set_use_data_types(true);
|
||||
|
||||
if (element_count) *element_count = el_count;
|
||||
if (element_size) *element_size = array_size / el_count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool byte_buffer::write_byte(char data)
|
||||
{
|
||||
this->write_data_type(3);
|
||||
return this->write(1, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_bool(bool data)
|
||||
{
|
||||
this->write_data_type(1);
|
||||
return this->write(1, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_int16(short data)
|
||||
{
|
||||
this->write_data_type(5);
|
||||
return this->write(2, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_uint16(unsigned short data)
|
||||
{
|
||||
this->write_data_type(6);
|
||||
return this->write(2, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_int32(int data)
|
||||
{
|
||||
this->write_data_type(7);
|
||||
return this->write(4, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_uint32(unsigned int data)
|
||||
{
|
||||
this->write_data_type(8);
|
||||
return this->write(4, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_int64(__int64 data)
|
||||
{
|
||||
this->write_data_type(9);
|
||||
return this->write(8, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_uint64(unsigned __int64 data)
|
||||
{
|
||||
this->write_data_type(10);
|
||||
return this->write(8, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_data_type(char data)
|
||||
{
|
||||
if (!this->use_data_types_) return true;
|
||||
return this->write(1, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_float(float data)
|
||||
{
|
||||
this->write_data_type(13);
|
||||
return this->write(4, &data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_string(const std::string& data)
|
||||
{
|
||||
return this->write_string(data.data());
|
||||
}
|
||||
|
||||
bool byte_buffer::write_string(const char* data)
|
||||
{
|
||||
this->write_data_type(16);
|
||||
return this->write(static_cast<int>(strlen(data)) + 1, data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_blob(const std::string& data)
|
||||
{
|
||||
return this->write_blob(data.data(), INT(data.size()));
|
||||
}
|
||||
|
||||
bool byte_buffer::write_blob(const char* data, const int length)
|
||||
{
|
||||
this->write_data_type(0x13);
|
||||
this->write_uint32(length);
|
||||
|
||||
return this->write(length, data);
|
||||
}
|
||||
|
||||
bool byte_buffer::write_array_header(const unsigned char type, const unsigned int element_count,
|
||||
const unsigned int element_size)
|
||||
{
|
||||
const auto using_types = this->is_using_data_types();
|
||||
this->set_use_data_types(false);
|
||||
|
||||
auto result = this->write_byte(type + 100);
|
||||
|
||||
this->set_use_data_types(true);
|
||||
result &= this->write_uint32(element_count * element_size);
|
||||
this->set_use_data_types(false);
|
||||
|
||||
result &= this->write_uint32(element_count);
|
||||
|
||||
this->set_use_data_types(using_types);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool byte_buffer::read(const int bytes, void* output)
|
||||
{
|
||||
if (bytes + this->current_byte_ > this->buffer_.size()) return false;
|
||||
|
||||
std::memmove(output, this->buffer_.data() + this->current_byte_, bytes);
|
||||
this->current_byte_ += bytes;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool byte_buffer::write(const int bytes, const void* data)
|
||||
{
|
||||
this->buffer_.append(static_cast<const char*>(data), bytes);
|
||||
this->current_byte_ += bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool byte_buffer::write(const std::string& data)
|
||||
{
|
||||
return this->write(static_cast<int>(data.size()), data.data());
|
||||
}
|
||||
|
||||
void byte_buffer::set_use_data_types(const bool use_data_types)
|
||||
{
|
||||
this->use_data_types_ = use_data_types;
|
||||
}
|
||||
|
||||
size_t byte_buffer::size() const
|
||||
{
|
||||
return this->buffer_.size();
|
||||
}
|
||||
|
||||
bool byte_buffer::is_using_data_types() const
|
||||
{
|
||||
return use_data_types_;
|
||||
}
|
||||
|
||||
std::string& byte_buffer::get_buffer()
|
||||
{
|
||||
return this->buffer_;
|
||||
}
|
||||
|
||||
std::string byte_buffer::get_remaining()
|
||||
{
|
||||
return std::string(this->buffer_.begin() + this->current_byte_, this->buffer_.end());
|
||||
}
|
||||
|
||||
bool byte_buffer::has_more_data() const
|
||||
{
|
||||
return this->buffer_.size() > this->current_byte_;
|
||||
}
|
||||
}
|
69
src/client/game/demonware/byte_buffer.hpp
Normal file
69
src/client/game/demonware/byte_buffer.hpp
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class byte_buffer final
|
||||
{
|
||||
public:
|
||||
byte_buffer() = default;
|
||||
|
||||
explicit byte_buffer(std::string buffer) : buffer_(std::move(buffer))
|
||||
{
|
||||
}
|
||||
|
||||
bool read_byte(unsigned char* output);
|
||||
bool read_bool(bool* output);
|
||||
bool read_int16(short* output);
|
||||
bool read_uint16(unsigned short* output);
|
||||
bool read_int32(int* output);
|
||||
bool read_uint32(unsigned int* output);
|
||||
bool read_int64(__int64* output);
|
||||
bool read_uint64(unsigned __int64* output);
|
||||
bool read_float(float* output);
|
||||
bool read_string(char** output);
|
||||
bool read_string(char* output, int length);
|
||||
bool read_string(std::string* output);
|
||||
bool read_blob(char** output, int* length);
|
||||
bool read_blob(std::string* output);
|
||||
bool read_data_type(char expected);
|
||||
|
||||
bool read_array_header(unsigned char expected, unsigned int* element_count,
|
||||
unsigned int* element_size = nullptr);
|
||||
|
||||
bool write_byte(char data);
|
||||
bool write_bool(bool data);
|
||||
bool write_int16(short data);
|
||||
bool write_uint16(unsigned short data);
|
||||
bool write_int32(int data);
|
||||
bool write_uint32(unsigned int data);
|
||||
bool write_int64(__int64 data);
|
||||
bool write_uint64(unsigned __int64 data);
|
||||
bool write_data_type(char data);
|
||||
bool write_float(float data);
|
||||
bool write_string(const char* data);
|
||||
bool write_string(const std::string& data);
|
||||
bool write_blob(const char* data, int length);
|
||||
bool write_blob(const std::string& data);
|
||||
|
||||
bool write_array_header(unsigned char type, unsigned int element_count, unsigned int element_size);
|
||||
|
||||
bool read(int bytes, void* output);
|
||||
bool write(int bytes, const void* data);
|
||||
bool write(const std::string& data);
|
||||
|
||||
void set_use_data_types(bool use_data_types);
|
||||
size_t size() const;
|
||||
|
||||
bool is_using_data_types() const;
|
||||
|
||||
std::string& get_buffer();
|
||||
std::string get_remaining();
|
||||
|
||||
bool has_more_data() const;
|
||||
|
||||
private:
|
||||
std::string buffer_;
|
||||
size_t current_byte_ = 0;
|
||||
bool use_data_types_ = true;
|
||||
};
|
||||
}
|
144
src/client/game/demonware/data_types.hpp
Normal file
144
src/client/game/demonware/data_types.hpp
Normal file
@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include "byte_buffer.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdTaskResult
|
||||
{
|
||||
public:
|
||||
virtual ~bdTaskResult() = default;
|
||||
|
||||
virtual void serialize(byte_buffer*)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void deserialize(byte_buffer*)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class bdFileData final : public bdTaskResult
|
||||
{
|
||||
public:
|
||||
std::string file_data;
|
||||
|
||||
explicit bdFileData(std::string buffer) : file_data(std::move(buffer))
|
||||
{
|
||||
}
|
||||
|
||||
void serialize(byte_buffer* buffer) override
|
||||
{
|
||||
buffer->write_blob(this->file_data);
|
||||
}
|
||||
|
||||
void deserialize(byte_buffer* buffer) override
|
||||
{
|
||||
buffer->read_blob(&this->file_data);
|
||||
}
|
||||
};
|
||||
|
||||
class bdFileInfo final : public bdTaskResult
|
||||
{
|
||||
public:
|
||||
uint64_t file_id;
|
||||
uint32_t create_time;
|
||||
uint32_t modified_time;
|
||||
bool priv;
|
||||
uint64_t owner_id;
|
||||
std::string filename;
|
||||
uint32_t file_size;
|
||||
|
||||
void serialize(byte_buffer* buffer) override
|
||||
{
|
||||
buffer->write_uint32(this->file_size);
|
||||
buffer->write_uint64(this->file_id);
|
||||
buffer->write_uint32(this->create_time);
|
||||
buffer->write_uint32(this->modified_time);
|
||||
buffer->write_bool(this->priv);
|
||||
buffer->write_uint64(this->owner_id);
|
||||
buffer->write_string(this->filename);
|
||||
}
|
||||
|
||||
void deserialize(byte_buffer* buffer) override
|
||||
{
|
||||
buffer->read_uint32(&this->file_size);
|
||||
buffer->read_uint64(&this->file_id);
|
||||
buffer->read_uint32(&this->create_time);
|
||||
buffer->read_uint32(&this->modified_time);
|
||||
buffer->read_bool(&this->priv);
|
||||
buffer->read_uint64(&this->owner_id);
|
||||
buffer->read_string(&this->filename);
|
||||
}
|
||||
};
|
||||
|
||||
class bdTimeStamp final : public bdTaskResult
|
||||
{
|
||||
public:
|
||||
uint32_t unix_time;
|
||||
|
||||
void serialize(byte_buffer* buffer) override
|
||||
{
|
||||
buffer->write_uint32(this->unix_time);
|
||||
}
|
||||
|
||||
void deserialize(byte_buffer* buffer) override
|
||||
{
|
||||
buffer->read_uint32(&this->unix_time);
|
||||
}
|
||||
};
|
||||
|
||||
class bdDMLInfo : public bdTaskResult
|
||||
{
|
||||
public:
|
||||
std::string country_code; // Char [3]
|
||||
std::string country; // Char [65]
|
||||
std::string region; // Char [65]
|
||||
std::string city; // Char [129]
|
||||
float latitude;
|
||||
float longitude;
|
||||
|
||||
void serialize(byte_buffer* buffer) override
|
||||
{
|
||||
buffer->write_string(this->country_code);
|
||||
buffer->write_string(this->country);
|
||||
buffer->write_string(this->region);
|
||||
buffer->write_string(this->city);
|
||||
buffer->write_float(this->latitude);
|
||||
buffer->write_float(this->longitude);
|
||||
}
|
||||
|
||||
void deserialize(byte_buffer* buffer) override
|
||||
{
|
||||
buffer->read_string(&this->country_code);
|
||||
buffer->read_string(&this->country);
|
||||
buffer->read_string(&this->region);
|
||||
buffer->read_string(&this->city);
|
||||
buffer->read_float(&this->latitude);
|
||||
buffer->read_float(&this->longitude);
|
||||
}
|
||||
};
|
||||
|
||||
class bdDMLRawData final : public bdDMLInfo
|
||||
{
|
||||
public:
|
||||
uint32_t asn; // Autonomous System Number.
|
||||
std::string timezone;
|
||||
|
||||
void serialize(byte_buffer* buffer) override
|
||||
{
|
||||
bdDMLInfo::serialize(buffer);
|
||||
|
||||
buffer->write_uint32(this->asn);
|
||||
buffer->write_string(this->timezone);
|
||||
}
|
||||
|
||||
void deserialize(byte_buffer* buffer) override
|
||||
{
|
||||
bdDMLInfo::deserialize(buffer);
|
||||
|
||||
buffer->read_uint32(&this->asn);
|
||||
buffer->read_string(&this->timezone);
|
||||
}
|
||||
};
|
||||
}
|
130
src/client/game/demonware/keys.cpp
Normal file
130
src/client/game/demonware/keys.cpp
Normal file
@ -0,0 +1,130 @@
|
||||
#include <std_include.hpp>
|
||||
#include "keys.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
struct data_t
|
||||
{
|
||||
char m_session_key[24];
|
||||
char m_response[8];
|
||||
char m_hmac_key[20];
|
||||
char m_enc_key[16];
|
||||
char m_dec_key[16];
|
||||
} data{};
|
||||
|
||||
std::string packet_buffer;
|
||||
|
||||
void calculate_hmacs_s1(const char* data_, const unsigned int data_size, const char* key,
|
||||
const unsigned int key_size,
|
||||
char* dst, const unsigned int dst_size)
|
||||
{
|
||||
char buffer[64];
|
||||
unsigned int pos = 0;
|
||||
unsigned int out_offset = 0;
|
||||
char count = 1;
|
||||
std::string result;
|
||||
|
||||
// buffer add key
|
||||
std::memcpy(&buffer[pos], key, key_size);
|
||||
pos += key_size;
|
||||
|
||||
// buffer add count
|
||||
buffer[pos] = count;
|
||||
pos++;
|
||||
|
||||
// calculate hmac
|
||||
result = utils::cryptography::hmac_sha1::compute(std::string(buffer, pos), std::string(data_, data_size));
|
||||
|
||||
// save output
|
||||
std::memcpy(dst, result.data(), std::min(20u, (dst_size - out_offset)));
|
||||
out_offset = 20;
|
||||
|
||||
// second loop
|
||||
while (true)
|
||||
{
|
||||
// if we filled the output buffer, exit
|
||||
if (out_offset >= dst_size)
|
||||
break;
|
||||
|
||||
// buffer add last result
|
||||
pos = 0;
|
||||
std::memcpy(&buffer[pos], result.data(), 20);
|
||||
pos += 20;
|
||||
|
||||
// buffer add key
|
||||
std::memcpy(&buffer[pos], key, key_size);
|
||||
pos += key_size;
|
||||
|
||||
// buffer add count
|
||||
count++;
|
||||
buffer[pos] = count;
|
||||
pos++;
|
||||
|
||||
// calculate hmac
|
||||
result = utils::cryptography::hmac_sha1::compute(std::string(buffer, pos), std::string(data_, data_size));
|
||||
|
||||
// save output
|
||||
std::memcpy(dst + out_offset, result.data(), std::min(20u, (dst_size - out_offset)));
|
||||
out_offset += 20;
|
||||
}
|
||||
}
|
||||
|
||||
void derive_keys_s1()
|
||||
{
|
||||
const auto out_1 = utils::cryptography::sha1::compute(packet_buffer); // out_1 size 20
|
||||
|
||||
auto data_3 = utils::cryptography::hmac_sha1::compute(data.m_session_key, out_1);
|
||||
|
||||
char out_2[16];
|
||||
calculate_hmacs_s1(data_3.data(), 20, "CLIENTCHAL", 10, out_2, 16);
|
||||
|
||||
char out_3[72];
|
||||
calculate_hmacs_s1(data_3.data(), 20, "BDDATA", 6, out_3, 72);
|
||||
|
||||
std::memcpy(data.m_response, &out_2[8], 8);
|
||||
std::memcpy(data.m_hmac_key, &out_3[20], 20);
|
||||
std::memcpy(data.m_dec_key, &out_3[40], 16);
|
||||
std::memcpy(data.m_enc_key, &out_3[56], 16);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW] Response id: %s\n", utils::string::dump_hex(std::string(&out_2[8], 8)).data());
|
||||
printf("[DW] Hash verify: %s\n", utils::string::dump_hex(std::string(&out_3[20], 20)).data());
|
||||
printf("[DW] AES dec key: %s\n", utils::string::dump_hex(std::string(&out_3[40], 16)).data());
|
||||
printf("[DW] AES enc key: %s\n", utils::string::dump_hex(std::string(&out_3[56], 16)).data());
|
||||
printf("[DW] Bravo 6, going dark.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
void queue_packet_to_hash(const std::string& packet)
|
||||
{
|
||||
packet_buffer.append(packet);
|
||||
}
|
||||
|
||||
void set_session_key(const std::string& key)
|
||||
{
|
||||
std::memcpy(data.m_session_key, key.data(), 24);
|
||||
}
|
||||
|
||||
std::string get_decrypt_key()
|
||||
{
|
||||
return std::string(data.m_dec_key, 16);
|
||||
}
|
||||
|
||||
std::string get_encrypt_key()
|
||||
{
|
||||
return std::string(data.m_enc_key, 16);
|
||||
}
|
||||
|
||||
std::string get_hmac_key()
|
||||
{
|
||||
return std::string(data.m_hmac_key, 20);
|
||||
}
|
||||
|
||||
std::string get_response_id()
|
||||
{
|
||||
return std::string(data.m_response, 8);
|
||||
}
|
||||
}
|
12
src/client/game/demonware/keys.hpp
Normal file
12
src/client/game/demonware/keys.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void derive_keys_s1();
|
||||
void queue_packet_to_hash(const std::string& packet);
|
||||
void set_session_key(const std::string& key);
|
||||
std::string get_decrypt_key();
|
||||
std::string get_encrypt_key();
|
||||
std::string get_hmac_key();
|
||||
std::string get_response_id();
|
||||
}
|
87
src/client/game/demonware/reply.cpp
Normal file
87
src/client/game/demonware/reply.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
#include <std_include.hpp>
|
||||
#include "keys.hpp"
|
||||
#include "reply.hpp"
|
||||
#include "servers/service_server.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
std::string unencrypted_reply::data()
|
||||
{
|
||||
byte_buffer result;
|
||||
result.set_use_data_types(false);
|
||||
|
||||
result.write_int32(static_cast<int>(this->buffer_.size()) + 2);
|
||||
result.write_bool(false);
|
||||
result.write_byte(this->type());
|
||||
result.write(this->buffer_);
|
||||
|
||||
return result.get_buffer();
|
||||
}
|
||||
|
||||
std::string encrypted_reply::data()
|
||||
{
|
||||
byte_buffer result;
|
||||
result.set_use_data_types(false);
|
||||
|
||||
byte_buffer enc_buffer;
|
||||
enc_buffer.set_use_data_types(false);
|
||||
|
||||
enc_buffer.write_uint32(static_cast<unsigned int>(this->buffer_.size())); // service data size CHECKTHIS!!
|
||||
enc_buffer.write_byte(this->type()); // TASK_REPLY type
|
||||
enc_buffer.write(this->buffer_); // service data
|
||||
|
||||
auto aligned_data = enc_buffer.get_buffer();
|
||||
auto size = aligned_data.size();
|
||||
size = ~15 & (size + 15); // 16 byte align
|
||||
aligned_data.resize(size);
|
||||
|
||||
// seed
|
||||
std::string seed("\x5E\xED\x5E\xED\x5E\xED\x5E\xED\x5E\xED\x5E\xED\x5E\xED\x5E\xED", 16);
|
||||
|
||||
// encrypt
|
||||
const auto enc_data = utils::cryptography::aes::encrypt(aligned_data, seed, demonware::get_encrypt_key());
|
||||
|
||||
// header : encrypted service data : hash
|
||||
static auto msg_count = 0;
|
||||
msg_count++;
|
||||
|
||||
byte_buffer response;
|
||||
response.set_use_data_types(false);
|
||||
|
||||
response.write_int32(30 + static_cast<int>(enc_data.size()));
|
||||
response.write_byte(static_cast<char>(0xAB));
|
||||
response.write_byte(static_cast<char>(0x85));
|
||||
response.write_int32(msg_count);
|
||||
response.write(16, seed.data());
|
||||
response.write(enc_data);
|
||||
|
||||
// hash entire packet and append end
|
||||
auto hash_data = utils::cryptography::hmac_sha1::compute(response.get_buffer(), demonware::get_hmac_key());
|
||||
hash_data.resize(8);
|
||||
response.write(8, hash_data.data());
|
||||
|
||||
return response.get_buffer();
|
||||
}
|
||||
|
||||
void remote_reply::send(bit_buffer* buffer, const bool encrypted)
|
||||
{
|
||||
std::unique_ptr<typed_reply> reply;
|
||||
|
||||
if (encrypted) reply = std::make_unique<encrypted_reply>(this->type_, buffer);
|
||||
else reply = std::make_unique<unencrypted_reply>(this->type_, buffer);
|
||||
|
||||
this->server_->send_reply(reply.get());
|
||||
}
|
||||
|
||||
void remote_reply::send(byte_buffer* buffer, const bool encrypted)
|
||||
{
|
||||
std::unique_ptr<typed_reply> reply;
|
||||
|
||||
if (encrypted) reply = std::make_unique<encrypted_reply>(this->type_, buffer);
|
||||
else reply = std::make_unique<unencrypted_reply>(this->type_, buffer);
|
||||
|
||||
this->server_->send_reply(reply.get());
|
||||
}
|
||||
}
|
164
src/client/game/demonware/reply.hpp
Normal file
164
src/client/game/demonware/reply.hpp
Normal file
@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
|
||||
#include "bit_buffer.hpp"
|
||||
#include "byte_buffer.hpp"
|
||||
#include "data_types.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class reply
|
||||
{
|
||||
public:
|
||||
reply() = default;
|
||||
|
||||
reply(reply&&) = delete;
|
||||
reply(const reply&) = delete;
|
||||
reply& operator=(reply&&) = delete;
|
||||
reply& operator=(const reply&) = delete;
|
||||
|
||||
virtual ~reply() = default;
|
||||
virtual std::string data() = 0;
|
||||
};
|
||||
|
||||
class raw_reply : public reply
|
||||
{
|
||||
protected:
|
||||
std::string buffer_;
|
||||
|
||||
public:
|
||||
raw_reply() = default;
|
||||
|
||||
explicit raw_reply(std::string data) : buffer_(std::move(data))
|
||||
{
|
||||
}
|
||||
|
||||
std::string data() override
|
||||
{
|
||||
return this->buffer_;
|
||||
}
|
||||
};
|
||||
|
||||
class typed_reply : public raw_reply
|
||||
{
|
||||
public:
|
||||
typed_reply(const uint8_t _type) : type_(_type)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t type() const { return this->type_; }
|
||||
|
||||
private:
|
||||
uint8_t type_;
|
||||
};
|
||||
|
||||
class encrypted_reply final : public typed_reply
|
||||
{
|
||||
public:
|
||||
encrypted_reply(const uint8_t type, bit_buffer* bbuffer) : typed_reply(type)
|
||||
{
|
||||
this->buffer_.append(bbuffer->get_buffer());
|
||||
}
|
||||
|
||||
encrypted_reply(const uint8_t type, byte_buffer* bbuffer) : typed_reply(type)
|
||||
{
|
||||
this->buffer_.append(bbuffer->get_buffer());
|
||||
}
|
||||
|
||||
std::string data() override;
|
||||
};
|
||||
|
||||
class unencrypted_reply final : public typed_reply
|
||||
{
|
||||
public:
|
||||
unencrypted_reply(const uint8_t _type, bit_buffer* bbuffer) : typed_reply(_type)
|
||||
{
|
||||
this->buffer_.append(bbuffer->get_buffer());
|
||||
}
|
||||
|
||||
unencrypted_reply(const uint8_t _type, byte_buffer* bbuffer) : typed_reply(_type)
|
||||
{
|
||||
this->buffer_.append(bbuffer->get_buffer());
|
||||
}
|
||||
|
||||
std::string data() override;
|
||||
};
|
||||
|
||||
class service_server;
|
||||
|
||||
class remote_reply final
|
||||
{
|
||||
public:
|
||||
remote_reply(service_server* server, uint8_t _type) : type_(_type), server_(server)
|
||||
{
|
||||
}
|
||||
|
||||
void send(bit_buffer* buffer, bool encrypted);
|
||||
void send(byte_buffer* buffer, bool encrypted);
|
||||
|
||||
uint8_t type() const { return this->type_; }
|
||||
|
||||
private:
|
||||
uint8_t type_;
|
||||
service_server* server_;
|
||||
};
|
||||
|
||||
class service_reply final
|
||||
{
|
||||
public:
|
||||
service_reply(service_server* _server, const uint8_t _type, const uint32_t _error)
|
||||
: type_(_type), error_(_error), reply_(_server, 1)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t send()
|
||||
{
|
||||
static uint64_t id = 0x0000000000000000;
|
||||
const auto transaction_id = ++id;
|
||||
|
||||
byte_buffer buffer;
|
||||
buffer.write_uint64(transaction_id);
|
||||
buffer.write_uint32(this->error_);
|
||||
buffer.write_byte(this->type_);
|
||||
|
||||
if (!this->error_)
|
||||
{
|
||||
buffer.write_uint32(uint32_t(this->objects_.size()));
|
||||
if (!this->objects_.empty())
|
||||
{
|
||||
buffer.write_uint32(uint32_t(this->objects_.size()));
|
||||
|
||||
for (auto& object : this->objects_)
|
||||
{
|
||||
object->serialize(&buffer);
|
||||
}
|
||||
|
||||
this->objects_.clear();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer.write_uint64(transaction_id);
|
||||
}
|
||||
|
||||
this->reply_.send(&buffer, true);
|
||||
return transaction_id;
|
||||
}
|
||||
|
||||
void add(const std::shared_ptr<bdTaskResult>& object)
|
||||
{
|
||||
this->objects_.push_back(object);
|
||||
}
|
||||
|
||||
void add(bdTaskResult* object)
|
||||
{
|
||||
this->add(std::shared_ptr<bdTaskResult>(object));
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t type_;
|
||||
uint32_t error_;
|
||||
remote_reply reply_;
|
||||
std::vector<std::shared_ptr<bdTaskResult>> objects_;
|
||||
};
|
||||
}
|
60
src/client/game/demonware/server_registry.hpp
Normal file
60
src/client/game/demonware/server_registry.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "servers/base_server.hpp"
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
template <typename T>
|
||||
class server_registry
|
||||
{
|
||||
static_assert(std::is_base_of<base_server, T>::value, "Invalid server registry type");
|
||||
|
||||
public:
|
||||
template <typename S, typename ...Args>
|
||||
void create(Args&&... args)
|
||||
{
|
||||
static_assert(std::is_base_of<T, S>::value, "Invalid server type");
|
||||
|
||||
auto server = std::make_unique<S>(std::forward<Args>(args)...);
|
||||
const auto address = server->get_address();
|
||||
servers_[address] = std::move(server);
|
||||
}
|
||||
|
||||
void for_each(const std::function<void(T&)>& callback) const
|
||||
{
|
||||
for (auto& server : servers_)
|
||||
{
|
||||
callback(*server.second);
|
||||
}
|
||||
}
|
||||
|
||||
T* find(const std::string& name)
|
||||
{
|
||||
const auto address = utils::cryptography::jenkins_one_at_a_time::compute(name);
|
||||
return find(address);
|
||||
}
|
||||
|
||||
T* find(const uint32_t address)
|
||||
{
|
||||
const auto it = servers_.find(address);
|
||||
if (it == servers_.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void frame()
|
||||
{
|
||||
for (auto& server : servers_)
|
||||
{
|
||||
server.second->frame();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<uint32_t, std::unique_ptr<T>> servers_;
|
||||
};
|
||||
}
|
162
src/client/game/demonware/servers/auth3_server.cpp
Normal file
162
src/client/game/demonware/servers/auth3_server.cpp
Normal file
@ -0,0 +1,162 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "auth3_server.hpp"
|
||||
#include "../keys.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
namespace
|
||||
{
|
||||
#pragma pack(push, 1)
|
||||
struct auth_ticket
|
||||
{
|
||||
unsigned int m_magicNumber;
|
||||
char m_type;
|
||||
unsigned int m_titleID;
|
||||
unsigned int m_timeIssued;
|
||||
unsigned int m_timeExpires;
|
||||
unsigned __int64 m_licenseID;
|
||||
unsigned __int64 m_userID;
|
||||
char m_username[64];
|
||||
char m_sessionKey[24];
|
||||
char m_usingHashMagicNumber[3];
|
||||
char m_hash[4];
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
void auth3_server::send_reply(reply* data)
|
||||
{
|
||||
if (!data) return;
|
||||
this->send(data->data());
|
||||
}
|
||||
|
||||
void auth3_server::handle(const std::string& packet)
|
||||
{
|
||||
if (packet.starts_with("POST /auth/"))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [auth]: user requested authentication.\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int title_id = 0;
|
||||
unsigned int iv_seed = 0;
|
||||
std::string identity{};
|
||||
std::string token{};
|
||||
|
||||
rapidjson::Document j;
|
||||
j.Parse(packet.data(), packet.size());
|
||||
|
||||
if (j.HasMember("title_id") && j["title_id"].IsString())
|
||||
{
|
||||
title_id = std::stoul(j["title_id"].GetString());
|
||||
}
|
||||
|
||||
if (j.HasMember("iv_seed") && j["iv_seed"].IsString())
|
||||
{
|
||||
iv_seed = std::stoul(j["iv_seed"].GetString());
|
||||
}
|
||||
|
||||
if (j.HasMember("extra_data") && j["extra_data"].IsString())
|
||||
{
|
||||
rapidjson::Document extra_data;
|
||||
auto& ed = j["extra_data"];
|
||||
extra_data.Parse(ed.GetString(), ed.GetStringLength());
|
||||
|
||||
if (extra_data.HasMember("token") && extra_data["token"].IsString())
|
||||
{
|
||||
auto& token_field = extra_data["token"];
|
||||
std::string token_b64(token_field.GetString(), token_field.GetStringLength());
|
||||
token = utils::cryptography::base64::decode(token_b64);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [auth]: authenticating user %s\n", token.data() + 64);
|
||||
#endif
|
||||
|
||||
std::string auth_key(reinterpret_cast<char*>(token.data() + 32), 24);
|
||||
std::string session_key(
|
||||
"\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37", 24);
|
||||
|
||||
// client_ticket
|
||||
auth_ticket ticket{};
|
||||
std::memset(&ticket, 0x0, sizeof ticket);
|
||||
ticket.m_magicNumber = 0x0EFBDADDE;
|
||||
ticket.m_type = 0;
|
||||
ticket.m_titleID = title_id;
|
||||
ticket.m_timeIssued = static_cast<uint32_t>(time(nullptr));
|
||||
ticket.m_timeExpires = ticket.m_timeIssued + 30000;
|
||||
ticket.m_licenseID = 0;
|
||||
ticket.m_userID = reinterpret_cast<uint64_t>(token.data() + 56);
|
||||
strncpy_s(ticket.m_username, sizeof(ticket.m_username), reinterpret_cast<char*>(token.data() + 64), 64);
|
||||
std::memcpy(ticket.m_sessionKey, session_key.data(), 24);
|
||||
|
||||
const auto iv = utils::cryptography::tiger::compute(std::string(reinterpret_cast<char*>(&iv_seed), 4));
|
||||
const auto ticket_enc = utils::cryptography::des3::encrypt(
|
||||
std::string(reinterpret_cast<char*>(&ticket), sizeof(ticket)), iv, auth_key);
|
||||
const auto ticket_b64 = utils::cryptography::base64::encode(
|
||||
reinterpret_cast<const unsigned char*>(ticket_enc.data()), 128);
|
||||
|
||||
// server_ticket
|
||||
uint8_t auth_data[128];
|
||||
std::memset(&auth_data, 0, sizeof auth_data);
|
||||
std::memcpy(auth_data, session_key.data(), 24);
|
||||
const auto auth_data_b64 = utils::cryptography::base64::encode(auth_data, 128);
|
||||
|
||||
demonware::set_session_key(session_key);
|
||||
|
||||
// header time
|
||||
char date[64];
|
||||
const auto now = time(nullptr);
|
||||
tm gmtm{};
|
||||
gmtime_s(&gmtm, &now);
|
||||
strftime(date, 64, "%a, %d %b %G %T", &gmtm);
|
||||
|
||||
// json content
|
||||
rapidjson::Document doc;
|
||||
doc.SetObject();
|
||||
|
||||
doc.AddMember("auth_task", "29", doc.GetAllocator());
|
||||
doc.AddMember("code", "700", doc.GetAllocator());
|
||||
|
||||
auto seed = std::to_string(iv_seed);
|
||||
doc.AddMember("iv_seed", rapidjson::StringRef(seed.data(), seed.size()), doc.GetAllocator());
|
||||
doc.AddMember("client_ticket", rapidjson::StringRef(ticket_b64.data(), ticket_b64.size()), doc.GetAllocator());
|
||||
doc.AddMember("server_ticket", rapidjson::StringRef(auth_data_b64.data(), auth_data_b64.size()),
|
||||
doc.GetAllocator());
|
||||
doc.AddMember("client_id", "", doc.GetAllocator());
|
||||
doc.AddMember("account_type", "steam", doc.GetAllocator());
|
||||
doc.AddMember("crossplay_enabled", false, doc.GetAllocator());
|
||||
doc.AddMember("loginqueue_eanbled", false, doc.GetAllocator());
|
||||
|
||||
rapidjson::Value value{};
|
||||
doc.AddMember("lsg_endpoint", value, doc.GetAllocator());
|
||||
|
||||
rapidjson::StringBuffer buffer{};
|
||||
rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::ASCII<>>
|
||||
writer(buffer);
|
||||
doc.Accept(writer);
|
||||
|
||||
// http stuff
|
||||
std::string result;
|
||||
result.append("HTTP/1.1 200 OK\r\n");
|
||||
result.append("Server: TornadoServer/4.5.3\r\n");
|
||||
result.append("Content-Type: application/json\r\n");
|
||||
result.append(utils::string::va("Date: %s GMT\r\n", date));
|
||||
result.append(utils::string::va("Content-Length: %d\r\n\r\n", buffer.GetLength()));
|
||||
result.append(buffer.GetString(), buffer.GetLength());
|
||||
|
||||
raw_reply reply(result);
|
||||
this->send_reply(&reply);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [auth]: user successfully authenticated.\n");
|
||||
#endif
|
||||
}
|
||||
}
|
16
src/client/game/demonware/servers/auth3_server.hpp
Normal file
16
src/client/game/demonware/servers/auth3_server.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "tcp_server.hpp"
|
||||
#include "../reply.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class auth3_server : public tcp_server
|
||||
{
|
||||
public:
|
||||
using tcp_server::tcp_server;
|
||||
|
||||
private:
|
||||
void send_reply(reply* data);
|
||||
void handle(const std::string& packet) override;
|
||||
};
|
||||
}
|
22
src/client/game/demonware/servers/base_server.cpp
Normal file
22
src/client/game/demonware/servers/base_server.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include <std_include.hpp>
|
||||
#include "base_server.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
base_server::base_server(std::string name): name_(std::move(name))
|
||||
{
|
||||
this->address_ = utils::cryptography::jenkins_one_at_a_time::compute(this->name_);
|
||||
}
|
||||
|
||||
const std::string& base_server::get_name() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
uint32_t base_server::get_address() const
|
||||
{
|
||||
return this->address_;
|
||||
}
|
||||
}
|
30
src/client/game/demonware/servers/base_server.hpp
Normal file
30
src/client/game/demonware/servers/base_server.hpp
Normal file
@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class base_server
|
||||
{
|
||||
public:
|
||||
using stream_queue = std::queue<char>;
|
||||
using data_queue = std::queue<std::string>;
|
||||
|
||||
base_server(std::string name);
|
||||
|
||||
base_server(base_server&&) = delete;
|
||||
base_server(const base_server&) = delete;
|
||||
base_server& operator=(base_server&&) = delete;
|
||||
base_server& operator=(const base_server&) = delete;
|
||||
|
||||
virtual ~base_server() = default;
|
||||
|
||||
const std::string& get_name() const;
|
||||
|
||||
uint32_t get_address() const;
|
||||
|
||||
virtual void frame() = 0;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::uint32_t address_ = 0;
|
||||
};
|
||||
}
|
177
src/client/game/demonware/servers/lobby_server.cpp
Normal file
177
src/client/game/demonware/servers/lobby_server.cpp
Normal file
@ -0,0 +1,177 @@
|
||||
#include <std_include.hpp>
|
||||
#include "lobby_server.hpp"
|
||||
|
||||
#include "../services.hpp"
|
||||
#include "../keys.hpp"
|
||||
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
lobby_server::lobby_server(std::string name) : tcp_server(std::move(name))
|
||||
{
|
||||
this->register_service<bdAnticheat>();
|
||||
this->register_service<bdBandwidthTest>();
|
||||
this->register_service<bdContentStreaming>();
|
||||
this->register_service<bdDML>();
|
||||
this->register_service<bdEventLog>();
|
||||
this->register_service<bdGroups>();
|
||||
this->register_service<bdStats>();
|
||||
this->register_service<bdStorage>();
|
||||
this->register_service<bdTitleUtilities>();
|
||||
this->register_service<bdProfiles>();
|
||||
this->register_service<bdRichPresence>();
|
||||
this->register_service<bdFacebook>();
|
||||
this->register_service<bdUNK63>();
|
||||
this->register_service<bdUNK80>();
|
||||
this->register_service<bdPresence>();
|
||||
this->register_service<bdMarketingComms>();
|
||||
this->register_service<bdMatchMaking2>();
|
||||
this->register_service<bdMarketing>();
|
||||
};
|
||||
|
||||
void lobby_server::send_reply(reply* data)
|
||||
{
|
||||
if (!data) return;
|
||||
this->send(data->data());
|
||||
}
|
||||
|
||||
void lobby_server::handle(const std::string& packet)
|
||||
{
|
||||
byte_buffer buffer(packet);
|
||||
buffer.set_use_data_types(false);
|
||||
|
||||
try
|
||||
{
|
||||
while (buffer.has_more_data())
|
||||
{
|
||||
int size;
|
||||
buffer.read_int32(&size);
|
||||
|
||||
if (size <= 0)
|
||||
{
|
||||
const std::string zero("\x00\x00\x00\x00", 4);
|
||||
raw_reply reply(zero);
|
||||
this->send_reply(&reply);
|
||||
return;
|
||||
}
|
||||
else if (size == 0xC8)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [lobby]: received client_header_ack.\n");
|
||||
#endif
|
||||
|
||||
int c8;
|
||||
buffer.read_int32(&c8);
|
||||
std::string packet_1 = buffer.get_remaining();
|
||||
demonware::queue_packet_to_hash(packet_1);
|
||||
|
||||
const std::string packet_2(
|
||||
"\x16\x00\x00\x00\xab\x81\xd2\x00\x00\x00\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37\x13\x37",
|
||||
26);
|
||||
demonware::queue_packet_to_hash(packet_2);
|
||||
|
||||
raw_reply reply(packet_2);
|
||||
this->send_reply(&reply);
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [lobby]: sending server_header_ack.\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.size() < size_t(size)) return;
|
||||
|
||||
uint8_t check_ab;
|
||||
buffer.read_byte(&check_ab);
|
||||
if (check_ab == 0xAB)
|
||||
{
|
||||
uint8_t type;
|
||||
buffer.read_byte(&type);
|
||||
|
||||
if (type == 0x82)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [lobby]: received client_auth.\n");
|
||||
#endif
|
||||
std::string packet_3(packet.data(), packet.size() - 8); // this 8 are client hash check?
|
||||
|
||||
demonware::queue_packet_to_hash(packet_3);
|
||||
demonware::derive_keys_s1();
|
||||
|
||||
char buff[14] = "\x0A\x00\x00\x00\xAB\x83";
|
||||
std::memcpy(&buff[6], demonware::get_response_id().data(), 8);
|
||||
std::string response(buff, 14);
|
||||
|
||||
raw_reply reply(response);
|
||||
this->send_reply(&reply);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [lobby]: sending server_auth_done.\n");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
else if (type == 0x85)
|
||||
{
|
||||
uint32_t msg_count;
|
||||
buffer.read_uint32(&msg_count);
|
||||
|
||||
char seed[16];
|
||||
buffer.read(16, &seed);
|
||||
|
||||
std::string enc = buffer.get_remaining();
|
||||
|
||||
char hash[8];
|
||||
std::memcpy(hash, &(enc.data()[enc.size() - 8]), 8);
|
||||
|
||||
std::string dec = utils::cryptography::aes::decrypt(
|
||||
std::string(enc.data(), enc.size() - 8), std::string(seed, 16),
|
||||
demonware::get_decrypt_key());
|
||||
|
||||
byte_buffer serv(dec);
|
||||
serv.set_use_data_types(false);
|
||||
|
||||
uint32_t serv_size;
|
||||
serv.read_uint32(&serv_size);
|
||||
|
||||
uint8_t magic; // 0x86
|
||||
serv.read_byte(&magic);
|
||||
|
||||
uint8_t service_id;
|
||||
serv.read_byte(&service_id);
|
||||
|
||||
this->call_service(service_id, serv.get_remaining());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
printf("[DW]: [lobby]: ERROR! received unk message.\n");
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void lobby_server::call_service(const uint8_t id, const std::string& data)
|
||||
{
|
||||
const auto& it = this->services_.find(id);
|
||||
|
||||
if (it != this->services_.end())
|
||||
{
|
||||
it->second->exec_task(this, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("[DW]: [lobby]: missing service '%s'\n", utils::string::va("%d", id));
|
||||
|
||||
// return no error
|
||||
byte_buffer buffer(data);
|
||||
uint8_t task_id;
|
||||
buffer.read_byte(&task_id);
|
||||
|
||||
this->create_reply(task_id)->send();
|
||||
}
|
||||
}
|
||||
}
|
33
src/client/game/demonware/servers/lobby_server.hpp
Normal file
33
src/client/game/demonware/servers/lobby_server.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "tcp_server.hpp"
|
||||
#include "service_server.hpp"
|
||||
#include "../service.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class lobby_server : public tcp_server, service_server
|
||||
{
|
||||
public:
|
||||
lobby_server(std::string name);
|
||||
|
||||
template <typename T>
|
||||
void register_service()
|
||||
{
|
||||
static_assert(std::is_base_of<service, T>::value, "service must inherit from service");
|
||||
|
||||
auto service = std::make_unique<T>();
|
||||
const uint8_t id = service->id();
|
||||
|
||||
this->services_[id] = std::move(service);
|
||||
}
|
||||
|
||||
void send_reply(reply* data) override;
|
||||
|
||||
private:
|
||||
std::unordered_map<uint8_t, std::unique_ptr<service>> services_;
|
||||
|
||||
void handle(const std::string& packet) override;
|
||||
void call_service(uint8_t id, const std::string& data);
|
||||
};
|
||||
}
|
27
src/client/game/demonware/servers/service_server.hpp
Normal file
27
src/client/game/demonware/servers/service_server.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "../reply.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class service_server
|
||||
{
|
||||
public:
|
||||
virtual ~service_server() = default;
|
||||
|
||||
virtual std::shared_ptr<remote_reply> create_message(uint8_t type)
|
||||
{
|
||||
auto reply = std::make_shared<remote_reply>(this, type);
|
||||
return reply;
|
||||
}
|
||||
|
||||
|
||||
virtual std::shared_ptr<service_reply> create_reply(uint8_t type, uint32_t error = 0)
|
||||
{
|
||||
auto reply = std::make_shared<service_reply>(this, type, error);
|
||||
return reply;
|
||||
}
|
||||
|
||||
virtual void send_reply(reply* data) = 0;
|
||||
};
|
||||
}
|
62
src/client/game/demonware/servers/stun_server.cpp
Normal file
62
src/client/game/demonware/servers/stun_server.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include <std_include.hpp>
|
||||
#include "stun_server.hpp"
|
||||
|
||||
#include "../byte_buffer.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void stun_server::handle(const endpoint_data& endpoint, const std::string& packet)
|
||||
{
|
||||
uint8_t type, version, padding;
|
||||
|
||||
byte_buffer buffer(packet);
|
||||
buffer.set_use_data_types(false);
|
||||
buffer.read_byte(&type);
|
||||
buffer.read_byte(&version);
|
||||
buffer.read_byte(&padding);
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case 30:
|
||||
this->ip_discovery(endpoint);
|
||||
break;
|
||||
case 20:
|
||||
this->nat_discovery(endpoint);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void stun_server::ip_discovery(const endpoint_data& endpoint)
|
||||
{
|
||||
const uint32_t ip = 0x0100007f;
|
||||
|
||||
byte_buffer buffer;
|
||||
buffer.set_use_data_types(false);
|
||||
buffer.write_byte(31); // type
|
||||
buffer.write_byte(2); // version
|
||||
buffer.write_byte(0); // version
|
||||
buffer.write_uint32(ip); // external ip
|
||||
buffer.write_uint16(3074); // port
|
||||
|
||||
this->send(endpoint, buffer.get_buffer());
|
||||
}
|
||||
|
||||
void stun_server::nat_discovery(const endpoint_data& endpoint)
|
||||
{
|
||||
const uint32_t ip = 0x0100007f;
|
||||
|
||||
byte_buffer buffer;
|
||||
buffer.set_use_data_types(false);
|
||||
buffer.write_byte(21); // type
|
||||
buffer.write_byte(2); // version
|
||||
buffer.write_byte(0); // version
|
||||
buffer.write_uint32(ip); // external ip
|
||||
buffer.write_uint16(3074); // port
|
||||
buffer.write_uint32(this->get_address()); // server ip
|
||||
buffer.write_uint16(3074); // server port
|
||||
|
||||
this->send(endpoint, buffer.get_buffer());
|
||||
}
|
||||
}
|
18
src/client/game/demonware/servers/stun_server.hpp
Normal file
18
src/client/game/demonware/servers/stun_server.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "udp_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class stun_server : public udp_server
|
||||
{
|
||||
public:
|
||||
using udp_server::udp_server;
|
||||
|
||||
private:
|
||||
void handle(const endpoint_data& endpoint, const std::string& packet) override;
|
||||
|
||||
void ip_discovery(const endpoint_data& endpoint);
|
||||
void nat_discovery(const endpoint_data& endpoint);
|
||||
};
|
||||
}
|
84
src/client/game/demonware/servers/tcp_server.cpp
Normal file
84
src/client/game/demonware/servers/tcp_server.cpp
Normal file
@ -0,0 +1,84 @@
|
||||
#include <std_include.hpp>
|
||||
#include "tcp_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void tcp_server::handle_input(const char* buf, size_t size)
|
||||
{
|
||||
in_queue_.access([&](data_queue& queue)
|
||||
{
|
||||
queue.emplace(buf, size);
|
||||
});
|
||||
}
|
||||
|
||||
size_t tcp_server::handle_output(char* buf, size_t size)
|
||||
{
|
||||
if (out_queue_.get_raw().empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return out_queue_.access<size_t>([&](stream_queue& queue)
|
||||
{
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
if (queue.empty())
|
||||
{
|
||||
return i;
|
||||
}
|
||||
|
||||
buf[i] = queue.front();
|
||||
queue.pop();
|
||||
}
|
||||
|
||||
return size;
|
||||
});
|
||||
}
|
||||
|
||||
bool tcp_server::pending_data()
|
||||
{
|
||||
return !this->out_queue_.get_raw().empty();
|
||||
}
|
||||
|
||||
void tcp_server::frame()
|
||||
{
|
||||
if (this->in_queue_.get_raw().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
std::string packet{};
|
||||
const auto result = this->in_queue_.access<bool>([&](data_queue& queue)
|
||||
{
|
||||
if (queue.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
packet = std::move(queue.front());
|
||||
queue.pop();
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!result)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
this->handle(packet);
|
||||
}
|
||||
}
|
||||
|
||||
void tcp_server::send(const std::string& data)
|
||||
{
|
||||
out_queue_.access([&](stream_queue& queue)
|
||||
{
|
||||
for (const auto& val : data)
|
||||
{
|
||||
queue.push(val);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
27
src/client/game/demonware/servers/tcp_server.hpp
Normal file
27
src/client/game/demonware/servers/tcp_server.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "base_server.hpp"
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class tcp_server : public base_server
|
||||
{
|
||||
public:
|
||||
using base_server::base_server;
|
||||
|
||||
void handle_input(const char* buf, size_t size);
|
||||
size_t handle_output(char* buf, size_t size);
|
||||
bool pending_data();
|
||||
void frame() override;
|
||||
|
||||
protected:
|
||||
virtual void handle(const std::string& data) = 0;
|
||||
|
||||
void send(const std::string& data);
|
||||
|
||||
private:
|
||||
utils::concurrency::container<data_queue> in_queue_;
|
||||
utils::concurrency::container<stream_queue> out_queue_;
|
||||
};
|
||||
}
|
103
src/client/game/demonware/servers/udp_server.cpp
Normal file
103
src/client/game/demonware/servers/udp_server.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include <std_include.hpp>
|
||||
#include "udp_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void udp_server::handle_input(const char* buf, size_t size, endpoint_data endpoint)
|
||||
{
|
||||
in_queue_.access([&](in_queue& queue)
|
||||
{
|
||||
in_packet p;
|
||||
p.data = std::string{buf, size};
|
||||
p.endpoint = std::move(endpoint);
|
||||
|
||||
queue.emplace(std::move(p));
|
||||
});
|
||||
}
|
||||
|
||||
size_t udp_server::handle_output(SOCKET socket, char* buf, size_t size, sockaddr* address, int* addrlen)
|
||||
{
|
||||
return out_queue_.access<size_t>([&](socket_queue_map& map) -> size_t
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto& queue = entry->second;
|
||||
|
||||
if (queue.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto data = std::move(queue.front());
|
||||
queue.pop();
|
||||
|
||||
const auto copy_size = std::min(size, data.data.size());
|
||||
std::memcpy(buf, data.data.data(), copy_size);
|
||||
std::memcpy(address, &data.address, sizeof(data.address));
|
||||
*addrlen = sizeof(data.address);
|
||||
|
||||
return copy_size;
|
||||
});
|
||||
}
|
||||
|
||||
bool udp_server::pending_data(SOCKET socket)
|
||||
{
|
||||
return this->out_queue_.access<bool>([&](const socket_queue_map& map)
|
||||
{
|
||||
const auto entry = map.find(socket);
|
||||
if (entry == map.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return !entry->second.empty();
|
||||
});
|
||||
}
|
||||
|
||||
void udp_server::send(const endpoint_data& endpoint, std::string data)
|
||||
{
|
||||
out_queue_.access([&](socket_queue_map& map)
|
||||
{
|
||||
out_packet p;
|
||||
p.data = std::move(data);
|
||||
p.address = endpoint.address;
|
||||
|
||||
map[endpoint.socket].emplace(std::move(p));
|
||||
});
|
||||
}
|
||||
|
||||
void udp_server::frame()
|
||||
{
|
||||
if (this->in_queue_.get_raw().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
in_packet packet{};
|
||||
const auto result = this->in_queue_.access<bool>([&](in_queue& queue)
|
||||
{
|
||||
if (queue.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
packet = std::move(queue.front());
|
||||
queue.pop();
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!result)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
this->handle(packet.endpoint, std::move(packet.data));
|
||||
}
|
||||
}
|
||||
}
|
62
src/client/game/demonware/servers/udp_server.hpp
Normal file
62
src/client/game/demonware/servers/udp_server.hpp
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "base_server.hpp"
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class udp_server : public base_server
|
||||
{
|
||||
public:
|
||||
struct endpoint_data
|
||||
{
|
||||
SOCKET socket{};
|
||||
sockaddr_in address{};
|
||||
|
||||
endpoint_data() = default;
|
||||
|
||||
endpoint_data(const SOCKET sock, const sockaddr* addr, const int size)
|
||||
{
|
||||
if (size != sizeof(this->address))
|
||||
{
|
||||
throw std::runtime_error("Invalid size");
|
||||
}
|
||||
|
||||
this->socket = sock;
|
||||
std::memcpy(&this->address, addr, sizeof(this->address));
|
||||
}
|
||||
};
|
||||
|
||||
using base_server::base_server;
|
||||
|
||||
void handle_input(const char* buf, size_t size, endpoint_data endpoint);
|
||||
size_t handle_output(SOCKET socket, char* buf, size_t size, sockaddr* address, int* addrlen);
|
||||
bool pending_data(SOCKET socket);
|
||||
|
||||
void frame() override;
|
||||
|
||||
protected:
|
||||
virtual void handle(const endpoint_data& endpoint, const std::string& data) = 0;
|
||||
void send(const endpoint_data& endpoint, std::string data);
|
||||
|
||||
private:
|
||||
struct in_packet
|
||||
{
|
||||
std::string data;
|
||||
endpoint_data endpoint;
|
||||
};
|
||||
|
||||
struct out_packet
|
||||
{
|
||||
std::string data;
|
||||
sockaddr_in address;
|
||||
};
|
||||
|
||||
using in_queue = std::queue<in_packet>;
|
||||
using out_queue = std::queue<out_packet>;
|
||||
using socket_queue_map = std::unordered_map<SOCKET, out_queue>;
|
||||
|
||||
utils::concurrency::container<in_queue> in_queue_;
|
||||
utils::concurrency::container<socket_queue_map> out_queue_;
|
||||
};
|
||||
}
|
11
src/client/game/demonware/servers/umbrella_server.cpp
Normal file
11
src/client/game/demonware/servers/umbrella_server.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include <std_include.hpp>
|
||||
|
||||
#include "umbrella_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
void umbrella_server::handle(const std::string& packet)
|
||||
{
|
||||
// TODO:
|
||||
}
|
||||
}
|
14
src/client/game/demonware/servers/umbrella_server.hpp
Normal file
14
src/client/game/demonware/servers/umbrella_server.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include "tcp_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class umbrella_server : public tcp_server
|
||||
{
|
||||
public:
|
||||
using tcp_server::tcp_server;
|
||||
|
||||
private:
|
||||
void handle(const std::string& packet) override;
|
||||
};
|
||||
}
|
89
src/client/game/demonware/service.hpp
Normal file
89
src/client/game/demonware/service.hpp
Normal file
@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
#include <utils/string.hpp>
|
||||
#include "servers/service_server.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class service
|
||||
{
|
||||
using callback_t = std::function<void(service_server*, byte_buffer*)>;
|
||||
|
||||
uint8_t id_;
|
||||
std::string name_;
|
||||
std::mutex mutex_;
|
||||
uint8_t task_id_;
|
||||
std::map<uint8_t, callback_t> tasks_;
|
||||
|
||||
public:
|
||||
virtual ~service() = default;
|
||||
service(service&&) = delete;
|
||||
service(const service&) = delete;
|
||||
service& operator=(const service&) = delete;
|
||||
|
||||
service(const uint8_t id, std::string name) : id_(id), name_(std::move(name)), task_id_(0)
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t id() const
|
||||
{
|
||||
return this->id_;
|
||||
}
|
||||
|
||||
const std::string& name() const
|
||||
{
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
uint8_t task_id() const
|
||||
{
|
||||
return this->task_id_;
|
||||
}
|
||||
|
||||
virtual void exec_task(service_server* server, const std::string& data)
|
||||
{
|
||||
std::lock_guard<std::mutex> _(this->mutex_);
|
||||
|
||||
byte_buffer buffer(data);
|
||||
|
||||
buffer.read_byte(&this->task_id_);
|
||||
|
||||
const auto& it = this->tasks_.find(this->task_id_);
|
||||
|
||||
if (it != this->tasks_.end())
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[DW] %s: executing task '%d'\n", name_.data(), this->task_id_);
|
||||
#endif
|
||||
|
||||
it->second(server, &buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("[DW] %s: missing task '%d'\n", name_.data(), this->task_id_);
|
||||
|
||||
// return no error
|
||||
server->create_reply(this->task_id_)->send();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
template <typename Class, typename T, typename... Args>
|
||||
void register_task(const uint8_t id, T (Class::* callback)(Args ...) const)
|
||||
{
|
||||
this->tasks_[id] = [this, callback](Args ... args) -> T
|
||||
{
|
||||
return (reinterpret_cast<Class*>(this)->*callback)(args...);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Class, typename T, typename... Args>
|
||||
void register_task(const uint8_t id, T (Class::* callback)(Args ...))
|
||||
{
|
||||
this->tasks_[id] = [this, callback](Args ... args) -> T
|
||||
{
|
||||
return (reinterpret_cast<Class*>(this)->*callback)(args...);
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
35
src/client/game/demonware/services.hpp
Normal file
35
src/client/game/demonware/services.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "bit_buffer.hpp"
|
||||
#include "byte_buffer.hpp"
|
||||
#include "data_types.hpp"
|
||||
#include "reply.hpp"
|
||||
#include "service.hpp"
|
||||
#include "servers/service_server.hpp"
|
||||
|
||||
//#include "services/bdTeams.hpp" // 3
|
||||
#include "services/bdStats.hpp" // 4
|
||||
//#include "services/bdMessaging.hpp" // 6
|
||||
#include "services/bdProfiles.hpp" // 8
|
||||
#include "services/bdStorage.hpp" // 10
|
||||
#include "services/bdTitleUtilities.hpp" // 12
|
||||
#include "services/bdBandwidthTest.hpp" // 18
|
||||
//#include "services/bdMatchMaking.hpp" // 21
|
||||
#include "services/bdCounters.hpp" // 23
|
||||
#include "services/bdDML.hpp" // 27
|
||||
#include "services/bdGroups.hpp" // 28
|
||||
//#include "services/bdCMail.hpp" // 29
|
||||
#include "services/bdFacebook.hpp" // 36
|
||||
#include "services/bdAnticheat.hpp" // 38
|
||||
#include "services/bdContentStreaming.hpp" // 50
|
||||
//#include "services/bdTags.hpp" // 52
|
||||
#include "services/bdUNK63.hpp" // 63
|
||||
#include "services/bdEventLog.hpp" // 67
|
||||
#include "services/bdRichPresence.hpp" // 68
|
||||
//#include "services/bdTitleUtilities2.hpp" // 72
|
||||
#include "services/bdUNK80.hpp"
|
||||
// AccountLinking // 86
|
||||
#include "services/bdPresence.hpp" //103
|
||||
#include "services/bdMarketingComms.hpp" //104
|
||||
#include "services/bdMatchMaking2.hpp" //138
|
||||
#include "services/bdMarketing.hpp" //139
|
25
src/client/game/demonware/services/bdAnticheat.cpp
Normal file
25
src/client/game/demonware/services/bdAnticheat.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdAnticheat::bdAnticheat() : service(38, "bdAnticheat")
|
||||
{
|
||||
this->register_task(2, &bdAnticheat::unk2);
|
||||
this->register_task(4, &bdAnticheat::report_console_details);
|
||||
}
|
||||
|
||||
void bdAnticheat::unk2(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO: Read data as soon as needed
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdAnticheat::report_console_details(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO: Read data as soon as needed
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
14
src/client/game/demonware/services/bdAnticheat.hpp
Normal file
14
src/client/game/demonware/services/bdAnticheat.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdAnticheat final : public service
|
||||
{
|
||||
public:
|
||||
bdAnticheat();
|
||||
|
||||
private:
|
||||
void unk2(service_server* server, byte_buffer* buffer) const;
|
||||
void report_console_details(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
27
src/client/game/demonware/services/bdBandwidthTest.cpp
Normal file
27
src/client/game/demonware/services/bdBandwidthTest.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
static uint8_t bandwidth_iw6[51] =
|
||||
{
|
||||
0x0F, 0xC1, 0x1C, 0x37, 0xB8, 0xEF, 0x7C, 0xD6, 0x00, 0x00, 0x04,
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0xF4, 0x01, 0x00, 0x00, 0xD0, 0x07,
|
||||
0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x88, 0x13, 0x00, 0x00, 0xF4, 0x01,
|
||||
0x00, 0x00, 0x02, 0x0C, 0x88, 0xB3, 0x04, 0x65, 0x89, 0xBF, 0xC3, 0x6A,
|
||||
0x27, 0x94, 0xD4, 0x8F
|
||||
};
|
||||
|
||||
bdBandwidthTest::bdBandwidthTest() : service(18, "bdBandwidthTest")
|
||||
{
|
||||
}
|
||||
|
||||
void bdBandwidthTest::exec_task(service_server* server, const std::string& data)
|
||||
{
|
||||
byte_buffer buffer;
|
||||
buffer.write(sizeof bandwidth_iw6, bandwidth_iw6);
|
||||
|
||||
auto reply = server->create_message(5);
|
||||
reply->send(&buffer, true);
|
||||
}
|
||||
}
|
13
src/client/game/demonware/services/bdBandwidthTest.hpp
Normal file
13
src/client/game/demonware/services/bdBandwidthTest.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdBandwidthTest final : public service
|
||||
{
|
||||
public:
|
||||
bdBandwidthTest();
|
||||
|
||||
private:
|
||||
void exec_task(service_server* server, const std::string& data) override;
|
||||
};
|
||||
}
|
25
src/client/game/demonware/services/bdContentStreaming.cpp
Normal file
25
src/client/game/demonware/services/bdContentStreaming.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdContentStreaming::bdContentStreaming() : service(50, "bdContentStreaming")
|
||||
{
|
||||
this->register_task(2, &bdContentStreaming::unk2);
|
||||
this->register_task(3, &bdContentStreaming::unk3);
|
||||
}
|
||||
|
||||
void bdContentStreaming::unk2(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdContentStreaming::unk3(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
14
src/client/game/demonware/services/bdContentStreaming.hpp
Normal file
14
src/client/game/demonware/services/bdContentStreaming.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdContentStreaming final : public service
|
||||
{
|
||||
public:
|
||||
bdContentStreaming();
|
||||
|
||||
private:
|
||||
void unk2(service_server* server, byte_buffer* buffer) const;
|
||||
void unk3(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
25
src/client/game/demonware/services/bdCounters.cpp
Normal file
25
src/client/game/demonware/services/bdCounters.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdCounters::bdCounters() : service(23, "bdCounters")
|
||||
{
|
||||
this->register_task(1, &bdCounters::unk1);
|
||||
this->register_task(2, &bdCounters::unk2);
|
||||
}
|
||||
|
||||
void bdCounters::unk1(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdCounters::unk2(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
14
src/client/game/demonware/services/bdCounters.hpp
Normal file
14
src/client/game/demonware/services/bdCounters.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdCounters final : public service
|
||||
{
|
||||
public:
|
||||
bdCounters();
|
||||
|
||||
private:
|
||||
void unk1(service_server* server, byte_buffer* buffer) const;
|
||||
void unk2(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
28
src/client/game/demonware/services/bdDML.cpp
Normal file
28
src/client/game/demonware/services/bdDML.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdDML::bdDML() : service(27, "bdDML")
|
||||
{
|
||||
this->register_task(2, &bdDML::get_user_raw_data);
|
||||
}
|
||||
|
||||
void bdDML::get_user_raw_data(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
auto result = new bdDMLRawData;
|
||||
result->country_code = "US";
|
||||
result->country_code = "'Murica";
|
||||
result->region = "New York";
|
||||
result->city = "New York";
|
||||
result->latitude = 0;
|
||||
result->longitude = 0;
|
||||
|
||||
result->asn = 0x2119;
|
||||
result->timezone = "+01:00";
|
||||
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->add(result);
|
||||
reply->send();
|
||||
}
|
||||
}
|
13
src/client/game/demonware/services/bdDML.hpp
Normal file
13
src/client/game/demonware/services/bdDML.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdDML final : public service
|
||||
{
|
||||
public:
|
||||
bdDML();
|
||||
|
||||
private:
|
||||
void get_user_raw_data(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
17
src/client/game/demonware/services/bdEventLog.cpp
Normal file
17
src/client/game/demonware/services/bdEventLog.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdEventLog::bdEventLog() : service(67, "bdEventLog")
|
||||
{
|
||||
this->register_task(6, &bdEventLog::unk6);
|
||||
}
|
||||
|
||||
void bdEventLog::unk6(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
13
src/client/game/demonware/services/bdEventLog.hpp
Normal file
13
src/client/game/demonware/services/bdEventLog.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdEventLog final : public service
|
||||
{
|
||||
public:
|
||||
bdEventLog();
|
||||
|
||||
private:
|
||||
void unk6(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
41
src/client/game/demonware/services/bdFacebook.cpp
Normal file
41
src/client/game/demonware/services/bdFacebook.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdFacebook::bdFacebook() : service(36, "bdFacebook")
|
||||
{
|
||||
this->register_task(1, &bdFacebook::unk1);
|
||||
this->register_task(3, &bdFacebook::unk3);
|
||||
this->register_task(7, &bdFacebook::unk7);
|
||||
this->register_task(8, &bdFacebook::unk8);
|
||||
}
|
||||
|
||||
void bdFacebook::unk1(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdFacebook::unk3(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdFacebook::unk7(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdFacebook::unk8(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
16
src/client/game/demonware/services/bdFacebook.hpp
Normal file
16
src/client/game/demonware/services/bdFacebook.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdFacebook final : public service
|
||||
{
|
||||
public:
|
||||
bdFacebook();
|
||||
|
||||
private:
|
||||
void unk1(service_server* server, byte_buffer* buffer) const;
|
||||
void unk3(service_server* server, byte_buffer* buffer) const;
|
||||
void unk7(service_server* server, byte_buffer* buffer) const;
|
||||
void unk8(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
25
src/client/game/demonware/services/bdGroups.cpp
Normal file
25
src/client/game/demonware/services/bdGroups.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdGroups::bdGroups() : service(28, "bdGroup")
|
||||
{
|
||||
this->register_task(1, &bdGroups::set_groups);
|
||||
this->register_task(4, &bdGroups::unk4);
|
||||
}
|
||||
|
||||
void bdGroups::set_groups(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdGroups::unk4(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
14
src/client/game/demonware/services/bdGroups.hpp
Normal file
14
src/client/game/demonware/services/bdGroups.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdGroups final : public service
|
||||
{
|
||||
public:
|
||||
bdGroups();
|
||||
|
||||
private:
|
||||
void set_groups(service_server* server, byte_buffer* buffer) const;
|
||||
void unk4(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
25
src/client/game/demonware/services/bdMarketing.cpp
Normal file
25
src/client/game/demonware/services/bdMarketing.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdMarketing::bdMarketing() : service(139, "bdMarketing")
|
||||
{
|
||||
this->register_task(2, &bdMarketing::unk2);
|
||||
this->register_task(3, &bdMarketing::unk3);
|
||||
}
|
||||
|
||||
void bdMarketing::unk2(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdMarketing::unk3(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
14
src/client/game/demonware/services/bdMarketing.hpp
Normal file
14
src/client/game/demonware/services/bdMarketing.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdMarketing final : public service
|
||||
{
|
||||
public:
|
||||
bdMarketing();
|
||||
|
||||
private:
|
||||
void unk2(service_server* server, byte_buffer* buffer) const;
|
||||
void unk3(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
17
src/client/game/demonware/services/bdMarketingComms.cpp
Normal file
17
src/client/game/demonware/services/bdMarketingComms.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdMarketingComms::bdMarketingComms() : service(104, "bdMarketingComms")
|
||||
{
|
||||
this->register_task(1, &bdMarketingComms::get_messages);
|
||||
}
|
||||
|
||||
void bdMarketingComms::get_messages(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
13
src/client/game/demonware/services/bdMarketingComms.hpp
Normal file
13
src/client/game/demonware/services/bdMarketingComms.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdMarketingComms final : public service
|
||||
{
|
||||
public:
|
||||
bdMarketingComms();
|
||||
|
||||
private:
|
||||
void get_messages(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
49
src/client/game/demonware/services/bdMatchMaking2.cpp
Normal file
49
src/client/game/demonware/services/bdMatchMaking2.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdMatchMaking2::bdMatchMaking2() : service(138, "bdMatchMaking2")
|
||||
{
|
||||
this->register_task(1, &bdMatchMaking2::unk1);
|
||||
this->register_task(2, &bdMatchMaking2::unk2);
|
||||
this->register_task(3, &bdMatchMaking2::unk3);
|
||||
this->register_task(5, &bdMatchMaking2::unk5);
|
||||
this->register_task(16, &bdMatchMaking2::unk16);
|
||||
}
|
||||
|
||||
void bdMatchMaking2::unk1(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdMatchMaking2::unk2(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdMatchMaking2::unk3(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdMatchMaking2::unk5(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdMatchMaking2::unk16(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
17
src/client/game/demonware/services/bdMatchMaking2.hpp
Normal file
17
src/client/game/demonware/services/bdMatchMaking2.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdMatchMaking2 final : public service
|
||||
{
|
||||
public:
|
||||
bdMatchMaking2();
|
||||
|
||||
private:
|
||||
void unk1(service_server* server, byte_buffer* buffer) const;
|
||||
void unk2(service_server* server, byte_buffer* buffer) const;
|
||||
void unk3(service_server* server, byte_buffer* buffer) const;
|
||||
void unk5(service_server* server, byte_buffer* buffer) const;
|
||||
void unk16(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
25
src/client/game/demonware/services/bdPresence.cpp
Normal file
25
src/client/game/demonware/services/bdPresence.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdPresence::bdPresence() : service(103, "bdPresence")
|
||||
{
|
||||
this->register_task(1, &bdPresence::unk1);
|
||||
this->register_task(3, &bdPresence::unk3);
|
||||
}
|
||||
|
||||
void bdPresence::unk1(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdPresence::unk3(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
14
src/client/game/demonware/services/bdPresence.hpp
Normal file
14
src/client/game/demonware/services/bdPresence.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdPresence final : public service
|
||||
{
|
||||
public:
|
||||
bdPresence();
|
||||
|
||||
private:
|
||||
void unk1(service_server* server, byte_buffer* buffer) const;
|
||||
void unk3(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
17
src/client/game/demonware/services/bdProfiles.cpp
Normal file
17
src/client/game/demonware/services/bdProfiles.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdProfiles::bdProfiles() : service(8, "bdProfiles")
|
||||
{
|
||||
this->register_task(3, &bdProfiles::unk3);
|
||||
}
|
||||
|
||||
void bdProfiles::unk3(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
13
src/client/game/demonware/services/bdProfiles.hpp
Normal file
13
src/client/game/demonware/services/bdProfiles.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdProfiles final : public service
|
||||
{
|
||||
public:
|
||||
bdProfiles();
|
||||
|
||||
private:
|
||||
void unk3(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
25
src/client/game/demonware/services/bdRichPresence.cpp
Normal file
25
src/client/game/demonware/services/bdRichPresence.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdRichPresence::bdRichPresence() : service(68, "bdRichPresence")
|
||||
{
|
||||
this->register_task(1, &bdRichPresence::unk1);
|
||||
this->register_task(2, &bdRichPresence::unk2);
|
||||
}
|
||||
|
||||
void bdRichPresence::unk1(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdRichPresence::unk2(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
14
src/client/game/demonware/services/bdRichPresence.hpp
Normal file
14
src/client/game/demonware/services/bdRichPresence.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdRichPresence final : public service
|
||||
{
|
||||
public:
|
||||
bdRichPresence();
|
||||
|
||||
private:
|
||||
void unk1(service_server* server, byte_buffer* buffer) const;
|
||||
void unk2(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
49
src/client/game/demonware/services/bdStats.cpp
Normal file
49
src/client/game/demonware/services/bdStats.cpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdStats::bdStats() : service(4, "bdStats")
|
||||
{
|
||||
this->register_task(1, &bdStats::unk1);
|
||||
this->register_task(3, &bdStats::unk3); // leaderboards
|
||||
this->register_task(4, &bdStats::unk4);
|
||||
this->register_task(8, &bdStats::unk8);
|
||||
this->register_task(11, &bdStats::unk11);
|
||||
}
|
||||
|
||||
void bdStats::unk1(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdStats::unk3(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdStats::unk4(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdStats::unk8(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdStats::unk11(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
17
src/client/game/demonware/services/bdStats.hpp
Normal file
17
src/client/game/demonware/services/bdStats.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdStats final : public service
|
||||
{
|
||||
public:
|
||||
bdStats();
|
||||
|
||||
private:
|
||||
void unk1(service_server* server, byte_buffer* buffer) const;
|
||||
void unk3(service_server* server, byte_buffer* buffer) const;
|
||||
void unk4(service_server* server, byte_buffer* buffer) const;
|
||||
void unk8(service_server* server, byte_buffer* buffer) const;
|
||||
void unk11(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
194
src/client/game/demonware/services/bdStorage.cpp
Normal file
194
src/client/game/demonware/services/bdStorage.cpp
Normal file
@ -0,0 +1,194 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
#include <utils/nt.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/cryptography.hpp>
|
||||
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdStorage::bdStorage() : service(10, "bdStorage")
|
||||
{
|
||||
this->register_task(6, &bdStorage::list_publisher_files);
|
||||
this->register_task(7, &bdStorage::get_publisher_file);
|
||||
this->register_task(10, &bdStorage::set_user_file);
|
||||
this->register_task(12, &bdStorage::get_user_file);
|
||||
this->register_task(13, &bdStorage::unk13);
|
||||
|
||||
this->map_publisher_resource("motd-.*\\.txt", DW_MOTD);
|
||||
this->map_publisher_resource("ffotd-.*\\.ff", DW_FASTFILE);
|
||||
this->map_publisher_resource("playlists(_.+)?\\.aggr", DW_PLAYLISTS);
|
||||
this->map_publisher_resource("winStoreConfig_[Tt][Uu][0-9]+\\.csv", DW_STORE_CONFIG);
|
||||
}
|
||||
|
||||
void bdStorage::map_publisher_resource(const std::string& expression, const INT id)
|
||||
{
|
||||
auto data = utils::nt::load_resource(id);
|
||||
this->map_publisher_resource_variant(expression, std::move(data));
|
||||
}
|
||||
|
||||
void bdStorage::map_publisher_resource_variant(const std::string& expression, resource_variant resource)
|
||||
{
|
||||
if (resource.valueless_by_exception())
|
||||
{
|
||||
throw std::runtime_error("Publisher resource variant is empty!");
|
||||
}
|
||||
|
||||
this->publisher_resources_.emplace_back(std::regex{expression}, std::move(resource));
|
||||
}
|
||||
|
||||
bool bdStorage::load_publisher_resource(const std::string& name, std::string& buffer)
|
||||
{
|
||||
for (const auto& resource : this->publisher_resources_)
|
||||
{
|
||||
if (std::regex_match(name, resource.first))
|
||||
{
|
||||
if (std::holds_alternative<std::string>(resource.second))
|
||||
{
|
||||
buffer = std::get<std::string>(resource.second);
|
||||
}
|
||||
else
|
||||
{
|
||||
buffer = std::get<callback>(resource.second)();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [bdStorage]: missing publisher file: %s\n", name.data());
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void bdStorage::list_publisher_files(service_server* server, byte_buffer* buffer)
|
||||
{
|
||||
uint32_t date;
|
||||
uint16_t num_results, offset;
|
||||
std::string filename, data;
|
||||
|
||||
buffer->read_uint32(&date);
|
||||
buffer->read_uint16(&num_results);
|
||||
buffer->read_uint16(&offset);
|
||||
buffer->read_string(&filename);
|
||||
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
|
||||
if (this->load_publisher_resource(filename, data))
|
||||
{
|
||||
auto* info = new bdFileInfo;
|
||||
|
||||
info->file_id = *reinterpret_cast<const uint64_t*>(utils::cryptography::sha1::compute(filename).data());
|
||||
info->filename = filename;
|
||||
info->create_time = 0;
|
||||
info->modified_time = info->create_time;
|
||||
info->file_size = uint32_t(data.size());
|
||||
info->owner_id = 0;
|
||||
info->priv = false;
|
||||
|
||||
reply->add(info);
|
||||
}
|
||||
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdStorage::get_publisher_file(service_server* server, byte_buffer* buffer)
|
||||
{
|
||||
std::string filename;
|
||||
buffer->read_string(&filename);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [bdStorage]: loading publisher file: %s\n", filename.data());
|
||||
#endif
|
||||
|
||||
std::string data;
|
||||
|
||||
if (this->load_publisher_resource(filename, data))
|
||||
{
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [bdStorage]: sending publisher file: %s, size: %lld\n", filename.data(), data.size());
|
||||
#endif
|
||||
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->add(new bdFileData(data));
|
||||
reply->send();
|
||||
}
|
||||
else
|
||||
{
|
||||
server->create_reply(this->task_id(), game::BD_NO_FILE)->send();
|
||||
}
|
||||
}
|
||||
|
||||
std::string bdStorage::get_user_file_path(const std::string& name)
|
||||
{
|
||||
return "players2/user/" + name;
|
||||
}
|
||||
|
||||
void bdStorage::set_user_file(service_server* server, byte_buffer* buffer) const
|
||||
{
|
||||
bool priv;
|
||||
uint64_t owner;
|
||||
std::string game, filename, data;
|
||||
|
||||
buffer->read_string(&game);
|
||||
buffer->read_string(&filename);
|
||||
buffer->read_bool(&priv);
|
||||
buffer->read_blob(&data);
|
||||
buffer->read_uint64(&owner);
|
||||
|
||||
const auto path = get_user_file_path(filename);
|
||||
utils::io::write_file(path, data);
|
||||
|
||||
auto* info = new bdFileInfo;
|
||||
|
||||
info->file_id = *reinterpret_cast<const uint64_t*>(utils::cryptography::sha1::compute(filename).data());
|
||||
info->filename = filename;
|
||||
info->create_time = uint32_t(time(nullptr));
|
||||
info->modified_time = info->create_time;
|
||||
info->file_size = uint32_t(data.size());
|
||||
info->owner_id = owner;
|
||||
info->priv = priv;
|
||||
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->add(info);
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdStorage::get_user_file(service_server* server, byte_buffer* buffer) const
|
||||
{
|
||||
uint64_t owner{};
|
||||
std::string game, filename, platform, data;
|
||||
|
||||
buffer->read_string(&game);
|
||||
buffer->read_string(&filename);
|
||||
buffer->read_uint64(&owner);
|
||||
buffer->read_string(&platform);
|
||||
|
||||
#ifdef DEBUG
|
||||
printf("[DW]: [bdStorage]: user file: %s, %s, %s\n", game.data(), filename.data(), platform.data());
|
||||
#endif
|
||||
|
||||
const auto path = get_user_file_path(filename);
|
||||
if (utils::io::read_file(path, &data))
|
||||
{
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->add(new bdFileData(data));
|
||||
reply->send();
|
||||
}
|
||||
else
|
||||
{
|
||||
server->create_reply(this->task_id(), game::BD_NO_FILE)->send();
|
||||
}
|
||||
}
|
||||
|
||||
void bdStorage::unk13(service_server* server, byte_buffer* buffer) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
27
src/client/game/demonware/services/bdStorage.hpp
Normal file
27
src/client/game/demonware/services/bdStorage.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdStorage final : public service
|
||||
{
|
||||
public:
|
||||
bdStorage();
|
||||
|
||||
private:
|
||||
using callback = std::function<std::string()>;
|
||||
using resource_variant = std::variant<std::string, callback>;
|
||||
std::vector<std::pair<std::regex, resource_variant>> publisher_resources_;
|
||||
|
||||
void map_publisher_resource(const std::string& expression, INT id);
|
||||
void map_publisher_resource_variant(const std::string& expression, resource_variant resource);
|
||||
bool load_publisher_resource(const std::string& name, std::string& buffer);
|
||||
|
||||
void list_publisher_files(service_server* server, byte_buffer* buffer);
|
||||
void get_publisher_file(service_server* server, byte_buffer* buffer);
|
||||
void set_user_file(service_server* server, byte_buffer* buffer) const;
|
||||
void get_user_file(service_server* server, byte_buffer* buffer) const;
|
||||
void unk13(service_server* server, byte_buffer* buffer) const;
|
||||
|
||||
static std::string get_user_file_path(const std::string& name);
|
||||
};
|
||||
}
|
20
src/client/game/demonware/services/bdTitleUtilities.cpp
Normal file
20
src/client/game/demonware/services/bdTitleUtilities.cpp
Normal file
@ -0,0 +1,20 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdTitleUtilities::bdTitleUtilities() : service(12, "bdTitleUtilities")
|
||||
{
|
||||
this->register_task(6, &bdTitleUtilities::get_server_time);
|
||||
}
|
||||
|
||||
void bdTitleUtilities::get_server_time(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
auto* const time_result = new bdTimeStamp;
|
||||
time_result->unix_time = uint32_t(time(nullptr));
|
||||
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->add(time_result);
|
||||
reply->send();
|
||||
}
|
||||
}
|
13
src/client/game/demonware/services/bdTitleUtilities.hpp
Normal file
13
src/client/game/demonware/services/bdTitleUtilities.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdTitleUtilities final : public service
|
||||
{
|
||||
public:
|
||||
bdTitleUtilities();
|
||||
|
||||
private:
|
||||
void get_server_time(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
17
src/client/game/demonware/services/bdUNK63.cpp
Normal file
17
src/client/game/demonware/services/bdUNK63.cpp
Normal file
@ -0,0 +1,17 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdUNK63::bdUNK63() : service(63, "bdUNK63")
|
||||
{
|
||||
//this->register_task(6, "unk6", &bdUNK63::unk6);
|
||||
}
|
||||
|
||||
void bdUNK63::unk(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
13
src/client/game/demonware/services/bdUNK63.hpp
Normal file
13
src/client/game/demonware/services/bdUNK63.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdUNK63 final : public service
|
||||
{
|
||||
public:
|
||||
bdUNK63();
|
||||
|
||||
private:
|
||||
void unk(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
57
src/client/game/demonware/services/bdUNK80.cpp
Normal file
57
src/client/game/demonware/services/bdUNK80.cpp
Normal file
@ -0,0 +1,57 @@
|
||||
#include <std_include.hpp>
|
||||
#include "../services.hpp"
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
bdUNK80::bdUNK80() : service(80, "bdUNK80")
|
||||
{
|
||||
this->register_task(42, &bdUNK80::unk42);
|
||||
this->register_task(49, &bdUNK80::unk49);
|
||||
this->register_task(60, &bdUNK80::unk60);
|
||||
this->register_task(130, &bdUNK80::unk130);
|
||||
this->register_task(165, &bdUNK80::unk165);
|
||||
this->register_task(193, &bdUNK80::unk193);
|
||||
}
|
||||
|
||||
void bdUNK80::unk42(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdUNK80::unk49(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdUNK80::unk60(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdUNK80::unk130(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdUNK80::unk165(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
|
||||
void bdUNK80::unk193(service_server* server, byte_buffer* /*buffer*/) const
|
||||
{
|
||||
// TODO:
|
||||
auto reply = server->create_reply(this->task_id());
|
||||
reply->send();
|
||||
}
|
||||
}
|
18
src/client/game/demonware/services/bdUNK80.hpp
Normal file
18
src/client/game/demonware/services/bdUNK80.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
namespace demonware
|
||||
{
|
||||
class bdUNK80 final : public service
|
||||
{
|
||||
public:
|
||||
bdUNK80();
|
||||
|
||||
private:
|
||||
void unk42(service_server* server, byte_buffer* buffer) const;
|
||||
void unk49(service_server* server, byte_buffer* buffer) const;
|
||||
void unk60(service_server* server, byte_buffer* buffer) const;
|
||||
void unk130(service_server* server, byte_buffer* buffer) const;
|
||||
void unk165(service_server* server, byte_buffer* buffer) const;
|
||||
void unk193(service_server* server, byte_buffer* buffer) const;
|
||||
};
|
||||
}
|
547
src/client/game/dvars.cpp
Normal file
547
src/client/game/dvars.cpp
Normal file
@ -0,0 +1,547 @@
|
||||
#include <std_include.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
#include "game.hpp"
|
||||
|
||||
namespace dvars
|
||||
{
|
||||
game::dvar_t* con_inputBoxColor = nullptr;
|
||||
game::dvar_t* con_inputHintBoxColor = nullptr;
|
||||
game::dvar_t* con_outputBarColor = nullptr;
|
||||
game::dvar_t* con_outputSliderColor = nullptr;
|
||||
game::dvar_t* con_outputWindowColor = nullptr;
|
||||
game::dvar_t* con_inputDvarMatchColor = nullptr;
|
||||
game::dvar_t* con_inputDvarValueColor = nullptr;
|
||||
game::dvar_t* con_inputDvarInactiveValueColor = nullptr;
|
||||
game::dvar_t* con_inputCmdMatchColor = nullptr;
|
||||
|
||||
game::dvar_t* jump_enableFallDamage;
|
||||
|
||||
game::dvar_t* r_fullbright;
|
||||
game::dvar_t* r_chams;
|
||||
|
||||
std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain)
|
||||
{
|
||||
if (domain.vector.min == -FLT_MAX)
|
||||
{
|
||||
if (domain.vector.max == FLT_MAX)
|
||||
{
|
||||
return utils::string::va("Domain is any %iD vector", components);
|
||||
}
|
||||
else
|
||||
{
|
||||
return utils::string::va("Domain is any %iD vector with components %g or smaller", components,
|
||||
domain.vector.max);
|
||||
}
|
||||
}
|
||||
else if (domain.vector.max == FLT_MAX)
|
||||
{
|
||||
return utils::string::va("Domain is any %iD vector with components %g or bigger", components,
|
||||
domain.vector.min);
|
||||
}
|
||||
else
|
||||
{
|
||||
return utils::string::va("Domain is any %iD vector with components from %g to %g", components,
|
||||
domain.vector.min, domain.vector.max);
|
||||
}
|
||||
}
|
||||
|
||||
std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain)
|
||||
{
|
||||
std::string str;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case game::dvar_type::boolean:
|
||||
return "Domain is 0 or 1"s;
|
||||
|
||||
case game::dvar_type::value:
|
||||
if (domain.value.min == -FLT_MAX)
|
||||
{
|
||||
if (domain.value.max == FLT_MAX)
|
||||
{
|
||||
return "Domain is any number"s;
|
||||
}
|
||||
|
||||
return utils::string::va("Domain is any number %g or smaller", domain.value.max);
|
||||
}
|
||||
|
||||
if (domain.value.max == FLT_MAX)
|
||||
{
|
||||
return utils::string::va("Domain is any number %g or bigger", domain.value.min);
|
||||
}
|
||||
|
||||
return utils::string::va("Domain is any number from %g to %g", domain.value.min, domain.value.max);
|
||||
|
||||
case game::dvar_type::vec2:
|
||||
return dvar_get_vector_domain(2, domain);
|
||||
|
||||
case game::dvar_type::rgb:
|
||||
case game::dvar_type::vec3:
|
||||
return dvar_get_vector_domain(3, domain);
|
||||
|
||||
case game::dvar_type::vec4:
|
||||
return dvar_get_vector_domain(4, domain);
|
||||
|
||||
case game::dvar_type::integer:
|
||||
if (domain.enumeration.stringCount == INT_MIN)
|
||||
{
|
||||
if (domain.integer.max == INT_MAX)
|
||||
{
|
||||
return "Domain is any integer"s;
|
||||
}
|
||||
|
||||
return utils::string::va("Domain is any integer %i or smaller", domain.integer.max);
|
||||
}
|
||||
|
||||
if (domain.integer.max == INT_MAX)
|
||||
{
|
||||
return utils::string::va("Domain is any integer %i or bigger", domain.integer.min);
|
||||
}
|
||||
|
||||
return utils::string::va("Domain is any integer from %i to %i", domain.integer.min, domain.integer.max);
|
||||
|
||||
case game::dvar_type::color:
|
||||
return "Domain is any 4-component color, in RGBA format"s;
|
||||
|
||||
case game::dvar_type::enumeration:
|
||||
str = "Domain is one of the following:"s;
|
||||
|
||||
for (auto string_index = 0; string_index < domain.enumeration.stringCount; ++string_index)
|
||||
{
|
||||
str += utils::string::va("\n %2i: %s", string_index, domain.enumeration.strings[string_index]);
|
||||
}
|
||||
|
||||
return str;
|
||||
|
||||
case game::dvar_type::string:
|
||||
return "Domain is any text"s;
|
||||
|
||||
default:
|
||||
return utils::string::va("unhandled dvar type '%i'", type);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> dvar_list =
|
||||
{
|
||||
"accessToSubscriberContent",
|
||||
"acousticSpikeMaxRange",
|
||||
"acousticSpikeMinRadius",
|
||||
"acousticSpikeMinRange",
|
||||
"acousticSpikeRingSize",
|
||||
"acousticSpikeSize",
|
||||
"actionSlotsHide",
|
||||
"activeAction",
|
||||
"activeFriendsMaxBackoffLevel",
|
||||
"activeFriendsNumDayBuckets",
|
||||
"activeFriendsNumPlayBuckets",
|
||||
"activeFriendsRefreshDelay",
|
||||
"activeFriendsSecondsPerBucket",
|
||||
"aim_accel_turnrate_lerp",
|
||||
"aim_aimAssistRangeScale",
|
||||
"aim_alternate_lockon_deflection",
|
||||
"aim_alternate_lockon_pitch_strength",
|
||||
"aim_alternate_lockon_region_height",
|
||||
"aim_alternate_lockon_region_width",
|
||||
"aim_alternate_lockon_strength",
|
||||
"aim_assist_min_target_distance",
|
||||
"aim_assist_script_disable",
|
||||
"cg_draw2D",
|
||||
"cg_drawBigFPS",
|
||||
"cg_drawBreathHint",
|
||||
"cg_drawBuildName",
|
||||
"cg_drawFPS",
|
||||
"cg_drawFPSLabels",
|
||||
"cg_drawFPSOnly",
|
||||
"cg_drawFPSScale",
|
||||
"cg_drawVersion",
|
||||
"cg_drawVersionX",
|
||||
"cg_drawVersionY",
|
||||
"cg_drawViewpos",
|
||||
"cg_drawgun",
|
||||
"cg_fov",
|
||||
"cg_fov_default",
|
||||
"cg_fov_default_thirdperson",
|
||||
"cg_fovCompMax",
|
||||
"cg_fovExtraCam",
|
||||
"cg_fovMin",
|
||||
"cg_fovScale",
|
||||
"cl_maxpackets",
|
||||
"cl_maxPing",
|
||||
"com_introPlayed",
|
||||
"com_isNotice",
|
||||
"com_maxclients",
|
||||
"com_maxfps",
|
||||
"com_maxFrameTime",
|
||||
"fs_basegame",
|
||||
"fs_basepath",
|
||||
"fs_basepath_output",
|
||||
"fs_cdpath",
|
||||
"fs_copyfiles",
|
||||
"fs_debug",
|
||||
"fs_game",
|
||||
"fs_homepath",
|
||||
"fs_ignoreLocalized",
|
||||
"fs_restrict",
|
||||
"fs_savepath",
|
||||
"fs_usedevdir",
|
||||
"fs_userDocuments",
|
||||
"fs_usermapdir",
|
||||
"g_gametype",
|
||||
"g_hardcore",
|
||||
"g_listEntity",
|
||||
"g_loadScripts",
|
||||
"g_log",
|
||||
"g_logSync",
|
||||
"g_logTimeStampInSeconds",
|
||||
"timescale", // Scale time of each frame ---> "5401"
|
||||
"g_motd",
|
||||
"g_scriptMainMenu",
|
||||
"g_smoothClients",
|
||||
"g_spawnai",
|
||||
"g_speed",
|
||||
"gamedate",
|
||||
"gamedvr_active",
|
||||
"gameMode",
|
||||
"gamename",
|
||||
"log_party_state",
|
||||
"logfile",
|
||||
"m_filter",
|
||||
"m_forward",
|
||||
"m_pitch",
|
||||
"m_side",
|
||||
"m_yaw",
|
||||
"r_drawLightmapDrawlists",
|
||||
"r_drawLitDrawlists",
|
||||
"r_drawSun",
|
||||
"r_drawWater",
|
||||
"r_fog",
|
||||
"r_fog_depthhack_scale",
|
||||
"r_fog_disable",
|
||||
"r_fog_ev_adjust",
|
||||
"r_fogBaseDist",
|
||||
"r_fogBaseHeight",
|
||||
"r_fogColor",
|
||||
"r_fogHalfDist",
|
||||
"r_fogHalfHeight",
|
||||
"r_fogOpacity",
|
||||
"r_fogSunColor",
|
||||
"r_fogSunInner",
|
||||
"r_fogSunOpacity",
|
||||
"r_fogSunOuter",
|
||||
"r_fogSunPitch",
|
||||
"r_fogSunYaw",
|
||||
"r_fogTweak",
|
||||
"r_forceLod",
|
||||
"r_fullbright",
|
||||
"r_fullPrepass",
|
||||
"r_fullscreen",
|
||||
"r_fullscreenWindow",
|
||||
"r_fxaa",
|
||||
"r_fxaaSubpixel",
|
||||
"r_FXAverageColorFunc",
|
||||
"r_gamma",
|
||||
"r_glossMap",
|
||||
"r_glow",
|
||||
"r_glow_allowed",
|
||||
"stat_version",
|
||||
"stats_version_check",
|
||||
"statsLocationFatal",
|
||||
"stopspeed",
|
||||
"storeMapPackMaskToStats",
|
||||
"stringtable_debug",
|
||||
"sv_allowAnonymous",
|
||||
"sv_allowClientConsole",
|
||||
"sv_allowDownload",
|
||||
"sv_allowedClan1",
|
||||
"sv_allowedClan2",
|
||||
"sv_archiveClientsPositions",
|
||||
"sv_assistWorkers",
|
||||
"sv_authenticating",
|
||||
"sv_bitfieldTracking",
|
||||
"sv_botsPressAttackBtn",
|
||||
"sv_cheats",
|
||||
"sv_checkMinPlayers",
|
||||
"sv_clientArchive",
|
||||
"sv_clientFpsLimit",
|
||||
"sv_clientside",
|
||||
"sv_clientSideBullets",
|
||||
"sv_clientSideVehicles",
|
||||
"sv_connectTimeout",
|
||||
"sv_cumulThinkTime",
|
||||
"sv_debugRate",
|
||||
"sv_debugReliableCmds",
|
||||
"sv_disableClientConsole",
|
||||
"sv_dwlsgerror",
|
||||
"sv_endGameIfISuck",
|
||||
"sv_error_on_baseline_failure",
|
||||
"sv_expensive_bullet_time",
|
||||
"sv_exponentialBackoffAfterNonAckedMsgs",
|
||||
"sv_externalEventLoop",
|
||||
"sv_FakeRemoteClient",
|
||||
"sv_fakeServerLoad",
|
||||
"sv_fakeServerLoadRand",
|
||||
"sv_FFCheckSums",
|
||||
"sv_FFNames",
|
||||
"sv_floodprotect",
|
||||
"sv_forceunranked",
|
||||
"sv_fps",
|
||||
"sv_hostname",
|
||||
"sv_hugeSnapshotDelay",
|
||||
"sv_hugeSnapshotSize",
|
||||
"sv_iwdNames",
|
||||
"sv_iwds",
|
||||
"sv_keywords",
|
||||
"sv_kickBanTime",
|
||||
"sv_lastSaveCommitedToDevice",
|
||||
"sv_local_client_snapshot_msec",
|
||||
"sv_mapname",
|
||||
"mapname",
|
||||
"sv_mapRotation",
|
||||
"sv_mapRotationCurrent",
|
||||
"cl_maxpackets",
|
||||
"sv_maxclients",
|
||||
"sv_maxPhysExplosionSpheres",
|
||||
"sv_maxPing",
|
||||
"sv_maxRate",
|
||||
"sv_minPing",
|
||||
"sv_minPingClamp",
|
||||
"sv_network_fps",
|
||||
"sv_networkRateSolution",
|
||||
"sv_noname",
|
||||
"sv_NoShapshotWarnings",
|
||||
"sv_numExpBackoffBeforeReleasingCachedSnapshots",
|
||||
"sv_packet_info",
|
||||
"sv_padPackets",
|
||||
"sv_paused",
|
||||
"sv_playlistFetchInterval",
|
||||
"sv_privateClients",
|
||||
"sv_privateClientsForClients",
|
||||
"sv_privatePassword",
|
||||
"sv_punkbuster",
|
||||
"sv_pure",
|
||||
"sv_reconnectlimit",
|
||||
"sv_referencedFFCheckSums",
|
||||
"sv_referencedFFNames",
|
||||
"sv_referencedIwdNames",
|
||||
"sv_referencedIwds",
|
||||
"sv_rejoinTimeout",
|
||||
"sv_remote_client_snapshot_joiningstate_msec",
|
||||
"sv_remote_client_snapshot_msec",
|
||||
"sv_resetOnSpawn",
|
||||
"sv_restrictedTempEnts",
|
||||
"sv_rewindPoseArchive",
|
||||
"sv_running",
|
||||
"sv_saveDeviceAvailable",
|
||||
"sv_saveGameAvailable",
|
||||
"sv_saveGameNotReadable",
|
||||
"sv_saveOnStartMap",
|
||||
"sv_serverid",
|
||||
"sv_showAverageBPS",
|
||||
"sv_showCommands",
|
||||
"sv_smp",
|
||||
"sv_SnapshotManLaw",
|
||||
"sv_testValue",
|
||||
"sv_timeout",
|
||||
"sv_trackFrameMsecThreshold",
|
||||
"sv_useExtraCompress",
|
||||
"sv_voice",
|
||||
"sv_voiceQuality",
|
||||
"sv_writeConfigStrings",
|
||||
"sv_wwwBaseURL",
|
||||
"sv_wwwDlDisconnected",
|
||||
"sv_wwwDownload",
|
||||
"sv_zlib_threshold",
|
||||
"sv_zombietime",
|
||||
"svwp",
|
||||
"syncTimeTimeout",
|
||||
"sys_configSum",
|
||||
"sys_configureGHz",
|
||||
"sys_cpuGHz",
|
||||
"sys_cpuName",
|
||||
"sys_gpu",
|
||||
"sys_lockThreads",
|
||||
"sys_quitMigrateTime",
|
||||
"sys_smp_allowed",
|
||||
"sys_SSE",
|
||||
"sys_sysMB",
|
||||
"systemlink",
|
||||
"systemlink_host",
|
||||
"bot_AllowGrenades",
|
||||
"bot_autoconnectdefault",
|
||||
"bot_CloseDistance",
|
||||
"bot_CrouchDistance",
|
||||
"bot_difficulty",
|
||||
"bot_difficultydefault",
|
||||
"bot_enemies",
|
||||
"bot_Fov",
|
||||
"bot_friends",
|
||||
"bot_GoalRadius",
|
||||
"bot_MaxAdsTime",
|
||||
"bot_MaxCrouchTime",
|
||||
"bot_MaxDeathTime",
|
||||
"bot_MaxFireTime",
|
||||
"bot_MaxGrenadeTime",
|
||||
"bot_MaxPitchTime",
|
||||
"bot_MaxReactionTime",
|
||||
"bot_MaxStrafeTime",
|
||||
"bot_MeleeDist",
|
||||
"bot_MinAdsTime",
|
||||
"bot_MinCrouchTime",
|
||||
"bot_MinDeathTime",
|
||||
"bot_MinFireTime",
|
||||
"bot_MinGrenadeTime",
|
||||
"bot_MinPitchTime",
|
||||
"bot_MinReactionTime",
|
||||
"bot_MinStrafeTime",
|
||||
"bot_PitchDown",
|
||||
"bot_PitchSpeed",
|
||||
"bot_PitchSpeedAds",
|
||||
"bot_PitchUp",
|
||||
"bot_SprintDistance",
|
||||
"bot_StrafeChance",
|
||||
"bot_TargetLeadBias",
|
||||
"bot_tips",
|
||||
"bot_UseFriendNames",
|
||||
"bot_YawSpeed",
|
||||
"bot_YawSpeedAds",
|
||||
"custom_roundlimit",
|
||||
"custom_scorelimit",
|
||||
"custom_scr_allowannouncer",
|
||||
"custom_scr_allowbattlechatter",
|
||||
"custom_scr_bot_difficulty",
|
||||
"custom_scr_ctf_enemycarriervisible",
|
||||
"custom_scr_ctf_idleflagreturntime",
|
||||
"custom_scr_ctf_roundswitch",
|
||||
"custom_scr_ctf_touchreturn",
|
||||
"custom_scr_custom_score_assist",
|
||||
"custom_scr_dem_bombtimer",
|
||||
"custom_scr_dem_defusetime",
|
||||
"custom_scr_dem_extratime",
|
||||
"custom_scr_dem_planttime",
|
||||
"custom_scr_dem_roundswitch",
|
||||
"custom_scr_dm_bonus_leader",
|
||||
"custom_scr_dm_score_assist",
|
||||
"custom_scr_dm_score_death",
|
||||
"custom_scr_dm_score_headshot",
|
||||
"custom_scr_dm_score_kill",
|
||||
"custom_scr_dm_score_suicide",
|
||||
"custom_scr_dom_flagcapturetime",
|
||||
"custom_scr_game_allowkillcam",
|
||||
"custom_scr_game_forceradar",
|
||||
"custom_scr_game_hardpoints",
|
||||
"custom_scr_game_onlyheadshots",
|
||||
"custom_scr_game_perks",
|
||||
"custom_scr_game_spectatetype",
|
||||
"custom_scr_hardcore",
|
||||
"custom_scr_num_bots",
|
||||
"custom_scr_num_bots_enemy",
|
||||
"custom_scr_num_bots_friendly",
|
||||
"custom_scr_player_forcerespawn",
|
||||
"custom_scr_player_healthregentime",
|
||||
"custom_scr_player_maxhealth",
|
||||
"custom_scr_player_sprintTime",
|
||||
"custom_scr_rcbomb_notimeout",
|
||||
"custom_scr_sab_bombtimer",
|
||||
"custom_scr_sab_defusetime",
|
||||
"custom_scr_sab_hotpotato",
|
||||
"custom_scr_sab_planttime",
|
||||
"custom_scr_sab_roundswitch",
|
||||
"custom_scr_sd_bombtimer",
|
||||
"custom_scr_sd_defusetime",
|
||||
"custom_scr_sd_multibomb",
|
||||
"custom_scr_sd_planttime",
|
||||
"custom_scr_sd_roundswitch",
|
||||
"custom_scr_tdm_bonus_leader",
|
||||
"custom_scr_tdm_score_death",
|
||||
"custom_scr_tdm_score_headshot",
|
||||
"custom_scr_tdm_score_kill",
|
||||
"custom_scr_tdm_score_suicide",
|
||||
"custom_scr_team_fftype",
|
||||
"custom_scr_team_teamkillspawndelay",
|
||||
"custom_scr_vehicles_enabled",
|
||||
"name",
|
||||
"custom_timelimit"
|
||||
};
|
||||
|
||||
game::dvar_t* register_int(const std::string& name, int value, int min, int max,
|
||||
game::DvarFlags flags, bool add_to_list)
|
||||
{
|
||||
const auto hash = game::generateHashValue(name.data());
|
||||
|
||||
if (add_to_list)
|
||||
{
|
||||
dvar_list.push_back(name);
|
||||
}
|
||||
|
||||
return game::Dvar_RegisterInt(hash, "", value, min, max, flags);
|
||||
}
|
||||
|
||||
game::dvar_t* register_bool(const std::string& name, bool value,
|
||||
game::DvarFlags flags, bool add_to_list)
|
||||
{
|
||||
const auto hash = game::generateHashValue(name.data());
|
||||
|
||||
if (add_to_list)
|
||||
{
|
||||
dvar_list.push_back(name);
|
||||
}
|
||||
|
||||
return game::Dvar_RegisterBool(hash, "", value, flags);
|
||||
}
|
||||
|
||||
game::dvar_t* register_string(const std::string& name, const char* value,
|
||||
game::DvarFlags flags, bool add_to_list)
|
||||
{
|
||||
const auto hash = game::generateHashValue(name.data());
|
||||
|
||||
if (add_to_list)
|
||||
{
|
||||
dvar_list.push_back(name);
|
||||
}
|
||||
|
||||
return game::Dvar_RegisterString(hash, "", value, flags);
|
||||
}
|
||||
|
||||
|
||||
game::dvar_t* register_float(const std::string& name, float value, float min,
|
||||
float max, game::DvarFlags flags, bool add_to_list)
|
||||
{
|
||||
const auto hash = game::generateHashValue(name.data());
|
||||
|
||||
if (add_to_list)
|
||||
{
|
||||
dvar_list.push_back(name);
|
||||
}
|
||||
|
||||
return game::Dvar_RegisterFloat(hash, "", value, min, max, flags);
|
||||
}
|
||||
|
||||
game::dvar_t* register_vec4(const std::string& name, float x, float y, float z,
|
||||
float w, float min, float max, game::DvarFlags flags, bool add_to_list)
|
||||
{
|
||||
const auto hash = game::generateHashValue(name.data());
|
||||
|
||||
if (add_to_list)
|
||||
{
|
||||
dvar_list.push_back(name);
|
||||
}
|
||||
|
||||
return game::Dvar_RegisterVec4(hash, "", x, y, z, w, min, max, flags);
|
||||
}
|
||||
|
||||
namespace override {
|
||||
game::dvar_t* register_int(const std::string& name, int value, int min, int max,
|
||||
const unsigned int flags, bool add_to_list)
|
||||
{
|
||||
const auto hash = game::generateHashValue(name.data());
|
||||
|
||||
if (add_to_list)
|
||||
{
|
||||
dvar_list.push_back(name);
|
||||
}
|
||||
|
||||
return game::Dvar_RegisterInt(hash, "", value, min, max, flags);
|
||||
}
|
||||
}
|
||||
}
|
38
src/client/game/dvars.hpp
Normal file
38
src/client/game/dvars.hpp
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "game.hpp"
|
||||
#include "structs.hpp"
|
||||
#include <string>
|
||||
|
||||
namespace dvars
|
||||
{
|
||||
extern game::dvar_t* con_inputBoxColor;
|
||||
extern game::dvar_t* con_inputHintBoxColor;
|
||||
extern game::dvar_t* con_outputBarColor;
|
||||
extern game::dvar_t* con_outputSliderColor;
|
||||
extern game::dvar_t* con_outputWindowColor;
|
||||
extern game::dvar_t* con_inputDvarMatchColor;
|
||||
extern game::dvar_t* con_inputDvarValueColor;
|
||||
extern game::dvar_t* con_inputDvarInactiveValueColor;
|
||||
extern game::dvar_t* con_inputCmdMatchColor;
|
||||
|
||||
extern game::dvar_t* jump_enableFallDamage;
|
||||
|
||||
extern game::dvar_t* r_fullbright;
|
||||
extern game::dvar_t* r_chams;
|
||||
|
||||
extern std::vector<std::string> dvar_list;
|
||||
|
||||
std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain);
|
||||
std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain);
|
||||
|
||||
game::dvar_t* register_int(const std::string& name, int value, int min, int max, game::DvarFlags flags, bool add_to_list = true);
|
||||
game::dvar_t* register_bool(const std::string& name, bool value, game::DvarFlags flags, bool add_to_list = true);
|
||||
game::dvar_t* register_string(const std::string& name, const char* value, game::DvarFlags flags, bool add_to_list = true);
|
||||
game::dvar_t* register_float(const std::string& name, float value, float min, float max, game::DvarFlags flags, bool add_to_list = true);
|
||||
game::dvar_t* register_vec4(const std::string& name, float x, float y, float z, float w, float min, float max, game::DvarFlags flags, bool add_to_list = true);
|
||||
|
||||
namespace override {
|
||||
game::dvar_t* register_int(const std::string& name, int value, int min, int max, const unsigned int flags, bool add_to_list = true);
|
||||
}
|
||||
}
|
100
src/client/game/game.cpp
Normal file
100
src/client/game/game.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
#include <std_include.hpp>
|
||||
#include "game.hpp"
|
||||
|
||||
namespace game
|
||||
{
|
||||
int Cmd_Argc()
|
||||
{
|
||||
return cmd_args->argc[cmd_args->nesting];
|
||||
}
|
||||
|
||||
const char* Cmd_Argv(const int index)
|
||||
{
|
||||
return cmd_args->argv[cmd_args->nesting][index];
|
||||
}
|
||||
|
||||
int SV_Cmd_Argc()
|
||||
{
|
||||
return sv_cmd_args->argc[sv_cmd_args->nesting];
|
||||
}
|
||||
|
||||
const char* SV_Cmd_Argv(const int index)
|
||||
{
|
||||
return sv_cmd_args->argv[sv_cmd_args->nesting][index];
|
||||
}
|
||||
|
||||
|
||||
namespace environment
|
||||
{
|
||||
launcher::mode mode = launcher::mode::none;
|
||||
|
||||
launcher::mode translate_surrogate(const launcher::mode _mode)
|
||||
{
|
||||
switch (_mode)
|
||||
{
|
||||
case launcher::mode::survival:
|
||||
case launcher::mode::zombies:
|
||||
return launcher::mode::multiplayer;
|
||||
default:
|
||||
return _mode;
|
||||
}
|
||||
}
|
||||
|
||||
launcher::mode get_real_mode()
|
||||
{
|
||||
if (mode == launcher::mode::none)
|
||||
{
|
||||
throw std::runtime_error("Launcher mode not valid. Something must be wrong.");
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
launcher::mode get_mode()
|
||||
{
|
||||
return translate_surrogate(get_real_mode());
|
||||
}
|
||||
|
||||
bool is_sp()
|
||||
{
|
||||
return get_mode() == launcher::mode::singleplayer;
|
||||
}
|
||||
|
||||
bool is_mp()
|
||||
{
|
||||
return get_mode() == launcher::mode::multiplayer;
|
||||
}
|
||||
|
||||
bool is_dedi()
|
||||
{
|
||||
return get_mode() == launcher::mode::server;
|
||||
}
|
||||
|
||||
void set_mode(const launcher::mode _mode)
|
||||
{
|
||||
mode = _mode;
|
||||
}
|
||||
|
||||
std::string get_string()
|
||||
{
|
||||
const auto current_mode = get_real_mode();
|
||||
switch (current_mode)
|
||||
{
|
||||
case launcher::mode::server:
|
||||
return "Dedicated Server";
|
||||
|
||||
case launcher::mode::multiplayer:
|
||||
return "Multiplayer";
|
||||
|
||||
case launcher::mode::singleplayer:
|
||||
return "Singleplayer";
|
||||
|
||||
case launcher::mode::none:
|
||||
return "None";
|
||||
|
||||
default:
|
||||
return "Unknown (" + std::to_string(static_cast<int>(mode)) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
70
src/client/game/game.hpp
Normal file
70
src/client/game/game.hpp
Normal file
@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "structs.hpp"
|
||||
#include "launcher/launcher.hpp"
|
||||
|
||||
#define SELECT_VALUE(sp, mp) (game::environment::is_sp() ? (sp) : (mp))
|
||||
|
||||
#define SERVER_CD_KEY "S1X-CD-Key"
|
||||
|
||||
namespace game
|
||||
{
|
||||
namespace environment
|
||||
{
|
||||
launcher::mode get_mode();
|
||||
launcher::mode get_real_mode();
|
||||
|
||||
bool is_sp();
|
||||
bool is_mp();
|
||||
bool is_dedi();
|
||||
|
||||
void set_mode(launcher::mode mode);
|
||||
|
||||
std::string get_string();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class symbol
|
||||
{
|
||||
public:
|
||||
symbol(const size_t sp_address, const size_t mp_address)
|
||||
: sp_object_(reinterpret_cast<T*>(sp_address))
|
||||
, mp_object_(reinterpret_cast<T*>(mp_address))
|
||||
{
|
||||
}
|
||||
|
||||
T* get() const
|
||||
{
|
||||
if (environment::is_sp())
|
||||
{
|
||||
return sp_object_;
|
||||
}
|
||||
|
||||
return mp_object_;
|
||||
}
|
||||
|
||||
operator T* () const
|
||||
{
|
||||
return this->get();
|
||||
}
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
return this->get();
|
||||
}
|
||||
|
||||
private:
|
||||
T* sp_object_;
|
||||
T* mp_object_;
|
||||
};
|
||||
|
||||
int Cmd_Argc();
|
||||
const char* Cmd_Argv(int index);
|
||||
|
||||
int SV_Cmd_Argc();
|
||||
const char* SV_Cmd_Argv(int index);
|
||||
|
||||
bool VirtualLobby_Loaded();
|
||||
}
|
||||
|
||||
#include "symbols.hpp"
|
120
src/client/game/scripting/entity.cpp
Normal file
120
src/client/game/scripting/entity.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
#include <std_include.hpp>
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
#include "execution.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
entity::entity()
|
||||
: entity(0)
|
||||
{
|
||||
}
|
||||
|
||||
entity::entity(const entity& other) : entity(other.entity_id_)
|
||||
{
|
||||
}
|
||||
|
||||
entity::entity(entity&& other) noexcept
|
||||
{
|
||||
this->entity_id_ = other.entity_id_;
|
||||
other.entity_id_ = 0;
|
||||
}
|
||||
|
||||
entity::entity(const unsigned int entity_id)
|
||||
: entity_id_(entity_id)
|
||||
{
|
||||
this->add();
|
||||
}
|
||||
|
||||
entity::entity(game::scr_entref_t entref)
|
||||
: entity(game::FindEntityId(entref.entnum, entref.classnum))
|
||||
{
|
||||
}
|
||||
|
||||
entity::~entity()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
entity& entity::operator=(const entity& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->entity_id_ = other.entity_id_;
|
||||
this->add();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
entity& entity::operator=(entity&& other) noexcept
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->entity_id_ = other.entity_id_;
|
||||
other.entity_id_ = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
unsigned int entity::get_entity_id() const
|
||||
{
|
||||
return this->entity_id_;
|
||||
}
|
||||
|
||||
game::scr_entref_t entity::get_entity_reference() const
|
||||
{
|
||||
if (!this->entity_id_)
|
||||
{
|
||||
const auto not_null = static_cast<uint16_t>(~0ui16);
|
||||
return game::scr_entref_t{not_null, not_null};
|
||||
}
|
||||
|
||||
return game::Scr_GetEntityIdRef(this->get_entity_id());
|
||||
}
|
||||
|
||||
bool entity::operator==(const entity& other) const noexcept
|
||||
{
|
||||
return this->get_entity_id() == other.get_entity_id();
|
||||
}
|
||||
|
||||
bool entity::operator!=(const entity& other) const noexcept
|
||||
{
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
void entity::add() const
|
||||
{
|
||||
if (this->entity_id_)
|
||||
{
|
||||
game::AddRefToValue(game::SCRIPT_OBJECT, {static_cast<int>(this->entity_id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void entity::release() const
|
||||
{
|
||||
if (this->entity_id_)
|
||||
{
|
||||
game::RemoveRefToValue(game::SCRIPT_OBJECT, {static_cast<int>(this->entity_id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void entity::set(const std::string& field, const script_value& value) const
|
||||
{
|
||||
set_entity_field(*this, field, value);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value entity::get<script_value>(const std::string& field) const
|
||||
{
|
||||
return get_entity_field(*this, field);
|
||||
}
|
||||
|
||||
script_value entity::call(const std::string& name, const std::vector<script_value>& arguments) const
|
||||
{
|
||||
return call_function(name, *this, arguments);
|
||||
}
|
||||
}
|
50
src/client/game/scripting/entity.hpp
Normal file
50
src/client/game/scripting/entity.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class entity final
|
||||
{
|
||||
public:
|
||||
entity();
|
||||
entity(unsigned int entity_id);
|
||||
entity(game::scr_entref_t entref);
|
||||
|
||||
entity(const entity& other);
|
||||
entity(entity&& other) noexcept;
|
||||
|
||||
~entity();
|
||||
|
||||
entity& operator=(const entity& other);
|
||||
entity& operator=(entity&& other) noexcept;
|
||||
|
||||
void set(const std::string& field, const script_value& value) const;
|
||||
|
||||
template <typename T = script_value>
|
||||
T get(const std::string& field) const;
|
||||
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments = {}) const;
|
||||
|
||||
unsigned int get_entity_id() const;
|
||||
game::scr_entref_t get_entity_reference() const;
|
||||
|
||||
bool operator ==(const entity& other) const noexcept;
|
||||
bool operator !=(const entity& other) const noexcept;
|
||||
|
||||
private:
|
||||
unsigned int entity_id_;
|
||||
|
||||
void add() const;
|
||||
void release() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
script_value entity::get(const std::string& field) const;
|
||||
|
||||
template <typename T>
|
||||
T entity::get(const std::string& field) const
|
||||
{
|
||||
return this->get<script_value>(field).as<T>();
|
||||
}
|
||||
}
|
13
src/client/game/scripting/event.hpp
Normal file
13
src/client/game/scripting/event.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "script_value.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
struct event
|
||||
{
|
||||
std::string name;
|
||||
entity entity{};
|
||||
std::vector<script_value> arguments;
|
||||
};
|
||||
}
|
250
src/client/game/scripting/execution.cpp
Normal file
250
src/client/game/scripting/execution.cpp
Normal file
@ -0,0 +1,250 @@
|
||||
#include <std_include.hpp>
|
||||
#include "execution.hpp"
|
||||
#include "safe_execution.hpp"
|
||||
#include "stack_isolation.hpp"
|
||||
|
||||
#include "component/scripting.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::VariableValue* allocate_argument()
|
||||
{
|
||||
game::VariableValue* value_ptr = ++game::scr_VmPub->top;
|
||||
++game::scr_VmPub->inparamcount;
|
||||
return value_ptr;
|
||||
}
|
||||
|
||||
void push_value(const script_value& value)
|
||||
{
|
||||
auto* value_ptr = allocate_argument();
|
||||
*value_ptr = value.get_raw();
|
||||
|
||||
game::AddRefToValue(value_ptr->type, value_ptr->u);
|
||||
}
|
||||
|
||||
int get_field_id(const int classnum, const std::string& field)
|
||||
{
|
||||
const auto class_id = game::g_classMap[classnum].id;
|
||||
const auto field_str = game::SL_GetString(field.data(), 0);
|
||||
const auto _ = gsl::finally([field_str]()
|
||||
{
|
||||
game::RemoveRefToValue(game::SCRIPT_STRING, {static_cast<int>(field_str)});
|
||||
});
|
||||
|
||||
const auto offset = game::FindVariable(class_id, field_str);
|
||||
if (offset)
|
||||
{
|
||||
const auto index = 3 * (offset + 0xFA00 * (class_id & 3));
|
||||
const auto id = reinterpret_cast<PINT64>(SELECT_VALUE(0x149BB5680, 0x14821DF80))[index];
|
||||
return static_cast<int>(id);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
script_value get_return_value()
|
||||
{
|
||||
if (game::scr_VmPub->inparamcount == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
game::Scr_ClearOutParams();
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]);
|
||||
}
|
||||
}
|
||||
|
||||
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments)
|
||||
{
|
||||
stack_isolation _;
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
const auto event_id = game::SL_GetString(event.data(), 0);
|
||||
game::Scr_NotifyId(entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount);
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
|
||||
const auto is_method_call = *reinterpret_cast<const int*>(&entref) != -1;
|
||||
const auto function = find_function(name, !is_method_call);
|
||||
if (function == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Unknown "s + (is_method_call ? "method" : "function") + " '" + name + "'");
|
||||
}
|
||||
|
||||
stack_isolation _;
|
||||
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::call(function, entref))
|
||||
{
|
||||
throw std::runtime_error(
|
||||
"Error executing "s + (is_method_call ? "method" : "function") + " '" + name + "'");
|
||||
}
|
||||
|
||||
return get_return_value();
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, entity(), arguments);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, arguments);
|
||||
}
|
||||
|
||||
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
stack_isolation _;
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
scripting::push_value(*i);
|
||||
}
|
||||
|
||||
game::AddRefToObject(id);
|
||||
|
||||
const auto local_id = game::AllocThread(id);
|
||||
const auto result = game::VM_Execute(local_id, pos, (unsigned int)arguments.size());
|
||||
game::RemoveRefToObject(result);
|
||||
|
||||
return get_return_value();
|
||||
}
|
||||
|
||||
const char* get_function_pos(const std::string& filename, const std::string& function)
|
||||
{
|
||||
if (scripting::script_function_table.find(filename) == scripting::script_function_table.end())
|
||||
{
|
||||
throw std::runtime_error("File '" + filename + "' not found");
|
||||
};
|
||||
|
||||
const auto functions = scripting::script_function_table[filename];
|
||||
if (functions.find(function) == functions.end())
|
||||
{
|
||||
throw std::runtime_error("Function '" + function + "' in file '" + filename + "' not found");
|
||||
}
|
||||
|
||||
return functions.at(function);
|
||||
}
|
||||
|
||||
script_value call_script_function(const entity& entity, const std::string& filename,
|
||||
const std::string& function, const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto pos = get_function_pos(filename, function);
|
||||
return exec_ent_thread(entity, pos, arguments);
|
||||
}
|
||||
|
||||
static std::unordered_map<unsigned int, std::unordered_map<std::string, script_value>> custom_fields;
|
||||
|
||||
script_value get_custom_field(const entity& entity, const std::string& field)
|
||||
{
|
||||
auto& fields = custom_fields[entity.get_entity_id()];
|
||||
const auto _field = fields.find(field);
|
||||
if (_field != fields.end())
|
||||
{
|
||||
return _field->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void set_custom_field(const entity& entity, const std::string& field, const script_value& value)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
if (custom_fields[id].find(field) != custom_fields[id].end())
|
||||
{
|
||||
custom_fields[id][field] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
custom_fields[id].insert(std::make_pair(field, value));
|
||||
}
|
||||
|
||||
void clear_entity_fields(const entity& entity)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
if (custom_fields.find(id) != custom_fields.end())
|
||||
{
|
||||
custom_fields[id].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void clear_custom_fields()
|
||||
{
|
||||
custom_fields.clear();
|
||||
}
|
||||
|
||||
void set_entity_field(const entity& entity, const std::string& field, const script_value& value)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const int id = get_field_id(entref.classnum, field);
|
||||
|
||||
if (id != -1)
|
||||
{
|
||||
stack_isolation _;
|
||||
push_value(value);
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::set_entity_field(entref, id))
|
||||
{
|
||||
throw std::runtime_error("Failed to set value for field '" + field + "'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read custom fields
|
||||
set_custom_field(entity, field, value);
|
||||
}
|
||||
}
|
||||
|
||||
script_value get_entity_field(const entity& entity, const std::string& field)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const auto id = get_field_id(entref.classnum, field);
|
||||
|
||||
if (id != -1)
|
||||
{
|
||||
stack_isolation _;
|
||||
|
||||
game::VariableValue value{};
|
||||
if (!safe_execution::get_entity_field(entref, id, &value))
|
||||
{
|
||||
throw std::runtime_error("Failed to get value for field '" + field + "'");
|
||||
}
|
||||
|
||||
const auto __ = gsl::finally([value]()
|
||||
{
|
||||
game::RemoveRefToValue(value.type, value.u);
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Add custom fields
|
||||
return get_custom_field(entity, field);
|
||||
}
|
||||
}
|
36
src/client/game/scripting/execution.hpp
Normal file
36
src/client/game/scripting/execution.hpp
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments);
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T = script_value>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments = {});
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call<script_value>(name, arguments).as<T>();
|
||||
}
|
||||
|
||||
script_value exec_ent_thread(const entity& entity, const char* pos, const std::vector<script_value>& arguments);
|
||||
const char* get_function_pos(const std::string& filename, const std::string& function);
|
||||
script_value call_script_function(const entity& entity, const std::string& filename,
|
||||
const std::string& function, const std::vector<script_value>& arguments);
|
||||
|
||||
void clear_entity_fields(const entity& entity);
|
||||
void clear_custom_fields();
|
||||
|
||||
void set_entity_field(const entity& entity, const std::string& field, const script_value& value);
|
||||
script_value get_entity_field(const entity& entity, const std::string& field);
|
||||
|
||||
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments);
|
||||
}
|
1535
src/client/game/scripting/function_tables.cpp
Normal file
1535
src/client/game/scripting/function_tables.cpp
Normal file
File diff suppressed because it is too large
Load Diff
106
src/client/game/scripting/functions.cpp
Normal file
106
src/client/game/scripting/functions.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
#include <std_include.hpp>
|
||||
#include "functions.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, unsigned> lowercase_map(
|
||||
const std::unordered_map<std::string, unsigned>& old_map)
|
||||
{
|
||||
std::unordered_map<std::string, unsigned> new_map{};
|
||||
for (auto& entry : old_map)
|
||||
{
|
||||
new_map[utils::string::to_lower(entry.first)] = entry.second;
|
||||
}
|
||||
|
||||
return new_map;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, unsigned>& get_methods()
|
||||
{
|
||||
static auto methods = lowercase_map(method_map);
|
||||
return methods;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, unsigned>& get_functions()
|
||||
{
|
||||
static auto function = lowercase_map(function_map);
|
||||
return function;
|
||||
}
|
||||
|
||||
int find_function_index(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto target = utils::string::to_lower(name);
|
||||
|
||||
const auto& primary_map = prefer_global
|
||||
? get_functions()
|
||||
: get_methods();
|
||||
const auto& secondary_map = !prefer_global
|
||||
? get_functions()
|
||||
: get_methods();
|
||||
|
||||
auto function_entry = primary_map.find(target);
|
||||
if (function_entry != primary_map.end())
|
||||
{
|
||||
return function_entry->second;
|
||||
}
|
||||
|
||||
function_entry = secondary_map.find(target);
|
||||
if (function_entry != secondary_map.end())
|
||||
{
|
||||
return function_entry->second;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
script_function get_function_by_index(const unsigned index)
|
||||
{
|
||||
static const auto function_table = SELECT_VALUE(0x149668F50, 0x147DD1850);
|
||||
static const auto method_table = SELECT_VALUE(0x14966A670, 0x147DD2F50);
|
||||
|
||||
if (index < 0x2DF)
|
||||
{
|
||||
return reinterpret_cast<script_function*>(function_table)[index];
|
||||
}
|
||||
|
||||
return reinterpret_cast<script_function*>(method_table)[index - 0x8000];
|
||||
}
|
||||
}
|
||||
|
||||
std::string find_token(unsigned int id)
|
||||
{
|
||||
for (const auto& token : token_map)
|
||||
{
|
||||
if (token.second == id)
|
||||
{
|
||||
return token.first;
|
||||
}
|
||||
}
|
||||
|
||||
return utils::string::va("_ID%i", id);
|
||||
}
|
||||
|
||||
unsigned int find_token_id(const std::string& name)
|
||||
{
|
||||
const auto result = token_map.find(name);
|
||||
|
||||
if (result != token_map.end())
|
||||
{
|
||||
return result->second;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto index = find_function_index(name, prefer_global);
|
||||
if (index < 0) return nullptr;
|
||||
|
||||
return get_function_by_index(index);
|
||||
}
|
||||
}
|
16
src/client/game/scripting/functions.hpp
Normal file
16
src/client/game/scripting/functions.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<std::string, unsigned> method_map;
|
||||
extern std::unordered_map<std::string, unsigned> function_map;
|
||||
extern std::unordered_map<std::string, unsigned> token_map;
|
||||
|
||||
using script_function = void(*)(game::scr_entref_t);
|
||||
|
||||
std::string find_token(unsigned int id);
|
||||
unsigned int find_token_id(const std::string& name);
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user