Add Enderal plugins to plugins.txt array in runtime
This commit is contained in:
parent
6917fd712c
commit
4379f11539
@ -13,6 +13,7 @@
|
||||
#include "Patches/HUDMenuPatch.h"
|
||||
#include "Patches/ForceBorderless.h"
|
||||
#include "Patches/AttachLightHitEffectCrash.h"
|
||||
#include "Patches/PluginsTxtPatch.h"
|
||||
|
||||
using namespace SKSE;
|
||||
|
||||
@ -178,7 +179,6 @@ SKSEPluginLoad(const LoadInterface* skse) {
|
||||
GetLoadInterface(skse);
|
||||
|
||||
InitializeLogging();
|
||||
EnsurePluginsTxt();
|
||||
|
||||
auto* plugin = PluginDeclaration::GetSingleton();
|
||||
auto version = plugin->GetVersion();
|
||||
@ -186,6 +186,7 @@ SKSEPluginLoad(const LoadInterface* skse) {
|
||||
|
||||
Init(skse);
|
||||
InitializeMessaging();
|
||||
PluginsTxtPatch::Install();
|
||||
|
||||
RE::INISettingCollection::GetSingleton()->GetSetting("sIntroSequence:General")->data.s = nullptr;
|
||||
|
||||
|
||||
92
source/Enderal DLL/src/Patches/PluginsTxtPatch.h
Normal file
92
source/Enderal DLL/src/Patches/PluginsTxtPatch.h
Normal file
@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
namespace PluginsTxtPatch
|
||||
{
|
||||
using PluginArray = RE::BSTArray<RE::BSFixedString>;
|
||||
|
||||
inline void PrependIfMissing(PluginArray* a_array, const char* a_name)
|
||||
{
|
||||
for (const auto& entry : *a_array) {
|
||||
if (_stricmp(entry.c_str(), a_name) == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
RE::BSFixedString newEntry(a_name);
|
||||
a_array->push_back(a_array->empty() ? newEntry : a_array->back());
|
||||
for (std::uint32_t i = a_array->size() - 1; i > 0; --i) {
|
||||
(*a_array)[i] = (*a_array)[i - 1];
|
||||
}
|
||||
(*a_array)[0] = newEntry;
|
||||
}
|
||||
|
||||
struct LoadPluginsList
|
||||
{
|
||||
static std::int64_t thunk(PluginArray* a_array, bool a_enabledOnly)
|
||||
{
|
||||
auto result = func(a_array, a_enabledOnly);
|
||||
if (!a_array) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const char* plugins[] = {
|
||||
"SkyUI_SE.esp",
|
||||
"Enderal - Forgotten Stories.esm" // Last = highest priority (index 0)
|
||||
};
|
||||
|
||||
for (const auto* plugin : plugins) {
|
||||
if (std::filesystem::exists(std::format("Data\\{}", plugin))) {
|
||||
PrependIfMissing(a_array, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
static inline REL::Relocation<decltype(thunk)> func;
|
||||
};
|
||||
|
||||
inline void Install()
|
||||
{
|
||||
// Hook at function entry to catch all callers
|
||||
// SE ID 13650: 1.5.97=0x16FEA0
|
||||
// AE ID 13758: 1.6.1170=0x1BB410, 1.6.1179=0x1BB240
|
||||
// VR: 0x180630 (not in address library, untested)
|
||||
// Prolog: 7 pushes (12 bytes) + lea rbp,[rsp+disp32] (8 bytes) = 20 bytes
|
||||
// if (REL::Module::IsVR()) {
|
||||
// constexpr std::size_t PROLOG_SIZE = 20;
|
||||
// auto target = REL::Module::get().base() + 0x180630;
|
||||
// // TODO: copy rest of Install() logic here when VR is tested
|
||||
// return;
|
||||
// }
|
||||
if (REL::Module::IsVR()) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr std::size_t PROLOG_SIZE = 20;
|
||||
auto target = REL::RelocationID(13650, 13758).address();
|
||||
|
||||
// Allocate trampoline near target (within rel32 range)
|
||||
void* mem = nullptr;
|
||||
for (std::uintptr_t off = 0x1000; off < 0x7FFF0000 && !mem; off += 0x1000) {
|
||||
mem = VirtualAlloc(reinterpret_cast<void*>(target - off), 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
if (!mem) mem = VirtualAlloc(reinterpret_cast<void*>(target + off), 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
}
|
||||
if (!mem) return;
|
||||
|
||||
// Trampoline: copy prolog + absolute jump back to original
|
||||
auto t = static_cast<std::uint8_t*>(mem);
|
||||
std::memcpy(t, reinterpret_cast<void*>(target), PROLOG_SIZE);
|
||||
struct JmpAbs : Xbyak::CodeGenerator {
|
||||
JmpAbs(std::uintptr_t dst, std::uint8_t* buf) : Xbyak::CodeGenerator(14, buf) { mov(rax, dst); jmp(rax); }
|
||||
};
|
||||
JmpAbs(target + PROLOG_SIZE, t + PROLOG_SIZE);
|
||||
LoadPluginsList::func = reinterpret_cast<std::uintptr_t>(t);
|
||||
|
||||
// Patch function entry: jump to thunk via stub
|
||||
auto stub = t + PROLOG_SIZE + 14;
|
||||
JmpAbs(reinterpret_cast<std::uintptr_t>(&LoadPluginsList::thunk), stub);
|
||||
REL::safe_write(target, static_cast<std::uint8_t>(0xE9));
|
||||
REL::safe_write(target + 1, static_cast<std::int32_t>(reinterpret_cast<std::uintptr_t>(stub) - target - 5));
|
||||
|
||||
logger::info("Installed PluginsTxt patch");
|
||||
}
|
||||
}
|
||||
@ -332,185 +332,3 @@ inline RE::BSFixedString StringToHex(RE::BSFixedString a_string)
|
||||
return sstream.str();
|
||||
}
|
||||
|
||||
inline std::filesystem::path GetPluginsTxtPath()
|
||||
{
|
||||
wchar_t* buffer{ nullptr };
|
||||
const auto result = ::SHGetKnownFolderPath(::FOLDERID_LocalAppData, ::KNOWN_FOLDER_FLAG::KF_FLAG_DEFAULT, nullptr, std::addressof(buffer));
|
||||
std::unique_ptr<wchar_t[], decltype(&::CoTaskMemFree)> knownPath(buffer, ::CoTaskMemFree);
|
||||
|
||||
if (!knownPath || result != S_OK) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::filesystem::path path = knownPath.get();
|
||||
|
||||
bool hasRedirector = GetLoadInterface()->GetPluginInfo("Skyrim Redirector") != nullptr;
|
||||
bool isGOG = GetModuleHandle(L"Galaxy64.dll") != NULL;
|
||||
|
||||
if (hasRedirector) {
|
||||
path /= isGOG ? "Enderal Special Edition GOG"sv : "Enderal Special Edition"sv;
|
||||
} else {
|
||||
path /= isGOG ? "Skyrim Special Edition GOG"sv : "Skyrim Special Edition"sv;
|
||||
}
|
||||
|
||||
path /= "plugins.txt"sv;
|
||||
return path;
|
||||
}
|
||||
|
||||
// Reads plugins.txt and returns the lines
|
||||
inline std::vector<std::string> ReadPluginsTxt(const std::filesystem::path& pluginsPath)
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
if (std::filesystem::exists(pluginsPath)) {
|
||||
std::ifstream inFile(pluginsPath);
|
||||
if (inFile.is_open()) {
|
||||
std::string line;
|
||||
while (std::getline(inFile, line)) {
|
||||
lines.push_back(line);
|
||||
}
|
||||
inFile.close();
|
||||
}
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
// Writes lines to plugins.txt
|
||||
inline bool WritePluginsTxt(const std::filesystem::path& pluginsPath, const std::vector<std::string>& lines)
|
||||
{
|
||||
std::filesystem::create_directories(pluginsPath.parent_path());
|
||||
std::ofstream outFile(pluginsPath);
|
||||
if (outFile.is_open()) {
|
||||
for (const auto& line : lines) {
|
||||
outFile << line << "\n";
|
||||
}
|
||||
outFile.close();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Gets the plugin name from a line (strips asterisk if present)
|
||||
inline std::string GetPluginNameFromLine(const std::string& line)
|
||||
{
|
||||
if (!line.empty() && line[0] == '*') {
|
||||
return line.substr(1);
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
// Checks if a line is a comment or empty
|
||||
inline bool IsCommentOrEmpty(const std::string& line)
|
||||
{
|
||||
return line.empty() || line[0] == '#';
|
||||
}
|
||||
|
||||
// Ensures a plugin is enabled in plugins.txt (adds asterisk if missing, adds to file if not present)
|
||||
// Only operates if the plugin file exists in Data folder
|
||||
// Returns true if plugins.txt was modified
|
||||
inline bool EnsurePluginEnabled(std::vector<std::string>& lines, const char* pluginName)
|
||||
{
|
||||
if (!DataFileExists(pluginName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string enabledEntry = std::string("*") + pluginName;
|
||||
|
||||
for (auto& line : lines) {
|
||||
if (IsCommentOrEmpty(line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string name = GetPluginNameFromLine(line);
|
||||
if (name == pluginName) {
|
||||
if (line[0] != '*') {
|
||||
logger::info("{} found but not enabled, enabling it", pluginName);
|
||||
line = enabledEntry;
|
||||
return true;
|
||||
}
|
||||
// Already enabled
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin not found in plugins.txt, add it at the end
|
||||
logger::info("{} not found in plugins.txt, adding it", pluginName);
|
||||
lines.push_back(enabledEntry);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Moves a plugin to the first position (after comments)
|
||||
// Returns true if plugins.txt was modified
|
||||
inline bool EnsurePluginFirst(std::vector<std::string>& lines, const char* pluginName)
|
||||
{
|
||||
int pluginIndex = -1;
|
||||
int firstPluginIndex = -1;
|
||||
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
if (IsCommentOrEmpty(lines[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (firstPluginIndex == -1) {
|
||||
firstPluginIndex = static_cast<int>(i);
|
||||
}
|
||||
|
||||
std::string name = GetPluginNameFromLine(lines[i]);
|
||||
if (name == pluginName) {
|
||||
pluginIndex = static_cast<int>(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pluginIndex == -1) {
|
||||
// Plugin not in list
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pluginIndex == firstPluginIndex) {
|
||||
// Already first
|
||||
return false;
|
||||
}
|
||||
|
||||
logger::info("{} found but not first plugin (at index {}, first plugin at {}), moving to first position",
|
||||
pluginName, pluginIndex, firstPluginIndex);
|
||||
|
||||
// Save the plugin line and remove it from current position
|
||||
std::string pluginLine = lines[pluginIndex];
|
||||
lines.erase(lines.begin() + pluginIndex);
|
||||
|
||||
// Insert at first plugin position
|
||||
lines.insert(lines.begin() + firstPluginIndex, pluginLine);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void EnsurePluginsTxt()
|
||||
{
|
||||
auto pluginsPath = GetPluginsTxtPath();
|
||||
if (pluginsPath.empty()) {
|
||||
logger::error("Failed to get plugins.txt path");
|
||||
return;
|
||||
}
|
||||
|
||||
logger::info("Checking plugins.txt at: {}", pluginsPath.string());
|
||||
|
||||
std::vector<std::string> lines = ReadPluginsTxt(pluginsPath);
|
||||
bool modified = false;
|
||||
|
||||
// Ensure required plugins are enabled
|
||||
modified |= EnsurePluginEnabled(lines, "Enderal - Forgotten Stories.esm");
|
||||
modified |= EnsurePluginEnabled(lines, "SkyUI_SE.esp");
|
||||
|
||||
// Ensure Enderal ESM is first
|
||||
modified |= EnsurePluginFirst(lines, "Enderal - Forgotten Stories.esm");
|
||||
|
||||
if (modified) {
|
||||
if (WritePluginsTxt(pluginsPath, lines)) {
|
||||
logger::info("Successfully updated plugins.txt");
|
||||
} else {
|
||||
logger::error("Failed to write plugins.txt");
|
||||
}
|
||||
} else {
|
||||
logger::info("plugins.txt is correctly configured");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user