diff --git a/source/Enderal DLL/src/CheckInvalidForms.h b/source/Enderal DLL/src/CheckInvalidForms.h index b45bdb6a8..a2d28fb1d 100644 --- a/source/Enderal DLL/src/CheckInvalidForms.h +++ b/source/Enderal DLL/src/CheckInvalidForms.h @@ -63,615 +63,6 @@ inline void CheckWorldspaces() } } -inline void CheckSkyrimCells() -{ - // FormIDs of all Skyrim cells. - // 9 of them are commented out, because they are cells in Enderal as well. - - uint32_t formids[574] = { - 0x0004B8FB, - 0x000352C7, - 0x000161E7, - 0x00013A73, - 0x000BA599, - 0x00042BED, - 0x000361F9, - 0x00016A11, - 0x000152A1, - 0x0001523D, - 0x000A8B23, - 0x000918D3, - 0x0004761B, - //0x0003CBCB, - 0x00016A07, - 0x00015233, - 0x000138CF, - 0x0009CCDD, - 0x0008B84D, - 0x0003EA9D, - 0x000198DD, - 0x000165B1, - 0x00015229, - 0x000D9DC7, - 0x000965B3, - 0x0006F3EB, - 0x000466CB, - 0x000165A7, - 0x00015283, - 0x000139E7, - 0x000EF325, - 0x000AD5A1, - 0x00016BDD, - 0x00015279, - 0x00015215, - 0x00013979, - 0x0007BEF7, - 0x0004E227, - 0x000240B7, - 0x00016BD3, - 0x00016787, - 0x0001526F, - 0x0005015D, - 0x0002E521, - 0x0001677D, - 0x00016205, - 0x00015265, - 0x00015201, - 0x00013901, - 0x0007BC27, - 0x0002D3E7, - 0x0001B147, - 0x0001A337, - 0x000196B7, - 0x000161FB, - 0x00013A87, - 0x00033779, - 0x0002D5D1, - 0x000161F1, - 0x00015251, - 0x00013A7D, - //0x00030442, - 0x000152AA, - 0x00013A72, - 0x000571B4, - 0x00042BEC, - 0x00037EE0, - 0x00016A10, - 0x000152A0, - 0x0001523C, - 0x00013A68, - 0x00013810, - 0x0001DB4E, - 0x00016A06, - 0x00015296, - 0x000138CE, - 0x0001528C, - 0x000371DE, - 0x00036A72, - 0x000165A6, - //0x00015282, - 0x00013856, - 0x000EF324, - 0x000EB440, - 0x0009B238, - 0x00081DEC, - 0x0007144C, - 0x0005DE24, - 0x000597D4, - 0x00016BDC, - 0x00013978, - 0x00065AB6, - 0x0001D09A, - 0x00016BD2, - 0x00016786, - 0x0001526E, - 0x0001520A, - 0x000139D2, - 0x000F39C4, - 0x000A9194, - 0x0005015C, - 0x00035604, - 0x00033D68, - 0x0001677C, - 0x00016204, - 0x00015200, - 0x000B6BE6, - 0x000A95D6, - 0x0009AA4A, - 0x0006C3B6, - 0x000152BE, - 0x000151F6, - 0x00013A86, - 0x0001382E, - 0x0001AB60, - 0x00019454, - 0x000161F0, - 0x000152B4, - 0x00013A7C, - 0x000F7631, - 0x00053081, - 0x000529DD, - 0x0003538D, - 0x00013A71, - 0x000138E1, - 0x001037E7, - 0x0005C483, - 0x00047817, - 0x00022F53, - 0x00016DF7, - 0x00016A0F, - 0x0001529F, - 0x00013A67, - 0x0001380F, - 0x00016A05, - 0x00015295, - 0x00015231, - 0x00013A5D, - 0x000138CD, - 0x0005B91B, - 0x000165AF, - 0x00015227, - 0x0001321F, - 0x0005D01D, - 0x000371DD, - 0x00025195, - 0x00016EA1, - 0x00015281, - 0x0007C98B, - 0x00052FEB, - 0x00028EDF, - 0x000243DF, - 0x0001F7B3, - 0x00016BDB, - 0x00015277, - 0x00065AB5, - 0x0005B5DD, - 0x0003B87D, - 0x0002D525, - 0x0002C779, - 0x00016BD1, - 0x00016785, - 0x0001526D, - 0x000139D1, - 0x00013909, - //0x00000025, - 0x000AE783, - 0x000A0877, - 0x0006CCBB, - 0x000317E7, - 0x00016203, - 0x000A87C5, - 0x000A0359, - 0x000152BD, - 0x000151F5, - 0x00013A85, - 0x0001382D, - 0x00028ACF, - 0x0001AB5F, - 0x000161EF, - 0x00013A7B, - 0x000547F0, - 0x00021594, - 0x000213A0, - 0x00016E00, - 0x000152A8, - 0x00013818, - 0x00022F52, - 0x00016DF6, - //0x00016A0E, - 0x00013A66, - 0x0001380E, - 0x000B64B4, - 0x00056E88, - 0x0001B7C0, - 0x00016A04, - 0x00015294, - 0x00015230, - 0x00013A5C, - 0x000138CC, - 0x00080C6A, - 0x0005A01A, - 0x0002031A, - 0x0001E11E, - 0x000165AE, - 0x0001528A, - 0x00015226, - 0x00075270, - 0x0003A184, - 0x00034B94, - 0x00015280, - 0x0001521C, - 0x00013A48, - //0x0010CEEA, - 0x00016BDA, - 0x00015276, - 0x000DB070, - 0x0003DD34, - 0x0002D524, - 0x0002C778, - 0x0001CB84, - 0x00016BD0, - 0x00016784, - 0x0001526C, - 0x00015208, - 0x0001677A, - 0x00015262, - 0x00069858, - 0x00015258, - 0x000151F4, - 0x00013A84, - 0x000B29D6, - 0x000A6D8E, - 0x0008CDEE, - 0x00054D0E, - 0x000527F2, - 0x00027552, - 0x000193EE, - 0x000161EE, - 0x0001605E, - 0x00013A7A, - 0x000C9DAB, - 0x000A1793, - 0x00094BAB, - //0x00030313, - 0x00016DFF, - 0x000152A7, - 0x0007FCDD, - 0x000414DD, - 0x000399D1, - 0x00016DF5, - 0x00016A0D, - 0x0001529D, - 0x00015239, - 0x00013871, - 0x000F2CAB, - 0x000A013B, - 0x0005A857, - 0x0001AEC3, - 0x00016A03, - 0x000165B7, - 0x0001522F, - 0x000138CB, - 0x00013867, - 0x000165AD, - 0x00056C1B, - //0x000446BF, - 0x0004401B, - 0x0003A183, - 0x000263EF, - 0x000165A3, - 0x0001527F, - 0x0001521B, - 0x000AB531, - 0x00083559, - 0x0005912D, - 0x00016BD9, - 0x00015275, - 0x00015211, - 0x0003D62B, - 0x00016BCF, - 0x0001620B, - 0x0001526B, - 0x00015207, - 0x00013907, - 0x000A1BFD, - 0x00052CB5, - 0x00034B75, - 0x00016779, - 0x00015261, - 0x000EA28B, - 0x000BBB2F, - 0x000B1783, - 0x00079F9F, - 0x00039F67, - 0x0002BCD7, - 0x000270AB, - 0x000161F7, - 0x000152BB, - 0x00015257, - 0x000151F3, - 0x00013A83, - 0x000EEC55, - 0x000B29D5, - 0x000161ED, - 0x000152B1, - 0x00013A79, - 0x00016DFE, - 0x000133CA, - 0x0006DAA0, - 0x00052390, - 0x00016DF4, - 0x00016A0C, - 0x0001529C, - 0x00015238, - 0x00013870, - //0x000B9BC6, - 0x0005A856, - 0x0002471A, - 0x000198E2, - 0x00016A02, - 0x000165B6, - 0x000138CA, - 0x00059398, - 0x0003E908, - 0x000165AC, - 0x00015288, - 0x00015224, - 0x000CAB92, - 0x0005F536, - 0x0002A976, - 0x00016D72, - 0x0001527E, - 0x000C7348, - 0x000AA20C, - 0x000A73F4, - 0x00095C44, - 0x0006E2AC, - 0x0005D010, - 0x00018BE0, - 0x00016BD8, - 0x0001678C, - 0x00015274, - 0x00015210, - 0x000C0BBA, - 0x000280C2, - 0x00016BCE, - 0x00016782, - 0x0001526A, - 0x00015206, - 0x000F5EDC, - 0x000ED8E0, - 0x0007DCFC, - 0x00018460, - 0x00016778, - 0x00016200, - 0x00015260, - 0x000151FC, - 0x00013A8C, - 0x0005554A, - 0x00054226, - 0x0003D35A, - 0x00037B76, - 0x000161F6, - 0x000152BA, - 0x000151F2, - 0x000B29D4, - 0x0003FF10, - 0x0002E760, - 0x000161EC, - 0x000152B0, - 0x00013A78, - 0x000ABD31, - 0x00055215, - 0x0002954D, - 0x00016DFD, - 0x000152A5, - 0x000133C9, - 0x0005EAC7, - 0x00058663, - 0x000336FB, - 0x00016DF3, - 0x00016A0B, - 0x00015237, - 0x00077289, - 0x00075669, - //0x0002FD85, - 0x000165B5, - 0x0003FC8F, - 0x000165AB, - 0x00015223, - 0x000CAB91, - 0x00068B99, - 0x0004C6DD, - 0x00035299, - 0x00018BE9, - 0x0001527D, - 0x00043FAB, - 0x00016BD7, - 0x0001678B, - 0x00015273, - 0x000DEBCD, - 0x00078715, - 0x0001F359, - 0x00016781, - 0x00016209, - 0x00015269, - 0x0005B88B, - 0x0005723B, - 0x00016777, - 0x000161FF, - 0x000152C3, - 0x0001525F, - 0x000151FB, - 0x000A5A71, - //0x000A1A61, - 0x000778F1, - 0x00045A1D, - 0x000161F5, - 0x00015255, - 0x00013A81, - 0x000EEC53, - 0x00081D5B, - 0x000161EB, - 0x000152AF, - 0x00054B70, - 0x0005258C, - 0x000471DC, - 0x0002954C, - 0x00016DFC, - 0x00013814, - 0x000133C8, - 0x000DBF12, - 0x000CCDAA, - 0x00091872, - 0x00036ED6, - 0x00016DF2, - 0x00034BA4, - 0x0002D4E4, - 0x00025E24, - 0x000198E0, - 0x00016A00, - 0x00015290, - 0x0001522C, - 0x0007947A, - 0x000165AA, - 0x00015286, - 0x000138BE, - 0x00087764, - 0x0003B698, - 0x000165A0, - 0x0001527C, - 0x00013850, - 0x0008E906, - 0x00071FFE, - 0x00016BD6, - 0x000C5AA0, - 0x0001F358, - 0x00016780, - 0x00015268, - //0x0002F83E, - 0x0002529E, - 0x00016776, - 0x000161FE, - 0x000151FA, - 0x00013A8A, - 0x00078124, - 0x000362D8, - 0x00023FD4, - 0x0001FA4C, - 0x000161F4, - 0x00015254, - 0x00013A80, - 0x000BBCB2, - 0x000AC5D2, - 0x00081972, - 0x000261C6, - 0x000161EA, - 0x000152AE, - 0x00013A76, - 0x00054B6F, - 0x00032AE7, - 0x000216BB, - 0x00016DFB, - 0x0001523F, - 0x00013813, - 0x000133C7, - 0x00070469, - 0x00036ED5, - 0x00034955, - 0x00016A09, - 0x00015235, - 0x0009CCDF, - 0x0007B7AB, - 0x0002D4E3, - 0x000198DF, - 0x000165B3, - 0x00097299, - 0x00089D51, - 0x000681DD, - 0x00024771, - 0x000AF227, - 0x0007D033, - 0x00041777, - 0x00016BDF, - 0x0001527B, - 0x00058B4D, - 0x000275D9, - 0x000227B9, - 0x00016BD5, - 0x00016789, - 0x000FE47B, - 0x0001677F, - 0x00016207, - 0x00015267, - 0x001052FD, - 0x00104871, - //0x0002F83D, - 0x00016775, - 0x000152C1, - 0x0001525D, - 0x000151F9, - 0x00013A89, - 0x000961FF, - 0x0007E00F, - 0x0004624F, - 0x000161F3, - 0x000152B7, - 0x00015253, - 0x00013A7F, - 0x000221B5, - 0x000161E9, - 0x000152AD, - 0x0009174E, - 0x0004A376, - //0x000361FA, - 0x0002A03A, - 0x00016DFA, - 0x000152A2, - 0x00013812, - 0x000133C6, - 0x000DDF7C, - 0x000A8B24, - 0x00016A08, - 0x000138D0, - 0x0001386C, - 0x000BE46A, - 0x0009CCDE, - //0x0007284E, - 0x00067F2A, - //0x0003016A, - 0x000198DE, - 0x000169FE, - 0x000165B2, - 0x0001522A, - 0x00097298, - 0x0007614C, - 0x000165A8, - 0x00015284, - 0x00015220, - 0x000DD216, - 0x000563E2, - 0x00050F1E, - 0x0003F516, - 0x0003B696, - 0x00037CC6, - 0x0003486E, - 0x0001FC66, - 0x00016BDE, - 0x0007BEF8, - 0x00058B4C, - 0x00034864, - 0x000275D8, - 0x00016BD4, - 0x00016788, - 0x0001677E, - 0x0001383A, - 0x0002D3E8, - 0x00016774, - 0x000161FC, - 0x000152C0, - 0x0001525C, - 0x00013A88, - 0x00094322, - 0x000580A2, - 0x00027F1A, - 0x0001CE8A, - 0x00013A7E, - 0x00039850, - 0x00027D1C - }; - - for (int i = 0; i < 574; i++) { - const auto* form = RE::TESForm::LookupByID(formids[i]); - if (form && form->Is(RE::FormType::Cell)) { - logger::warn("Detected invalid cell: {:08X}", formids[i]); - NotifyInvalidForm(form); - } - } - - //memset(formids, 0, sizeof(formids)); -} - inline void CheckCCMods() { std::string filenames[73] = { diff --git a/source/Enderal DLL/src/Main.cpp b/source/Enderal DLL/src/Main.cpp index 259cce1f9..5fdd20c68 100644 --- a/source/Enderal DLL/src/Main.cpp +++ b/source/Enderal DLL/src/Main.cpp @@ -14,6 +14,7 @@ #include "Patches/ForceBorderless.h" #include "Patches/AttachLightHitEffectCrash.h" #include "Patches/PluginsTxtPatch.h" +#include "Patches/FormTypeCollisionDetector.h" using namespace SKSE; @@ -133,7 +134,10 @@ namespace { RE::DebugMessageBox("E - Meshes.bsa is not loaded. Make sure Enderal - Forgotten Stories.esm is enabled and revalidate your files."); } - CheckIncompatibleMods(); + // Show any form type collision warnings collected during loading + if (!FormTypeCollisionDetector::ShowCollisionWarning()) { + CheckIncompatibleMods(); + } }); } else if ((message->type == MessagingInterface::kPostLoadGame && message->data) || message->type == MessagingInterface::kNewGame) { NewGameCount(true); @@ -186,6 +190,7 @@ SKSEPluginLoad(const LoadInterface* skse) { Init(skse); InitializeMessaging(); + FormTypeCollisionDetector::Install(); PluginsTxtPatch::Install(); RE::INISettingCollection::GetSingleton()->GetSetting("sIntroSequence:General")->data.s = nullptr; diff --git a/source/Enderal DLL/src/Patches/FormTypeCollisionDetector.h b/source/Enderal DLL/src/Patches/FormTypeCollisionDetector.h new file mode 100644 index 000000000..6d070401a --- /dev/null +++ b/source/Enderal DLL/src/Patches/FormTypeCollisionDetector.h @@ -0,0 +1,143 @@ +#pragma once + +#include +#include + +// Detects when a plugin overrides an existing form with a different type +// This catches Skyrim mods that conflict with Enderal's repurposed form IDs +// +// Enderal modifies Skyrim.esm to repurpose many forms with different types. +// For example, a Skyrim CELL might become an NPC in Enderal. +// When a Skyrim mod loads, it may try to load form data expecting the original +// Skyrim type, causing a type mismatch. The game silently skips these, but +// this detector logs them to help users identify incompatible mods. +namespace FormTypeCollisionDetector +{ + inline std::unordered_set flaggedMods; + inline bool messageShown = false; + + // Track collisions during loading + struct CollisionInfo + { + RE::FormID formID; + RE::FormType existingType; + RE::FormType incomingType; + std::string modName; + }; + inline std::vector detectedCollisions; + + inline void NotifyCollision(RE::FormID formID, RE::FormType existingType, RE::FormType incomingType, const char* modName) + { + logger::error("Type collision: {:08X} is {} but {} tries to load it as {}", + formID, + RE::FormTypeToString(existingType), + modName, + RE::FormTypeToString(incomingType)); + + detectedCollisions.push_back({ formID, existingType, incomingType, modName }); + + if (!flaggedMods.contains(modName)) { + flaggedMods.insert(modName); + } + } + + inline bool ShowCollisionWarning() + { + if (detectedCollisions.empty()) { + logger::info("No form type collisions detected"); + return false; + } + + logger::warn("Detected {} form type collision(s) from {} mod(s)", + detectedCollisions.size(), flaggedMods.size()); + + // Print to console + if (auto* console = RE::ConsoleLog::GetSingleton()) { + for (const auto& mod : flaggedMods) { + console->Print(std::format("{} causes form type collisions (incompatible with Enderal)", mod).c_str()); + } + } + + // Show message box for first mod only + if (!messageShown && !flaggedMods.empty()) { + messageShown = true; + auto firstMod = *flaggedMods.begin(); + std::string message = std::format( + "{} is incompatible with Enderal (form type collision).\n" + "Check console for details.", firstMod); + + if (flaggedMods.size() > 1) { + message = std::format( + "{} and {} other mod(s) are incompatible with Enderal.\n" + "Check console for details.", + firstMod, flaggedMods.size() - 1); + } + + RE::DebugMessageBox(message.c_str()); + } + + return true; + } + + namespace detail + { + // Helper template for thunk-style call hooks (from po3-Tweaks) + template + void write_thunk_call(std::uintptr_t a_src) + { + SKSE::AllocTrampoline(14); + auto& trampoline = SKSE::GetTrampoline(); + T::func = trampoline.write_call(a_src, T::thunk); + } + + struct LoadFormFromFile + { + static inline bool firstLog = true; + + static bool thunk(void* a_dataHandler, RE::TESFile* a_file, char a_flags, RE::TESForm* a_existingForm, std::int32_t a_unk) + { + // Check for type collisions before calling original + if (a_file) { + RE::FormID formID = a_file->currentform.formID; + RE::FormType incomingType = a_file->GetFormType(); + + // Log first few calls for debugging + if (firstLog) { + logger::info("FormTypeCollisionDetector: First form from {} - ID {:08X}, type {}", + a_file->fileName, formID, RE::FormTypeToString(incomingType)); + firstLog = false; + } + + // Only check Skyrim.esm forms (mod index 0 in the file) + std::uint8_t modIndex = (formID >> 24) & 0xFF; + + if (modIndex == 0 && formID != 0) { + // Look up existing form + auto* existingForm = a_existingForm ? a_existingForm : RE::TESForm::LookupByID(formID); + + if (existingForm && existingForm->GetFormType() != incomingType) { + // Type mismatch detected! + const char* modName = a_file->fileName; + NotifyCollision(formID, existingForm->GetFormType(), incomingType, modName); + } + } + } + + return func(a_dataHandler, a_file, a_flags, a_existingForm, a_unk); + } + + static inline REL::Relocation func; + }; + } + + inline void Install() + { + // Hook call to form loading function in the main loading loop + // SE: ID 13652 (0x1700E0), call at offset 0x19D to sub_1401703F0 + // AE: ID 13760 (0x1BB6C0), call at offset 0x271 to sub_1401BBAA0 + REL::Relocation target{ REL::RelocationID(13652, 13760), REL::VariantOffset(0x19D, 0x271, 0) }; + detail::write_thunk_call(target.address()); + + logger::info("Form type collision detector installed at {:X}", target.address()); + } +} diff --git a/source/Enderal DLL/src/Util.h b/source/Enderal DLL/src/Util.h index 051396509..88f9b302e 100644 --- a/source/Enderal DLL/src/Util.h +++ b/source/Enderal DLL/src/Util.h @@ -53,7 +53,6 @@ inline void CheckIncompatibleMods() CheckWorldspaces(); CheckUnconvertedMap(); - CheckSkyrimCells(); } inline bool PapyrusGlobalFunctionExists(const char* scriptName, const char* funcName)