Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Saints Row 1 support & additions to Saints Row 2 (#39)
Browse files Browse the repository at this point in the history
* SR1 fixup.
Patches to allow mouse in-game to work, temporary RS sens fix, / X-axis addition over frametime as the game does so, this fixes sens changing with framerate.

* Havok frametime fix for SR1&2, rename SaintsRow to SaintsRow2

* Pause flag, fix menu & vehicle flag addreses, should fix missions.

* bindings

* Add support for @Tervel1337 SR1 fine aim plugin

* Don't hog GetModule path for fine aim plugin & default havok frametime fix to false

* Slowdown slow_pan_horizontal_multiplier & slow_pan_vertical_multiplier_address

* Update both normal Y axis and unused fine aim Y axis.
This isn't done in the original game as it seems to be unfinished but this is much more preferable.

* isPaused flag, enhances plugin check.

* Improve sens/slow pan multiplier patch

* Better drive cam patch for Saints Row 1

* Port Hold Fine Aim for Saints Row 2
https://github.com/Clippy95/RowInput/blob/8f2dd873c2bccd6d48b7b868cabc36297587f13a/addons/HoldFineAIM.cpp
also nop havok write address for SR2

* Lint havok fix

* Patch to increase vehicle rotation limit for Saints Row 1

* Always apply FOV for SR2

* sniper zoom function address, used to holdfineaim for snipers, since the zoom seems to be different from normal fineaim.

* Sensible max limit for sr1_increase_vehicle_rotation_limit.

* bindings: rename Saints Row to Saints Row 1

---------

Co-authored-by: Clippy95 <[email protected]>
Clippy95 and Clippy95 authored Nov 6, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent fa121b6 commit 88a8b05
Showing 7 changed files with 718 additions and 45 deletions.
64 changes: 64 additions & 0 deletions bindings.ini
Original file line number Diff line number Diff line change
@@ -22,6 +22,70 @@ 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 = Left
2 = 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 = Left
2 = 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
105 changes: 99 additions & 6 deletions src/xenia/emulator.cc
Original file line number Diff line number Diff line change
@@ -82,16 +82,34 @@ 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,
"(Saints Row 2) unties X rotation from vehicles when "
"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 "
@@ -1659,6 +1677,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<SR1PatchOffsets> 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<uint32_t>*)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;
@@ -1702,6 +1793,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<SR2PatchOffsets> supported_builds = {
@@ -1711,7 +1803,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) {
@@ -1751,7 +1843,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);
}

@@ -1762,7 +1854,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;
}
}
382 changes: 382 additions & 0 deletions src/xenia/hid/winkey/hookables/SaintsRow1.cc
Original file line number Diff line number Diff line change
@@ -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<SaintsRow1Game::GameBuild, GameBuildAddrs> 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<float>* ingamesens_x =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
supported_builds[game_build_].ingame_sens);

xe::be<float>* slow_pan_horizontal_multiplier =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
supported_builds[game_build_].slow_pan_horizontal_multiplier_address);

xe::be<float>* slow_pan_vertical_multiplier =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
supported_builds[game_build_].slow_pan_horizontal_multiplier_address +
0x4);

xe::be<float>* ingamesens_y =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
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<float>* ingame_frametime =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
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<std::chrono::milliseconds>(
now - last_movement_time_x_)
.count();
auto elapsed_y = std::chrono::duration_cast<std::chrono::milliseconds>(
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<long long>(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<float>* addition_x = kernel_memory()->TranslateVirtual<xe::be<float>*>(
supported_builds[game_build_].x_address);

xe::be<float>* radian_y = kernel_memory()->TranslateVirtual<xe::be<float>*>(
supported_builds[game_build_].y_address);

xe::be<float>* current_fov =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
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<float>* fine_aim_x = NULL;
static xe::be<float>* fine_aim_y = NULL;
if (inFirstPerson() && isTervelPlugin()) {
divider_x = 15.f;
frametime = 1.f;

fine_aim_x = kernel_memory()->TranslateVirtual<xe::be<float>*>(
supported_builds[game_build_].fineaim_y_address + 0x4);

fine_aim_y = kernel_memory()->TranslateVirtual<xe::be<float>*>(
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<float>* havok_frametime =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
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<uint8_t*>(
supported_builds[game_build_].isfirstperson_address);
if (*firstperson && *firstperson == 1)
return true;
else
return false;
}

bool SaintsRow1Game::isPaused() {
auto* pause_flag = kernel_memory()->TranslateVirtual<uint8_t*>(
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<uint8_t*>(
supported_builds[game_build_].weapon_wheel_address);
auto* menu_status = kernel_memory()->TranslateVirtual<uint8_t*>(
supported_builds[game_build_].menu_status_address);
auto* vehicle_status = kernel_memory()->TranslateVirtual<uint8_t*>(
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
54 changes: 54 additions & 0 deletions src/xenia/hid/winkey/hookables/SaintsRow1.h
Original file line number Diff line number Diff line change
@@ -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 <chrono> // 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_
Original file line number Diff line number Diff line change
@@ -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<SaintsRowGame::GameBuild, GameBuildAddrs> supported_builds{
{SaintsRowGame::GameBuild::Unknown, {"", NULL, NULL, NULL, NULL, NULL}},
{SaintsRowGame::GameBuild::SaintsRow2_TU3,
std::map<SaintsRow2Game::GameBuild, GameBuildAddrs> 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<uint8_t*>(
supported_builds[game_build_].sniper_status_address);

auto* menu_status = kernel_memory()->TranslateVirtual<uint8_t*>(
supported_builds[game_build_].menu_status_address);
if (*menu_status == 2) { // Our paused check.
auto* holding_rs = kernel_memory()->TranslateVirtual<uint8_t*>(
supported_builds[game_build_].RS_held_address);

player_status = *kernel_memory()->TranslateVirtual<xe::be<uint32_t>*>(
supported_builds[game_build_].player_status_address);
if (cvars::sr2_hold_fine_aim) {
uint32_t player_ptr =
*kernel_memory()->TranslateVirtual<xe::be<uint32_t>*>(
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<float>* radian_x = kernel_memory()->TranslateVirtual<xe::be<float>*>(
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<uint8_t*>(
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<float>* currentFOV =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
supported_builds[game_build_].currentFOV_address);
divisor = (58.f / *currentFOV) * 10.0f;
} else {
divisor = 5.5f;
}
xe::be<float>* currentFOV =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
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<uint8_t*>(
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<xe::be<uint32_t>*>(
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<float>* havok_frametime =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
supported_builds[game_build_].havok_frametime_address);

xe::be<float>* current_frametime =
kernel_memory()->TranslateVirtual<xe::be<float>*>(
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
Original file line number Diff line number Diff line change
@@ -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,13 +36,19 @@ 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;

// 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_;
uint32_t player_status;
};

} // namespace winkey
6 changes: 4 additions & 2 deletions src/xenia/hid/winkey/winkey_input_driver.cc
Original file line number Diff line number Diff line change
@@ -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<Halo3Game>()));
hookable_games_.push_back(std::move(std::make_unique<SourceEngine>()));
hookable_games_.push_back(std::move(std::make_unique<Crackdown2Game>()));
hookable_games_.push_back(std::move(std::make_unique<SaintsRowGame>()));
hookable_games_.push_back(std::move(std::make_unique<SaintsRow2Game>()));
hookable_games_.push_back(std::move(std::make_unique<SaintsRow1Game>()));
hookable_games_.push_back(std::move(std::make_unique<JustCauseGame>()));
hookable_games_.push_back(
std::move(std::make_unique<RedDeadRedemptionGame>()));

0 comments on commit 88a8b05

Please sign in to comment.