diff --git a/src/client/component/auth.cpp b/src/client/component/auth.cpp index f35196e..05db767 100644 --- a/src/client/component/auth.cpp +++ b/src/client/component/auth.cpp @@ -3,7 +3,9 @@ #include #include +#include #include +#include #include #include @@ -63,9 +65,68 @@ std::string get_key_entropy() { return entropy; } -utils::cryptography::ecc::key& get_key() { - static auto key = - utils::cryptography::ecc::generate_key(512, get_key_entropy()); +bool load_key(utils::cryptography::ecc::key& key) { + std::string data{}; + + auto key_path = (utils::properties::get_appdata_path() / "iw4xsp-private.key") + .generic_string(); + if (!utils::io::read_file(key_path, &data)) { + return false; + } + + key.deserialize(data); + if (!key.is_valid()) { + game::Com_PrintError(game::CON_CHANNEL_ERROR, "Loaded key is invalid!\n"); + return false; + } + + return true; +} + +utils::cryptography::ecc::key generate_key() { + auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy()); + if (!key.is_valid()) { + throw std::runtime_error("Failed to generate cryptographic key!"); + } + + auto key_path = (utils::properties::get_appdata_path() / "iw4xsp-private.key") + .generic_string(); + if (!utils::io::write_file(key_path, key.serialize())) { + game::Com_PrintError(game::CON_CHANNEL_ERROR, + "Failed to write cryptographic key!\n"); + } + + game::Com_PrintError(game::CON_CHANNEL_ERROR, + "Generated cryptographic key: %llX\n", key.get_hash()); + return key; +} + +utils::cryptography::ecc::key load_or_generate_key() { + utils::cryptography::ecc::key key{}; + if (load_key(key)) { + game::Com_PrintError(game::CON_CHANNEL_ERROR, + "Loaded cryptographic key: %llX\n", key.get_hash()); + return key; + } + + return generate_key(); +} + +utils::cryptography::ecc::key get_key_internal() { + auto key = load_or_generate_key(); + + auto key_path = (utils::properties::get_appdata_path() / "iw4xsp-public.key") + .generic_string(); + if (!utils::io::write_file(key_path, key.get_public_key())) { + game::Com_PrintError(game::CON_CHANNEL_ERROR, + "Failed to write public key!\n"); + } + + return key; +} + +const utils::cryptography::ecc::key& get_key() { + static auto key = get_key_internal(); return key; } diff --git a/src/client/component/branding.cpp b/src/client/component/branding.cpp index e4d398a..60102b2 100644 --- a/src/client/component/branding.cpp +++ b/src/client/component/branding.cpp @@ -2,6 +2,7 @@ #include "loader/component_loader.hpp" #include "game/dvars.hpp" +#include "branding.hpp" #include "scheduler.hpp" #include @@ -17,10 +18,6 @@ constexpr auto* BUILD_TYPE = "IW4x_DEV SP"; constexpr auto* BUILD_TYPE = "IW4x 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(), @@ -101,6 +98,10 @@ void branding_loop() { } } // namespace +const char* get_build_number() { + return SHORTVERSION " latest " __DATE__ " " __TIME__; +} + class component final : public component_interface { public: void post_load() override { diff --git a/src/client/component/branding.hpp b/src/client/component/branding.hpp new file mode 100644 index 0000000..e626578 --- /dev/null +++ b/src/client/component/branding.hpp @@ -0,0 +1,5 @@ +#pragma once + +namespace branding { +const char* get_build_number(); +} diff --git a/src/client/component/debug.cpp b/src/client/component/debug.cpp index 58e4da2..004303f 100644 --- a/src/client/component/debug.cpp +++ b/src/client/component/debug.cpp @@ -2,7 +2,6 @@ #include "loader/component_loader.hpp" #include "game/dvars.hpp" #include "game/engine/scoped_critical_section.hpp" -#include "game/engine/large_local.hpp" #include @@ -33,7 +32,7 @@ void com_bug_f(const command::params& params) { } sprintf_s(new_file_name, "%s_%s.log", bug, - game::Live_GetLocalClientName(game::LOCAL_CLIENT_0)); + game::Live_GetLocalClientName(game::CONTROLLER_INDEX_0)); game::engine::scoped_critical_section lock(game::CRITSECT_CONSOLE, game::SCOPED_CRITSECT_NORMAL); @@ -79,8 +78,8 @@ void com_bug_name_inc_f() { 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(rawfile_buf_large_local.get_buf()); + const auto rawfile_buf_large = std::make_unique(0x18000); + auto* rawfile_buf = rawfile_buf_large.get(); auto* text = game::DB_ReadRawFile(fastfile, rawfile_buf, 0x18000); @@ -91,6 +90,10 @@ void g_print_fast_file_errors(const char* fastfile) { "There were errors when building fast file '%s'\n", fastfile); game::Com_PrintError(game::CON_CHANNEL_ERROR, "%s", text); + } else { + game::Com_Printf(game::CON_CHANNEL_DONT_FILTER, + "There were no errors when building fast file '%s'\n", + fastfile); } } diff --git a/src/client/component/dvar_patches.cpp b/src/client/component/dvar_patches.cpp index e138d67..5bca68b 100644 --- a/src/client/component/dvar_patches.cpp +++ b/src/client/component/dvar_patches.cpp @@ -25,6 +25,8 @@ public: dvar::override::register_float("cg_fovScale", 1.0f, 0.2f, 2.0f, game::DVAR_ARCHIVE | game::DVAR_SAVED); dvar::override::register_string("fs_basegame", BASEGAME, game::DVAR_INIT); + dvar::override::register_bool("com_filter_output", true, + game::DVAR_ARCHIVE); #ifdef _DEBUG dvar::override::register_bool("sv_cheats", true, game::DVAR_NONE); diff --git a/src/client/component/filesystem.cpp b/src/client/component/filesystem.cpp index 0a83475..105a4f8 100644 --- a/src/client/component/filesystem.cpp +++ b/src/client/component/filesystem.cpp @@ -1,11 +1,13 @@ #include #include "loader/component_loader.hpp" +#include "game/game.hpp" +#include "game/dvars.hpp" #include #include -#include "game_module.hpp" #include "filesystem.hpp" +#include "game_module.hpp" namespace filesystem { namespace { @@ -15,6 +17,54 @@ const char* sys_default_install_path_stub() { } } // namespace +bool file_wrapper_rotate(const char* ospath) { + constexpr auto MAX_BACKUPS = 20; + + char renamed_path[game::MAX_OSPATH]{}; + struct _stat64i32 stat_buf; + + auto oldest_index = -1; + auto current_index = 0; + time_t oldest_time = 0; + + // Check if the original file exists + if (_stat64i32(ospath, &stat_buf)) { + return true; // Return true if the file does not exist (no file to rotate) + } + + for (; current_index < MAX_BACKUPS; ++current_index) { + (void)sprintf_s(renamed_path, "%s.%03i", ospath, current_index); + + if (_stat64i32(renamed_path, &stat_buf)) { + break; // Stop if an available slot is found + } + + if (oldest_index == -1 || stat_buf.st_mtime < oldest_time) { + oldest_time = stat_buf.st_mtime; + oldest_index = current_index; + } + } + + if (current_index == MAX_BACKUPS) { + (void)sprintf_s(renamed_path, "%s.%03i", ospath, oldest_index); + (void)std::remove(renamed_path); // Remove the oldest backup file + } else { + (void)sprintf_s(renamed_path, "%s.%03i", ospath, current_index); + } + + // Rename the original file to the selected backup slot + return std::rename(ospath, renamed_path) == 0; +} + +bool file_rotate(const char* filename) { + char ospath[game::MAX_OSPATH]{}; + + const auto* basepath = (*dvars::fs_homepath)->current.string; + game::FS_BuildOSPath(basepath, reinterpret_cast(0x1956038), filename, + ospath); + return file_wrapper_rotate(ospath); +} + std::vector vectored_file_list(const std::string& path, const std::string& extension) { std::vector file_list; diff --git a/src/client/component/filesystem.hpp b/src/client/component/filesystem.hpp index 177e3ca..fad65eb 100644 --- a/src/client/component/filesystem.hpp +++ b/src/client/component/filesystem.hpp @@ -1,6 +1,9 @@ #pragma once namespace filesystem { +bool file_wrapper_rotate(const char* ospath); +bool file_rotate(const char* filename); + std::vector vectored_file_list(const std::string& path, const std::string& extension); std::string get_binary_directory(); diff --git a/src/client/component/gsc/extension.cpp b/src/client/component/gsc/extension.cpp index 902909c..2844e32 100644 --- a/src/client/component/gsc/extension.cpp +++ b/src/client/component/gsc/extension.cpp @@ -59,6 +59,11 @@ game::BuiltinMethod built_in_get_method_stub(const char** p_name, int* type) { // If no method was found let's call BuiltIn_GetMethod return utils::hook::invoke(0x5DB850, p_name, type); } + +int hud_elem_set_enum_string_stub(char* string, const char* format, + const char* string_value, const char* name) { + return snprintf(string, 0x800, format, string_value, name); +} } // namespace void add_function(const char* name, game::BuiltinFunction func, bool type) { @@ -89,6 +94,11 @@ public: .install() ->quick(); // Scr_GetMethod + // Patch buffer overflows + utils::hook(0x5BD3B9, hud_elem_set_enum_string_stub, HOOK_CALL) + .install() + ->quick(); + add_functions(); #ifdef GSC_DEBUG_FUNCTIONS add_debug_functions(); diff --git a/src/client/component/log_file.cpp b/src/client/component/log_file.cpp new file mode 100644 index 0000000..108b4f9 --- /dev/null +++ b/src/client/component/log_file.cpp @@ -0,0 +1,64 @@ +#include +#include "loader/component_loader.hpp" + +#include "branding.hpp" +#include "filesystem.hpp" + +#include +#include + +namespace log_file { +namespace { +void com_open_log_file_stub() { + time_t aclock; + char time[32]{}; + char log_file[game::MAX_OSPATH]; + + if (!game::Sys_IsMainThread() || *game::opening_qconsole) { + return; + } + + *game::opening_qconsole = true; + + tm new_time{}; + _time64(&aclock); + _localtime64_s(&new_time, &aclock); + + for (auto i = 0; i < 16; ++i) { + const auto* file_name = + (i == 0) ? "console.log" + : utils::string::va("{0}.{1:03}", "console.log", i); + game::I_strncpyz(log_file, file_name, sizeof(log_file)); + + if (!filesystem::file_rotate(log_file)) { + continue; + } + + *game::logfile = game::FS_FOpenTextFileWrite(log_file); + if (*game::logfile) { + game::Com_Printf(game::CON_CHANNEL_SYSTEM, "\'%s\'\n", + game::Com_GetCommandLine()); + (void)asctime_s(time, sizeof(time), &new_time); + game::Com_Printf(game::CON_CHANNEL_SYSTEM, + "Build %s. Logfile opened on %s\n", + branding::get_build_number(), time); + break; // Stop attempting further backups + } + } + + *game::opening_qconsole = false; + *game::com_consoleLogOpenFailed = *game::logfile == 0; +} +} // namespace + +class component final : public component_interface { +public: + void post_load() override { + utils::hook(0x603103, com_open_log_file_stub, HOOK_CALL) + .install() // hook* + ->quick(); + } +}; +} // namespace log_file + +REGISTER_COMPONENT(log_file::component) diff --git a/src/client/component/patches.cpp b/src/client/component/patches.cpp index eef70d6..bf64ac9 100644 --- a/src/client/component/patches.cpp +++ b/src/client/component/patches.cpp @@ -55,6 +55,51 @@ void ui_replace_directive_stub(const int local_client_num, game::UI_ReplaceDirective(local_client_num, src_string, dst_string, dst_buffer_size); } + +int g_parse_weapon_accuracy_graph_internal(char* string, const char* format, + const char* dir_name, + const char* graph_name) { + return snprintf(string, 0x64, format, dir_name, graph_name); +} + +int scr_load_anim_tree_internal(char* string, const char* format, + const char* file_name) { + return snprintf(string, 0x64, format, file_name); +} + +// clang-format off +void __declspec(naked) string_table_get_column_value_for_row() { + static const char* fmt = + "Looking up row %i, column %i of table \'%s\' results in an empty string " + "(first column is %s, second column is %s for that row)\n"; + static const DWORD Com_PrintError = 0x4C6980; + + // EAX is safe to reuse because it's nuked by the game's code after this stub exits + __asm { + // game's code + mov eax, dword ptr [ecx + eax * 0x8] + push edx + push eax + + mov eax, [esp + 0xC] // get table pointer + mov eax, [eax] // get table->name + push eax + + // game's code + push edi // column + push ebx // row + + push fmt + + push 0xD // channel + call Com_PrintError + add esp, 0x1C + + push 0x4BC84E + ret + } +} +// clang-format on } // namespace class component final : public component_interface { @@ -95,6 +140,19 @@ public: .install() // hook* ->quick(); + utils::hook(0x5DF16F, g_parse_weapon_accuracy_graph_internal, HOOK_CALL) + .install() // hook* + ->quick(); + + utils::hook(0x609EAF, scr_load_anim_tree_internal, HOOK_CALL) + .install() // hook* + ->quick(); + + // Fix crash in StringTable_GetColumnValueForRow + utils::hook(0x4BC838, string_table_get_column_value_for_row, HOOK_JUMP) + .install() // hook* + ->quick(); + patch_sp(); } diff --git a/src/client/component/player_movement.cpp b/src/client/component/player_movement.cpp index ac4713c..fae8747 100644 --- a/src/client/component/player_movement.cpp +++ b/src/client/component/player_movement.cpp @@ -30,29 +30,67 @@ void __declspec(naked) pm_step_slide_move_stub() { } } -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]; +void __declspec(naked) pm_project_velocity_stub() { + __asm { + push eax; + mov eax, dvars::pm_bouncesAllAngles; + cmp byte ptr [eax + 0x10], 1; + pop eax; - 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; + je force_bounce; + + test ah, 0x5; + jnp force_bounce; + push 0x4761AF; + ret; + + force_bounce: + push 0x4761CF; + ret; } +} - auto new_z = vel_in[0] * normal[0] + vel_in[1] * normal[1]; - new_z = -new_z / normal[2]; +void __declspec(naked) pm_project_velocity2_stub() { + __asm { + push eax; + mov eax, dvars::pm_bouncesAllAngles; + cmp byte ptr [eax + 0x10], 1; + pop eax; - const auto length_scale = - std::sqrtf((vel_in[2] * vel_in[2] + length_squared_2d) / - (new_z * new_z + length_squared_2d)); + je force_bounce; - if (dvars::pm_bouncesAllAngles->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; + fld1; + jmp go_back; + + force_bounce: + fldpi; + + go_back: + fld [esp + 0x14 + 0x8]; + push 0x4761A4; + ret; + } +} + +void __declspec(naked) pm_step_slide_move2_stub() { + static float flt = 0.005f; + __asm { + push eax; + mov eax, dvars::pm_bouncesAllAngles; + cmp byte ptr [eax + 0x10], 1; + pop eax; + + je force_bounce; + + fld ds:0x6D3AE4; + jmp go_back; + + force_bounce: + fld flt; + + go_back: + push 0x4E9061; + ret; } } @@ -116,6 +154,129 @@ void pm_crash_land_stub(const float* v, float scale, const float* result) { } } +void __declspec(naked) pm_accelerate_stub() { + __asm { + // Game's code + sub esp, 0x20; + + push eax; + mov eax, dvars::pm_csStrafe; + cmp byte ptr [eax + 0x10], 1; + pop eax; + + je attempt_to_force_movement; + + // Game's code + test byte ptr [esi + 0xC], 8; + jnz movement; + + go_back: + push 0x64D87D; + ret; + + attempt_to_force_movement: + test byte ptr [esi + 0xC], 8; + je movement; + jmp go_back; + + movement: + push 0x64D91A; + ret; + } +} + +void __declspec(naked) cm_is_edge_walkable_stub() { + __asm { + push eax; + mov eax, dvars::pm_terrainEdgeBounces; + cmp byte ptr [eax + 0x10], 1; + pop eax; + + je bounce; + + // Game's code + lea eax, [eax + ecx * 2]; + add eax, ecx; + push 0x5FAED5; + ret; + + bounce: + xor eax, eax; + ret; + } +} + +void __declspec(naked) cm_is_edge_walkable2_stub() { + __asm { + push eax; + mov eax, dvars::pm_terrainEdgeBounces; + cmp byte ptr [eax + 0x10], 1; + pop eax; + + je bounce; + jmp go_back; + + bounce: + mov cl, 0; + + go_back: + pop ebp; + mov byte ptr [edx + 0x2A], cl; + pop ebx; + add esp, 0x78; + ret; + } +} + +void __declspec(naked) pm_step_slide_move3_stub() { + __asm { + push eax; + mov eax, dvars::pm_doubleBounces; + cmp byte ptr [eax + 0x10], 1; + pop eax; + + je force_double_bounce; + + test ah, 0x5; + jp go_back; + + force_double_bounce: + push 0x4E904A; + ret; + + go_back: + push 0x4E90C8; + ret; + } +} + +void __declspec(naked) pm_step_slide_move4_stub() { + __asm { + push eax; + mov eax, dvars::pm_doubleBounces; + cmp byte ptr [eax + 0x10], 1; + pop eax; + + // Code hook skipped + add esp, 0xC; + + je force_double_bounce; + + push 0x4E8FB7; + ret; + + force_double_bounce: + push 0x4E8FD0; + ret; + } +} + +void jump_clear_state_stub(game::playerState_s* ps) { + if (!dvars::pm_doubleBounces->current.enabled) { + game::Jump_ClearState(ps); + } +} + void __declspec(naked) jump_check_stub() { __asm { push eax; @@ -184,9 +345,16 @@ public: utils::hook(0x4E9054, pm_step_slide_move_stub, HOOK_JUMP) .install() ->quick(); // PM_StepSlideMove - utils::hook(0x4E90BE, pm_project_velocity_stub, HOOK_CALL) + + utils::hook(0x4761AA, pm_project_velocity_stub, HOOK_JUMP) .install() ->quick(); // PM_StepSlideMove + utils::hook(0x47619E, pm_project_velocity2_stub, HOOK_JUMP) + .install() + ->quick(); // PM_StepSlideMove + utils::hook(0x4E905B, pm_step_slide_move2_stub, HOOK_JUMP) + .install() + ->quick(); utils::hook(0x4FA809, weapon_rocket_launcher_fire_stub, HOOK_CALL) .install() @@ -209,10 +377,33 @@ public: .install() ->quick(); // PM_CorrectAllSolid + // Double bounces + utils::hook(0x4E9045, pm_step_slide_move3_stub, HOOK_JUMP) + .install() + ->quick(); + utils::hook(0x4E8FAE, pm_step_slide_move4_stub, HOOK_JUMP) + .install() + ->quick(); + utils::hook(0x651D80, jump_clear_state_stub, HOOK_CALL) + .install() + ->quick(); // PM_GroundTrace + + // Bunnny hop + utils::hook(0x4D25E8, jump_check_stub, HOOK_JUMP).install()->quick(); + // Enable / Disable jump landing punishment within PM_CrashLand utils::hook(0x64E571, pm_crash_land_stub, HOOK_CALL) .install() ->quick(); // Vec3Scale - utils::hook(0x4D25E8, jump_check_stub, HOOK_JUMP).install()->quick(); + + utils::hook(0x64D870, pm_accelerate_stub, HOOK_JUMP).install()->quick(); + + // Implement terrain edge bounces + utils::hook(0x5FAED0, cm_is_edge_walkable_stub, HOOK_JUMP) + .install() + ->quick(); + utils::hook(0x5FBB14, cm_is_edge_walkable2_stub, HOOK_JUMP) + .install() + ->quick(); utils::hook(0x6530C3, pm_move_single_stub, HOOK_JUMP).install()->quick(); @@ -224,10 +415,14 @@ public: // clang-format off dvars::pm_bounces = game::Dvar_RegisterBool( "pm_bounces", false, game::DVAR_NONE, "CoD4 Bounces"); + dvars::pm_doubleBounces = game::Dvar_RegisterBool( + "pm_doubleBounces", false, game::DVAR_NONE, "CoD4 Double Bounces"); dvars::pm_bouncesAllAngles = game::Dvar_RegisterBool( "pm_bouncesAllAngles", 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_csStrafe = game::Dvar_RegisterBool( + "pm_csStrafe", false, game::DVAR_NONE, "Imitate CS Strafe movement"); dvars::pm_rocketJumpScale = game::Dvar_RegisterFloat( "pm_rocketJumpScale", 64.0f, 0.0f, 1024.0f, game::DVAR_NONE, ""); dvars::pm_playerCollision = game::Dvar_RegisterBool( @@ -239,6 +434,8 @@ public: 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"); + dvars::pm_terrainEdgeBounces = game::Dvar_RegisterBool( + "pm_terrainEdgeBounces", false, game::DVAR_NONE, "Bounces on terrain edges"); // clang-format on } }; diff --git a/src/client/game/dvars.cpp b/src/client/game/dvars.cpp index 26f1acb..d73bb96 100644 --- a/src/client/game/dvars.cpp +++ b/src/client/game/dvars.cpp @@ -4,14 +4,17 @@ namespace dvars { const game::dvar_t* r_noBorder = nullptr; const game::dvar_t* pm_bounces = nullptr; +const game::dvar_t* pm_doubleBounces = nullptr; const game::dvar_t* pm_bouncesAllAngles = nullptr; const game::dvar_t* pm_rocketJump = nullptr; +const game::dvar_t* pm_csStrafe = 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* pm_terrainEdgeBounces = nullptr; const game::dvar_t* cg_drawVersion = nullptr; const game::dvar_t* cg_drawVersionX = nullptr; @@ -36,6 +39,8 @@ const game::dvar_t** com_developer = const game::dvar_t** com_developer_script = reinterpret_cast(0x145EC58); +const game::dvar_t** fs_homepath = + reinterpret_cast(0x195624C); const game::dvar_t** fs_gameDirVar = reinterpret_cast(0x1956138); } // namespace dvars diff --git a/src/client/game/dvars.hpp b/src/client/game/dvars.hpp index f5291c2..d6494cc 100644 --- a/src/client/game/dvars.hpp +++ b/src/client/game/dvars.hpp @@ -4,14 +4,17 @@ namespace dvars { extern const game::dvar_t* r_noBorder; extern const game::dvar_t* pm_bounces; +extern const game::dvar_t* pm_doubleBounces; extern const game::dvar_t* pm_bouncesAllAngles; extern const game::dvar_t* pm_rocketJump; +extern const game::dvar_t* pm_csStrafe; 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* pm_terrainEdgeBounces; extern const game::dvar_t* cg_drawVersion; extern const game::dvar_t* cg_drawVersionX; @@ -31,5 +34,6 @@ extern const game::dvar_t** version; extern const game::dvar_t** com_developer; extern const game::dvar_t** com_developer_script; +extern const game::dvar_t** fs_homepath; extern const game::dvar_t** fs_gameDirVar; } // namespace dvars diff --git a/src/client/game/engine/large_local.cpp b/src/client/game/engine/large_local.cpp deleted file mode 100644 index b5743dc..0000000 --- a/src/client/game/engine/large_local.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include - -#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 diff --git a/src/client/game/engine/large_local.hpp b/src/client/game/engine/large_local.hpp deleted file mode 100644 index 895c714..0000000 --- a/src/client/game/engine/large_local.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#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 diff --git a/src/client/game/game.cpp b/src/client/game/game.cpp index d9a90f7..7a3cae4 100644 --- a/src/client/game/game.cpp +++ b/src/client/game/game.cpp @@ -141,4 +141,6 @@ void Menu_FreeItemMemory(itemDef_s* item) { popad; } } + +char* Com_GetCommandLine() { return reinterpret_cast(0x145D800); } } // namespace game diff --git a/src/client/game/game.hpp b/src/client/game/game.hpp index 10e66f4..6a9f7be 100644 --- a/src/client/game/game.hpp +++ b/src/client/game/game.hpp @@ -47,6 +47,8 @@ int PC_Float_Parse(int handle, float* f); void Menu_FreeItemMemory(itemDef_s* item); +char* Com_GetCommandLine(); + // Global definitions constexpr auto CMD_MAX_NESTING = 8; diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index dc61a7d..d7fd699 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -15,6 +15,13 @@ enum LocalClientNum_t { LOCAL_CLIENT_COUNT = 1, }; +enum ControllerIndex_t { + INVALID_CONTROLLER_PORT = -1, + CONTROLLER_INDEX_0 = 0x0, + CONTROLLER_INDEX_FIRST = 0x0, + CONTROLLER_INDEX_COUNT = 0x1, +}; + struct scr_entref_t { unsigned __int16 entnum; unsigned __int16 classnum; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index ed56cc3..1913343 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -221,6 +221,7 @@ WEAK symbol FS_BuildOSPath{0x4E48F0}; WEAK symbol FS_Startup{0x47AF20}; +WEAK symbol FS_FOpenTextFileWrite{0x4495F0}; // UI WEAK symbol @@ -257,6 +258,7 @@ WEAK symbol PM_playerTrace{0x447B90}; WEAK symbol PM_IsSprinting{0x47CF70}; +WEAK symbol Jump_ClearState{0x435A40}; // Live WEAK symbol Live_GetLocalClientName{0x492EF0}; @@ -278,12 +280,12 @@ WEAK symbol StringTable_HashString{0x498080}; // Vec3 WEAK symbol Vec3Scale{ 0x429220}; + +// Renderer WEAK symbol R_AddCmdDrawText{0x50E7A0}; - -// Renderer WEAK symbol R_TextWidth{ 0x508960}; @@ -312,11 +314,12 @@ WEAK symbol g_minLargeLocalRightPos{0x195AB00}; WEAK symbol g_dwTlsIndex{0x1BFC750}; -WEAK symbol com_frameTime{0x145EC7C}; - WEAK symbol cin_skippable{0x73264C}; +WEAK symbol com_frameTime{0x145EC7C}; WEAK symbol com_fixedConsolePosition{0x145EC10}; +WEAK symbol opening_qconsole{0x145ECD4}; +WEAK symbol com_consoleLogOpenFailed{0x145ECB0}; WEAK symbol g_consoleField{0x88C700}; WEAK symbol conDrawInputGlob{0x86E788}; @@ -342,5 +345,5 @@ WEAK symbol DB_GetXAssetSizeHandlers{0x733408}; WEAK symbol DB_XAssetPool{0x7337F8}; WEAK symbol g_poolSize{0x733510}; -WEAK symbol localization{0x19ff820}; +WEAK symbol localization{0x19FF820}; } // namespace game diff --git a/src/client/loader/binary_loader.cpp b/src/client/loader/binary_loader.cpp index 4101ff9..fdab935 100644 --- a/src/client/loader/binary_loader.cpp +++ b/src/client/loader/binary_loader.cpp @@ -10,21 +10,33 @@ #define SP_XLABS_HASH \ "05D499D77028859D4BA30C852DA85CCA5F02678B22AEA9E27D7C56973B14A0BC" +#define SP_ALTER_HASH \ + "9480909B18CF5A4EC483C989AAFCE929D2172C5F9448F8C23B723683D1A43565" + namespace binary_loader { +namespace { std::string load_base() { + std::string path = "iw4sp.exe"; + + if (utils::io::file_exists("data/iw4sp.exe")) { + path = "data/iw4sp.exe"; + } + std::string data; - if (!utils::io::read_file("iw4sp.exe", &data)) { + if (!utils::io::read_file(path, &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)) { + if ((hash != SP_ALTER_HASH) && (hash != SP_HASH)) { throw std::runtime_error( - "Your iw4sp.exe is incompatible with this client."); + "Your iw4sp.exe is incompatible with this client. Please use the " + "AlterWare launcher to install IW4x-SP"); } return data; } +} // namespace std::string load() { return load_base(); } } // namespace binary_loader diff --git a/src/common/utils/properties.cpp b/src/common/utils/properties.cpp new file mode 100644 index 0000000..e7e2280 --- /dev/null +++ b/src/common/utils/properties.cpp @@ -0,0 +1,20 @@ +#include "properties.hpp" + +#include + +#include + +namespace utils::properties { +std::filesystem::path get_appdata_path() { + PWSTR path; + if (!SUCCEEDED( + SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path))) { + throw std::runtime_error("Failed to read APPDATA path!"); + } + + auto _ = gsl::finally([&path] { CoTaskMemFree(path); }); + + static auto appdata = std::filesystem::path(path) / "alterware"; + return appdata; +} +} // namespace utils::properties diff --git a/src/common/utils/properties.hpp b/src/common/utils/properties.hpp new file mode 100644 index 0000000..fc47676 --- /dev/null +++ b/src/common/utils/properties.hpp @@ -0,0 +1,6 @@ +#pragma once +#include + +namespace utils::properties { +std::filesystem::path get_appdata_path(); +}