2025-07-25 15:07:43 +02:00
|
|
|
#include "Memory.hpp";
|
|
|
|
|
#include "Maths.hpp";
|
|
|
|
|
#include "ObfuscateString.h"
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <spdlog/spdlog.h>
|
|
|
|
|
#include <spdlog/sinks/basic_file_sink.h>
|
|
|
|
|
#include <safetyhook.hpp>
|
|
|
|
|
|
|
|
|
|
// Constants
|
|
|
|
|
const std::string PLUGIN_NAME = "Starfield";
|
|
|
|
|
const std::string PLUGIN_LOG = "Starfield.log";
|
|
|
|
|
const std::string gameExecutable = "Starfield.exe";
|
|
|
|
|
|
|
|
|
|
// Logger
|
|
|
|
|
std::shared_ptr<spdlog::logger> logger;
|
|
|
|
|
|
|
|
|
|
// Screen informations
|
|
|
|
|
static int screenWidth = GetSystemMetrics(SM_CXSCREEN);
|
|
|
|
|
static int screenHeight = GetSystemMetrics(SM_CYSCREEN);
|
|
|
|
|
static float aspectRatio = (float)screenWidth / screenHeight;
|
|
|
|
|
|
|
|
|
|
// Plugin states
|
|
|
|
|
static bool AOBScanDone = false;
|
|
|
|
|
static bool g_fix_enabled = false;
|
|
|
|
|
static bool g_dialog_fov_fix_enabled = false;
|
|
|
|
|
static bool g_weapon_fov_fix_enabled = false;
|
|
|
|
|
static bool g_HUD_fix_enabled = false;
|
|
|
|
|
static bool g_photomode_fix_enabled = false;
|
|
|
|
|
static int g_AdditionalDialogFOVValue = 0;
|
|
|
|
|
static int g_AdditionalWeaponFOVValue = 0;
|
|
|
|
|
static float g_HUDXValue = 0;
|
|
|
|
|
static float g_HUDYValue = 0;
|
|
|
|
|
|
|
|
|
|
// Shared values
|
|
|
|
|
static float g_Dialog_FOV_In = 0;
|
|
|
|
|
static float g_Dialog_FOV_Out = 0;
|
|
|
|
|
static float g_Weapon_FOV_In = 0;
|
|
|
|
|
static float g_Weapon_FOV_Out = 0;
|
|
|
|
|
|
|
|
|
|
// AOB Scan pointers
|
|
|
|
|
static uint8_t* DialogFOVAddress = nullptr;
|
|
|
|
|
static uint8_t* WeaponFOVAddress = nullptr;
|
|
|
|
|
static uint8_t* HUDAddress = nullptr;
|
|
|
|
|
static uint8_t* PhotomodeAddress = nullptr;
|
|
|
|
|
|
|
|
|
|
// Hooking
|
|
|
|
|
static SafetyHookMid DialogFOVHook{};
|
|
|
|
|
static SafetyHookMid WeaponFOVHook{};
|
|
|
|
|
static SafetyHookMid HUDHook{};
|
|
|
|
|
|
|
|
|
|
// Prototypes
|
|
|
|
|
static void DialogFOVFixEnabled(bool fix_enabled);
|
|
|
|
|
static void WeaponFOVFixEnabled(bool fix_enabled);
|
|
|
|
|
static void HUDFixEnabled(bool fix_enabled);
|
|
|
|
|
static void PhotomodeFixEnabled(bool fix_enabled);
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetFixEnabled(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
g_fix_enabled = enabled;
|
|
|
|
|
if (g_fix_enabled && !AOBScanDone) {
|
|
|
|
|
logger->info("--------------- AOB scan started ---------------");
|
|
|
|
|
// === AOB Scans ===
|
|
|
|
|
if (!DialogFOVAddress) {
|
|
|
|
|
constexpr auto DialogFOVPattern = make_obfuscated<0x4A>("E9 ?? ?? ?? ?? 49 ?? ?? E8 ?? ?? ?? ?? 84 ?? 0F 84 ?? ?? ?? ?? 48 8B ?? ?? ?? ?? ?? 48 89");
|
2025-08-20 10:53:58 +02:00
|
|
|
DialogFOVAddress = Memory::AOBScan(gameExecutable, DialogFOVPattern.decrypt(), PAGE_EXECUTE_READ);
|
2025-07-25 15:27:41 +02:00
|
|
|
//Starfield.exe + BBA13E - A8 01 - test al, 01
|
|
|
|
|
//Starfield.exe + BBA140 - 74 0D - je Starfield.exe + BBA14F
|
|
|
|
|
//Starfield.exe + BBA142 - C5 FA 10 35 FE 15 23 05 - vmovss xmm6, [Starfield.exe + 5DEB748]
|
|
|
|
|
//Starfield.exe + BBA14A - E9 01 01 00 00 - jmp Starfield.exe + BBA250
|
|
|
|
|
//Starfield.exe + BBA14F - 49 8B CF - mov rcx, r15
|
2025-07-25 15:07:43 +02:00
|
|
|
if (DialogFOVAddress)
|
|
|
|
|
{
|
|
|
|
|
logger->info("Dialog FOV signature found at address: 0x{:X}.", reinterpret_cast<uintptr_t>(DialogFOVAddress));
|
|
|
|
|
}
|
|
|
|
|
else logger->warn("Dialog FOV signature not found. Maybe the game was updated.");
|
|
|
|
|
}
|
|
|
|
|
if (!WeaponFOVAddress) {
|
|
|
|
|
constexpr auto WeaponFOVPattern = make_obfuscated<0x4A>("C5 FA ?? ?? ?? ?? ?? ?? 80 3D ?? ?? ?? ?? ?? 48 8B ?? ?? ?? ?? ?? 74");
|
2025-08-20 10:53:58 +02:00
|
|
|
WeaponFOVAddress = Memory::AOBScan(gameExecutable, WeaponFOVPattern.decrypt(), PAGE_EXECUTE_READ);
|
2025-07-25 15:27:41 +02:00
|
|
|
//Starfield.exe + 12D9F27 - C5 F8 28 C2 - vmovaps xmm0, xmm2
|
|
|
|
|
//Starfield.exe + 12D9F2B - 48 8B 05 5E C4 BD 04 - mov rax, [Starfield.exe + 5EB6390]
|
|
|
|
|
//Starfield.exe + 12D9F32 - C5 FA 11 80 E0 02 00 00 - vmovss[rax + 000002E0], xmm0
|
|
|
|
|
//Starfield.exe + 12D9F3A - 80 3D EB A0 AD 04 00 - cmp byte ptr[Starfield.exe + 5DB402C], 00
|
|
|
|
|
//Starfield.exe + 12D9F41 - 48 8B 05 10 32 BB 04 - mov rax, [Starfield.exe + 5E8D158]
|
2025-07-25 15:07:43 +02:00
|
|
|
if (WeaponFOVAddress)
|
|
|
|
|
{
|
|
|
|
|
logger->info("Weapon FOV signature found at address: 0x{:X}.", reinterpret_cast<uintptr_t>(WeaponFOVAddress));
|
|
|
|
|
}
|
|
|
|
|
else logger->warn("Weapon FOV signature not found. Maybe the game was updated.");
|
|
|
|
|
}
|
|
|
|
|
if (!HUDAddress) {
|
|
|
|
|
constexpr auto HUDPattern = make_obfuscated<0x4A>("C4 61 ?? ?? ?? ?? ?? ?? ?? C4 61 ?? ?? ?? ?? ?? ?? ?? 45 ?? ?? 74");
|
2025-08-20 10:53:58 +02:00
|
|
|
HUDAddress = Memory::AOBScan(gameExecutable, HUDPattern.decrypt(), PAGE_EXECUTE_READ);
|
2025-07-25 15:27:41 +02:00
|
|
|
//Starfield.exe + 24D6D3B - C4 61 FA 2C 05 3C 28 38 03 - vcvttss2si r8, [Starfield.exe + 5859580]
|
|
|
|
|
//Starfield.exe + 24D6D44 - C4 61 FA 2C 0D 13 28 38 03 - vcvttss2si r9, [Starfield.exe + 5859560]
|
|
|
|
|
//Starfield.exe + 24D6D4D - 45 84 DB - test r11b, r11b
|
|
|
|
|
//Starfield.exe + 24D6D50 - 74 15 - je Starfield.exe + 24D6D67
|
|
|
|
|
//Starfield.exe + 24D6D52 - 8B 42 38 - mov eax, [rdx + 38]
|
2025-07-25 15:07:43 +02:00
|
|
|
if (HUDAddress)
|
|
|
|
|
{
|
|
|
|
|
logger->info("HUD safe zone signature found at address: 0x{:X}.", reinterpret_cast<uintptr_t>(HUDAddress));
|
|
|
|
|
HUDAddress += 0x12;
|
|
|
|
|
}
|
|
|
|
|
else logger->warn("HUD safe zone signature not found. Maybe the game was updated.");
|
|
|
|
|
}
|
|
|
|
|
if (!PhotomodeAddress) {
|
|
|
|
|
constexpr auto PhotomodePattern = make_obfuscated<0x4A>("74 ?? 0F BA ?? ?? 89 87 ?? ?? ?? ?? C6 87 ?? ?? ?? ?? ?? E8");
|
2025-08-20 10:53:58 +02:00
|
|
|
PhotomodeAddress = Memory::AOBScan(gameExecutable, PhotomodePattern.decrypt(), PAGE_EXECUTE_READ);
|
2025-07-25 15:27:41 +02:00
|
|
|
//Starfield.exe + 159D76C - C6 87 D2 00 00 00 01 - mov byte ptr[rdi + 000000D2], 01
|
|
|
|
|
//Starfield.exe + 159D773 - 40 38 35 96 1F 87 04 - cmp[Starfield.exe + 5E0F710], sil
|
|
|
|
|
//Starfield.exe + 159D77A - 74 11 - je Starfield.exe + 159D78D
|
|
|
|
|
//Starfield.exe + 159D77C - 0F BA E8 1B - bts eax, 1B
|
|
|
|
|
//Starfield.exe + 159D780 - 89 87 C0 00 00 00 - mov[rdi + 000000C0], eax
|
2025-07-25 15:07:43 +02:00
|
|
|
if (PhotomodeAddress)
|
|
|
|
|
{
|
|
|
|
|
logger->info("Photo mode signature found at address: 0x{:X}.", reinterpret_cast<uintptr_t>(PhotomodeAddress));
|
|
|
|
|
}
|
|
|
|
|
else logger->warn("Photo mode signature not found. Maybe the game was updated.");
|
|
|
|
|
if (DialogFOVAddress && WeaponFOVAddress && HUDAddress && PhotomodeAddress) {
|
|
|
|
|
logger->info("All AOB signatures found. Ready to patch...");
|
|
|
|
|
logger->info("--------------- AOB scan finished ---------------");
|
|
|
|
|
AOBScanDone = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
logger->warn("Some AOB signatures could not be found. Fixes may be partially unavailable.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// === Activer ou désactiver les patchs ===
|
|
|
|
|
if (DialogFOVAddress && WeaponFOVAddress && HUDAddress && PhotomodeAddress)
|
|
|
|
|
{
|
|
|
|
|
if (g_fix_enabled)
|
|
|
|
|
{
|
|
|
|
|
DialogFOVFixEnabled(g_dialog_fov_fix_enabled);
|
|
|
|
|
WeaponFOVFixEnabled(g_weapon_fov_fix_enabled);
|
|
|
|
|
HUDFixEnabled(g_HUD_fix_enabled);
|
|
|
|
|
PhotomodeFixEnabled(g_photomode_fix_enabled);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
DialogFOVFixEnabled(false);
|
|
|
|
|
WeaponFOVFixEnabled(false);
|
|
|
|
|
HUDFixEnabled(false);
|
|
|
|
|
PhotomodeFixEnabled(false);
|
|
|
|
|
logger->info("All fixes disabled.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setters for Reshade addon call
|
|
|
|
|
extern "C" __declspec(dllexport) void SetDialogFOVFixEnabled(bool enabled, bool init)
|
|
|
|
|
{
|
|
|
|
|
g_dialog_fov_fix_enabled = enabled;
|
|
|
|
|
if (!init) DialogFOVFixEnabled(g_dialog_fov_fix_enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetWeaponFOVFixEnabled(bool enabled, bool init)
|
|
|
|
|
{
|
|
|
|
|
g_weapon_fov_fix_enabled = enabled;
|
|
|
|
|
if (!init) WeaponFOVFixEnabled(g_weapon_fov_fix_enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetHUDFixEnabled(bool enabled, bool init)
|
|
|
|
|
{
|
|
|
|
|
g_HUD_fix_enabled = enabled;
|
|
|
|
|
if (!init) HUDFixEnabled(g_HUD_fix_enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetPhotoModeFixEnabled(bool enabled, bool init)
|
|
|
|
|
{
|
|
|
|
|
g_photomode_fix_enabled = enabled;
|
|
|
|
|
if (!init) PhotomodeFixEnabled(g_photomode_fix_enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetDialogFOV(int fov)
|
|
|
|
|
{
|
|
|
|
|
g_AdditionalDialogFOVValue = fov;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetWeaponFOV(int fov)
|
|
|
|
|
{
|
|
|
|
|
g_AdditionalWeaponFOVValue = fov;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetHUDX(int HUDX)
|
|
|
|
|
{
|
|
|
|
|
g_HUDXValue = HUDX;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) void SetHUDY(int HUDY)
|
|
|
|
|
{
|
|
|
|
|
g_HUDYValue = HUDY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Getters for Reshade addon call
|
|
|
|
|
extern "C" __declspec(dllexport) float GetDialogFOVIn() {
|
|
|
|
|
return g_Dialog_FOV_In;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) float GetDialogFOVOut() {
|
|
|
|
|
return g_Dialog_FOV_Out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) float GetWeaponFOVIn() {
|
|
|
|
|
return g_Weapon_FOV_In;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" __declspec(dllexport) float GetWeaponFOVOut() {
|
|
|
|
|
return g_Weapon_FOV_Out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Assembly code injections functions
|
|
|
|
|
static void DialogFOVFixEnabled(bool fix_enabled) {
|
|
|
|
|
if (g_fix_enabled && fix_enabled && DialogFOVAddress != nullptr) {
|
|
|
|
|
if (!DialogFOVHook) { // Hook only once
|
|
|
|
|
DialogFOVHook = safetyhook::create_mid(DialogFOVAddress,
|
|
|
|
|
[](SafetyHookContext& ctx) {
|
|
|
|
|
g_Dialog_FOV_In = ctx.xmm6.f32[0];
|
|
|
|
|
g_Dialog_FOV_Out = ctx.xmm6.f32[0] += (g_dialog_fov_fix_enabled ? g_AdditionalDialogFOVValue : 0); // World FOV
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else DialogFOVHook.enable();
|
|
|
|
|
logger->info("FOV fix enabled");
|
|
|
|
|
}
|
|
|
|
|
if (!fix_enabled && DialogFOVHook) {
|
|
|
|
|
DialogFOVHook.disable();
|
|
|
|
|
logger->info("FOV fix disabled");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void WeaponFOVFixEnabled(bool fix_enabled) {
|
|
|
|
|
if (g_fix_enabled && fix_enabled && WeaponFOVAddress != nullptr) {
|
|
|
|
|
if (!WeaponFOVHook) {
|
|
|
|
|
WeaponFOVHook = safetyhook::create_mid(WeaponFOVAddress,
|
|
|
|
|
[](SafetyHookContext& ctx) {
|
|
|
|
|
g_Weapon_FOV_In = ctx.xmm0.f32[0];
|
|
|
|
|
g_Weapon_FOV_Out = ctx.xmm0.f32[0] += (g_weapon_fov_fix_enabled ? g_AdditionalWeaponFOVValue : 0);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else WeaponFOVHook.enable();
|
|
|
|
|
logger->info("Weapon FOV fix enabled");
|
|
|
|
|
}
|
|
|
|
|
if (!fix_enabled && WeaponFOVHook) {
|
|
|
|
|
WeaponFOVHook.disable();
|
|
|
|
|
logger->info("Weapon FOV fix disabled");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void HUDFixEnabled(bool fix_enabled) {
|
|
|
|
|
if (g_fix_enabled && fix_enabled && HUDAddress) {
|
|
|
|
|
if (!HUDHook) {
|
|
|
|
|
HUDHook = safetyhook::create_mid(HUDAddress,
|
|
|
|
|
[](SafetyHookContext& ctx) {
|
|
|
|
|
int safeZoneX = (screenWidth / 2) * (g_HUDXValue / 100);
|
|
|
|
|
int safeZoneY = (screenHeight / 2) * (g_HUDYValue / 100);
|
|
|
|
|
ctx.r9 = *reinterpret_cast<uint32_t*>(&safeZoneX); // X axis
|
|
|
|
|
ctx.r8 = *reinterpret_cast<uint32_t*>(&safeZoneY); // Y axis
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
else HUDHook.enable();
|
|
|
|
|
logger->info("HUD fix enabled");
|
|
|
|
|
}
|
|
|
|
|
if (!fix_enabled && HUDHook) {
|
|
|
|
|
HUDHook.disable();
|
|
|
|
|
logger->info("HUD fix disabled");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void PhotomodeFixEnabled(bool fix_enabled) {
|
|
|
|
|
if (g_fix_enabled && fix_enabled && PhotomodeAddress != nullptr) {
|
|
|
|
|
Memory::PatchBytes(PhotomodeAddress, "\xEB", 1); // je ==> jmp
|
|
|
|
|
logger->info("Photomode fix enabled");
|
|
|
|
|
}
|
|
|
|
|
if (!fix_enabled && PhotomodeAddress) {
|
|
|
|
|
Memory::RestoreBytes(PhotomodeAddress);
|
|
|
|
|
logger->info("Photomode fix disabled");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// spdlog init with specific format
|
|
|
|
|
static void InitializeLogger()
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
logger = spdlog::basic_logger_mt("Fixlib", PLUGIN_LOG, true);
|
|
|
|
|
spdlog::set_default_logger(logger);
|
|
|
|
|
// Format : [YYYY-MM-DD HH:MM:SS] [INFO] message
|
|
|
|
|
spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%^%l%$] %v");
|
|
|
|
|
spdlog::set_level(spdlog::level::debug);
|
|
|
|
|
logger->flush_on(spdlog::level::debug); // Flush automatically
|
|
|
|
|
}
|
|
|
|
|
catch (const spdlog::spdlog_ex& ex)
|
|
|
|
|
{
|
|
|
|
|
std::string plugin_error_message = "Could not open " + PLUGIN_LOG;
|
|
|
|
|
MessageBoxA(nullptr, plugin_error_message.c_str(), "Logger Error", MB_ICONERROR | MB_OK);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Entry point
|
|
|
|
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID)
|
|
|
|
|
{
|
|
|
|
|
if (reason == DLL_PROCESS_ATTACH)
|
|
|
|
|
{
|
|
|
|
|
InitializeLogger();
|
|
|
|
|
logger->info("Plugin {} loaded.", PLUGIN_NAME);
|
|
|
|
|
}
|
|
|
|
|
else if (reason == DLL_PROCESS_DETACH)
|
|
|
|
|
{
|
|
|
|
|
logger->info("Plugin {} unloaded.", PLUGIN_NAME);
|
|
|
|
|
spdlog::drop_all();
|
|
|
|
|
}
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|