From 3939ba1daa3106c95cb4e995cdbd6b0eb3dc3cd3 Mon Sep 17 00:00:00 2001 From: Eddoursul Date: Sat, 22 Aug 2026 14:19:01 +0200 Subject: [PATCH] Updated DLL for compatibility with SSE 1.7.99 --- SKSE/Plugins/versionlib-1-7-99-0.bin | 3 + source/Enderal DLL/CMakeLists.txt | 5 +- .../cmake/patch_commonlibsse.cmake | 80 +++++++++++++++++++ .../Enderal DLL/src/Patches/AchievementFix.h | 2 +- .../src/Patches/BinkInterruptPatch.h | 2 +- .../src/Patches/DialogueMenuPatch.h | 4 +- .../Enderal DLL/src/Patches/FlatMapMarkers.h | 2 +- .../Enderal DLL/src/Patches/ForceBorderless.h | 33 ++++++-- .../Enderal DLL/src/Patches/GogVramLeakFix.h | 6 +- .../Enderal DLL/src/Patches/HeroMenuPatch.cpp | 2 +- .../src/Patches/MapMarkerPlacement.h | 4 +- .../src/Patches/MenuAspectRatioFix.h | 2 +- .../Enderal DLL/src/Patches/TweenMenuPatch.h | 2 +- source/Enderal DLL/src/Util.h | 2 +- source/Steam DLL/CMakeLists.txt | 5 +- .../Steam DLL/cmake/patch_commonlibsse.cmake | 80 +++++++++++++++++++ source/Steam DLL/src/PapyrusFunctions.h | 2 +- 17 files changed, 209 insertions(+), 27 deletions(-) create mode 100644 SKSE/Plugins/versionlib-1-7-99-0.bin create mode 100644 source/Enderal DLL/cmake/patch_commonlibsse.cmake create mode 100644 source/Steam DLL/cmake/patch_commonlibsse.cmake diff --git a/SKSE/Plugins/versionlib-1-7-99-0.bin b/SKSE/Plugins/versionlib-1-7-99-0.bin new file mode 100644 index 000000000..605eecf2b --- /dev/null +++ b/SKSE/Plugins/versionlib-1-7-99-0.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:184fca0c834e0d2523b450d18ea32c9fbf9f6295e88e936712b7360f1fccc4ec +size 2260388 diff --git a/source/Enderal DLL/CMakeLists.txt b/source/Enderal DLL/CMakeLists.txt index 94c1671c8..b6df7c89e 100644 --- a/source/Enderal DLL/CMakeLists.txt +++ b/source/Enderal DLL/CMakeLists.txt @@ -145,8 +145,9 @@ set(BUILD_TESTS OFF CACHE BOOL " " FORCE) message(STATUS "Fetching CommonLibSSE-NG...") FetchContent_Declare( CommonLibSSE - GIT_REPOSITORY https://github.com/alandtse/CommonLibVR - GIT_TAG aacbd76c01bff9381e253ffdfcb4f9d5f263f1df + GIT_REPOSITORY https://github.com/alandtse/CommonLibSSE-NG + GIT_TAG 8b48fb1b76d6ce9353af138d86037b4246c73527 + PATCH_COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch_commonlibsse.cmake" ) FetchContent_MakeAvailable(CommonLibSSE) diff --git a/source/Enderal DLL/cmake/patch_commonlibsse.cmake b/source/Enderal DLL/cmake/patch_commonlibsse.cmake new file mode 100644 index 000000000..59c8a9a50 --- /dev/null +++ b/source/Enderal DLL/cmake/patch_commonlibsse.cmake @@ -0,0 +1,80 @@ +# Patch applied to CommonLibSSE-NG via FetchContent PATCH_COMMAND. +# +# Skyrim AE 1.7.99 inserts two event sink bases into RE::SkyrimVM at 0x180 and +# 0x188, so every member from 0x180 onwards moves up by 0x10. CommonLibSSE-NG +# 6.5.0 modelled most of that shift (handlePolicy, the runtime data blocks and +# the tail block all get version aware accessors), but it left the pointer block +# that starts at "impl" declared at its pre-1.7.99 offsets. +# +# "impl" holds the BSScript::IVirtualMachine the whole Papyrus layer runs on: +# +# RE::BSScript::Internal::VirtualMachine::GetSingleton() +# -> SkyrimVM::GetSingleton()->impl.get() +# +# On 1.7.99 that reads offset 0x200, which now lands inside the preceding +# BSTEventSource, so the singleton is null or garbage. The visible effects are +# "Failed to get vm type id for class !" for every object typed Papyrus +# parameter (RE::BSScript::GetRawTypeFromVMType null checks the pointer) and a +# crash in any caller that does not (Papyrus argument marshalling in +# PackUnpack.cpp, and this plugin's own script checks). +# +# The patch adds a version aware GetImpl() and routes the two in tree consumers +# through it. It deliberately leaves the member declarations alone, so every +# layout static_assert still holds. +# +# Verification: 61 call sites in SkyrimSE.exe 1.6.1170 read the field at +# [SkyrimVM + 0x200], and the matching 61 sites in 1.7.99 read [SkyrimVM + 0x210]. +# +# Remove this file, its two PATCH_COMMAND entries and this note once upstream +# fixes the offset. The script is idempotent, and it fails the configure step if +# any of the three anchors is missing, so a bump that changes or fixes this code +# is reported instead of silently producing an unpatched build. + +set(_clib_header "include/RE/S/SkyrimVM.h") +set(_clib_vm "src/RE/V/VirtualMachine.cpp") +set(_clib_skvm "src/RE/S/SkyrimVM.cpp") + +set(_clib_marker "GetImpl") + +set(_clib_accessor +" // AE 1.7.99 shifts every member from 0x180 onwards by +0x10, and impl is + // declared at its pre-1.7.99 offset. Read it through here, never directly. + [[nodiscard]] inline BSTSmartPointer& GetImpl() noexcept + { + // Only AE reaches 1.7.99, so the version test alone selects the runtime. + return REL::RelocateMemberIfNewer>( + SKSE::RUNTIME_SSE_1_7_99, this, 0x200, 0x210); + } + + [[nodiscard]] inline const BSTSmartPointer& GetImpl() const noexcept + { + return const_cast(this)->GetImpl(); + } + + static SkyrimVM* GetSingleton();") + +function(_clib_patch_file path from to) + if(NOT EXISTS "${path}") + message(FATAL_ERROR "CommonLibSSE patch: ${path} is missing.") + endif() + file(READ "${path}" _content) + string(FIND "${_content}" "${_clib_marker}" _found) + if(_found GREATER_EQUAL 0) + return() # already patched + endif() + string(FIND "${_content}" "${from}" _anchor) + if(_anchor LESS 0) + message(FATAL_ERROR + "CommonLibSSE patch: anchor not found in ${path}.\n" + "Check whether upstream fixed SkyrimVM::impl for AE 1.7.99. If it did, " + "delete cmake/patch_commonlibsse.cmake and its PATCH_COMMAND entries.") + endif() + string(REPLACE "${from}" "${to}" _content "${_content}") + file(WRITE "${path}" "${_content}") +endfunction() + +_clib_patch_file("${_clib_header}" " static SkyrimVM* GetSingleton();" "${_clib_accessor}") +_clib_patch_file("${_clib_vm}" "vm->impl.get()" "vm->GetImpl().get()") +_clib_patch_file("${_clib_skvm}" "impl.get()->SendEvent" "GetImpl().get()->SendEvent") + +message(STATUS "Patched CommonLibSSE-NG SkyrimVM::impl for AE 1.7.99.") diff --git a/source/Enderal DLL/src/Patches/AchievementFix.h b/source/Enderal DLL/src/Patches/AchievementFix.h index b732215e3..e3a660cc7 100644 --- a/source/Enderal DLL/src/Patches/AchievementFix.h +++ b/source/Enderal DLL/src/Patches/AchievementFix.h @@ -9,7 +9,7 @@ namespace AchievementFix if (REL::Module::IsVR()) { target = REL::Offset(0x17FB90); } else if (REL::Module::get().version() >= REL::Version(1, 6, 1130, 0)) { - // Checked: 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 target = REL::ID(441528); } else { // Checked: 1.5.97, 1.6.640, 1.6.659 diff --git a/source/Enderal DLL/src/Patches/BinkInterruptPatch.h b/source/Enderal DLL/src/Patches/BinkInterruptPatch.h index 089ffecf7..34f249be0 100644 --- a/source/Enderal DLL/src/Patches/BinkInterruptPatch.h +++ b/source/Enderal DLL/src/Patches/BinkInterruptPatch.h @@ -35,7 +35,7 @@ namespace BinkInterruptPatch void Install() { - // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 REL::Relocation target{ REL::RelocationID(87890, 90259), REL::Relocate(0x22, 0x21) }; SKSE::AllocTrampoline(14); BinkListener::func = SKSE::GetTrampoline().write_call<5>(target.address(), BinkListener::thunk); diff --git a/source/Enderal DLL/src/Patches/DialogueMenuPatch.h b/source/Enderal DLL/src/Patches/DialogueMenuPatch.h index ffb9a6fe0..2d1bb1783 100644 --- a/source/Enderal DLL/src/Patches/DialogueMenuPatch.h +++ b/source/Enderal DLL/src/Patches/DialogueMenuPatch.h @@ -89,7 +89,7 @@ private: // A failsafe when Goodbye does not close the dialogue menu. if (bTabBlocked) { auto* mtm = RE::MenuTopicManager::GetSingleton(); - if (mtm && mtm->isSayingGoodbye && !mtm->currentTopicInfo && RE::UI::GetSingleton()->IsMenuOpen(MENU_NAME)) { + if (mtm && mtm->forceGoodbye && !mtm->currentTopicInfo && RE::UI::GetSingleton()->IsMenuOpen(MENU_NAME)) { BlockTab(false); } } @@ -121,7 +121,7 @@ private: void static OpenJournal(bool a_bool) { using func_t = decltype(&OpenJournal); - // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 REL::Relocation func{ REL::VariantID(52428, 53327, 0x92F0F0) }; func(a_bool); } diff --git a/source/Enderal DLL/src/Patches/FlatMapMarkers.h b/source/Enderal DLL/src/Patches/FlatMapMarkers.h index 8e1891488..9c0d47de0 100644 --- a/source/Enderal DLL/src/Patches/FlatMapMarkers.h +++ b/source/Enderal DLL/src/Patches/FlatMapMarkers.h @@ -12,7 +12,7 @@ namespace FlatMapMarkers inline void Install() { - // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 REL::Relocation offset{ REL::RelocationID(52224, 53111), REL::Relocate(0x22F, 0x21F) }; SKSE::AllocTrampoline(14); SKSE::GetTrampoline().write_call<5>(offset.address(), &Hook_WorldPtToScreenPt3); diff --git a/source/Enderal DLL/src/Patches/ForceBorderless.h b/source/Enderal DLL/src/Patches/ForceBorderless.h index 04e52844e..9a6c8027e 100644 --- a/source/Enderal DLL/src/Patches/ForceBorderless.h +++ b/source/Enderal DLL/src/Patches/ForceBorderless.h @@ -3,6 +3,12 @@ // Bink playback is prone to freezing in bordered window, we enforce borderless mode here namespace ForceBorderless { + // The patched instruction reads the fullscreen flag the window style is built from, and is + // "movzx eax, byte ptr [rip+disp32]" on every runtime checked so far. Only the three opcode + // bytes are compared: the displacement moves with every build, while the opcode alone already + // pins the instruction length at 7 bytes, which is exactly what the replacement occupies. + constexpr std::uint8_t FULLSCREEN_READ[]{ 0x0F, 0xB6, 0x05 }; + void Install() { if (REL::Module::IsVR()) { @@ -11,20 +17,31 @@ namespace ForceBorderless std::uint8_t code[] = { 0xB8, 0x01, 0x00, 0x00, 0x00, REL::NOP, REL::NOP }; // mov eax,0x1 auto version = REL::Module::get().version(); + std::uintptr_t target = 0; - if (version >= REL::Version(1, 6, 1130, 0) && version <= REL::Version(1, 6, 1179, 0)) { - logger::info("Initializing borderless mode..."); - REL::Relocation target{ REL::ID(36547), 0xBEA }; - REL::safe_write(target.address(), code, sizeof(code)); + // Checked: 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 + if (version >= REL::Version(1, 6, 1130, 0)) { + target = REL::Relocation{ REL::ID(36547), 0xBEA }.address(); - } else if (version >= REL::Version(1, 5, 97, 0) && version < REL::Version(1, 6, 1130, 0)) { - logger::info("Initializing borderless mode..."); - REL::Relocation target{ REL::RelocationID(35548, 36547), REL::Relocate(0x83A, version >= REL::Version(1, 6, 342, 0) ? version >= REL::Version(1, 6, 629, 0) ? 0xCFA : 0xCDA : 0xCBB) }; - REL::safe_write(target.address(), code, sizeof(code)); + // Checked: 1.5.97, 1.6.640, 1.6.659 + } else if (version >= REL::Version(1, 5, 97, 0)) { + target = REL::Relocation{ REL::RelocationID(35548, 36547), REL::Relocate(0x83A, version >= REL::Version(1, 6, 342, 0) ? version >= REL::Version(1, 6, 629, 0) ? 0xCFA : 0xCDA : 0xCBB) }.address(); } else { logger::info("Untested version of Skyrim, skipping borderless mode..."); return; } + + // The branch above is open ended, so a future build resolves an offset nobody checked. + // Confirm the instruction is the expected read before overwriting it. A build that moved + // the read loses borderless mode, instead of getting seven bytes written into the middle + // of whatever landed there. + if (!target || std::memcmp(reinterpret_cast(target), FULLSCREEN_READ, sizeof(FULLSCREEN_READ)) != 0) { + logger::error("Unexpected instruction at the fullscreen check, skipping borderless mode..."); + return; + } + + logger::info("Initializing borderless mode..."); + REL::safe_write(target, code, sizeof(code)); } } diff --git a/source/Enderal DLL/src/Patches/GogVramLeakFix.h b/source/Enderal DLL/src/Patches/GogVramLeakFix.h index 99ca829b0..2f1cf0fd4 100644 --- a/source/Enderal DLL/src/Patches/GogVramLeakFix.h +++ b/source/Enderal DLL/src/Patches/GogVramLeakFix.h @@ -8,15 +8,15 @@ namespace GogVramLeakFix { static void thunk(RE::BSGraphics::Renderer* a_renderer, RE::BSGraphics::Texture* a_texture) { - if (_InterlockedExchangeAdd(&a_texture->unk20, 0xFFFFFFFF) == 1) { + if (_InterlockedExchangeAdd(&a_texture->refCount, 0xFFFFFFFF) == 1) { if (a_texture->resourceView) { a_texture->resourceView->Release(); } if (a_texture->texture) { a_texture->texture->Release(); } - if (a_texture->unk08) { - reinterpret_cast(a_texture->unk08)->Release(); + if (a_texture->UAV) { + a_texture->UAV->Release(); } REL::Relocation NiMemFree{ REL::RelocationID(102158, 109588) }; diff --git a/source/Enderal DLL/src/Patches/HeroMenuPatch.cpp b/source/Enderal DLL/src/Patches/HeroMenuPatch.cpp index 77dfad36f..287de09e6 100644 --- a/source/Enderal DLL/src/Patches/HeroMenuPatch.cpp +++ b/source/Enderal DLL/src/Patches/HeroMenuPatch.cpp @@ -38,7 +38,7 @@ void HeroMenuPatch::Install(bool aAutoScaleHeroMenu) } // Patch the Quick Stats hotkey to open the menu - // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 REL::Relocation target{ REL::RelocationID(51400, 52249), REL::Relocate(0x41E, 0x421) }; REL::safe_fill(target.address(), REL::NOP, 45); diff --git a/source/Enderal DLL/src/Patches/MapMarkerPlacement.h b/source/Enderal DLL/src/Patches/MapMarkerPlacement.h index d2269b8f6..545df89f1 100644 --- a/source/Enderal DLL/src/Patches/MapMarkerPlacement.h +++ b/source/Enderal DLL/src/Patches/MapMarkerPlacement.h @@ -21,7 +21,7 @@ namespace MapMarkerPlacement void InstallPlacementDiscoveredFix() { // Place a marker, if a location has been discovered and fast travel is disabled - // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 REL::Relocation target{ REL::RelocationID(52208, 53095), REL::Relocate(0x2C5, 0x328, 0x358) }; SKSE::AllocTrampoline(14); IsFastTravelEnabled::func = SKSE::GetTrampoline().write_call<5>(target.address(), IsFastTravelEnabled::thunk); @@ -32,7 +32,7 @@ namespace MapMarkerPlacement void InstallPlacementUndiscoveredFix() { // Place a marker without asking, if a location isn't discovered yet - // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 REL::Relocation target2{ REL::RelocationID(52208, 53095), REL::Relocate(0x17A, 0x183) }; std::uint8_t code[] = { 0xB0, 0x01, REL::NOP, REL::NOP, REL::NOP }; // mov al,0x1 REL::safe_write(target2.address(), code, sizeof(code)); diff --git a/source/Enderal DLL/src/Patches/MenuAspectRatioFix.h b/source/Enderal DLL/src/Patches/MenuAspectRatioFix.h index 17dbc6200..020abbf0c 100644 --- a/source/Enderal DLL/src/Patches/MenuAspectRatioFix.h +++ b/source/Enderal DLL/src/Patches/MenuAspectRatioFix.h @@ -110,7 +110,7 @@ namespace MenuAspectRatioFix { 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 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 const auto target = REL::RelocationID(80302, 82325).address(); // mov rax,rsp followed by one argument spill - 7 bytes of position-independent code, which diff --git a/source/Enderal DLL/src/Patches/TweenMenuPatch.h b/source/Enderal DLL/src/Patches/TweenMenuPatch.h index 95c908b62..f99bddee1 100644 --- a/source/Enderal DLL/src/Patches/TweenMenuPatch.h +++ b/source/Enderal DLL/src/Patches/TweenMenuPatch.h @@ -11,7 +11,7 @@ private: static void OpenMenu(RE::IMenu* tweenMenu, std::int32_t index) { using func_t = decltype(&OpenMenu); - // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 REL::Relocation func{ REL::VariantID(51845, 52718, 0x8FE7E0) }; return func(tweenMenu, index); } diff --git a/source/Enderal DLL/src/Util.h b/source/Enderal DLL/src/Util.h index efe69b4c7..745d7ed48 100644 --- a/source/Enderal DLL/src/Util.h +++ b/source/Enderal DLL/src/Util.h @@ -237,7 +237,7 @@ inline void CloseTweenMenu() { if (RE::UI::GetSingleton()->IsMenuOpen(RE::TweenMenu::MENU_NAME)) { using func_t = decltype(&CloseTweenMenu); - // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179 + // Checked: 1.5.97, 1.6.640, 1.6.659, 1.6.1130, 1.6.1170, 1.6.1179, 1.7.99 REL::Relocation func{ REL::VariantID(51839, 52711, 0x8FE180) }; func(); } diff --git a/source/Steam DLL/CMakeLists.txt b/source/Steam DLL/CMakeLists.txt index 20ac08ade..d78a895ca 100644 --- a/source/Steam DLL/CMakeLists.txt +++ b/source/Steam DLL/CMakeLists.txt @@ -140,8 +140,9 @@ set(BUILD_TESTS OFF CACHE BOOL " " FORCE) message(STATUS "Fetching CommonLibSSE-NG...") FetchContent_Declare( CommonLibSSE - GIT_REPOSITORY https://github.com/alandtse/CommonLibVR - GIT_TAG aacbd76c01bff9381e253ffdfcb4f9d5f263f1df + GIT_REPOSITORY https://github.com/alandtse/CommonLibSSE-NG + GIT_TAG 8b48fb1b76d6ce9353af138d86037b4246c73527 + PATCH_COMMAND ${CMAKE_COMMAND} -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/patch_commonlibsse.cmake" ) FetchContent_MakeAvailable(CommonLibSSE) diff --git a/source/Steam DLL/cmake/patch_commonlibsse.cmake b/source/Steam DLL/cmake/patch_commonlibsse.cmake new file mode 100644 index 000000000..59c8a9a50 --- /dev/null +++ b/source/Steam DLL/cmake/patch_commonlibsse.cmake @@ -0,0 +1,80 @@ +# Patch applied to CommonLibSSE-NG via FetchContent PATCH_COMMAND. +# +# Skyrim AE 1.7.99 inserts two event sink bases into RE::SkyrimVM at 0x180 and +# 0x188, so every member from 0x180 onwards moves up by 0x10. CommonLibSSE-NG +# 6.5.0 modelled most of that shift (handlePolicy, the runtime data blocks and +# the tail block all get version aware accessors), but it left the pointer block +# that starts at "impl" declared at its pre-1.7.99 offsets. +# +# "impl" holds the BSScript::IVirtualMachine the whole Papyrus layer runs on: +# +# RE::BSScript::Internal::VirtualMachine::GetSingleton() +# -> SkyrimVM::GetSingleton()->impl.get() +# +# On 1.7.99 that reads offset 0x200, which now lands inside the preceding +# BSTEventSource, so the singleton is null or garbage. The visible effects are +# "Failed to get vm type id for class !" for every object typed Papyrus +# parameter (RE::BSScript::GetRawTypeFromVMType null checks the pointer) and a +# crash in any caller that does not (Papyrus argument marshalling in +# PackUnpack.cpp, and this plugin's own script checks). +# +# The patch adds a version aware GetImpl() and routes the two in tree consumers +# through it. It deliberately leaves the member declarations alone, so every +# layout static_assert still holds. +# +# Verification: 61 call sites in SkyrimSE.exe 1.6.1170 read the field at +# [SkyrimVM + 0x200], and the matching 61 sites in 1.7.99 read [SkyrimVM + 0x210]. +# +# Remove this file, its two PATCH_COMMAND entries and this note once upstream +# fixes the offset. The script is idempotent, and it fails the configure step if +# any of the three anchors is missing, so a bump that changes or fixes this code +# is reported instead of silently producing an unpatched build. + +set(_clib_header "include/RE/S/SkyrimVM.h") +set(_clib_vm "src/RE/V/VirtualMachine.cpp") +set(_clib_skvm "src/RE/S/SkyrimVM.cpp") + +set(_clib_marker "GetImpl") + +set(_clib_accessor +" // AE 1.7.99 shifts every member from 0x180 onwards by +0x10, and impl is + // declared at its pre-1.7.99 offset. Read it through here, never directly. + [[nodiscard]] inline BSTSmartPointer& GetImpl() noexcept + { + // Only AE reaches 1.7.99, so the version test alone selects the runtime. + return REL::RelocateMemberIfNewer>( + SKSE::RUNTIME_SSE_1_7_99, this, 0x200, 0x210); + } + + [[nodiscard]] inline const BSTSmartPointer& GetImpl() const noexcept + { + return const_cast(this)->GetImpl(); + } + + static SkyrimVM* GetSingleton();") + +function(_clib_patch_file path from to) + if(NOT EXISTS "${path}") + message(FATAL_ERROR "CommonLibSSE patch: ${path} is missing.") + endif() + file(READ "${path}" _content) + string(FIND "${_content}" "${_clib_marker}" _found) + if(_found GREATER_EQUAL 0) + return() # already patched + endif() + string(FIND "${_content}" "${from}" _anchor) + if(_anchor LESS 0) + message(FATAL_ERROR + "CommonLibSSE patch: anchor not found in ${path}.\n" + "Check whether upstream fixed SkyrimVM::impl for AE 1.7.99. If it did, " + "delete cmake/patch_commonlibsse.cmake and its PATCH_COMMAND entries.") + endif() + string(REPLACE "${from}" "${to}" _content "${_content}") + file(WRITE "${path}" "${_content}") +endfunction() + +_clib_patch_file("${_clib_header}" " static SkyrimVM* GetSingleton();" "${_clib_accessor}") +_clib_patch_file("${_clib_vm}" "vm->impl.get()" "vm->GetImpl().get()") +_clib_patch_file("${_clib_skvm}" "impl.get()->SendEvent" "GetImpl().get()->SendEvent") + +message(STATUS "Patched CommonLibSSE-NG SkyrimVM::impl for AE 1.7.99.") diff --git a/source/Steam DLL/src/PapyrusFunctions.h b/source/Steam DLL/src/PapyrusFunctions.h index 4bc421a33..a63e8fcd5 100644 --- a/source/Steam DLL/src/PapyrusFunctions.h +++ b/source/Steam DLL/src/PapyrusFunctions.h @@ -15,7 +15,7 @@ namespace Papyrus::PapyrusFunctions } return steam->setAchievementUnlocked(achievement.c_str()); } else { - RE::DebugNotification(std::format("Achievement unlocked: {}", achievement.c_str()).c_str()); + RE::SendHUDMessage::ShowHUDMessage(std::format("Achievement unlocked: {}", achievement.c_str()).c_str()); logger::info("{}", std::format("Achievement unlocked: {}", achievement.c_str()).c_str()); return true; }