diff --git a/Shaders/Granblue Fantasy Relink/Includes/Tonemap.hlsli b/Shaders/Granblue Fantasy Relink/Includes/Tonemap.hlsli index 157b0b3e..a5292f9e 100644 --- a/Shaders/Granblue Fantasy Relink/Includes/Tonemap.hlsli +++ b/Shaders/Granblue Fantasy Relink/Includes/Tonemap.hlsli @@ -539,7 +539,9 @@ float3 ApplyUserGradingAndToneMap(float3 color_bt709, float3 bloom, float2 grain true); const float peakRatioCorrected = gammaCorrectedPeakRatio.x; - bloom = ApplyCustomBloom(color_bt709, bloom, 0.5f); + // bloom = ApplyCustomBloom(color_bt709, bloom, 0.5f); + if (BLOOM_TYPE == 0 && LumaSettings.DisplayMode != 0) + bloom = FakeHDR(bloom, 0.05, 0.33f); // blow out and hue shift float3 purity_and_hue_source = Reinhard::ReinhardPiecewise(color_bt709, 8.f, 0.18f); diff --git a/Source/Games/Granblue Fantasy Relink/includes/hooks.cpp b/Source/Games/Granblue Fantasy Relink/includes/hooks.cpp index 92bcc54c..d1e44235 100644 --- a/Source/Games/Granblue Fantasy Relink/includes/hooks.cpp +++ b/Source/Games/Granblue Fantasy Relink/includes/hooks.cpp @@ -5,11 +5,12 @@ bool TryReadCameraJitter(float2& out_jitter) { - const uintptr_t mod_base = reinterpret_cast(GetModuleHandleA(NULL)); - if (mod_base == 0) + const uintptr_t camera = ResolveGBFRDataOrFallback( + g_resolved_addresses.camera_global, + kCameraGlobal_RVA); + if (camera == 0) return false; - const uintptr_t camera = mod_base + kCameraGlobal_RVA; const uintptr_t projection_ptr = *reinterpret_cast(camera + kCameraProjectionDataOffset); if (projection_ptr == 0) return false; @@ -82,17 +83,15 @@ void PatchJitterPhases() static_assert(JITTER_PHASES >= 1 && JITTER_PHASES <= 64, "JITTER_PHASES must be between 1 and 64"); #ifndef PATCH_JITTER_TABLE_INIT - const uintptr_t base_addr = reinterpret_cast(GetModuleHandleA(NULL)); - if (base_addr == 0) - return; - constexpr uint8_t mask = static_cast(JITTER_PHASES - 1); const uintptr_t patch_addrs[2] = { - base_addr + kJitterPhaseMask_CL_RVA, - base_addr + kJitterPhaseMask_EAX_RVA, + ResolveGBFRDataOrFallback(g_resolved_addresses.jitter_phase_mask_cl_imm, kJitterPhaseMask_CL_RVA), + ResolveGBFRDataOrFallback(g_resolved_addresses.jitter_phase_mask_eax_imm, kJitterPhaseMask_EAX_RVA), }; for (uintptr_t addr : patch_addrs) { + if (addr == 0) + continue; auto* byte_ptr = reinterpret_cast(addr); DWORD old_protect; VirtualProtect(byte_ptr, 1, PAGE_EXECUTE_READWRITE, &old_protect); @@ -109,13 +108,15 @@ bool IsTAARunningThisFrame() static std::atomic s_last_taa_running{false}; const bool last_known = s_last_taa_running.load(std::memory_order_acquire); - const uintptr_t mod_base = reinterpret_cast(GetModuleHandleA(NULL)); - if (mod_base == 0) + const uintptr_t settings_ptr_addr = ResolveGBFRDataOrFallback( + g_resolved_addresses.taa_settings_global, + kTAASettingsGlobal_RVA); + if (settings_ptr_addr == 0) return last_known; __try { - const uintptr_t settings_obj = *reinterpret_cast(mod_base + kTAASettingsGlobal_RVA); + const uintptr_t settings_obj = *reinterpret_cast(settings_ptr_addr); if (settings_obj == 0) return last_known; @@ -173,9 +174,17 @@ static char __fastcall Hooked_InitializeDX11RenderingPipeline(int screen_width, // CreateRenderTargets initialises these from g_outputWidth/g_outputHeight (always output // dims) and never applies a scale, so without this write the frame graph sees // render == output and skips the temporal upscale path every frame. - const uintptr_t mod_base = reinterpret_cast(GetModuleHandleA(NULL)); - *reinterpret_cast(mod_base + kRenderWidth_RVA) = render_w; - *reinterpret_cast(mod_base + kRenderHeight_RVA) = render_h; + const uintptr_t render_w_addr = ResolveGBFRDataOrFallback( + g_resolved_addresses.render_width, + kRenderWidth_RVA); + const uintptr_t render_h_addr = ResolveGBFRDataOrFallback( + g_resolved_addresses.render_height, + kRenderHeight_RVA); + if (render_w_addr != 0 && render_h_addr != 0) + { + *reinterpret_cast(render_w_addr) = render_w; + *reinterpret_cast(render_h_addr) = render_h; + } } // Pass render dims to the game — g_outputWidth/g_outputHeight are not touched. diff --git a/Source/Games/Granblue Fantasy Relink/includes/hooks.hpp b/Source/Games/Granblue Fantasy Relink/includes/hooks.hpp index 0451b22f..c6110fea 100644 --- a/Source/Games/Granblue Fantasy Relink/includes/hooks.hpp +++ b/Source/Games/Granblue Fantasy Relink/includes/hooks.hpp @@ -2,6 +2,30 @@ #include "safetyhook.hpp" +struct GBFRResolvedAddresses +{ + void* initialize_dx11_rendering_pipeline = nullptr; + void* dispatch_render_pass_viewport = nullptr; + void* ui_render_orchestrator = nullptr; + void* jitter_write_site = nullptr; +#ifdef PATCH_JITTER_TABLE_INIT + void* temporal_aa_component_init = nullptr; +#endif + + uintptr_t output_width = 0; + uintptr_t output_height = 0; + uintptr_t render_width = 0; + uintptr_t render_height = 0; + uintptr_t camera_global = 0; + uintptr_t taa_settings_global = 0; + uintptr_t jitter_phase_counter = 0; + uintptr_t jitter_phase_mask_cl_imm = 0; + uintptr_t jitter_phase_mask_eax_imm = 0; + + bool resolve_attempted = false; + bool ready = false; +}; + struct GBFRHookGlobals { SafetyHookInline rt_creation_hook; @@ -45,10 +69,6 @@ constexpr uintptr_t kCameraProjectionDataOffset = 0x60; constexpr uintptr_t kProjectionJitterXOffset = 0x940; constexpr uintptr_t kProjectionJitterYOffset = 0x944; constexpr uintptr_t kTAASettingsGlobal_RVA = 0x05E55EA0; -constexpr uintptr_t kPauseCandidate_GlobalBit_RVA = 0x061720A4; -constexpr uintptr_t kPauseCandidate_TonemapGate_RVA = 0x05E5CABD; -constexpr uintptr_t kPauseCandidate_DofGateA_RVA = 0x06130C5C; -constexpr uintptr_t kPauseCandidate_DofGateB_RVA = 0x06130E13; constexpr uintptr_t kJitterPhaseCounter_RVA = 0x05E61790; constexpr uintptr_t kJitterPhaseMask_CL_RVA = 0x01A9EB76; constexpr uintptr_t kJitterPhaseMask_EAX_RVA = 0x01A9EB7C; @@ -71,6 +91,11 @@ inline auto& g_taa_init_hook = g_hook_globals.taa_init_hook; #endif inline auto& g_device_data_ptr = g_hook_globals.device_data_ptr; inline auto& g_native_device_ptr = g_hook_globals.native_device_ptr; +inline GBFRResolvedAddresses g_resolved_addresses; + +bool ResolveGBFRAddresses(); +uintptr_t ResolveGBFRDataOrFallback(uintptr_t resolved_absolute, uintptr_t fallback_rva); +void* ResolveGBFRCodeOrFallback(void* resolved_absolute, uintptr_t fallback_rva); bool TryReadCameraJitter(float2& out_jitter); void OnJitterWrite(safetyhook::Context& ctx); diff --git a/Source/Games/Granblue Fantasy Relink/includes/postprocess.cpp b/Source/Games/Granblue Fantasy Relink/includes/postprocess.cpp index b8ad09ef..166bf122 100644 --- a/Source/Games/Granblue Fantasy Relink/includes/postprocess.cpp +++ b/Source/Games/Granblue Fantasy Relink/includes/postprocess.cpp @@ -1079,7 +1079,7 @@ static void RunLatePostProcessPasses( settings_data.render_width = static_cast(device_data.render_resolution.x); settings_data.render_height = static_cast(device_data.render_resolution.y); settings_data.dynamic_resolution = false; - settings_data.hdr = cb_luma_global_settings.DisplayMode == DisplayModeType::HDR ? true : false; + settings_data.hdr = cb_luma_global_settings.DisplayMode == DisplayModeType::HDR ? true : tonemap_after_taa; settings_data.auto_exposure = true; settings_data.inverted_depth = false; // Granblue MVs are unjittered (g_ProjectionOffset cancels jitter in the PS) diff --git a/Source/Games/Granblue Fantasy Relink/includes/sig_helper.hpp b/Source/Games/Granblue Fantasy Relink/includes/sig_helper.hpp new file mode 100644 index 00000000..ea8b5ac6 --- /dev/null +++ b/Source/Games/Granblue Fantasy Relink/includes/sig_helper.hpp @@ -0,0 +1,178 @@ +#pragma once + +// License: MIT License + +// Copyright (c) 2024 Lyall + +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. + +// Source: https://github.com/Lyall/GBFRelinkFix/blob/master/src/helper.hpp + +#include +#include +#include +#include +#include +#include + +namespace Memory +{ + inline std::vector PatternToByte(const char* pattern) + { + std::vector bytes; + const char* current = pattern; + const char* end = pattern + std::strlen(pattern); + + while (current < end) + { + if (*current == ' ') + { + ++current; + continue; + } + + if (*current == '?') + { + ++current; + if (current < end && *current == '?') + ++current; + bytes.push_back(-1); + continue; + } + + char* next = nullptr; + bytes.push_back(static_cast(std::strtoul(current, &next, 16))); + current = next; + } + + return bytes; + } + + inline bool GetSectionRange(void* module, std::string_view section_name, std::uint8_t*& begin, std::uint8_t*& end) + { + if (!module) + return false; + + auto* dos_header = reinterpret_cast(module); + if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) + return false; + + auto* nt_headers = reinterpret_cast(reinterpret_cast(module) + dos_header->e_lfanew); + if (nt_headers->Signature != IMAGE_NT_SIGNATURE) + return false; + + IMAGE_SECTION_HEADER* section = IMAGE_FIRST_SECTION(nt_headers); + for (unsigned i = 0; i < nt_headers->FileHeader.NumberOfSections; ++i, ++section) + { + char name[9] = {}; + std::memcpy(name, section->Name, sizeof(section->Name)); + if (section_name == name) + { + begin = reinterpret_cast(module) + section->VirtualAddress; + end = begin + section->Misc.VirtualSize; + return true; + } + } + + return false; + } + + inline std::uint8_t* PatternScan(void* module, const char* signature, std::string_view section_name = ".text") + { + std::uint8_t* section_begin = nullptr; + std::uint8_t* section_end = nullptr; + if (!GetSectionRange(module, section_name, section_begin, section_end)) + return nullptr; + + const auto pattern_bytes = PatternToByte(signature); + if (pattern_bytes.empty()) + return nullptr; + + const std::size_t section_size = static_cast(section_end - section_begin); + if (section_size < pattern_bytes.size()) + return nullptr; + + const std::size_t pattern_size = pattern_bytes.size(); + for (std::size_t i = 0; i <= section_size - pattern_size; ++i) + { + bool found = true; + for (std::size_t j = 0; j < pattern_size; ++j) + { + const int want = pattern_bytes[j]; + if (want != -1 && section_begin[i + j] != static_cast(want)) + { + found = false; + break; + } + } + + if (found) + return §ion_begin[i]; + } + + return nullptr; + } + + inline std::uint8_t* PatternScanUnique(void* module, const char* signature, std::string_view section_name, std::size_t& out_match_count) + { + out_match_count = 0; + + std::uint8_t* section_begin = nullptr; + std::uint8_t* section_end = nullptr; + if (!GetSectionRange(module, section_name, section_begin, section_end)) + return nullptr; + + const auto pattern_bytes = PatternToByte(signature); + if (pattern_bytes.empty()) + return nullptr; + + const std::size_t section_size = static_cast(section_end - section_begin); + if (section_size < pattern_bytes.size()) + return nullptr; + + const std::size_t pattern_size = pattern_bytes.size(); + std::uint8_t* first_match = nullptr; + for (std::size_t i = 0; i <= section_size - pattern_size; ++i) + { + bool found = true; + for (std::size_t j = 0; j < pattern_size; ++j) + { + const int want = pattern_bytes[j]; + if (want != -1 && section_begin[i + j] != static_cast(want)) + { + found = false; + break; + } + } + + if (found) + { + ++out_match_count; + if (!first_match) + first_match = §ion_begin[i]; + } + } + + return (out_match_count == 1) ? first_match : nullptr; + } + + inline std::uintptr_t GetAbsolute64(std::uintptr_t address) noexcept + { + return address + 4 + static_cast(*reinterpret_cast(address)); + } + + inline std::uintptr_t GetAbsolute64(std::uintptr_t instruction_address, std::size_t disp_offset, std::size_t instruction_size) noexcept + { + const auto disp = *reinterpret_cast(instruction_address + disp_offset); + return instruction_address + instruction_size + static_cast(disp); + } + +} diff --git a/Source/Games/Granblue Fantasy Relink/includes/sigscan.cpp b/Source/Games/Granblue Fantasy Relink/includes/sigscan.cpp new file mode 100644 index 00000000..caf55349 --- /dev/null +++ b/Source/Games/Granblue Fantasy Relink/includes/sigscan.cpp @@ -0,0 +1,151 @@ +#include "..\..\Core\core.hpp" +#include "helper.hpp" +#include "hooks.hpp" + +namespace +{ +// Mid-function anchor inside GBFR_InitializeDX11RenderingPipeline around the cache fast-path. +constexpr const char* kSigInitDx11Anchor = + "48 89 4D 20 48 39 0D ?? ?? ?? ?? 75 ?? 48 39 35 ?? ?? ?? ?? 75 ?? B0 01"; +constexpr const char* kSigDispatchViewport = + "55 56 48 81 EC 88 00 00 00 48 8D AC 24 80 00 00 00 48 83 E4 E0 48 89 CE C5 F8 57 C0 C5 FC 29 44 24 60 C5 FC 29 44 24 40"; +// Early-function anchor inside UIRenderOrchestrator; signature is stable across prologue/XMM save changes. +constexpr const char* kSigUIOrchestratorAnchor = + "C7 81 50 02 00 00 65 00 00 00 48 83 3D ?? ?? ?? ?? 00 0F 84 ?? ?? ?? ??"; +#ifdef PATCH_JITTER_TABLE_INIT +constexpr const char* kSigTAAInit = + "56 57 48 83 EC 48 C5 F8 29 7C 24 30 C5 F8 29 74 24 20 48 89 CF 48 8D 0D ?? ?? ?? ?? BA 24 00 00 00 E8 ?? ?? ?? ?? 89 87 28 02 00 00"; +#endif +constexpr const char* kSigJitterCore = + "48 8B 05 ?? ?? ?? ?? 89 C1 80 E1 ?? 88 4E 24 83 E0 ?? 8B 4C C6 28 8B 44 C6 2C 48 8B 15 ?? ?? ?? ?? F6 42 0B 01"; +constexpr const char* kSigOutputLoadCaller = + "48 63 0D ?? ?? ?? ?? 48 63 15 ?? ?? ?? ?? E8 ?? ?? ?? ?? 4C 8B 35 ?? ?? ?? ?? 48 8B 3D ?? ?? ?? ??"; +constexpr const char* kSigCameraGlobalLoad = + "48 8B 05 ?? ?? ?? ?? FF 50 10 40 84 F6 74 07 C6 05 ?? ?? ?? ?? 01"; + +void LogResolve(const char* name, const void* addr) +{ + std::string msg = std::string("GBFR sigscan: ") + name + " = 0x" + std::to_string(reinterpret_cast(addr)); + reshade::log::message(reshade::log::level::info, msg.c_str()); +} + +void LogResolveFailure(const char* name, std::size_t matches) +{ + std::string msg = std::string("GBFR sigscan: failed to resolve ") + name + ", matches=" + std::to_string(matches); + reshade::log::message(reshade::log::level::warning, msg.c_str()); +} + +std::uint8_t* FindUnique(void* module, const char* name, const char* sig) +{ + std::size_t matches = 0; + std::uint8_t* p = Memory::PatternScanUnique(module, sig, ".text", matches); + if (!p) + LogResolveFailure(name, matches); + return p; +} + +void* FindFunctionStartFromAnchor(void* module, const char* name, const char* sig) +{ + std::uint8_t* anchor = FindUnique(module, name, sig); + if (!anchor) + return nullptr; + + const uintptr_t anchor_addr = reinterpret_cast(anchor); + DWORD64 image_base = reinterpret_cast(module); + const RUNTIME_FUNCTION* rf = RtlLookupFunctionEntry(anchor_addr, &image_base, nullptr); + if (!rf) + { + LogResolveFailure(name, 0); + return nullptr; + } + + return reinterpret_cast(image_base + rf->BeginAddress); +} +} + +uintptr_t ResolveGBFRDataOrFallback(uintptr_t resolved_absolute, uintptr_t fallback_rva) +{ + if (resolved_absolute != 0) + return resolved_absolute; + + const uintptr_t mod_base = reinterpret_cast(GetModuleHandleA(NULL)); + return (mod_base != 0) ? (mod_base + fallback_rva) : 0; +} + +void* ResolveGBFRCodeOrFallback(void* resolved_absolute, uintptr_t fallback_rva) +{ + if (resolved_absolute != nullptr) + return resolved_absolute; + + const uintptr_t mod_base = reinterpret_cast(GetModuleHandleA(NULL)); + return (mod_base != 0) ? reinterpret_cast(mod_base + fallback_rva) : nullptr; +} + +bool ResolveGBFRAddresses() +{ + if (g_resolved_addresses.resolve_attempted) + return g_resolved_addresses.ready; + + g_resolved_addresses.resolve_attempted = true; + + void* module = GetModuleHandleA(NULL); + if (!module) + return false; + + // Resolve hook/function targets. + if (void* p = FindFunctionStartFromAnchor(module, "InitializeDX11RenderingPipeline", kSigInitDx11Anchor)) + g_resolved_addresses.initialize_dx11_rendering_pipeline = p; + + if (std::uint8_t* p = FindUnique(module, "DispatchRenderPassViewport", kSigDispatchViewport)) + g_resolved_addresses.dispatch_render_pass_viewport = p; + + if (void* p = FindFunctionStartFromAnchor(module, "UIRenderOrchestrator", kSigUIOrchestratorAnchor)) + g_resolved_addresses.ui_render_orchestrator = p; + +#ifdef PATCH_JITTER_TABLE_INIT + if (std::uint8_t* p = FindUnique(module, "TemporalAntiAliasingComponentInit", kSigTAAInit)) + g_resolved_addresses.temporal_aa_component_init = p; +#endif + + if (std::uint8_t* p = FindUnique(module, "JitterCore", kSigJitterCore)) + { + // Pattern starts at: mov rax, [rip+disp32] + g_resolved_addresses.jitter_phase_counter = Memory::GetAbsolute64(reinterpret_cast(p), 3, 7); + g_resolved_addresses.jitter_phase_mask_cl_imm = reinterpret_cast(p + 11); + g_resolved_addresses.jitter_phase_mask_eax_imm = reinterpret_cast(p + 17); + g_resolved_addresses.jitter_write_site = reinterpret_cast(p + 0x30); + g_resolved_addresses.taa_settings_global = Memory::GetAbsolute64(reinterpret_cast(p + 26), 3, 7); + } + + if (std::uint8_t* p = FindUnique(module, "OutputDimensionCaller", kSigOutputLoadCaller)) + { + g_resolved_addresses.output_width = Memory::GetAbsolute64(reinterpret_cast(p), 3, 7); + g_resolved_addresses.output_height = Memory::GetAbsolute64(reinterpret_cast(p + 7), 3, 7); + + // g_renderWidth/g_renderHeight are contiguous 8 bytes before output globals in current layouts. + if (g_resolved_addresses.output_width != 0 && g_resolved_addresses.output_height != 0) + { + g_resolved_addresses.render_width = g_resolved_addresses.output_width - 8; + g_resolved_addresses.render_height = g_resolved_addresses.output_height - 8; + } + } + + if (std::uint8_t* p = FindUnique(module, "CameraGlobalLoad", kSigCameraGlobalLoad)) + { + g_resolved_addresses.camera_global = Memory::GetAbsolute64(reinterpret_cast(p), 3, 7); + } + + g_resolved_addresses.ready = + g_resolved_addresses.initialize_dx11_rendering_pipeline != nullptr && + g_resolved_addresses.dispatch_render_pass_viewport != nullptr && + g_resolved_addresses.ui_render_orchestrator != nullptr && + g_resolved_addresses.jitter_write_site != nullptr; + + LogResolve("InitializeDX11RenderingPipeline", g_resolved_addresses.initialize_dx11_rendering_pipeline); + LogResolve("DispatchRenderPassViewport", g_resolved_addresses.dispatch_render_pass_viewport); + LogResolve("UIRenderOrchestrator", g_resolved_addresses.ui_render_orchestrator); + LogResolve("JitterWrite", g_resolved_addresses.jitter_write_site); + LogResolve("CameraGlobal", reinterpret_cast(g_resolved_addresses.camera_global)); + + return g_resolved_addresses.ready; +} diff --git a/Source/Games/Granblue Fantasy Relink/includes/sigscan.hpp b/Source/Games/Granblue Fantasy Relink/includes/sigscan.hpp new file mode 100644 index 00000000..adc57091 --- /dev/null +++ b/Source/Games/Granblue Fantasy Relink/includes/sigscan.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct GBFRResolvedAddresses; + +bool ResolveGBFRAddresses(); +uintptr_t ResolveGBFRDataOrFallback(uintptr_t resolved_absolute, uintptr_t fallback_rva); +void* ResolveGBFRCodeOrFallback(void* resolved_absolute, uintptr_t fallback_rva); diff --git a/Source/Games/Granblue Fantasy Relink/includes/ui_scale.cpp b/Source/Games/Granblue Fantasy Relink/includes/ui_scale.cpp index 0a52e856..0247f95b 100644 --- a/Source/Games/Granblue Fantasy Relink/includes/ui_scale.cpp +++ b/Source/Games/Granblue Fantasy Relink/includes/ui_scale.cpp @@ -171,11 +171,13 @@ static void RedirectUIDrawToScaledTarget( if (!ui_scale.ui_scaled_needed) return; + com_ptr current_rtv; + ctx->OMGetRenderTargets(1, ¤t_rtv, nullptr); + const bool is_detected_ui_rtv = AreViewsOfSameResource(ui_scale.original_ui_rtv.get(), current_rtv.get()); + // --- Render target redirect --- { - com_ptr current_rtv; - ctx->OMGetRenderTargets(1, ¤t_rtv, nullptr); - if (AreViewsOfSameResource(ui_scale.original_ui_rtv.get(), current_rtv.get())) + if (is_detected_ui_rtv) { ID3D11RenderTargetView* const scaled_rtv = ui_scale.scaled_texture_rtv.get(); ctx->OMSetRenderTargets(1, &scaled_rtv, nullptr); @@ -184,16 +186,20 @@ static void RedirectUIDrawToScaledTarget( // --- Viewport redirect --- { - D3D11_VIEWPORT vp; - UINT num_vp = 1; - ctx->RSGetViewports(&num_vp, &vp); - const UINT render_w = static_cast(device_data.render_resolution.x); - const UINT render_h = static_cast(device_data.render_resolution.y); - if (static_cast(vp.Width) == render_w && static_cast(vp.Height) == render_h) + // Only rewrite viewport when drawing to the same RTV detected at UI-phase entry. + if (is_detected_ui_rtv) { - vp.Width = device_data.output_resolution.x; - vp.Height = device_data.output_resolution.y; - ctx->RSSetViewports(1, &vp); + D3D11_VIEWPORT vp; + UINT num_vp = 1; + ctx->RSGetViewports(&num_vp, &vp); + const UINT render_w = static_cast(device_data.render_resolution.x); + const UINT render_h = static_cast(device_data.render_resolution.y); + if (static_cast(vp.Width) == render_w && static_cast(vp.Height) == render_h) + { + vp.Width = device_data.output_resolution.x; + vp.Height = device_data.output_resolution.y; + ctx->RSSetViewports(1, &vp); + } } } diff --git a/Source/Games/Granblue Fantasy Relink/main.cpp b/Source/Games/Granblue Fantasy Relink/main.cpp index f1c0b40f..80aaf7d7 100644 --- a/Source/Games/Granblue Fantasy Relink/main.cpp +++ b/Source/Games/Granblue Fantasy Relink/main.cpp @@ -5,6 +5,7 @@ #define JITTER_PHASES 8 #define PATCH_JITTER_TABLE_INIT #define PATCH_SCENE_BUFFER 0 +#define ENABLE_UI_VIEWPORT_SCALING_HOOK 0 #define ENABLE_POST_DRAW_DISPATCH_CALLBACK 1 #define CHECK_GRAPHICS_API_COMPATIBILITY 1 @@ -13,8 +14,10 @@ #include "includes\cbuffers.h" #include "includes\common.hpp" #include "includes\hooks.hpp" +#include "includes\sigscan.hpp" #include "includes\safetyhook.hpp" #include "includes\common.cpp" +#include "includes\sigscan.cpp" #include "includes\hooks.cpp" namespace @@ -63,6 +66,7 @@ class GranblueFantasyRelink final : public Game }; shader_defines_data.append_range(game_shader_defines_data); GetShaderDefineData(UI_DRAW_TYPE_HASH).SetDefaultValue('2'); + GetShaderDefineData(GAMUT_MAPPING_TYPE_HASH).SetDefaultValue('3'); native_shaders_definitions.emplace( CompileTimeStringHash("GBFR Post Tonemap"), @@ -671,50 +675,61 @@ class GranblueFantasyRelink final : public Game g_device_data_ptr.store(&device_data, std::memory_order_release); g_native_device_ptr.store(native_device, std::memory_order_release); + ResolveGBFRAddresses(); + if (!g_rt_creation_hook) { - const uintptr_t base_addr = reinterpret_cast(GetModuleHandleA(NULL)); - if (base_addr != 0) + void* rt_creation_fn = ResolveGBFRCodeOrFallback( + g_resolved_addresses.initialize_dx11_rendering_pipeline, + kInitializeDX11RenderingPipeline_RVA); + if (rt_creation_fn) { - void* rt_creation_fn = reinterpret_cast(base_addr + kInitializeDX11RenderingPipeline_RVA); g_rt_creation_hook = safetyhook::create_inline( rt_creation_fn, reinterpret_cast(&Hooked_InitializeDX11RenderingPipeline)); } } +#if ENABLE_UI_VIEWPORT_SCALING_HOOK if (!g_dispatch_viewport_hook) { - const uintptr_t base_addr = reinterpret_cast(GetModuleHandleA(NULL)); - if (base_addr != 0) + void* dispatch_fn = ResolveGBFRCodeOrFallback( + g_resolved_addresses.dispatch_render_pass_viewport, + kDispatchRenderPassViewport_RVA); + if (dispatch_fn) { g_dispatch_viewport_hook = safetyhook::create_inline( - reinterpret_cast(base_addr + kDispatchRenderPassViewport_RVA), + dispatch_fn, reinterpret_cast(&Hooked_DispatchRenderPassViewport)); } } if (!g_ui_orchestrator_hook) { - const uintptr_t base_addr = reinterpret_cast(GetModuleHandleA(NULL)); - if (base_addr != 0) + void* ui_orchestrator_fn = ResolveGBFRCodeOrFallback( + g_resolved_addresses.ui_render_orchestrator, + kUIRenderOrchestrator_RVA); + if (ui_orchestrator_fn) { g_ui_orchestrator_hook = safetyhook::create_mid( - reinterpret_cast(base_addr + kUIRenderOrchestrator_RVA), + ui_orchestrator_fn, &OnUIRenderOrchestratorEntry); } } +#endif PatchJitterPhases(); #ifdef PATCH_JITTER_TABLE_INIT if (!g_taa_init_hook) { - const uintptr_t base_addr = reinterpret_cast(GetModuleHandleA(NULL)); - if (base_addr != 0) + void* taa_init_fn = ResolveGBFRCodeOrFallback( + g_resolved_addresses.temporal_aa_component_init, + kTemporalAntiAliasingComponent_Init_RVA); + if (taa_init_fn) { g_taa_init_hook = safetyhook::create_inline( - reinterpret_cast(base_addr + kTemporalAntiAliasingComponent_Init_RVA), + taa_init_fn, reinterpret_cast(&Hooked_TemporalAntiAliasingComponentInit)); } } @@ -722,11 +737,13 @@ class GranblueFantasyRelink final : public Game if (!g_jitter_write_hook) { - const uintptr_t base_addr = reinterpret_cast(GetModuleHandleA(NULL)); - if (base_addr != 0) + void* jitter_write_site = ResolveGBFRCodeOrFallback( + g_resolved_addresses.jitter_write_site, + kJitterWrite_RVA); + if (jitter_write_site) { g_jitter_write_hook = safetyhook::create_mid( - reinterpret_cast(base_addr + kJitterWrite_RVA), + jitter_write_site, &OnJitterWrite); } } @@ -751,9 +768,11 @@ class GranblueFantasyRelink final : public Game trace_scheduled = true; game_device_data.pause_trace_delay_countdown = -1; - const uintptr_t mod_base = reinterpret_cast(GetModuleHandleA(NULL)); - const uintptr_t settings_obj = (mod_base != 0) - ? *reinterpret_cast(mod_base + kTAASettingsGlobal_RVA) + const uintptr_t settings_ptr_addr = ResolveGBFRDataOrFallback( + g_resolved_addresses.taa_settings_global, + kTAASettingsGlobal_RVA); + const uintptr_t settings_obj = (settings_ptr_addr != 0) + ? *reinterpret_cast(settings_ptr_addr) : 0; auto& snap = game_device_data.pause_snapshot; @@ -898,7 +917,7 @@ class GranblueFantasyRelink final : public Game reshade::get_config_value(runtime, NAME, "Saturation", cb_luma_global_settings.GameSettings.Saturation); reshade::get_config_value(runtime, NAME, "Dechroma", cb_luma_global_settings.GameSettings.Dechroma); reshade::get_config_value(runtime, NAME, "HighlightSaturation", cb_luma_global_settings.GameSettings.HighlightSaturation); - reshade::get_config_value(runtime, NAME, "BloomType", cb_luma_global_settings.GameSettings.BloomType); + // reshade::get_config_value(runtime, NAME, "BloomType", cb_luma_global_settings.GameSettings.BloomType); reshade::get_config_value(runtime, NAME, "BloomStrength", cb_luma_global_settings.GameSettings.BloomStrength); } @@ -1062,19 +1081,19 @@ class GranblueFantasyRelink final : public Game reshade::set_config_value(runtime, NAME, "Flare", cb_luma_global_settings.GameSettings.Flare); } - // Bloom settings - const char* bloom_type_names[] = {"Vanilla", "HDR"}; - if (ImGui::SliderInt("Bloom Type", &bloom_type, 0, 1, bloom_type_names[bloom_type], ImGuiSliderFlags_AlwaysClamp)) - { - cb_luma_global_settings.GameSettings.BloomType = bloom_type; - reshade::set_config_value(runtime, NAME, "BloomType", cb_luma_global_settings.GameSettings.BloomType); - } - if (DrawResetButton(bloom_type, 1, "BloomType", runtime)) - { - bloom_type = 1; - cb_luma_global_settings.GameSettings.BloomType = bloom_type; - reshade::set_config_value(runtime, NAME, "BloomType", cb_luma_global_settings.GameSettings.BloomType); - } + // // Bloom settings + // const char* bloom_type_names[] = {"Vanilla", "HDR"}; + // if (ImGui::SliderInt("Bloom Type", &bloom_type, 0, 1, bloom_type_names[bloom_type], ImGuiSliderFlags_AlwaysClamp)) + // { + // cb_luma_global_settings.GameSettings.BloomType = bloom_type; + // reshade::set_config_value(runtime, NAME, "BloomType", cb_luma_global_settings.GameSettings.BloomType); + // } + // if (DrawResetButton(bloom_type, 1, "BloomType", runtime)) + // { + // bloom_type = 1; + // cb_luma_global_settings.GameSettings.BloomType = bloom_type; + // reshade::set_config_value(runtime, NAME, "BloomType", cb_luma_global_settings.GameSettings.BloomType); + // } if (ImGui::SliderFloat("Bloom Strength", &blooom_strength, 0.0f, 100.0f, "%.0f", ImGuiSliderFlags_AlwaysClamp)) { @@ -1107,11 +1126,13 @@ class GranblueFantasyRelink final : public Game void PrintImGuiInfo(const DeviceData& device_data) override { auto& game_device_data = GetGameDeviceData(device_data); - const uintptr_t mod_base = reinterpret_cast(GetModuleHandleA(NULL)); // Read TAA settings object for per-bit queries beyond the TAA-enabled flag - const uintptr_t settings_obj = (mod_base != 0) - ? *reinterpret_cast(mod_base + kTAASettingsGlobal_RVA) + const uintptr_t settings_ptr_addr = ResolveGBFRDataOrFallback( + g_resolved_addresses.taa_settings_global, + kTAASettingsGlobal_RVA); + const uintptr_t settings_obj = (settings_ptr_addr != 0) + ? *reinterpret_cast(settings_ptr_addr) : 0; ImGui::NewLine(); @@ -1155,15 +1176,18 @@ class GranblueFantasyRelink final : public Game // Jitter phase and direct table read { - const uint8_t phase = (mod_base != 0) - ? (*reinterpret_cast(mod_base + kJitterPhaseCounter_RVA) & static_cast(JITTER_PHASES - 1)) + const uintptr_t phase_counter_addr = ResolveGBFRDataOrFallback( + g_resolved_addresses.jitter_phase_counter, + kJitterPhaseCounter_RVA); + const uint8_t phase = (phase_counter_addr != 0) + ? (*reinterpret_cast(phase_counter_addr) & static_cast(JITTER_PHASES - 1)) : 0u; ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted("Jitter Phase"); ImGui::TableSetColumnIndex(1); - if (mod_base != 0) + if (phase_counter_addr != 0) ImGui::Text("%u / %u", static_cast(phase), static_cast(JITTER_PHASES)); else ImGui::TextUnformatted("N/A"); @@ -1223,61 +1247,67 @@ class GranblueFantasyRelink final : public Game ImGui::TableSetColumnIndex(1); ImGui::TextUnformatted(game_device_data.taa_output_texture.get() ? "Ready" : "Null"); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Likely Pause Branch: byte_1461720A4 bit0"); - ImGui::TableSetColumnIndex(1); - if (mod_base != 0) - { - const uint8_t v = *reinterpret_cast(mod_base + kPauseCandidate_GlobalBit_RVA); - ImGui::Text("%s (0x%02X)", (v & 1) ? "1/true" : "0/false", static_cast(v)); - } - else - { - ImGui::TextUnformatted("N/A"); - } + ImGui::EndTable(); + } - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Candidate: byte_145E5CABD (Tonemap gate)"); - ImGui::TableSetColumnIndex(1); - if (mod_base != 0) - { - const uint8_t v = *reinterpret_cast(mod_base + kPauseCandidate_TonemapGate_RVA); - ImGui::Text("%s (0x%02X)", (v == 1) ? "1/enabled" : "!=1/disabled", static_cast(v)); - } - else - { - ImGui::TextUnformatted("N/A"); - } + if (ImGui::BeginTable("gbfr_address_info", 3, ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) + { + ImGui::TableSetupColumn("Address", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Active", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Candidate: byte_146130C5C (DoF/Bloom)"); - ImGui::TableSetColumnIndex(1); - if (mod_base != 0) - { - const uint8_t v = *reinterpret_cast(mod_base + kPauseCandidate_DofGateA_RVA); - ImGui::Text("%s (0x%02X)", (v == 0) ? "0/branch-taken" : "!=0/branch-skipped", static_cast(v)); - } - else + const auto draw_data_addr_row = [](const char* label, uintptr_t resolved_abs, uintptr_t fallback_rva) { - ImGui::TextUnformatted("N/A"); - } + const uintptr_t active_addr = ResolveGBFRDataOrFallback(resolved_abs, fallback_rva); + const bool from_signature = resolved_abs != 0; - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextUnformatted("Candidate: byte_146130E13 (DoF/Bloom)"); - ImGui::TableSetColumnIndex(1); - if (mod_base != 0) - { - const uint8_t v = *reinterpret_cast(mod_base + kPauseCandidate_DofGateB_RVA); - ImGui::Text("%s (0x%02X)", (v == 0) ? "0/branch-taken" : "!=0/branch-skipped", static_cast(v)); - } - else + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(label); + ImGui::TableSetColumnIndex(1); + if (active_addr != 0) + ImGui::Text("0x%llX", static_cast(active_addr)); + else + ImGui::TextUnformatted("N/A"); + ImGui::TableSetColumnIndex(2); + ImGui::TextUnformatted(from_signature ? "Signature" : "RVA fallback"); + }; + + const auto draw_code_addr_row = [](const char* label, void* resolved_abs, uintptr_t fallback_rva) { - ImGui::TextUnformatted("N/A"); - } + const uintptr_t active_addr = reinterpret_cast(ResolveGBFRCodeOrFallback(resolved_abs, fallback_rva)); + const bool from_signature = resolved_abs != nullptr; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(label); + ImGui::TableSetColumnIndex(1); + if (active_addr != 0) + ImGui::Text("0x%llX", static_cast(active_addr)); + else + ImGui::TextUnformatted("N/A"); + ImGui::TableSetColumnIndex(2); + ImGui::TextUnformatted(from_signature ? "Signature" : "RVA fallback"); + }; + + draw_code_addr_row("InitializeDX11RenderingPipeline", g_resolved_addresses.initialize_dx11_rendering_pipeline, kInitializeDX11RenderingPipeline_RVA); + draw_code_addr_row("DispatchRenderPassViewport", g_resolved_addresses.dispatch_render_pass_viewport, kDispatchRenderPassViewport_RVA); + draw_code_addr_row("UIRenderOrchestrator", g_resolved_addresses.ui_render_orchestrator, kUIRenderOrchestrator_RVA); + draw_code_addr_row("Jitter Write Site", g_resolved_addresses.jitter_write_site, kJitterWrite_RVA); +#ifdef PATCH_JITTER_TABLE_INIT + draw_code_addr_row("TemporalAAComponentInit", g_resolved_addresses.temporal_aa_component_init, kTemporalAntiAliasingComponent_Init_RVA); +#endif + + draw_data_addr_row("g_outputWidth", g_resolved_addresses.output_width, kOutputWidth_RVA); + draw_data_addr_row("g_outputHeight", g_resolved_addresses.output_height, kOutputHeight_RVA); + draw_data_addr_row("g_renderWidth", g_resolved_addresses.render_width, kRenderWidth_RVA); + draw_data_addr_row("g_renderHeight", g_resolved_addresses.render_height, kRenderHeight_RVA); + draw_data_addr_row("g_camera", g_resolved_addresses.camera_global, kCameraGlobal_RVA); + draw_data_addr_row("g_taa_settings_obj", g_resolved_addresses.taa_settings_global, kTAASettingsGlobal_RVA); + draw_data_addr_row("g_frame_counter", g_resolved_addresses.jitter_phase_counter, kJitterPhaseCounter_RVA); + draw_data_addr_row("JitterPhaseMask CL imm", g_resolved_addresses.jitter_phase_mask_cl_imm, kJitterPhaseMask_CL_RVA); + draw_data_addr_row("JitterPhaseMask EAX imm", g_resolved_addresses.jitter_phase_mask_eax_imm, kJitterPhaseMask_EAX_RVA); ImGui::EndTable(); } @@ -1443,6 +1473,10 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv shader_hashes_Tonemap.pixel_shaders.emplace(std::stoul("60F0256B", nullptr, 16)); shader_hashes_MotionBlur.pixel_shaders.emplace(std::stoul("45841F6D", nullptr, 16)); shader_hashes_MotionBlurDenoise.pixel_shaders.emplace(std::stoul("199A3FBC", nullptr, 16)); + shader_hashes_DoFCoCPrefilter.pixel_shaders.emplace(std::stoul("0BB781D8", nullptr, 16)); + shader_hashes_DoFBokehGather.pixel_shaders.emplace(std::stoul("AA6346F2", nullptr, 16)); + shader_hashes_DoFTemporalResolve.pixel_shaders.emplace(std::stoul("560B601E", nullptr, 16)); + shader_hashes_DoFFinalComposite.pixel_shaders.emplace(std::stoul("F041F90A", nullptr, 16)); shader_hashes_CutsceneGamma.pixel_shaders.emplace(std::stoul("1085E11F", nullptr, 16)); shader_hashes_CutsceneColorGrade.pixel_shaders.emplace(std::stoul("50BE35B0", nullptr, 16)); shader_hashes_CutsceneOverlayBlend.pixel_shaders.emplace(std::stoul("4517077B", nullptr, 16)); @@ -1473,6 +1507,10 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv forced_shader_names.emplace(std::stoul("478E345C", nullptr, 16), "TAA"); forced_shader_names.emplace(std::stoul("E49E117A", nullptr, 16), "TAA RenoDX"); forced_shader_names.emplace(std::stoul("45841F6D", nullptr, 16), "Motion Blur"); + forced_shader_names.emplace(std::stoul("0BB781D8", nullptr, 16), "DoF CoC Prefilter"); + forced_shader_names.emplace(std::stoul("AA6346F2", nullptr, 16), "DoF Bokeh Gather"); + forced_shader_names.emplace(std::stoul("560B601E", nullptr, 16), "DoF Temporal Resolve"); + forced_shader_names.emplace(std::stoul("F041F90A", nullptr, 16), "DoF Final Composite"); forced_shader_names.emplace(std::stoul("1085E11F", nullptr, 16), "Cutscene Gamma"); forced_shader_names.emplace(std::stoul("50BE35B0", nullptr, 16), "Cutscene Color Grade"); forced_shader_names.emplace(std::stoul("B9AFD904", nullptr, 16), "Cutscene Overlay Modulate"); @@ -1502,7 +1540,7 @@ BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserv cb_luma_global_settings.GameSettings.HighlightSaturation = 1.f; cb_luma_global_settings.GameSettings.HueEmulation = 0.f; cb_luma_global_settings.GameSettings.PurityEmulation = 0.f; - cb_luma_global_settings.GameSettings.BloomType = 1; // Default to HDR bloom + cb_luma_global_settings.GameSettings.BloomType = 0; // Default to HDR bloom cb_luma_global_settings.GameSettings.BloomStrength = 1.f; game = new GranblueFantasyRelink();