forked from xenia-canary/xenia-canary
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
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)
* 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]>
Showing
7 changed files
with
718 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters