Force-enable "Enderal - Forgotten Stories.esm" with EnderalSE.dll

This commit is contained in:
Eddoursul 2025-12-20 03:01:15 +01:00
parent 0769eeba5c
commit e19b182ac1
3 changed files with 196 additions and 6 deletions

View File

@ -6,7 +6,13 @@ static bool bMessageShown = false;
static std::unordered_set<std::string> aModNames;
inline bool DataFileExists(std::string filename, int maxSize = 1000000)
inline bool DataFileExists(std::string filename)
{
const auto path = std::format("Data\\{}", filename);
return std::filesystem::exists(path);
}
inline bool DLCExists(std::string filename, int maxSize = 1000000)
{
const auto path = std::format("Data\\{}", filename);
return std::filesystem::exists(path) && std::filesystem::file_size(path) > maxSize;
@ -745,13 +751,13 @@ inline void CheckCCMods()
};
for (short i = 0; i < 73; i++) {
if (DataFileExists(filenames[i], 800)) {
if (DLCExists(filenames[i], 800)) {
MessageBoxW(NULL, L"Creation Club mods are incompatible with Enderal.", L"Error", MB_OK | MB_ICONERROR);
exit(EXIT_FAILURE);
}
}
if (DataFileExists("ccBGSSSE001-Fish.esm", 1200000)) {
if (DLCExists("ccBGSSSE001-Fish.esm", 1200000)) {
MessageBoxW(NULL, L"Fishing CC are incompatible with Enderal without a patch.", L"Error", MB_OK | MB_ICONERROR);
exit(EXIT_FAILURE);
}

View File

@ -158,17 +158,17 @@ SKSEPluginLoad(const LoadInterface* skse) {
}
}
if (DataFileExists("Dawnguard.esm") || DataFileExists("Dragonborn.esm") || DataFileExists("HearthFires.esm") || DataFileExists("Update.esm")) {
if (DLCExists("Dawnguard.esm") || DLCExists("Dragonborn.esm") || DLCExists("HearthFires.esm") || DLCExists("Update.esm")) {
MessageBoxW(NULL, L"Skyrim DLCs are incompatible with Enderal.", L"Enderal SE Error", MB_OK | MB_ICONERROR);
exit(EXIT_FAILURE);
}
if (DataFileExists("Unofficial Skyrim Special Edition Patch.esp")) {
if (DLCExists("Unofficial Skyrim Special Edition Patch.esp")) {
MessageBoxW(NULL, L"Unofficial Skyrim Special Edition Patch is incompatible with Enderal.", L"Enderal SE Error", MB_OK | MB_ICONERROR);
exit(EXIT_FAILURE);
}
if (!DataFileExists("Enderal - Forgotten Stories.esm")) {
if (!DLCExists("Enderal - Forgotten Stories.esm")) {
MessageBoxW(NULL, L"Enderal - Forgotten Stories.esm is not loaded!", L"Enderal SE Error", MB_OK | MB_ICONERROR);
exit(EXIT_FAILURE);
}
@ -178,6 +178,7 @@ SKSEPluginLoad(const LoadInterface* skse) {
GetLoadInterface(skse);
InitializeLogging();
EnsurePluginsTxt();
auto* plugin = PluginDeclaration::GetSingleton();
auto version = plugin->GetVersion();

View File

@ -331,3 +331,186 @@ 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");
}
}