diff --git a/README.md b/README.md
index 45adf3570f..8e18a7b4ae 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,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 58466a7a62..57e2eab44e 100644
--- a/src/xenia/emulator.cc
+++ b/src/xenia/emulator.cc
@@ -82,9 +82,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,
@@ -92,6 +93,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 "
@@ -1649,6 +1667,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;
@@ -1692,6 +1783,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 = {
@@ -1701,7 +1793,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) {
@@ -1741,7 +1833,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);
       }
 
@@ -1752,7 +1844,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<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
\ 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 <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_
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<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
\ 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 e1940c6e27..ceafabb722 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"
@@ -462,7 +463,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>()));