Compare commits
11 Commits
2.1.4.3
...
developmen
| Author | SHA1 | Date | |
|---|---|---|---|
| 11a9cf118a | |||
| baab99c7ae | |||
| 66ec4c998b | |||
| 5158e48628 | |||
| a7a8ce18db | |||
| 505c79c348 | |||
| 74dbcd14c1 | |||
| bf6495d9c9 | |||
| f62502c61a | |||
| cd5820e029 | |||
| 42865d8d62 |
Binary file not shown.
@ -6,6 +6,12 @@ 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.
|
||||
@ -10,3 +10,4 @@ AutoScaleHeroMenu = true
|
||||
WarnFormTypeCollisions = true
|
||||
MenuAspectRatioFix = true
|
||||
LoadScreenFrameFix = true
|
||||
TextureDoubleReleaseFix = true
|
||||
|
||||
@ -1 +1 @@
|
||||
version = 2.1.4.3
|
||||
version = 2.1.4.4
|
||||
|
||||
BIN
Skyrim.esm
BIN
Skyrim.esm
Binary file not shown.
@ -9,8 +9,7 @@ Interface\statssheetmenu.swf
|
||||
Interface\loadingmenu.swf
|
||||
Interface\startmenu.swf
|
||||
Interface\translate_*.txt
|
||||
Interface\translations\skyui_se_chinese.txt
|
||||
Interface\translations\skyui_se_japanese.txt
|
||||
Interface\translations\skyui_se_*.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)
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.
Binary file not shown.
Binary file not shown.
BIN
interface/translations/skyui_se_english.txt
Normal file
BIN
interface/translations/skyui_se_english.txt
Normal file
Binary file not shown.
BIN
interface/translations/skyui_se_french.txt
Normal file
BIN
interface/translations/skyui_se_french.txt
Normal file
Binary file not shown.
BIN
interface/translations/skyui_se_german.txt
Normal file
BIN
interface/translations/skyui_se_german.txt
Normal file
Binary file not shown.
BIN
interface/translations/skyui_se_italian.txt
Normal file
BIN
interface/translations/skyui_se_italian.txt
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/translations/skyui_se_polish.txt
Normal file
BIN
interface/translations/skyui_se_polish.txt
Normal file
Binary file not shown.
BIN
interface/translations/skyui_se_russian.txt
Normal file
BIN
interface/translations/skyui_se_russian.txt
Normal file
Binary file not shown.
BIN
interface/translations/skyui_se_spanish.txt
Normal file
BIN
interface/translations/skyui_se_spanish.txt
Normal file
Binary file not shown.
@ -35,10 +35,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# Link-time optimization for release builds only (debug builds stay fast).
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
|
||||
|
||||
# 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")
|
||||
# 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>")
|
||||
|
||||
add_compile_definitions(NOMINMAX _USE_MATH_DEFINES WIN32_LEAN_AND_MEAN)
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "Patches/FormTypeCollisionDetector.h"
|
||||
#include "Patches/ForceLanguage.h"
|
||||
#include "Patches/MenuAspectRatioFix.h"
|
||||
#include "Patches/TextureDoubleReleaseFix.h"
|
||||
|
||||
using namespace SKSE;
|
||||
|
||||
@ -32,7 +33,8 @@ static std::map<std::string, bool> g_settings{
|
||||
{ "AttachLightHitEffectCrashFix", true },
|
||||
{ "AutoScaleHeroMenu", true },
|
||||
{ "MenuAspectRatioFix", true },
|
||||
{ "LoadScreenFrameFix", true }
|
||||
{ "LoadScreenFrameFix", true },
|
||||
{ "TextureDoubleReleaseFix", true }
|
||||
};
|
||||
|
||||
namespace {
|
||||
@ -258,6 +260,9 @@ 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());
|
||||
|
||||
@ -154,10 +154,16 @@ 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(0);
|
||||
args3[1].SetNumber(0);
|
||||
args3[2].SetNumber(0);
|
||||
args3[0].SetNumber(statModifier(RE::ActorValue::kHealth));
|
||||
args3[1].SetNumber(statModifier(RE::ActorValue::kMagicka));
|
||||
args3[2].SetNumber(statModifier(RE::ActorValue::kStamina));
|
||||
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));
|
||||
|
||||
63
source/Enderal DLL/src/Patches/TextureDoubleReleaseFix.h
Normal file
63
source/Enderal DLL/src/Patches/TextureDoubleReleaseFix.h
Normal file
@ -0,0 +1,63 @@
|
||||
#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...");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -258,38 +258,9 @@ Func UpdateModIniButtonState()
|
||||
EndFunc
|
||||
|
||||
Func RunWithAssociation($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)
|
||||
; The shell resolves the association and expands REG_EXPAND_SZ paths itself
|
||||
ShellExecute($filePath)
|
||||
If @error Then Run('notepad.exe "' & $filePath & '"')
|
||||
Return True
|
||||
EndFunc
|
||||
|
||||
|
||||
@ -36,10 +36,12 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
# Link-time optimization for release builds only (debug builds stay fast).
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
|
||||
|
||||
# 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")
|
||||
# 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>")
|
||||
|
||||
add_compile_definitions(NOMINMAX _USE_MATH_DEFINES WIN32_LEAN_AND_MEAN)
|
||||
add_definitions(-DUNICODE -D_UNICODE)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user