This commit is contained in:
2023-12-08 17:16:22 +01:00
commit ce6b6f4112
169 changed files with 22772 additions and 0 deletions

View File

@ -0,0 +1,49 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/memory.hpp>
namespace asset_restrict {
namespace {
game::XAssetEntry entry_pool[789312];
game::XAssetHeader reallocate_asset_pool(game::XAssetType type,
const int size) {
const auto entry_size =
reinterpret_cast<int (*)()>(game::DB_GetXAssetSizeHandlers[type])();
const game::XAssetHeader pool_entry = {
.data = utils::memory::allocate(entry_size * size)};
game::DB_XAssetPool[type] = pool_entry.data;
game::g_poolSize[type] = size;
return pool_entry;
}
} // namespace
class component final : public component_interface {
public:
void post_load() override {
patch_entry_pool_sp();
reallocate_asset_pool(game::ASSET_TYPE_GAMEWORLD_MP, 1);
}
static void patch_entry_pool_sp() {
// Apply new size
utils::hook::set<std::uint32_t>(0x581740, sizeof(entry_pool) /
sizeof(game::XAssetEntry));
utils::hook::set<game::XAssetEntry*>(0x581721, entry_pool + 1);
utils::hook::set<game::XAssetEntry*>(0x581732, entry_pool + 1);
utils::hook::signature signature(0x411000, 0x200000);
signature.add({"\x60\xB3\xB2\x00", "xxxx", [](char* address) {
utils::hook::set<game::XAssetEntry*>(address, entry_pool);
}});
signature.process();
}
};
} // namespace asset_restrict
REGISTER_COMPONENT(asset_restrict::component)

View File

@ -0,0 +1,64 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "localize_entry.hpp"
#include "map_ents.hpp"
#include "raw_file.hpp"
#include "string_table.hpp"
#include <utils/hook.hpp>
namespace assets {
namespace {
void load_asset(game::XAssetType type, game::XAssetHeader* header) {
if (header) {
switch (type) {
case game::ASSET_TYPE_LOCALIZE_ENTRY:
process_localize_entry(header);
break;
case game::ASSET_TYPE_MAP_ENTS:
process_map_ents(header);
break;
case game::ASSET_TYPE_RAWFILE:
process_raw_file(header);
break;
case game::ASSET_TYPE_STRINGTABLE:
process_string_table(header);
break;
default:
break;
}
}
}
void __declspec(naked) db_add_x_asset_stub() {
__asm {
pushad;
push [esp + 0x20 + 0x8];
push [esp + 0x20 + 0x8];
call load_asset;
add esp, 0x8;
popad;
sub esp, 0x14;
mov eax, dword ptr [esp + 0x1C];
push 0x581EE7;
ret;
}
}
} // namespace
class asset final : public component_interface {
public:
void post_load() override {
// We may modify assets to some extents here or just dump them
utils::hook(0x581EE0, db_add_x_asset_stub, HOOK_JUMP).install()->quick();
utils::hook::nop(0x581EE5, 2);
}
};
} // namespace assets
REGISTER_COMPONENT(assets::asset)

View File

@ -0,0 +1,26 @@
#include <std_include.hpp>
#include "localize_entry.hpp"
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/flags.hpp>
namespace assets {
namespace {
bool is_enabled() { IS_FLAG_ENABLED(dump_localize_entry); }
} // namespace
void process_localize_entry(game::XAssetHeader* header) {
if (!is_enabled()) {
return;
}
auto* localize = header->localize;
const auto filename =
utils::string::va("raw/localizedstrings/{0}", localize->name);
// Simple format, should be fine for now
utils::io::write_file(filename, localize->value);
}
} // namespace assets

View File

@ -0,0 +1,5 @@
#pragma once
namespace assets {
void process_localize_entry(game::XAssetHeader* header);
}

View File

@ -0,0 +1,30 @@
#include <std_include.hpp>
#include "component/filesystem.hpp"
#include "map_ents.hpp"
#include <utils/string.hpp>
namespace assets {
namespace {
std::string map_entities;
void load_map_entities(game::MapEnts* entry) {
const auto file_name = utils::string::va("{0}.ents", entry->name);
const filesystem::file ent_file(file_name, game::FS_THREAD_DATABASE);
// Load ent file from raw if it exists
if (ent_file.exists()) {
map_entities = ent_file.get_buffer();
entry->entityString = map_entities.data();
entry->numEntityChars = static_cast<int>(map_entities.size()) + 1;
}
}
} // namespace
void process_map_ents(game::XAssetHeader* header) {
auto* map_ents = header->mapEnts;
load_map_entities(map_ents);
}
} // namespace assets

View File

@ -0,0 +1,5 @@
#pragma once
namespace assets {
void process_map_ents(game::XAssetHeader* header);
}

View File

@ -0,0 +1,181 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "raw_file.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/flags.hpp>
#include <zlib.h>
namespace assets {
namespace {
utils::hook::detour db_read_raw_file_hook;
utils::hook::detour com_load_info_string_hook;
char* db_read_raw_file_stub(const char* filename, char* buf, int size) {
auto file_handle = 0;
const auto file_size = game::FS_FOpenFileRead(filename, &file_handle);
if (file_handle) {
if ((file_size + 1) <= size) {
game::FS_Read(buf, file_size, file_handle);
buf[file_size] = '\0';
game::FS_FCloseFile(file_handle);
return buf;
}
game::FS_FCloseFile(file_handle);
game::Com_PrintError(
game::CON_CHANNEL_ERROR,
"Ignoring raw file '%s' as it exceeds buffer size %i > %i\n", filename,
file_size, size);
}
auto* rawfile =
game::DB_FindXAssetHeader(game::ASSET_TYPE_RAWFILE, filename).rawfile;
if (game::DB_IsXAssetDefault(game::ASSET_TYPE_RAWFILE, filename)) {
return nullptr;
}
game::DB_GetRawBuffer(rawfile, buf, size);
return buf;
}
const char* com_load_info_string_fast_file(const char* file_name,
const char* file_desc,
const char* ident,
char* load_buffer) {
const static DWORD Com_LoadInfoString_FastFile_t = 0x602FA0;
const char* result{};
__asm {
pushad;
mov ebx, load_buffer;
mov edi, file_name;
push ident;
push file_desc;
call Com_LoadInfoString_FastFile_t;
add esp, 0x8;
mov result, eax;
popad;
}
return result;
}
const char* com_load_info_string_load_obj(const char* file_name,
const char* file_desc,
const char* ident,
char* load_buffer) {
int file_handle;
const auto file_len =
game::FS_FOpenFileByMode(file_name, &file_handle, game::FS_READ);
if (file_len < 0) {
game::Com_DPrintf(game::CON_CHANNEL_SYSTEM,
"Could not load %s [%s] as rawfile\n", file_desc,
file_name);
return nullptr;
}
const auto ident_len = static_cast<int>(std::strlen(ident));
game::FS_Read(load_buffer, ident_len, file_handle);
load_buffer[ident_len] = '\0';
if (std::strncmp(load_buffer, ident, ident_len) != 0) {
game::Com_Error(game::ERR_DROP,
"\x15"
"File [%s] is not a %s\n",
file_name, file_desc);
return nullptr;
}
if ((file_len - ident_len) >= 0x4000) {
game::Com_Error(game::ERR_DROP,
"\x15"
"File [%s] is too long of a %s to parse\n",
file_name, file_desc);
return nullptr;
}
game::FS_Read(load_buffer, file_len - ident_len, file_handle);
load_buffer[file_len - ident_len] = '\0';
game::FS_FCloseFile(file_handle);
return load_buffer;
}
const char* com_load_info_string_stub(const char* file_name,
const char* file_desc, const char* ident,
char* load_buffer) {
const auto* buffer =
com_load_info_string_load_obj(file_name, file_desc, ident, load_buffer);
if (!buffer) {
buffer = com_load_info_string_fast_file(file_name, file_desc, ident,
load_buffer);
}
if (!game::Info_Validate(buffer)) {
game::Com_Error(game::ERR_DROP,
"\x15"
"File [%s] is not a valid %s\n",
file_name, file_desc);
return nullptr;
}
return buffer;
}
bool is_enabled() { IS_FLAG_ENABLED(dump_raw_file); }
} // namespace
void process_raw_file(game::XAssetHeader* header) {
if (!is_enabled()) {
return;
}
const auto* raw_file = header->rawfile;
const auto filename = utils::string::va("raw/{0}", raw_file->name);
if (raw_file->compressedLen > 0) {
std::vector<std::uint8_t> uncompressed;
uncompressed.resize(raw_file->len);
if (uncompress(uncompressed.data(), (uLongf*)&raw_file->len,
reinterpret_cast<const Bytef*>(raw_file->buffer),
raw_file->compressedLen) == Z_OK) {
std::string data;
data.assign(uncompressed.begin(), uncompressed.end());
utils::io::write_file(filename, data);
}
return;
}
// If uncompressed just dump it
utils::io::write_file(filename, raw_file->buffer);
}
class raw_file final : public component_interface {
public:
void post_load() override {
// Remove fs_game check for moddable raw files
// allows non-fs_game to modify raw files
utils::hook::nop(0x612932, 2);
db_read_raw_file_hook.create(0x46DA60, &db_read_raw_file_stub);
com_load_info_string_hook.create(0x42DB20, &com_load_info_string_stub);
}
void pre_destroy() override { db_read_raw_file_hook.clear(); }
};
} // namespace assets
REGISTER_COMPONENT(assets::raw_file)

View File

@ -0,0 +1,5 @@
#pragma once
namespace assets {
void process_raw_file(game::XAssetHeader* header);
}

View File

@ -0,0 +1,46 @@
#include <std_include.hpp>
#include "string_table.hpp"
#include <utils/flags.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
namespace assets {
namespace {
void dump_string_table(game::XAssetHeader* header) {
const auto* string_table = header->stringTable;
const auto filename = utils::string::va("raw/{0}", string_table->name);
std::string csv;
const auto rows = string_table->rowCount;
const auto columns = string_table->columnCount;
for (auto x = 0; x < rows; ++x) {
for (auto y = 0; y < columns; ++y) {
const char* cell = string_table->values[(x * columns) + y].string;
csv += cell;
if (y + 1 < columns) {
csv += ",";
}
}
if (x + 1 < rows) {
csv += "\n";
}
}
utils::io::write_file(filename, csv);
}
bool is_enabled() { IS_FLAG_ENABLED(dump_string_table); }
} // namespace
void process_string_table(game::XAssetHeader* header) {
if (is_enabled()) {
dump_string_table(header);
}
}
} // namespace assets

View File

@ -0,0 +1,5 @@
#pragma once
namespace assets {
void process_string_table(game::XAssetHeader* header);
}

View File

@ -0,0 +1,84 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/cryptography.hpp>
#include <utils/hook.hpp>
#include <utils/smbios.hpp>
#include <utils/string.hpp>
#include "auth.hpp"
namespace auth {
namespace {
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 = "Alter-Ware-IW4-SP-Auth";
DATA_BLOB data_in{}, data_out{};
data_in.pbData = reinterpret_cast<std::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<std::uint32_t>(data_out.cbData, 52);
std::string result(reinterpret_cast<char*>(data_out.pbData), size);
LocalFree(data_out.pbData);
return result;
}
std::string get_hdd_serial() {
DWORD serial{};
if (!GetVolumeInformationA("C:\\", nullptr, 0, &serial, nullptr, nullptr,
nullptr, 0)) {
return {};
}
return utils::string::va("{0:08X}", serial);
}
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;
}
} // namespace
std::uint64_t get_guid() { return get_key().get_hash(); }
class component final : public component_interface {
public:
void post_load() override {
// Patch Steam_GetClientIDAsXUID
utils::hook::set<std::uint32_t>(0x4911B0, 0xC301B0);
}
};
} // namespace auth
REGISTER_COMPONENT(auth::component)

View File

@ -0,0 +1,5 @@
#pragma once
namespace auth {
std::uint64_t get_guid();
}

View File

@ -0,0 +1,254 @@
#include <std_include.hpp>
#include "l_precomp.hpp"
#include "l_script.hpp"
namespace pc {
constexpr auto DEFINEHASHSIZE = 1024;
constexpr auto MAX_SOURCEFILES = 64;
game::define_s* globaldefines;
void free_token(game::token_s* token) {
game::FreeMemory(token);
--*game::numtokens;
}
game::define_s* define_from_string(const char* string) {
game::source_s src;
auto* script = load_script_memory(
string, static_cast<int>(std::strlen(string)), "*extern");
// create a new source
std::memset(&src, 0, sizeof(game::source_s));
strncpy_s(src.filename, "*extern", _TRUNCATE);
src.scriptstack = script;
src.definehash = static_cast<game::define_s**>(
game::GetClearedMemory(DEFINEHASHSIZE * sizeof(game::define_s*)));
// create a define from the source
auto res = game::PC_Directive_define(&src);
// free any tokens if left
for (auto* t = src.tokens; t; t = src.tokens) {
src.tokens = src.tokens->next;
free_token(t);
}
game::define_s* def = nullptr;
for (auto i = 0; i < DEFINEHASHSIZE; ++i) {
if (src.definehash[i]) {
def = src.definehash[i];
break;
}
}
game::FreeMemory(src.definehash);
free_script(script);
// if the define was created successfully
if (res > 0) {
return def;
}
// free the define is created
if (src.defines) {
game::PC_FreeDefine(def);
}
return nullptr;
}
int name_hash(char* name) {
auto hash = 0;
for (auto i = 0; name[i] != '\0'; ++i) {
hash += name[i] * (119 + i);
}
hash = (hash ^ (hash >> 10) ^ (hash >> 20)) & (DEFINEHASHSIZE - 1);
return hash;
}
void add_define_to_hash(game::define_s* define, game::define_s** definehash) {
auto hash = name_hash(define->name);
define->hashnext = definehash[hash];
definehash[hash] = define;
}
int add_define(game::source_s* source, const char* string) {
auto* define = define_from_string(string);
if (!define) {
return 0;
}
add_define_to_hash(define, source->definehash);
return true;
}
game::define_s* copy_define([[maybe_unused]] game::source_s* source,
game::define_s* define) {
game::token_s *token, *newtoken, *lasttoken;
auto* newdefine = static_cast<game::define_s*>(
game::GetMemory(sizeof(game::define_s) + std::strlen(define->name) + 1));
// copy the define name
newdefine->name = (char*)newdefine + sizeof(game::define_s);
std::memcpy(newdefine->name, define->name, std::strlen(define->name) + 1);
newdefine->flags = define->flags;
newdefine->builtin = define->builtin;
newdefine->numparms = define->numparms;
// the define is not linked
newdefine->next = nullptr;
newdefine->hashnext = nullptr;
// copy the define tokens
newdefine->tokens = nullptr;
for (lasttoken = nullptr, token = define->tokens; token;
token = token->next) {
newtoken = game::PC_CopyToken(token);
newtoken->next = nullptr;
if (lasttoken) {
lasttoken->next = newtoken;
} else {
newdefine->tokens = newtoken;
}
lasttoken = newtoken;
}
// copy the define parameters
newdefine->parms = nullptr;
for (lasttoken = nullptr, token = define->parms; token; token = token->next) {
newtoken = game::PC_CopyToken(token);
newtoken->next = nullptr;
if (lasttoken) {
lasttoken->next = newtoken;
} else {
newdefine->parms = newtoken;
}
lasttoken = newtoken;
}
return newdefine;
}
void add_global_defines_to_source(game::source_s* source) {
for (auto* define = globaldefines; define; define = define->next) {
auto* newdefine = copy_define(source, define);
add_define_to_hash(newdefine, source->definehash);
}
}
game::source_s* load_source_file(const char* filename) {
auto* script = load_script_file(filename);
if (!script) {
return nullptr;
}
script->next = nullptr;
auto* source =
static_cast<game::source_s*>(game::GetMemory(sizeof(game::source_s)));
std::memset(source, 0, sizeof(game::source_s));
strncpy_s(source->filename, filename, _TRUNCATE);
source->scriptstack = script;
source->tokens = nullptr;
source->defines = nullptr;
source->indentstack = nullptr;
source->skip = 0;
source->definehash = static_cast<game::define_s**>(
game::GetClearedMemory(DEFINEHASHSIZE * sizeof(game::define_s*)));
add_global_defines_to_source(source);
return source;
}
int load_source_handle(const char* filename, const char** builtin_defines) {
int i;
for (i = 1; i < MAX_SOURCEFILES; ++i) {
if (!game::sourceFiles[i]) {
break;
}
}
if (i >= MAX_SOURCEFILES) {
return 0;
}
auto* source = load_source_file(filename);
if (!source) {
return 0;
}
if (builtin_defines) {
for (auto define_iter = 0; builtin_defines[define_iter]; ++define_iter) {
add_define(source, builtin_defines[define_iter]);
}
}
game::sourceFiles[i] = source;
return i;
}
void free_source(game::source_s* source) {
// free all the scripts
while (source->scriptstack) {
auto* script = source->scriptstack;
source->scriptstack = source->scriptstack->next;
free_script(script);
}
// free all the tokens
while (source->tokens) {
auto* token = source->tokens;
source->tokens = source->tokens->next;
free_token(token);
}
for (auto i = 0; i < DEFINEHASHSIZE; ++i) {
while (source->definehash[i]) {
auto* define = source->definehash[i];
source->definehash[i] = source->definehash[i]->hashnext;
game::PC_FreeDefine(define);
}
}
// free all indents
while (source->indentstack) {
auto* indent = source->indentstack;
source->indentstack = source->indentstack->next;
game::FreeMemory(indent);
}
if (source->definehash) {
game::FreeMemory(source->definehash);
}
// free the source itself
game::FreeMemory(source);
}
int free_source_handle(int handle) {
if (handle < 1 || handle >= MAX_SOURCEFILES)
return 0;
if (!game::sourceFiles[handle])
return 0;
free_source(game::sourceFiles[handle]);
game::sourceFiles[handle] = nullptr;
return 1;
}
} // namespace pc

View File

@ -0,0 +1,6 @@
#pragma once
namespace pc {
int load_source_handle(const char* filename, const char** builtin_defines);
int free_source_handle(int handle);
} // namespace pc

View File

@ -0,0 +1,204 @@
#include <std_include.hpp>
#include "l_script.hpp"
namespace pc {
// clang-format off
game::punctuation_s default_punctuations[] =
{
{ ">>=", P_RSHIFT_ASSIGN, nullptr },
{ "<<=", P_LSHIFT_ASSIGN, nullptr },
{ "...", P_PARMS, nullptr },
// define merge operator
{ "##", P_PRECOMPMERGE, nullptr },
// logic operators
{ "&&", P_LOGIC_AND, nullptr },
{ "||", P_LOGIC_OR, nullptr },
{ ">=", P_LOGIC_GEQ, nullptr },
{ "<=", P_LOGIC_LEQ, nullptr },
{ "==", P_LOGIC_EQ, nullptr },
{ "!=", P_LOGIC_UNEQ, nullptr },
// arithmatic operators
{ "*=", P_MUL_ASSIGN, nullptr },
{ "/=",P_DIV_ASSIGN, nullptr },
{ "%=", P_MOD_ASSIGN, nullptr },
{ "+=", P_ADD_ASSIGN, nullptr },
{ "-=", P_SUB_ASSIGN,nullptr },
{ "++", P_INC, nullptr },
{ "--", P_DEC, nullptr },
// binary operators
{ "&=", P_BIN_AND_ASSIGN, nullptr },
{ "|=", P_BIN_OR_ASSIGN, nullptr },
{ "^=", P_BIN_XOR_ASSIGN, nullptr },
{ ">>", P_RSHIFT, nullptr },
{ "<<", P_LSHIFT, nullptr },
// reference operators
{ "->", P_POINTERREF, nullptr },
// C++
{ "::", P_CPP1, nullptr },
{ ".*", P_CPP2, nullptr },
// arithmatic operators
{ "*", P_MUL, nullptr },
{ "/", P_DIV,nullptr },
{ "%", P_MOD, nullptr },
{ "+", P_ADD, nullptr },
{ "-", P_SUB, nullptr },
{ "=", P_ASSIGN, nullptr },
// binary operators
{ "&", P_BIN_AND, nullptr },
{ "|", P_BIN_OR, nullptr },
{ "^", P_BIN_XOR, nullptr },
{ "~", P_BIN_NOT, nullptr },
// logic operators
{ "!", P_LOGIC_NOT, nullptr },
{ ">", P_LOGIC_GREATER, nullptr },
{ "<", P_LOGIC_LESS, nullptr },
// reference operator
{ ".", P_REF, nullptr },
// seperators
{ ",", P_COMMA, nullptr },
{ ";", P_SEMICOLON, nullptr },
// label indication
{ ":", P_COLON, nullptr },
// if statement
{ "?", P_QUESTIONMARK, nullptr },
// embracements
{ "(", P_PARENTHESESOPEN, nullptr },
{ ")", P_PARENTHESESCLOSE, nullptr },
{ "{", P_BRACEOPEN, nullptr },
{ "}", P_BRACECLOSE, nullptr },
{ "[", P_SQBRACKETOPEN, nullptr },
{ "]", P_SQBRACKETCLOSE, nullptr },
//
{ "\\", P_BACKSLASH, nullptr },
// precompiler operator
{ "#", P_PRECOMP, nullptr },
{ "$", P_DOLLAR, nullptr },
{ nullptr, 0, nullptr },
};
// clang-format on
void create_punctuation_table(game::script_s* script,
game::punctuation_s* punctuations) {
game::punctuation_s* p;
// get memory for the table
if (!script->punctuationtable) {
script->punctuationtable = static_cast<game::punctuation_s**>(
game::GetMemory(256 * sizeof(game::punctuation_s*)));
}
std::memset(script->punctuationtable, 0, 256 * sizeof(game::punctuation_s*));
// add the punctuations in the list to the punctuation table
for (auto i = 0; punctuations[i].p; ++i) {
auto* newp = &punctuations[i];
game::punctuation_s* lastp = nullptr;
// sort the punctuations in this table entry on length (longer punctuations
// first)
for (p = script->punctuationtable[(unsigned int)newp->p[0]]; p;
p = p->next) {
if (std::strlen(p->p) < std::strlen(newp->p)) {
newp->next = p;
if (lastp) {
lastp->next = newp;
} else {
script->punctuationtable[(unsigned int)newp->p[0]] = newp;
}
break;
}
lastp = p;
}
if (!p) {
newp->next = nullptr;
if (lastp) {
lastp->next = newp;
} else {
script->punctuationtable[(unsigned int)newp->p[0]] = newp;
}
}
}
}
void set_script_punctuations(game::script_s* script) {
create_punctuation_table(script, default_punctuations);
script->punctuations = default_punctuations;
}
game::script_s* load_script_file(const char* filename) {
int fp;
char pathname[game::MAX_QPATH];
sprintf_s(pathname, "%s", filename);
const auto length = game::FS_FOpenFileRead(pathname, &fp);
if (!fp) {
return nullptr;
}
auto* buffer = game::GetClearedMemory(sizeof(game::script_s) + length + 1);
auto* script = static_cast<game::script_s*>(buffer);
strncpy_s(script->filename, filename, _TRUNCATE);
script->buffer = static_cast<char*>(buffer) + sizeof(game::script_s);
script->buffer[length] = '\0';
script->length = length;
// pointer in script buffer
script->script_p = script->buffer;
// pointer in script buffer before reading token
script->lastscript_p = script->buffer;
// pointer to end of script buffer
script->end_p = &script->buffer[length];
// set if there's a token available in script->token
script->tokenavailable = false;
script->line = 1;
script->lastline = 1;
set_script_punctuations(script);
game::FS_Read(script->buffer, length, fp);
game::FS_FCloseFile(fp);
script->length = game::Com_Compress(script->buffer);
return script;
}
game::script_s* load_script_memory(const char* ptr, int length,
const char* name) {
auto* buffer = game::GetClearedMemory(sizeof(game::script_s) + length + 1);
auto* script = static_cast<game::script_s*>(buffer);
strncpy_s(script->filename, name, _TRUNCATE);
script->buffer = static_cast<char*>(buffer) + sizeof(game::script_s);
script->buffer[length] = '\0';
script->length = length;
// pointer in script buffer
script->script_p = script->buffer;
// pointer in script buffer before reading token
script->lastscript_p = script->buffer;
// pointer to end of script buffer
script->end_p = &script->buffer[length];
// set if there's a token available in script->token
script->tokenavailable = false;
script->line = 1;
script->lastline = 1;
set_script_punctuations(script);
std::memcpy(script->buffer, ptr, length);
return script;
}
void free_script(game::script_s* script) {
if (script->punctuationtable)
game::FreeMemory(script->punctuationtable);
game::FreeMemory(script);
}
} // namespace pc

View File

@ -0,0 +1,70 @@
#pragma once
#define P_RSHIFT_ASSIGN 1
#define P_LSHIFT_ASSIGN 2
#define P_PARMS 3
#define P_PRECOMPMERGE 4
#define P_LOGIC_AND 5
#define P_LOGIC_OR 6
#define P_LOGIC_GEQ 7
#define P_LOGIC_LEQ 8
#define P_LOGIC_EQ 9
#define P_LOGIC_UNEQ 10
#define P_MUL_ASSIGN 11
#define P_DIV_ASSIGN 12
#define P_MOD_ASSIGN 13
#define P_ADD_ASSIGN 14
#define P_SUB_ASSIGN 15
#define P_INC 16
#define P_DEC 17
#define P_BIN_AND_ASSIGN 18
#define P_BIN_OR_ASSIGN 19
#define P_BIN_XOR_ASSIGN 20
#define P_RSHIFT 21
#define P_LSHIFT 22
#define P_POINTERREF 23
#define P_CPP1 24
#define P_CPP2 25
#define P_MUL 26
#define P_DIV 27
#define P_MOD 28
#define P_ADD 29
#define P_SUB 30
#define P_ASSIGN 31
#define P_BIN_AND 32
#define P_BIN_OR 33
#define P_BIN_XOR 34
#define P_BIN_NOT 35
#define P_LOGIC_NOT 36
#define P_LOGIC_GREATER 37
#define P_LOGIC_LESS 38
#define P_REF 39
#define P_COMMA 40
#define P_SEMICOLON 41
#define P_COLON 42
#define P_QUESTIONMARK 43
#define P_PARENTHESESOPEN 44
#define P_PARENTHESESCLOSE 45
#define P_BRACEOPEN 46
#define P_BRACECLOSE 47
#define P_SQBRACKETOPEN 48
#define P_SQBRACKETCLOSE 49
#define P_BACKSLASH 50
#define P_PRECOMP 51
#define P_DOLLAR 52
namespace pc {
game::script_s* load_script_file(const char* filename);
game::script_s* load_script_memory(const char* ptr, int length,
const char* name);
void free_script(game::script_s* script);
} // namespace pc

View File

@ -0,0 +1,123 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <version.h>
namespace branding {
namespace {
#ifdef _DEBUG
constexpr auto* BUILD_TYPE = "IW4_DEV SP";
#else
constexpr auto* BUILD_TYPE = "IW4 SP";
#endif
constexpr const char* get_build_number() {
return SHORTVERSION " latest " __DATE__ " " __TIME__;
}
const char* get_version_string() {
const auto* result = utils::string::va(
"{0} {1} build {2} {3}", BUILD_TYPE, "(Alpha)", get_build_number(),
reinterpret_cast<const char*>(0x6A1574));
return result;
}
void dvar_set_version_string(const game::dvar_t* dvar, const char* /*value*/) {
const auto* result = get_version_string();
utils::hook::invoke<void>(0x480E70, dvar, result);
}
void cg_draw_version() {
assert(game::ScrPlace_IsFullScreenActive());
// Default values
constexpr auto font_scale = 0.25f;
constexpr auto max_chars = std::numeric_limits<int>::max();
// Default colours
constexpr float shadow_color[] = {0.0f, 0.0f, 0.0f, 0.69f};
constexpr float color[] = {0.4f, 0.69f, 1.0f, 0.69f};
auto* const placement = game::ScrPlace_GetUnsafeFullPlacement();
auto* const font = game::UI_GetFontHandle(placement, 0, 0.583f);
const auto width = game::UI_TextWidth((*dvars::version)->current.string, 0,
font, font_scale);
const auto height = game::UI_TextHeight(font, font_scale);
// clang-format off
game::UI_DrawText(placement, (*dvars::version)->current.string, max_chars,
font,
1.0f - (dvars::cg_drawVersionX->current.value +
static_cast<float>(width)),
1.0f - (dvars::cg_drawVersionY->current.value +
static_cast<float>(height)),
3, 3, font_scale, shadow_color, 0);
game::UI_DrawText(placement, (*dvars::version)->current.string, max_chars,
font,
(0.0f - static_cast<float>(width)) -
dvars::cg_drawVersionX->current.value,
(0.0f - static_cast<float>(height)) -
dvars::cg_drawVersionY->current.value,
3, 3, font_scale, color, 0);
// clang-format on
}
void cg_draw_full_screen_debug_overlays_stub(int local_client_num) {
assert(game::ScrPlace_IsFullScreenActive());
if (dvars::cg_drawVersion->current.enabled) {
cg_draw_version();
}
utils::hook::invoke<void>(0x44BD00, local_client_num);
}
} // namespace
class component final : public component_interface {
public:
void post_load() override {
utils::hook(0x46F570, get_build_number, HOOK_JUMP).install()->quick();
utils::hook(0x60429A, dvar_set_version_string, HOOK_CALL)
.install()
->quick();
utils::hook::set<const char*>(0x446A48, "iw4-sp: Console");
utils::hook::set<const char*>(0x50C110, "iw4-sp: Game");
utils::hook::set<const char*>(0x579364, "iw4-sp: " SHORTVERSION "> ");
// Com_Init_Try_Block_Function
utils::hook::set<const char*>(0x604004, BUILD_TYPE);
utils::hook::set<const char*>(0x603FFF, SHORTVERSION);
utils::hook::set<const char*>(0x603FF5, __DATE__);
register_branding_dvars();
utils::hook(0x57DAFF, cg_draw_full_screen_debug_overlays_stub, HOOK_CALL)
.install() // hook*
->quick();
}
static void register_branding_dvars() {
#ifdef _DEBUG
constexpr auto value = true;
#else
constexpr auto value = false;
#endif
dvars::cg_drawVersion = game::Dvar_RegisterBool(
"cg_drawVersion", value, game::DVAR_NONE, "Draw the game version");
dvars::cg_drawVersionX = game::Dvar_RegisterFloat(
"cg_drawVersionX", 50.0f, 0.0f, 512.0f, game::DVAR_NONE,
"X offset for the version string");
dvars::cg_drawVersionY = game::Dvar_RegisterFloat(
"cg_drawVersionY", 18.0f, 0.0f, 512.0f, game::DVAR_NONE,
"Y offset for the version string");
}
};
} // namespace branding
REGISTER_COMPONENT(branding::component)

View File

@ -0,0 +1,39 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
namespace ceg {
class component final : public component_interface {
public:
void post_load() override {
// Some unnecessary CEG functions.
// Important functions are patched in the exe already
utils::hook::set<std::uint32_t>(0x44AD80, 0xC301B0);
utils::hook::set<std::uint32_t>(0x476A20, 0xC301B0);
utils::hook::set<std::uint32_t>(0x4E3B90, 0xC301B0);
utils::hook::set<std::uint32_t>(0x4E3B90, 0xC301B0);
utils::hook::set<std::uint32_t>(0x411160, 0xC301B0);
utils::hook::set<std::uint32_t>(0x41E390, 0xC301B0);
utils::hook::set<std::uint32_t>(0x47C2E0, 0xC301B0);
utils::hook::set<std::uint32_t>(0x4EEA90, 0xC301B0);
utils::hook::set<std::uint32_t>(0x40E380, 0xC301B0);
utils::hook::set<std::uint32_t>(0x4E45C0, 0xC301B0);
// Killer caller
utils::hook::set<std::uint8_t>(0x43F320, 0xC3);
utils::hook::set<std::uint8_t>(0x458ED0, 0xC3);
utils::hook::set<std::uint8_t>(0x47A140, 0xC3);
utils::hook::nop(0x411166, 9);
// Remove 'Steam Start' check
utils::hook::nop(0x43FAD5, 12);
// Shutdown
utils::hook::set<std::uint8_t>(0x4619B0, 0xC3);
}
};
} // namespace ceg
REGISTER_COMPONENT(ceg::component)

View File

@ -0,0 +1,152 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/string.hpp>
#include <utils/memory.hpp>
#include <utils/hook.hpp>
#include "command.hpp"
namespace command {
namespace {
utils::hook::detour client_command_hook;
std::unordered_map<std::string, std::function<void(params&)>> handlers;
std::unordered_map<std::string,
std::function<void(game::gentity_s*, params_sv&)>>
handlers_sv;
void main_handler() {
params params;
const auto command = utils::string::to_lower(params[0]);
if (handlers.contains(command)) {
handlers[command](params);
}
}
void client_command_stub(int client_num, const char* s) {
auto* entity = &game::g_entities[client_num];
params_sv params;
const auto command = utils::string::to_lower(params[0]);
const auto got = handlers_sv.find(command);
if (got != handlers_sv.end()) {
got->second(entity, params);
return;
}
client_command_hook.invoke<void>(client_num, s);
}
} // namespace
params::params() : nesting_(game::cmd_args->nesting) {
assert(game::cmd_args->nesting < game::CMD_MAX_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) {
assert(game::sv_cmd_args->nesting < game::CMD_MAX_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)(), const int is_key) {
assert(is_key == 0 || is_key == 1);
game::Cmd_AddCommand(
name, callback,
utils::memory::get_allocator()->allocate<game::cmd_function_s>(), is_key);
}
void add(const char* name, const std::function<void(const params&)>& callback) {
const auto command = utils::string::to_lower(name);
if (!handlers.contains(command)) {
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(game::gentity_s*, const params_sv&)> callback) {
const auto command = utils::string::to_lower(name);
if (!handlers_sv.contains(command)) {
handlers_sv[command] = 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());
}
}
class component final : public component_interface {
public:
static_assert(sizeof(game::cmd_function_s) == 0x18);
static_assert(offsetof(game::gentity_s, client) == 0x108);
void post_load() override {
client_command_hook.create(0x4DF4B0, client_command_stub);
}
void pre_destroy() override { client_command_hook.clear(); }
};
} // namespace command
REGISTER_COMPONENT(command::component)

View File

@ -0,0 +1,40 @@
#pragma once
namespace command {
class params {
public:
params();
[[nodiscard]] int size() const;
[[nodiscard]] const char* get(int index) const;
[[nodiscard]] 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();
[[nodiscard]] int size() const;
[[nodiscard]] const char* get(int index) const;
[[nodiscard]] std::string join(int index) const;
const char* operator[](const int index) const { return this->get(index); }
private:
int nesting_;
};
void add_raw(const char* name, void (*callback)(), int is_key = 0);
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(game::gentity_s*, const params_sv&)> callback);
void execute(std::string command, bool sync = false);
} // namespace command

View File

@ -0,0 +1,71 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include "dvar.hpp"
namespace console {
namespace {
bool is_command;
void con_toggle_console() {
game::Field_Clear(game::g_consoleField);
if (game::conDrawInputGlob->matchIndex >= 0 &&
game::conDrawInputGlob->autoCompleteChoice[0] != '\0') {
game::conDrawInputGlob->matchIndex = -1;
game::conDrawInputGlob->autoCompleteChoice[0] = '\0';
}
game::g_consoleField->widthInPixels = *game::g_console_field_width;
game::g_consoleField->charHeight = *game::g_console_char_height;
game::g_consoleField->fixedSize = 1;
game::con->outputVisible = false;
// clientUIActives[0].keyCatchers
utils::hook::set<std::uint32_t>(0x929140,
*reinterpret_cast<std::uint32_t*>(0x929140) ^
game::KEYCATCH_CONSOLE);
}
bool con_is_dvar_command_stub(const char* cmd) {
is_command = game::Con_IsDvarCommand(cmd);
return is_command;
}
void cmd_for_each_stub(void (*callback)(const char* str)) {
if (!is_command) {
utils::hook::invoke<void>(0x4B7000, callback);
}
}
} // namespace
class component final : public component_interface {
public:
static_assert(sizeof(game::field_t) == 0x118);
static_assert(sizeof(game::ConDrawInputGlob) == 0x64);
void post_start() override {
// Prevents console from opening
dvar::override::register_bool("monkeytoy", false, game::DVAR_NONE);
}
void post_load() override {
utils::hook(0x44317E, con_toggle_console, HOOK_CALL)
.install()
->quick(); // CL_KeyEvent
utils::hook(0x442E8E, con_toggle_console, HOOK_JUMP)
.install()
->quick(); // CL_KeyEvent
// Con_DrawInput
utils::hook(0x57946D, con_is_dvar_command_stub, HOOK_CALL)
.install()
->quick();
utils::hook(0x57951C, cmd_for_each_stub, HOOK_CALL).install()->quick();
}
};
} // namespace console
REGISTER_COMPONENT(console::component)

View File

@ -0,0 +1,129 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/dvars.hpp"
#include "game/engine/scoped_critical_section.hpp"
#include "game/engine/large_local.hpp"
#include <utils/hook.hpp>
#include "command.hpp"
#include "scheduler.hpp"
namespace debug {
namespace {
void com_assert_f() { assert("a" && false); }
void com_bug_f(const command::params& params) {
char new_file_name[0x105]{};
char to_ospath[MAX_PATH]{};
char from_ospath[MAX_PATH]{};
const char* bug;
if (!*game::logfile) {
game::Com_PrintError(game::CON_CHANNEL_ERROR,
"CopyFile failed: logfile wasn't opened\n");
}
if (params.size() == 2) {
bug = params.get(1);
} else {
assert(dvars::bug_name);
bug = dvars::bug_name->current.string;
}
sprintf_s(new_file_name, "%s_%s.log", bug, game::Live_GetLocalClientName(0));
game::engine::scoped_critical_section _(game::CRITSECT_CONSOLE,
game::SCOPED_CRITSECT_NORMAL);
if (*game::logfile) {
game::FS_FCloseFile(*game::logfile);
*game::logfile = 0;
}
game::FS_BuildOSPath(std::filesystem::current_path().string().data(), "",
"console.log", from_ospath);
game::FS_BuildOSPath(std::filesystem::current_path().string().data(), "",
new_file_name, to_ospath);
const auto result = CopyFileA(from_ospath, to_ospath, 0);
game::Com_OpenLogFile();
if (!result) {
game::Com_PrintError(game::CON_CHANNEL_ERROR, "CopyFile failed(%d) %s %s\n",
GetLastError(), "console.log", new_file_name);
}
}
void com_bug_name_inc_f() {
char buf[260]{};
if (std::strlen(dvars::bug_name->current.string) < 4) {
game::Dvar_SetString(dvars::bug_name, "bug0");
return;
}
if (std::strncmp(dvars::bug_name->current.string, "bug", 3) != 0) {
game::Dvar_SetString(dvars::bug_name, "bug0");
return;
}
const auto n = std::strtol(dvars::bug_name->current.string + 3, nullptr, 10);
sprintf_s(buf, "bug%d", n + 1);
game::Dvar_SetString(dvars::bug_name, buf);
}
void g_print_fast_file_errors(const char* fastfile) {
assert(fastfile);
game::engine::large_local rawfile_buf_large_local(0x18000);
auto* rawfile_buf = static_cast<char*>(rawfile_buf_large_local.get_buf());
auto* text = game::DB_ReadRawFile(fastfile, rawfile_buf, 0x18000);
assert(text);
if (*text) {
game::Com_PrintError(game::CON_CHANNEL_ERROR,
"There were errors when building fast file '%s'\n",
fastfile);
game::Com_PrintError(game::CON_CHANNEL_ERROR, "%s", text);
}
}
void g_init_game_stub() {
utils::hook::invoke<void>(0x4D6410);
g_print_fast_file_errors("code_post_gfx");
g_print_fast_file_errors("common");
g_print_fast_file_errors((*dvars::sv_mapname)->current.string);
}
} // namespace
class component final : public component_interface {
public:
static_assert(sizeof(RTL_CRITICAL_SECTION) == 0x18);
void post_load() override {
utils::hook::set<void (*)()>(0x604203, com_assert_f);
scheduler::once(
[] {
dvars::bug_name = game::Dvar_RegisterString(
"bug_name", "bug0", game::DVAR_CHEAT | game::DVAR_CODINFO,
"Name appended to the copied console log");
},
scheduler::pipeline::main);
command::add("bug", com_bug_f);
command::add("bug_name_inc", com_bug_name_inc_f);
#ifdef _DEBUG
utils::hook(0x4C79DF, g_init_game_stub, HOOK_CALL)
.install() // hook*
->quick(); // Scr_FreeEntityList
#endif
}
};
} // namespace debug
REGISTER_COMPONENT(debug::component)

View File

@ -0,0 +1,76 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <discord_rpc.h>
#include "scheduler.hpp"
using namespace std::literals;
namespace discord {
namespace {
DiscordRichPresence discord_presence;
void update_discord() {
Discord_RunCallbacks();
if (!discord_presence.startTimestamp) {
discord_presence.startTimestamp =
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
}
Discord_UpdatePresence(&discord_presence);
}
} // namespace
class component final : public component_interface {
public:
void post_load() override {
DiscordEventHandlers handlers;
ZeroMemory(&handlers, sizeof(handlers));
handlers.ready = ready;
handlers.errored = errored;
handlers.disconnected = errored;
handlers.joinGame = nullptr;
handlers.spectateGame = nullptr;
handlers.joinRequest = nullptr;
Discord_Initialize("978049907585863710", &handlers, 1, nullptr);
scheduler::once(
[] {
scheduler::once(update_discord, scheduler::pipeline::async);
scheduler::loop(update_discord, scheduler::pipeline::async, 15s);
},
scheduler::pipeline::main);
initialized_ = true;
}
void pre_destroy() override {
if (initialized_) {
Discord_Shutdown();
}
}
private:
bool initialized_ = false;
static void ready(const DiscordUser* request) {
ZeroMemory(&discord_presence, sizeof(discord_presence));
discord_presence.instance = 1;
discord_presence.startTimestamp = 0;
printf("Discord: Ready\n");
Discord_UpdatePresence(&discord_presence);
}
static void errored(const int error_code, const char* message) {
printf("Discord: (%i) %s\n", error_code, message);
}
};
} // namespace discord
REGISTER_COMPONENT(discord::component)

View File

@ -0,0 +1,172 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include "dvar.hpp"
namespace dvar {
struct dvar_base {
std::uint16_t flags{};
};
struct dvar_bool : dvar_base {
bool value{};
};
struct dvar_float : dvar_base {
float value{};
float min{};
float max{};
};
struct dvar_int : dvar_base {
int value{};
int min{};
int max{};
};
struct dvar_string : dvar_base {
std::string value{};
};
namespace {
template <typename T>
T* find_dvar(std::unordered_map<std::string, T>& map, const std::string& name) {
auto i = map.find(name);
if (i != map.end()) {
return &i->second;
}
return nullptr;
}
} // namespace
namespace override {
static std::unordered_map<std::string, dvar_bool> register_bool_overrides;
static std::unordered_map<std::string, dvar_float> register_float_overrides;
static std::unordered_map<std::string, dvar_int> register_int_overrides;
static std::unordered_map<std::string, dvar_string> register_string_overrides;
void register_bool(const std::string& name, const bool value,
const std::uint16_t flags) {
dvar_bool values;
values.value = value;
values.flags = flags;
register_bool_overrides[name] = values;
}
void register_float(const std::string& name, const float value, const float min,
const float max, const std::uint16_t flags) {
dvar_float values;
values.value = value;
values.min = min;
values.max = max;
values.flags = flags;
register_float_overrides[name] = values;
}
void register_int(const std::string& name, const int value, const int min,
const int max, const std::uint16_t flags) {
dvar_int values;
values.value = value;
values.min = min;
values.max = max;
values.flags = flags;
register_int_overrides[name] = values;
}
void register_string(const std::string& name, const std::string& value,
const std::uint16_t flags) {
dvar_string values;
values.value = value;
values.flags = flags;
register_string_overrides[name] = values;
}
} // namespace override
namespace {
utils::hook::detour dvar_register_bool_hook;
utils::hook::detour dvar_register_float_hook;
utils::hook::detour dvar_register_int_hook;
utils::hook::detour dvar_register_string_hook;
const game::dvar_t* dvar_register_bool_stub(const char* name, bool value,
std::uint16_t flags,
const char* description) {
auto* var = find_dvar(override::register_bool_overrides, name);
if (var) {
value = var->value;
flags = var->flags;
}
return dvar_register_bool_hook.invoke<const game::dvar_t*>(name, value, flags,
description);
}
const game::dvar_t* dvar_register_float_stub(const char* name, float value,
float min, float max,
std::uint16_t flags,
const char* description) {
auto* var = find_dvar(override::register_float_overrides, name);
if (var) {
value = var->value;
min = var->min;
max = var->max;
flags = var->flags;
}
return dvar_register_float_hook.invoke<const game::dvar_t*>(
name, value, min, max, flags, description);
}
const game::dvar_t* dvar_register_int_stub(const char* name, int value, int min,
int max, std::uint16_t flags,
const char* description) {
auto* var = find_dvar(override::register_int_overrides, name);
if (var) {
value = var->value;
min = var->min;
max = var->max;
flags = var->flags;
}
return dvar_register_int_hook.invoke<const game::dvar_t*>(
name, value, min, max, flags, description);
}
const game::dvar_t* dvar_register_string_stub(const char* name,
const char* value,
std::uint16_t flags,
const char* description) {
auto* var = find_dvar(override::register_string_overrides, name);
if (var) {
value = var->value.data();
flags = var->flags;
}
return dvar_register_string_hook.invoke<const game::dvar_t*>(
name, value, flags, description);
}
} // namespace
class component final : public component_interface {
public:
void post_load() override {
dvar_register_bool_hook.create(0x429390, &dvar_register_bool_stub);
dvar_register_float_hook.create(0x4051D0, &dvar_register_float_stub);
dvar_register_int_hook.create(0x4E9490, &dvar_register_int_stub);
dvar_register_string_hook.create(0x49E0B0, &dvar_register_string_stub);
}
void pre_destroy() override {
dvar_register_bool_hook.clear();
dvar_register_float_hook.clear();
dvar_register_int_hook.clear();
dvar_register_string_hook.clear();
}
};
} // namespace dvar
REGISTER_COMPONENT(dvar::component)

View File

@ -0,0 +1,13 @@
#pragma once
namespace dvar {
namespace override {
void register_bool(const std::string& name, bool value, std::uint16_t flags);
void register_float(const std::string& name, float value, float min, float max,
std::uint16_t flags);
void register_int(const std::string& name, int value, int min, int max,
std::uint16_t flags);
void register_string(const std::string& name, const std::string& value,
std::uint16_t flags);
} // namespace override
} // namespace dvar

View File

@ -0,0 +1,68 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include "scheduler.hpp"
#include "dvar.hpp"
namespace dvar_patches {
namespace {
const game::dvar_t* dvar_register_name(const char* dvar_name, const char* value,
unsigned __int16 flags,
const char* description) {
return game::Dvar_RegisterString(
dvar_name, value, game::DVAR_ARCHIVE | game::DVAR_USERINFO, description);
}
} // namespace
class component final : public component_interface {
public:
void post_start() override {
dvar::override::register_bool("intro", false, game::DVAR_ARCHIVE);
dvar::override::register_float("cg_fov", 65.0f, 65.0f, 160.0f,
game::DVAR_ARCHIVE | game::DVAR_SAVED);
dvar::override::register_string("fs_basegame", BASEGAME, game::DVAR_INIT);
#ifdef _DEBUG
dvar::override::register_bool("sv_cheats", true, game::DVAR_NONE);
#endif
}
void post_load() override {
utils::hook(0x475156, dvar_register_name, HOOK_CALL).install()->quick();
patch_sp();
}
private:
static void patch_sp() {
utils::hook::set<std::uint8_t>(0x635841, 0xEB); // Read only
utils::hook::set<std::uint8_t>(0x635913, 0xEB); // Cheat protected
utils::hook::set<std::uint8_t>(0x6358A5, 0xEB); // Write protected
utils::hook::set<std::uint8_t>(0x635974, 0xEB); // Latched
#ifdef _DEBUG
// Nop Dvar_RegisterVariant for sv_cheats in Dvar_Init
utils::hook::nop(0x471522, 5);
#endif
// Skip dvar output
utils::hook::set<std::uint8_t>(0x4CD2B7, 0xEB);
// Ignore server dvar change for clients
utils::hook::set<std::uint8_t>(0x63580B, 0xEB);
scheduler::once(
[] {
game::Dvar_RegisterBool("scr_damageFeedback", true, game::DVAR_NONE,
"Show marker when hitting enemies");
game::Dvar_RegisterString("connect_ip", "127.0.0.1:28960",
game::DVAR_NONE,
"Temporary dvar used to connect");
},
scheduler::pipeline::main);
}
};
} // namespace dvar_patches
REGISTER_COMPONENT(dvar_patches::component)

View File

@ -0,0 +1,82 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "game_module.hpp"
#include "filesystem.hpp"
namespace filesystem {
namespace {
const char* sys_default_install_path_stub() {
static auto current_path = std::filesystem::current_path().string();
return current_path.data();
}
} // namespace
std::vector<std::string> vectored_file_list(const std::string& path,
const std::string& extension) {
std::vector<std::string> file_list;
auto num_files = 0;
const auto** files = game::FS_ListFiles(path.data(), extension.data(),
game::FS_LIST_ALL, &num_files, 10);
if (files) {
for (auto i = 0; i < num_files; ++i) {
if (files[i]) {
file_list.emplace_back(files[i]);
}
}
game::FS_FreeFileList(files, 10);
}
return file_list;
}
std::string get_binary_directory() {
const auto dir = game_module::get_host_module().get_folder();
return utils::string::replace(dir, "/", "\\");
}
file::file(std::string name, game::FsThread thread) : name_(std::move(name)) {
assert(!this->name_.empty());
auto handle = 0;
const auto len =
game::FS_FOpenFileReadForThread(name_.data(), &handle, thread);
if (!handle) {
this->valid_ = false;
return;
}
auto* buf = static_cast<char*>(game::Hunk_AllocateTempMemory(len + 1));
game::FS_Read(buf, len, handle);
buf[len] = '\0';
game::FS_FCloseFile(handle);
this->valid_ = true;
this->buffer_.append(buf, len);
}
bool file::exists() const { return this->valid_; }
const std::string& file::get_buffer() const { return this->buffer_; }
const std::string& file::get_name() const { return this->name_; }
class component final : public component_interface {
public:
void post_load() override {
utils::hook(0x465B90, sys_default_install_path_stub, HOOK_CALL)
.install()
->quick(); // Sys_CreateFile
}
};
} // namespace filesystem
REGISTER_COMPONENT(filesystem::component)

View File

@ -0,0 +1,21 @@
#pragma once
namespace filesystem {
std::vector<std::string> vectored_file_list(const std::string& path,
const std::string& extension);
std::string get_binary_directory();
class file {
public:
file(std::string name, game::FsThread thread);
[[nodiscard]] bool exists() const;
[[nodiscard]] const std::string& get_buffer() const;
[[nodiscard]] const std::string& get_name() const;
private:
bool valid_ = false;
std::string name_;
std::string buffer_;
};
} // namespace filesystem

View File

@ -0,0 +1,115 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include "gsc/extension.hpp"
#include "game_log.hpp"
#include "scheduler.hpp"
namespace game_log {
namespace {
int log_file = 0;
void g_scr_log_print() {
char string[1024]{};
std::size_t i_string_len = 0;
const auto i_num_parms = game::Scr_GetNumParam();
for (std::size_t i = 0; i < i_num_parms; ++i) {
const auto* psz_token = game::Scr_GetString(i);
const auto i_token_len = std::strlen(psz_token);
i_string_len += i_token_len;
if (i_string_len >= sizeof(string)) {
// Do not overflow the buffer
break;
}
strncat_s(string, psz_token, _TRUNCATE);
}
log_printf("%s", string);
}
void g_init_game_stub() {
game::Com_Printf(game::CON_CHANNEL_SERVER,
"------- Game Initialization -------\n");
game::Com_Printf(game::CON_CHANNEL_SERVER, "gamename: iw4-sp\n");
game::Com_Printf(game::CON_CHANNEL_SERVER, "gamedate: %s\n", __DATE__);
if (*dvars::g_log->current.string == '\0') {
game::Com_Printf(game::CON_CHANNEL_SERVER, "Not logging to disk.\n");
} else {
game::FS_FOpenFileByMode(dvars::g_log->current.string, &log_file,
game::FS_APPEND_SYNC);
if (!log_file) {
game::Com_PrintWarning(game::CON_CHANNEL_SERVER,
"WARNING: Couldn't open logfile: %s\n");
} else {
log_printf(
"------------------------------------------------------------\n");
log_printf("InitGame\n");
}
}
utils::hook::invoke<void>(0x4FA880); // Vehicle_ClearServerDefs
}
void g_shutdown_game_stub() {
if (log_file) {
log_printf("ShutdownGame:\n");
log_printf(
"------------------------------------------------------------\n");
game::FS_FCloseFile(log_file);
log_file = 0;
}
utils::hook::invoke<void>(0x455F90); // Actor_ClearThreatBiasGroups
}
} // namespace
void log_printf(const char* fmt, ...) {
char string[1024]{};
char string2[1024]{};
va_list ap;
if (!log_file) {
return;
}
va_start(ap, fmt);
vsnprintf_s(string2, _TRUNCATE, fmt, ap);
va_end(ap);
const auto time = game::level->time / 1000;
const auto len = sprintf_s(string, "%3i:%i%i %s", time / 60, time % 60 / 10,
time % 60 % 10, string2);
game::FS_Write(string, len, log_file);
}
class component final : public component_interface {
public:
static_assert(offsetof(game::level_locals_t, time) == 0x34);
void post_load() override {
utils::hook(0x4C75E0, g_init_game_stub, HOOK_CALL).install()->quick();
utils::hook(0x418B5A, g_shutdown_game_stub, HOOK_CALL).install()->quick();
scheduler::once(
[] {
dvars::g_log = game::Dvar_RegisterString(
"g_log", "games_sp.log", game::DVAR_NONE, "Log file name");
},
scheduler::pipeline::main);
gsc::add_function("logprint", g_scr_log_print);
}
};
} // namespace game_log
REGISTER_COMPONENT(game_log::component)

View File

@ -0,0 +1,5 @@
#pragma once
namespace game_log {
void log_printf(const char* fmt, ...);
}

View File

@ -0,0 +1,110 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#ifdef INJECT_HOST_AS_LIB
#include <utils/hook.hpp>
#endif
#include <utils/nt.hpp>
#include "game_module.hpp"
namespace game_module {
#ifdef INJECT_HOST_AS_LIB
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 WINAPI get_module_handle_a(const LPCSTR module_name) {
if (!module_name) {
return get_game_module();
}
return handle_a_hook.invoke<HMODULE>(module_name);
}
HMODULE WINAPI get_module_handle_w(const LPWSTR module_name) {
if (!module_name) {
return get_game_module();
}
return handle_w_hook.invoke<HMODULE>(module_name);
}
BOOL WINAPI 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 WINAPI 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 WINAPI 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 WINAPI get_module_file_name_w(HMODULE hmodule, const LPWSTR filename,
const DWORD size) {
if (!hmodule || utils::nt::library(hmodule) == get_game_module()) {
hmodule = get_host_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);
}
} // namespace
#endif
utils::nt::library get_game_module() {
static utils::nt::library game{reinterpret_cast<HMODULE>(0x400000)};
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 {
#ifdef INJECT_HOST_AS_LIB
hook_module_resolving();
#else
assert(get_host_module() == get_game_module());
#endif
}
};
} // namespace game_module
REGISTER_COMPONENT(game_module::component)

View File

@ -0,0 +1,6 @@
#pragma once
namespace game_module {
utils::nt::library get_game_module();
utils::nt::library get_host_module();
} // namespace game_module

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,126 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include "extension.hpp"
namespace gsc {
#define GSC_DEBUG_FUNCTIONS
namespace {
struct script_function_def {
game::BuiltinFunction actionFunc;
bool type;
};
struct script_method_def {
game::BuiltinMethod actionFunc;
bool type;
};
std::unordered_map<std::string, script_function_def> custom_scr_funcs;
std::unordered_map<std::string, script_method_def> custom_scr_meths;
game::BuiltinFunction built_in_get_function_stub(const char** p_name,
int* type) {
if (p_name) {
const auto itr = custom_scr_funcs.find(utils::string::to_lower(*p_name));
if (itr != custom_scr_funcs.end()) {
*type = itr->second.type;
return itr->second.actionFunc;
}
} else {
for (const auto& [name, func] : custom_scr_funcs) {
game::Scr_RegisterFunction(reinterpret_cast<int>(func.actionFunc),
name.data());
}
}
// If no function was found let's call BuiltIn_GetFunction
return utils::hook::invoke<game::BuiltinFunction>(0x4DD160, p_name, type);
}
game::BuiltinMethod built_in_get_method_stub(const char** p_name, int* type) {
if (p_name) {
const auto itr = custom_scr_meths.find(utils::string::to_lower(*p_name));
if (itr != custom_scr_meths.end()) {
*type = itr->second.type;
return itr->second.actionFunc;
}
} else {
for (const auto& [name, meth] : custom_scr_meths) {
game::Scr_RegisterFunction(reinterpret_cast<int>(meth.actionFunc),
name.data());
}
}
// If no method was found let's call BuiltIn_GetMethod
return utils::hook::invoke<game::BuiltinMethod>(0x5DB850, p_name, type);
}
} // namespace
void add_function(const char* name, game::BuiltinFunction func, bool type) {
script_function_def def;
def.actionFunc = func;
def.type = type;
custom_scr_funcs.emplace(utils::string::to_lower(name), def);
}
void add_method(const char* name, game::BuiltinMethod func, bool type) {
script_method_def def;
def.actionFunc = func;
def.type = type;
custom_scr_meths.emplace(utils::string::to_lower(name), def);
}
class extension final : public component_interface {
public:
void post_load() override {
// Fetch custom functions
utils::hook(0x4ADF9C, built_in_get_function_stub, HOOK_CALL)
.install()
->quick(); // Scr_GetFunction
utils::hook(0x444827, built_in_get_method_stub, HOOK_CALL)
.install()
->quick(); // Scr_GetMethod
add_functions();
#ifdef GSC_DEBUG_FUNCTIONS
add_debug_functions();
#endif
}
static void add_functions() {
add_function("Float", [] {
switch (game::Scr_GetType(0)) {
case game::VAR_STRING:
game::Scr_AddFloat(
static_cast<float>(std::atof(game::Scr_GetString(0))));
break;
case game::VAR_FLOAT:
game::Scr_AddFloat(game::Scr_GetFloat(0));
break;
case game::VAR_INTEGER:
game::Scr_AddFloat(static_cast<float>(game::Scr_GetInt(0)));
break;
default:
game::Scr_ParamError(0, utils::string::va("cannot cast {0} to float",
game::Scr_GetTypeName(0)));
break;
}
});
}
static void add_debug_functions() {
add_function("AddDebugCommand",
[] { game::Cbuf_AddText(0, game::Scr_GetString(0)); });
}
};
} // namespace gsc
REGISTER_COMPONENT(gsc::extension)

View File

@ -0,0 +1,7 @@
#pragma once
namespace gsc {
void add_function(const char* name, game::BuiltinFunction func,
bool type = false);
void add_method(const char* name, game::BuiltinMethod func, bool type = false);
} // namespace gsc

View File

@ -0,0 +1,88 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace gsc {
namespace {
std::vector<int> main_handles;
std::vector<int> init_handles;
// Do not use C++ objects because Scr_LoadScript may longjmp
void g_scr_load_scripts_stub() {
// Clear handles (from previous GSC loading session)
main_handles.clear();
init_handles.clear();
char path[MAX_PATH]{};
auto num_files = 0;
const auto** files =
game::FS_ListFiles("scripts/", "gsc", game::FS_LIST_ALL, &num_files, 10);
for (auto i = 0; i < num_files; ++i) {
const auto* script_file = files[i];
game::Com_Printf(game::CON_CHANNEL_SERVER, "Loading script %s...\n",
script_file);
const auto len = sprintf_s(path, "%s/%s", "scripts", script_file);
if (len == -1) {
continue;
}
// Scr_LoadScriptInternal will add the '.gsc' suffix so we remove it
path[len - 4] = '\0';
if (!game::Scr_LoadScript(path)) {
game::Com_Printf(game::CON_CHANNEL_SERVER,
"Script %s encountered an error while loading\n", path);
continue;
}
game::Com_Printf(game::CON_CHANNEL_SERVER,
"Script %s.gsc loaded successfully\n", path);
const auto main_handle = game::Scr_GetFunctionHandle(path, "main");
if (main_handle) {
main_handles.push_back(main_handle);
}
const auto init_handle = game::Scr_GetFunctionHandle(path, "init");
if (init_handle) {
init_handles.push_back(init_handle);
}
// Allow scripts with no handles
}
game::FS_FreeFileList(files, 10);
}
void scr_load_level_stub() {
for (const auto& handle : main_handles) {
const auto id = game::Scr_ExecThread(handle, 0);
game::Scr_FreeThread(static_cast<std::uint16_t>(id));
}
utils::hook::invoke<void>(0x470860); // Scr_LoadLevel
for (const auto& handle : init_handles) {
const auto id = game::Scr_ExecThread(handle, 0);
game::Scr_FreeThread(static_cast<std::uint16_t>(id));
}
}
} // namespace
class loading final : public component_interface {
public:
void post_load() override {
utils::hook(0x4054C2, g_scr_load_scripts_stub, HOOK_JUMP)
.install()
->quick();
utils::hook(0x4C1E34, scr_load_level_stub, HOOK_CALL).install()->quick();
}
};
} // namespace gsc
REGISTER_COMPONENT(gsc::loading)

View File

@ -0,0 +1,83 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "extension.hpp"
namespace gsc {
namespace {
void scr_str_i_cmp() {
const auto* string1 = game::SL_ConvertToString(game::Scr_GetConstString(0));
const auto* string2 = game::SL_ConvertToString(game::Scr_GetConstString(1));
game::Scr_AddInt(game::I_stricmp(string1, string2));
}
void scr_is_end_str() {
const auto* str = game::Scr_GetString(0);
const auto* suffix = game::Scr_GetString(1);
const auto str_len = std::strlen(str);
const auto suffix_len = std::strlen(suffix);
if (suffix_len > str_len) {
game::Scr_AddInt(0);
return;
}
game::Scr_AddInt(
std::memcmp(str + str_len - suffix_len, suffix, suffix_len) == 0);
}
void scr_to_upper() {
const auto script_value = game::Scr_GetConstString(0);
const auto* string = game::SL_ConvertToString(script_value);
char out[1024]{};
bool changed = false;
std::size_t i = 0;
while (i < sizeof(out)) {
const auto value = *string;
const auto result =
static_cast<char>(std::toupper(static_cast<unsigned char>(value)));
out[i] = result;
if (value != result) {
changed = true;
}
if (result == '\0') {
break;
}
++string;
++i;
}
if (i >= sizeof(out)) {
game::Scr_Error("string too long");
return;
}
if (changed) {
game::Scr_AddString(out);
} else {
game::SL_AddRefToString(script_value);
game::Scr_AddConstString(script_value);
game::SL_RemoveRefToString(script_value);
}
}
} // namespace
class string final : public component_interface {
public:
void post_load() override {
add_function("StrICmp", scr_str_i_cmp);
add_function("IsEndStr", scr_is_end_str);
add_function("ToUpper", scr_to_upper);
}
};
} // namespace gsc
REGISTER_COMPONENT(gsc::string)

View File

@ -0,0 +1,16 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
namespace images {
class component final : public component_interface {
public:
void post_load() override {
// Skip version check
utils::hook::set<std::uint8_t>(0x544746, 0xEB);
}
};
} // namespace images
REGISTER_COMPONENT(images::component)

View File

@ -0,0 +1,73 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include "command.hpp"
namespace lean {
namespace {
game::kbutton_t in_lean_left;
game::kbutton_t in_lean_right;
void in_lean_left_down() { game::IN_KeyDown(&in_lean_left); }
void in_lean_left_up() { game::IN_KeyUp(&in_lean_left); }
void in_lean_right_down() { game::IN_KeyDown(&in_lean_right); }
void in_lean_right_up() { game::IN_KeyUp(&in_lean_right); }
void set_lean_flags(game::usercmd_s* cmd) {
if (in_lean_left.active || in_lean_left.wasPressed) {
cmd->buttons |= game::CMD_BUTTON_LEAN_LEFT;
}
if (in_lean_right.active || in_lean_right.wasPressed) {
cmd->buttons |= game::CMD_BUTTON_LEAN_RIGHT;
}
in_lean_left.wasPressed = false;
in_lean_right.wasPressed = false;
}
void __declspec(naked) cl_cmd_buttons_stub() {
__asm {
pushad;
push esi;
call set_lean_flags;
add esp, 0x4;
popad;
// code skipped by our hook
mov eax, ecx;
imul eax, eax, 0x21C;
// CL_CmdButton
push 0x57B2B8;
ret;
}
}
} // namespace
class component final : public component_interface {
public:
static_assert(sizeof(game::kbutton_t) == 0x14);
static_assert(sizeof(game::usercmd_s) == 0x40);
void post_load() override {
command::add_raw("+leanleft", in_lean_left_down, TRUE);
command::add_raw("-leanleft", in_lean_left_up, TRUE);
command::add_raw("+leanright", in_lean_right_down, TRUE);
command::add_raw("-leanright", in_lean_right_up, TRUE);
utils::hook(0x57B2B0, cl_cmd_buttons_stub, HOOK_JUMP).install()->quick();
utils::hook::nop(0x57B2B0 + 5, 3);
}
};
} // namespace lean
REGISTER_COMPONENT(lean::component)

View File

@ -0,0 +1,83 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "command.hpp"
#include "network.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace network {
namespace {
void cl_co_op_connect(const command::params& params) {
// Valid format is IP Port
if (params.size() < 3) {
return;
}
const auto input = std::strtol(params.get(2), nullptr, 10);
const auto port = ::htons(static_cast<std::uint16_t>(input));
command::execute(
utils::string::va("connect_coop {0} {1}", params.get(1), port));
}
} // namespace
class component final : public component_interface {
public:
void post_load() override { patch_sp(); }
private:
static void patch_sp() {
// Ignore protocol mismatch
utils::hook::set<std::uint8_t>(0x65D0C2, 0xEB);
utils::hook::set<std::uint8_t>(0x65D0ED, 0xEB);
// Disable matchmaking stuff
utils::hook::set<std::uint8_t>(0x43BAE0, 0xEB);
// Ignore 'MAX_PACKET_USERCMDS'
utils::hook::set<std::uint8_t>(0x4B1436, 0xEB);
// Disable IWNet stuff
utils::hook::set<std::uint8_t>(0x44E824, 0xEB);
utils::hook::set<std::uint8_t>(0x44E808, 0xEB);
// Disable MP packet handler
utils::hook::set<std::uint8_t>(0x65E717, 0xEB);
// Disable LSP packet handler
utils::hook::set<std::uint8_t>(0x65E3A4, 0xEB);
// Avoid spam
utils::hook(0x65E786, game::Com_DPrintf, HOOK_CALL).install()->quick();
utils::hook(0x65D659, game::Com_DPrintf, HOOK_CALL).install()->quick();
// Disable BigShort in NET_AdrToString
utils::hook(0x4BF4D1, game::ShortNoSwap, HOOK_CALL).install()->quick();
utils::hook(0x4BF50C, game::ShortNoSwap, HOOK_CALL).install()->quick();
// Fix NET_StringToAdr (Only known workaround that actually works)
utils::hook::set<const char*>(0x475417, "connect_coop");
command::add("connect", cl_co_op_connect);
// Disable BigShort in GetLocalAddressForEncryptedConnection
utils::hook(0x4CF42D, game::ShortNoSwap, HOOK_CALL).install()->quick();
// Parse port as unsigned short in Net_AddrToString
utils::hook::set<const char*>(0x4BF4F3, "%u.%u.%u.%u:%hu");
// Force Win socket initialization
utils::hook::nop(0x42B649, 2);
// Kill LSP
utils::hook::set<std::uint8_t>(0x4553F0, 0xC3); // Hello
utils::hook::set<std::uint8_t>(0x428D00, 0xC3); // LSP_SendLogRequest
utils::hook::set<std::uint8_t>(0x4D13C0, 0xC3); // LSP_ParsePacket
utils::hook::set<std::uint8_t>(0x66D440, 0xC3); // LSP_AddKeepAlive
utils::hook::nop(0x494E68, 5); // Don't create LSP socket
}
};
} // namespace network
REGISTER_COMPONENT(network::component)

View File

@ -0,0 +1,5 @@
#pragma once
namespace network {
using callback = std::function<void(const game::netadr_t&, const std::string&)>;
}

View File

@ -0,0 +1,43 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
namespace no_border {
namespace {
void __declspec(naked) window_style_stub() {
__asm {
push eax;
mov eax, dvars::r_noBorder;
cmp byte ptr [eax + 0x10], 1;
pop eax;
je remove_border;
mov ebp, WS_VISIBLE | WS_SYSMENU | WS_CAPTION;
jmp safe_return;
remove_border:
mov ebp, WS_VISIBLE | WS_POPUP;
safe_return:
push 0x50C0B8;
ret;
}
}
} // namespace
class component final : public component_interface {
public:
void post_load() override {
utils::hook(0x50C0B3, window_style_stub, HOOK_JUMP).install()->quick();
dvars::r_noBorder =
game::Dvar_RegisterBool("r_noBorder", false, game::DVAR_ARCHIVE,
"Do not use a border in windowed mode");
}
};
} // namespace no_border
REGISTER_COMPONENT(no_border::component)

View File

@ -0,0 +1,100 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
#include <utils/nt.hpp>
#include "command.hpp"
#include "filesystem.hpp"
namespace patches {
namespace {
void sys_init_stub() {
game::Cmd_SetAutoComplete("exec", "", "cfg");
utils::hook::invoke<void>(0x4EEB20);
}
void cl_play_unskippable_cinematic_f() {
// CL_PlayCinematic_f
utils::hook::invoke<void>(0x4CC950);
*game::cin_skippable = true;
}
void cl_start_multiplayer_f() {
utils::nt::update_dll_search_path(filesystem::get_binary_directory());
utils::nt::launch_process("iw4mp.exe", "-multiplayer");
command::execute("quit", false);
}
const char* live_get_local_client_name_stub() {
return game::Dvar_FindVar("name")->current.string;
}
} // namespace
class component final : public component_interface {
public:
void post_load() override {
utils::hook(0x6042A2, sys_init_stub, HOOK_CALL).install()->quick();
utils::hook(0x492EF0, live_get_local_client_name_stub, HOOK_JUMP)
.install()
->quick();
patch_sp();
}
private:
static void patch_sp() {
// Force external console
utils::hook::nop(0x604071, 21);
// Remove limit on IWD file loading
utils::hook::set<std::uint8_t>(0x630FF3, 0xEB);
// Ignore XUID match
utils::hook::set<std::uint8_t>(0x65D1AF, 0xEB);
// Remove this particular string from the log file
utils::hook::nop(0x6030A6, 5);
// Config related
utils::hook::set<std::uint8_t>(0x4D3FD3, 0xEB);
// Improper quit popup
utils::hook::nop(0x4F5B3A, 2);
// Remove fs_game check
utils::hook::nop(0x612932, 2);
// Build os path stuff
utils::hook::set<std::uint8_t>(0x6300BF, 0xEB);
// raw -> main
utils::hook::set<std::uint32_t>(0x50A0B2, 0x723390);
// Disable 'replay' output
utils::hook::nop(0x65F14A, 5);
// Disable heartbeat output
utils::hook::nop(0x57EF87, 5);
// Allow intro to be skipped
utils::hook::set<void (*)()>(0x47529F, cl_play_unskippable_cinematic_f);
// Start IW4 MP
utils::hook::set<void (*)()>(0x475327, cl_start_multiplayer_f);
// Enable commandline arguments
utils::hook::set<std::uint8_t>(0x453B24, 0xEB);
// Rename config
utils::hook::set<const char*>(0x6040F2, CLIENT_CONFIG);
utils::hook::set<const char*>(0x602D38, CLIENT_CONFIG);
utils::hook::set<const char*>(0x6037C9, CLIENT_CONFIG);
utils::hook::set<const char*>(0x4D3FDB, CLIENT_CONFIG);
utils::hook::set<const char*>(0x469DB6, CLIENT_CONFIG);
}
};
} // namespace patches
REGISTER_COMPONENT(patches::component)

View File

@ -0,0 +1,245 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/dvars.hpp"
#include "gsc/extension.hpp"
#include <utils/hook.hpp>
namespace player_movement {
namespace {
void __declspec(naked) pm_step_slide_move_stub() {
__asm {
push eax;
mov eax, dvars::pm_bounce;
cmp byte ptr [eax + 0x10], 1;
pop eax;
je bounce;
cmp dword ptr [esp + 0x24], 0;
jnz no_bounce;
bounce:
push 0x4E905B;
ret;
no_bounce:
push 0x4E906F;
ret;
}
}
void pm_project_velocity_stub(const float* vel_in, const float* normal,
float* vel_out) {
const auto length_squared_2d = vel_in[0] * vel_in[0] + vel_in[1] * vel_in[1];
if (std::fabsf(normal[2]) < 0.001f || length_squared_2d == 0.0f) {
vel_out[0] = vel_in[0];
vel_out[1] = vel_in[1];
vel_out[2] = vel_in[2];
return;
}
auto new_z = vel_in[0] * normal[0] + vel_in[1] * normal[1];
new_z = -new_z / normal[2];
const auto length_scale =
std::sqrtf((vel_in[2] * vel_in[2] + length_squared_2d) /
(new_z * new_z + length_squared_2d));
if (dvars::pm_bouncingAllAngles->current.enabled ||
(length_scale < 1.f || new_z < 0.f || vel_in[2] > 0.f)) {
vel_out[0] = vel_in[0] * length_scale;
vel_out[1] = vel_in[1] * length_scale;
vel_out[2] = new_z * length_scale;
}
}
game::gentity_s* weapon_rocket_launcher_fire_stub(
game::gentity_s* ent, unsigned int weapon_index, float spread,
game::weaponParms* wp, const float* gun_vel,
game::lockonFireParms* lock_parms, bool magic_bullet) {
auto* result = utils::hook::invoke<game::gentity_s*>(
0x443F20, ent, weapon_index, spread, wp, gun_vel, lock_parms,
magic_bullet);
if (ent->client && dvars::pm_rocketJump->current.enabled) {
const auto scale = dvars::pm_rocketJumpScale->current.value;
ent->client->ps.velocity[0] += (0.0f - wp->forward[0]) * scale;
ent->client->ps.velocity[1] += (0.0f - wp->forward[1]) * scale;
ent->client->ps.velocity[2] += (0.0f - wp->forward[2]) * scale;
}
return result;
}
void cm_transformed_capsule_trace_stub(game::trace_t* results,
const float* start, const float* end,
const game::Bounds* bounds,
const game::Bounds* capsule,
int contents, const float* origin,
const float* angles) {
if (dvars::pm_playerCollision->current.enabled) {
utils::hook::invoke<void>(0x47C7D0, results, start, end, bounds, capsule,
contents, origin, angles);
}
}
void pm_trace_stub(game::pmove_t* pm, game::trace_t* results,
const float* start, const float* end,
const game::Bounds* bounds, int pass_entity_num,
int content_mask) {
game::PM_trace(pm, results, start, end, bounds, pass_entity_num,
content_mask);
// Allow the player to stand even when there is no headroom
if (dvars::pm_elevators->current.enabled) {
results->allsolid = false;
}
}
void pm_player_trace_stub(game::pmove_t* pm, game::trace_t* results,
const float* start, const float* end,
const game::Bounds* bounds, int pass_entity_num,
int content_mask) {
game::PM_playerTrace(pm, results, start, end, bounds, pass_entity_num,
content_mask);
if (dvars::pm_elevators->current.enabled) {
results->startsolid = false;
}
}
void pm_crash_land_stub(const float* v, float scale, const float* result) {
if (!dvars::pm_disableLandingSlowdown->current.enabled) {
game::Vec3Scale(v, scale, result);
}
}
void __declspec(naked) jump_check_stub() {
__asm {
push eax;
mov eax, dvars::pm_bunnyHop;
cmp byte ptr [eax + 0x10], 1;
pop eax;
je auto_hop;
// Game's code
test dword ptr [ebx + 0x48], 0x400;
push 0x4D25EF;
ret;
auto_hop:
push 0x4D25FE;
ret;
}
}
void __declspec(naked) p_move_single_stub() {
__asm {
push eax;
mov eax, dvars::pm_snapVector;
cmp byte ptr [eax + 0x10], 1;
pop eax;
jnz skip;
lea ebp, [ebp + 0x28]; // velocity
push ebp;
call game::Sys_SnapVector;
add esp, 0x4;
skip:
// Game's code
pop edi;
pop esi;
pop ebp;
pop ebx;
add esp, 0x90;
ret;
}
}
void g_scr_is_sprinting(const game::scr_entref_t entref) {
const auto* client = game::GetEntity(entref)->client;
if (!client) {
game::Scr_Error("IsSprinting can only be called on a player");
return;
}
game::Scr_AddInt(game::PM_IsSprinting(&client->ps));
}
} // namespace
class component final : public component_interface {
public:
static_assert(sizeof(game::weaponParms) == 0x48);
static_assert(sizeof(game::lockonFireParms) == 0x18);
void post_load() override {
utils::hook(0x4E9054, pm_step_slide_move_stub, HOOK_JUMP)
.install()
->quick(); // PM_StepSlideMove
utils::hook(0x4E90BE, pm_project_velocity_stub, HOOK_CALL)
.install()
->quick(); // PM_StepSlideMove
utils::hook(0x4FA809, weapon_rocket_launcher_fire_stub, HOOK_CALL)
.install()
->quick(); // FireWeapon
utils::hook(0x4B6FC0, cm_transformed_capsule_trace_stub, HOOK_CALL)
.install()
->quick(); // SV_ClipMoveToEntity
utils::hook(0x57635F, cm_transformed_capsule_trace_stub, HOOK_CALL)
.install()
->quick(); // CG_ClipMoveToEntity
utils::hook(0x64F439, pm_trace_stub, HOOK_CALL)
.install()
->quick(); // PM_CheckDuck
utils::hook(0x651A60, pm_player_trace_stub, HOOK_CALL)
.install()
->quick(); // PM_CorrectAllSolid
utils::hook(0x651AF2, pm_player_trace_stub, HOOK_CALL)
.install()
->quick(); // PM_CorrectAllSolid
utils::hook(0x64E571, pm_crash_land_stub, HOOK_CALL)
.install()
->quick(); // Vec3Scale
utils::hook(0x4D25E8, jump_check_stub, HOOK_JUMP).install()->quick();
utils::hook(0x6530C3, p_move_single_stub, HOOK_JUMP).install()->quick();
gsc::add_method("IsSprinting", g_scr_is_sprinting);
register_dvars();
}
static void register_dvars() {
// clang-format off
dvars::pm_bounce = game::Dvar_RegisterBool(
"pm_bounce", false, game::DVAR_NONE, "CoD4 Bounces");
dvars::pm_bouncingAllAngles = game::Dvar_RegisterBool(
"pm_bouncingAllAngles", false, game::DVAR_NONE, "Enable bouncing from all angles");
dvars::pm_rocketJump = game::Dvar_RegisterBool(
"pm_rocketJump", true, game::DVAR_NONE, "CoD4 rocket jumps");
dvars::pm_rocketJumpScale = game::Dvar_RegisterFloat(
"pm_rocketJumpScale", 64.0f, 0.0f, 1024.0f, game::DVAR_NONE, "");
dvars::pm_playerCollision = game::Dvar_RegisterBool(
"pm_playerCollision", true, game::DVAR_NONE, "Push intersecting players away from each other");
dvars::pm_elevators = game::Dvar_RegisterBool(
"pm_elevators", false, game::DVAR_NONE, "CoD4 elevators");
dvars::pm_disableLandingSlowdown = game::Dvar_RegisterBool("pm_disableLandingSlowdown",
false, game::DVAR_NONE, "Toggle landing slowdown");
dvars::pm_bunnyHop = game::Dvar_RegisterBool("pm_bunnyHop",
false, game::DVAR_NONE, "Constantly jump when holding space");
dvars::pm_snapVector = game::Dvar_RegisterBool("pm_snapVector", false, game::DVAR_NONE, "Snap velocity");
// clang-format on
}
};
} // namespace player_movement
REGISTER_COMPONENT(player_movement::component)

View File

@ -0,0 +1,163 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/concurrency.hpp>
#include <utils/hook.hpp>
#include <utils/thread.hpp>
#include "scheduler.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, this](task_list& tasks) {
tasks.emplace_back(std::move(task));
});
}
void clear() {
callbacks_.access([&](task_list& tasks) {
this->merge_callbacks();
tasks.clear();
});
}
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];
void execute(const pipeline type) {
assert(type >= 0 && type < pipeline::count);
pipelines[type].execute();
}
void g_glass_update_stub() {
utils::hook::invoke<void>(0x43D7E0);
execute(pipeline::server);
}
void r_end_frame_stub() {
utils::hook::invoke<void>(0x50F540);
execute(pipeline::renderer);
}
void main_frame_stub() {
utils::hook::invoke<void>(0x4D3FC0);
execute(pipeline::main);
}
} // namespace
void clear_tasks(const pipeline type) { return pipelines[type].clear(); }
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);
}
unsigned int thread_id;
class component final : public component_interface {
public:
void post_start() override {
thread = utils::thread::create_named_thread("Async Scheduler", [] {
while (!kill) {
execute(pipeline::async);
std::this_thread::sleep_for(10ms);
}
});
}
void post_load() override {
utils::hook(0x4EDBDD, g_glass_update_stub, HOOK_CALL).install()->quick();
utils::hook(0x49C3AF, main_frame_stub, HOOK_CALL).install()->quick();
utils::hook(0x57DB13, r_end_frame_stub, HOOK_CALL).install()->quick();
}
void pre_destroy() override {
kill = true;
if (thread.joinable()) {
thread.join();
}
}
};
} // namespace scheduler
REGISTER_COMPONENT(scheduler::component)

View File

@ -0,0 +1,33 @@
#pragma once
using namespace std::literals;
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 constexpr auto cond_continue = false;
static constexpr auto cond_end = true;
void clear_tasks(pipeline type);
void schedule(const std::function<bool()>& callback, pipeline type,
std::chrono::milliseconds delay = 0ms);
void loop(const std::function<void()>& callback, pipeline type,
std::chrono::milliseconds delay = 0ms);
void once(const std::function<void()>& callback, pipeline type,
std::chrono::milliseconds delay = 0ms);
} // namespace scheduler

View File

@ -0,0 +1,47 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
namespace steam_patches {
namespace {
void __declspec(naked) steam_init() {
const static DWORD Steam_IsClientSignedInLocally_t = 0x4293F0;
__asm {
call Steam_IsClientSignedInLocally_t;
test al, al;
jz return_safe;
push 0x43FAF9;
ret;
return_safe:
mov al, 1;
ret;
}
}
} // namespace
class component final : public component_interface {
public:
void post_load() override {
patch_sp();
utils::hook(0x43FAF0, steam_init, HOOK_JUMP).install()->quick();
}
static void patch_sp() {
// Prevent stat loading from steam
utils::hook::set<std::uint8_t>(0x43FB33, 0xC3);
// Steam must be running
utils::hook::nop(0x6040A3, 30);
// No-Steam
utils::hook::nop(0x4E9458, 7);
utils::hook::nop(0x4E9470, 7);
}
};
} // namespace steam_patches
REGISTER_COMPONENT(steam_patches::component)

View File

@ -0,0 +1,167 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/binary_resource.hpp>
#include <utils/nt.hpp>
#include <utils/string.hpp>
#include <resource.hpp>
#include "steam/steam.hpp"
#include "steam/interface.hpp"
#include "scheduler.hpp"
namespace {
utils::binary_resource runner_file(RUNNER, "iw4sp-runner.exe");
} // namespace
class steam_proxy final : public component_interface {
public:
void post_load() override {
this->load_client();
this->clean_up_on_error();
try {
this->start_mod("iw4-sp singleplayer", 10180);
} catch (const std::exception& ex) {
printf("Steam: %s\n", ex.what());
}
}
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_);
}
}
}
private:
utils::nt::library steam_client_module_{};
steam::interface client_engine_{};
steam::interface client_user_{};
steam::interface client_utils_{};
void* steam_pipe_ = nullptr;
void* global_user_ = nullptr;
[[nodiscard]] void* load_client_engine() const {
if (!this->steam_client_module_)
return nullptr;
for (auto i = 1; i < 1000; ++i) {
const auto* name =
utils::string::va("CLIENTENGINE_INTERFACE_VERSION{0:03}", i);
auto* const client_engine = this->steam_client_module_.invoke<void*>(
"CreateInterface", name, nullptr);
if (client_engine)
return client_engine;
}
return nullptr;
}
void load_client() {
const auto steam_path = steam::get_steam_install_directory();
if (steam_path.empty())
return;
utils::nt::library::load(steam_path / "tier0_s.dll");
utils::nt::library::load(steam_path / "vstdlib_s.dll");
this->steam_client_module_ =
utils::nt::library::load(steam_path / "steamclient.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, const std::size_t app_id) {
__try {
this->start_mod_unsafe(title, app_id);
} __except (EXCEPTION_EXECUTE_HANDLER) {
this->do_cleanup();
}
}
void start_mod_unsafe(const std::string& title, std::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]{};
GetCurrentDirectoryA(sizeof(our_directory), our_directory);
const auto path = runner_file.get_extracted_file();
const auto* cmd_line =
utils::string::va("\"{0}\" -proc {1}", path, GetCurrentProcessId());
game_id game_id;
game_id.raw.type = 1; // k_EGameIDTypeGameMod
game_id.raw.app_id = app_id & 0xFFFFFF;
const auto* mod_id = "IW4";
game_id.raw.mod_id =
*reinterpret_cast<const unsigned int*>(mod_id) | 0x80000000;
this->client_user_.invoke<bool>("SpawnProcess", path.data(), cmd_line,
our_directory, game_id.bits, title.data(),
app_id, 0, 0, 0);
}
void do_cleanup() {
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};
}
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->do_cleanup();
return scheduler::cond_end;
},
scheduler::pipeline::async);
}
};
REGISTER_COMPONENT(steam_proxy)

629
src/client/component/ui.cpp Normal file
View File

@ -0,0 +1,629 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "botlib/l_precomp.hpp"
#include <utils/hook.hpp>
namespace ui {
namespace {
#define ITEM_TYPE_LISTBOX 6 // scrollable list
#define ITEM_TYPE_MULTI 12 // multiple list setting, enumerated
#define PARSE_FLOAT_TOKEN(name) \
if (!game::I_stricmp(token.string, #name)) { \
if (!game::PC_Float_Parse(handle, &g_load.loadAssets.##name##)) { \
return false; \
} \
continue; \
}
#define PARSE_INT_TOKEN(name) \
if (!game::I_stricmp(token.string, #name)) { \
if (!game::PC_Int_Parse(handle, &g_load.loadAssets.##name##)) { \
return false; \
} \
continue; \
}
#define FREE_STATEMENT(statement) \
{ \
if ((statement)) { \
menu_free_expression_supporting_data((statement)->supportingData); \
} \
game::free_expression((statement)); \
}
template <typename T, int N, int M> struct KeywordHashEntry {
const char* keyword;
int (*func)(T*, int);
};
KeywordHashEntry<game::menuDef_t, 128, 3523>** menu_parse_keyword_hash;
std::vector<game::menuDef_t*> loaded_menus_list;
struct {
game::loadAssets_t loadAssets;
game::MenuList menuList;
game::itemDef_s* items[512];
game::menuDef_t* menus[512];
} g_load;
char menu_buf[0x8000];
int asset_parse(int handle) {
game::pc_token_s token{};
if (!game::PC_ReadTokenHandle(handle, &token)) {
return false;
}
if (game::I_stricmp(token.string, "{") != 0) {
return false;
}
while (true) {
if (!game::PC_ReadTokenHandle(handle, &token)) {
return false;
}
if (!game::I_stricmp(token.string, ";")) {
continue;
}
if (!game::I_stricmp(token.string, "}")) {
return true;
}
PARSE_FLOAT_TOKEN(fadeClamp);
PARSE_INT_TOKEN(fadeCycle);
PARSE_FLOAT_TOKEN(fadeAmount);
PARSE_FLOAT_TOKEN(fadeInAmount);
game::PC_SourceError(
handle,
"Unknown token %s in assetGlobalDef. Valid commands are 'fadeClamp', "
"'fadeCycle', 'fadeAmount', and 'fadeInAmount'\n",
token.string);
}
}
void* alloc(int size, int alignment) {
return game::Hunk_AllocAlignInternal(size, alignment);
}
template <int HASH_COUNT, int HASH_SEED>
int keyword_hash_key(const char* keyword) {
auto hash = 0;
for (auto i = 0; keyword[i]; ++i) {
hash +=
(i + HASH_SEED) * std::tolower(static_cast<unsigned char>(keyword[i]));
}
return (hash + (hash >> 8)) & (128 - 1);
}
template <typename T, int N, int M>
KeywordHashEntry<T, N, M>* keyword_hash_find(KeywordHashEntry<T, N, M>** table,
const char* keyword) {
auto hash = keyword_hash_key<N, M>(keyword);
KeywordHashEntry<T, N, M>* key = table[hash];
if (key && !game::I_stricmp(key->keyword, keyword)) {
return key;
}
return nullptr;
}
int menu_parse(int handle, game::menuDef_t* menu) {
game::pc_token_s token{};
if (!game::PC_ReadTokenHandle(handle, &token))
return false;
if (*token.string != '{')
return false;
while (true) {
std::memset(&token, 0, sizeof(game::pc_token_s));
if (!game::PC_ReadTokenHandle(handle, &token)) {
game::PC_SourceError(handle, "end of file inside menu\n");
return false;
}
if (*token.string == '}') {
return true;
}
auto* key = keyword_hash_find(menu_parse_keyword_hash, token.string);
if (!key) {
game::PC_SourceError(handle, "unknown menu keyword %s", token.string);
continue;
}
if (!key->func(menu, handle)) {
game::PC_SourceError(handle, "couldn't parse menu keyword %s",
token.string);
return false;
}
}
}
void item_set_screen_coords([[maybe_unused]] int context_index,
game::itemDef_s* item, float x, float y,
int horz_align, int vert_align) {
assert(item);
if (!item) {
return;
}
if (item->window.border) {
x += item->window.borderSize;
y += item->window.borderSize;
}
item->window.rect = item->window.rectClient;
item->window.rect.x = item->window.rect.x + x;
item->window.rect.y = item->window.rect.y + y;
if (!item->window.rect.horzAlign && !item->window.rect.vertAlign) {
item->window.rect.horzAlign = static_cast<char>(horz_align);
item->window.rect.vertAlign = static_cast<char>(vert_align);
}
}
game::rectDef_s* window_get_rect(game::windowDef_t* w) {
assert(w);
return &w->rect;
}
void menu_update_position(int context_index, game::menuDef_t* menu) {
if (menu == nullptr) {
return;
}
const auto* rect = window_get_rect(&menu->window);
float x = rect->x;
float y = rect->y;
if (menu->window.border) {
x += menu->window.borderSize;
y += menu->window.borderSize;
}
for (int i = 0; i < menu->itemCount; ++i) {
item_set_screen_coords(context_index, menu->items[i], x, y, rect->horzAlign,
rect->vertAlign);
}
}
void menu_post_parse(game::menuDef_t* menu) {
assert(menu);
const auto item_count = 4 * menu->itemCount;
menu->items = static_cast<game::itemDef_s**>(alloc(item_count, 4));
std::memcpy(menu->items, g_load.items, item_count);
if (menu->fullScreen) {
menu->window.rect.x = 0.0f;
menu->window.rect.y = 0.0f;
menu->window.rect.w = 640.0f;
menu->window.rect.h = 480.0f;
}
menu_update_position(0, menu);
}
void menu_set_cursor_item(int context_index, game::menuDef_t* menu,
int cursor_item) {
assert(context_index < game::MAX_POSSIBLE_LOCAL_CLIENTS);
assert(menu);
menu->cursorItem[context_index] = cursor_item;
}
void window_init(game::windowDef_t* w) {
std::memset(w, 0, sizeof(game::windowDef_t));
w->borderSize = 1.0f;
w->foreColor[0] = w->foreColor[1] = w->foreColor[2] = w->foreColor[3] = 1.0f;
}
void menu_init(game::menuDef_t* menu) {
std::memset(menu, 0, sizeof(game::menuDef_t));
menu_set_cursor_item(0, menu, -1);
menu->fadeAmount = g_load.loadAssets.fadeAmount;
menu->fadeInAmount = g_load.loadAssets.fadeInAmount;
menu->fadeClamp = g_load.loadAssets.fadeClamp;
menu->fadeCycle = g_load.loadAssets.fadeCycle;
menu->items = g_load.items;
window_init(&menu->window);
}
void menu_free_expression_supporting_data(
game::ExpressionSupportingData* data) {
if (!data) {
return;
}
for (auto i = 0; i < data->uifunctions.totalFunctions; ++i) {
auto* function = data->uifunctions.functions[i];
FREE_STATEMENT(function);
}
data->uifunctions.totalFunctions = 0;
}
void menu_free_event_handler(game::MenuEventHandlerSet* event_handler) {
if (!event_handler) {
return;
}
for (auto i = 0; i < event_handler->eventHandlerCount; ++i) {
auto* event = event_handler->eventHandlers[i];
game::ConditionalScript* conditional_script;
game::MenuEventHandlerSet* else_script;
game::SetLocalVarData* local_var;
switch (event->eventType) {
case game::EVENT_IF:
conditional_script = event->eventData.conditionalScript;
menu_free_event_handler(conditional_script->eventHandlerSet);
FREE_STATEMENT(conditional_script->eventExpression);
break;
case game::EVENT_ELSE:
else_script = event->eventData.elseScript;
menu_free_event_handler(else_script);
break;
case game::EVENT_SET_LOCAL_VAR_BOOL:
case game::EVENT_SET_LOCAL_VAR_INT:
case game::EVENT_SET_LOCAL_VAR_FLOAT:
case game::EVENT_SET_LOCAL_VAR_STRING:
local_var = event->eventData.setLocalVarData;
FREE_STATEMENT(local_var->expression);
break;
default:
break;
}
}
event_handler->eventHandlerCount = 0;
}
void menu_free_item_key_handler(const game::ItemKeyHandler* key_handler) {
while (key_handler) {
menu_free_event_handler(key_handler->action);
key_handler = key_handler->next;
}
}
void menu_free_memory(game::menuDef_t* menu) {
if (!menu) {
return;
}
for (auto i = 0; i < menu->itemCount; ++i) {
auto* item = menu->items[i];
FREE_STATEMENT(item->visibleExp);
FREE_STATEMENT(item->disabledExp);
FREE_STATEMENT(item->textExp);
FREE_STATEMENT(item->materialExp);
menu_free_event_handler(item->mouseEnterText);
menu_free_event_handler(item->mouseExitText);
menu_free_event_handler(item->mouseEnter);
menu_free_event_handler(item->mouseExit);
menu_free_event_handler(item->action);
menu_free_event_handler(item->accept);
menu_free_event_handler(item->onFocus);
menu_free_event_handler(item->leaveFocus);
menu_free_item_key_handler(item->onKey);
// free ItemFloatExpression*
game::Menu_FreeItemMemory(item);
if (item->dataType == ITEM_TYPE_LISTBOX) {
menu_free_event_handler(item->typeData.listBox->onDoubleClick);
}
}
menu->itemCount = 0;
menu_free_event_handler(menu->onOpen);
menu_free_event_handler(menu->onCloseRequest);
menu_free_event_handler(menu->onClose);
menu_free_event_handler(menu->onESC);
menu_free_item_key_handler(menu->onKey);
FREE_STATEMENT(menu->visibleExp);
FREE_STATEMENT(menu->rectXExp);
FREE_STATEMENT(menu->rectYExp);
FREE_STATEMENT(menu->rectWExp);
FREE_STATEMENT(menu->rectHExp);
FREE_STATEMENT(menu->openSoundExp);
FREE_STATEMENT(menu->closeSoundExp);
// Our menu compiler code does not support parsing 'ui functions'
if (!menu->expressionData) {
return;
}
for (auto i = 0; i < menu->expressionData->uifunctions.totalFunctions; ++i) {
auto* function_statement = menu->expressionData->uifunctions.functions[i];
FREE_STATEMENT(function_statement);
}
menu->expressionData->uifunctions.totalFunctions = 0;
}
void menus_free_all_memory(game::UiContext* dc) {
for (auto menu = 0; menu < dc->menuCount; ++menu) {
menu_free_memory(dc->Menus[menu]);
}
}
bool menu_new(int handle) {
auto* menu = static_cast<game::menuDef_t*>(alloc(sizeof(game::menuDef_t), 4));
menu_init(menu);
if (!menu_parse(handle, menu)) {
menu_free_memory(menu);
return false;
}
if (!menu->window.name) {
game::PC_SourceError(handle, "menu has no name");
menu_free_memory(menu);
return false;
}
menu_post_parse(menu);
if (static_cast<std::size_t>(g_load.menuList.menuCount) >=
std::extent_v<decltype(g_load.menus)>) {
game::Com_Error(game::ERR_DROP, "\x15"
"Menu_New: "
"\x14"
"EXE_ERR_OUT_OF_MEMORY");
}
g_load.menuList.menus[g_load.menuList.menuCount++] = menu;
loaded_menus_list.emplace_back(menu);
return true;
}
bool parse_menu_internal(const char* menu_file) {
const char* builtin_defines[2];
game::pc_token_s token;
builtin_defines[0] = "PC";
builtin_defines[1] = nullptr;
game::Com_Printf(game::CON_CHANNEL_UI, "\tLoading '%s'...\n", menu_file);
const auto handle = pc::load_source_handle(menu_file, builtin_defines);
if (!handle) {
game::Com_PrintError(game::CON_CHANNEL_UI, "Couldn't find menu file '%s'\n",
menu_file);
return false;
}
while (game::PC_ReadTokenHandle(handle, &token)) {
if (!game::I_stricmp(token.string, "}") ||
!game::I_stricmp(token.string, "{")) {
continue;
}
if (!game::I_stricmp(token.string, "assetGlobalDef")) {
asset_parse(handle);
continue;
}
if (!game::I_stricmp(token.string, "menudef")) {
menu_new(handle);
continue;
}
game::PC_SourceError(handle,
"Unknown token %s in menu file. Expected \"menudef\" "
"or \"assetglobaldef\".\n",
token.string);
}
pc::free_source_handle(handle);
return true;
}
int load_menu(const char** p) {
if (*game::Com_Parse(p) != '{') {
return false;
}
for (;;) {
const auto* token = game::Com_Parse(p);
if (!game::I_stricmp(token, "}")) {
return true;
}
if (!token || !*token) {
break;
}
parse_menu_internal(token);
}
return false;
}
game::MenuList* load_menus_load_obj(const char* menu_file) {
std::memset(&g_load, 0, sizeof(g_load));
g_load.menuList.menus = g_load.menus;
auto f = 0;
auto len = game::FS_FOpenFileRead(menu_file, &f);
if (!f) {
game::Com_PrintWarning(game::CON_CHANNEL_UI,
"WARNING: menu file not found: %s\n", menu_file);
#ifdef UI_LOAD_DEFAULT_MENU
len = game::FS_FOpenFileByMode("ui/default.menu", &f, game::FS_READ);
if (!f) {
game::Com_Error(game::ERR_SERVERDISCONNECT,
"\x15"
"default.menu file not found. This is a default menu "
"that you should have.\n");
return nullptr;
}
#else
return nullptr;
#endif
}
if (static_cast<std::size_t>(len) >= sizeof(menu_buf)) {
game::FS_FCloseFile(f);
game::Com_Error(game::ERR_SERVERDISCONNECT,
"\x15^1menu file too large: %s is %i, max allowed is %i",
menu_file, len, sizeof(menu_buf));
}
game::FS_Read(menu_buf, len, f);
menu_buf[len] = '\0';
game::FS_FCloseFile(f);
game::Com_Compress(menu_buf);
const auto* p = menu_buf;
game::Com_BeginParseSession(menu_file);
for (auto* token = game::Com_Parse(&p); token; token = game::Com_Parse(&p)) {
if (!*token || *token == '}' || !game::I_stricmp(token, "}") ||
!game::I_stricmp(token, "loadmenu") && !load_menu(&p)) {
break;
}
}
game::Com_EndParseSession();
return &g_load.menuList;
}
game::MenuList* load_menu_load_obj(const char* menu_file) {
std::memset(&g_load, 0, sizeof(g_load));
g_load.menuList.menus = g_load.menus;
if (!parse_menu_internal(menu_file)) {
game::Com_PrintWarning(game::CON_CHANNEL_UI,
"WARNING: menu file not found: %s\n", menu_file);
#ifdef UI_LOAD_DEFAULT_MENU
if (!parse_menu_internal("ui/default.menu")) {
game::Com_Error(game::ERR_SERVERDISCONNECT,
"\x15"
"default.menu file not found. This is a default menu "
"that you should have.\n");
}
#else
return nullptr;
#endif
}
return &g_load.menuList;
}
game::MenuList* load_menus_fast_file(const char* menu_file) {
return game::DB_FindXAssetHeader(game::ASSET_TYPE_MENULIST, menu_file)
.menuList;
}
game::MenuList* load_menus_stub(const char* menu_file) {
auto* menu_list = load_menus_load_obj(menu_file);
if (!menu_list) {
menu_list = load_menus_fast_file(menu_file);
}
return menu_list;
}
game::MenuList* load_menu_fast_file(const char* menu_file) {
return game::DB_FindXAssetHeader(game::ASSET_TYPE_MENULIST, menu_file)
.menuList;
}
game::MenuList* load_menu_stub(const char* menu_file) {
auto* menu_list = load_menu_load_obj(menu_file);
if (!menu_list) {
menu_list = load_menu_fast_file(menu_file);
}
return menu_list;
}
void snd_fade_all_sounds_stub(float volume, int fade_time) {
utils::hook::invoke<void>(0x4206A0, volume, fade_time);
for (auto& menu : loaded_menus_list) {
menu_free_memory(menu);
}
loaded_menus_list.clear();
}
void ui_shutdown_stub() {
for (auto& menu : loaded_menus_list) {
menu_free_memory(menu);
}
loaded_menus_list.clear();
utils::hook::invoke<void>(0x4CBD70);
}
} // namespace
class component final : public component_interface {
public:
static_assert(offsetof(game::UiContext, menuCount) == 0xA38);
static_assert(offsetof(game::UiContext, Menus) == 0x38);
static_assert(offsetof(game::menuDef_t, itemCount) == 0xAC);
static_assert(offsetof(game::itemDef_s, dataType) == 0xBC);
static_assert(offsetof(game::itemDef_s, typeData) == 0x134);
static_assert(offsetof(game::itemDef_s, floatExpressionCount) == 0x13C);
static_assert(offsetof(game::itemDef_s, floatExpressions) == 0x140);
void post_load() override {
menu_parse_keyword_hash =
reinterpret_cast<KeywordHashEntry<game::menuDef_t, 128, 3523>**>(
0x1933DB0);
patch_sp();
}
void pre_destroy() override { assert(loaded_menus_list.empty()); }
private:
static void patch_sp() {
utils::hook(0x62DDD0, load_menus_stub, HOOK_JUMP).install()->quick();
utils::hook::nop(0x62DDD0 + 5, 3);
utils::hook(0x621194, load_menu_stub, HOOK_CALL).install()->quick();
// Remove the menus from memory (CG_Shutdown)
utils::hook(0x447905, snd_fade_all_sounds_stub, HOOK_CALL)
.install() // hook*
->quick();
// Remove the menus from memory (UI_Shutdown)
utils::hook(0x4F4C4F, ui_shutdown_stub, HOOK_CALL)
.install() // hook*
->quick();
}
};
} // namespace ui
REGISTER_COMPONENT(ui::component)

View File

@ -0,0 +1,74 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
namespace ultra_wide {
const game::dvar_t* r_customAspectRatio;
const game::dvar_t* dvar_register_aspect_ratio(const char* dvar_name,
const char** /*value_list*/,
int default_index,
unsigned __int16 flags,
const char* description) {
static const char* values[] = {"auto", "standard", "wide 16:10",
"wide 16:9", "custom", nullptr};
// register enumeration dvar
return game::Dvar_RegisterEnum(dvar_name, values, default_index, flags,
description);
}
void set_aspect_ratio() {
*reinterpret_cast<float*>(0x1C91A78) = r_customAspectRatio->current.value;
}
__declspec(naked) void set_aspect_ratio_stub() {
__asm {
mov eax, [eax + 0x10];
cmp eax, 4;
mov dword ptr ds:0x1C91A68, edx;
mov dword ptr ds:0x1C91A6C, esi;
mov dword ptr ds:0x1C91A74, ecx;
ja default_case;
je custom_ratio;
push 0x50AE6C;
ret;
default_case:
push 0x50AF6C;
ret;
custom_ratio:
pushad;
call set_aspect_ratio;
popad;
mov eax, 1; // set widescreen to 1
push 0x50AF05;
ret;
}
}
class component final : public component_interface {
public:
void post_load() override {
utils::hook(0x51E80B, &dvar_register_aspect_ratio, HOOK_CALL)
.install()
->quick();
utils::hook(0x50AE4E, &set_aspect_ratio_stub, HOOK_JUMP).install()->quick();
utils::hook::nop(0x50AE4E + 5, 1);
r_customAspectRatio = game::Dvar_RegisterFloat(
"r_customAspectRatio", 16.0f / 9.0f, 4.0f / 3.0f, 63.0f / 9.0f,
game::DVAR_ARCHIVE, "Screen aspect ratio");
}
};
} // namespace ultra_wide
REGISTER_COMPONENT(ultra_wide::component)

View File

@ -0,0 +1,48 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
namespace weapons {
namespace {
game::WeaponCompleteDef*
bg_load_weapon_variant_def_fast_file_stub(const char* name) {
if (auto* raw_weapon_file =
game::BG_LoadWeaponVariantDefInternal("sp", name)) {
return raw_weapon_file;
}
auto* zone_weapon_file =
game::DB_FindXAssetHeader(game::ASSET_TYPE_WEAPON, name).weapon;
return game::DB_IsXAssetDefault(game::ASSET_TYPE_WEAPON, name)
? nullptr
: zone_weapon_file;
}
} // namespace
class component final : public component_interface {
public:
void post_load() override { patch_sp(); }
private:
static void patch_sp() {
// Weapon asset existence check
utils::hook::nop(0x43E1D8, 5);
// Is asset default
utils::hook::nop(0x43E1E0, 5);
// Do not jump
utils::hook::nop(0x43E1EA, 2);
// Ignore missing default weapon
utils::hook::set<std::uint8_t>(0x659DBE, 0xEB);
utils::hook(0x659E00, bg_load_weapon_variant_def_fast_file_stub, HOOK_JUMP)
.install()
->quick();
// Disable warning if raw weapon file cannot be found
utils::hook::nop(0x659730, 5);
}
};
} // namespace weapons
REGISTER_COMPONENT(weapons::component)

View File

@ -0,0 +1,25 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include <utils/hook.hpp>
namespace zone {
class component final : public component_interface {
public:
static_assert(sizeof(game::XZoneInfo) == 0xC);
void post_load() override { patch_sp(); }
private:
static void patch_sp() {
// Ignore zone version mismatch
utils::hook::set<std::uint8_t>(0x4256D8, 0xEB);
// Disc read error
utils::hook::nop(0x4B7335, 2);
utils::hook::set<std::uint8_t>(0x4256B9, 0xEB);
}
};
} // namespace zone
REGISTER_COMPONENT(zone::component)

38
src/client/game/dvars.cpp Normal file
View File

@ -0,0 +1,38 @@
#include <std_include.hpp>
namespace dvars {
const game::dvar_t* r_noBorder = nullptr;
const game::dvar_t* pm_bounce = nullptr;
const game::dvar_t* pm_bouncingAllAngles = nullptr;
const game::dvar_t* pm_rocketJump = nullptr;
const game::dvar_t* pm_rocketJumpScale = nullptr;
const game::dvar_t* pm_playerCollision = nullptr;
const game::dvar_t* pm_elevators = nullptr;
const game::dvar_t* pm_disableLandingSlowdown = nullptr;
const game::dvar_t* pm_bunnyHop = nullptr;
const game::dvar_t* pm_snapVector = nullptr;
const game::dvar_t* cg_drawVersion = nullptr;
const game::dvar_t* cg_drawVersionX = nullptr;
const game::dvar_t* cg_drawVersionY = nullptr;
const game::dvar_t* bug_name = nullptr;
const game::dvar_t* g_log = nullptr;
// Game dvars
const game::dvar_t** g_specialops =
reinterpret_cast<const game::dvar_t**>(0x1B2E1E8);
const game::dvar_t** sv_mapname =
reinterpret_cast<const game::dvar_t**>(0x1B2E1E4);
const game::dvar_t** version =
reinterpret_cast<const game::dvar_t**>(0x145D690);
const game::dvar_t** com_developer =
reinterpret_cast<const game::dvar_t**>(0x145D648);
const game::dvar_t** com_developer_script =
reinterpret_cast<const game::dvar_t**>(0x145EC58);
} // namespace dvars

33
src/client/game/dvars.hpp Normal file
View File

@ -0,0 +1,33 @@
#pragma once
namespace dvars {
extern const game::dvar_t* r_noBorder;
extern const game::dvar_t* pm_bounce;
extern const game::dvar_t* pm_bouncingAllAngles;
extern const game::dvar_t* pm_rocketJump;
extern const game::dvar_t* pm_rocketJumpScale;
extern const game::dvar_t* pm_playerCollision;
extern const game::dvar_t* pm_elevators;
extern const game::dvar_t* pm_disableLandingSlowdown;
extern const game::dvar_t* pm_bunnyHop;
extern const game::dvar_t* pm_snapVector;
extern const game::dvar_t* cg_drawVersion;
extern const game::dvar_t* cg_drawVersionX;
extern const game::dvar_t* cg_drawVersionY;
extern const game::dvar_t* bug_name;
extern const game::dvar_t* g_log;
// Game dvars
extern const game::dvar_t** g_specialops;
extern const game::dvar_t** sv_mapname;
extern const game::dvar_t** version;
extern const game::dvar_t** com_developer;
extern const game::dvar_t** com_developer_script;
} // namespace dvars

View File

@ -0,0 +1,35 @@
#include <std_include.hpp>
#include <game/game.hpp>
#include "fast_critical_section.hpp"
namespace game::engine {
fast_critical_section_scope_read::fast_critical_section_scope_read(
FastCriticalSection* cs) noexcept
: cs_(cs) {
if (this->cs_) {
Sys_LockRead(this->cs_);
}
}
fast_critical_section_scope_read::~fast_critical_section_scope_read() noexcept {
if (this->cs_) {
Sys_UnlockRead(this->cs_);
}
}
fast_critical_section_scope_write::fast_critical_section_scope_write(
FastCriticalSection* cs) noexcept
: cs_(cs) {
if (this->cs_) {
Sys_LockWrite(this->cs_);
}
}
fast_critical_section_scope_write::
~fast_critical_section_scope_write() noexcept {
if (this->cs_) {
Sys_UnlockWrite(this->cs_);
}
}
} // namespace game::engine

View File

@ -0,0 +1,21 @@
#pragma once
namespace game::engine {
class fast_critical_section_scope_read {
public:
fast_critical_section_scope_read(FastCriticalSection* cs) noexcept;
~fast_critical_section_scope_read() noexcept;
private:
FastCriticalSection* cs_;
};
class fast_critical_section_scope_write {
public:
fast_critical_section_scope_write(FastCriticalSection* cs) noexcept;
~fast_critical_section_scope_write() noexcept;
private:
FastCriticalSection* cs_;
};
} // namespace game::engine

View File

@ -0,0 +1,87 @@
#include <std_include.hpp>
#include "large_local.hpp"
namespace game::engine {
namespace {
constexpr auto PAGE_SIZE = 4096;
int can_use_server_large_local() { return Sys_IsServerThread(); }
void large_local_end(int start_pos) {
assert(Sys_IsMainThread());
assert(g_largeLocalBuf);
*g_largeLocalPos = start_pos;
assert(((*g_maxLargeLocalPos + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1)) <=
(*g_minLargeLocalRightPos & ~(PAGE_SIZE - 1)));
}
void large_local_end_right(int start_pos) {
assert(can_use_server_large_local());
assert(g_largeLocalBuf);
*g_largeLocalRightPos = start_pos;
assert(((*g_maxLargeLocalPos + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1)) <=
(*g_minLargeLocalRightPos & ~(PAGE_SIZE - 1)));
}
void* large_local_get_buf(int start_pos, int size) {
assert(Sys_IsMainThread() || can_use_server_large_local());
assert(g_largeLocalBuf);
assert(!(size & 127));
if (Sys_IsMainThread()) {
return &g_largeLocalBuf[start_pos];
}
const auto start_index = start_pos - size;
assert(start_index >= 0);
return &g_largeLocalBuf[start_index];
}
} // namespace
large_local::large_local(int size_param) {
assert(size_param);
assert(Sys_IsMainThread() || can_use_server_large_local());
size_param = ((size_param + (128 - 1)) & ~(128 - 1));
if (Sys_IsMainThread()) {
this->start_pos_ = LargeLocalBegin(size_param);
} else {
this->start_pos_ = LargeLocalBeginRight(size_param);
}
this->size_ = size_param;
}
large_local::~large_local() {
if (this->size_) {
this->pop_buf();
}
}
void large_local::pop_buf() {
assert(this->size_);
assert(Sys_IsMainThread() || can_use_server_large_local());
if (Sys_IsMainThread()) {
large_local_end(this->start_pos_);
} else {
large_local_end_right(this->start_pos_);
}
this->size_ = 0;
}
void* large_local::get_buf() const {
assert(this->size_);
assert(Sys_IsMainThread() || can_use_server_large_local());
return large_local_get_buf(this->start_pos_, this->size_);
}
} // namespace game::engine

View File

@ -0,0 +1,22 @@
#pragma once
namespace game::engine {
class large_local {
public:
explicit large_local(int size_param);
~large_local();
large_local(large_local&&) = delete;
large_local(const large_local&) = delete;
large_local& operator=(large_local&&) = delete;
large_local& operator=(const large_local&) = delete;
[[nodiscard]] void* get_buf() const;
private:
void pop_buf();
int start_pos_;
int size_;
};
} // namespace game::engine

View File

@ -0,0 +1,65 @@
#include <std_include.hpp>
#include "scoped_critical_section.hpp"
namespace game::engine {
scoped_critical_section::scoped_critical_section(
const CriticalSection s, const ScopedCriticalSectionType type) noexcept
: s_(s), is_scoped_release_(false) {
if (type == SCOPED_CRITSECT_NORMAL) {
Sys_EnterCriticalSection(this->s_);
this->has_ownership_ = true;
} else {
if (type == SCOPED_CRITSECT_TRY) {
this->has_ownership_ = Sys_TryEnterCriticalSection(this->s_);
} else {
if (type == SCOPED_CRITSECT_RELEASE) {
Sys_LeaveCriticalSection(this->s_);
this->is_scoped_release_ = true;
}
this->has_ownership_ = false;
}
}
}
scoped_critical_section::~scoped_critical_section() noexcept {
if (!this->has_ownership_ || this->is_scoped_release_) {
if (!this->has_ownership_ && this->is_scoped_release_) {
Sys_EnterCriticalSection(this->s_);
}
} else {
Sys_LeaveCriticalSection(this->s_);
}
}
void scoped_critical_section::enter_crit_sect() noexcept {
assert(!this->has_ownership_);
this->has_ownership_ = true;
Sys_EnterCriticalSection(this->s_);
}
void scoped_critical_section::leave_crit_sect() noexcept {
assert(this->has_ownership_);
this->has_ownership_ = false;
Sys_LeaveCriticalSection(this->s_);
}
bool scoped_critical_section::try_enter_crit_sect() noexcept {
assert(!this->has_ownership_);
const auto result = Sys_TryEnterCriticalSection(this->s_);
this->has_ownership_ = result;
return result;
}
bool scoped_critical_section::has_ownership() const noexcept {
return this->has_ownership_;
}
bool scoped_critical_section::is_scoped_release() const noexcept {
return this->is_scoped_release_;
}
} // namespace game::engine

View File

@ -0,0 +1,22 @@
#pragma once
namespace game::engine {
class scoped_critical_section {
public:
scoped_critical_section(CriticalSection s,
ScopedCriticalSectionType type) noexcept;
~scoped_critical_section() noexcept;
void enter_crit_sect() noexcept;
void leave_crit_sect() noexcept;
bool try_enter_crit_sect() noexcept;
[[nodiscard]] bool has_ownership() const noexcept;
[[nodiscard]] bool is_scoped_release() const noexcept;
private:
CriticalSection s_;
bool has_ownership_;
bool is_scoped_release_;
};
} // namespace game::engine

139
src/client/game/game.cpp Normal file
View File

@ -0,0 +1,139 @@
#include <std_include.hpp>
namespace game {
int FS_FOpenFileReadForThread(const char* filename, int* file,
FsThread thread) {
const static DWORD FS_FOpenFileReadForThread_t = 0x630380;
int result{};
__asm {
pushad;
mov eax, file;
push thread;
push filename;
call FS_FOpenFileReadForThread_t;
add esp, 0x8;
mov result, eax;
popad;
}
return result;
}
void IN_KeyDown(kbutton_t* b) {
const static DWORD IN_KeyDown_t = 0x57A350;
__asm {
pushad;
mov esi, b;
call IN_KeyDown_t;
popad;
}
}
void IN_KeyUp(kbutton_t* b) {
const static DWORD IN_KeyUp_t = 0x57A3F0;
__asm {
pushad;
mov esi, b;
call IN_KeyUp_t;
popad;
}
}
bool ScrPlace_IsFullScreenActive() {
return *activeScreenPlacementMode == SCRMODE_FULL;
}
ScreenPlacement* ScrPlace_GetUnsafeFullPlacement() {
return scrPlaceFullUnsafe;
}
bool Sys_TryEnterCriticalSection(CriticalSection critSect) {
assert(static_cast<std::uint32_t>(critSect) <
static_cast<std::uint32_t>(CRITSECT_COUNT));
return TryEnterCriticalSection(&s_criticalSection[critSect]) != FALSE;
}
void Sys_LockRead(FastCriticalSection* critSect) {
InterlockedIncrement(&critSect->readCount);
while (critSect->writeCount) {
Sys_Sleep(0);
}
}
void Sys_UnlockRead(FastCriticalSection* critSect) {
assert(critSect->readCount > 0);
InterlockedDecrement(&critSect->readCount);
}
void Sys_UnlockWrite(FastCriticalSection* critSect) {
assert(critSect->writeCount > 0);
InterlockedDecrement(&critSect->writeCount);
Sys_TempPriorityEnd(&critSect->tempPriority);
}
void Sys_SnapVector(float* v) {
v[0] = std::floorf(v[0] + 0.5f);
v[1] = std::floorf(v[1] + 0.5f);
v[2] = std::floorf(v[2] + 0.5f);
}
int PC_Int_Parse(int handle, int* i) {
const static DWORD PC_Int_Parse_t = 0x62DF10;
int result{};
__asm {
pushad;
mov eax, handle;
mov esi, i;
call PC_Int_Parse_t;
mov result, eax;
popad;
}
return result;
}
int PC_Float_Parse(int handle, float* f) {
const static DWORD PC_Float_Parse_t = 0x62DE40;
int result{};
__asm {
pushad;
mov eax, handle;
mov esi, f;
call PC_Float_Parse_t;
mov result, eax;
popad;
}
return result;
}
void Menu_FreeItemMemory(itemDef_s* item) {
const static DWORD Menu_FreeItemMemory_t = 0x62B7E0;
__asm {
pushad;
mov edi, item;
call Menu_FreeItemMemory_t;
popad;
}
}
} // namespace game

63
src/client/game/game.hpp Normal file
View File

@ -0,0 +1,63 @@
#pragma once
namespace game {
template <typename T> struct base_symbol {
base_symbol(const std::size_t address) : address_(address) {}
[[nodiscard]] T* get() const { return reinterpret_cast<T*>(this->address_); }
operator T*() const { return this->get(); }
T* operator->() const { return this->get(); }
private:
std::size_t address_{};
};
template <typename T> struct symbol : base_symbol<T> {
using base_symbol<T>::base_symbol;
};
template <typename T, typename... Args>
struct symbol<T(Args...)> : base_symbol<T(Args...)> {
using func_type = T(Args...);
using base_symbol<func_type>::base_symbol;
};
// Functions that require asm stubs to be called
// or have been inlined
int FS_FOpenFileReadForThread(const char* filename, int* file, FsThread thread);
void IN_KeyDown(kbutton_t* b);
void IN_KeyUp(kbutton_t* b);
bool ScrPlace_IsFullScreenActive();
ScreenPlacement* ScrPlace_GetUnsafeFullPlacement();
bool Sys_TryEnterCriticalSection(CriticalSection critSect);
void Sys_LockRead(FastCriticalSection* critSect);
void Sys_UnlockRead(FastCriticalSection* critSect);
void Sys_UnlockWrite(FastCriticalSection* critSect);
void Sys_SnapVector(float* v);
int PC_Int_Parse(int handle, int* i);
int PC_Float_Parse(int handle, float* f);
void Menu_FreeItemMemory(itemDef_s* item);
// Global definitions
constexpr auto CMD_MAX_NESTING = 8;
constexpr auto MAX_POSSIBLE_LOCAL_CLIENTS = 1;
constexpr std::size_t MAX_LOCAL_CLIENTS = 1;
constexpr auto MAX_QPATH = 64;
constexpr auto MAX_OPCODE_LOOKUP_SIZE = 0x1000000;
constexpr auto MAX_SOURCEPOS_LOOKUP_SIZE = 0x800000;
constexpr auto MAX_SOURCEBUF_LOOKUP_SIZE = 0x40000;
} // namespace game
#include "symbols.hpp"

1690
src/client/game/structs.hpp Normal file

File diff suppressed because it is too large Load Diff

321
src/client/game/symbols.hpp Normal file
View File

@ -0,0 +1,321 @@
#pragma once
#define WEAK __declspec(selectany)
namespace game {
// Com
WEAK symbol<void(int channel, const char* fmt, ...)> Com_Printf{0x41BD20};
WEAK symbol<void(int channel, const char* fmt, ...)> Com_PrintWarning{0x406320};
WEAK symbol<void(int channel, const char* fmt, ...)> Com_PrintError{0x4C6980};
WEAK symbol<void(int channel, const char* fmt, ...)> Com_DPrintf{0x42B1F0};
WEAK symbol<void(int channel, const char* msg, int error)> Com_PrintMessage{
0x456B70};
WEAK symbol<void(errorParm_t code, const char* fmt, ...)> Com_Error{0x43DD90};
WEAK symbol<void()> Com_OpenLogFile{0x603030};
WEAK symbol<int(char* data_p)> Com_Compress{0x4316A0};
WEAK symbol<void()> Com_EventLoop{0x4987C0};
WEAK symbol<void()> Com_ServerPacketEvent{0x47FD30};
WEAK symbol<void(const char* filename)> Com_BeginParseSession{0x4A5C90};
WEAK symbol<void()> Com_EndParseSession{0x4D12C0};
WEAK symbol<const char*(const char** data_p)> Com_Parse{0x486600};
WEAK symbol<const char*(const char* fmt, ...)> va{0x4869F0};
// Con
WEAK symbol<bool(const char* cmd)> Con_IsDvarCommand{0x4B6610};
// Sys
WEAK symbol<void(const char* exeName)> Sys_QuitAndStartProcess{0x4D69A0};
WEAK symbol<void(CriticalSection critSect)> Sys_EnterCriticalSection{0x4A4CD0};
WEAK symbol<void(CriticalSection critSect)> Sys_LeaveCriticalSection{0x4F78E0};
WEAK symbol<void(TempPriority*)> Sys_TempPriorityEnd{0x4FB800};
WEAK symbol<void(FastCriticalSection* critSect)> Sys_LockWrite{0x43C1D0};
WEAK symbol<int()> Sys_Milliseconds{0x44E130};
WEAK symbol<bool()> Sys_IsMainThread{0x42FA00};
WEAK symbol<bool()> Sys_IsServerThread{0x4590E0};
WEAK symbol<bool()> Sys_IsDatabaseThread{0x4C9380};
WEAK symbol<void(int valueIndex, void* data)> Sys_SetValue{0x483310};
WEAK symbol<void(int msec)> Sys_Sleep{0x4CFBE0};
WEAK symbol<void(const char* error, ...)> Sys_Error{0x40BFF0};
WEAK symbol<short(short l)> BigShort{0x40E7E0};
WEAK symbol<short(short l)> ShortNoSwap{0x4261A0};
WEAK symbol<int(int size)> LargeLocalBegin{0x43fA50};
WEAK symbol<int(int size)> LargeLocalBeginRight{0x6317D0};
// CL
WEAK symbol<int(int localClientNum)> CL_IsCgameInitialized{0x4EEA50};
WEAK symbol<void()> CL_CoOpConnect{0x57D240};
// BG
WEAK symbol<WeaponCompleteDef*(const char* name, const char* folder)>
BG_LoadWeaponVariantDefInternal{0x4F5AF0};
// Game
WEAK symbol<int()> G_GetTime{0x4E94E0};
WEAK symbol<void(int, const char* text)> Cbuf_AddText{0x4A1090};
WEAK symbol<void(int localClientNum, int controllerIndex, const char* text)>
Cmd_ExecuteSingleCommand{0x46AFD0};
WEAK symbol<void(const char* cmdName, void(*function),
cmd_function_s* allocedCmd, int isKey)>
Cmd_AddCommand{0x4478A0};
WEAK symbol<void(const char* cmdName, const char* dir, const char* ext)>
Cmd_SetAutoComplete{0x48A880};
// Dvars
WEAK symbol<dvar_t*(const char* dvarName)> Dvar_FindVar{0x4B29D0};
WEAK symbol<const dvar_t*(const char* dvarName, const char* value,
unsigned __int16 flags, const char* description)>
Dvar_RegisterString{0x49E0B0};
WEAK symbol<const dvar_t*(const char* dvarName, bool value,
unsigned __int16 flags, const char* description)>
Dvar_RegisterBool{0x429390};
WEAK symbol<const dvar_t*(const char* dvarName, const char** valueList,
int defaultIndex, unsigned __int16 flags,
const char* description)>
Dvar_RegisterEnum{0x4CB7C0};
WEAK symbol<const dvar_t*(const char* dvarName, float value, float min,
float max, unsigned __int16 flags,
const char* description)>
Dvar_RegisterFloat{0x4051D0};
WEAK symbol<const dvar_t*(const char* dvarName, int value, int min, int max,
unsigned __int16 flags, const char* description)>
Dvar_RegisterInt{0x4E9490};
WEAK symbol<void(const char* dvarName, double value)> Dvar_SetFloatByName{
0x497250};
WEAK symbol<void(const char* dvarName, const char* value)> Dvar_SetStringByName{
0x440C60};
WEAK symbol<void(const dvar_t* dvar, int value)> Dvar_SetInt{0x4FA540};
WEAK symbol<void(const dvar_t* dvar, bool value)> Dvar_SetBool{0x4E57E0};
WEAK symbol<void(const dvar_t* dvar, const char* value)> Dvar_SetString{
0x480E70};
WEAK symbol<const char*(const char* dvarName)> Dvar_GetString{0x411F50};
WEAK symbol<bool(const char* dvarName)> Dvar_GetBool{0x481010};
// Script
WEAK symbol<void(const char* error)> Scr_Error{0x4E9C50};
WEAK symbol<void(const char* error)> Scr_ObjectError{0x470600};
WEAK symbol<void(unsigned int paramIndex, const char* error)> Scr_ParamError{
0x42C880};
WEAK symbol<void()> Scr_ShutdownAllocNode{0x486BC0};
WEAK symbol<unsigned int(const char* filename)> Scr_CreateCanonicalFilename{
0x43A5E0};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)>
FindVariable{0x4B78B0};
WEAK symbol<void(unsigned int parentId, unsigned int unsignedValue)>
RemoveVariable{0x4C2DD0};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int id)> FindObject{
0x49A980};
WEAK symbol<gentity_s*(scr_entref_t entref)> GetEntity{0x4678C0};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)>
GetVariable{0x482290};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)>
GetNewVariable{0x4F1990};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int id)> GetObject{
0x4370B0};
WEAK symbol<unsigned int()> Scr_GetNumParam{0x4443F0};
WEAK symbol<void()> Scr_ClearOutParams{0x4A3A00};
WEAK symbol<const char*(unsigned int index)> Scr_GetTypeName{0x4CE240};
WEAK symbol<const char*(unsigned int index)> Scr_GetString{0x4D39C0};
WEAK symbol<void(const char* value)> Scr_AddString{0x4CD670};
WEAK symbol<unsigned int(unsigned int index)> Scr_GetConstString{0x4AF1B0};
WEAK symbol<void(unsigned int value)> Scr_AddConstString{0x404CF0};
WEAK symbol<int(unsigned int index)> Scr_GetInt{0x454520};
WEAK symbol<void(int value)> Scr_AddInt{0x4865B0};
WEAK symbol<float(unsigned int index)> Scr_GetFloat{0x4AE590};
WEAK symbol<void(float value)> Scr_AddFloat{0x4986E0};
WEAK symbol<int(unsigned int index)> Scr_GetType{0x464EE0};
WEAK symbol<void(int func, const char* name)> Scr_RegisterFunction{0x4F59C0};
WEAK symbol<unsigned int(unsigned int index)> Scr_GetFunc{0x438E10};
WEAK symbol<int(const char* pos)> Scr_IsInOpcodeMemory{0x47D1D0};
WEAK symbol<char*(const char* filename, const char* extFilename,
const char* codePos, bool archive)>
Scr_AddSourceBuffer{0x4173C0};
WEAK symbol<unsigned int(const char* filename)> Scr_LoadScript{0x46CD90};
WEAK symbol<int(const char* filename, const char* name)> Scr_GetFunctionHandle{
0x462750};
WEAK symbol<int(int handle, unsigned int paramcount)> Scr_ExecThread{0x41A2C0};
WEAK symbol<void(unsigned __int16 handle)> Scr_FreeThread{0x4C44A0};
WEAK symbol<void(sval_u* parseData, unsigned char user)> ScriptParse{0x4956B0};
WEAK symbol<void(sval_u* val, unsigned int filePosId, unsigned int fileCountId,
unsigned int scriptId, PrecacheEntry* entries,
int entriesCount)>
ScriptCompile{0x4FFDA0};
WEAK symbol<char*(int len)> TempMalloc{0x4EA7C0};
// SL
WEAK symbol<const char*(unsigned int stringValue)> SL_ConvertToString{0x40E990};
WEAK symbol<void(unsigned int stringValue)> SL_AddRefToString{0x4C4BD0};
WEAK symbol<void(unsigned int stringValue)> SL_RemoveRefToString{0x4698E0};
// NET
WEAK symbol<const char*(netadr_t a)> NET_AdrToString{0x4BF490};
WEAK symbol<const char*()> NET_ErrorString{0x430390};
// Memory
WEAK symbol<void*(int size)> Hunk_AllocateTempMemory{0x492DF0};
WEAK symbol<void*(int size, int alignment)> Hunk_AllocAlignInternal{0x486C40};
WEAK symbol<void*(int size)> Hunk_AllocateTempMemoryHigh{0x403B40};
WEAK symbol<HunkUser*(int maxSize, const char* name, bool fixed, int type)>
Hunk_UserCreate{0x4F1A10};
WEAK symbol<void*(HunkUser* user, int size, int alignment)> Hunk_UserAlloc{
0x469410};
WEAK symbol<void(Statement_s* statement)> free_expression{0x436260};
WEAK symbol<void(void*)> _free{0x674BC5};
// Zone
WEAK symbol<void*(int size)> Z_VirtualAllocInternal{0x4D9CF0};
WEAK symbol<void*(int size, const char* name)> Z_TryVirtualAllocInternal{
0x4D9590};
WEAK symbol<void(void* ptr)> Z_VirtualFreeInternal{0x4FE260};
// DB
WEAK symbol<XAssetHeader(XAssetType type, const char* name)>
DB_FindXAssetHeader{0x40B200};
WEAK symbol<int(XAssetType type, const char* name)> DB_IsXAssetDefault{
0x41AB70};
WEAK symbol<void(RawFile* rawfile, char* buffer, int size)> DB_GetRawBuffer{
0x4345E0};
WEAK symbol<void(XZoneInfo* zoneInfo, unsigned int zoneCount,
unsigned int syncMode)>
DB_LoadXAssets{0x4CFC90};
WEAK symbol<char*(const char* filename, char* buf, int size)> DB_ReadRawFile{
0x46DA60};
WEAK symbol<int(RawFile* rawfile)> DB_GetRawFileLen{0x4D2E60};
// FS
WEAK symbol<int(const char* qpath, void** buffer)> _FS_ReadFile{0x4A5480};
WEAK symbol<unsigned int(void* buffer, int len, int h)> FS_Read{0x42EDC0};
WEAK symbol<int(const void* buffer, int len, int h)> FS_Write{0x449FA0};
WEAK symbol<void(int h)> FS_FCloseFile{0x44E0A0};
WEAK symbol<int(const char* qpath, int* f, fsMode_t mode)> FS_FOpenFileByMode{
0x41DF70};
WEAK symbol<int(const char* filename, int* file)> FS_FOpenFileRead{0x48DD10};
WEAK symbol<const char**(const char* path, const char* extension,
FsListBehavior_e behavior, int* numfiles,
int allocTrackType)>
FS_ListFiles{0x4448F0};
WEAK symbol<void(const char** list, int allocTrackType)> FS_FreeFileList{
0x41C7A0};
WEAK symbol<void(const char* base, const char* game, const char* qpath,
char* ospath)>
FS_BuildOSPath{0x4E48F0};
WEAK symbol<void(const char* gameName)> FS_Startup{0x47AF20};
// UI
WEAK symbol<Font_s*(const ScreenPlacement* scrPlace, int fontEnum, float scale)>
UI_GetFontHandle{0x4C2600};
WEAK symbol<int(const char* text, int maxChars, Font_s* font, float scale)>
UI_TextWidth{0x4F5070};
WEAK symbol<int(Font_s* font, float scale)> UI_TextHeight{0x407710};
WEAK symbol<void(const ScreenPlacement* scrPlace, const char* text,
int maxChars, Font_s* font, float x, float y, int horzAlign,
int vertAlign, float scale, const float* color, int style)>
UI_DrawText{0x40FC70};
// PC
WEAK symbol<int(source_s* source)> PC_Directive_define{0x4F8CF0};
WEAK symbol<void(define_s* define)> PC_FreeDefine{0x464F40};
WEAK symbol<token_s*(token_s* token)> PC_CopyToken{0x4D3670};
WEAK symbol<int(int handle, pc_token_s* pc_token)> PC_ReadTokenHandle{0x46C3B0};
WEAK symbol<void(int handle, const char* format, ...)> PC_SourceError{0x43A6D0};
WEAK symbol<void*(unsigned int size)> GetMemory{0x441880};
WEAK symbol<void*(unsigned int size)> GetClearedMemory{0x41BCD0};
WEAK symbol<void(void* ptr)> FreeMemory{0x4A7D20};
// PM
WEAK symbol<void(pmove_t* pm, trace_t* results, const float* start,
const float* end, const Bounds* bounds, int passEntityNum,
int contentMask)>
PM_trace{0x4B7A20};
WEAK symbol<void(pmove_t* pm, trace_t* results, const float* start,
const float* end, const Bounds* bounds, int passEntityNum,
int contentMask)>
PM_playerTrace{0x447B90};
WEAK symbol<bool(const playerState_s* ps)> PM_IsSprinting{0x47CF70};
// Live
WEAK symbol<const char*(int controllerIndex)> Live_GetLocalClientName{0x492EF0};
// Info
WEAK symbol<int(const char* s)> Info_Validate{0x425530};
// IW functions, could use Microsoft specific functions but who cares
WEAK symbol<int(const char* s0, const char* s1)> I_stricmp{0x409B80};
WEAK symbol<int(const char* s0, const char* s1, int n)> I_strnicmp{0x491E60};
WEAK symbol<void(field_t* edit)> Field_Clear{0x45C350};
// String
WEAK symbol<int(const char* string)> StringTable_HashString{0x498080};
// Vec3
WEAK symbol<void(const float* v, float scale, const float* result)> Vec3Scale{
0x429220};
// Variables
WEAK symbol<CmdArgs> cmd_args{0x144FED0};
WEAK symbol<CmdArgs> sv_cmd_args{0x145ABA0};
WEAK symbol<gentity_s> g_entities{0xEAAC38};
WEAK symbol<gclient_s> g_clients{0x10911E8};
WEAK symbol<scrVmPub_t> scrVmPub{0x190DDF0};
WEAK symbol<scrVarPub_t> scrVarPub{0x18E7508};
WEAK symbol<scrCompilePub_t> scrCompilePub{0x156BF88};
WEAK symbol<scrCompileGlob_t> scrCompileGlob{0x158CFC8};
WEAK symbol<scrAnimPub_t> scrAnimPub{0x156BB68};
WEAK symbol<bool> g_loadedImpureScript{0x168F308};
WEAK symbol<char> g_EndPos{0x1912598};
WEAK symbol<unsigned char*> g_largeLocalBuf{0x195AAF8};
WEAK symbol<int> g_largeLocalPos{0x1963998};
WEAK symbol<int> g_maxLargeLocalPos{0x195AAFC};
WEAK symbol<int> g_largeLocalRightPos{0x195AAE8};
WEAK symbol<int> g_minLargeLocalRightPos{0x195AB00};
WEAK symbol<unsigned long> g_dwTlsIndex{0x1BFC750};
WEAK symbol<int> com_frameTime{0x145EC7C};
WEAK symbol<bool> cin_skippable{0x73264C};
WEAK symbol<int> com_fixedConsolePosition{0x145EC10};
WEAK symbol<field_t> g_consoleField{0x88C700};
WEAK symbol<ConDrawInputGlob> conDrawInputGlob{0x86E788};
WEAK symbol<Console> con{0x86ED88};
WEAK symbol<float> g_console_char_height{0x732658};
WEAK symbol<int> g_console_field_width{0x732654};
WEAK symbol<ScreenPlacementMode> activeScreenPlacementMode{0x93AAF4};
WEAK symbol<ScreenPlacement> scrPlaceFullUnsafe{0x93AB70};
WEAK symbol<int> logfile{0x145EC6C};
WEAK symbol<level_locals_t> level{0x10A7190};
WEAK symbol<RTL_CRITICAL_SECTION> s_criticalSection{0x19FBA28};
WEAK symbol<SOCKET> ip_socket{0x1A040C8};
WEAK symbol<source_s*> sourceFiles{0x7440E8};
WEAK symbol<int> numtokens{0x7441F0};
WEAK symbol<uiInfo_s> uiInfoArray{0x1920470};
WEAK symbol<void*> DB_GetXAssetSizeHandlers{0x733408};
WEAK symbol<void*> DB_XAssetPool{0x7337F8};
WEAK symbol<unsigned int> g_poolSize{0x733510};
} // namespace game

View File

@ -0,0 +1,30 @@
#include <std_include.hpp>
#include <utils/io.hpp>
#include <utils/cryptography.hpp>
#include "binary_loader.hpp"
#define SP_HASH \
"39E9C4FACEA4B19017BB8C2FC64E4149708927AC40C7C3DEACC25BDD25E93D32"
#define SP_XLABS_HASH \
"05D499D77028859D4BA30C852DA85CCA5F02678B22AEA9E27D7C56973B14A0BC"
namespace binary_loader {
std::string load_base() {
std::string data;
if (!utils::io::read_file("iw4sp.exe", &data)) {
throw std::runtime_error("Failed to read game binary (iw4sp.exe)!");
}
const auto hash = utils::cryptography::sha256::compute(data, true);
if ((hash != SP_XLABS_HASH) && (hash != SP_HASH)) {
throw std::runtime_error(
"Your iw4sp.exe is incompatible with this client.");
}
return data;
}
std::string load() { return load_base(); }
} // namespace binary_loader

View File

@ -0,0 +1,5 @@
#pragma once
namespace binary_loader {
std::string load();
}

View File

@ -0,0 +1,19 @@
#pragma once
class component_interface {
public:
virtual ~component_interface() = default;
virtual void post_start() {}
virtual void post_load() {}
virtual void pre_destroy() {}
virtual void* load_import([[maybe_unused]] const std::string& library,
[[maybe_unused]] const std::string& function) {
return nullptr;
}
virtual bool is_supported() { return true; }
};

View File

@ -0,0 +1,100 @@
#include <std_include.hpp>
#include "component_loader.hpp"
void component_loader::register_component(
std::unique_ptr<component_interface>&& component_) {
get_components().push_back(std::move(component_));
}
bool component_loader::post_start() {
static auto handled = false;
if (handled)
return true;
handled = true;
try {
for (const auto& component_ : get_components()) {
component_->post_start();
}
} catch (premature_shutdown_trigger&) {
return false;
}
return true;
}
bool component_loader::post_load() {
static auto handled = false;
if (handled)
return true;
handled = true;
clean();
try {
for (const auto& component_ : get_components()) {
component_->post_load();
}
} catch (premature_shutdown_trigger&) {
return false;
}
return true;
}
void component_loader::pre_destroy() {
static auto handled = false;
if (handled)
return;
handled = true;
for (const auto& component_ : get_components()) {
component_->pre_destroy();
}
}
void component_loader::clean() {
auto& components = get_components();
for (auto i = components.begin(); i != components.end();) {
if (!(*i)->is_supported()) {
(*i)->pre_destroy();
i = components.erase(i);
} else {
++i;
}
}
}
void* component_loader::load_import(const std::string& library,
const std::string& function) {
void* function_ptr = nullptr;
for (const auto& component_ : get_components()) {
auto* const component_function_ptr =
component_->load_import(library, function);
if (component_function_ptr) {
function_ptr = component_function_ptr;
}
}
return function_ptr;
}
void component_loader::trigger_premature_shutdown() {
throw premature_shutdown_trigger();
}
std::vector<std::unique_ptr<component_interface>>&
component_loader::get_components() {
using component_vector = std::vector<std::unique_ptr<component_interface>>;
using component_vector_container =
std::unique_ptr<component_vector, std::function<void(component_vector*)>>;
static component_vector_container components(
new component_vector, [](component_vector* component_vector) {
pre_destroy();
delete component_vector;
});
return *components;
}

View File

@ -0,0 +1,50 @@
#pragma once
#include "component_interface.hpp"
class component_loader final {
public:
class premature_shutdown_trigger final : public std::exception {
[[nodiscard]] const char* what() const noexcept override {
return "Premature shutdown requested";
}
};
template <typename T> class installer final {
static_assert(std::is_base_of<component_interface, T>::value,
"component has invalid base class");
public:
installer() { register_component(std::make_unique<T>()); }
};
template <typename T> static T* get() {
for (const auto& component_ : get_components()) {
if (typeid(*component_.get()) == typeid(T)) {
return reinterpret_cast<T*>(component_.get());
}
}
return nullptr;
}
static void
register_component(std::unique_ptr<component_interface>&& component);
static bool post_start();
static bool post_load();
static void pre_destroy();
static void clean();
static void* load_import(const std::string& library,
const std::string& function);
static void trigger_premature_shutdown();
private:
static std::vector<std::unique_ptr<component_interface>>& get_components();
};
#define REGISTER_COMPONENT(name) \
namespace { \
static component_loader::installer<name> __component; \
}

View File

@ -0,0 +1,190 @@
#include <std_include.hpp>
#include <utils/string.hpp>
#include <utils/hook.hpp>
#include "loader.hpp"
#include "tls.hpp"
FARPROC loader::load(const utils::nt::library& library,
const std::string& buffer) const {
if (buffer.empty())
return nullptr;
const utils::nt::library source(HMODULE(buffer.data()));
if (!source)
return nullptr;
this->load_sections(library, source);
this->load_imports(library, source);
this->load_tls(library, source);
DWORD oldProtect;
VirtualProtect(library.get_nt_headers(), 0x1000, PAGE_EXECUTE_READWRITE,
&oldProtect);
library.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] =
source.get_optional_header()->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
std::memmove(library.get_nt_headers(), source.get_nt_headers(),
sizeof(IMAGE_NT_HEADERS) +
source.get_nt_headers()->FileHeader.NumberOfSections *
sizeof(IMAGE_SECTION_HEADER));
return FARPROC(library.get_ptr() + source.get_relative_entry_point());
}
FARPROC loader::load_library(const std::string& filename) const {
const auto target = utils::nt::library::load(filename);
if (!target) {
throw std::runtime_error{"Failed to map binary!"};
}
const auto base = size_t(target.get_ptr());
if (base != 0x400000) {
throw std::runtime_error{
utils::string::va("Binary was mapped at {0:#x} (instead of {1:#x}). "
"Something is severely broken :(",
base, 0x400000)};
}
this->load_imports(target, target);
this->load_tls(target, target);
return FARPROC(target.get_ptr() + target.get_relative_entry_point());
}
void loader::set_import_resolver(
const std::function<void*(const std::string&, const std::string&)>&
resolver) {
this->import_resolver_ = resolver;
}
void loader::load_section(const utils::nt::library& target,
const utils::nt::library& source,
IMAGE_SECTION_HEADER* section) {
void* target_ptr = target.get_ptr() + section->VirtualAddress;
const void* source_ptr = source.get_ptr() + section->PointerToRawData;
if (PBYTE(target_ptr) >= (target.get_ptr() + BINARY_PAYLOAD_SIZE)) {
throw std::runtime_error(
"Section exceeds the binary payload size, please increase it!");
}
if (section->SizeOfRawData > 0) {
DWORD old_protect;
VirtualProtect(target_ptr, section->Misc.VirtualSize,
PAGE_EXECUTE_READWRITE, &old_protect);
std::memmove(target_ptr, source_ptr, section->SizeOfRawData);
}
}
void loader::load_sections(const utils::nt::library& target,
const utils::nt::library& source) const {
for (auto& section : source.get_section_headers()) {
this->load_section(target, source, section);
}
}
void loader::load_imports(const utils::nt::library& target,
const utils::nt::library& source) const {
const auto import_directory =
&source.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
auto descriptor = PIMAGE_IMPORT_DESCRIPTOR(target.get_ptr() +
import_directory->VirtualAddress);
while (descriptor->Name) {
std::string name = LPSTR(target.get_ptr() + descriptor->Name);
auto name_table_entry = reinterpret_cast<uintptr_t*>(
target.get_ptr() + descriptor->OriginalFirstThunk);
auto address_table_entry =
reinterpret_cast<uintptr_t*>(target.get_ptr() + descriptor->FirstThunk);
if (!descriptor->OriginalFirstThunk) {
name_table_entry = reinterpret_cast<uintptr_t*>(target.get_ptr() +
descriptor->FirstThunk);
}
while (*name_table_entry) {
FARPROC function = nullptr;
std::string function_name;
const char* function_procname;
if (IMAGE_SNAP_BY_ORDINAL(*name_table_entry)) {
function_name = "#" + std::to_string(IMAGE_ORDINAL(*name_table_entry));
function_procname = MAKEINTRESOURCEA(IMAGE_ORDINAL(*name_table_entry));
} else {
auto* import =
PIMAGE_IMPORT_BY_NAME(target.get_ptr() + *name_table_entry);
function_name = import->Name;
function_procname = function_name.data();
}
if (this->import_resolver_)
function = FARPROC(this->import_resolver_(name, function_name));
if (!function) {
auto library = utils::nt::library::load(name);
if (library) {
function = GetProcAddress(library, function_procname);
}
}
if (!function) {
throw std::runtime_error(
utils::string::va("Unable to load import '{0}' from library {1}'",
function_name, name));
}
utils::hook::set(address_table_entry,
reinterpret_cast<uintptr_t>(function));
name_table_entry++;
address_table_entry++;
}
descriptor++;
}
}
void loader::load_tls(const utils::nt::library& target,
const utils::nt::library& source) const {
if (!target.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]
.Size) {
return;
}
auto* target_tls = tls::allocate_tls_index();
const auto* const source_tls = reinterpret_cast<PIMAGE_TLS_DIRECTORY>(
target.get_ptr() + target.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]
.VirtualAddress);
auto* target_tls_start = PVOID(target_tls->StartAddressOfRawData);
auto* tls_start = PVOID(source_tls->StartAddressOfRawData);
const auto tls_size =
source_tls->EndAddressOfRawData - source_tls->StartAddressOfRawData;
const auto tls_index = *reinterpret_cast<DWORD*>(target_tls->AddressOfIndex);
utils::hook::set<DWORD>(source_tls->AddressOfIndex, tls_index);
if (target_tls->AddressOfCallBacks) {
utils::hook::set<void*>(target_tls->AddressOfCallBacks, nullptr);
}
DWORD old_protect;
VirtualProtect(target_tls_start, tls_size, PAGE_READWRITE, &old_protect);
auto* const tls_base =
*reinterpret_cast<LPVOID*>(__readfsdword(0x2C) + 4 * tls_index);
std::memmove(tls_base, tls_start, tls_size);
std::memmove(target_tls_start, tls_start, tls_size);
VirtualProtect(target_tls, sizeof(*target_tls), PAGE_READWRITE, &old_protect);
*target_tls = *source_tls;
}

View File

@ -0,0 +1,25 @@
#pragma once
class loader final {
public:
FARPROC load(const utils::nt::library& library,
const std::string& buffer) const;
FARPROC load_library(const std::string& filename) const;
void set_import_resolver(
const std::function<void*(const std::string&, const std::string&)>&
resolver);
private:
std::function<void*(const std::string&, const std::string&)> import_resolver_;
static void load_section(const utils::nt::library& target,
const utils::nt::library& source,
IMAGE_SECTION_HEADER* section);
void load_sections(const utils::nt::library& target,
const utils::nt::library& source) const;
void load_imports(const utils::nt::library& target,
const utils::nt::library& source) const;
void load_tls(const utils::nt::library& target,
const utils::nt::library& source) const;
};

34
src/client/loader/tls.cpp Normal file
View File

@ -0,0 +1,34 @@
#include <std_include.hpp>
#include "tls.hpp"
#include <utils/nt.hpp>
#include <utils/binary_resource.hpp>
#include <resource.hpp>
namespace tls {
namespace {
utils::binary_resource tls_dll_file(TLS_DLL, "iw4sp-tlsdll.dll");
}
PIMAGE_TLS_DIRECTORY allocate_tls_index() {
static auto already_allocated = false;
if (already_allocated) {
throw std::runtime_error(
"Currently only a single allocation is supported!");
}
already_allocated = true;
const auto dll_path = tls_dll_file.get_extracted_file();
const auto tls_dll = utils::nt::library::load(dll_path);
if (!tls_dll) {
throw std::runtime_error("Failed to load TLS DLL");
}
return reinterpret_cast<PIMAGE_TLS_DIRECTORY>(
tls_dll.get_ptr() + tls_dll.get_optional_header()
->DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]
.VirtualAddress);
}
} // namespace tls

View File

@ -0,0 +1,5 @@
#pragma once
namespace tls {
PIMAGE_TLS_DIRECTORY allocate_tls_index();
}

150
src/client/main.cpp Normal file
View File

@ -0,0 +1,150 @@
#include "std_include.hpp"
#include <utils/nt.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include "loader/binary_loader.hpp"
#include "loader/component_loader.hpp"
#include "loader/loader.hpp"
#include <gsl/gsl>
#include <version.hpp>
#include <DbgHelp.h>
LONG WINAPI exception_handler(PEXCEPTION_POINTERS exception_info) {
if (exception_info->ExceptionRecord->ExceptionCode == 0x406D1388) {
return EXCEPTION_CONTINUE_EXECUTION;
}
if (exception_info->ExceptionRecord->ExceptionCode < 0x80000000 ||
exception_info->ExceptionRecord->ExceptionCode == 0xE06D7363) {
return EXCEPTION_CONTINUE_SEARCH;
}
MINIDUMP_EXCEPTION_INFORMATION exception_information = {
GetCurrentThreadId(), exception_info, FALSE};
const auto type = MiniDumpIgnoreInaccessibleMemory //
| MiniDumpWithHandleData //
| MiniDumpScanMemory //
| MiniDumpWithProcessThreadData //
| MiniDumpWithFullMemoryInfo //
| MiniDumpWithThreadInfo;
CreateDirectoryA("minidumps", nullptr);
const auto* file_name =
utils::string::va("minidumps\\iw4-sp_{0}.dmp", SHORTVERSION);
constexpr auto file_share = FILE_SHARE_READ | FILE_SHARE_WRITE;
const auto file_handle =
CreateFileA(file_name, GENERIC_WRITE | GENERIC_READ, file_share, nullptr,
CREATE_ALWAYS, NULL, nullptr);
if (!MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),
file_handle, static_cast<MINIDUMP_TYPE>(type),
&exception_information, nullptr, nullptr)) {
char buf[4096]{};
sprintf_s(buf, "An exception 0x%08X occurred at location 0x%p\n",
exception_info->ExceptionRecord->ExceptionCode,
exception_info->ExceptionRecord->ExceptionAddress);
MessageBoxA(nullptr, buf, "Fatal Error", MB_ICONERROR);
}
CloseHandle(file_handle);
TerminateProcess(GetCurrentProcess(),
exception_info->ExceptionRecord->ExceptionCode);
return EXCEPTION_CONTINUE_SEARCH;
}
DECLSPEC_NORETURN void WINAPI exit_hook(const int code) {
component_loader::pre_destroy();
std::exit(code);
}
FARPROC load_binary() {
loader loader;
utils::nt::library self;
loader.set_import_resolver(
[self](const std::string& library, const std::string& function) -> void* {
if (library == "steam_api.dll") {
return self.get_proc<FARPROC>(function);
}
if (function == "ExitProcess") {
return exit_hook;
}
return component_loader::load_import(library, function);
});
const auto buffer = binary_loader::load();
return loader.load(self, buffer);
}
void enable_dpi_awareness() {
const utils::nt::library user32{"user32.dll"};
const auto set_dpi =
user32 ? user32.get_proc<BOOL(WINAPI*)(DPI_AWARENESS_CONTEXT)>(
"SetProcessDpiAwarenessContext")
: nullptr;
if (set_dpi) {
set_dpi(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
}
void apply_environment() {
char* buffer{};
std::size_t size{};
if (_dupenv_s(&buffer, &size, "MW2_INSTALL") != 0 || buffer == nullptr) {
return;
}
const auto _ = gsl::finally([&] { std::free(buffer); });
SetCurrentDirectoryA(buffer);
SetDllDirectoryA(buffer);
}
int main() {
AddVectoredExceptionHandler(0, exception_handler);
SetProcessDEPPolicy(PROCESS_DEP_ENABLE);
FARPROC entry_point;
enable_dpi_awareness();
std::srand(std::uint32_t(time(nullptr)) ^
~(GetTickCount() * GetCurrentProcessId()));
{
auto premature_shutdown = true;
const auto _0 = gsl::finally([&premature_shutdown] {
if (premature_shutdown) {
component_loader::pre_destroy();
}
});
try {
apply_environment();
if (!component_loader::post_start())
return 0;
entry_point = load_binary();
if (!entry_point) {
throw std::runtime_error("Unable to load binary into memory");
}
if (!component_loader::post_load())
return 0;
premature_shutdown = false;
} catch (const std::exception& ex) {
MessageBoxA(nullptr, ex.what(), "ERROR", MB_ICONERROR);
return 1;
}
}
return entry_point();
}
int APIENTRY WinMain(HINSTANCE, HINSTANCE, PSTR, int) { return main(); }

8
src/client/resource.hpp Normal file
View File

@ -0,0 +1,8 @@
#pragma once
#define ID_ICON 102
#define TLS_DLL 301
#define RUNNER 302
#define ICON_IMAGE 303

117
src/client/resource.rc Normal file
View File

@ -0,0 +1,117 @@
// Microsoft Visual C++ generated resource script.
//
#pragma code_page(65001)
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "windows.h"
#include "version.h"
#include "resource.hpp"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"#include ""windows.h""\r\n"
"\0"
END
2 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE VFT_DLL
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "AlterWare"
VALUE "FileDescription", "IW4 SP client modification"
VALUE "FileVersion", VERSION_FILE
VALUE "InternalName", "iw4-sp"
VALUE "LegalCopyright", "Copyright (C) 2023 AlterWare. All rights reserved."
VALUE "Licence", "GPLv3"
VALUE "Info", "https://alterware.dev"
VALUE "OriginalFilename", "iw4-sp.exe"
VALUE "ProductName", "iw4-sp"
VALUE "ProductVersion", VERSION_PRODUCT
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
/////////////////////////////////////////////////////////////////////////////
//
// Binary Data
//
ID_ICON ICON "resources/icon.ico"
#ifdef _DEBUG
TLS_DLL RCDATA "../../build/bin/Win32/Debug/tlsdll.dll"
#else
TLS_DLL RCDATA "../../build/bin/Win32/Release/tlsdll.dll"
#endif
#ifdef _DEBUG
RUNNER RCDATA "../../build/bin/Win32/Debug/runner.exe"
#else
RUNNER RCDATA "../../build/bin/Win32/Release/runner.exe"
#endif
ICON_IMAGE RCDATA "resources/icon.png"
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,27 @@
#include "std_include.hpp"
#pragma comment(linker, "/merge:.data=.cld")
#pragma comment(linker, "/merge:.rdata=.clr")
#pragma comment(linker, "/merge:.cl=.main")
#pragma comment(linker, "/merge:.text=.main")
#pragma comment(linker, "/base:0x400000")
#pragma bss_seg(".payload")
char payload_data[BINARY_PAYLOAD_SIZE];
extern "C" {
__declspec(dllexport) DWORD NvOptimusEnablement = 1;
__declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1;
}
extern "C" {
char* get_payload_data() { return payload_data; }
}
extern "C" {
int s_read_arc4random(void*, std::size_t) { return -1; }
int s_read_getrandom(void*, std::size_t) { return -1; }
int s_read_urandom(void*, std::size_t) { return -1; }
}

View File

@ -0,0 +1,57 @@
#pragma once
#define BINARY_PAYLOAD_SIZE 0x0A000000
#define DLL_EXPORT extern "C" __declspec(dllexport)
#define WIN32_LEAN_AND_MEAN
#define BASEGAME "spdata"
#define CLIENT_CONFIG "iw4_sp_config.cfg"
#include <WinSock2.h>
#include <Windows.h>
#include <wincrypt.h>
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#undef GetObject
#include <cassert>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <filesystem>
#include <format>
#include <fstream>
#include <functional>
#include <iostream>
#include <map>
#include <mutex>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
#pragma comment(lib, "ntdll.lib")
#pragma comment(lib, "Crypt32.lib")
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "dbghelp.lib")
#pragma warning(disable : 4100)
#pragma warning(disable : 4244)
// I have witnessed clang re-ordering my included even when
// it was set not to do it. :(
// clang-format off
#include "game/structs.hpp"
#include "game/game.hpp"
// clang-format on

View File

@ -0,0 +1,117 @@
#include <std_include.hpp>
#include "interface.hpp"
#include <utils/memory.hpp>
#include <utils/nt.hpp>
#include <udis86.h>
namespace steam {
interface::interface() : interface(nullptr) {}
interface::interface(void* interface_ptr)
: interface_ptr_(static_cast<void***>(interface_ptr)) {}
interface::operator bool() const { return this->interface_ptr_ != nullptr; }
interface::method interface::find_method(const std::string& name) {
const auto method_entry = this->methods_.find(name);
if (method_entry != this->methods_.end()) {
return method_entry->second;
}
return this->search_method(name);
}
interface::method interface::search_method(const std::string& name) {
if (!utils::memory::is_bad_read_ptr(this->interface_ptr_)) {
auto vftbl = *this->interface_ptr_;
while (!utils::memory::is_bad_read_ptr(vftbl) &&
!utils::memory::is_bad_code_ptr(*vftbl)) {
const auto result = this->analyze_method(*vftbl);
if (result.param_size_found && result.name_found) {
const method method_result{*vftbl, result.param_size};
this->methods_[result.name] = method_result;
if (result.name == name) {
return method_result;
}
}
++vftbl;
}
}
return {};
}
interface::method_result interface::analyze_method(const void* method_ptr) {
method_result result;
if (utils::memory::is_bad_code_ptr(method_ptr))
return result;
ud_t ud;
ud_init(&ud);
ud_set_mode(&ud, 32);
ud_set_pc(&ud, reinterpret_cast<std::uint64_t>(method_ptr));
ud_set_input_buffer(&ud, static_cast<const std::uint8_t*>(method_ptr),
INT32_MAX);
while (true) {
ud_disassemble(&ud);
if (ud_insn_mnemonic(&ud) == UD_Iret && !result.param_size_found) {
const ud_operand* operand = ud_insn_opr(&ud, 0);
if (operand && operand->type == UD_OP_IMM && operand->size == 16) {
result.param_size = operand->lval.uword;
} else {
result.param_size = 0;
}
result.param_size_found = true;
}
if (ud_insn_mnemonic(&ud) == UD_Ipush && !result.name_found) {
const auto operand = ud_insn_opr(&ud, 0);
if (operand->type == UD_OP_IMM && operand->size == 32) {
auto* operand_ptr = reinterpret_cast<char*>(operand->lval.udword);
if (!utils::memory::is_bad_read_ptr(operand_ptr) &&
this->is_rdata(operand_ptr)) {
result.name = operand_ptr;
result.name_found = true;
}
}
}
if (*reinterpret_cast<unsigned char*>(ud.pc) == 0xCC)
break; // int 3
if (result.param_size_found && result.name_found)
break;
}
return result;
}
bool interface::is_rdata(void* pointer) {
const auto pointer_lib = utils::nt::library::get_by_address(pointer);
for (const auto& section : pointer_lib.get_section_headers()) {
const auto size = sizeof(section->Name);
char name[size + 1];
name[size] = 0;
std::memcpy(name, section->Name, size);
if (std::strcmp(name, ".rdata") == 0) {
const auto target = reinterpret_cast<std::size_t>(pointer);
const size_t source_start =
size_t(pointer_lib.get_ptr()) + section->PointerToRawData;
const size_t source_end = source_start + section->SizeOfRawData;
return target >= source_start && target <= source_end;
}
}
return false;
}
} // namespace steam

View File

@ -0,0 +1,82 @@
#pragma once
#ifdef interface
#undef interface
#endif
namespace steam {
template <std::size_t...>
struct argument_size_calculator final : std::integral_constant<std::size_t, 0> {
};
template <std::size_t X, std::size_t... Xs>
struct argument_size_calculator<X, Xs...> final
: std::integral_constant<std::size_t,
X + ((argument_size_calculator<Xs...>::value +
(sizeof(void*) - 1)) &
~(sizeof(void*) - 1))> {};
class interface final {
public:
class method final {
public:
void* pointer = nullptr;
std::size_t param_size = 0;
};
class method_result final {
public:
std::string name;
std::size_t param_size = 0;
bool name_found = false;
bool param_size_found = false;
};
interface();
interface(void* interface_ptr);
operator bool() const;
template <typename T, typename... Args>
T invoke(const std::string& method_name, Args... args) {
if (!this->interface_ptr_) {
throw std::runtime_error("Invalid interface pointer");
}
const auto method_result = this->find_method(method_name);
if (!method_result.pointer) {
throw std::runtime_error("Unable to find desired method");
}
constexpr std::size_t passed_argc =
argument_size_calculator<sizeof(Args)...>::value;
if (passed_argc != method_result.param_size) {
throw std::runtime_error("Invalid argument count");
}
return static_cast<T(__thiscall*)(void*, Args...)>(method_result.pointer)(
this->interface_ptr_, args...);
}
template <typename T, typename... Args>
T invoke(const std::size_t table_entry, Args... args) {
if (!this->interface_ptr_) {
throw std::runtime_error("Invalid interface pointer");
}
return static_cast<T(__thiscall*)(void*, Args...)>(
(*this->interface_ptr_)[table_entry])(this->interface_ptr_, args...);
}
private:
void*** interface_ptr_;
std::unordered_map<std::string, method> methods_;
method find_method(const std::string& name);
method search_method(const std::string& name);
method_result analyze_method(const void* method_ptr);
bool is_rdata(void* pointer);
};
} // namespace steam

View File

@ -0,0 +1,37 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
namespace steam {
bool apps::BIsSubscribed() { return true; }
bool apps::BIsLowViolence() { return false; }
bool apps::BIsCybercafe() { return false; }
bool apps::BIsVACBanned() { return false; }
const char* apps::GetCurrentGameLanguage() { return "english"; }
const char* apps::GetAvailableGameLanguages() { return "english"; }
bool apps::BIsSubscribedApp(unsigned int appID) { return true; }
bool apps::BIsDlcInstalled(unsigned int appID) { return true; }
unsigned int apps::GetEarliestPurchaseUnixTime(unsigned int nAppID) {
return 0;
}
bool apps::BIsSubscribedFromFreeWeekend() { return false; }
int apps::GetDLCCount() { return 0; }
bool apps::BGetDLCDataByIndex(int iDLC, unsigned int* pAppID, bool* pbAvailable,
char* pchName, int cchNameBufferSize) {
return false;
}
void apps::InstallDLC(unsigned int nAppID) {}
void apps::UninstallDLC(unsigned int nAppID) {}
} // namespace steam

View File

@ -0,0 +1,26 @@
#pragma once
namespace steam {
class apps {
protected:
~apps() = default;
public:
virtual bool BIsSubscribed();
virtual bool BIsLowViolence();
virtual bool BIsCybercafe();
virtual bool BIsVACBanned();
virtual const char* GetCurrentGameLanguage();
virtual const char* GetAvailableGameLanguages();
virtual bool BIsSubscribedApp(unsigned int appID);
virtual bool BIsDlcInstalled(unsigned int appID);
virtual unsigned int GetEarliestPurchaseUnixTime(unsigned int nAppID);
virtual bool BIsSubscribedFromFreeWeekend();
virtual int GetDLCCount();
virtual bool BGetDLCDataByIndex(int iDLC, unsigned int* pAppID,
bool* pbAvailable, char* pchName,
int cchNameBufferSize);
virtual void InstallDLC(unsigned int nAppID);
virtual void UninstallDLC(unsigned int nAppID);
};
} // namespace steam

View File

@ -0,0 +1,130 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
namespace steam {
const char* friends::GetPersonaName() { return "GlaDos"; }
void friends::SetPersonaName(const char* pchPersonaName) {}
int friends::GetPersonaState() { return 1; }
int friends::GetFriendCount(int eFriendFlags) { return 0; }
steam_id friends::GetFriendByIndex(int iFriend, int iFriendFlags) {
return steam_id();
}
int friends::GetFriendRelationship(steam_id steamIDFriend) { return 0; }
int friends::GetFriendPersonaState(steam_id steamIDFriend) { return 0; }
const char* friends::GetFriendPersonaName(steam_id steamIDFriend) { return ""; }
bool friends::GetFriendGamePlayed(steam_id steamIDFriend,
void* pFriendGameInfo) {
return false;
}
const char* friends::GetFriendPersonaNameHistory(steam_id steamIDFriend,
int iPersonaName) {
return "";
}
bool friends::HasFriend(steam_id steamIDFriend, int eFriendFlags) {
return false;
}
int friends::GetClanCount() { return 0; }
steam_id friends::GetClanByIndex(int iClan) { return steam_id(); }
const char* friends::GetClanName(steam_id steamIDClan) { return "3arc"; }
const char* friends::GetClanTag(steam_id steamIDClan) {
return this->GetClanName(steamIDClan);
}
int friends::GetFriendCountFromSource(steam_id steamIDSource) { return 0; }
steam_id friends::GetFriendFromSourceByIndex(steam_id steamIDSource,
int iFriend) {
return steam_id();
}
bool friends::IsUserInSource(steam_id steamIDUser, steam_id steamIDSource) {
return false;
}
void friends::SetInGameVoiceSpeaking(steam_id steamIDUser, bool bSpeaking) {}
void friends::ActivateGameOverlay(const char* pchDialog) {}
void friends::ActivateGameOverlayToUser(const char* pchDialog,
steam_id steamID) {}
void friends::ActivateGameOverlayToWebPage(const char* pchURL) {}
void friends::ActivateGameOverlayToStore(unsigned int nAppID) {
OutputDebugStringA("Store requested!");
}
void friends::SetPlayedWith(steam_id steamIDUserPlayedWith) {}
void friends::ActivateGameOverlayInviteDialog(steam_id steamIDLobby) {}
int friends::GetSmallFriendAvatar(steam_id steamIDFriend) { return 0; }
int friends::GetMediumFriendAvatar(steam_id steamIDFriend) { return 0; }
int friends::GetLargeFriendAvatar(steam_id steamIDFriend) { return 0; }
bool friends::RequestUserInformation(steam_id steamIDUser,
bool bRequireNameOnly) {
return false;
}
unsigned __int64 friends::RequestClanOfficerList(steam_id steamIDClan) {
return 0;
}
steam_id friends::GetClanOwner(steam_id steamIDClan) { return steam_id(); }
int friends::GetClanOfficerCount(steam_id steamIDClan) { return 0; }
steam_id friends::GetClanOfficerByIndex(steam_id steamIDClan, int iOfficer) {
return steam_id();
}
int friends::GetUserRestrictions() { return 0; }
bool friends::SetRichPresence(const char* pchKey, const char* pchValue) {
return true;
}
void friends::ClearRichPresence() {}
const char* friends::GetFriendRichPresence(steam_id steamIDFriend,
const char* pchKey) {
return "";
}
int friends::GetFriendRichPresenceKeyCount(steam_id steamIDFriend) { return 0; }
const char* friends::GetFriendRichPresenceKeyByIndex(steam_id steamIDFriend,
int iKey) {
return "a";
}
bool friends::InviteUserToGame(steam_id steamIDFriend,
const char* pchConnectString) {
return false;
}
int friends::GetCoplayFriendCount() { return 0; }
steam_id friends::GetCoplayFriend(int iCoplayFriend) { return steam_id(); }
int friends::GetFriendCoplayTime(steam_id steamIDFriend) { return 0; }
unsigned int friends::GetFriendCoplayGame(steam_id steamIDFriend) { return 0; }
} // namespace steam

View File

@ -0,0 +1,62 @@
#pragma once
namespace steam {
class friends {
protected:
~friends() = default;
public:
virtual const char* GetPersonaName();
virtual void SetPersonaName(const char* pchPersonaName);
virtual int GetPersonaState();
virtual int GetFriendCount(int eFriendFlags);
virtual steam_id GetFriendByIndex(int iFriend, int iFriendFlags);
virtual int GetFriendRelationship(steam_id steamIDFriend);
virtual int GetFriendPersonaState(steam_id steamIDFriend);
virtual const char* GetFriendPersonaName(steam_id steamIDFriend);
virtual bool GetFriendGamePlayed(steam_id steamIDFriend,
void* pFriendGameInfo);
virtual const char* GetFriendPersonaNameHistory(steam_id steamIDFriend,
int iPersonaName);
virtual bool HasFriend(steam_id steamIDFriend, int eFriendFlags);
virtual int GetClanCount();
virtual steam_id GetClanByIndex(int iClan);
virtual const char* GetClanName(steam_id steamIDClan);
virtual const char* GetClanTag(steam_id steamIDClan);
virtual int GetFriendCountFromSource(steam_id steamIDSource);
virtual steam_id GetFriendFromSourceByIndex(steam_id steamIDSource,
int iFriend);
virtual bool IsUserInSource(steam_id steamIDUser, steam_id steamIDSource);
virtual void SetInGameVoiceSpeaking(steam_id steamIDUser, bool bSpeaking);
virtual void ActivateGameOverlay(const char* pchDialog);
virtual void ActivateGameOverlayToUser(const char* pchDialog,
steam_id steamID);
virtual void ActivateGameOverlayToWebPage(const char* pchURL);
virtual void ActivateGameOverlayToStore(unsigned int nAppID);
virtual void SetPlayedWith(steam_id steamIDUserPlayedWith);
virtual void ActivateGameOverlayInviteDialog(steam_id steamIDLobby);
virtual int GetSmallFriendAvatar(steam_id steamIDFriend);
virtual int GetMediumFriendAvatar(steam_id steamIDFriend);
virtual int GetLargeFriendAvatar(steam_id steamIDFriend);
virtual bool RequestUserInformation(steam_id steamIDUser,
bool bRequireNameOnly);
virtual unsigned __int64 RequestClanOfficerList(steam_id steamIDClan);
virtual steam_id GetClanOwner(steam_id steamIDClan);
virtual int GetClanOfficerCount(steam_id steamIDClan);
virtual steam_id GetClanOfficerByIndex(steam_id steamIDClan, int iOfficer);
virtual int GetUserRestrictions();
virtual bool SetRichPresence(const char* pchKey, const char* pchValue);
virtual void ClearRichPresence();
virtual const char* GetFriendRichPresence(steam_id steamIDFriend,
const char* pchKey);
virtual int GetFriendRichPresenceKeyCount(steam_id steamIDFriend);
virtual const char* GetFriendRichPresenceKeyByIndex(steam_id steamIDFriend,
int iKey);
virtual bool InviteUserToGame(steam_id steamIDFriend,
const char* pchConnectString);
virtual int GetCoplayFriendCount();
virtual steam_id GetCoplayFriend(int iCoplayFriend);
virtual int GetFriendCoplayTime(steam_id steamIDFriend);
virtual unsigned int GetFriendCoplayGame(steam_id steamIDFriend);
};
} // namespace steam

View File

@ -0,0 +1,74 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
namespace steam {
void game_server::LogOn() {}
void game_server::LogOff() {}
bool game_server::LoggedOn() { return true; }
bool game_server::Secure() { return false; }
steam_id game_server::GetSteamID() { return steam_id(); }
bool game_server::SendUserConnectAndAuthenticate(unsigned int unIPClient,
const void* pvAuthBlob,
unsigned int cubAuthBlobSize,
steam_id* pSteamIDUser) {
return true;
}
steam_id game_server::CreateUnauthenticatedUserConnection() {
return steam_id();
}
void game_server::SendUserDisconnect(steam_id steamIDUser) {}
bool game_server::UpdateUserData(steam_id steamIDUser,
const char* pchPlayerName,
unsigned int uScore) {
return true;
}
bool game_server::SetServerType(unsigned int unServerFlags,
unsigned int unGameIP,
unsigned short unGamePort,
unsigned short unSpectatorPort,
unsigned short usQueryPort,
const char* pchGameDir, const char* pchVersion,
bool bLANMode) {
return true;
}
void game_server::UpdateServerStatus(int cPlayers, int cPlayersMax,
int cBotPlayers, const char* pchServerName,
const char* pSpectatorServerName,
const char* pchMapName) {}
void game_server::UpdateSpectatorPort(unsigned short unSpectatorPort) {}
void game_server::SetGameType(const char* pchGameType) {}
bool game_server::GetUserAchievementStatus(steam_id steamID,
const char* pchAchievementName) {
return false;
}
void game_server::GetGameplayStats() {}
unsigned __int64 game_server::GetServerReputation() { return 0; }
bool game_server::RequestUserGroupStatus(steam_id steamIDUser,
steam_id steamIDGroup) {
return false;
}
unsigned int game_server::GetPublicIP() { return 0; }
void game_server::SetGameData(const char* pchGameData) {}
int game_server::UserHasLicenseForApp(steam_id steamID, unsigned int appID) {
return 0;
}
} // namespace steam

View File

@ -0,0 +1,43 @@
#pragma once
namespace steam {
class game_server {
protected:
~game_server() = default;
public:
virtual void LogOn();
virtual void LogOff();
virtual bool LoggedOn();
virtual bool Secure();
virtual steam_id GetSteamID();
virtual bool SendUserConnectAndAuthenticate(unsigned int unIPClient,
const void* pvAuthBlob,
unsigned int cubAuthBlobSize,
steam_id* pSteamIDUser);
virtual steam_id CreateUnauthenticatedUserConnection();
virtual void SendUserDisconnect(steam_id steamIDUser);
virtual bool UpdateUserData(steam_id steamIDUser, const char* pchPlayerName,
unsigned int uScore);
virtual bool SetServerType(unsigned int unServerFlags, unsigned int unGameIP,
unsigned short unGamePort,
unsigned short unSpectatorPort,
unsigned short usQueryPort, const char* pchGameDir,
const char* pchVersion, bool bLANMode);
virtual void UpdateServerStatus(int cPlayers, int cPlayersMax,
int cBotPlayers, const char* pchServerName,
const char* pSpectatorServerName,
const char* pchMapName);
virtual void UpdateSpectatorPort(unsigned short unSpectatorPort);
virtual void SetGameType(const char* pchGameType);
virtual bool GetUserAchievementStatus(steam_id steamID,
const char* pchAchievementName);
virtual void GetGameplayStats();
virtual unsigned __int64 GetServerReputation();
virtual bool RequestUserGroupStatus(steam_id steamIDUser,
steam_id steamIDGroup);
virtual unsigned int GetPublicIP();
virtual void SetGameData(const char* pchGameData);
virtual int UserHasLicenseForApp(steam_id steamID, unsigned int appID);
};
} // namespace steam

View File

@ -0,0 +1,51 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
namespace steam {
void master_server_updater::SetActive(bool bActive) {}
void master_server_updater::SetHeartbeatInterval(int iHeartbeatInterval) {}
bool master_server_updater::HandleIncomingPacket(const void* pData, int cbData,
unsigned int srcIP,
unsigned short srcPort) {
return true;
}
int master_server_updater::GetNextOutgoingPacket(void* pOut, int cbMaxOut,
unsigned int* pNetAdr,
unsigned short* pPort) {
return 0;
}
void master_server_updater::SetBasicServerData(
unsigned short nProtocolVersion, bool bDedicatedServer,
const char* pRegionName, const char* pProductName,
unsigned short nMaxReportedClients, bool bPasswordProtected,
const char* pGameDescription) {}
void master_server_updater::ClearAllKeyValues() {}
void master_server_updater::SetKeyValue(const char* pKey, const char* pValue) {}
void master_server_updater::NotifyShutdown() {}
bool master_server_updater::WasRestartRequested() { return false; }
void master_server_updater::ForceHeartbeat() {}
bool master_server_updater::AddMasterServer(const char* pServerAddress) {
return true;
}
bool master_server_updater::RemoveMasterServer(const char* pServerAddress) {
return true;
}
int master_server_updater::GetNumMasterServers() { return 0; }
int master_server_updater::GetMasterServerAddress(int iServer, char* pOut,
int outBufferSize) {
return 0;
}
} // namespace steam

View File

@ -0,0 +1,32 @@
#pragma once
namespace steam {
class master_server_updater {
protected:
~master_server_updater() = default;
public:
virtual void SetActive(bool bActive);
virtual void SetHeartbeatInterval(int iHeartbeatInterval);
virtual bool HandleIncomingPacket(const void* pData, int cbData,
unsigned int srcIP, unsigned short srcPort);
virtual int GetNextOutgoingPacket(void* pOut, int cbMaxOut,
unsigned int* pNetAdr,
unsigned short* pPort);
virtual void
SetBasicServerData(unsigned short nProtocolVersion, bool bDedicatedServer,
const char* pRegionName, const char* pProductName,
unsigned short nMaxReportedClients,
bool bPasswordProtected, const char* pGameDescription);
virtual void ClearAllKeyValues();
virtual void SetKeyValue(const char* pKey, const char* pValue);
virtual void NotifyShutdown();
virtual bool WasRestartRequested();
virtual void ForceHeartbeat();
virtual bool AddMasterServer(const char* pServerAddress);
virtual bool RemoveMasterServer(const char* pServerAddress);
virtual int GetNumMasterServers();
virtual int GetMasterServerAddress(int iServer, char* pOut,
int outBufferSize);
};
} // namespace steam

View File

@ -0,0 +1,184 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
namespace steam {
int matchmaking::GetFavoriteGameCount() { return 0; }
bool matchmaking::GetFavoriteGame(int iGame, unsigned int* pnAppID,
unsigned int* pnIP,
unsigned short* pnConnPort,
unsigned short* pnQueryPort,
unsigned int* punFlags,
unsigned int* pRTime32LastPlayedOnServer) {
return false;
}
int matchmaking::AddFavoriteGame(unsigned int nAppID, unsigned int nIP,
unsigned short nConnPort,
unsigned short nQueryPort,
unsigned int unFlags,
unsigned int rTime32LastPlayedOnServer) {
return 0;
}
bool matchmaking::RemoveFavoriteGame(unsigned int nAppID, unsigned int nIP,
unsigned short nConnPort,
unsigned short nQueryPort,
unsigned int unFlags) {
return false;
}
unsigned __int64 matchmaking::RequestLobbyList() { return 0; }
void matchmaking::AddRequestLobbyListStringFilter(const char* pchKeyToMatch,
const char* pchValueToMatch,
int eComparisonType) {}
void matchmaking::AddRequestLobbyListNumericalFilter(const char* pchKeyToMatch,
int nValueToMatch,
int eComparisonType) {}
void matchmaking::AddRequestLobbyListNearValueFilter(const char* pchKeyToMatch,
int nValueToBeCloseTo) {}
void matchmaking::AddRequestLobbyListFilterSlotsAvailable(int nSlotsAvailable) {
}
void matchmaking::AddRequestLobbyListDistanceFilter(int eLobbyDistanceFilter) {}
void matchmaking::AddRequestLobbyListResultCountFilter(int cMaxResults) {}
steam_id matchmaking::GetLobbyByIndex(int iLobby) { return steam_id(); }
unsigned __int64 matchmaking::CreateLobby(int eLobbyType, int cMaxMembers) {
const auto result = callbacks::register_call();
auto retvals = static_cast<lobby_created*>(calloc(1, sizeof(lobby_created)));
//::Utils::Memory::AllocateArray<LobbyCreated>();
steam_id id;
id.raw.account_id = 1337132;
id.raw.universe = 1;
id.raw.account_type = 8;
id.raw.account_instance = 0x40000;
retvals->m_e_result = 1;
retvals->m_ul_steam_id_lobby = id;
callbacks::return_call(retvals, sizeof(lobby_created),
lobby_created::callback_id, result);
matchmaking::JoinLobby(id);
return result;
}
unsigned __int64 matchmaking::JoinLobby(steam_id steamIDLobby) {
const auto result = callbacks::register_call();
auto* retvals = static_cast<lobby_enter*>(calloc(1, sizeof(lobby_enter)));
//::Utils::Memory::AllocateArray<LobbyEnter>();
retvals->m_b_locked = false;
retvals->m_e_chat_room_enter_response = 1;
retvals->m_rgf_chat_permissions = 0xFFFFFFFF;
retvals->m_ul_steam_id_lobby = steamIDLobby;
callbacks::return_call(retvals, sizeof(lobby_enter), lobby_enter::callback_id,
result);
return result;
}
void matchmaking::LeaveLobby(steam_id steamIDLobby) {
// Components::Party::RemoveLobby(steamIDLobby);
}
bool matchmaking::InviteUserToLobby(steam_id steamIDLobby,
steam_id steamIDInvitee) {
return true;
}
int matchmaking::GetNumLobbyMembers(steam_id steamIDLobby) { return 1; }
steam_id matchmaking::GetLobbyMemberByIndex(steam_id steamIDLobby,
int iMember) {
return SteamUser()->GetSteamID();
}
const char* matchmaking::GetLobbyData(steam_id steamIDLobby,
const char* pchKey) {
return "212"; // Components::Party::GetLobbyInfo(steamIDLobby, pchKey);
}
bool matchmaking::SetLobbyData(steam_id steamIDLobby, const char* pchKey,
const char* pchValue) {
return true;
}
int matchmaking::GetLobbyDataCount(steam_id steamIDLobby) { return 0; }
bool matchmaking::GetLobbyDataByIndex(steam_id steamIDLobby, int iLobbyData,
char* pchKey, int cchKeyBufferSize,
char* pchValue, int cchValueBufferSize) {
return false;
}
bool matchmaking::DeleteLobbyData(steam_id steamIDLobby, const char* pchKey) {
return false;
}
const char* matchmaking::GetLobbyMemberData(steam_id steamIDLobby,
steam_id steamIDUser,
const char* pchKey) {
return "";
}
void matchmaking::SetLobbyMemberData(steam_id steamIDLobby, const char* pchKey,
const char* pchValue) {}
bool matchmaking::SendLobbyChatMsg(steam_id steamIDLobby, const void* pvMsgBody,
int cubMsgBody) {
return true;
}
int matchmaking::GetLobbyChatEntry(steam_id steamIDLobby, int iChatID,
steam_id* pSteamIDUser, void* pvData,
int cubData, int* peChatEntryType) {
return 0;
}
bool matchmaking::RequestLobbyData(steam_id steamIDLobby) { return false; }
void matchmaking::SetLobbyGameServer(steam_id steamIDLobby,
unsigned int unGameServerIP,
unsigned short unGameServerPort,
steam_id steamIDGameServer) {}
bool matchmaking::GetLobbyGameServer(steam_id steamIDLobby,
unsigned int* punGameServerIP,
unsigned short* punGameServerPort,
steam_id* psteamIDGameServer) {
return false;
}
bool matchmaking::SetLobbyMemberLimit(steam_id steamIDLobby, int cMaxMembers) {
return true;
}
int matchmaking::GetLobbyMemberLimit(steam_id steamIDLobby) { return 0; }
bool matchmaking::SetLobbyType(steam_id steamIDLobby, int eLobbyType) {
return true;
}
bool matchmaking::SetLobbyJoinable(steam_id steamIDLobby, bool bLobbyJoinable) {
return true;
}
steam_id matchmaking::GetLobbyOwner(steam_id steamIDLobby) {
return SteamUser()->GetSteamID();
}
bool matchmaking::SetLobbyOwner(steam_id steamIDLobby,
steam_id steamIDNewOwner) {
return true;
}
} // namespace steam

View File

@ -0,0 +1,94 @@
#pragma once
namespace steam {
struct lobby_created final {
enum { callback_id = 513 };
int m_e_result;
int m_pad;
steam_id m_ul_steam_id_lobby;
};
struct lobby_enter final {
enum { callback_id = 504 };
steam_id m_ul_steam_id_lobby;
int m_rgf_chat_permissions;
bool m_b_locked;
int m_e_chat_room_enter_response;
};
class matchmaking {
protected:
~matchmaking() = default;
public:
virtual int GetFavoriteGameCount();
virtual bool GetFavoriteGame(int iGame, unsigned int* pnAppID,
unsigned int* pnIP, unsigned short* pnConnPort,
unsigned short* pnQueryPort,
unsigned int* punFlags,
unsigned int* pRTime32LastPlayedOnServer);
virtual int AddFavoriteGame(unsigned int nAppID, unsigned int nIP,
unsigned short nConnPort,
unsigned short nQueryPort, unsigned int unFlags,
unsigned int rTime32LastPlayedOnServer);
virtual bool RemoveFavoriteGame(unsigned int nAppID, unsigned int nIP,
unsigned short nConnPort,
unsigned short nQueryPort,
unsigned int unFlags);
virtual unsigned __int64 RequestLobbyList();
virtual void AddRequestLobbyListStringFilter(const char* pchKeyToMatch,
const char* pchValueToMatch,
int eComparisonType);
virtual void AddRequestLobbyListNumericalFilter(const char* pchKeyToMatch,
int nValueToMatch,
int eComparisonType);
virtual void AddRequestLobbyListNearValueFilter(const char* pchKeyToMatch,
int nValueToBeCloseTo);
virtual void AddRequestLobbyListFilterSlotsAvailable(int nSlotsAvailable);
virtual void AddRequestLobbyListDistanceFilter(int eLobbyDistanceFilter);
virtual void AddRequestLobbyListResultCountFilter(int cMaxResults);
virtual steam_id GetLobbyByIndex(int iLobby);
virtual unsigned __int64 CreateLobby(int eLobbyType, int cMaxMembers);
virtual unsigned __int64 JoinLobby(steam_id steamIDLobby);
virtual void LeaveLobby(steam_id steamIDLobby);
virtual bool InviteUserToLobby(steam_id steamIDLobby,
steam_id steamIDInvitee);
virtual int GetNumLobbyMembers(steam_id steamIDLobby);
virtual steam_id GetLobbyMemberByIndex(steam_id steamIDLobby, int iMember);
virtual const char* GetLobbyData(steam_id steamIDLobby, const char* pchKey);
virtual bool SetLobbyData(steam_id steamIDLobby, const char* pchKey,
const char* pchValue);
virtual int GetLobbyDataCount(steam_id steamIDLobby);
virtual bool GetLobbyDataByIndex(steam_id steamIDLobby, int iLobbyData,
char* pchKey, int cchKeyBufferSize,
char* pchValue, int cchValueBufferSize);
virtual bool DeleteLobbyData(steam_id steamIDLobby, const char* pchKey);
virtual const char* GetLobbyMemberData(steam_id steamIDLobby,
steam_id steamIDUser,
const char* pchKey);
virtual void SetLobbyMemberData(steam_id steamIDLobby, const char* pchKey,
const char* pchValue);
virtual bool SendLobbyChatMsg(steam_id steamIDLobby, const void* pvMsgBody,
int cubMsgBody);
virtual int GetLobbyChatEntry(steam_id steamIDLobby, int iChatID,
steam_id* pSteamIDUser, void* pvData,
int cubData, int* peChatEntryType);
virtual bool RequestLobbyData(steam_id steamIDLobby);
virtual void SetLobbyGameServer(steam_id steamIDLobby,
unsigned int unGameServerIP,
unsigned short unGameServerPort,
steam_id steamIDGameServer);
virtual bool GetLobbyGameServer(steam_id steamIDLobby,
unsigned int* punGameServerIP,
unsigned short* punGameServerPort,
steam_id* psteamIDGameServer);
virtual bool SetLobbyMemberLimit(steam_id steamIDLobby, int cMaxMembers);
virtual int GetLobbyMemberLimit(steam_id steamIDLobby);
virtual bool SetLobbyType(steam_id steamIDLobby, int eLobbyType);
virtual bool SetLobbyJoinable(steam_id steamIDLobby, bool bLobbyJoinable);
virtual steam_id GetLobbyOwner(steam_id steamIDLobby);
virtual bool SetLobbyOwner(steam_id steamIDLobby, steam_id steamIDNewOwner);
};
} // namespace steam

View File

@ -0,0 +1,72 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
namespace steam {
void* matchmaking_servers::RequestInternetServerList(
unsigned int iApp, void** ppchFilters, unsigned int nFilters,
void* pRequestServersResponse) {
return nullptr;
}
void* matchmaking_servers::RequestLANServerList(unsigned int iApp,
void* pRequestServersResponse) {
return nullptr;
}
void* matchmaking_servers::RequestFriendsServerList(
unsigned int iApp, void** ppchFilters, unsigned int nFilters,
void* pRequestServersResponse) {
return nullptr;
}
void* matchmaking_servers::RequestFavoritesServerList(
unsigned int iApp, void** ppchFilters, unsigned int nFilters,
void* pRequestServersResponse) {
return nullptr;
}
void* matchmaking_servers::RequestHistoryServerList(
unsigned int iApp, void** ppchFilters, unsigned int nFilters,
void* pRequestServersResponse) {
return nullptr;
}
void* matchmaking_servers::RequestSpectatorServerList(
unsigned int iApp, void** ppchFilters, unsigned int nFilters,
void* pRequestServersResponse) {
return nullptr;
}
void matchmaking_servers::ReleaseRequest(void* hServerListRequest) {}
void* matchmaking_servers::GetServerDetails(void* hRequest, int iServer) {
return nullptr;
}
void matchmaking_servers::CancelQuery(void* hRequest) {}
void matchmaking_servers::RefreshQuery(void* hRequest) {}
bool matchmaking_servers::IsRefreshing(void* hRequest) { return false; }
int matchmaking_servers::GetServerCount(void* hRequest) { return 0; }
void matchmaking_servers::RefreshServer(void* hRequest, int iServer) {}
int matchmaking_servers::PingServer(unsigned int unIP, unsigned short usPort,
void* pRequestServersResponse) {
return 0;
}
int matchmaking_servers::PlayerDetails(unsigned int unIP, unsigned short usPort,
void* pRequestServersResponse) {
return 0;
}
int matchmaking_servers::ServerRules(unsigned int unIP, unsigned short usPort,
void* pRequestServersResponse) {
return 0;
}
void matchmaking_servers::CancelServerQuery(int hServerQuery) {}
} // namespace steam

View File

@ -0,0 +1,43 @@
#pragma once
namespace steam {
class matchmaking_servers {
protected:
~matchmaking_servers() = default;
public:
virtual void* RequestInternetServerList(unsigned int iApp, void** ppchFilters,
unsigned int nFilters,
void* pRequestServersResponse);
virtual void* RequestLANServerList(unsigned int iApp,
void* pRequestServersResponse);
virtual void* RequestFriendsServerList(unsigned int iApp, void** ppchFilters,
unsigned int nFilters,
void* pRequestServersResponse);
virtual void* RequestFavoritesServerList(unsigned int iApp,
void** ppchFilters,
unsigned int nFilters,
void* pRequestServersResponse);
virtual void* RequestHistoryServerList(unsigned int iApp, void** ppchFilters,
unsigned int nFilters,
void* pRequestServersResponse);
virtual void* RequestSpectatorServerList(unsigned int iApp,
void** ppchFilters,
unsigned int nFilters,
void* pRequestServersResponse);
virtual void ReleaseRequest(void* hServerListRequest);
virtual void* GetServerDetails(void* hRequest, int iServer);
virtual void CancelQuery(void* hRequest);
virtual void RefreshQuery(void* hRequest);
virtual bool IsRefreshing(void* hRequest);
virtual int GetServerCount(void* hRequest);
virtual void RefreshServer(void* hRequest, int iServer);
virtual int PingServer(unsigned int unIP, unsigned short usPort,
void* pRequestServersResponse);
virtual int PlayerDetails(unsigned int unIP, unsigned short usPort,
void* pRequestServersResponse);
virtual int ServerRules(unsigned int unIP, unsigned short usPort,
void* pRequestServersResponse);
virtual void CancelServerQuery(int hServerQuery);
};
} // namespace steam

View File

@ -0,0 +1,112 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
namespace steam {
bool networking::SendP2PPacket(steam_id steamIDRemote, const void* pubData,
unsigned int cubData, int eP2PSendType) {
return false;
}
bool networking::IsP2PPacketAvailable(unsigned int* pcubMsgSize, int idk) {
return false;
}
bool networking::ReadP2PPacket(void* pubDest, unsigned int cubDest,
unsigned int* pcubMsgSize,
steam_id* psteamIDRemote) {
return false;
}
bool networking::AcceptP2PSessionWithUser(steam_id steamIDRemote) {
return false;
}
bool networking::CloseP2PSessionWithUser(steam_id steamIDRemote) {
return false;
}
bool networking::CloseP2PChannelWithUser(steam_id steamIDRemote,
int iVirtualPort) {
return false;
}
bool networking::GetP2PSessionState(steam_id steamIDRemote,
void* pConnectionState) {
return false;
}
bool networking::AllowP2PPacketRelay(bool bAllow) { return false; }
unsigned int networking::CreateListenSocket(int nVirtualP2PPort,
unsigned int nIP,
unsigned short nPort,
bool bAllowUseOfPacketRelay) {
return NULL;
}
unsigned int
networking::CreateP2PConnectionSocket(steam_id steamIDTarget, int nVirtualPort,
int nTimeoutSec,
bool bAllowUseOfPacketRelay) {
return NULL;
}
unsigned int networking::CreateConnectionSocket(unsigned int nIP,
unsigned short nPort,
int nTimeoutSec) {
return NULL;
}
bool networking::DestroySocket(unsigned int hSocket, bool bNotifyRemoteEnd) {
return false;
}
bool networking::DestroyListenSocket(unsigned int hSocket,
bool bNotifyRemoteEnd) {
return false;
}
bool networking::SendDataOnSocket(unsigned int hSocket, void* pubData,
unsigned int cubData, bool bReliable) {
return false;
}
bool networking::IsDataAvailableOnSocket(unsigned int hSocket,
unsigned int* pcubMsgSize) {
return false;
}
bool networking::RetrieveDataFromSocket(unsigned int hSocket, void* pubDest,
unsigned int cubDest,
unsigned int* pcubMsgSize) {
return false;
}
bool networking::IsDataAvailable(unsigned int hListenSocket,
unsigned int* pcubMsgSize,
unsigned int* phSocket) {
return false;
}
bool networking::RetrieveData(unsigned int hListenSocket, void* pubDest,
unsigned int cubDest, unsigned int* pcubMsgSize,
unsigned int* phSocket) {
return false;
}
bool networking::GetSocketInfo(unsigned int hSocket, steam_id* pSteamIDRemote,
int* peSocketStatus, unsigned int* punIPRemote,
unsigned short* punPortRemote) {
return false;
}
bool networking::GetListenSocketInfo(unsigned int hListenSocket,
unsigned int* pnIP,
unsigned short* pnPort) {
return false;
}
int networking::GetSocketConnectionType(unsigned int hSocket) { return 0; }
int networking::GetMaxPacketSize(unsigned int hSocket) { return 0; }
} // namespace steam

View File

@ -0,0 +1,55 @@
#pragma once
namespace steam {
class networking {
protected:
~networking() = default;
public:
virtual bool SendP2PPacket(steam_id steamIDRemote, const void* pubData,
unsigned int cubData, int eP2PSendType);
virtual bool IsP2PPacketAvailable(unsigned int* pcubMsgSize, int idk);
virtual bool ReadP2PPacket(void* pubDest, unsigned int cubDest,
unsigned int* pcubMsgSize,
steam_id* psteamIDRemote);
virtual bool AcceptP2PSessionWithUser(steam_id steamIDRemote);
virtual bool CloseP2PSessionWithUser(steam_id steamIDRemote);
virtual bool CloseP2PChannelWithUser(steam_id steamIDRemote,
int iVirtualPort);
virtual bool GetP2PSessionState(steam_id steamIDRemote,
void* pConnectionState);
virtual bool AllowP2PPacketRelay(bool bAllow);
virtual unsigned int CreateListenSocket(int nVirtualP2PPort, unsigned int nIP,
unsigned short nPort,
bool bAllowUseOfPacketRelay);
virtual unsigned int CreateP2PConnectionSocket(steam_id steamIDTarget,
int nVirtualPort,
int nTimeoutSec,
bool bAllowUseOfPacketRelay);
virtual unsigned int CreateConnectionSocket(unsigned int nIP,
unsigned short nPort,
int nTimeoutSec);
virtual bool DestroySocket(unsigned int hSocket, bool bNotifyRemoteEnd);
virtual bool DestroyListenSocket(unsigned int hSocket, bool bNotifyRemoteEnd);
virtual bool SendDataOnSocket(unsigned int hSocket, void* pubData,
unsigned int cubData, bool bReliable);
virtual bool IsDataAvailableOnSocket(unsigned int hSocket,
unsigned int* pcubMsgSize);
virtual bool RetrieveDataFromSocket(unsigned int hSocket, void* pubDest,
unsigned int cubDest,
unsigned int* pcubMsgSize);
virtual bool IsDataAvailable(unsigned int hListenSocket,
unsigned int* pcubMsgSize,
unsigned int* phSocket);
virtual bool RetrieveData(unsigned int hListenSocket, void* pubDest,
unsigned int cubDest, unsigned int* pcubMsgSize,
unsigned int* phSocket);
virtual bool GetSocketInfo(unsigned int hSocket, steam_id* pSteamIDRemote,
int* peSocketStatus, unsigned int* punIPRemote,
unsigned short* punPortRemote);
virtual bool GetListenSocketInfo(unsigned int hListenSocket,
unsigned int* pnIP, unsigned short* pnPort);
virtual int GetSocketConnectionType(unsigned int hSocket);
virtual int GetMaxPacketSize(unsigned int hSocket);
};
} // namespace steam

View File

@ -0,0 +1,33 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
namespace steam {
bool remote_storage::FileWrite(const char* pchFile, const void* pvData,
int cubData) {
return true;
}
int remote_storage::GetFileSize(const char* pchFile) { return 0; }
int remote_storage::FileRead(const char* pchFile, void* pvData,
int cubDataToRead) {
OutputDebugStringA(pchFile);
return 0;
}
bool remote_storage::FileExists(const char* pchFile) { return false; }
int remote_storage::GetFileCount() { return 0; }
const char* remote_storage::GetFileNameAndSize(int iFile,
int* pnFileSizeInBytes) {
*pnFileSizeInBytes = 0;
return "";
}
bool remote_storage::GetQuota(int* pnTotalBytes, int* puAvailableBytes) {
*pnTotalBytes = 0x10000000;
*puAvailableBytes = 0x10000000;
return false;
}
} // namespace steam

View File

@ -0,0 +1,17 @@
#pragma once
namespace steam {
class remote_storage {
protected:
~remote_storage() = default;
public:
virtual bool FileWrite(const char* pchFile, const void* pvData, int cubData);
virtual int GetFileSize(const char* pchFile);
virtual int FileRead(const char* pchFile, void* pvData, int cubDataToRead);
virtual bool FileExists(const char* pchFile);
virtual int GetFileCount();
virtual const char* GetFileNameAndSize(int iFile, int* pnFileSizeInBytes);
virtual bool GetQuota(int* pnTotalBytes, int* puAvailableBytes);
};
} // namespace steam

View File

@ -0,0 +1,126 @@
#include <std_include.hpp>
#include "steam/steam.hpp"
#include "component/auth.hpp"
namespace steam {
namespace {
std::string auth_ticket;
} // namespace
int user::GetHSteamUser() { return NULL; }
bool user::LoggedOn() { return true; }
steam_id user::GetSteamID() {
static std::uint64_t seed = 0;
if (!seed) {
seed = auth::get_guid();
}
steam_id id;
id.bits = seed;
return id;
}
int user::InitiateGameConnection(void* pAuthBlob, int cbMaxAuthBlob,
steam_id steamIDGameServer,
unsigned int unIPServer,
unsigned short usPortServer, bool bSecure) {
return 0;
}
void user::TerminateGameConnection(unsigned int unIPServer,
unsigned short usPortServer) {}
void user::TrackAppUsageEvent(steam_id gameID, int eAppUsageEvent,
const char* pchExtraInfo) {}
bool user::GetUserDataFolder(char* pchBuffer, int cubBuffer) { return false; }
void user::StartVoiceRecording() {}
void user::StopVoiceRecording() {}
int user::GetAvailableVoice(unsigned int* pcbCompressed,
unsigned int* pcbUncompressed,
unsigned int nUncompressedVoiceDesiredSampleRate) {
return 0;
}
int user::GetVoice(bool bWantCompressed, void* pDestBuffer,
unsigned int cbDestBufferSize, unsigned int* nBytesWritten,
bool bWantUncompressed, void* pUncompressedDestBuffer,
unsigned int cbUncompressedDestBufferSize,
unsigned int* nUncompressBytesWritten,
unsigned int nUncompressedVoiceDesiredSampleRate) {
return 0;
}
int user::DecompressVoice(void* pCompressed, unsigned int cbCompressed,
void* pDestBuffer, unsigned int cbDestBufferSize,
unsigned int* nBytesWritten) {
return 0;
}
unsigned int user::GetVoiceOptimalSampleRate() { return 0; }
unsigned int user::GetAuthSessionTicket(void* pTicket, int cbMaxTicket,
unsigned int* pcbTicket) {
return 0;
}
int user::BeginAuthSession(const void* pAuthTicket, int cbAuthTicket,
steam_id steamID) {
return 0;
}
void user::EndAuthSession(steam_id steamID) {}
void user::CancelAuthTicket(unsigned int hAuthTicket) {}
unsigned int user::UserHasLicenseForApp(steam_id steamID, unsigned int appID) {
return 0;
}
bool user::BIsBehindNAT() { return false; }
void user::AdvertiseGame(steam_id steamIDGameServer, unsigned int unIPServer,
unsigned short usPortServer) {}
unsigned __int64 user::RequestEncryptedAppTicket(void* pUserData,
int cbUserData) {
// Generate the authentication ticket
const auto id = this->GetSteamID();
auth_ticket = "iw4-sp";
auth_ticket.resize(32);
auth_ticket.append(static_cast<char*>(pUserData), cbUserData);
auth_ticket.append(reinterpret_cast<const char*>(&id.bits), sizeof(id.bits));
// Create the call response
const auto result = callbacks::register_call();
auto retvals = static_cast<encrypted_app_ticket_response*>(
calloc(1, sizeof(encrypted_app_ticket_response)));
retvals->m_e_result = 1;
// Return the call response
callbacks::return_call(retvals, sizeof(encrypted_app_ticket_response),
encrypted_app_ticket_response::callback_id, result);
return result;
}
bool user::GetEncryptedAppTicket(void* pTicket, int cbMaxTicket,
unsigned int* pcbTicket) {
if (cbMaxTicket < 0 || auth_ticket.empty())
return false;
const auto size =
std::min(static_cast<std::size_t>(cbMaxTicket), auth_ticket.size());
std::memcpy(pTicket, auth_ticket.data(), size);
*pcbTicket = size;
return true;
}
} // namespace steam

View File

@ -0,0 +1,61 @@
#pragma once
namespace steam {
struct encrypted_app_ticket_response final {
enum { callback_id = 154 };
int m_e_result;
};
class user {
protected:
~user() = default;
public:
virtual int GetHSteamUser();
virtual bool LoggedOn();
virtual steam_id GetSteamID();
virtual int InitiateGameConnection(void* pAuthBlob, int cbMaxAuthBlob,
steam_id steamIDGameServer,
unsigned int unIPServer,
unsigned short usPortServer, bool bSecure);
virtual void TerminateGameConnection(unsigned int unIPServer,
unsigned short usPortServer);
virtual void TrackAppUsageEvent(steam_id gameID, int eAppUsageEvent,
const char* pchExtraInfo = "");
virtual bool GetUserDataFolder(char* pchBuffer, int cubBuffer);
virtual void StartVoiceRecording();
virtual void StopVoiceRecording();
virtual int
GetAvailableVoice(unsigned int* pcbCompressed, unsigned int* pcbUncompressed,
unsigned int nUncompressedVoiceDesiredSampleRate);
virtual int GetVoice(bool bWantCompressed, void* pDestBuffer,
unsigned int cbDestBufferSize,
unsigned int* nBytesWritten, bool bWantUncompressed,
void* pUncompressedDestBuffer,
unsigned int cbUncompressedDestBufferSize,
unsigned int* nUncompressBytesWritten,
unsigned int nUncompressedVoiceDesiredSampleRate);
virtual int DecompressVoice(void* pCompressed, unsigned int cbCompressed,
void* pDestBuffer, unsigned int cbDestBufferSize,
unsigned int* nBytesWritten);
virtual unsigned int GetVoiceOptimalSampleRate();
virtual unsigned int GetAuthSessionTicket(void* pTicket, int cbMaxTicket,
unsigned int* pcbTicket);
virtual int BeginAuthSession(const void* pAuthTicket, int cbAuthTicket,
steam_id steamID);
virtual void EndAuthSession(steam_id steamID);
virtual void CancelAuthTicket(unsigned int hAuthTicket);
virtual unsigned int UserHasLicenseForApp(steam_id steamID,
unsigned int appID);
virtual bool BIsBehindNAT();
virtual void AdvertiseGame(steam_id steamIDGameServer,
unsigned int unIPServer,
unsigned short usPortServer);
virtual unsigned __int64 RequestEncryptedAppTicket(void* pUserData,
int cbUserData);
virtual bool GetEncryptedAppTicket(void* pTicket, int cbMaxTicket,
unsigned int* pcbTicket);
};
} // namespace steam

Some files were not shown because too many files have changed in this diff Show More