fix: it's time to edge those bounces

This commit is contained in:
6arelyFuture 2025-02-04 12:18:39 +01:00
parent e530495ecb
commit 87ce6fcf61
Signed by: Future
GPG Key ID: F2000F181A4F7C85
22 changed files with 555 additions and 149 deletions

View File

@ -3,7 +3,9 @@
#include <utils/cryptography.hpp>
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/nt.hpp>
#include <utils/properties.hpp>
#include <utils/smbios.hpp>
#include <utils/string.hpp>
@ -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;
}

View File

@ -2,6 +2,7 @@
#include "loader/component_loader.hpp"
#include "game/dvars.hpp"
#include "branding.hpp"
#include "scheduler.hpp"
#include <utils/hook.hpp>
@ -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 {

View File

@ -0,0 +1,5 @@
#pragma once
namespace branding {
const char* get_build_number();
}

View File

@ -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 <utils/hook.hpp>
@ -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<char*>(rawfile_buf_large_local.get_buf());
const auto rawfile_buf_large = std::make_unique<char[]>(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);
}
}

View File

@ -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);

View File

@ -1,11 +1,13 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#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<char*>(0x1956038), filename,
ospath);
return file_wrapper_rotate(ospath);
}
std::vector<std::string> vectored_file_list(const std::string& path,
const std::string& extension) {
std::vector<std::string> file_list;

View File

@ -1,6 +1,9 @@
#pragma once
namespace filesystem {
bool file_wrapper_rotate(const char* ospath);
bool file_rotate(const char* filename);
std::vector<std::string> vectored_file_list(const std::string& path,
const std::string& extension);
std::string get_binary_directory();

View File

@ -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<game::BuiltinMethod>(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();

View File

@ -0,0 +1,64 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "branding.hpp"
#include "filesystem.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
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)

View File

@ -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();
}

View File

@ -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
}
};

View File

@ -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<const game::dvar_t**>(0x145EC58);
const game::dvar_t** fs_homepath =
reinterpret_cast<const game::dvar_t**>(0x195624C);
const game::dvar_t** fs_gameDirVar =
reinterpret_cast<const game::dvar_t**>(0x1956138);
} // namespace dvars

View File

@ -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

View File

@ -1,87 +0,0 @@
#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

@ -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

View File

@ -141,4 +141,6 @@ void Menu_FreeItemMemory(itemDef_s* item) {
popad;
}
}
char* Com_GetCommandLine() { return reinterpret_cast<char*>(0x145D800); }
} // namespace game

View File

@ -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;

View File

@ -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;

View File

@ -221,6 +221,7 @@ 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};
WEAK symbol<int(const char* filename)> FS_FOpenTextFileWrite{0x4495F0};
// UI
WEAK symbol<Font_s*(const ScreenPlacement* scrPlace, int fontEnum, float scale)>
@ -257,6 +258,7 @@ WEAK symbol<void(pmove_t* pm, trace_t* results, const float* start,
int contentMask)>
PM_playerTrace{0x447B90};
WEAK symbol<bool(const playerState_s* ps)> PM_IsSprinting{0x47CF70};
WEAK symbol<void(playerState_s* ps)> Jump_ClearState{0x435A40};
// Live
WEAK symbol<const char*(int controllerIndex)> Live_GetLocalClientName{0x492EF0};
@ -278,12 +280,12 @@ WEAK symbol<int(const char* string)> StringTable_HashString{0x498080};
// Vec3
WEAK symbol<void(const float* v, float scale, const float* result)> Vec3Scale{
0x429220};
// Renderer
WEAK symbol<void(const char* text, int maxChars, Font_s* font, float x, float y,
float xScale, float yScale, float rotation, const float* color,
int style)>
R_AddCmdDrawText{0x50E7A0};
// Renderer
WEAK symbol<int(const char* text, int maxChars, Font_s* font)> R_TextWidth{
0x508960};
@ -312,11 +314,12 @@ 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_frameTime{0x145EC7C};
WEAK symbol<int> com_fixedConsolePosition{0x145EC10};
WEAK symbol<int> opening_qconsole{0x145ECD4};
WEAK symbol<int> com_consoleLogOpenFailed{0x145ECB0};
WEAK symbol<field_t> g_consoleField{0x88C700};
WEAK symbol<ConDrawInputGlob> conDrawInputGlob{0x86E788};
@ -342,5 +345,5 @@ WEAK symbol<void*> DB_GetXAssetSizeHandlers{0x733408};
WEAK symbol<void*> DB_XAssetPool{0x7337F8};
WEAK symbol<unsigned int> g_poolSize{0x733510};
WEAK symbol<localization_t> localization{0x19ff820};
WEAK symbol<localization_t> localization{0x19FF820};
} // namespace game

View File

@ -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

View File

@ -0,0 +1,20 @@
#include "properties.hpp"
#include <gsl/gsl>
#include <ShlObj.h>
namespace utils::properties {
std::filesystem::path get_appdata_path() {
PWSTR path;
if (!SUCCEEDED(
SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &path))) {
throw std::runtime_error("Failed to read APPDATA path!");
}
auto _ = gsl::finally([&path] { CoTaskMemFree(path); });
static auto appdata = std::filesystem::path(path) / "alterware";
return appdata;
}
} // namespace utils::properties

View File

@ -0,0 +1,6 @@
#pragma once
#include <filesystem>
namespace utils::properties {
std::filesystem::path get_appdata_path();
}