diff --git a/README.md b/README.md index e150a20f7c..ab7419ca6a 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ This is a fork of [emoose's Xenia build](https://github.com/emoose/xenia) as ori | Halo Reach | TU0/TU1 | | Halo 4 | TU0/TU8 | | Crackdown 2 | TU0/TU5 | +| Saints Row | TU1 | | Saints Row 2 | TU3 | | Dark Messiah of Might and Magic | Singleplayer & Multiplayer | | Just Cause | TU0 | diff --git a/bindings.ini b/bindings.ini index 363d619917..d6dc8d4357 100644 --- a/bindings.ini +++ b/bindings.ini @@ -22,6 +22,74 @@ Down = Down Left = Left Right = Right +[545107D1 Default - Saints Row 1] +W = LS-Up +S = LS-Down +A = LS-Left +D = LS-Right +CapsLock = Modifier +Ctrl = LS +C = LS +LClick = RT +RClick = LT +Mouse5 = B +Tab = B +F = Y +R = A +Space = X +V = LB +1 = Up +2 = Down +3 = Left +4 = Right +Z = Down +LShift = RB +;Tidle on US keyboard "`" +' = Start +Enter = Start +;Back is unused but for any mods that utilize it. +P = Back +Up = RS-Up +Down = RS-Down +Left = RS-Left +Right = RS-Right + +[545107D1 Vehicle - Saints Row 1] +W = A +S = X +A = LS-Left +D = LS-Right +Q = LB +E = RB +CapsLock = Modifier +Ctrl = LS +C = LS +LClick = RT +RClick = LT +Mouse5 = B +Tab = B +F = Y +R = A +Space = LT +V = LB +1 = Up +2 = Down +3 = Left +4 = Right +Z = Down +MWheelDown = Left +MWheelUp = Right +LShift = RB +;Tidle on US keyboard "`" +' = Start +Enter = Start +;Back is unused but for any mods that utilize it. +P = Back +Up = RS-Up +Down = RS-Down +Left = RS-Left +Right = RS-Right + [555307DC Default - Far Cry Instincts Predator] CapsLock = Modifier W = LS-Up diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index ccd828776f..efa5bf34c0 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -83,9 +83,10 @@ DEFINE_bool(ge_remove_blur, false, DEFINE_bool(ge_debug_menu, false, "(GoldenEye) Enables the debug menu, accessible with LB/1", "MouseHook"); -DEFINE_bool(sr2_better_drive_cam, true, - "(Saints Row 2) unties X rotation from vehicles when " - "auto-centering is disabled akin to GTA IV.", +DEFINE_bool(sr_better_drive_cam, true, + "(Saints Row 1&2) unties X rotation from vehicles when " + "auto-centering is disabled, this makes the camera similar to the " + "GTA series vehicle camera.", "MouseHook"); DEFINE_bool(sr2_better_handbrake_cam, true, @@ -93,6 +94,23 @@ DEFINE_bool(sr2_better_handbrake_cam, true, "handbraking akin to SR1.", "MouseHook"); +DEFINE_bool( + sr2_hold_fine_aim, true, + "(Saints Row 2) Switches fineaim (ADS) from a toggle to hold press.", + "MouseHook"); + +DEFINE_bool(sr_havok_fix_frametime, false, + "(Saints Row 1&2) Fixes cutscene object synchronization and doors " + "teleporting on high fps, as seen in Juiced Patch. (Causes " + "Performance loss at a higher FPSes.) ", + "MouseHook"); + +DEFINE_bool(sr1_increase_vehicle_rotation_limit, true, + "(Saints Row 1) Patch vehicle vertical rotation limit to be mostly " + "the same " + "as on-foot.", + "MouseHook"); + DEFINE_bool(allow_game_relative_writes, false, "Not useful to non-developers. Allows code to write to paths " "relative to game://. Used for " @@ -1675,6 +1693,79 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, } } + if (module->title_id() == 0x545107D1) { + struct SR1PatchOffsets { + uint32_t check_addr; + uint32_t check_value; + uint32_t beNOP; + uint32_t mousefix_addr1; + uint32_t mousefix_addr2; + uint32_t mousefix_addr3; + uint32_t aim_assist_xbtl; // File declares aim_assist values. + uint32_t havok_write_frametime_address1; + uint32_t havok_write_frametime_address2; + uint32_t vehicle_rotationXWrite_addr_start; // lfs f0, (flt_827F9B04 - + // 0x827F99A0)(r31) -- no + // idea how I figured this, + // or why/how it works. + uint32_t vehicle_rotationYLimit_max_read_addr1; + uint32_t vehicle_rotationYLimit_max_read_addr2; + uint32_t vehicle_rotationYLimit_min_read_addr1; + uint32_t max_float_addr_lis1; + uint32_t max_float_addr_lis2; + uint32_t min_float_addr_lis1; + uint32_t write_max_value1; // making it same as on-foot causes the camera + // to clip. + }; + std::vector supported_builds = { + // TU1 Release build + {0x82050304, 0x7361696E, 0x60000000, 0x8249db00, 0x8249dd28, 0x8249dd50, + 0x82079cbc, 0x82195324, 0x8225BD8C, 0x8211D604, 0x82772D90, 0x8211FC8C, + 0x82772DB0, 0xC108C88C, 0xC00BC88C, 0xC0C8B850, 0x8208C88C}, + }; + for (auto& build : supported_builds) { + auto* test_addr = (xe::be*)module->memory()->TranslateVirtual( + build.check_addr); + if (*test_addr != build.check_value) { + continue; + } + // Write beNOP to each write address + patch_addr(build.mousefix_addr1, build.beNOP); + patch_addr(build.mousefix_addr2, build.beNOP); + patch_addr(build.mousefix_addr3, build.beNOP); + if (cvars::disable_autoaim && build.aim_assist_xbtl) { + patch_addr(build.aim_assist_xbtl, build.beNOP); + } + if (cvars::sr_havok_fix_frametime && + build.havok_write_frametime_address1 && + build.havok_write_frametime_address2) { + patch_addr(build.havok_write_frametime_address1, build.beNOP); + patch_addr(build.havok_write_frametime_address2, build.beNOP); + } + if (cvars::sr_better_drive_cam && + build.vehicle_rotationXWrite_addr_start) { + uint32_t addr = build.vehicle_rotationXWrite_addr_start; + for (int i = 0; i < 4; ++i) { + patch_addr(addr, build.beNOP); + addr += 0x4; + } + } + if (cvars::sr1_increase_vehicle_rotation_limit && + build.vehicle_rotationYLimit_max_read_addr1) { + // 827FA3F8 , 827FA3F4 for current rotation limit, maybe lower the max + // value because it clips into the vehicle? + patch_addr(build.vehicle_rotationYLimit_max_read_addr1, + build.max_float_addr_lis1); + patch_addr(build.vehicle_rotationYLimit_max_read_addr2, + build.max_float_addr_lis2); + patch_addr(build.vehicle_rotationYLimit_min_read_addr1, + build.min_float_addr_lis1); + patch_addr(build.write_max_value1, 0xbf000000); + } + break; + } + } + if (module->title_id() == 0x545107FC) { struct SR2PatchOffsets { uint32_t check_addr; @@ -1718,6 +1809,7 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, uint32_t Vehicle_RotationXWrite_addr1; uint32_t Vehicle_RotationXWrite_addr2; // Handbrake. uint32_t aim_assist_xbtl; // File declares aim_assist values. + uint32_t havok_write_frametime_address1; }; std::vector supported_builds = { @@ -1727,7 +1819,7 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, 0x8247832c, 0x821a4b84, 0x824e6a68, 0x824e7f50, 0x824e6b8c, 0x82478934, 0x824e6b2c, 0x82478330, 0x82478094, 0x821a4b88, 0x82B7A5AC, 0x82B7A5A8, 0x82B77C04, 0x82B77C08, 0x82B77C0C, 0x82B77C08, 0x82B77C10, 0x821A4D20, - 0x821A4D18, 0x821a1f74, 0x821A2A2C, 0x820A61C0}, + 0x821A4D18, 0x821a1f74, 0x821A2A2C, 0x820A61C0, 0x8221CEAC}, }; for (auto& build : supported_builds) { @@ -1767,7 +1859,7 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, patch_addr(build.multiplierread_addr5, build.zero_patch1); patch_addr(build.sensYvalue_addr1, build.zero_patch1); patch_addr(build.sensXvalue_addr2, build.zero_patch1); - if (cvars::sr2_better_drive_cam && build.Vehicle_RotationXWrite_addr1) { + if (cvars::sr_better_drive_cam && build.Vehicle_RotationXWrite_addr1) { patch_addr(build.Vehicle_RotationXWrite_addr1, build.beNOP); } @@ -1778,7 +1870,8 @@ X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path, if (cvars::disable_autoaim && build.aim_assist_xbtl) { patch_addr(build.aim_assist_xbtl, build.beNOP); } - + if (cvars::sr_havok_fix_frametime && build.havok_write_frametime_address1) + patch_addr(build.havok_write_frametime_address1, build.beNOP); break; } } diff --git a/src/xenia/hid/winkey/hookables/SaintsRow1.cc b/src/xenia/hid/winkey/hookables/SaintsRow1.cc new file mode 100644 index 0000000000..430914f9c4 --- /dev/null +++ b/src/xenia/hid/winkey/hookables/SaintsRow1.cc @@ -0,0 +1,382 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2023 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#define _USE_MATH_DEFINES + +#include "xenia/hid/winkey/hookables/SaintsRow1.h" + +#include "xenia/base/platform_win.h" +#include "xenia/cpu/processor.h" +#include "xenia/emulator.h" +#include "xenia/hid/hid_flags.h" +#include "xenia/hid/input_system.h" +#include "xenia/kernel/util/shim_utils.h" +#include "xenia/kernel/xmodule.h" +#include "xenia/kernel/xthread.h" +#include "xenia/xbox.h" + +using namespace xe::kernel; + +DECLARE_double(sensitivity); +DECLARE_bool(invert_y); +DECLARE_bool(invert_x); +DECLARE_double(right_stick_hold_time_workaround); +DECLARE_bool(sr_havok_fix_frametime) + + const uint32_t kTitleIdSaintsRow1 = 0x545107D1; + +namespace xe { +namespace hid { +namespace winkey { +struct GameBuildAddrs { + const char* title_version; + uint32_t x_address; + uint32_t y_address; + uint32_t vehicle_address; + uint32_t weapon_wheel_address; + uint32_t menu_status_address; + uint32_t havok_frametime_address; + uint32_t current_frametime_address; // x_axis_addition = + // -(float)((float)_FP12 / + // current_frametime); + uint32_t ingame_sens; + uint32_t current_fov_address; + uint32_t isfirstperson_address; // Unused game camera mode ; toggleable with + // a console command mostly usable with + // Tervel's sr1fineaim plugin. + uint32_t fineaim_y_address; + uint32_t slow_pan_horizontal_multiplier_address; +}; + +std::map supported_builds{ + {SaintsRow1Game::GameBuild::Unknown, {" ", NULL, NULL}}, + {SaintsRow1Game::GameBuild::SaintsRow1_TU1, + {"1.0.1", 0x827f9af8, 0x827F9B00, 0x82932407, 0x8283CA7B, 0x835F27A3, + 0x835F2684, 0x827CA69C, 0x827F9AD8, 0x827F9B58, 0x827F99C7, 0x827F9BA4, + 0x827F956C}}}; + +SaintsRow1Game::~SaintsRow1Game() = default; + +bool SaintsRow1Game::IsGameSupported() { + if (kernel_state()->title_id() != kTitleIdSaintsRow1) { + return false; + } + + const std::string current_version = + kernel_state()->emulator()->title_version(); + + for (auto& build : supported_builds) { + if (current_version == build.second.title_version) { + game_build_ = build.first; + return true; + } + } + + return false; +} + +float SaintsRow1Game::DegreetoRadians(float degree) { + return (float)(degree * (M_PI / 180)); +} + +float SaintsRow1Game::RadianstoDegree(float radians) { + return (float)(radians * (180 / M_PI)); +} + +bool SaintsRow1Game::DoHooks(uint32_t user_index, RawInputState& input_state, + X_INPUT_STATE* out_state) { + if (!IsGameSupported()) { + return false; + } + + if (supported_builds.count(game_build_) == 0) { + return false; + } + + // REMOVE THIS FOR RELEASE NEEDS TO BE A PATCH! + // xtbl edits can't be made into a patch most likely? + xe::be* ingamesens_x = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].ingame_sens); + + xe::be* slow_pan_horizontal_multiplier = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].slow_pan_horizontal_multiplier_address); + + xe::be* slow_pan_vertical_multiplier = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].slow_pan_horizontal_multiplier_address + + 0x4); + + xe::be* ingamesens_y = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].ingame_sens + 0x4); + + if (*ingamesens_x != 0.01999999955f || *ingamesens_y != 0.01999999955f) { + *ingamesens_x = 0.01999999955f; + *ingamesens_y = 0.01999999955f; + } + + if (*slow_pan_vertical_multiplier != 0.00009999999747f || + *slow_pan_horizontal_multiplier != 0.00009999999747f) { + *slow_pan_horizontal_multiplier = 0.00009999999747f; + *slow_pan_vertical_multiplier = 0.00009999999747f; + } + + xe::be* ingame_frametime = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].current_frametime_address); + + float frametime = *ingame_frametime; + if (cvars::sr_havok_fix_frametime && !isTervelPlugin()) + FixHavokFrameTime(frametime); + + // float correctFrametime = 1 / *currentFPS; + + //*frametime = correctFrametime * 2; + + auto now = std::chrono::steady_clock::now(); + auto elapsed_x = std::chrono::duration_cast( + now - last_movement_time_x_) + .count(); + auto elapsed_y = std::chrono::duration_cast( + now - last_movement_time_y_) + .count(); + + if (!(inFirstPerson() && isTervelPlugin())) { + // Declare static variables for last deltas + static int last_x_delta = 0; + static int last_y_delta = 0; + + const long long hold_time = + static_cast(cvars::right_stick_hold_time_workaround); + // Check for mouse movement and set thumbstick values + if (input_state.mouse.x_delta != 0) { + if (input_state.mouse.x_delta > 0) { + out_state->gamepad.thumb_rx = SHRT_MAX; + } else { + out_state->gamepad.thumb_rx = SHRT_MIN; + } + last_movement_time_x_ = now; + last_x_delta = input_state.mouse.x_delta; + } else if (elapsed_x < hold_time) { // hold time + if (last_x_delta > 0) { + out_state->gamepad.thumb_rx = SHRT_MAX; + } else { + out_state->gamepad.thumb_rx = SHRT_MIN; + } + } + + if (input_state.mouse.y_delta != 0) { + if (input_state.mouse.y_delta > 0) { + out_state->gamepad.thumb_ry = SHRT_MAX; + } else { + out_state->gamepad.thumb_ry = SHRT_MIN; + } + last_movement_time_y_ = now; + last_y_delta = input_state.mouse.y_delta; + } else if (elapsed_y < hold_time) { // hold time + if (last_y_delta > 0) { + out_state->gamepad.thumb_ry = SHRT_MIN; + } else { + out_state->gamepad.thumb_ry = SHRT_MAX; + } + } + + // Return true if either X or Y delta is non-zero or if within the hold time + if (input_state.mouse.x_delta == 0 && input_state.mouse.y_delta == 0 && + elapsed_x >= hold_time && elapsed_y >= hold_time) { + return false; + } + } + // Stop mouse this late here to allow RS in menus and frametime fix to apply. + if (isPaused()) return false; + + XThread* current_thread = XThread::GetCurrentThread(); + + if (!current_thread) { + return false; + } + + xe::be* addition_x = kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].x_address); + + xe::be* radian_y = kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].y_address); + + xe::be* current_fov = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].current_fov_address); + + float degree_x = *addition_x; + float degree_y = RadianstoDegree(*radian_y); + float fov = *current_fov; + + float divider_y = 15.f; + float divider_x = 1350.f; + + static xe::be* fine_aim_x = NULL; + static xe::be* fine_aim_y = NULL; + if (inFirstPerson() && isTervelPlugin()) { + divider_x = 15.f; + frametime = 1.f; + + fine_aim_x = kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].fineaim_y_address + 0x4); + + fine_aim_y = kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].fineaim_y_address); + degree_x = RadianstoDegree(*fine_aim_x); + } + + if (fov < 60.f) { + fov = 60.f / fov; + divider_y = divider_y * fov; + divider_x = divider_x * fov; + } + + // X-axis = 0 to 360 + // division over 1350 is assuming if frametime is 1/30, this should fix + // sensitivity fluctuation due to framerate as that's what the game does at + // 8249DD28(TU1); x_axis_addition = -(float)((float)_FP12 / frametime); + // stuttering might still occur due to framerates, as it's expected each + // frame? -= isn't ideal but that's the only way it works. - Clippy95 + if (!cvars::invert_x) { + degree_x += + ((input_state.mouse.x_delta / divider_x) * (float)cvars::sensitivity) / + frametime; + } else { + degree_x -= + ((input_state.mouse.x_delta / divider_x) * (float)cvars::sensitivity) / + frametime; + } + if (!(inFirstPerson() && isTervelPlugin())) + *addition_x = degree_x; + else if (*fine_aim_x != NULL) + *fine_aim_x = DegreetoRadians(degree_x); + + float delta_y = + (input_state.mouse.y_delta / divider_y) * (float)cvars::sensitivity; + + if (cvars::invert_y) { + delta_y = -delta_y; + } + + degree_y += delta_y; + *radian_y = DegreetoRadians(degree_y); + if ((inFirstPerson() && isTervelPlugin())) { + degree_y = RadianstoDegree(*fine_aim_y); + degree_y += delta_y; + *fine_aim_y = DegreetoRadians(degree_y); + } + return true; +} + +void SaintsRow1Game::FixHavokFrameTime(float frametime) { + xe::be* havok_frametime = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].havok_frametime_address); + + if (frametime < 0.03333333333f) { + frametime = frametime / 2.f; + if (*havok_frametime != frametime) *havok_frametime = frametime; + } else { + if (*havok_frametime != 0.01666666666f) *havok_frametime = 0.01666666666f; + } +} + +bool SaintsRow1Game::isTervelPlugin() { + /* Although the fineaim option exists as a console command, realistically + users will be using a plugin to switch to it. DoHooks only checks + isTervelPlugin when supported game is loaded, we can't hog GetModule + otherwise it causes an impact performance according to SourceEngine.cc + */ + if (!isPaused()) { + if (tervelplugin_status == 0) { + if (kernel_state()->GetModule("sr1fineaim.xex")) { + tervelplugin_status = 1; + return true; + } else { + tervelplugin_status = 2; + return false; + } + return false; + } + if (tervelplugin_status == 1) { + return true; + } else + return false; + + return false; + } else + return false; +} + +bool SaintsRow1Game::inFirstPerson() { + auto* firstperson = kernel_memory()->TranslateVirtual( + supported_builds[game_build_].isfirstperson_address); + if (*firstperson && *firstperson == 1) + return true; + else + return false; +} + +bool SaintsRow1Game::isPaused() { + auto* pause_flag = kernel_memory()->TranslateVirtual( + supported_builds[game_build_].menu_status_address); + + if (*pause_flag != 2) + return true; + else + return false; +} + +std::string SaintsRow1Game::ChooseBinds() { + auto* wheel_status = kernel_memory()->TranslateVirtual( + supported_builds[game_build_].weapon_wheel_address); + auto* menu_status = kernel_memory()->TranslateVirtual( + supported_builds[game_build_].menu_status_address); + auto* vehicle_status = kernel_memory()->TranslateVirtual( + supported_builds[game_build_].vehicle_address); + + if (*wheel_status == 1 || (menu_status && *menu_status != 2)) { + return "Default"; + } + if (vehicle_status && *vehicle_status == 1) { + return "Vehicle"; + } + + return "Default"; +} +bool SaintsRow1Game::ModifierKeyHandler(uint32_t user_index, + RawInputState& input_state, + X_INPUT_STATE* out_state) { + float thumb_lx = (int16_t)out_state->gamepad.thumb_lx; + float thumb_ly = (int16_t)out_state->gamepad.thumb_ly; + + if (thumb_lx != 0 || + thumb_ly != + 0) { // Required otherwise stick is pushed to the right by default. + // Work out angle from the current stick values + float angle = atan2f(thumb_ly, thumb_lx); + + // Sticks get set to SHRT_MAX if key pressed, use half of that + float distance = (float)SHRT_MAX; + distance /= 2; + + out_state->gamepad.thumb_lx = (int16_t)(distance * cosf(angle)); + out_state->gamepad.thumb_ly = (int16_t)(distance * sinf(angle)); + } + // Return true to signal that we've handled the modifier, so default modifier + // won't be used + return true; +} +} // namespace winkey +} // namespace hid +} // namespace xe \ No newline at end of file diff --git a/src/xenia/hid/winkey/hookables/SaintsRow1.h b/src/xenia/hid/winkey/hookables/SaintsRow1.h new file mode 100644 index 0000000000..900d71380c --- /dev/null +++ b/src/xenia/hid/winkey/hookables/SaintsRow1.h @@ -0,0 +1,54 @@ +#pragma once +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2023 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_HID_WINKEY_SaintsRow1_H_ +#define XENIA_HID_WINKEY_SaintsRow1_H_ +#include // Include for chrono timing +#include "xenia/hid/winkey/hookables/hookable_game.h" + +namespace xe { +namespace hid { +namespace winkey { + +class SaintsRow1Game : public HookableGame { + public: + enum class GameBuild { Unknown, SaintsRow1_TU1 }; + + ~SaintsRow1Game() override; + + bool IsGameSupported(); + + float RadianstoDegree(float radians); + float DegreetoRadians(float degree); + + bool DoHooks(uint32_t user_index, RawInputState& input_state, + X_INPUT_STATE* out_state); + void FixHavokFrameTime(float frametime); + bool isTervelPlugin(); + bool inFirstPerson(); + bool isPaused(); + std::string ChooseBinds(); + bool ModifierKeyHandler(uint32_t user_index, RawInputState& input_state, + X_INPUT_STATE* out_state); + + private: + GameBuild game_build_ = GameBuild::Unknown; + // Timer variables to hold the state for a while // this is probably not ideal + // -Clippy95 + std::chrono::steady_clock::time_point last_movement_time_x_; + std::chrono::steady_clock::time_point last_movement_time_y_; + uint8_t tervelplugin_status; +}; + +} // namespace winkey +} // namespace hid +} // namespace xe + +#endif // XENIA_HID_WINKEY_SaintsRow1_H_ diff --git a/src/xenia/hid/winkey/hookables/SaintsRow.cc b/src/xenia/hid/winkey/hookables/SaintsRow2.cc similarity index 59% rename from src/xenia/hid/winkey/hookables/SaintsRow.cc rename to src/xenia/hid/winkey/hookables/SaintsRow2.cc index 53de5dfc8f..92328495a5 100644 --- a/src/xenia/hid/winkey/hookables/SaintsRow.cc +++ b/src/xenia/hid/winkey/hookables/SaintsRow2.cc @@ -9,7 +9,7 @@ #define _USE_MATH_DEFINES -#include "xenia/hid/winkey/hookables/SaintsRow.h" +#include "xenia/hid/winkey/hookables/SaintsRow2.h" #include "xenia/base/platform_win.h" #include "xenia/cpu/processor.h" @@ -28,6 +28,8 @@ DECLARE_bool(invert_y); DECLARE_bool(invert_x); DECLARE_bool(disable_autoaim); DECLARE_double(right_stick_hold_time_workaround); +DECLARE_bool(sr_havok_fix_frametime); +DECLARE_bool(sr2_hold_fine_aim); const uint32_t kTitleIdSaintsRow2 = 0x545107FC; @@ -43,17 +45,27 @@ struct GameBuildAddrs { uint32_t menu_status_address; uint32_t sniper_status_address; uint32_t currentFOV_address; + uint32_t havok_frametime_address; + uint32_t current_frametime_address; + uint32_t RS_held_address; + uint32_t reset_fineaim_address; // 0x826CBA60(TU3) seems to call this + // reset_fineaim_address with the same + // parameters, maybe use it instead? seems to + // manage player's fineaim. + uint32_t player_pointer_address; + uint32_t sniper_zoom_function_address; }; -std::map supported_builds{ - {SaintsRowGame::GameBuild::Unknown, {"", NULL, NULL, NULL, NULL, NULL}}, - {SaintsRowGame::GameBuild::SaintsRow2_TU3, +std::map supported_builds{ + {SaintsRow2Game::GameBuild::Unknown, {"", NULL, NULL, NULL, NULL, NULL}}, + {SaintsRow2Game::GameBuild::SaintsRow2_TU3, {"8.0.3", 0x82B7A570, 0x82B7A590, 0x82B7ABC4, 0x837B79C3, 0x82B58DA3, - 0x82BCBA78, 0x82B7A4BC}}}; + 0x82BCBA78, 0x82B7A4BC, 0x837DB620, 0x82B7A518, 0x837B7BBB, 0x826CB818, + 0x835BF42C, 0x826CBB40}}}; -SaintsRowGame::~SaintsRowGame() = default; +SaintsRow2Game::~SaintsRow2Game() = default; -bool SaintsRowGame::IsGameSupported() { +bool SaintsRow2Game::IsGameSupported() { if (kernel_state()->title_id() != kTitleIdSaintsRow2) { return false; } @@ -71,16 +83,16 @@ bool SaintsRowGame::IsGameSupported() { return false; } -float SaintsRowGame::DegreetoRadians(float degree) { +float SaintsRow2Game::DegreetoRadians(float degree) { return (float)(degree * (M_PI / 180)); } -float SaintsRowGame::RadianstoDegree(float radians) { +float SaintsRow2Game::RadianstoDegree(float radians) { return (float)(radians * (180 / M_PI)); } -bool SaintsRowGame::DoHooks(uint32_t user_index, RawInputState& input_state, - X_INPUT_STATE* out_state) { +bool SaintsRow2Game::DoHooks(uint32_t user_index, RawInputState& input_state, + X_INPUT_STATE* out_state) { if (!IsGameSupported()) { return false; } @@ -97,6 +109,8 @@ bool SaintsRowGame::DoHooks(uint32_t user_index, RawInputState& input_state, now - last_movement_time_y_) .count(); + if (cvars::sr_havok_fix_frametime) FixHavokFrameTime(); + // Declare static variables for last deltas static int last_x_delta = 0; static int last_y_delta = 0; @@ -136,20 +150,41 @@ bool SaintsRowGame::DoHooks(uint32_t user_index, RawInputState& input_state, } } - // Return true if either X or Y delta is non-zero or if within the hold time - if (input_state.mouse.x_delta == 0 && input_state.mouse.y_delta == 0 && - elapsed_x >= hold_time && elapsed_y >= hold_time) { - return false; - } - XThread* current_thread = XThread::GetCurrentThread(); if (!current_thread) { return false; } + + auto* sniper_status = kernel_memory()->TranslateVirtual( + supported_builds[game_build_].sniper_status_address); + auto* menu_status = kernel_memory()->TranslateVirtual( supported_builds[game_build_].menu_status_address); if (*menu_status == 2) { // Our paused check. + auto* holding_rs = kernel_memory()->TranslateVirtual( + supported_builds[game_build_].RS_held_address); + + player_status = *kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].player_status_address); + if (cvars::sr2_hold_fine_aim) { + uint32_t player_ptr = + *kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].player_pointer_address); + if (player_status && (*holding_rs == 0)) { + if (player_ptr != NULL) { + if (player_status == 16 || player_status == 17) { + reset_fineaim(supported_builds[game_build_].reset_fineaim_address, + player_ptr, 144, 0); + } + if (*sniper_status == 0) { + reset_fineaim( + supported_builds[game_build_].sniper_zoom_function_address, + player_ptr, 0, NULL); + } + } + } + } xe::be* radian_x = kernel_memory()->TranslateVirtual*>( supported_builds[game_build_].x_address); @@ -165,18 +200,13 @@ bool SaintsRowGame::DoHooks(uint32_t user_index, RawInputState& input_state, float degree_x = RadianstoDegree(*radian_x); float degree_y = RadianstoDegree(*radian_y); - auto* sniper_status = kernel_memory()->TranslateVirtual( - supported_builds[game_build_].sniper_status_address); + float divisor = 7.5f; + if (*sniper_status == 0) divisor = 10.f; - float divisor; - if (*sniper_status == 0) { - xe::be* currentFOV = - kernel_memory()->TranslateVirtual*>( - supported_builds[game_build_].currentFOV_address); - divisor = (58.f / *currentFOV) * 10.0f; - } else { - divisor = 5.5f; - } + xe::be* currentFOV = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].currentFOV_address); + if (*currentFOV < 58.f) divisor = (58.f / *currentFOV) * divisor; // X-axis = 0 to 360 if (!cvars::invert_x) { @@ -202,7 +232,7 @@ bool SaintsRowGame::DoHooks(uint32_t user_index, RawInputState& input_state, return true; } -std::string SaintsRowGame::ChooseBinds() { +std::string SaintsRow2Game::ChooseBinds() { // Highest priority: auto* wheel_status = kernel_memory()->TranslateVirtual( supported_builds[game_build_].pressB_status_address); @@ -224,10 +254,9 @@ std::string SaintsRowGame::ChooseBinds() { } // Check the player status next - auto* player_status = kernel_memory()->TranslateVirtual*>( - supported_builds[game_build_].player_status_address); + if (player_status) { - switch (*player_status) { + switch (player_status) { case 3: case 5: return "Vehicle"; @@ -243,9 +272,9 @@ std::string SaintsRowGame::ChooseBinds() { return "Default"; } -bool SaintsRowGame::ModifierKeyHandler(uint32_t user_index, - RawInputState& input_state, - X_INPUT_STATE* out_state) { +bool SaintsRow2Game::ModifierKeyHandler(uint32_t user_index, + RawInputState& input_state, + X_INPUT_STATE* out_state) { float thumb_lx = (int16_t)out_state->gamepad.thumb_lx; float thumb_ly = (int16_t)out_state->gamepad.thumb_ly; @@ -266,6 +295,49 @@ bool SaintsRowGame::ModifierKeyHandler(uint32_t user_index, // won't be used return true; } + +void SaintsRow2Game::FixHavokFrameTime() { + xe::be* havok_frametime = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].havok_frametime_address); + + xe::be* current_frametime = + kernel_memory()->TranslateVirtual*>( + supported_builds[game_build_].current_frametime_address); + + float frametime = *current_frametime; + + if (frametime < 0.03333333333f) { + frametime = frametime / 2.f; + if (*havok_frametime != frametime) *havok_frametime = frametime; + } else { + if (*havok_frametime != 0.01666666666f) *havok_frametime = 0.01666666666f; + } +} + +uint64_t SaintsRow2Game::reset_fineaim(uint32_t function_address, + uint32_t player_ptr, uint32_t a2, + uint32_t a3) { + XThread* current_thread = XThread::GetCurrentThread(); + + if (function_address == NULL && player_ptr == NULL) { + return 0; + } + + current_thread->thread_state()->context()->r[3] = player_ptr; + // Unknown what these mean or it's significance, PC port just expects player + // address, (0x9D9FD0) + current_thread->thread_state()->context()->r[4] = a2; + if (a3 != NULL) current_thread->thread_state()->context()->r[5] = a3; + + kernel_state()->processor()->Execute(current_thread->thread_state(), + function_address); + + uint64_t return_value = current_thread->thread_state()->context()->r[3]; + + return return_value != 0; +} + } // namespace winkey } // namespace hid } // namespace xe \ No newline at end of file diff --git a/src/xenia/hid/winkey/hookables/SaintsRow.h b/src/xenia/hid/winkey/hookables/SaintsRow2.h similarity index 85% rename from src/xenia/hid/winkey/hookables/SaintsRow.h rename to src/xenia/hid/winkey/hookables/SaintsRow2.h index 7aa097662b..067c30a374 100644 --- a/src/xenia/hid/winkey/hookables/SaintsRow.h +++ b/src/xenia/hid/winkey/hookables/SaintsRow2.h @@ -17,11 +17,11 @@ namespace xe { namespace hid { namespace winkey { -class SaintsRowGame : public HookableGame { +class SaintsRow2Game : public HookableGame { public: enum class GameBuild { Unknown, SaintsRow2_TU3 }; - ~SaintsRowGame() override; + ~SaintsRow2Game() override; bool IsGameSupported(); @@ -36,6 +36,11 @@ class SaintsRowGame : public HookableGame { bool ModifierKeyHandler(uint32_t user_index, RawInputState& input_state, X_INPUT_STATE* out_state); + void FixHavokFrameTime(); + + uint64_t reset_fineaim(uint32_t function_address, uint32_t player_ptr, + uint32_t a2, uint32_t a3); + private: GameBuild game_build_ = GameBuild::Unknown; @@ -43,6 +48,7 @@ class SaintsRowGame : public HookableGame { // -Clippy95 std::chrono::steady_clock::time_point last_movement_time_x_; std::chrono::steady_clock::time_point last_movement_time_y_; + uint32_t player_status; }; } // namespace winkey diff --git a/src/xenia/hid/winkey/winkey_input_driver.cc b/src/xenia/hid/winkey/winkey_input_driver.cc index fc58916355..7f33efc3d0 100644 --- a/src/xenia/hid/winkey/winkey_input_driver.cc +++ b/src/xenia/hid/winkey/winkey_input_driver.cc @@ -25,7 +25,8 @@ #include "xenia/hid/winkey/hookables/GearsOfWars.h" #include "xenia/hid/winkey/hookables/JustCause.h" #include "xenia/hid/winkey/hookables/RDR.h" -#include "xenia/hid/winkey/hookables/SaintsRow.h" +#include "xenia/hid/winkey/hookables/SaintsRow1.h" +#include "xenia/hid/winkey/hookables/SaintsRow2.h" #include "xenia/hid/winkey/hookables/SourceEngine.h" #include "xenia/hid/winkey/hookables/goldeneye.h" #include "xenia/hid/winkey/hookables/halo3.h" @@ -438,7 +439,8 @@ WinKeyInputDriver::WinKeyInputDriver(xe::ui::Window* window, hookable_games_.push_back(std::move(std::make_unique())); hookable_games_.push_back(std::move(std::make_unique())); hookable_games_.push_back(std::move(std::make_unique())); - hookable_games_.push_back(std::move(std::make_unique())); + hookable_games_.push_back(std::move(std::make_unique())); + hookable_games_.push_back(std::move(std::make_unique())); hookable_games_.push_back(std::move(std::make_unique())); hookable_games_.push_back( std::move(std::make_unique()));