Updated the kNoBorder fix to cover all non-VR versions

This commit is contained in:
Eddoursul 2026-07-31 01:10:43 +02:00
parent 9cc519a8aa
commit d710d60519
2 changed files with 38 additions and 20 deletions

View File

@ -255,8 +255,7 @@ SKSEPluginLoad(const LoadInterface* skse) {
logger::info("Initializing Stay At The System Page...");
JournalMenuEx::InstallHooks();
}
// Menus are only stretched to fill the viewport since 1.6.1130
if (g_settings.at("MenuAspectRatioFix") && REL::Module::get().version() >= REL::Version(1, 6, 1130, 0)) {
if (g_settings.at("MenuAspectRatioFix")) {
MenuAspectRatioFix::Install();
}
LoadScreenFrameFix::Install(g_settings.at("LoadScreenFrameFix"));

View File

@ -1,17 +1,21 @@
#pragma once
// Restores the Scaleform scale modes menus were loaded with before Skyrim 1.6.1130, and stops
// kNoBorder from cropping them on displays wider than 16:9.
// Two independent corrections to the Scaleform scale mode menus are loaded with, both applied
// from the same BSScaleformManager::LoadMovie hook:
//
// 1.6.1130 switched nearly every menu to kExactFit, which stretches the movie across the whole
// viewport instead of preserving its aspect ratio. Enderal's menus - including the SkyUI-derived
// widescreen versions shipped with them - are authored for the old modes, so on anything that is
// not 16:9 they come out distorted.
// 1. Restore the modes used before 1.6.1130. That build switched nearly every menu to
// kExactFit, which stretches the movie across the whole viewport instead of preserving its
// aspect ratio. Enderal's menus - including the SkyUI-derived widescreen versions shipped
// with them - are authored for the old modes, so on anything that is not 16:9 they come out
// distorted. Only 1.6.1130 and later need this; see g_restoreScaleModes.
// 2. Stop kNoBorder from cropping menus on displays wider than 16:9. That one predates
// 1.6.1130 and is needed on every runtime, so it is not version gated; see ClampToViewport.
//
// The table below is what 1.6.640 passes to BSScaleformManager::LoadMovie, plus BookMenu and
// GiftMenu, which 1.6.1130 left on kNoBorder and which need the same clamp. Menus that want no
// help (Book, Console, CreditsMenu, CursorMenu, FaderMenu, StartMenu) are absent and keep
// whatever the caller asked for.
// The table below is what 1.6.640 passes to LoadMovie, plus BookMenu and GiftMenu, which 1.6.1130
// left on kNoBorder and which need the clamp. Menus that want no help (Book, Console, CreditsMenu,
// CursorMenu, FaderMenu, StartMenu) are absent and keep whatever the caller asked for. It doubles
// as the list of menus this patch is allowed to touch at all, so on older runtimes - where the
// engine already passes these very modes - a third-party menu loaded with kNoBorder is left alone.
//
// Based on Widescreen Scale Removed by SkyHorizon (GPL-3.0)
// https://www.nexusmods.com/skyrimspecialedition/mods/136793
@ -19,6 +23,10 @@ namespace MenuAspectRatioFix
{
using ScaleModeType = RE::GFxMovieView::ScaleModeType;
// True on the runtimes that stretch menus to fill the viewport, i.e. 1.6.1130 and later. Below
// that the engine already asks for the modes in the table, so only the clamp has work to do.
inline bool g_restoreScaleModes = false;
std::optional<ScaleModeType> GetScaleMode(const char* a_fileName)
{
static const std::map<std::string_view, ScaleModeType> modes{
@ -88,7 +96,7 @@ namespace MenuAspectRatioFix
{
if (a_fileName && a_fileName[0]) {
if (const auto mode = GetScaleMode(a_fileName); mode) {
a_mode = ClampToViewport(*mode);
a_mode = ClampToViewport(g_restoreScaleModes ? *mode : a_mode);
}
}
@ -100,21 +108,32 @@ namespace MenuAspectRatioFix
void Install()
{
// Checked: 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179
g_restoreScaleModes = REL::Module::get().version() >= REL::Version(1, 6, 1130, 0);
// Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179
const auto target = REL::RelocationID(80302, 82325).address();
// mov rax,rsp / mov qword ptr [rax+10h],rdx - relocated into the trampoline by the hook,
// so bail out rather than corrupt the function if a future runtime changes the prologue
constexpr std::uint8_t prologue[]{ 0x48, 0x8B, 0xC4, 0x48, 0x89, 0x50, 0x10 };
// mov rax,rsp followed by one argument spill - 7 bytes of position-independent code, which
// the hook relocates into the trampoline. The spilled argument differs between the runtime
// families, so accept either and bail out rather than corrupt the function if a future
// build changes the prologue again.
constexpr std::uint8_t prologueAE[]{ 0x48, 0x8B, 0xC4, 0x48, 0x89, 0x50, 0x10 }; // mov [rax+10h],rdx
constexpr std::uint8_t prologueSE[]{ 0x48, 0x8B, 0xC4, 0x4C, 0x89, 0x40, 0x18 }; // mov [rax+18h],r8
if (!target || std::memcmp(reinterpret_cast<const void*>(target), prologue, sizeof(prologue)) != 0) {
static_assert(sizeof(prologueAE) == sizeof(prologueSE));
const auto prologue = reinterpret_cast<const void*>(target);
if (!target ||
(std::memcmp(prologue, prologueAE, sizeof(prologueAE)) != 0 &&
std::memcmp(prologue, prologueSE, sizeof(prologueSE)) != 0)) {
logger::error("Unexpected BSScaleformManager::LoadMovie prologue, menu aspect ratio fix is disabled");
return;
}
SKSE::AllocTrampoline(64);
SKSE::stl::hook_function_prologue<LoadMovie, sizeof(prologue)>(target);
SKSE::stl::hook_function_prologue<LoadMovie, sizeof(prologueAE)>(target);
logger::info("Applied menu aspect ratio fix");
logger::info("Applied menu aspect ratio fix{}", g_restoreScaleModes ? "" : " (viewport clamp only)");
}
}