diff --git a/source/Enderal DLL/src/Patches/PluginsTxtPatch.h b/source/Enderal DLL/src/Patches/PluginsTxtPatch.h index 0af5305ec..9c67cc143 100644 --- a/source/Enderal DLL/src/Patches/PluginsTxtPatch.h +++ b/source/Enderal DLL/src/Patches/PluginsTxtPatch.h @@ -1,22 +1,162 @@ #pragma once +#include +#include +#include + namespace PluginsTxtPatch { using PluginArray = RE::BSTArray; - inline void PrependIfMissing(PluginArray* a_array, const char* a_name) + // Precached plugin lists (populated in Install) + inline std::vector cachedPrependPlugins; + inline std::vector cachedAppendPlugins; + + inline std::vector ReadCccFile() { - for (const auto& entry : *a_array) { - if (_stricmp(entry.c_str(), a_name) == 0) { - return; + std::vector 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& 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(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(target - off), 64, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (!mem) mem = VirtualAlloc(reinterpret_cast(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(mem);