Enforce loading Enderal - Forgotten Stories.ini

This commit is contained in:
Eddoursul 2026-01-22 00:15:12 +01:00
parent 7692789c38
commit 587076d219

View File

@ -1,22 +1,162 @@
#pragma once
#include <fstream>
#include <vector>
#include <string>
namespace PluginsTxtPatch
{
using PluginArray = RE::BSTArray<RE::BSFixedString>;
inline void PrependIfMissing(PluginArray* a_array, const char* a_name)
// Precached plugin lists (populated in Install)
inline std::vector<std::string> cachedPrependPlugins;
inline std::vector<std::string> cachedAppendPlugins;
inline std::vector<std::string> ReadCccFile()
{
for (const auto& entry : *a_array) {
if (_stricmp(entry.c_str(), a_name) == 0) {
return;
std::vector<std::string> plugins;
std::ifstream file("Skyrim.ccc");
if (!file.is_open()) {
return plugins;
}
std::string line;
while (std::getline(file, line)) {
// Trim whitespace
while (!line.empty() && (line.back() == '\r' || line.back() == '\n' || line.back() == ' ')) {
line.pop_back();
}
if (!line.empty()) {
plugins.push_back(line);
}
}
return plugins;
}
inline bool IsInCccList(const std::vector<std::string>& a_cccList, const char* a_name)
{
if (!a_name) {
return false;
}
for (const auto& entry : a_cccList) {
if (_stricmp(entry.c_str(), a_name) == 0) {
return true;
}
}
return false;
}
inline bool IsInList(PluginArray* a_array, const char* a_name)
{
if (!a_array || !a_name) {
return false;
}
for (const auto& entry : *a_array) {
if (_stricmp(entry.c_str(), a_name) == 0) {
return true;
}
}
return false;
}
inline void PrependIfMissing(PluginArray* a_array, const char* a_name)
{
if (!a_array || !a_name) {
return;
}
RE::BSFixedString newEntry(a_name);
// Check if already at first position
if (!a_array->empty() && _stricmp((*a_array)[0].c_str(), a_name) == 0) {
return;
}
// Find and remove from current position if present
std::int32_t oldPosition = -1;
for (std::uint32_t i = 0; i < a_array->size(); ++i) {
if (_stricmp((*a_array)[i].c_str(), a_name) == 0) {
oldPosition = static_cast<std::int32_t>(i);
a_array->erase(a_array->begin() + i);
break;
}
}
// Insert at first position
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;
if (oldPosition > 0) {
logger::info("PluginsTxt: Moved {} from position {} to position 0", a_name, oldPosition);
} else if (oldPosition < 0) {
logger::info("PluginsTxt: Added {} at position 0", a_name);
}
}
inline void AddIfMissing(PluginArray* a_array, const char* a_name)
{
if (!a_array || !a_name) {
return;
}
if (IsInList(a_array, a_name)) {
return;
}
a_array->push_back(RE::BSFixedString(a_name));
logger::info("PluginsTxt: Added {} at position {}", a_name, a_array->size() - 1);
}
inline void PrecachePluginLists()
{
try {
auto cccList = ReadCccFile();
// Plugins that must always be in plugins.txt (for INI loading)
const char* requiredPlugins[] = {
"Enderal - Forgotten Stories.esm",
"_ResourcePack.esl"
};
// CC plugins - only add if not in Skyrim.ccc
const char* ccPlugins[] = {
"ccBGSSSE025-AdvDSGS.esm",
"ccBGSSSE037-Curios.esl",
"ccQDRSSE001-SurvivalMode.esl",
"ccBGSSSE001-Fish.esm"
};
// Build prepend list in reverse order so first entry ends up at position 0
// First CC plugins (will be at higher positions)
for (const auto* plugin : ccPlugins) {
if (IsInCccList(cccList, plugin)) {
continue;
}
if (std::filesystem::exists(std::format("Data\\{}", plugin))) {
cachedPrependPlugins.push_back(plugin);
}
}
// Then required plugins (will be at position 0)
for (const auto* plugin : requiredPlugins) {
if (std::filesystem::exists(std::format("Data\\{}", plugin))) {
cachedPrependPlugins.push_back(plugin);
}
}
// Append plugins
if (!IsInCccList(cccList, "SkyUI_SE.esp") && std::filesystem::exists("Data\\SkyUI_SE.esp")) {
cachedAppendPlugins.push_back("SkyUI_SE.esp");
}
logger::info("PluginsTxt: Precached {} prepend and {} append plugins",
cachedPrependPlugins.size(), cachedAppendPlugins.size());
} catch (const std::exception& e) {
logger::error("PluginsTxt: Exception during precaching: {}", e.what());
} catch (...) {
logger::error("PluginsTxt: Unknown exception during precaching");
}
}
struct LoadPluginsList
@ -28,15 +168,13 @@ namespace PluginsTxtPatch
return result;
}
const char* plugins[] = {
"SkyUI_SE.esp",
"Enderal - Forgotten Stories.esm" // Last = highest priority (index 0)
};
// Use precached lists (populated in Install)
for (const auto& plugin : cachedPrependPlugins) {
PrependIfMissing(a_array, plugin.c_str());
}
for (const auto* plugin : plugins) {
if (std::filesystem::exists(std::format("Data\\{}", plugin))) {
PrependIfMissing(a_array, plugin);
}
for (const auto& plugin : cachedAppendPlugins) {
AddIfMissing(a_array, plugin.c_str());
}
return result;
@ -61,16 +199,26 @@ namespace PluginsTxtPatch
return;
}
PrecachePluginLists();
constexpr std::size_t PROLOG_SIZE = 20;
auto target = REL::RelocationID(13650, 13758).address();
if (!target) {
logger::error("PluginsTxt: Failed to resolve target address");
return;
}
// 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;
if (!mem) {
logger::error("PluginsTxt: Failed to allocate trampoline memory");
return;
}
// Trampoline: copy prolog + absolute jump back to original
auto t = static_cast<std::uint8_t*>(mem);