diff --git a/loader/include/Geode/loader/Index.hpp b/loader/include/Geode/loader/Index.hpp
index 4a027823..fb0830c1 100644
--- a/loader/include/Geode/loader/Index.hpp
+++ b/loader/include/Geode/loader/Index.hpp
@@ -115,6 +115,7 @@ namespace geode {
         std::unordered_set<PlatformID> getAvailablePlatforms() const;
         bool isFeatured() const;
         std::unordered_set<std::string> getTags() const;
+        bool isInstalled() const;
 
         IndexItem();
         ~IndexItem();
diff --git a/loader/include/Geode/loader/Loader.hpp b/loader/include/Geode/loader/Loader.hpp
index 96e542d8..1b38dd69 100644
--- a/loader/include/Geode/loader/Loader.hpp
+++ b/loader/include/Geode/loader/Loader.hpp
@@ -4,6 +4,7 @@
 #include "../utils/Result.hpp"
 #include "../utils/MiniFunction.hpp"
 #include "Log.hpp"
+#include "ModEvent.hpp"
 #include "ModInfo.hpp"
 #include "ModMetadata.hpp"
 #include "Types.hpp"
@@ -19,6 +20,24 @@ namespace geode {
         std::string reason;
     };
 
+    struct LoadProblem {
+        enum class Type : uint8_t {
+            Unknown,
+            Suggestion,
+            Recommendation,
+            InvalidFile,
+            Duplicate,
+            SetupFailed,
+            LoadFailed,
+            EnableFailed,
+            MissingDependency,
+            PresentIncompatibility
+        };
+        Type type;
+        std::variant<ghc::filesystem::path, ModMetadata, Mod*> cause;
+        std::string message;
+    };
+
     class LoaderImpl;
 
     class GEODE_DLL Loader {
@@ -37,8 +56,8 @@ namespace geode {
         void dispatchScheduledFunctions(Mod* mod);
         friend void GEODE_CALL ::geode_implicit_load(Mod*);
 
-        Result<Mod*> loadModFromInfo(ModInfo const& info);
-        
+        [[deprecated]] Result<Mod*> loadModFromInfo(ModInfo const& info);
+
         Mod* takeNextMod();
 
     public:
@@ -53,16 +72,18 @@ namespace geode {
         VersionInfo maxModVersion();
         bool isModVersionSupported(VersionInfo const& version);
 
-        Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
-        void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
-        void refreshModsList();
+        [[deprecated]] Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
+        [[deprecated]] void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
+        [[deprecated]] void refreshModsList();
         bool isModInstalled(std::string const& id) const;
         Mod* getInstalledMod(std::string const& id) const;
         bool isModLoaded(std::string const& id) const;
         Mod* getLoadedMod(std::string const& id) const;
         std::vector<Mod*> getAllMods();
-        Mod* getModImpl();
         [[deprecated("use Mod::get instead")]] Mod* getModImpl();
+        [[deprecated]] void updateAllDependencies();
+        [[deprecated("use getProblems instead")]] std::vector<InvalidGeodeFile> getFailedMods() const;
+        std::vector<LoadProblem> getProblems() const;
 
         void updateResources();
         void updateResources(bool forceReload);
diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp
index 8a0d869f..569a2a6d 100644
--- a/loader/include/Geode/loader/Mod.hpp
+++ b/loader/include/Geode/loader/Mod.hpp
@@ -80,11 +80,10 @@ namespace geode {
         bool isEnabled() const;
         bool isLoaded() const;
         bool supportsDisabling() const;
-        bool supportsUnloading() const;
-        bool wasSuccesfullyLoaded() const;
-        ModInfo getModInfo() const;
+        bool canDisable() const;
+        bool canEnable() const;
         [[deprecated]] bool supportsUnloading() const;
-        [[deprecated("wasSuccessfullyLoaded")]] bool wasSuccesfullyLoaded() const;
+        [[deprecated("use wasSuccessfullyLoaded instead")]] bool wasSuccesfullyLoaded() const;
         bool wasSuccessfullyLoaded() const;
         [[deprecated("use getMetadata instead")]] ModInfo getModInfo() const;
         ModMetadata getMetadata() const;
@@ -299,7 +298,7 @@ namespace geode {
          * @returns Successful result on success,
          * errorful result with info on error
          */
-        Result<> loadBinary();
+        [[deprecated]] Result<> loadBinary();
 
         /**
          * Disable & unload this mod
@@ -308,7 +307,7 @@ namespace geode {
          * @returns Successful result on success,
          * errorful result with info on error
          */
-        Result<> unloadBinary();
+        [[deprecated]] Result<> unloadBinary();
 
         /**
          * Enable this mod
@@ -325,10 +324,7 @@ namespace geode {
         Result<> disable();
 
         /**
-         * Disable & unload this mod (if supported), then delete the mod's
-         * .geode package. If unloading isn't supported, the mod's binary
-         * will stay loaded, and in all cases the Mod* instance will still
-         * exist and be interactable.
+         * Disable this mod (if supported), then delete the mod's .geode package.
          * @returns Successful result on success,
          * errorful result with info on error
          */
@@ -341,6 +337,16 @@ namespace geode {
          */
         bool depends(std::string const& id) const;
 
+        /**
+         * Update the state of each of the
+         * dependencies. Depending on if the
+         * mod has unresolved dependencies,
+         * it will either be loaded or unloaded
+         * @returns Error.
+         * @deprecated No longer needed.
+         */
+        [[deprecated("no longer needed")]] Result<> updateDependencies();
+
         /**
          * Check whether all the required
          * dependencies for this mod have
@@ -350,21 +356,20 @@ namespace geode {
          */
         bool hasUnresolvedDependencies() const;
         /**
-         * Update the state of each of the
-         * dependencies. Depending on if the
-         * mod has unresolved dependencies,
-         * it will either be loaded or unloaded
+         * Check whether none of the
+         * incompatibilities with this mod are loaded
          * @returns True if the mod has unresolved
-         * dependencies, false if not.
+         * incompatibilities, false if not.
          */
-        Result<> updateDependencies();
+        bool hasUnresolvedIncompatibilities() const;
         /**
          * Get a list of all the unresolved
          * dependencies this mod has
          * @returns List of all the unresolved
          * dependencies
+         * @deprecated Use Loader::getProblems instead.
          */
-        std::vector<Dependency> getUnresolvedDependencies();
+        [[deprecated("use Loader::getProblems instead")]] std::vector<Dependency> getUnresolvedDependencies();
 
         char const* expandSpriteName(char const* name);
 
diff --git a/loader/include/Geode/loader/ModInfo.hpp b/loader/include/Geode/loader/ModInfo.hpp
index feefcbc6..431fd653 100644
--- a/loader/include/Geode/loader/ModInfo.hpp
+++ b/loader/include/Geode/loader/ModInfo.hpp
@@ -36,6 +36,7 @@ namespace geode {
      */
     class GEODE_DLL [[deprecated("use ModMetadata instead")]] ModInfo {
         class Impl;
+#pragma warning(suppress : 4996)
         std::unique_ptr<Impl> m_impl;
 
     public:
@@ -200,18 +201,29 @@ namespace geode {
         operator ModMetadata() const;
 
     private:
+        ModJson& rawJSON();
+        ModJson const& rawJSON() const;
+        /**
+         * Version is passed for backwards
+         * compatibility if we update the mod.json
+         * format
+         */
+        static Result<ModInfo> createFromSchemaV010(ModJson const& json);
+
+        Result<> addSpecialFiles(ghc::filesystem::path const& dir);
+        Result<> addSpecialFiles(utils::file::Unzip& zip);
+
+        std::vector<std::pair<std::string, std::optional<std::string>*>> getSpecialFiles();
+
         friend class ModInfoImpl;
 
         friend class ModMetadata;
     };
 }
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
 template <>
-struct json::Serialize<geode::ModInfo> {
+struct [[deprecated]] json::Serialize<geode::ModInfo> {
     static json::Value to_json(geode::ModInfo const& info) {
         return info.toJSON();
     }
 };
-#pragma clang diagnostic pop
diff --git a/loader/include/Geode/loader/ModMetadata.hpp b/loader/include/Geode/loader/ModMetadata.hpp
index 4364d77b..23d2f173 100644
--- a/loader/include/Geode/loader/ModMetadata.hpp
+++ b/loader/include/Geode/loader/ModMetadata.hpp
@@ -44,14 +44,10 @@ namespace geode {
             Mod* mod = nullptr;
             [[nodiscard]] bool isResolved() const;
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#pragma ide diagnostic ignored "google-explicit-constructor"
-            operator geode::Dependency();
-            operator geode::Dependency() const;
+            [[deprecated]] operator geode::Dependency();
+            [[deprecated]] operator geode::Dependency() const;
 
-            static Dependency fromDeprecated(geode::Dependency const& value);
-#pragma clang diagnostic pop
+            [[deprecated]] static Dependency fromDeprecated(geode::Dependency const& value);
         };
 
         struct GEODE_DLL Incompatibility {
@@ -65,29 +61,25 @@ namespace geode {
             std::string info;
             std::optional<std::string> url;
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#pragma ide diagnostic ignored "google-explicit-constructor"
-            operator geode::IssuesInfo();
-            operator geode::IssuesInfo() const;
+            [[deprecated]] operator geode::IssuesInfo();
+            [[deprecated]] operator geode::IssuesInfo() const;
 
-            static IssuesInfo fromDeprecated(geode::IssuesInfo const& value);
-#pragma clang diagnostic pop
+            [[deprecated]] static IssuesInfo fromDeprecated(geode::IssuesInfo const& value);
         };
 
         /**
          * Path to the mod file
          */
-        [[maybe_unused, nodiscard]] ghc::filesystem::path getPath() const;
+        [[nodiscard]] ghc::filesystem::path getPath() const;
         /**
          * Name of the platform binary within
          * the mod zip
          */
-        [[maybe_unused, nodiscard]] std::string getBinaryName() const;
+        [[nodiscard]] std::string getBinaryName() const;
         /**
          * Mod Version. Should follow semantic versioning.
          */
-        [[maybe_unused, nodiscard]] VersionInfo getVersion() const;
+        [[nodiscard]] VersionInfo getVersion() const;
         /**
          * Human-readable ID of the Mod.
          * Recommended to be in the format
@@ -97,14 +89,14 @@ namespace geode {
          * be restricted to the ASCII
          * character set.
          */
-        [[maybe_unused, nodiscard]] std::string getID() const;
+        [[nodiscard]] std::string getID() const;
         /**
          * Name of the mod. May contain
          * spaces & punctuation, but should
          * be restricted to the ASCII
          * character set.
          */
-        [[maybe_unused, nodiscard]] std::string getName() const;
+        [[nodiscard]] std::string getName() const;
         /**
          * The name of the head developer.
          * Should be a single name, like
@@ -116,98 +108,94 @@ namespace geode {
          * should be named in `m_credits`
          * instead.
          */
-        [[maybe_unused, nodiscard]] std::string getDeveloper() const;
+        [[nodiscard]] std::string getDeveloper() const;
         /**
          * Short & concise description of the
          * mod.
          */
-        [[maybe_unused, nodiscard]] std::optional<std::string> getDescription() const;
+        [[nodiscard]] std::optional<std::string> getDescription() const;
         /**
          * Detailed description of the mod, written in Markdown (see
          * <Geode/ui/MDTextArea.hpp>) for more info
          */
-        [[maybe_unused, nodiscard]] std::optional<std::string> getDetails() const;
+        [[nodiscard]] std::optional<std::string> getDetails() const;
         /**
          * Changelog for the mod, written in Markdown (see
          * <Geode/ui/MDTextArea.hpp>) for more info
          */
-        [[maybe_unused, nodiscard]] std::optional<std::string> getChangelog() const;
+        [[nodiscard]] std::optional<std::string> getChangelog() const;
         /**
          * Support info for the mod; this means anything to show ways to
          * support the mod's development, like donations. Written in Markdown
          * (see MDTextArea for more info)
          */
-        [[maybe_unused, nodiscard]] std::optional<std::string> getSupportInfo() const;
+        [[nodiscard]] std::optional<std::string> getSupportInfo() const;
         /**
          * Git Repository of the mod
          */
-        [[maybe_unused, nodiscard]] std::optional<std::string> getRepository() const;
+        [[nodiscard]] std::optional<std::string> getRepository() const;
         /**
          * Info about where users should report issues and request help
          */
-        [[maybe_unused, nodiscard]] std::optional<IssuesInfo> getIssues() const;
+        [[nodiscard]] std::optional<IssuesInfo> getIssues() const;
         /**
          * Dependencies
          */
-        [[maybe_unused, nodiscard]] std::vector<Dependency> getDependencies() const;
+        [[nodiscard]] std::vector<Dependency> getDependencies() const;
         /**
          * Incompatibilities
          */
-        [[maybe_unused, nodiscard]] std::vector<Incompatibility> getIncompatibilities() const;
+        [[nodiscard]] std::vector<Incompatibility> getIncompatibilities() const;
         /**
          * Mod spritesheet names
          */
-        [[maybe_unused, nodiscard]] std::vector<std::string> getSpritesheets() const;
+        [[nodiscard]] std::vector<std::string> getSpritesheets() const;
         /**
          * Mod settings
          * @note Not a map because insertion order must be preserved
          */
-        [[maybe_unused, nodiscard]] std::vector<std::pair<std::string, Setting>> getSettings() const;
+        [[nodiscard]] std::vector<std::pair<std::string, Setting>> getSettings() const;
         /**
          * Whether this mod has to be loaded before the loading screen or not
          */
-        [[maybe_unused, nodiscard]] bool needsEarlyLoad() const;
+        [[ nodiscard]] bool needsEarlyLoad() const;
         /**
          * Whether this mod is an API or not
          */
-        [[maybe_unused, nodiscard]] bool isAPI() const;
+        [[nodiscard]] bool isAPI() const;
         /**
          * Create ModInfo from an unzipped .geode package
          */
-        [[maybe_unused]] static Result<ModMetadata> createFromGeodeZip(utils::file::Unzip& zip);
+        static Result<ModMetadata> createFromGeodeZip(utils::file::Unzip& zip);
         /**
          * Create ModInfo from a .geode package
          */
-        [[maybe_unused]] static Result<ModMetadata> createFromGeodeFile(ghc::filesystem::path const& path);
+        static Result<ModMetadata> createFromGeodeFile(ghc::filesystem::path const& path);
         /**
          * Create ModInfo from a mod.json file
          */
-        [[maybe_unused]] static Result<ModMetadata> createFromFile(ghc::filesystem::path const& path);
+        static Result<ModMetadata> createFromFile(ghc::filesystem::path const& path);
         /**
          * Create ModInfo from a parsed json document
          */
-        [[maybe_unused]] static Result<ModMetadata> create(ModJson const& json);
+        static Result<ModMetadata> create(ModJson const& json);
 
         /**
          * Convert to JSON. Essentially same as getRawJSON except dynamically
          * adds runtime fields like path
          */
-        [[maybe_unused, nodiscard]] ModJson toJSON() const;
+        [[nodiscard]] ModJson toJSON() const;
         /**
          * Get the raw JSON file
          */
-        [[maybe_unused, nodiscard]] ModJson getRawJSON() const;
+        [[nodiscard]] ModJson getRawJSON() const;
 
-        [[maybe_unused]] bool operator==(ModMetadata const& other) const;
+        bool operator==(ModMetadata const& other) const;
 
-        [[maybe_unused]] static bool validateID(std::string const& id);
+        static bool validateID(std::string const& id);
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-#pragma ide diagnostic ignored "google-explicit-constructor"
-        operator ModInfo();
-        operator ModInfo() const;
-#pragma clang diagnostic pop
+        [[deprecated]] operator ModInfo();
+        [[deprecated]] operator ModInfo() const;
 
     private:
         /**
@@ -226,11 +214,7 @@ namespace geode {
 
         friend class ModMetadataImpl;
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-        // ModInfo => ModMetadata conversion stuff
-        friend class ModInfo::Impl;
-#pragma clang diagnostic pop
+        friend class ModInfo;
     };
 }
 
diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp
index eb89a19d..4942a0ec 100644
--- a/loader/src/hooks/MenuLayer.cpp
+++ b/loader/src/hooks/MenuLayer.cpp
@@ -23,34 +23,34 @@ class CustomMenuLayer;
 static Ref<Notification> INDEX_UPDATE_NOTIF = nullptr;
 
 $execute {
-	new EventListener<IndexUpdateFilter>(+[](IndexUpdateEvent* event) {
-		if (!INDEX_UPDATE_NOTIF) return;
-		std::visit(makeVisitor {
-			[](UpdateProgress const& prog) {},
-			[](UpdateFinished const&) {
-				INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Success);
-				INDEX_UPDATE_NOTIF->setString("Index Up-to-Date");
-				INDEX_UPDATE_NOTIF->waitAndHide();
-				INDEX_UPDATE_NOTIF = nullptr;
-			},
-			[](UpdateFailed const& info) {
-				INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Error);
-				INDEX_UPDATE_NOTIF->setString(info);
-				INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
-				INDEX_UPDATE_NOTIF = nullptr;
-			},
-		}, event->status);
-	});
+    new EventListener<IndexUpdateFilter>(+[](IndexUpdateEvent* event) {
+        if (!INDEX_UPDATE_NOTIF) return;
+        std::visit(makeVisitor {
+            [](UpdateProgress const& prog) {},
+            [](UpdateFinished const&) {
+                INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Success);
+                INDEX_UPDATE_NOTIF->setString("Index Up-to-Date");
+                INDEX_UPDATE_NOTIF->waitAndHide();
+                INDEX_UPDATE_NOTIF = nullptr;
+            },
+            [](UpdateFailed const& info) {
+                INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Error);
+                INDEX_UPDATE_NOTIF->setString(info);
+                INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
+                INDEX_UPDATE_NOTIF = nullptr;
+            },
+        }, event->status);
+    });
 };
 
 struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
-	static void onModify(auto& self) {
-		if (!self.setHookPriority("MenuLayer::init", GEODE_ID_PRIORITY)) {
+    static void onModify(auto& self) {
+        if (!self.setHookPriority("MenuLayer::init", GEODE_ID_PRIORITY)) {
             log::warn("Failed to set MenuLayer::init hook priority, node IDs may not work properly");
         }
     }
 
-	CCSprite* m_geodeButton;
+    CCSprite* m_geodeButton;
 
     bool init() {
         if (!MenuLayer::init()) return false;
@@ -61,28 +61,28 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
 
         auto winSize = CCDirector::sharedDirector()->getWinSize();
 
-		// add geode button
-		
-		m_fields->m_geodeButton = CircleButtonSprite::createWithSpriteFrameName(
-			"geode-logo-outline-gold.png"_spr,
-			1.0f,
-			CircleBaseColor::Green,
-			CircleBaseSize::MediumAlt
-		);
-		auto geodeBtnSelector = &CustomMenuLayer::onGeode;
-		if (!m_fields->m_geodeButton) {
-			geodeBtnSelector = &CustomMenuLayer::onMissingTextures;
-			m_fields->m_geodeButton = ButtonSprite::create("!!");
-		}
+        // add geode button
+        
+        m_fields->m_geodeButton = CircleButtonSprite::createWithSpriteFrameName(
+            "geode-logo-outline-gold.png"_spr,
+            1.0f,
+            CircleBaseColor::Green,
+            CircleBaseSize::MediumAlt
+        );
+        auto geodeBtnSelector = &CustomMenuLayer::onGeode;
+        if (!m_fields->m_geodeButton) {
+            geodeBtnSelector = &CustomMenuLayer::onMissingTextures;
+            m_fields->m_geodeButton = ButtonSprite::create("!!");
+        }
 
         auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
 
-		auto btn = CCMenuItemSpriteExtra::create(
-			m_fields->m_geodeButton, this,
-			static_cast<SEL_MenuHandler>(geodeBtnSelector)
-		);
-		btn->setID("geode-button"_spr);
-		bottomMenu->addChild(btn);
+        auto btn = CCMenuItemSpriteExtra::create(
+            m_fields->m_geodeButton, this,
+            static_cast<SEL_MenuHandler>(geodeBtnSelector)
+        );
+        btn->setID("geode-button"_spr);
+        bottomMenu->addChild(btn);
 
         bottomMenu->updateLayout();
 
@@ -96,54 +96,57 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
         static bool shownFailedNotif = false;
         if (!shownFailedNotif) {
             shownFailedNotif = true;
-            if (Loader::get()->getFailedMods().size()) {
-                Notification::create("Some mods failed to load", NotificationIcon::Error)->show();
+            auto problems = Loader::get()->getProblems();
+            if (std::any_of(problems.begin(), problems.end(), [&](auto& item) {
+                    return item.type != LoadProblem::Type::Suggestion && item.type != LoadProblem::Type::Recommendation;
+                })) {
+                Notification::create("There were problems loading some mods", NotificationIcon::Error)->show();
             }
         }
 
-		// show if the user tried to be naughty and load arbitary DLLs
-		static bool shownTriedToLoadDlls = false;
-		if (!shownTriedToLoadDlls) {
-			shownTriedToLoadDlls = true;
-			if (Loader::get()->userTriedToLoadDLLs()) {
-				auto popup = FLAlertLayer::create(
-					"Hold up!",
-					"It appears that you have tried to <cr>load DLLs</c> with Geode. "
-					"Please note that <cy>Geode is incompatible with ALL DLLs</c>, "
-					"as they can cause Geode mods to <cr>error</c>, or even "
-					"<cr>crash</c>.\n\n"
-					"Remove the DLLs / other mod loaders you have, or <cr>proceed at "
-					"your own risk.</c>",
-					"OK"
-				);
-				popup->m_scene = this;
-				popup->m_noElasticity = true;
-				popup->show();
-			}
-		}
+        // show if the user tried to be naughty and load arbitrary DLLs
+        static bool shownTriedToLoadDlls = false;
+        if (!shownTriedToLoadDlls) {
+            shownTriedToLoadDlls = true;
+            if (Loader::get()->userTriedToLoadDLLs()) {
+                auto popup = FLAlertLayer::create(
+                    "Hold up!",
+                    "It appears that you have tried to <cr>load DLLs</c> with Geode. "
+                    "Please note that <cy>Geode is incompatible with ALL DLLs</c>, "
+                    "as they can cause Geode mods to <cr>error</c>, or even "
+                    "<cr>crash</c>.\n\n"
+                    "Remove the DLLs / other mod loaders you have, or <cr>proceed at "
+                    "your own risk.</c>",
+                    "OK"
+                );
+                popup->m_scene = this;
+                popup->m_noElasticity = true;
+                popup->show();
+            }
+        }
 
-		// show auto update message
-		static bool shownUpdateInfo = false;
-		if (LoaderImpl::get()->isNewUpdateDownloaded() && !shownUpdateInfo) {
-			shownUpdateInfo = true;
-			auto popup = FLAlertLayer::create(
-				"Update downloaded",
-				"A new <cy>update</c> for Geode has been installed! "
-				"Please <cy>restart the game</c> to apply.",
-				"OK"
-			);
-			popup->m_scene = this;
-			popup->m_noElasticity = true;
-			popup->show();
-		}
+        // show auto update message
+        static bool shownUpdateInfo = false;
+        if (LoaderImpl::get()->isNewUpdateDownloaded() && !shownUpdateInfo) {
+            shownUpdateInfo = true;
+            auto popup = FLAlertLayer::create(
+                "Update downloaded",
+                "A new <cy>update</c> for Geode has been installed! "
+                "Please <cy>restart the game</c> to apply.",
+                "OK"
+            );
+            popup->m_scene = this;
+            popup->m_noElasticity = true;
+            popup->show();
+        }
 
         // show crash info
         static bool shownLastCrash = false;
         if (
-			Loader::get()->didLastLaunchCrash() &&
-			!shownLastCrash &&
-			!Mod::get()->template getSettingValue<bool>("disable-last-crashed-popup")
-		) {
+            Loader::get()->didLastLaunchCrash() &&
+            !shownLastCrash &&
+            !Mod::get()->template getSettingValue<bool>("disable-last-crashed-popup")
+        ) {
             shownLastCrash = true;
             auto popup = createQuickPopup(
                 "Crashed",
@@ -163,94 +166,94 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
             popup->show();
         }
 
-		// update mods index
-		if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
-			this->addChild(EventListenerNode<IndexUpdateFilter>::create(
-				this, &CustomMenuLayer::onIndexUpdate
-			));
-			INDEX_UPDATE_NOTIF = Notification::create(
-				"Updating Index", NotificationIcon::Loading, 0
-			);
-			INDEX_UPDATE_NOTIF->show();
-			Index::get()->update();
-		}
+        // update mods index
+        if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
+            this->addChild(EventListenerNode<IndexUpdateFilter>::create(
+                this, &CustomMenuLayer::onIndexUpdate
+            ));
+            INDEX_UPDATE_NOTIF = Notification::create(
+                "Updating Index", NotificationIcon::Loading, 0
+            );
+            INDEX_UPDATE_NOTIF->show();
+            Index::get()->update();
+        }
 
-		this->addUpdateIndicator();
-	
-		return true;
-	}
+        this->addUpdateIndicator();
+    
+        return true;
+    }
 
-	void onIndexUpdate(IndexUpdateEvent* event) {
-		if (
-			std::holds_alternative<UpdateFinished>(event->status) ||
-			std::holds_alternative<UpdateFailed>(event->status)
-		) {
-			this->addUpdateIndicator();
-		}
-	}
+    void onIndexUpdate(IndexUpdateEvent* event) {
+        if (
+            std::holds_alternative<UpdateFinished>(event->status) ||
+            std::holds_alternative<UpdateFailed>(event->status)
+        ) {
+            this->addUpdateIndicator();
+        }
+    }
 
-	void addUpdateIndicator() {
-		if (Index::get()->areUpdatesAvailable()) {
-			auto icon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
-			icon->setPosition(
-				m_fields->m_geodeButton->getContentSize() - CCSize { 10.f, 10.f }
-			);
-			icon->setZOrder(99);
-			icon->setScale(.5f);
-			m_fields->m_geodeButton->addChild(icon);
-		}
-	}
+    void addUpdateIndicator() {
+        if (Index::get()->areUpdatesAvailable()) {
+            auto icon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
+            icon->setPosition(
+                m_fields->m_geodeButton->getContentSize() - CCSize { 10.f, 10.f }
+            );
+            icon->setZOrder(99);
+            icon->setScale(.5f);
+            m_fields->m_geodeButton->addChild(icon);
+        }
+    }
 
-	void onMissingTextures(CCObject*) {
-		
-	#ifdef GEODE_IS_DESKTOP
+    void onMissingTextures(CCObject*) {
+        
+    #ifdef GEODE_IS_DESKTOP
 
-		(void) utils::file::createDirectoryAll(dirs::getGeodeDir() / "update" / "resources");
+        (void) utils::file::createDirectoryAll(dirs::getGeodeDir() / "update" / "resources");
 
-		createQuickPopup(
-			"Missing Textures",
-			"You appear to be missing textures, and the automatic texture fixer "
-			"hasn't fixed the issue.\n"
-			"Download <cy>resources.zip</c> from the latest release on GitHub, "
-			"and <cy>unzip its contents</c> into <cb>geode/update/resources</c>.\n"
-			"Afterwards, <cg>restart the game</c>.\n"
-			"You may also continue without installing resources, but be aware that "
-			"you won't be able to open <cr>the Geode menu</c>.",
-			"Dismiss", "Open Github",
-			[](auto, bool btn2) {
-				if (btn2) {
-					web::openLinkInBrowser("https://github.com/geode-sdk/geode/releases/latest");
-					file::openFolder(dirs::getGeodeDir() / "update" / "resources");
-					FLAlertLayer::create(
-						"Info",
-						"Opened GitHub in your browser and the destination in "
-						"your file browser.\n"
-						"Download <cy>resources.zip</c>, "
-						"and <cy>unzip its contents</c> into the destination "
-						"folder.\n"
-						"<cb>Don't add any new folders to the destination!</c>",
-						"OK"
-					)->show();
-				}
-			}
-		);
+        createQuickPopup(
+            "Missing Textures",
+            "You appear to be missing textures, and the automatic texture fixer "
+            "hasn't fixed the issue.\n"
+            "Download <cy>resources.zip</c> from the latest release on GitHub, "
+            "and <cy>unzip its contents</c> into <cb>geode/update/resources</c>.\n"
+            "Afterwards, <cg>restart the game</c>.\n"
+            "You may also continue without installing resources, but be aware that "
+            "you won't be able to open <cr>the Geode menu</c>.",
+            "Dismiss", "Open Github",
+            [](auto, bool btn2) {
+                if (btn2) {
+                    web::openLinkInBrowser("https://github.com/geode-sdk/geode/releases/latest");
+                    file::openFolder(dirs::getGeodeDir() / "update" / "resources");
+                    FLAlertLayer::create(
+                        "Info",
+                        "Opened GitHub in your browser and the destination in "
+                        "your file browser.\n"
+                        "Download <cy>resources.zip</c>, "
+                        "and <cy>unzip its contents</c> into the destination "
+                        "folder.\n"
+                        "<cb>Don't add any new folders to the destination!</c>",
+                        "OK"
+                    )->show();
+                }
+            }
+        );
 
-	#else
+    #else
 
-		// dunno if we can auto-create target directory on mobile, nor if the 
-		// user has access to moving stuff there
+        // dunno if we can auto-create target directory on mobile, nor if the 
+        // user has access to moving stuff there
 
-		FLAlertLayer::create(
-			"Missing Textures",
-			"You appear to be missing textures, and the automatic texture fixer "
-			"hasn't fixed the issue.\n"
-			"**<cy>Report this bug to the Geode developers</c>**. It is very likely "
-			"that your game <cr>will crash</c> until the issue is resolved.",
-			"OK"
-		)->show();
+        FLAlertLayer::create(
+            "Missing Textures",
+            "You appear to be missing textures, and the automatic texture fixer "
+            "hasn't fixed the issue.\n"
+            "**<cy>Report this bug to the Geode developers</c>**. It is very likely "
+            "that your game <cr>will crash</c> until the issue is resolved.",
+            "OK"
+        )->show();
 
-	#endif
-	}
+    #endif
+    }
 
     void onGeode(CCObject*) {
         ModListLayer::scene();
diff --git a/loader/src/internal/crashlog.cpp b/loader/src/internal/crashlog.cpp
index e5b3af57..5995dfb3 100644
--- a/loader/src/internal/crashlog.cpp
+++ b/loader/src/internal/crashlog.cpp
@@ -18,7 +18,7 @@ static std::string getDateString(bool filesafe) {
 static void printGeodeInfo(std::stringstream& stream) {
     stream << "Loader Version: " << Loader::get()->getVersion().toString() << "\n"
            << "Installed mods: " << Loader::get()->getAllMods().size() << "\n"
-           << "Failed mods: " << Loader::get()->getFailedMods().size() << "\n";
+           << "Problems: " << Loader::get()->getProblems().size() << "\n";
 }
 
 static void printMods(std::stringstream& stream) {
@@ -84,4 +84,4 @@ std::string crashlog::writeCrashlog(geode::Mod* faultyMod, std::string const& in
     actualFile.close();
 
     return file.str();
-}
\ No newline at end of file
+}
diff --git a/loader/src/loader/Index.cpp b/loader/src/loader/Index.cpp
index bf366247..b564b410 100644
--- a/loader/src/loader/Index.cpp
+++ b/loader/src/loader/Index.cpp
@@ -64,6 +64,8 @@ public:
     static Result<std::shared_ptr<IndexItem>> create(
         ghc::filesystem::path const& dir
     );
+
+    bool isInstalled() const;
 };
 
 IndexItem::IndexItem() : m_impl(std::make_unique<Impl>()) {}
@@ -101,6 +103,10 @@ std::unordered_set<std::string> IndexItem::getTags() const {
     return m_impl->m_tags;
 }
 
+bool IndexItem::isInstalled() const {
+    return m_impl->isInstalled();
+}
+
 Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir) {
     GEODE_UNWRAP_INTO(
         auto entry, file::readJson(dir / "entry.json")
@@ -134,6 +140,10 @@ Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir
     return Ok(item);
 }
 
+bool IndexItem::Impl::isInstalled() const {
+    return ghc::filesystem::exists(dirs::getModsDir() / (m_metadata.getID() + ".geode"));
+}
+
 // Helpers
 
 static Result<> flattenGithubRepo(ghc::filesystem::path const& dir) {
@@ -595,9 +605,6 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
             }
         }
 
-        // load mods
-        Loader::get()->refreshModsList();
-
         auto const& eventModID = list.target->getMetadata().getID();
         Loader::get()->queueInGDThread([eventModID]() {
             ModInstallEvent(eventModID, UpdateFinished()).post();
diff --git a/loader/src/loader/Loader.cpp b/loader/src/loader/Loader.cpp
index 6335ccee..8d55893c 100644
--- a/loader/src/loader/Loader.cpp
+++ b/loader/src/loader/Loader.cpp
@@ -95,6 +95,10 @@ std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
     return m_impl->getFailedMods();
 }
 
+std::vector<LoadProblem> Loader::getProblems() const {
+    return m_impl->getProblems();
+}
+
 void Loader::updateResources() {
     return m_impl->updateResources();
 }
diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp
index 99d12cb6..23ea529b 100644
--- a/loader/src/loader/LoaderImpl.cpp
+++ b/loader/src/loader/LoaderImpl.cpp
@@ -1,4 +1,3 @@
-
 #include "LoaderImpl.hpp"
 #include <cocos2d.h>
 
@@ -10,24 +9,19 @@
 #include <Geode/loader/Loader.hpp>
 #include <Geode/loader/Log.hpp>
 #include <Geode/loader/Mod.hpp>
-#include <Geode/loader/ModJsonTest.hpp>
+#include <Geode/utils/JsonValidation.hpp>
 #include <Geode/utils/file.hpp>
 #include <Geode/utils/map.hpp>
 #include <Geode/utils/ranges.hpp>
 #include <Geode/utils/string.hpp>
 #include <Geode/utils/web.hpp>
-#include <Geode/utils/JsonValidation.hpp>
-#include "ModImpl.hpp"
-#include "ModInfoImpl.hpp"
 #include <about.hpp>
 #include <crashlog.hpp>
 #include <fmt/format.h>
 #include <hash.hpp>
 #include <iostream>
 #include <resources.hpp>
-#include <sstream>
 #include <string>
-#include <thread>
 #include <vector>
 
 using namespace geode::prelude;
@@ -36,9 +30,9 @@ Loader::Impl* LoaderImpl::get() {
     return Loader::get()->m_impl.get();
 }
 
-Loader::Impl::Impl() {}
+Loader::Impl::Impl() = default;
 
-Loader::Impl::~Impl() {}
+Loader::Impl::~Impl() = default;
 
 // Initialization
 
@@ -92,15 +86,10 @@ Result<> Loader::Impl::setup() {
     this->setupIPC();
 
     this->createDirectories();
-    auto sett = this->loadData();
-    if (!sett) {
-        log::warn("Unable to load loader settings: {}", sett.unwrapErr());
-    }
-    this->refreshModsList();
 
-    this->queueInGDThread([]() {
-        Loader::get()->addSearchPaths();
-    });
+    this->addSearchPaths();
+
+    this->refreshModGraph();
 
     m_isSetup = true;
 
@@ -132,12 +121,19 @@ std::vector<Mod*> Loader::Impl::getAllMods() {
     return map::values(m_mods);
 }
 
-Mod* Loader::Impl::getModImpl() {
-    return Mod::get();
-}
-
 std::vector<InvalidGeodeFile> Loader::Impl::getFailedMods() const {
-    return m_invalidMods;
+    std::vector<InvalidGeodeFile> inv;
+    for (auto const& item : this->getProblems()) {
+        if (item.type != LoadProblem::Type::InvalidFile)
+            continue;
+        if (!holds_alternative<ghc::filesystem::path>(item.cause))
+            continue;
+        inv.push_back({
+            std::get<ghc::filesystem::path>(item.cause),
+            item.message
+        });
+    }
+    return inv;
 }
 
 // Version info
@@ -170,7 +166,7 @@ bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
 Result<> Loader::Impl::saveData() {
     // save mods' data
     for (auto& [id, mod] : m_mods) {
-        Mod::get()->setSavedValue("should-load-" + id, mod->isEnabled());
+        Mod::get()->setSavedValue("should-load-" + id, mod->isUninstalled() || mod->isEnabled());
         auto r = mod->saveData();
         if (!r) {
             log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
@@ -178,15 +174,11 @@ Result<> Loader::Impl::saveData() {
     }
     // save loader data
     GEODE_UNWRAP(Mod::get()->saveData());
-    
+
     return Ok();
 }
 
 Result<> Loader::Impl::loadData() {
-    auto e = Mod::get()->loadData();
-    if (!e) {
-        log::warn("Unable to load loader settings: {}", e.unwrapErr());
-    }
     for (auto& [_, mod] : m_mods) {
         auto r = mod->loadData();
         if (!r) {
@@ -198,60 +190,6 @@ Result<> Loader::Impl::loadData() {
 
 // Mod loading
 
-Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
-    if (m_mods.count(info.id())) {
-        return Err(fmt::format("Mod with ID '{}' already loaded", info.id()));
-    }
-
-    // create Mod instance
-    auto mod = new Mod(info);
-    auto setupRes = mod->m_impl->setup();
-    if (!setupRes) {
-        // old code artifcat, idk why we are not using unique_ptr TBH
-        delete mod;
-        return Err(fmt::format(
-            "Unable to setup mod '{}': {}",
-            info.id(), setupRes.unwrapErr()
-        ));
-    }
-
-    m_mods.insert({ info.id(), mod });
-
-    mod->m_impl->m_enabled = Mod::get()->getSavedValue<bool>(
-        "should-load-" + info.id(), true
-    );
-
-    // this loads the mod if its dependencies are resolved
-    auto dependenciesRes = mod->updateDependencies();
-    if (!dependenciesRes) {
-        delete mod;
-        m_mods.erase(info.id());
-        return Err(dependenciesRes.unwrapErr());
-    }
-
-    // add mod resources
-    this->queueInGDThread([this, mod]() {
-        auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
-
-        CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
-        this->updateModResources(mod);
-    });
-
-    return Ok(mod);
-}
-
-Result<Mod*> Loader::Impl::loadModFromFile(ghc::filesystem::path const& file) {
-    auto res = ModInfo::createFromGeodeFile(file);
-    if (!res) {
-        m_invalidMods.push_back(InvalidGeodeFile {
-            .path = file,
-            .reason = res.unwrapErr(),
-        });
-        return Err(res.unwrapErr());
-    }
-    return this->loadModFromInfo(res.unwrap());
-}
-
 bool Loader::Impl::isModInstalled(std::string const& id) const {
     return m_mods.count(id) && !m_mods.at(id)->isUninstalled();
 }
@@ -308,116 +246,320 @@ void Loader::Impl::updateModResources(Mod* mod) {
 
 // Dependencies and refreshing
 
-void Loader::Impl::loadModsFromDirectory(
-    ghc::filesystem::path const& dir,
-    bool recursive
-) {
-    log::debug("Searching {}", dir);
-    for (auto const& entry : ghc::filesystem::directory_iterator(dir)) {
-        // recursively search directories
-        if (ghc::filesystem::is_directory(entry) && recursive) {
-            this->loadModsFromDirectory(entry.path(), true);
-            continue;
-        }
+Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
+    return Err("Loader::loadModFromInfo is deprecated");
+}
 
-        // skip this entry if it's not a file
-        if (!ghc::filesystem::is_regular_file(entry)) {
-            continue;
-        }
+Result<Mod*> Loader::Impl::loadModFromFile(ghc::filesystem::path const& file) {
+    return Err("Loader::loadModFromFile is deprecated");
+}
 
-        // skip this entry if its extension is not .geode
-        if (entry.path().extension() != GEODE_MOD_EXTENSION) {
-            continue;
-        }
-        // skip this entry if it's already loaded
-        if (map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool {
-            return p->m_impl->m_info.path() == entry.path();
-        })) {
-            continue;
-        }
-
-        // if mods should be loaded immediately, do that
-        if (m_earlyLoadFinished) {
-            auto load = this->loadModFromFile(entry);
-            if (!load) {
-                log::error("Unable to load {}: {}", entry, load.unwrapErr());
-            }
-        }
-        // otherwise collect mods to load first to make sure the correct 
-        // versions of the mods are loaded and that early-loaded mods are 
-        // loaded early
-        else {
-            auto res = ModInfo::createFromGeodeFile(entry.path());
-            if (!res) {
-                m_invalidMods.push_back(InvalidGeodeFile {
-                    .path = entry.path(),
-                    .reason = res.unwrapErr(),
-                });
-                continue;
-            }
-            auto info = res.unwrap();
-
-            // skip this entry if it's already set to be loaded
-            if (ranges::contains(m_modsToLoad, info)) {
-                continue;
-            }
-
-            // add to list of mods to load
-            m_modsToLoad.push_back(info);
-        }
-    }
+void Loader::Impl::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) {
+    log::error("Called deprecated stub: Loader::loadModsFromDirectory");
 }
 
 void Loader::Impl::refreshModsList() {
-    log::debug("Loading mods...");
-
-    // find mods
-    for (auto& dir : m_modSearchDirectories) {
-        this->loadModsFromDirectory(dir);
-    }
-    
-    // load early-load mods first
-    for (auto& mod : m_modsToLoad) {
-        if (mod.needsEarlyLoad()) {
-            auto load = this->loadModFromInfo(mod);
-            if (!load) {
-                log::error("Unable to load {}: {}", mod.id(), load.unwrapErr());
-
-                m_invalidMods.push_back(InvalidGeodeFile {
-                    .path = mod.path(),
-                    .reason = load.unwrapErr(),
-                });
-            }
-        }
-    }
-
-    // UI can be loaded now
-    m_earlyLoadFinished = true;
-    m_earlyLoadFinishedCV.notify_all();
-
-    // load the rest of the mods
-    for (auto& mod : m_modsToLoad) {
-        if (!mod.needsEarlyLoad()) {
-            auto load = this->loadModFromInfo(mod);
-            if (!load) {
-                log::error("Unable to load {}: {}", mod.id(), load.unwrapErr());
-
-                m_invalidMods.push_back(InvalidGeodeFile {
-                    .path = mod.path(),
-                    .reason = load.unwrapErr(),
-                });
-            }
-        }
-    }
-    m_modsToLoad.clear();
+    log::error("Called deprecated stub: Loader::refreshModsList");
 }
 
 void Loader::Impl::updateAllDependencies() {
-    for (auto const& [_, mod] : m_mods) {
-        (void)mod->updateDependencies();
+    log::error("Called deprecated stub: Loader::updateAllDependencies");
+}
+
+void Loader::Impl::queueMods(std::vector<ModMetadata>& modQueue) {
+    for (auto const& dir : m_modSearchDirectories) {
+        log::debug("Searching {}", dir);
+        log::pushNest();
+        for (auto const& entry : ghc::filesystem::directory_iterator(dir)) {
+            if (!ghc::filesystem::is_regular_file(entry) ||
+                entry.path().extension() != GEODE_MOD_EXTENSION)
+                continue;
+
+            log::debug("Found {}", entry.path().filename());
+            log::pushNest();
+
+            auto res = ModMetadata::createFromGeodeFile(entry.path());
+            if (!res) {
+                m_problems.push_back({
+                    LoadProblem::Type::InvalidFile,
+                    entry.path(),
+                    res.unwrapErr()
+                });
+                log::error("Failed to queue: {}", res.unwrapErr());
+                log::popNest();
+                continue;
+            }
+            auto modMetadata = res.unwrap();
+
+            log::debug("id: {}", modMetadata.getID());
+            log::debug("version: {}", modMetadata.getVersion());
+
+            if (std::find_if(modQueue.begin(), modQueue.end(), [&](auto& item) {
+                    return modMetadata.getID() == item.getID();
+                }) != modQueue.end()) {
+                m_problems.push_back({
+                    LoadProblem::Type::Duplicate,
+                    modMetadata,
+                    "a mod with the same ID is already present"
+                });
+                log::error("Failed to queue: a mod with the same ID is already queued");
+                log::popNest();
+                continue;
+            }
+
+            modQueue.push_back(modMetadata);
+            log::popNest();
+        }
+        log::popNest();
     }
 }
 
+void Loader::Impl::populateModList(std::vector<ModMetadata>& modQueue) {
+    std::vector<std::string> toRemove;
+    for (auto& [id, mod] : m_mods) {
+        if (id == "geode.loader")
+            continue;
+        delete mod;
+        toRemove.push_back(id);
+    }
+    for (auto const& id : toRemove) {
+        m_mods.erase(id);
+    }
+
+    for (auto const& metadata : modQueue) {
+        log::debug("{} {}", metadata.getID(), metadata.getVersion());
+        log::pushNest();
+
+        auto mod = new Mod(metadata);
+
+        auto res = mod->m_impl->setup();
+        if (!res) {
+            m_problems.push_back({
+                LoadProblem::Type::SetupFailed,
+                mod,
+                res.unwrapErr()
+            });
+            log::error("Failed to set up: {}", res.unwrapErr());
+            log::popNest();
+            continue;
+        }
+
+        m_mods.insert({metadata.getID(), mod});
+
+        queueInGDThread([this, mod]() {
+            auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
+            CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
+            updateModResources(mod);
+        });
+
+        log::popNest();
+    }
+}
+
+void Loader::Impl::buildModGraph() {
+    for (auto const& [id, mod] : m_mods) {
+        log::debug("{}", mod->getID());
+        log::pushNest();
+        for (auto& dependency : mod->m_impl->m_metadata.m_impl->m_dependencies) {
+            log::debug("{}", dependency.id);
+            if (!m_mods.contains(dependency.id)) {
+                dependency.mod = nullptr;
+                continue;
+            }
+
+            dependency.mod = m_mods[dependency.id];
+
+            if (!dependency.version.compare(dependency.mod->getVersion())) {
+                dependency.mod = nullptr;
+                continue;
+            }
+
+            if (dependency.importance != ModMetadata::Dependency::Importance::Required || dependency.mod == nullptr)
+                continue;
+
+            dependency.mod->m_impl->m_dependants.push_back(mod);
+        }
+        for (auto& incompatibility : mod->m_impl->m_metadata.m_impl->m_incompatibilities) {
+            incompatibility.mod =
+                m_mods.contains(incompatibility.id) ? m_mods[incompatibility.id] : nullptr;
+        }
+        log::popNest();
+    }
+}
+
+void Loader::Impl::loadModGraph(Mod* node) {
+    if (node->hasUnresolvedDependencies())
+        return;
+    if (node->hasUnresolvedIncompatibilities())
+        return;
+
+    log::debug("{} {}", node->getID(), node->getVersion());
+    log::pushNest();
+
+    if (node->isLoaded()) {
+        for (auto const& dep : node->m_impl->m_dependants) {
+            this->loadModGraph(dep);
+        }
+        log::popNest();
+        return;
+    }
+
+    log::debug("Load");
+    auto res = node->m_impl->loadBinary();
+    if (!res) {
+        m_problems.push_back({
+            LoadProblem::Type::LoadFailed,
+            node,
+            res.unwrapErr()
+        });
+        log::error("Failed to load binary: {}", res.unwrapErr());
+        log::popNest();
+        return;
+    }
+
+    if (Mod::get()->getSavedValue<bool>("should-load-" + node->getID(), true)) {
+        log::debug("Enable");
+        res = node->m_impl->enable();
+        if (!res) {
+            node->m_impl->m_enabled = true;
+            (void)node->m_impl->disable();
+            m_problems.push_back({
+                LoadProblem::Type::EnableFailed,
+                node,
+                res.unwrapErr()
+            });
+            log::error("Failed to enable: {}", res.unwrapErr());
+            log::popNest();
+            return;
+        }
+
+        for (auto const& dep : node->m_impl->m_dependants) {
+            this->loadModGraph(dep);
+        }
+    }
+
+    log::popNest();
+}
+
+void Loader::Impl::findProblems() {
+    for (auto const& [id, mod] : m_mods) {
+        log::debug(id);
+        log::pushNest();
+
+        for (auto const& dep : mod->getMetadata().getDependencies()) {
+            if (dep.mod && dep.mod->isLoaded() && dep.version.compare(dep.mod->getVersion()))
+                continue;
+            switch(dep.importance) {
+                case ModMetadata::Dependency::Importance::Suggested:
+                    m_problems.push_back({
+                        LoadProblem::Type::Suggestion,
+                        mod,
+                        fmt::format("{} {}", dep.id, dep.version.toString())
+                    });
+                    log::info("{} suggests {} {}", id, dep.id, dep.version);
+                    break;
+                case ModMetadata::Dependency::Importance::Recommended:
+                    m_problems.push_back({
+                        LoadProblem::Type::Recommendation,
+                        mod,
+                        fmt::format("{} {}", dep.id, dep.version.toString())
+                    });
+                    log::warn("{} recommends {} {}", id, dep.id, dep.version);
+                    break;
+                case ModMetadata::Dependency::Importance::Required:
+                    m_problems.push_back({
+                        LoadProblem::Type::MissingDependency,
+                        mod,
+                        fmt::format("{} {}", dep.id, dep.version.toString())
+                    });
+                    log::error("{} requires {} {}", id, dep.id, dep.version);
+                    break;
+            }
+        }
+
+        for (auto const& dep : mod->getMetadata().getIncompatibilities()) {
+            if (!dep.mod || !dep.version.compare(dep.mod->getVersion()))
+                continue;
+            m_problems.push_back({
+                LoadProblem::Type::PresentIncompatibility,
+                mod,
+                fmt::format("{} {}", dep.id, dep.version.toString())
+            });
+            log::error("{} is incompatible with {} {}", id, dep.id, dep.version);
+        }
+
+        Mod* myEpicMod = mod; // clang fix
+        // if the mod is not loaded but there are no problems related to it
+        if (!mod->isLoaded() && !std::any_of(m_problems.begin(), m_problems.end(), [myEpicMod](auto& item) {
+                return std::holds_alternative<ModMetadata>(item.cause) &&
+                    std::get<ModMetadata>(item.cause).getID() == myEpicMod->getID() ||
+                    std::holds_alternative<Mod*>(item.cause) &&
+                    std::get<Mod*>(item.cause) == myEpicMod;
+            })) {
+            m_problems.push_back({
+                LoadProblem::Type::Unknown,
+                mod,
+                ""
+            });
+            log::error("{} failed to load for an unknown reason", id);
+        }
+
+        log::popNest();
+    }
+}
+
+void Loader::Impl::refreshModGraph() {
+    log::info("Refreshing mod graph...");
+    log::pushNest();
+
+    if (m_mods.size() > 1) {
+        log::error("Cannot refresh mod graph after startup");
+        log::popNest();
+        return;
+    }
+
+    m_problems.clear();
+
+    log::debug("Queueing mods");
+    log::pushNest();
+    std::vector<ModMetadata> modQueue;
+    this->queueMods(modQueue);
+    log::popNest();
+
+    log::debug("Populating mod list");
+    log::pushNest();
+    this->populateModList(modQueue);
+    modQueue.clear();
+    log::popNest();
+
+    log::debug("Building mod graph");
+    log::pushNest();
+    this->buildModGraph();
+    log::popNest();
+
+    // TODO: not early load
+    log::debug("Loading mods");
+    log::pushNest();
+    for (auto const& dep : Mod::get()->m_impl->m_dependants) {
+        this->loadModGraph(dep);
+    }
+    log::popNest();
+
+    log::debug("Finding problems");
+    log::pushNest();
+    this->findProblems();
+    log::popNest();
+
+    m_earlyLoadFinished = true;
+    m_earlyLoadFinishedCV.notify_all();
+
+    log::popNest();
+}
+
+std::vector<LoadProblem> Loader::Impl::getProblems() const {
+    return m_problems;
+}
+
 void Loader::Impl::waitForModsToBeLoaded() {
     auto lock = std::unique_lock(m_earlyLoadFinishedMutex);
     log::debug("Waiting for mods to be loaded... {}", bool(m_earlyLoadFinished));
diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp
index 7b4fd48e..aac71705 100644
--- a/loader/src/loader/LoaderImpl.hpp
+++ b/loader/src/loader/LoaderImpl.hpp
@@ -54,8 +54,7 @@ namespace geode {
         mutable std::mutex m_mutex;
 
         std::vector<ghc::filesystem::path> m_modSearchDirectories;
-        std::vector<ModInfo> m_modsToLoad;
-        std::vector<InvalidGeodeFile> m_invalidMods;
+        std::vector<LoadProblem> m_problems;
         std::unordered_map<std::string, Mod*> m_mods;
         std::vector<ghc::filesystem::path> m_texturePaths;
         bool m_isSetup = false;
@@ -113,7 +112,7 @@ namespace geode {
 
         friend void GEODE_CALL ::geode_implicit_load(Mod*);
 
-        Result<Mod*> loadModFromInfo(ModInfo const& info);
+        [[deprecated]] Result<Mod*> loadModFromInfo(ModInfo const& info);
 
         Result<> setup();
         void forceReset();
@@ -126,17 +125,24 @@ namespace geode {
         VersionInfo maxModVersion();
         bool isModVersionSupported(VersionInfo const& version);
 
-        Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
-        void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
-        void refreshModsList();
+        [[deprecated]] Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
+        [[deprecated]] void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
+        [[deprecated]] void refreshModsList();
+        void queueMods(std::vector<ModMetadata>& modQueue);
+        void populateModList(std::vector<ModMetadata>& modQueue);
+        void buildModGraph();
+        void loadModGraph(Mod* node);
+        void findProblems();
+        void refreshModGraph();
         bool isModInstalled(std::string const& id) const;
         Mod* getInstalledMod(std::string const& id) const;
         bool isModLoaded(std::string const& id) const;
         Mod* getLoadedMod(std::string const& id) const;
         std::vector<Mod*> getAllMods();
-        Mod* getModImpl();
-        void updateAllDependencies();
-        std::vector<InvalidGeodeFile> getFailedMods() const;
+        [[deprecated]] Mod* getModImpl();
+        [[deprecated]] void updateAllDependencies();
+        [[deprecated]] std::vector<InvalidGeodeFile> getFailedMods() const;
+        std::vector<LoadProblem> getProblems() const;
 
         void updateResources();
         void updateResources(bool forceReload);
diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp
index 46b8774e..b6696fcc 100644
--- a/loader/src/loader/Mod.cpp
+++ b/loader/src/loader/Mod.cpp
@@ -4,6 +4,7 @@
 
 using namespace geode::prelude;
 
+#pragma warning(suppress : 4996)
 Mod::Mod(ModInfo const& info) : m_impl(std::make_unique<Impl>(this, info)) {}
 Mod::Mod(ModMetadata const& metadata) : m_impl(std::make_unique<Impl>(this, metadata)) {}
 
@@ -53,6 +54,14 @@ bool Mod::supportsDisabling() const {
     return m_impl->supportsDisabling();
 }
 
+bool Mod::canDisable() const {
+    return m_impl->canDisable();
+}
+
+bool Mod::canEnable() const {
+    return m_impl->canEnable();
+}
+
 bool Mod::supportsUnloading() const {
     return false;
 }
@@ -153,11 +162,11 @@ Result<> Mod::unpatch(Patch* patch) {
 }
 
 Result<> Mod::loadBinary() {
-    return m_impl->loadBinary();
+    return Err("Load mod binaries after startup is not supported");
 }
 
 Result<> Mod::unloadBinary() {
-    return m_impl->unloadBinary();
+    return Err("Unloading mod binaries is not supported");
 }
 
 Result<> Mod::enable() {
@@ -180,14 +189,19 @@ bool Mod::depends(std::string const& id) const {
     return m_impl->depends(id);
 }
 
-bool Mod::hasUnresolvedDependencies() const {
-    return m_impl->hasUnresolvedDependencies();
-}
-
 Result<> Mod::updateDependencies() {
     return m_impl->updateDependencies();
 }
 
+bool Mod::hasUnresolvedDependencies() const {
+    return m_impl->hasUnresolvedDependencies();
+}
+
+bool Mod::hasUnresolvedIncompatibilities() const {
+    return m_impl->hasUnresolvedIncompatibilities();
+}
+
+#pragma warning(suppress : 4996)
 std::vector<Dependency> Mod::getUnresolvedDependencies() {
     return m_impl->getUnresolvedDependencies();
 }
diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp
index 78f58c70..829cbcfd 100644
--- a/loader/src/loader/ModImpl.cpp
+++ b/loader/src/loader/ModImpl.cpp
@@ -28,9 +28,7 @@ Mod::Impl* ModImpl::getImpl(Mod* mod)  {
 Mod::Impl::Impl(Mod* self, ModMetadata const& metadata) : m_self(self), m_metadata(metadata) {
 }
 
-Mod::Impl::~Impl() {
-    (void)this->unloadBinary();
-}
+Mod::Impl::~Impl() = default;
 
 Result<> Mod::Impl::setup() {
     m_saveDirPath = dirs::getModsSaveDir() / m_metadata.getID();
@@ -110,11 +108,23 @@ bool Mod::Impl::isLoaded() const {
 }
 
 bool Mod::Impl::supportsDisabling() const {
-    return m_info.supportsDisabling();
+    return m_metadata.getID() != "geode.loader" && !m_metadata.isAPI();
 }
 
-bool Mod::Impl::supportsUnloading() const {
-    return m_info.supportsUnloading();
+bool Mod::Impl::canDisable() const {
+    auto deps = m_dependants;
+    return this->supportsDisabling() &&
+        (deps.empty() || std::all_of(deps.begin(), deps.end(), [&](auto& item) {
+               return item->canDisable();
+           }));
+}
+
+bool Mod::Impl::canEnable() const {
+    auto deps = m_metadata.getDependencies();
+    return !this->isUninstalled() &&
+        (deps.empty() || std::all_of(deps.begin(), deps.end(), [&](auto& item) {
+                return item.isResolved();
+           }));
 }
 
 bool Mod::Impl::wasSuccessfullyLoaded() const {
@@ -304,11 +314,6 @@ Result<> Mod::Impl::loadBinary() {
     log::debug("Loading binary for mod {}", m_metadata.getID());
     if (m_binaryLoaded)
         return Ok();
-    }
-
-    if (this->hasUnresolvedDependencies()) {
-        return Err("Mod has unresolved dependencies");
-    }
 
     LoaderImpl::get()->provideNextMod(m_self);
 
@@ -327,54 +332,30 @@ Result<> Mod::Impl::loadBinary() {
         ModStateEvent(m_self, ModEventType::Loaded).post();
     });
 
-    Loader::get()->updateAllDependencies();
-    
-    log::debug("Enabling mod {}", m_info.id());
-    GEODE_UNWRAP(this->enable());
-
-    return Ok();
-}
-
-Result<> Mod::Impl::unloadBinary() {
-    if (!m_binaryLoaded) {
-        return Ok();
-    }
-
-    if (!m_info.supportsUnloading()) {
-        return Err("Mod does not support unloading");
-    }
-
-    GEODE_UNWRAP(this->saveData());
-
-    GEODE_UNWRAP(this->disable());
-    Loader::get()->queueInGDThread([&]() {
-        ModStateEvent(m_self, ModEventType::Unloaded).post();
-    });
-
-    // Disabling unhooks and unpatches already
-    for (auto const& hook : m_hooks) {
-        delete hook;
-    }
-    m_hooks.clear();
-
-    for (auto const& patch : m_patches) {
-        delete patch;
-    }
-    m_patches.clear();
-
-    GEODE_UNWRAP(this->unloadPlatformBinary());
-    m_binaryLoaded = false;
-
-    Loader::get()->updateAllDependencies();
-
     return Ok();
 }
 
 Result<> Mod::Impl::enable() {
-    if (!m_binaryLoaded) {
-        return this->loadBinary();
+    if (!m_binaryLoaded)
+        return Err("Tried to enable {} but its binary is not loaded", m_metadata.getID());
+
+    bool enabledDependencies = true;
+    for (auto const& item : m_metadata.getDependencies()) {
+        if (!item.isResolved())
+            continue;
+        auto res = item.mod->enable();
+        if (!res) {
+            enabledDependencies = false;
+            log::error("Failed to enable {}: {}", item.id, res.unwrapErr());
+        }
     }
 
+    if (!enabledDependencies)
+        return Err("Mod cannot be enabled because one or more of its dependencies cannot be enabled.");
+
+    if (!this->canEnable())
+        return Err("Mod cannot be enabled because it has unresolved dependencies.");
+
     for (auto const& hook : m_hooks) {
         if (!hook) {
             log::warn("Hook is null in mod \"{}\"", m_metadata.getName());
@@ -401,41 +382,66 @@ Result<> Mod::Impl::enable() {
 }
 
 Result<> Mod::Impl::disable() {
-    if (!m_enabled) {
+    if (!m_enabled)
         return Ok();
+
+    if (!this->supportsDisabling())
+        return Err("Mod does not support disabling.");
+
+    if (!this->canDisable())
+        return Err("Mod cannot be disabled because one or more of its dependants cannot be disabled.");
+
+    // disable dependants
+    bool disabledDependants = true;
+    for (auto& item : m_dependants) {
+        auto res = item->disable();
+        if (res)
+            continue;
+        disabledDependants = false;
+        log::error("Failed to disable {}: {}", item->getID(), res.unwrapErr());
     }
-    if (!m_info.supportsDisabling()) {
-        return Err("Mod does not support disabling");
-    }
+
+    if (!disabledDependants)
+        return Err("Mod cannot be disabled because one or more of its dependants cannot be disabled.");
 
     Loader::get()->queueInGDThread([&]() {
         ModStateEvent(m_self, ModEventType::Disabled).post();
     });
 
+    std::vector<std::string> errors;
     for (auto const& hook : m_hooks) {
-        GEODE_UNWRAP(this->disableHook(hook));
+        auto res = this->disableHook(hook);
+        if (!res)
+            errors.push_back(res.unwrapErr());
     }
     for (auto const& patch : m_patches) {
-        if (!patch->restore()) {
-            return Err("Unable to restore patch at " + std::to_string(patch->getAddress()));
-        }
+        auto res = this->unpatch(patch);
+        if (!res)
+            errors.push_back(res.unwrapErr());
     }
 
     m_enabled = false;
 
+    if (!errors.empty())
+        return Err(utils::string::join(errors, "\n"));
+
     return Ok();
 }
 
 Result<> Mod::Impl::uninstall() {
-    if (m_info.supportsDisabling()) {
+    if (supportsDisabling()) {
         GEODE_UNWRAP(this->disable());
-        if (m_info.supportsUnloading()) {
-            GEODE_UNWRAP(this->unloadBinary());
+    }
+    else {
+        for (auto& item : m_dependants) {
+            if (!item->canDisable())
+                continue;
+            GEODE_UNWRAP(item->disable());
         }
     }
 
     try {
-        ghc::filesystem::remove(m_info.path());
+        ghc::filesystem::remove(m_metadata.getPath());
     }
     catch (std::exception& e) {
         return Err(
@@ -455,51 +461,12 @@ bool Mod::Impl::isUninstalled() const {
 // Dependencies
 
 Result<> Mod::Impl::updateDependencies() {
-    bool hasUnresolved = false;
-    for (auto& dep : m_info.dependencies()) {
-        // set the dependency's loaded mod if such exists
-        if (!dep.mod) {
-            dep.mod = Loader::get()->getLoadedMod(dep.id);
-            // verify loaded dependency version
-            if (dep.mod && !dep.version.compare(dep.mod->getVersion())) {
-                dep.mod = nullptr;
-            }
-        }
-
-        // check if the dependency is loaded
-        if (dep.mod) {
-            // update the dependency recursively
-            GEODE_UNWRAP(dep.mod->updateDependencies());
-
-            // enable mod if it's resolved & enabled
-            if (!dep.mod->hasUnresolvedDependencies()) {
-                if (dep.mod->isEnabled()) {
-                    GEODE_UNWRAP(dep.mod->loadBinary().expect("Unable to load dependency: {error}"));
-                }
-            }
-        }
-        // check if the dependency is resolved now
-        if (!dep.isResolved()) {
-            GEODE_UNWRAP(this->unloadBinary().expect("Unable to unload mod: {error}"));
-            hasUnresolved = true;
-        }
-    }
-    // load if there weren't any unresolved dependencies
-    if (!hasUnresolved && !m_binaryLoaded) {
-        log::debug("All dependencies for {} found", m_info.id());
-        if (m_enabled) {
-            log::debug("Resolved & loading {}", m_info.id());
-            GEODE_UNWRAP(this->loadBinary());
-        }
-        else {
-            log::debug("Resolved {}, however not loading it as it is disabled", m_info.id());
-        }
-    }
-    return Ok();
+    return Err("Mod::updateDependencies is no longer needed, "
+        "as this is handled by Loader::refreshModGraph");
 }
 
 bool Mod::Impl::hasUnresolvedDependencies() const {
-    for (auto const& dep : m_info.dependencies()) {
+    for (auto const& dep : m_metadata.getDependencies()) {
         if (!dep.isResolved()) {
             return true;
         }
@@ -507,10 +474,23 @@ bool Mod::Impl::hasUnresolvedDependencies() const {
     return false;
 }
 
+bool Mod::Impl::hasUnresolvedIncompatibilities() const {
+    for (auto const& dep : m_metadata.getIncompatibilities()) {
+        if (!dep.isResolved()) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// msvc stop fucking screaming please i BEG YOU
+#pragma warning(suppress : 4996)
 std::vector<Dependency> Mod::Impl::getUnresolvedDependencies() {
+#pragma warning(suppress : 4996)
     std::vector<Dependency> unresolved;
     for (auto const& dep : m_metadata.getDependencies()) {
         if (!dep.isResolved()) {
+#pragma warning(suppress : 4996)
             unresolved.push_back(dep);
         }
     }
@@ -541,7 +521,7 @@ Result<> Mod::Impl::disableHook(Hook* hook) {
 Result<Hook*> Mod::Impl::addHook(Hook* hook) {
     m_hooks.push_back(hook);
     if (LoaderImpl::get()->isReadyToHook()) {
-        if (hook->getAutoEnable()) {
+        if (this->isEnabled() && hook->getAutoEnable()) {
             auto res = this->enableHook(hook);
             if (!res) {
                 delete hook;
@@ -582,21 +562,20 @@ Result<Patch*> Mod::Impl::patch(void* address, ByteVector const& data) {
     p->m_original = readMemory(address, data.size());
     p->m_owner = m_self;
     p->m_patch = data;
-    if (!p->apply()) {
+    if (this->isEnabled() && !p->apply()) {
         delete p;
-        return Err("Unable to enable patch at " + std::to_string(p->getAddress()));
+        return Err("Unable to enable patch at " + std::to_string(reinterpret_cast<uintptr_t>(address)));
     }
     m_patches.push_back(p);
     return Ok(p);
 }
 
 Result<> Mod::Impl::unpatch(Patch* patch) {
-    if (patch->restore()) {
-        ranges::remove(m_patches, patch);
-        delete patch;
-        return Ok();
-    }
-    return Err("Unable to restore patch!");
+    if (!patch->restore())
+        return Err("Unable to restore patch at " + std::to_string(patch->getAddress()));
+    ranges::remove(m_patches, patch);
+    delete patch;
+    return Ok();
 }
 
 // Misc.
@@ -697,27 +676,24 @@ static Result<ModMetadata> getModImplInfo() {
 
 Mod* Loader::Impl::createInternalMod() {
     auto& mod = Mod::sharedMod<>;
-    if (!mod) {
-        auto infoRes = getModImplInfo();
-        if (!infoRes) {
-            LoaderImpl::get()->platformMessageBox(
-                "Fatal Internal Error",
-                "Unable to create internal mod info: \"" + infoRes.unwrapErr() +
-                    "\"\n"
-                    "This is a fatal internal error in the loader, please "
-                    "contact Geode developers immediately!"
-            );
-            auto info = ModInfo();
-            info.id() = "geode.loader";
-            mod = new Mod(info);
-        }
-        else {
-            mod = new Mod(infoRes.unwrap());
-        }
-        mod->m_impl->m_binaryLoaded = true;
-        mod->m_impl->m_enabled = true;
-        m_mods.insert({ mod->getID(), mod });
+    if (mod) return mod;
+    auto infoRes = getModImplInfo();
+    if (!infoRes) {
+        LoaderImpl::get()->platformMessageBox(
+            "Fatal Internal Error",
+            "Unable to create internal mod info: \"" + infoRes.unwrapErr() +
+                "\"\n"
+                "This is a fatal internal error in the loader, please "
+                "contact Geode developers immediately!"
+        );
+        mod = new Mod(ModMetadata("geode.loader"));
     }
+    else {
+        mod = new Mod(infoRes.unwrap());
+    }
+    mod->m_impl->m_binaryLoaded = true;
+    mod->m_impl->m_enabled = true;
+    m_mods.insert({ mod->getID(), mod });
     return mod;
 }
 
diff --git a/loader/src/loader/ModImpl.hpp b/loader/src/loader/ModImpl.hpp
index 14f1b7f6..a4b2560f 100644
--- a/loader/src/loader/ModImpl.hpp
+++ b/loader/src/loader/ModImpl.hpp
@@ -39,12 +39,11 @@ namespace geode {
          */
         ghc::filesystem::path m_saveDirPath;
         /**
-         * Pointers to mods that depend on
-         * this Mod. Makes it possible to
-         * enable / disable them automatically,
+         * Pointers to mods that depend on this Mod.
+         * Makes it possible to enable / disable them automatically,
          * when their dependency is disabled.
          */
-        std::vector<Mod*> m_parentDependencies;
+        std::vector<Mod*> m_dependants;
         /**
          * Saved values
          */
@@ -84,8 +83,8 @@ namespace geode {
         bool isEnabled() const;
         bool isLoaded() const;
         bool supportsDisabling() const;
-        bool wasSuccesfullyLoaded() const;
-        ModInfo getModInfo() const;
+        bool canDisable() const;
+        bool canEnable() const;
         bool wasSuccessfullyLoaded() const;
         ModMetadata getMetadata() const;
         ghc::filesystem::path getTempDir() const;
@@ -113,16 +112,17 @@ namespace geode {
         Result<> removeHook(Hook* hook);
         Result<Patch*> patch(void* address, ByteVector const& data);
         Result<> unpatch(Patch* patch);
-        Result<> loadBinary();
-        Result<> unloadBinary();
         Result<> enable();
         Result<> disable();
         Result<> uninstall();
         bool isUninstalled() const;
         bool depends(std::string const& id) const;
-        bool hasUnresolvedDependencies() const;
         Result<> updateDependencies();
-        std::vector<Dependency> getUnresolvedDependencies();
+        bool hasUnresolvedDependencies() const;
+        bool hasUnresolvedIncompatibilities() const;
+        [[deprecated]] std::vector<Dependency> getUnresolvedDependencies();
+
+        Result<> loadBinary();
 
         char const* expandSpriteName(char const* name);
         ModJson getRuntimeInfo() const;
diff --git a/loader/src/loader/ModInfoImpl.cpp b/loader/src/loader/ModInfoImpl.cpp
index 7c1e4ed3..d968c386 100644
--- a/loader/src/loader/ModInfoImpl.cpp
+++ b/loader/src/loader/ModInfoImpl.cpp
@@ -5,8 +5,7 @@
 
 #include "ModInfoImpl.hpp"
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#pragma warning(disable : 4996) // deprecation
 
 using namespace geode::prelude;
 
@@ -20,34 +19,6 @@ bool Dependency::isResolved() const {
          this->version.compare(this->mod->getVersion()));
 }
 
-bool ModInfo::Impl::validateID(std::string const& id) {
-    return ModMetadata::Impl::validateID(id);
-}
-
-Result<ModInfo> ModInfo::Impl::create(ModJson const& json) {
-    return ModMetadata::Impl::create(json);
-}
-
-Result<ModInfo> ModInfo::Impl::createFromFile(ghc::filesystem::path const& path) {
-    return ModMetadata::Impl::createFromFile(path);
-}
-
-Result<ModInfo> ModInfo::Impl::createFromGeodeFile(ghc::filesystem::path const& path) {
-    return ModMetadata::Impl::createFromGeodeFile(path);
-}
-
-Result<ModInfo> ModInfo::Impl::createFromGeodeZip(file::Unzip& unzip) {
-    return ModMetadata::Impl::createFromGeodeZip(unzip);
-}
-
-ModJson ModInfo::Impl::toJSON() const {
-    return m_metadata.m_rawJSON;
-}
-
-ModJson ModInfo::Impl::getRawJSON() const {
-    return m_metadata.m_rawJSON;
-}
-
 bool ModInfo::Impl::operator==(ModInfo::Impl const& other) const {
     return this->m_metadata.m_id == other.m_metadata.m_id;
 }
@@ -186,33 +157,34 @@ bool const& ModInfo::isAPI() const {
 }
 
 Result<ModInfo> ModInfo::createFromGeodeZip(utils::file::Unzip& zip) {
-    return Impl::createFromGeodeZip(zip);
+    return ModMetadata::Impl::createFromGeodeZip(zip);
 }
 
 Result<ModInfo> ModInfo::createFromGeodeFile(ghc::filesystem::path const& path) {
-    return Impl::createFromGeodeFile(path);
+    return ModMetadata::Impl::createFromGeodeFile(path);
 }
 
 Result<ModInfo> ModInfo::createFromFile(ghc::filesystem::path const& path) {
-    return Impl::createFromFile(path);
+    return ModMetadata::Impl::createFromFile(path);
 }
 
 Result<ModInfo> ModInfo::create(ModJson const& json) {
-    return Impl::create(json);
+    return ModMetadata::Impl::create(json);
 }
 
 ModJson ModInfo::toJSON() const {
-    return m_impl->toJSON();
+    return m_impl->m_metadata.m_rawJSON;
 }
 
 ModJson ModInfo::getRawJSON() const {
-    return m_impl->getRawJSON();
+    return m_impl->m_metadata.m_rawJSON;
 }
 
 bool ModInfo::operator==(ModInfo const& other) const {
     return m_impl->operator==(*other.m_impl);
 }
 
+#pragma warning(suppress : 4996)
 ModInfo::ModInfo() : m_impl(std::make_unique<Impl>()) {}
 
 ModInfo::ModInfo(ModInfo const& other) : m_impl(std::make_unique<Impl>(*other.m_impl)) {}
@@ -247,6 +219,26 @@ ModInfo::operator ModMetadata() const {
     return metadata;
 }
 
-ModInfo::~ModInfo() = default;
+ModJson& ModInfo::rawJSON() {
+    return m_impl->m_metadata.m_rawJSON;
+}
+ModJson const& ModInfo::rawJSON() const {
+    return m_impl->m_metadata.m_rawJSON;
+}
 
-#pragma clang diagnostic pop
+Result<ModInfo> ModInfo::createFromSchemaV010(geode::ModJson const& json) {
+    return ModMetadata::Impl::createFromSchemaV010(json);
+}
+
+Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) {
+    return m_impl->m_metadata.addSpecialFiles(dir);
+}
+Result<> ModInfo::addSpecialFiles(utils::file::Unzip& zip) {
+    return m_impl->m_metadata.addSpecialFiles(zip);
+}
+
+std::vector<std::pair<std::string, std::optional<std::string>*>> ModInfo::getSpecialFiles() {
+    return m_impl->m_metadata.getSpecialFiles();
+}
+
+ModInfo::~ModInfo() = default;
diff --git a/loader/src/loader/ModInfoImpl.hpp b/loader/src/loader/ModInfoImpl.hpp
index 3ebd981c..fcd80232 100644
--- a/loader/src/loader/ModInfoImpl.hpp
+++ b/loader/src/loader/ModInfoImpl.hpp
@@ -7,10 +7,12 @@
 #include <Geode/utils/JsonValidation.hpp>
 #include <Geode/utils/VersionInfo.hpp>
 
+#pragma warning(disable : 4996) // deprecation
+
 using namespace geode::prelude;
 
 namespace geode {
-    class ModInfo::Impl {
+    class [[deprecated]] ModInfo::Impl {
     public:
         ModMetadata::Impl m_metadata;
         std::optional<IssuesInfo> m_issues;
@@ -18,31 +20,11 @@ namespace geode {
         bool m_supportsDisabling = true;
         bool m_supportsUnloading = false;
 
-        static Result<ModInfo> createFromGeodeZip(utils::file::Unzip& zip);
-        static Result<ModInfo> createFromGeodeFile(ghc::filesystem::path const& path);
-        static Result<ModInfo> createFromFile(ghc::filesystem::path const& path);
-        static Result<ModInfo> create(ModJson const& json);
-
-        ModJson toJSON() const;
-        ModJson getRawJSON() const;
-
         bool operator==(ModInfo::Impl const& other) const;
-
-        static bool validateID(std::string const& id);
-
-        static Result<ModInfo> createFromSchemaV010(ModJson const& json);
-
-        Result<> addSpecialFiles(ghc::filesystem::path const& dir);
-        Result<> addSpecialFiles(utils::file::Unzip& zip);
-
-        std::vector<std::pair<std::string, std::optional<std::string>*>> getSpecialFiles();
     };
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-    class ModInfoImpl {
+    class [[deprecated]] ModInfoImpl {
     public:
         static ModInfo::Impl& getImpl(ModInfo& info);
     };
-#pragma clang diagnostic pop
 }
diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp
index f7157dd5..a814d611 100644
--- a/loader/src/loader/ModMetadataImpl.cpp
+++ b/loader/src/loader/ModMetadataImpl.cpp
@@ -22,11 +22,9 @@ bool ModMetadata::Dependency::isResolved() const {
 }
 
 bool ModMetadata::Incompatibility::isResolved() const {
-    return !this->mod || !this->mod->isLoaded() || !this->version.compare(this->mod->getVersion());
+    return !this->mod || !this->version.compare(this->mod->getVersion());
 }
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
 ModMetadata::Dependency::operator geode::Dependency() {
     return {id, version, importance == Importance::Required, mod};
 }
@@ -53,7 +51,6 @@ ModMetadata::Dependency ModMetadata::Dependency::fromDeprecated(geode::Dependenc
 ModMetadata::IssuesInfo ModMetadata::IssuesInfo::fromDeprecated(geode::IssuesInfo const& value) {
     return {value.info, value.url};
 }
-#pragma clang diagnostic pop
 
 static std::string sanitizeDetailsData(std::string const& str) {
     // delete CRLF
@@ -473,8 +470,6 @@ ModMetadata& ModMetadata::operator=(ModMetadata&& other) noexcept {
     return *this;
 }
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
 ModMetadata::operator ModInfo() {
     ModInfo info;
     auto infoImpl = ModInfoImpl::getImpl(info);
@@ -493,7 +488,6 @@ ModMetadata::operator ModInfo() const {
         infoImpl.m_dependencies.push_back(dep);
     return info;
 }
-#pragma clang diagnostic pop
 
 ModMetadata::~ModMetadata() = default;
 
@@ -504,6 +498,7 @@ struct json::Serialize<geode::ModMetadata::Dependency::Importance> {
             case geode::ModMetadata::Dependency::Importance::Required: return {"required"};
             case geode::ModMetadata::Dependency::Importance::Recommended: return {"recommended"};
             case geode::ModMetadata::Dependency::Importance::Suggested: return {"suggested"};
+            default: return {"unknown"};
         }
     }
     static geode::ModMetadata::Dependency::Importance GEODE_DLL from_json(json::Value const& importance) {
diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp
index ce47c675..92068f44 100644
--- a/loader/src/ui/internal/info/ModInfoPopup.cpp
+++ b/loader/src/ui/internal/info/ModInfoPopup.cpp
@@ -573,9 +573,9 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
         )->show();
     }
     if (as<CCMenuItemToggler*>(sender)->isToggled()) {
-        auto res = m_mod->loadBinary();
+        auto res = m_mod->enable();
         if (!res) {
-            FLAlertLayer::create(nullptr, "Error Loading Mod", res.unwrapErr(), "OK", nullptr)->show();
+            FLAlertLayer::create(nullptr, "Error Enabling Mod", res.unwrapErr(), "OK", nullptr)->show();
         }
     }
     else {
@@ -585,7 +585,7 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
         }
     }
     if (m_layer) {
-        m_layer->updateAllStates(nullptr);
+        m_layer->updateAllStates();
     }
     as<CCMenuItemToggler*>(sender)->toggle(m_mod->isEnabled());
 }
diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp
index 5944760d..4bd8c9fd 100644
--- a/loader/src/ui/internal/list/ModListCell.cpp
+++ b/loader/src/ui/internal/list/ModListCell.cpp
@@ -31,7 +31,8 @@ float ModListCell::getLogoSize() const {
 void ModListCell::setupInfo(
     ModMetadata const& metadata,
     bool spaceForTags,
-    ModListDisplay display
+    ModListDisplay display,
+    bool inactive
 ) {
     m_menu = CCMenu::create();
     m_menu->setPosition(m_width - 40.f, m_height / 2);
@@ -41,6 +42,10 @@ void ModListCell::setupInfo(
 
     auto logoSpr = this->createLogo({ logoSize, logoSize });
     logoSpr->setPosition({ logoSize / 2 + 12.f, m_height / 2 });
+    auto logoSprColor = typeinfo_cast<CCRGBAProtocol*>(logoSpr);
+    if (inactive && logoSprColor) {
+        logoSprColor->setColor({ 163, 163, 163 });
+    }
     this->addChild(logoSpr);
 
     bool hasDesc =
@@ -63,6 +68,9 @@ void ModListCell::setupInfo(
         titleLabel->setPositionY(m_height / 2 + 7.f);
     }
     titleLabel->limitLabelWidth(m_width / 2 - 40.f, .5f, .1f);
+    if (inactive) {
+        titleLabel->setColor({ 163, 163, 163 });
+    }
     this->addChild(titleLabel);
 
     auto versionLabel = CCLabelBMFont::create(
@@ -76,6 +84,9 @@ void ModListCell::setupInfo(
         titleLabel->getPositionY() - 1.f
     );
     versionLabel->setColor({ 0, 255, 0 });
+    if (inactive) {
+        versionLabel->setColor({ 0, 163, 0 });
+    }
     this->addChild(versionLabel);
 
     if (auto tag = metadata.getVersion().getTag()) {
@@ -93,6 +104,9 @@ void ModListCell::setupInfo(
     auto creatorStr = "by " + metadata.getDeveloper();
     auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
     creatorLabel->setScale(.43f);
+    if (inactive) {
+        creatorLabel->setColor({ 163, 163, 163 });
+    }
 
     m_developerBtn = CCMenuItemSpriteExtra::create(
         creatorLabel, this, menu_selector(ModListCell::onViewDev)
@@ -133,6 +147,9 @@ void ModListCell::setupInfo(
         m_description->setAnchorPoint({ .0f, .5f });
         m_description->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY());
         m_description->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f);
+        if (inactive) {
+            m_description->setColor({ 163, 163, 163 });
+        }
         this->addChild(m_description);
     }
 }
@@ -187,11 +204,14 @@ void ModCell::onEnable(CCObject* sender) {
     else {
         tryOrAlert(m_mod->disable(), "Error disabling mod");
     }
-    if (m_layer) {
-        m_layer->updateAllStates(this);
-    }
+    Loader::get()->queueInGDThread([this]() {
+        if (m_layer) {
+            m_layer->updateAllStates();
+        }
+    });
 }
 
+// TODO: for fod maybe :3 show problems related to this mod
 void ModCell::onUnresolvedInfo(CCObject*) {
     std::string info =
         "This mod has the following "
@@ -211,6 +231,11 @@ void ModCell::onInfo(CCObject*) {
     LocalModInfoPopup::create(m_mod, m_layer)->show();
 }
 
+void ModCell::onRestart(CCObject*) {
+    utils::game::restart();
+}
+
+// TODO: for fod maybe :3 check if there are any problems related to this mod
 void ModCell::updateState() {
     bool unresolved = m_mod->hasUnresolvedDependencies();
     if (m_enableToggle) {
@@ -232,18 +257,50 @@ bool ModCell::init(
 ) {
     if (!ModListCell::init(list, size))
         return false;
-
     m_mod = mod;
 
     this->setupInfo(mod->getMetadata(), false, display, m_mod->isUninstalled());
 
-    auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
-    viewSpr->setScale(.65f);
+    if (mod->isUninstalled()) {
+        auto restartSpr = ButtonSprite::create("Restart", "bigFont.fnt", "GJ_button_03.png", .8f);
+        restartSpr->setScale(.65f);
 
-    auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ModCell::onInfo));
-    m_menu->addChild(viewBtn);
+        auto restartBtn = CCMenuItemSpriteExtra::create(restartSpr, this, menu_selector(ModCell::onRestart));
+        restartBtn->setPositionX(-16.f);
+        m_menu->addChild(restartBtn);
+    }
+    else {
+        auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
+        viewSpr->setScale(.65f);
 
-    if (m_mod->wasSuccesfullyLoaded() && m_mod->supportsDisabling()) {
+        auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ModCell::onInfo));
+        m_menu->addChild(viewBtn);
+
+        if (m_mod->wasSuccessfullyLoaded()) {
+            auto latestIndexItem = Index::get()->getMajorItem(
+                mod->getMetadata().getID()
+            );
+
+            if (latestIndexItem && Index::get()->isUpdateAvailable(latestIndexItem)) {
+                viewSpr->updateBGImage("GE_button_01.png"_spr);
+
+                auto minorIndexItem = Index::get()->getItem(
+                    mod->getMetadata().getID(),
+                    ComparableVersionInfo(mod->getMetadata().getVersion(), VersionCompare::MoreEq)
+                );
+
+                if (latestIndexItem->getMetadata().getVersion().getMajor() > minorIndexItem->getMetadata().getVersion().getMajor()) {
+                    auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
+                    updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
+                    updateIcon->setZOrder(99);
+                    updateIcon->setScale(.5f);
+                    viewSpr->addChild(updateIcon);
+                }
+            }
+        }
+    }
+
+    if (m_mod->wasSuccessfullyLoaded() && m_mod->supportsDisabling() && !m_mod->isUninstalled()) {
         m_enableToggle =
             CCMenuItemToggler::createWithStandardSprites(this, menu_selector(ModCell::onEnable), .7f);
         m_enableToggle->setPosition(-45.f, 0.f);
@@ -259,30 +316,6 @@ bool ModCell::init(
     m_unresolvedExMark->setVisible(false);
     m_menu->addChild(m_unresolvedExMark);
 
-    if (m_mod->wasSuccesfullyLoaded()) {
-    
-        auto latestIndexItem = Index::get()->getMajorItem(
-            mod->getModInfo().id()
-        );
-        
-        if (latestIndexItem && Index::get()->isUpdateAvailable(latestIndexItem)) {
-            viewSpr->updateBGImage("GE_button_01.png"_spr);
-
-            auto minorIndexItem = Index::get()->getItem(
-                mod->getModInfo().id(),
-                ComparableVersionInfo(mod->getModInfo().version(), VersionCompare::MoreEq)
-            );
-
-            if (latestIndexItem->getModInfo().version().getMajor() > minorIndexItem->getModInfo().version().getMajor()) {
-                auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
-                updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
-                updateIcon->setZOrder(99);
-                updateIcon->setScale(.5f);
-                viewSpr->addChild(updateIcon);
-            }
-        }
-    }
-
     this->updateState();
 
     return true;
@@ -302,6 +335,10 @@ void IndexItemCell::onInfo(CCObject*) {
     IndexItemInfoPopup::create(m_item, m_layer)->show();
 }
 
+void IndexItemCell::onRestart(CCObject*) {
+    utils::game::restart();
+}
+
 IndexItemCell* IndexItemCell::create(
     IndexItemHandle item,
     ModListLayer* list,
@@ -327,11 +364,24 @@ bool IndexItemCell::init(
 
     m_item = item;
 
-    this->setupInfo(item->getModInfo(), item->getTags().size(), display);
     this->setupInfo(item->getMetadata(), item->getTags().size(), display, item->isInstalled());
 
-    auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
-    m_menu->addChild(viewBtn);
+    if (item->isInstalled()) {
+        auto restartSpr = ButtonSprite::create("Restart", "bigFont.fnt", "GJ_button_03.png", .8f);
+        restartSpr->setScale(.65f);
+
+        auto restartBtn = CCMenuItemSpriteExtra::create(restartSpr, this, menu_selector(IndexItemCell::onRestart));
+        restartBtn->setPositionX(-16.f);
+        m_menu->addChild(restartBtn);
+    }
+    else {
+        auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
+        viewSpr->setScale(.65f);
+
+        auto viewBtn =
+            CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
+        m_menu->addChild(viewBtn);
+    }
 
     if (item->getTags().size()) {
         float x = m_height / 2 + this->getLogoSize() / 2 + 13.f;
@@ -401,7 +451,6 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
             )
                 ->show();
         }
-        Loader::get()->refreshModsList();
         if (m_layer) {
             m_layer->reloadList();
         }
diff --git a/loader/src/ui/internal/list/ModListCell.hpp b/loader/src/ui/internal/list/ModListCell.hpp
index 3fe4c7ef..64892045 100644
--- a/loader/src/ui/internal/list/ModListCell.hpp
+++ b/loader/src/ui/internal/list/ModListCell.hpp
@@ -55,6 +55,7 @@ protected:
     );
 
     void onInfo(CCObject*);
+    void onRestart(CCObject*);
     void onEnable(CCObject*);
     void onUnresolvedInfo(CCObject*);
 
@@ -86,6 +87,7 @@ protected:
     );
 
     void onInfo(CCObject*);
+    void onRestart(CCObject*);
 
 public:
     static IndexItemCell* create(
diff --git a/loader/src/ui/internal/list/ModListLayer.cpp b/loader/src/ui/internal/list/ModListLayer.cpp
index 58103ce0..8a768c65 100644
--- a/loader/src/ui/internal/list/ModListLayer.cpp
+++ b/loader/src/ui/internal/list/ModListLayer.cpp
@@ -155,26 +155,32 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
                 ));
             }
 
-            // sort the mods by match score 
-            std::multimap<int, Mod*> sorted;
+            // sort the mods by match score
+            std::multimap<int, ModListCell*> sorted;
 
             // then other mods
+
+            // newly installed
+            for (auto const& item : Index::get()->getItems()) {
+                if (!item->isInstalled())
+                    continue;
+                if (auto match = queryMatch(query, item)) {
+                    auto cell = IndexItemCell::create(item, this, m_display, this->getCellSize());
+                    sorted.insert({ match.value(), cell });
+                }
+            }
+
+            // loaded
             for (auto const& mod : Loader::get()->getAllMods()) {
-                // if the mod is no longer installed nor
-                // loaded, it's as good as not existing
-                // (because it doesn't)
-                if (mod->isUninstalled() && !mod->isLoaded()) continue;
-                // only show mods that match query in list
                 if (auto match = queryMatch(query, mod)) {
-                    sorted.insert({ match.value(), mod });
+                    auto cell = ModCell::create(mod, this, m_display, this->getCellSize());
+                    sorted.insert({ match.value(), cell });
                 }
             }
 
             // add the mods sorted
-            for (auto& [score, mod] : ranges::reverse(sorted)) {
-                mods->addObject(ModCell::create(
-                    mod, this, m_display, this->getCellSize()
-                ));
+            for (auto& [score, cell] : ranges::reverse(sorted)) {
+                mods->addObject(cell);
             }
         } break;
 
@@ -546,14 +552,11 @@ void ModListLayer::reloadList(std::optional<ModListQuery> const& query) {
     }
 }
 
-void ModListLayer::updateAllStates(ModListCell* toggled) {
+void ModListLayer::updateAllStates() {
     for (auto cell : CCArrayExt<GenericListCell>(
         m_list->m_listView->m_tableView->m_cellArray
     )) {
-        auto node = static_cast<ModListCell*>(cell->getChildByID("mod-list-cell"));
-        if (toggled != node) {
-            node->updateState();
-        }
+        static_cast<ModListCell*>(cell->getChildByID("mod-list-cell"))->updateState();
     }
 }
 
@@ -612,7 +615,6 @@ void ModListLayer::onExit(CCObject*) {
 }
 
 void ModListLayer::onReload(CCObject*) {
-    Loader::get()->refreshModsList();
     this->reloadList();
 }
 
diff --git a/loader/src/ui/internal/list/ModListLayer.hpp b/loader/src/ui/internal/list/ModListLayer.hpp
index 4cff18d8..16ca3ec7 100644
--- a/loader/src/ui/internal/list/ModListLayer.hpp
+++ b/loader/src/ui/internal/list/ModListLayer.hpp
@@ -84,7 +84,7 @@ protected:
 public:
     static ModListLayer* create();
     static ModListLayer* scene();
-    void updateAllStates(ModListCell* except = nullptr);
+    void updateAllStates();
 
     ModListDisplay getDisplay() const;
     ModListQuery& getQuery();