Added Widescreen Scale Removed by SkyHorizon

This commit is contained in:
Eddoursul 2026-07-30 21:45:06 +02:00
parent ce55b39a25
commit 1b2eccfd6c
4 changed files with 136 additions and 1 deletions

View File

@ -9,3 +9,4 @@ AttachLightHitEffectCrashFix = true
AutoScaleHeroMenu = true
WarnFormTypeCollisions = true
GogVramLeakFix = true
MenuAspectRatioFix = true

View File

@ -17,6 +17,7 @@
#include "Patches/FormTypeCollisionDetector.h"
#include "Patches/GogVramLeakFix.h"
#include "Patches/ForceLanguage.h"
#include "Patches/MenuAspectRatioFix.h"
using namespace SKSE;
@ -31,7 +32,8 @@ static std::map<std::string, bool> g_settings{
{ "ForceBorderless", true },
{ "AttachLightHitEffectCrashFix", true },
{ "AutoScaleHeroMenu", true },
{ "GogVramLeakFix", true }
{ "GogVramLeakFix", true },
{ "MenuAspectRatioFix", true }
};
namespace {
@ -252,6 +254,10 @@ 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)) {
MenuAspectRatioFix::Install();
}
}
logger::info("{} has finished loading.", plugin->GetName());

View File

@ -37,4 +37,41 @@ namespace SKSE::stl
auto& trampoline = SKSE::GetTrampoline();
T::func = trampoline.write_branch<N>(a_src, T::thunk);
}
// Detours a whole function: the first BYTES bytes are copied into the trampoline and followed by
// a jump back into the original, so T::func is a callable stand-in for the untouched function.
// BYTES must cover complete, position-independent instructions and be at least 5.
// Costs 14 bytes of trampoline for the branch island plus BYTES + 14 for the stub.
// Thanks Nukem and po3, via Widescreen Scale Removed by SkyHorizon (GPL-3.0).
template <class T, std::size_t BYTES>
void hook_function_prologue(std::uintptr_t a_src)
{
static_assert(BYTES >= 5, "not enough room for a jump");
// Xbyak::CodeGenerator has a member function std() - the STD instruction - which hides the
// namespace inside the class body, hence the leading :: on every std:: name below
struct Stub : Xbyak::CodeGenerator
{
Stub(std::uintptr_t a_target, std::size_t a_size)
{
for (::std::size_t i = 0; i < a_size; ++i) {
db(*reinterpret_cast<const ::std::uint8_t*>(a_target + i));
}
jmp(ptr[rip]);
dq(a_target + a_size);
}
};
Stub stub(a_src, BYTES);
stub.ready();
auto& trampoline = SKSE::GetTrampoline();
auto* mem = trampoline.allocate(stub.getSize());
std::memcpy(mem, stub.getCode(), stub.getSize());
// Publish the stand-in before redirecting the function, the thunk needs it on its first call
T::func = reinterpret_cast<std::uintptr_t>(mem);
trampoline.write_branch<5>(a_src, T::thunk);
}
}

View File

@ -0,0 +1,91 @@
#pragma once
// Restores the Scaleform scale modes menus were loaded with before Skyrim 1.6.1130.
//
// 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.
//
// The table below is what 1.6.640 passes to BSScaleformManager::LoadMovie. Menus the update left
// alone (Book, BookMenu, Console, CreditsMenu, CursorMenu, FaderMenu, GiftMenu, StartMenu) are
// deliberately absent and keep whatever the caller asked for.
//
// Based on Widescreen Scale Removed by SkyHorizon (GPL-3.0)
// https://www.nexusmods.com/skyrimspecialedition/mods/136793
namespace MenuAspectRatioFix
{
using ScaleModeType = RE::GFxMovieView::ScaleModeType;
std::optional<ScaleModeType> GetScaleMode(const char* a_fileName)
{
static const std::map<std::string_view, ScaleModeType> modes{
{ "FavoritesMenu"sv, ScaleModeType::kShowAll },
{ "HUDMenu"sv, ScaleModeType::kShowAll },
{ "LevelUpMenu"sv, ScaleModeType::kShowAll },
{ "LoadWaitSpinner"sv, ScaleModeType::kShowAll },
{ "LoadingMenu"sv, ScaleModeType::kShowAll },
{ "Map"sv, ScaleModeType::kShowAll },
{ "Quest_Journal"sv, ScaleModeType::kShowAll },
{ "SafeZone"sv, ScaleModeType::kShowAll },
{ "SleepWaitMenu"sv, ScaleModeType::kShowAll },
{ "Titles"sv, ScaleModeType::kShowAll },
{ "TutorialMenu"sv, ScaleModeType::kShowAll },
{ "TweenMenu"sv, ScaleModeType::kShowAll },
{ "BarterMenu"sv, ScaleModeType::kNoBorder },
{ "ContainerMenu"sv, ScaleModeType::kNoBorder },
{ "CraftingMenu"sv, ScaleModeType::kNoBorder },
{ "DialogueMenu"sv, ScaleModeType::kNoBorder },
{ "InventoryMenu"sv, ScaleModeType::kNoBorder },
{ "LockpickingMenu"sv, ScaleModeType::kNoBorder },
{ "MagicMenu"sv, ScaleModeType::kNoBorder },
{ "MessageBox"sv, ScaleModeType::kNoBorder },
{ "RaceSex_menu"sv, ScaleModeType::kNoBorder },
{ "StatsMenu"sv, ScaleModeType::kNoBorder },
{ "TrainingMenu"sv, ScaleModeType::kNoBorder },
// Replaces CraftingMenu, https://www.nexusmods.com/skyrimspecialedition/mods/81409
{ "ConstructibleObjectMenu"sv, ScaleModeType::kNoBorder }
};
const auto it = modes.find(a_fileName);
return it != modes.end() ? std::optional{ it->second } : std::nullopt;
}
struct LoadMovie
{
static bool thunk(RE::BSScaleformManager* a_scaleformManager, RE::IMenu* a_menu, RE::GPtr<RE::GFxMovieView>& a_viewOut, const char* a_fileName, ScaleModeType a_mode, float a_backgroundAlpha)
{
if (a_fileName && a_fileName[0]) {
if (const auto mode = GetScaleMode(a_fileName); mode) {
a_mode = *mode;
}
}
return func(a_scaleformManager, a_menu, a_viewOut, a_fileName, a_mode, a_backgroundAlpha);
}
static inline REL::Relocation<decltype(thunk)> func;
};
void Install()
{
// Checked: 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 };
if (!target || std::memcmp(reinterpret_cast<const void*>(target), prologue, sizeof(prologue)) != 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);
logger::info("Applied menu aspect ratio fix");
}
}