Updated DLL for compatibility with SSE 1.7.99

This commit is contained in:
Eddoursul 2026-08-22 14:19:01 +02:00
parent 579239e4f4
commit 3939ba1daa
17 changed files with 209 additions and 27 deletions

BIN
SKSE/Plugins/versionlib-1-7-99-0.bin (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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)

View File

@ -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 <n>!" 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<BSScript::IVirtualMachine>& GetImpl() noexcept
{
// Only AE reaches 1.7.99, so the version test alone selects the runtime.
return REL::RelocateMemberIfNewer<BSTSmartPointer<BSScript::IVirtualMachine>>(
SKSE::RUNTIME_SSE_1_7_99, this, 0x200, 0x210);
}
[[nodiscard]] inline const BSTSmartPointer<BSScript::IVirtualMachine>& GetImpl() const noexcept
{
return const_cast<SkyrimVM*>(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.")

View File

@ -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

View File

@ -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<std::uintptr_t> target{ REL::RelocationID(87890, 90259), REL::Relocate(0x22, 0x21) };
SKSE::AllocTrampoline(14);
BinkListener::func = SKSE::GetTrampoline().write_call<5>(target.address(), BinkListener::thunk);

View File

@ -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_t> func{ REL::VariantID(52428, 53327, 0x92F0F0) };
func(a_bool);
}

View File

@ -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<std::uintptr_t> offset{ REL::RelocationID(52224, 53111), REL::Relocate(0x22F, 0x21F) };
SKSE::AllocTrampoline(14);
SKSE::GetTrampoline().write_call<5>(offset.address(), &Hook_WorldPtToScreenPt3);

View File

@ -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<std::uintptr_t> 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<std::uintptr_t>{ 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<std::uintptr_t> 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<std::uintptr_t>{ 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<const void*>(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));
}
}

View File

@ -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<ID3D11UnorderedAccessView*>(a_texture->unk08)->Release();
if (a_texture->UAV) {
a_texture->UAV->Release();
}
REL::Relocation<void(void*, int)> NiMemFree{ REL::RelocationID(102158, 109588) };

View File

@ -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<std::uintptr_t> target{ REL::RelocationID(51400, 52249), REL::Relocate(0x41E, 0x421) };
REL::safe_fill(target.address(), REL::NOP, 45);

View File

@ -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<std::uintptr_t> 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<std::uintptr_t> 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));

View File

@ -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

View File

@ -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_t> func{ REL::VariantID(51845, 52718, 0x8FE7E0) };
return func(tweenMenu, index);
}

View File

@ -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_t> func{ REL::VariantID(51839, 52711, 0x8FE180) };
func();
}

View File

@ -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)

View File

@ -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 <n>!" 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<BSScript::IVirtualMachine>& GetImpl() noexcept
{
// Only AE reaches 1.7.99, so the version test alone selects the runtime.
return REL::RelocateMemberIfNewer<BSTSmartPointer<BSScript::IVirtualMachine>>(
SKSE::RUNTIME_SSE_1_7_99, this, 0x200, 0x210);
}
[[nodiscard]] inline const BSTSmartPointer<BSScript::IVirtualMachine>& GetImpl() const noexcept
{
return const_cast<SkyrimVM*>(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.")

View File

@ -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;
}