Compare commits

..

No commits in common. "development" and "2.1.4.3" have entirely different histories.

35 changed files with 49 additions and 104 deletions

Binary file not shown.

View File

@ -6,12 +6,6 @@ See https://en.wiki.sureai.net/Enderal:Patch for the list of patches up to versi
Beware, spoilers ahead!
2.1.4.4 (2026-09-18)
- Fixed the difficulty switcher missing in SSE 1.7.
- Fixed a crash caused by a regression in SSE 1.6.1179+ (double texture release).
- Yet another alchemy system revision (fAlchemySkillFactor=4.5, fAlchemyIngredientInitMult=1.0).
2.1.4.3 (2026-08-31)
- Added compatibility with Skyrim SE 1.7.104.
- Added Czech localization by Dogzey.

View File

@ -10,4 +10,3 @@ AutoScaleHeroMenu = true
WarnFormTypeCollisions = true
MenuAspectRatioFix = true
LoadScreenFrameFix = true
TextureDoubleReleaseFix = true

View File

@ -1 +1 @@
version = 2.1.4.4
version = 2.1.4.3

Binary file not shown.

View File

@ -9,7 +9,8 @@ Interface\statssheetmenu.swf
Interface\loadingmenu.swf
Interface\startmenu.swf
Interface\translate_*.txt
Interface\translations\skyui_se_*.txt
Interface\translations\skyui_se_chinese.txt
Interface\translations\skyui_se_japanese.txt
meshes\terrain\Vyn\vyn.32.-5.9.btr
Scripts\Game.pex
Scripts\SKI_FavoritesManager.pex

BIN
interface/quest_journal.swf (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -35,12 +35,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Link-time optimization for release builds only (debug builds stay fast).
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
# Static CRT: the plugin must not depend on the user's VC Redist version. The MSVC STL
# requires msvcp140.dll to be at least as new as the toolset that built the plugin, so a
# /MD build crashes on load against an outdated redistributable. All dependencies build
# from source under this setting; CommonLibSSE-GG's prebuilt bundle is /MD only and is
# skipped automatically.
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
# CommonLibSSE-NG requires the dynamic CRT. Dependencies used to be supplied by
# vcpkg (which set this via a preset); now that they come from FetchContent we
# set it here so both build.cmd and Visual Studio "Open Folder" pick it up.
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
add_compile_definitions(NOMINMAX _USE_MATH_DEFINES WIN32_LEAN_AND_MEAN)
add_definitions(-DUNICODE -D_UNICODE)

View File

@ -17,7 +17,6 @@
#include "Patches/FormTypeCollisionDetector.h"
#include "Patches/ForceLanguage.h"
#include "Patches/MenuAspectRatioFix.h"
#include "Patches/TextureDoubleReleaseFix.h"
using namespace SKSE;
@ -33,8 +32,7 @@ static std::map<std::string, bool> g_settings{
{ "AttachLightHitEffectCrashFix", true },
{ "AutoScaleHeroMenu", true },
{ "MenuAspectRatioFix", true },
{ "LoadScreenFrameFix", true },
{ "TextureDoubleReleaseFix", true }
{ "LoadScreenFrameFix", true }
};
namespace {
@ -260,9 +258,6 @@ SKSEPluginLoad(const LoadInterface* skse) {
MenuAspectRatioFix::Install();
}
LoadScreenFrameFix::Install(g_settings.at("LoadScreenFrameFix"));
if (g_settings.at("TextureDoubleReleaseFix")) {
TextureDoubleReleaseFix::Install();
}
}
logger::info("{} has finished loading.", plugin->GetName());

View File

@ -154,16 +154,10 @@ void HeroMenuPatch::FillMenuValues()
args2[32].SetNumber(fEXPNeededForNextLevel - fEXPNeededForCurrentLevel);
uiMovie->Invoke("heromenu_mc.SetIntValues", nullptr, args2, 33);
// The movie paints a stat green or red when its modifier is non-zero and shows the modifier
// next to the base value. Base plus permanent plus temporary modifiers is the boosted maximum.
auto statModifier = [&](RE::ActorValue av) {
return std::round(player->GetActorValueMax(av) - playerAV->GetBaseActorValue(av));
};
RE::GFxValue args3[21];
args3[0].SetNumber(statModifier(RE::ActorValue::kHealth));
args3[1].SetNumber(statModifier(RE::ActorValue::kMagicka));
args3[2].SetNumber(statModifier(RE::ActorValue::kStamina));
args3[0].SetNumber(0);
args3[1].SetNumber(0);
args3[2].SetNumber(0);
args3[3].SetNumber(playerAV->GetActorValue(RE::ActorValue::kIllusion) - playerAV->GetBaseActorValue(RE::ActorValue::kIllusion));
args3[4].SetNumber(playerAV->GetActorValue(RE::ActorValue::kDestruction) - playerAV->GetBaseActorValue(RE::ActorValue::kDestruction));
args3[5].SetNumber(playerAV->GetActorValue(RE::ActorValue::kAlteration) - playerAV->GetBaseActorValue(RE::ActorValue::kAlteration));

View File

@ -1,63 +0,0 @@
#pragma once
// From 1.6.1179 on, the renderer function that frees a BSGraphics::Texture (AE id 77322) calls
// Release() twice on each of its three D3D11 objects: once in the original sequence, then again
// in what looks like an inlined destructor. The texture loader takes one reference per object,
// so the second batch underflows. D3D11 usually defers the destruction of an object that a
// pending command list still references, which is why the second call normally lands on live
// memory. When the runtime frees the object at once, the game crashes in this function with
// `call [rax+0x10]` on a zeroed vtable. The patch jumps over the second batch.
namespace TextureDoubleReleaseFix
{
// Everything before the deallocation call. Only rel8 branches inside, so these bytes are
// identical on 1.6.1179, 1.7.99 and 1.7.104.
constexpr std::uint8_t DOUBLE_RELEASE_FORM[]{
0x40, 0x53, // push rbx
0x48, 0x83, 0xEC, 0x30, // sub rsp,0x30
0x48, 0xC7, 0x44, 0x24, 0x20, 0xFE, 0xFF, 0xFF, 0xFF, // mov [rsp+0x20],-2
0x48, 0x8B, 0xDA, // mov rbx,rdx
0xB8, 0xFF, 0xFF, 0xFF, 0xFF, // mov eax,-1
0xF0, 0x0F, 0xC1, 0x42, 0x20, // lock xadd [rdx+0x20],eax
0x83, 0xF8, 0x01, // cmp eax,1
0x75, 0x6C, // jne +0x8D (return)
0x48, 0x8B, 0x4A, 0x10, 0x48, 0x85, 0xC9, 0x74, 0x06, 0x48, 0x8B, 0x01, 0xFF, 0x50, 0x10, // Release [rdx+0x10]
0x48, 0x8B, 0x0B, 0x48, 0x85, 0xC9, 0x74, 0x06, 0x48, 0x8B, 0x01, 0xFF, 0x50, 0x10, // Release [rbx]
0x48, 0x8B, 0x4B, 0x08, 0x48, 0x85, 0xC9, 0x74, 0x07, 0x48, 0x8B, 0x01, 0xFF, 0x50, 0x10, // Release [rbx+0x08]
0x90, // nop
0x48, 0x8B, 0x0B, 0x48, 0x85, 0xC9, 0x74, 0x06, 0x48, 0x8B, 0x01, 0xFF, 0x50, 0x10, // +0x4E: Release [rbx] again
0x48, 0x8B, 0x4B, 0x08, 0x48, 0x85, 0xC9, 0x74, 0x06, 0x48, 0x8B, 0x01, 0xFF, 0x50, 0x10, // Release [rbx+0x08] again
0x48, 0x8B, 0x4B, 0x10, 0x48, 0x85, 0xC9, 0x74, 0x07, 0x48, 0x8B, 0x01, 0xFF, 0x50, 0x10, // Release [rbx+0x10] again
0x90 // nop, +0x7B follows: mov edx,0x28
};
// 1.6.1170 and older release each object once. Recognized so the log can say so.
constexpr std::uint8_t SINGLE_RELEASE_PROLOGUE[]{ 0x40, 0x53, 0x48, 0x83, 0xEC, 0x20, 0x48, 0x8B, 0xDA };
constexpr std::size_t SECOND_BATCH = 0x4E;
constexpr std::size_t DEALLOCATE = 0x7B;
void Install()
{
if (!REL::Module::IsAE()) {
return;
}
const auto target = REL::Relocation<std::uintptr_t>{ REL::ID(77322) }.address();
const auto bytes = reinterpret_cast<const std::uint8_t*>(target);
// jmp short +0x2B, from +0x50 to +0x7B
const std::uint8_t code[]{ 0xEB, static_cast<std::uint8_t>(DEALLOCATE - SECOND_BATCH - 2) };
if (std::memcmp(bytes, DOUBLE_RELEASE_FORM, sizeof(DOUBLE_RELEASE_FORM)) == 0) {
logger::info("Patching the double texture release...");
REL::safe_write(target + SECOND_BATCH, code, sizeof(code));
} else if (std::memcmp(bytes, DOUBLE_RELEASE_FORM, SECOND_BATCH) == 0 && std::memcmp(bytes + SECOND_BATCH, code, sizeof(code)) == 0) {
// Another plugin wrote the same jump first. Nothing left to do.
logger::info("Double texture release already patched by another plugin");
} else if (std::memcmp(bytes, SINGLE_RELEASE_PROLOGUE, sizeof(SINGLE_RELEASE_PROLOGUE)) == 0) {
logger::info("Texture release function releases once, no patch needed");
} else {
logger::error("Unexpected bytes in the texture release function, skipping the double release patch...");
}
}
}

View File

@ -258,9 +258,38 @@ Func UpdateModIniButtonState()
EndFunc
Func RunWithAssociation($filePath)
; The shell resolves the association and expands REG_EXPAND_SZ paths itself
ShellExecute($filePath)
If @error Then Run('notepad.exe "' & $filePath & '"')
; Get file extension
Local $ext = StringRegExpReplace($filePath, ".*(\.[^.]+)$", "$1")
If $ext = $filePath Then Return False
; Get file type from registry
Local $fileType = RegRead("HKEY_CLASSES_ROOT\" & $ext, "")
If @error Or $fileType = "" Then
; Fallback to notepad if no association found
Run('notepad.exe "' & $filePath & '"')
Return True
EndIf
; Get command for opening this file type
Local $command = RegRead("HKEY_CLASSES_ROOT\" & $fileType & "\shell\open\command", "")
If @error Or $command = "" Then
; Fallback to notepad if no command found
Run('notepad.exe "' & $filePath & '"')
Return True
EndIf
; Replace %1 or %L with the file path, handle quoted and unquoted variants
If StringInStr($command, "%1") Or StringInStr($command, "%L") Then
$command = StringReplace($command, '"%1"', '"' & $filePath & '"')
$command = StringReplace($command, "%1", '"' & $filePath & '"')
$command = StringReplace($command, '"%L"', '"' & $filePath & '"')
$command = StringReplace($command, "%L", '"' & $filePath & '"')
Else
; Append file path if no placeholder
$command = $command & ' "' & $filePath & '"'
EndIf
Run($command)
Return True
EndFunc

View File

@ -36,12 +36,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Link-time optimization for release builds only (debug builds stay fast).
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
# Static CRT: the plugin must not depend on the user's VC Redist version. The MSVC STL
# requires msvcp140.dll to be at least as new as the toolset that built the plugin, so a
# /MD build crashes on load against an outdated redistributable. All dependencies build
# from source under this setting; CommonLibSSE-GG's prebuilt bundle is /MD only and is
# skipped automatically.
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
# CommonLibSSE-NG requires the dynamic CRT. Dependencies used to be supplied by
# vcpkg (which set this via a preset); now that they come from FetchContent we
# set it here so both build.cmd and Visual Studio "Open Folder" pick it up.
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
add_compile_definitions(NOMINMAX _USE_MATH_DEFINES WIN32_LEAN_AND_MEAN)
add_definitions(-DUNICODE -D_UNICODE)