diff --git a/bindings/GeometryDash.bro b/bindings/GeometryDash.bro
index de62d935..a1ef1f3a 100644
--- a/bindings/GeometryDash.bro
+++ b/bindings/GeometryDash.bro
@@ -4747,6 +4747,7 @@ class PlayerObject : GameObject, AnimatedSpriteDelegate {
     cocos2d::CCSprite* m_unk500;
     cocos2d::CCSprite* m_vehicleSpriteWhitener;
     cocos2d::CCSprite* m_vehicleGlow;
+    PAD = mac 0x8; // idk about windows
     cocos2d::CCMotionStreak* m_regularTrail;
     HardStreak* m_waveTrail;
     double m_xAccel;
diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt
index 4078d47f..b715a2cd 100644
--- a/loader/CMakeLists.txt
+++ b/loader/CMakeLists.txt
@@ -106,6 +106,8 @@ package_geode_resources_now(
 )
 
 target_include_directories(${PROJECT_NAME} PRIVATE
+	src/
+	src/loader/
 	src/internal/
 	src/platform/
 	src/gui/
diff --git a/loader/include/Geode/loader/Loader.hpp b/loader/include/Geode/loader/Loader.hpp
index ef1cbab3..82eab6cd 100644
--- a/loader/include/Geode/loader/Loader.hpp
+++ b/loader/include/Geode/loader/Loader.hpp
@@ -1,12 +1,13 @@
 #pragma once
 
-#include "Types.hpp"
-#include "Log.hpp"
 #include "../external/filesystem/fs/filesystem.hpp"
-#include <mutex>
-#include <atomic>
 #include "../utils/Result.hpp"
+#include "Log.hpp"
 #include "ModInfo.hpp"
+#include "Types.hpp"
+
+#include <atomic>
+#include <mutex>
 
 namespace geode {
     using ScheduledFunction = std::function<void GEODE_CALL(void)>;
@@ -16,18 +17,16 @@ namespace geode {
         std::string reason;
     };
 
-    class GEODE_DLL Loader {
-    protected:
-        std::vector<ghc::filesystem::path> m_modSearchDirectories;
-        std::vector<ModInfo> m_modsToLoad;
-        std::vector<InvalidGeodeFile> m_invalidMods;
-        std::unordered_map<std::string, Mod*> m_mods;
-        std::vector<ghc::filesystem::path> m_texturePaths;
-        std::vector<ScheduledFunction> m_scheduledFunctions;
-        mutable std::mutex m_scheduledFunctionsMutex;
-        bool m_isSetup = false;
-        std::atomic_bool m_earlyLoadFinished = false;
+    class LoaderImpl;
 
+    class GEODE_DLL Loader {
+    private:
+        class Impl;
+        std::unique_ptr<Impl> m_impl;
+        Loader();
+        ~Loader();
+
+    protected:
         void createDirectories();
 
         void updateModResources(Mod* mod);
@@ -39,31 +38,26 @@ namespace geode {
         Result<Mod*> loadModFromInfo(ModInfo const& info);
 
     public:
-        ~Loader();
+        // TODO: do we want to expose all of these functions?
         static Loader* get();
 
-        Result<> setup();
-
         Result<> saveData();
         Result<> loadData();
 
-        static VersionInfo getVersion();
-        static VersionInfo minModVersion();
-        static VersionInfo maxModVersion();
-        static bool isModVersionSupported(VersionInfo const& version);
+        VersionInfo getVersion();
+        VersionInfo minModVersion();
+        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 loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
         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();
-        static Mod* getInternalMod();
+        Mod* getInternalMod();
         void updateAllDependencies();
         std::vector<InvalidGeodeFile> getFailedMods() const;
 
@@ -72,16 +66,18 @@ namespace geode {
         void queueInGDThread(ScheduledFunction func);
         void scheduleOnModLoad(Mod* mod, ScheduledFunction func);
         void waitForModsToBeLoaded();
-        
+
         /**
          * Open the platform-specific external console (if one exists)
          */
-        static void openPlatformConsole();
+        void openPlatformConsole();
         /**
          * Close the platform-specific external console (if one exists)
          */
-        static void closePlatfromConsole();
+        void closePlatformConsole();
 
         bool didLastLaunchCrash() const;
+
+        friend class LoaderImpl;
     };
 }
diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp
index fcb613b3..c6b84ddf 100644
--- a/loader/include/Geode/loader/Mod.hpp
+++ b/loader/include/Geode/loader/Mod.hpp
@@ -1,16 +1,16 @@
 #pragma once
 
-#include "Types.hpp"
-#include "Hook.hpp"
-#include "Setting.hpp"
-#include "ModInfo.hpp"
-
 #include "../DefaultInclude.hpp"
+#include "../cocos/support/zip_support/ZipUtils.h"
+#include "../external/json/json.hpp"
 #include "../utils/Result.hpp"
 #include "../utils/VersionInfo.hpp"
-#include "../external/json/json.hpp"
 #include "../utils/general.hpp"
-#include "../cocos/support/zip_support/ZipUtils.h"
+#include "Hook.hpp"
+#include "ModInfo.hpp"
+#include "Setting.hpp"
+#include "Types.hpp"
+
 #include <optional>
 #include <string_view>
 #include <type_traits>
@@ -18,15 +18,14 @@
 #include <vector>
 
 namespace geode {
-    template<class T>
+    template <class T>
     struct HandleToSaved : public T {
         Mod* m_mod;
         std::string m_key;
 
-        HandleToSaved(std::string const& key, Mod* mod, T const& value)
-          : T(value),
-            m_key(key),
-            m_mod(mod) {}
+        HandleToSaved(std::string const& key, Mod* mod, T const& value) :
+            T(value), m_key(key), m_mod(mod) {}
+
         HandleToSaved(HandleToSaved const&) = delete;
         HandleToSaved(HandleToSaved&&) = delete;
         ~HandleToSaved();
@@ -104,7 +103,6 @@ namespace geode {
 
         friend class ::InternalMod;
         friend class Loader;
-        friend class ::InternalLoader;
         friend struct ModInfo;
 
         template <class = void>
@@ -169,41 +167,45 @@ namespace geode {
             return false;
         }
 
-        template<class T>
+        template <class T>
         T getSavedValue(std::string const& key) {
             if (m_saved.count(key)) {
                 try {
                     // json -> T may fail
                     return m_saved.at(key);
-                } catch(...) {}
+                }
+                catch (...) {
+                }
             }
             return T();
         }
 
-        template<class T>
+        template <class T>
         T getSavedValue(std::string const& key, T const& defaultValue) {
             if (m_saved.count(key)) {
                 try {
                     // json -> T may fail
                     return m_saved.at(key);
-                } catch(...) {}
+                }
+                catch (...) {
+                }
             }
             m_saved[key] = defaultValue;
             return defaultValue;
         }
 
-        template<class T>
+        template <class T>
         HandleToSaved<T> getSavedMutable(std::string const& key) {
             return HandleToSaved(key, this, this->getSavedValue<T>(key));
         }
 
-        template<class T>
+        template <class T>
         HandleToSaved<T> getSavedMutable(std::string const& key, T const& defaultValue) {
             return HandleToSaved(key, this, this->getSavedValue<T>(key, defaultValue));
         }
 
         /**
-         * Set the value of an automatically saved variable. When the game is 
+         * Set the value of an automatically saved variable. When the game is
          * closed, the value is automatically saved under the key
          * @param key Key of the saved value
          * @param value Value
@@ -337,9 +339,9 @@ 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 
+         * 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.
          * @returns Successful result on success,
          * errorful result with info on error
@@ -387,7 +389,7 @@ namespace geode {
         ModJson getRuntimeInfo() const;
     };
 
-    template<class T>
+    template <class T>
     HandleToSaved<T>::~HandleToSaved() {
         m_mod->setSavedValue(m_key, static_cast<T>(*this));
     }
diff --git a/loader/include/Geode/loader/Types.hpp b/loader/include/Geode/loader/Types.hpp
index 1e49be06..d264d1e0 100644
--- a/loader/include/Geode/loader/Types.hpp
+++ b/loader/include/Geode/loader/Types.hpp
@@ -2,9 +2,9 @@
 
 #include "../DefaultInclude.hpp"
 #include "../platform/cplatform.h"
+
 #include <string>
 
-class InternalLoader;
 class InternalMod;
 
 namespace geode {
@@ -130,7 +130,7 @@ namespace geode {
     };
 
     constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
-    
+
     class Mod;
     class Setting;
     class Loader;
@@ -146,7 +146,7 @@ namespace geode {
         template <class, class>
         class FieldIntermediate;
     }
-    
+
 }
 
 /**
diff --git a/loader/include/Geode/utils/cocos.hpp b/loader/include/Geode/utils/cocos.hpp
index de4b2cce..18e36215 100644
--- a/loader/include/Geode/utils/cocos.hpp
+++ b/loader/include/Geode/utils/cocos.hpp
@@ -338,7 +338,7 @@ namespace geode {
 
         template <class C>
         static EventListenerNode* create(
-            C* cls, typename EventListener<Filter>::MemberFn<C> callback
+            C* cls, typename EventListener<Filter>::template MemberFn<C> callback
         ) {
             // for some reason msvc won't let me just call EventListenerNode::create...
             // it claims no return value...
diff --git a/loader/src/hooks/LoadingLayer.cpp b/loader/src/hooks/LoadingLayer.cpp
index b1a973a3..b48c7c71 100644
--- a/loader/src/hooks/LoadingLayer.cpp
+++ b/loader/src/hooks/LoadingLayer.cpp
@@ -1,9 +1,9 @@
 
-#include <InternalLoader.hpp>
-#include <array>
 #include <Geode/modify/LoadingLayer.hpp>
-#include <fmt/format.h>
 #include <Geode/utils/cocos.hpp>
+#include <array>
+#include <fmt/format.h>
+#include <loader/LoaderImpl.hpp>
 
 USE_GEODE_NAMESPACE();
 
@@ -21,23 +21,21 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
 
         auto count = Loader::get()->getAllMods().size();
 
-        auto label = CCLabelBMFont::create(
-            fmt::format("Geode: Loaded {} mods", count).c_str(),
-            "goldFont.fnt"
-        );
+        auto label =
+            CCLabelBMFont::create(fmt::format("Geode: Loaded {} mods", count).c_str(), "goldFont.fnt");
         label->setPosition(winSize.width / 2, 30.f);
         label->setScale(.45f);
         label->setID("geode-loaded-info");
         this->addChild(label);
 
-        // for some reason storing the listener as a field caused the 
+        // for some reason storing the listener as a field caused the
         // destructor for the field not to be run
         this->addChild(EventListenerNode<ResourceDownloadFilter>::create(
             this, &CustomLoadingLayer::updateResourcesProgress
         ));
 
         // verify loader resources
-        if (!InternalLoader::get()->verifyLoaderResources()) {
+        if (!LoaderImpl::get()->verifyLoaderResources()) {
             m_fields->m_updatingResources = true;
             this->setUpdateText("Downloading Resources");
         }
@@ -62,7 +60,7 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
                 this->loadAssets();
             },
             [&](UpdateFailed const& error) {
-                InternalLoader::platformMessageBox(
+                LoaderImpl::get()->platformMessageBox(
                     "Error updating resources",
                     "Unable to update Geode resources: " + 
                     error + ".\n"
diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp
index e0b37acb..18d953f5 100644
--- a/loader/src/hooks/MenuLayer.cpp
+++ b/loader/src/hooks/MenuLayer.cpp
@@ -1,17 +1,16 @@
 
-#include <Geode/utils/cocos.hpp>
+#include "../ids/AddIDs.hpp"
 #include "../ui/internal/list/ModListLayer.hpp"
+
+#include <Geode/loader/Index.hpp>
+#include <Geode/modify/MenuLayer.hpp>
+#include <Geode/modify/Modify.hpp>
 #include <Geode/ui/BasedButtonSprite.hpp>
-#include <Geode/ui/Notification.hpp>
 #include <Geode/ui/GeodeUI.hpp>
+#include <Geode/ui/Notification.hpp>
 #include <Geode/ui/Popup.hpp>
 #include <Geode/utils/cocos.hpp>
-#include <Geode/loader/Index.hpp>
-#include <InternalLoader.hpp>
-#include "../ids/AddIDs.hpp"
 #include <InternalMod.hpp>
-#include <Geode/modify/Modify.hpp>
-#include <Geode/modify/MenuLayer.hpp>
 
 USE_GEODE_NAMESPACE();
 
@@ -45,15 +44,14 @@ $execute {
 struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
 	CCSprite* m_geodeButton;
 
-	bool init() {
-		if (!MenuLayer::init())
-			return false;
-		
-		// make sure to add the string IDs for nodes (Geode has no manual 
-		// hook order support yet so gotta do this to ensure)
-		NodeIDs::provideFor(this);
+    bool init() {
+        if (!MenuLayer::init()) return false;
 
-		auto winSize = CCDirector::sharedDirector()->getWinSize();
+        // make sure to add the string IDs for nodes (Geode has no manual
+        // hook order support yet so gotta do this to ensure)
+        NodeIDs::provideFor(this);
+
+        auto winSize = CCDirector::sharedDirector()->getWinSize();
 
 		// add geode button
 		
@@ -66,7 +64,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
 			))
 			.orMake<ButtonSprite>("!!");
 
-		auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
+        auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
 
 		auto btn = CCMenuItemSpriteExtra::create(
 			m_fields->m_geodeButton, this, menu_selector(CustomMenuLayer::onGeode)
@@ -74,46 +72,44 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
 		btn->setID("geode-button"_spr);
 		bottomMenu->addChild(btn);
 
-		bottomMenu->updateLayout();
+        bottomMenu->updateLayout();
 
-		if (auto node = this->getChildByID("settings-gamepad-icon")) {
-			node->setPositionX(bottomMenu->getChildByID(
-				"settings-button"
-			)->getPositionX() + winSize.width / 2);
-		}
-
-		// show if some mods failed to load
-		static bool shownFailedNotif = false;
-		if (!shownFailedNotif) {
-			shownFailedNotif = true;
-			if (Loader::get()->getFailedMods().size()) {
-				Notification::create(
-					"Some mods failed to load",
-					NotificationIcon::Error
-				)->show();
-			}
+        if (auto node = this->getChildByID("settings-gamepad-icon")) {
+            node->setPositionX(
+                bottomMenu->getChildByID("settings-button")->getPositionX() + winSize.width / 2
+            );
         }
 
-		// show crash info
-		static bool shownLastCrash = false;
-		if (Loader::get()->didLastLaunchCrash() && !shownLastCrash) {
-			shownLastCrash = true;
-			auto popup = createQuickPopup(
-				"Crashed",
-				"It appears that the last session crashed. Would you like to "
-				"send a <cy>crash report</c>?",
-				"No", "Send",
-				[](auto, bool btn2) {
-					if (btn2) {
-						geode::openIssueReportPopup(InternalMod::get());
-					}
-				},
-				false
-			);
-			popup->m_scene = this;
-			popup->m_noElasticity = true;
-			popup->show();
-		}
+        // show if some mods failed to load
+        static bool shownFailedNotif = false;
+        if (!shownFailedNotif) {
+            shownFailedNotif = true;
+            if (Loader::get()->getFailedMods().size()) {
+                Notification::create("Some mods failed to load", NotificationIcon::Error)->show();
+            }
+        }
+
+        // show crash info
+        static bool shownLastCrash = false;
+        if (Loader::get()->didLastLaunchCrash() && !shownLastCrash) {
+            shownLastCrash = true;
+            auto popup = createQuickPopup(
+                "Crashed",
+                "It appears that the last session crashed. Would you like to "
+                "send a <cy>crash report</c>?",
+                "No",
+                "Send",
+                [](auto, bool btn2) {
+                    if (btn2) {
+                        geode::openIssueReportPopup(InternalMod::get());
+                    }
+                },
+                false
+            );
+            popup->m_scene = this;
+            popup->m_noElasticity = true;
+            popup->show();
+        }
 
 		// update mods index
 		if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
@@ -153,7 +149,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
 		}
 	}
 
-	void onGeode(CCObject*) {
-		ModListLayer::scene();
-	}
+    void onGeode(CCObject*) {
+        ModListLayer::scene();
+    }
 };
diff --git a/loader/src/hooks/save.cpp b/loader/src/hooks/save.cpp
index 23232e73..a0db4471 100644
--- a/loader/src/hooks/save.cpp
+++ b/loader/src/hooks/save.cpp
@@ -6,14 +6,14 @@ USE_GEODE_NAMESPACE();
 
 struct SaveLoader : Modify<SaveLoader, AppDelegate> {
     void trySaveGame() {
-        log::log(Severity::Info, Loader::getInternalMod(), "Saving...");
+        log::info("Saving...");
 
         auto r = Loader::get()->saveData();
         if (!r) {
-            log::log(Severity::Error, Loader::getInternalMod(), "{}", r.unwrapErr());
+            log::info("{}", r.unwrapErr());
         }
 
-        log::log(Severity::Info, Loader::getInternalMod(), "Saved");
+        log::info("Saved");
 
         return AppDelegate::trySaveGame();
     }
diff --git a/loader/src/hooks/update.cpp b/loader/src/hooks/update.cpp
index 7f789d51..fa160c5a 100644
--- a/loader/src/hooks/update.cpp
+++ b/loader/src/hooks/update.cpp
@@ -1,4 +1,4 @@
-#include <InternalLoader.hpp>
+#include "../loader/LoaderImpl.hpp"
 
 USE_GEODE_NAMESPACE();
 
@@ -6,7 +6,7 @@ USE_GEODE_NAMESPACE();
 
 struct FunctionQueue : Modify<FunctionQueue, CCScheduler> {
     void update(float dt) {
-        InternalLoader::get()->executeGDThreadQueue();
+        LoaderImpl::get()->executeGDThreadQueue();
         return CCScheduler::update(dt);
     }
 };
diff --git a/loader/src/internal/InternalLoader.cpp b/loader/src/internal/InternalLoader.cpp
deleted file mode 100644
index 004688fe..00000000
--- a/loader/src/internal/InternalLoader.cpp
+++ /dev/null
@@ -1,227 +0,0 @@
-#include "InternalLoader.hpp"
-
-#include "InternalMod.hpp"
-#include "resources.hpp"
-
-#include <Geode/loader/Loader.hpp>
-#include <Geode/loader/IPC.hpp>
-#include <Geode/loader/Log.hpp>
-#include <Geode/loader/Dirs.hpp>
-#include <Geode/utils/web.hpp>
-#include <Geode/utils/file.hpp>
-#include <fmt/format.h>
-#include <hash.hpp>
-#include <iostream>
-#include <sstream>
-#include <string>
-#include <thread>
-#include <vector>
-
-ResourceDownloadEvent::ResourceDownloadEvent(
-    UpdateStatus const& status
-) : status(status) {}
-
-ListenerResult ResourceDownloadFilter::handle(
-    std::function<Callback> fn,
-    ResourceDownloadEvent* event
-) {
-    fn(event);
-    return ListenerResult::Propagate;
-}
-
-ResourceDownloadFilter::ResourceDownloadFilter() {}
-
-InternalLoader::InternalLoader() : Loader() {}
-
-InternalLoader::~InternalLoader() {
-    this->closePlatformConsole();
-}
-
-InternalLoader* InternalLoader::get() {
-    static auto g_geode = new InternalLoader;
-    return g_geode;
-}
-
-bool InternalLoader::setup() {
-    log::log(Severity::Debug, InternalMod::get(), "Set up internal mod representation");
-    log::log(Severity::Debug, InternalMod::get(), "Loading hooks... ");
-
-    if (!this->loadHooks()) {
-        log::log(
-            Severity::Error, InternalMod::get(),
-            "There were errors loading some hooks, see console for details"
-        );
-    }
-
-    log::log(Severity::Debug, InternalMod::get(), "Loaded hooks");
-
-    log::log(Severity::Debug, InternalMod::get(), "Setting up IPC...");
-
-    this->setupIPC();
-
-    return true;
-}
-
-bool InternalLoader::isReadyToHook() const {
-    return m_readyToHook;
-}
-
-void InternalLoader::addInternalHook(Hook* hook, Mod* mod) {
-    m_internalHooks.push_back({hook, mod});
-}
-
-bool InternalLoader::loadHooks() {
-    m_readyToHook = true;
-    auto thereWereErrors = false;
-    for (auto const& hook : m_internalHooks) {
-        auto res = hook.second->addHook(hook.first);
-        if (!res) {
-            log::log(Severity::Error, hook.second, "{}", res.unwrapErr());
-            thereWereErrors = true;
-        }
-    }
-    // free up memory
-    m_internalHooks.clear();
-    return !thereWereErrors;
-}
-
-void InternalLoader::queueInGDThread(ScheduledFunction func) {
-    std::lock_guard<std::mutex> lock(m_gdThreadMutex);
-    m_gdThreadQueue.push_back(func);
-}
-
-void InternalLoader::executeGDThreadQueue() {
-    // copy queue to avoid locking mutex if someone is
-    // running addToGDThread inside their function
-    m_gdThreadMutex.lock();
-    auto queue = m_gdThreadQueue;
-    m_gdThreadQueue.clear();
-    m_gdThreadMutex.unlock();
-
-    // call queue
-    for (auto const& func : queue) {
-        func();
-    }
-}
-
-void InternalLoader::logConsoleMessage(std::string const& msg) {
-    if (m_platformConsoleOpen) {
-        // TODO: make flushing optional
-        std::cout << msg << '\n' << std::flush;
-    }
-}
-
-bool InternalLoader::platformConsoleOpen() const {
-    return m_platformConsoleOpen;
-}
-
-void InternalLoader::downloadLoaderResources() {
-    auto version = this->getVersion().toString();
-    auto tempResourcesZip = dirs::getTempDir() / "new.zip";
-    auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID();
-
-    web::AsyncWebRequest()
-        .join("update-geode-loader-resources")
-        .fetch(fmt::format(
-            "https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version
-        ))
-        .into(tempResourcesZip)
-        .then([tempResourcesZip, resourcesDir](auto) {
-            // unzip resources zip
-            auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true);
-            if (!unzip) {
-                return ResourceDownloadEvent(
-                    UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr())
-                ).post();
-            }
-            ResourceDownloadEvent(UpdateFinished()).post();
-        })
-        .expect([](std::string const& info) {
-            ResourceDownloadEvent(
-                UpdateFailed("Unable to download resources: " + info)
-            ).post();
-        })
-        .progress([](auto&, double now, double total) {
-            ResourceDownloadEvent(
-                UpdateProgress(
-                    static_cast<uint8_t>(now / total * 100.0),
-                    "Downloading resources"
-                )
-            ).post();
-        });
-}
-
-bool InternalLoader::verifyLoaderResources() {
-    static std::optional<bool> CACHED = std::nullopt;
-    if (CACHED.has_value()) {
-        return CACHED.value();
-    }
-
-    // geode/resources/geode.loader
-    auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID();
-
-    // if the resources dir doesn't exist, then it's probably incorrect
-    if (!(
-        ghc::filesystem::exists(resourcesDir) &&
-        ghc::filesystem::is_directory(resourcesDir)
-    )) {
-        this->downloadLoaderResources();
-        return false;
-    }
-
-    // make sure every file was covered
-    size_t coverage = 0;
-
-    // verify hashes
-    for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) {
-        auto name = file.path().filename().string();
-        // skip unknown files
-        if (!LOADER_RESOURCE_HASHES.count(name)) {
-            continue;
-        }
-        // verify hash
-        auto hash = calculateSHA256(file.path());
-        if (hash != LOADER_RESOURCE_HASHES.at(name)) {
-            log::debug(
-                "compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name)
-            );
-            this->downloadLoaderResources();
-            return false;
-        }
-        coverage += 1;
-    }
-
-    // make sure every file was found
-    if (coverage != LOADER_RESOURCE_HASHES.size()) {
-        this->downloadLoaderResources();
-        return false;
-    }
-
-    return true;
-}
-
-nlohmann::json InternalLoader::processRawIPC(void* rawHandle, std::string const& buffer) {
-    nlohmann::json reply;
-    try {
-        // parse received message
-        auto json = nlohmann::json::parse(buffer);
-        if (!json.contains("mod") || !json["mod"].is_string()) {
-            log::warn("Received IPC message without 'mod' field");
-            return reply;
-        }
-        if (!json.contains("message") || !json["message"].is_string()) {
-            log::warn("Received IPC message without 'message' field");
-            return reply;
-        }
-        nlohmann::json data;
-        if (json.contains("data")) {
-            data = json["data"];
-        }
-        // log::debug("Posting IPC event");
-        // ! warning: if the event system is ever made asynchronous this will break!
-        IPCEvent(rawHandle, json["mod"], json["message"], data, reply).post();
-    } catch(...) {
-        log::warn("Received IPC message that isn't valid JSON");
-    }
-    return reply;
-}
diff --git a/loader/src/internal/InternalLoader.hpp b/loader/src/internal/InternalLoader.hpp
deleted file mode 100644
index 05cf8f01..00000000
--- a/loader/src/internal/InternalLoader.hpp
+++ /dev/null
@@ -1,78 +0,0 @@
-#pragma once
-
-#include "FileWatcher.hpp"
-
-#include <Geode/loader/Index.hpp>
-#include <Geode/loader/Loader.hpp>
-#include <Geode/loader/Log.hpp>
-#include <Geode/utils/Result.hpp>
-#include <Geode/external/json/json.hpp>
-
-#include <mutex>
-#include <optional>
-#include <unordered_map>
-#include <unordered_set>
-#include <vector>
-#include <thread>
-
-USE_GEODE_NAMESPACE();
-
-struct ResourceDownloadEvent : public Event {
-    const UpdateStatus status;
-    ResourceDownloadEvent(UpdateStatus const& status);
-};
-
-class GEODE_DLL ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> {
-public:
-    using Callback = void(ResourceDownloadEvent*);
-
-    ListenerResult handle(std::function<Callback> fn, ResourceDownloadEvent* event);
-    ResourceDownloadFilter();
-};
-
-/**
- * Internal extension of Loader for private information
- * @class InternalLoader
- */
-class InternalLoader : public Loader {
-protected:
-    std::vector<std::function<void(void)>> m_gdThreadQueue;
-    mutable std::mutex m_gdThreadMutex;
-    bool m_platformConsoleOpen = false;
-
-    std::vector<std::pair<Hook*, Mod*>> m_internalHooks;
-    bool m_readyToHook;
-
-    void downloadLoaderResources();
-
-    bool loadHooks();
-    void setupIPC();
-
-    InternalLoader();
-    ~InternalLoader();
-
-    friend class Loader;
-
-public:
-    static InternalLoader* get();
-
-    bool setup();
-
-    static nlohmann::json processRawIPC(void* rawHandle, std::string const& buffer);
-
-    void queueInGDThread(ScheduledFunction func);
-    void executeGDThreadQueue();
-
-    void logConsoleMessage(std::string const& msg);
-    bool platformConsoleOpen() const;
-    void openPlatformConsole();
-    void closePlatformConsole();
-    static void platformMessageBox(char const* title, std::string const& info);
-
-    bool verifyLoaderResources();
-
-    bool isReadyToHook() const;
-    void addInternalHook(Hook* hook, Mod* mod);
-
-    friend int geodeEntry(void* platformData);
-};
diff --git a/loader/src/internal/InternalMod.cpp b/loader/src/internal/InternalMod.cpp
index a37eecf6..4add508f 100644
--- a/loader/src/internal/InternalMod.cpp
+++ b/loader/src/internal/InternalMod.cpp
@@ -1,8 +1,10 @@
 #include "InternalMod.hpp"
-#include <Geode/loader/Dirs.hpp>
-#include "InternalLoader.hpp"
+
 #include "about.hpp"
 
+#include <Geode/loader/Dirs.hpp>
+#include <LoaderImpl.hpp>
+
 static constexpr char const* SUPPORT_INFO = R"MD(
 **Geode** is funded through your gracious <cy>**donations**</c>!
 You can support our work by sending <cp>**catgirl pictures**</c> to [HJfod](user:104257) :))
@@ -13,7 +15,7 @@ static ModInfo getInternalModInfo() {
         auto json = ModJson::parse(LOADER_MOD_JSON);
         auto infoRes = ModInfo::create(json);
         if (infoRes.isErr()) {
-            InternalLoader::platformMessageBox(
+            LoaderImpl::get()->platformMessageBox(
                 "Fatal Internal Error",
                 "Unable to parse loader mod.json: \"" + infoRes.unwrapErr() +
                     "\"\n"
@@ -29,7 +31,7 @@ static ModInfo getInternalModInfo() {
         return info;
     }
     catch (std::exception& e) {
-        InternalLoader::platformMessageBox(
+        LoaderImpl::get()->platformMessageBox(
             "Fatal Internal Error",
             "Unable to parse loader mod.json: \"" + std::string(e.what()) +
                 "\"\n"
diff --git a/loader/src/loader/Dirs.cpp b/loader/src/loader/Dirs.cpp
index 4ef28cf2..eb463679 100644
--- a/loader/src/loader/Dirs.cpp
+++ b/loader/src/loader/Dirs.cpp
@@ -2,6 +2,7 @@
 #include <Geode/loader/Dirs.hpp>
 #include <cocos2d.h>
 #include <crashlog.hpp>
+#include <filesystem>
 
 USE_GEODE_NAMESPACE();
 
@@ -10,30 +11,26 @@ ghc::filesystem::path dirs::getGameDir() {
 }
 
 ghc::filesystem::path dirs::getSaveDir() {
-    #ifdef GEODE_IS_MACOS
-        // not using ~/Library/Caches
-        return ghc::filesystem::path("/Users/Shared/Geode");
-    #elif defined(GEODE_IS_WINDOWS)
-        return ghc::filesystem::path(
-            ghc::filesystem::weakly_canonical(
-                CCFileUtils::sharedFileUtils()->getWritablePath().c_str()
-            ).string()
-        );
-    #else
-        return ghc::filesystem::path(
-            CCFileUtils::sharedFileUtils()->getWritablePath().c_str()
-        );
-    #endif
+#ifdef GEODE_IS_MACOS
+    // not using ~/Library/Caches
+    return ghc::filesystem::path("/Users/Shared/Geode");
+#elif defined(GEODE_IS_WINDOWS)
+    // this is std::filesystem intentionally because ghc version doesnt want to work with softlinked directories
+    return ghc::filesystem::path(
+        std::filesystem::weakly_canonical(CCFileUtils::sharedFileUtils()->getWritablePath().c_str())
+            .string()
+    );
+#else
+    return ghc::filesystem::path(CCFileUtils::sharedFileUtils()->getWritablePath().c_str());
+#endif
 }
 
 ghc::filesystem::path dirs::getGeodeDir() {
-    #ifdef GEODE_IS_MACOS
-    char cwd[PATH_MAX];
-    getcwd(cwd, sizeof(cwd));
-    return ghc::filesystem::path(cwd) / "geode";
-    #else
+#ifdef GEODE_IS_MACOS
+    return ghc::filesystem::current_path() / "geode";
+#else
     return dirs::getGameDir() / "geode";
-    #endif
+#endif
 }
 
 ghc::filesystem::path dirs::getGeodeSaveDir() {
diff --git a/loader/src/loader/Hook.cpp b/loader/src/loader/Hook.cpp
index 79d666f9..72188dac 100644
--- a/loader/src/loader/Hook.cpp
+++ b/loader/src/loader/Hook.cpp
@@ -5,7 +5,6 @@
 #include <Geode/utils/ranges.hpp>
 #include <vector>
 // #include <hook/hook.hpp>
-#include "InternalLoader.hpp"
 #include "InternalMod.hpp"
 
 #include <Geode/hook-core/Hook.hpp>
diff --git a/loader/src/loader/IPC.cpp b/loader/src/loader/IPC.cpp
index 96a011e9..dafb4e80 100644
--- a/loader/src/loader/IPC.cpp
+++ b/loader/src/loader/IPC.cpp
@@ -1,5 +1,4 @@
 #include <Geode/loader/IPC.hpp>
-#include <InternalLoader.hpp>
 
 USE_GEODE_NAMESPACE();
 
@@ -25,8 +24,5 @@ ListenerResult IPCFilter::handle(std::function<Callback> fn, IPCEvent* event) {
     return ListenerResult::Propagate;
 }
 
-IPCFilter::IPCFilter(
-    std::string const& modID,
-    std::string const& messageID
-) : m_modID(modID),
-    m_messageID(messageID) {}
+IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID) :
+    m_modID(modID), m_messageID(messageID) {}
diff --git a/loader/src/loader/Loader.cpp b/loader/src/loader/Loader.cpp
index 63f7254f..5d3e783c 100644
--- a/loader/src/loader/Loader.cpp
+++ b/loader/src/loader/Loader.cpp
@@ -1,395 +1,126 @@
+#include "LoaderImpl.hpp"
 
-#include <Geode/loader/Loader.hpp>
-#include <Geode/loader/Mod.hpp>
-#include <Geode/loader/Dirs.hpp>
-#include <InternalLoader.hpp>
-#include <InternalMod.hpp>
-#include <about.hpp>
-#include <Geode/utils/ranges.hpp>
-#include <Geode/utils/map.hpp>
-#include <crashlog.hpp>
+Loader::Loader() : m_impl(new Impl) {}
 
-USE_GEODE_NAMESPACE();
+Loader::~Loader() {}
 
 Loader* Loader::get() {
-    return InternalLoader::get();
+    static auto g_geode = new Loader;
+    return g_geode;
 }
 
-Loader::~Loader() {
-    for (auto& [_, mod] : m_mods) {
-        delete mod;
-    }
-    m_mods.clear();
-    log::Logs::clear();
-    ghc::filesystem::remove_all(dirs::getModRuntimeDir());
-    ghc::filesystem::remove_all(dirs::getTempDir());
-}
-
-// Initialization
-
 void Loader::createDirectories() {
-#ifdef GEODE_IS_MACOS
-    ghc::filesystem::create_directory(dirs::getSaveDir());
-#endif
-
-    ghc::filesystem::create_directories(dirs::getGeodeResourcesDir());
-    ghc::filesystem::create_directory(dirs::getModConfigDir());
-    ghc::filesystem::create_directory(dirs::getModsDir());
-    ghc::filesystem::create_directory(dirs::getGeodeLogDir());
-    ghc::filesystem::create_directory(dirs::getTempDir());
-    ghc::filesystem::create_directory(dirs::getModRuntimeDir());
-
-    if (!ranges::contains(m_modSearchDirectories, dirs::getModsDir())) {
-        m_modSearchDirectories.push_back(dirs::getModsDir());
-    }
-}
-
-Result<> Loader::setup() {
-    if (m_isSetup) {
-        return Ok();
-    }
-
-    log::Logs::setup();
-
-    if (crashlog::setupPlatformHandler()) {
-        log::debug("Set up platform crash logger");
-    }
-    else {
-        log::debug("Unable to set up platform crash logger");
-    }
-
-    log::debug("Setting up Loader...");
-
-    this->createDirectories();
-    auto sett = this->loadData();
-    if (!sett) {
-        log::warn("Unable to load loader settings: {}", sett.unwrapErr());
-    }
-    this->refreshModsList();
-
-    this->queueInGDThread([]() {
-        Loader::get()->addSearchPaths();
-    });
-
-    m_isSetup = true;
-
-    return Ok();
-}
-
-void Loader::addSearchPaths() {
-    CCFileUtils::get()->addPriorityPath(dirs::getGeodeResourcesDir().string().c_str());
-    CCFileUtils::get()->addPriorityPath(dirs::getModRuntimeDir().string().c_str());
-}
-
-void Loader::updateResources() {
-    log::debug("Adding resources");
-
-    // add own spritesheets
-    this->updateModResources(InternalMod::get());
-
-    // add mods' spritesheets
-    for (auto const& [_, mod] : m_mods) {
-        this->updateModResources(mod);
-    }
-}
-
-std::vector<Mod*> Loader::getAllMods() {
-    return map::values(m_mods);
-}
-
-Mod* Loader::getInternalMod() {
-    return InternalMod::get();
-}
-
-std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
-    return m_invalidMods;
-}
-
-// Version info
-
-VersionInfo Loader::getVersion() {
-    return LOADER_VERSION;
-}
-
-VersionInfo Loader::minModVersion() {
-    return VersionInfo { 0, 3, 1 };
-}
-
-VersionInfo Loader::maxModVersion() {
-    return VersionInfo {
-        Loader::getVersion().getMajor(),
-        Loader::getVersion().getMinor(),
-        // todo: dynamic version info (vM.M.*)
-        99999999,
-    };
-}
-
-bool Loader::isModVersionSupported(VersionInfo const& version) {
-    return
-        version >= Loader::minModVersion() &&
-        version <= Loader::maxModVersion();
-}
-
-// Data saving
-
-Result<> Loader::saveData() {
-    // save mods' data
-    for (auto& [_, mod] : m_mods) {
-        auto r = mod->saveData();
-        if (!r) {
-            log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
-        }
-    }
-    // save loader data
-    GEODE_UNWRAP(InternalMod::get()->saveData());
-    
-    return Ok();
-}
-
-Result<> Loader::loadData() {
-    auto e = InternalMod::get()->loadData();
-    if (!e) {
-        log::warn("Unable to load loader settings: {}", e.unwrapErr());
-    }
-    for (auto& [_, mod] : m_mods) {
-        auto r = mod->loadData();
-        if (!r) {
-            log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
-        }
-    }
-    return Ok();
-}
-
-// Mod loading
-
-Result<Mod*> Loader::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);
-    m_mods.insert({ info.id, mod });
-    mod->m_enabled = InternalMod::get()->getSavedValue<bool>(
-        "should-load-" + info.id, true
-    );
-
-    // add mod resources
-    this->queueInGDThread([this, mod]() {
-        auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
-
-        CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
-        this->updateModResources(mod);
-    });
-
-    // this loads the mod if its dependencies are resolved
-    GEODE_UNWRAP(mod->updateDependencies());
-
-    return Ok(mod);
-}
-
-Result<Mod*> Loader::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::isModInstalled(std::string const& id) const {
-    return m_mods.count(id) && !m_mods.at(id)->isUninstalled();
-}
-
-Mod* Loader::getInstalledMod(std::string const& id) const {
-    if (m_mods.count(id) && !m_mods.at(id)->isUninstalled()) {
-        return m_mods.at(id);
-    }
-    return nullptr;
-}
-
-bool Loader::isModLoaded(std::string const& id) const {
-    return m_mods.count(id) && m_mods.at(id)->isLoaded();
-}
-
-Mod* Loader::getLoadedMod(std::string const& id) const {
-    if (m_mods.count(id)) {
-        auto mod = m_mods.at(id);
-        if (mod->isLoaded()) {
-            return mod;
-        }
-    }
-    return nullptr;
-}
-
-void Loader::dispatchScheduledFunctions(Mod* mod) {
-    std::lock_guard _(m_scheduledFunctionsMutex);
-    for (auto& func : m_scheduledFunctions) {
-        func();
-    }
-    m_scheduledFunctions.clear();
-}
-
-void Loader::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
-    std::lock_guard _(m_scheduledFunctionsMutex);
-    if (mod) {
-        return func();
-    }
-    m_scheduledFunctions.push_back(func);
+    return m_impl->createDirectories();
 }
 
 void Loader::updateModResources(Mod* mod) {
-    if (!mod->m_info.spritesheets.size()) {
-        return;
-    }
-
-    auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
-
-    log::debug("Adding resources for {}", mod->getID());
-
-    // add spritesheets
-    for (auto const& sheet : mod->m_info.spritesheets) {
-        log::debug("Adding sheet {}", sheet);
-        auto png = sheet + ".png";
-        auto plist = sheet + ".plist";
-        auto ccfu = CCFileUtils::get();
-
-        if (png == std::string(ccfu->fullPathForFilename(png.c_str(), false)) ||
-            plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) {
-            log::warn(
-                "The resource dir of \"{}\" is missing \"{}\" png and/or plist files",
-                mod->m_info.id, sheet
-            );
-        }
-        else {
-            CCTextureCache::get()->addImage(png.c_str(), false);
-            CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
-        }
-    }
+    return m_impl->updateModResources(mod);
 }
 
-// Dependencies and refreshing
+void Loader::addSearchPaths() {
+    return m_impl->addSearchPaths();
+}
 
-void Loader::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;
-        }
+void Loader::dispatchScheduledFunctions(Mod* mod) {
+    return m_impl->dispatchScheduledFunctions(mod);
+}
 
-        // skip this entry if it's not a file
-        if (!ghc::filesystem::is_regular_file(entry)) {
-            continue;
-        }
+Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) {
+    return m_impl->loadModFromInfo(info);
+}
 
-        // 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_info.path == entry.path();
-        })) {
-            continue;
-        }
+Result<> Loader::saveData() {
+    return m_impl->saveData();
+}
 
-        // 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();
+Result<> Loader::loadData() {
+    return m_impl->loadData();
+}
 
-            // skip this entry if it's already set to be loaded
-            if (ranges::contains(m_modsToLoad, info)) {
-                continue;
-            }
+VersionInfo Loader::getVersion() {
+    return m_impl->getVersion();
+}
 
-            // add to list of mods to load
-            m_modsToLoad.push_back(info);
-        }
-    }
+VersionInfo Loader::minModVersion() {
+    return m_impl->minModVersion();
+}
+
+VersionInfo Loader::maxModVersion() {
+    return m_impl->maxModVersion();
+}
+
+bool Loader::isModVersionSupported(VersionInfo const& version) {
+    return m_impl->isModVersionSupported(version);
+}
+
+Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) {
+    return m_impl->loadModFromFile(file);
+}
+
+void Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) {
+    return m_impl->loadModsFromDirectory(dir, recursive);
 }
 
 void Loader::refreshModsList() {
-    log::debug("Loading mods...");
+    return m_impl->refreshModsList();
+}
 
-    // 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());
-            }
-        }
-    }
+bool Loader::isModInstalled(std::string const& id) const {
+    return m_impl->isModInstalled(id);
+}
 
-    // UI can be loaded now
-    m_earlyLoadFinished = true;
+Mod* Loader::getInstalledMod(std::string const& id) const {
+    return m_impl->getInstalledMod(id);
+}
 
-    // 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_modsToLoad.clear();
+bool Loader::isModLoaded(std::string const& id) const {
+    return m_impl->isModLoaded(id);
+}
+
+Mod* Loader::getLoadedMod(std::string const& id) const {
+    return m_impl->getLoadedMod(id);
+}
+
+std::vector<Mod*> Loader::getAllMods() {
+    return m_impl->getAllMods();
+}
+
+Mod* Loader::getInternalMod() {
+    return m_impl->getInternalMod();
 }
 
 void Loader::updateAllDependencies() {
-    for (auto const& [_, mod] : m_mods) {
-        (void)mod->updateDependencies();
-    }
+    return m_impl->updateAllDependencies();
+}
+
+std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
+    return m_impl->getFailedMods();
+}
+
+void Loader::updateResources() {
+    return m_impl->updateResources();
+}
+
+void Loader::queueInGDThread(ScheduledFunction func) {
+    return m_impl->queueInGDThread(func);
+}
+
+void Loader::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
+    return m_impl->scheduleOnModLoad(mod, func);
 }
 
 void Loader::waitForModsToBeLoaded() {
-    while (!m_earlyLoadFinished) {}
-}
-
-// Misc
-
-void Loader::queueInGDThread(ScheduledFunction func) {
-    InternalLoader::get()->queueInGDThread(func);
-}
-
-bool Loader::didLastLaunchCrash() const {
-    return crashlog::didLastLaunchCrash();
+    return m_impl->waitForModsToBeLoaded();
 }
 
 void Loader::openPlatformConsole() {
-    InternalLoader::get()->openPlatformConsole();
+    return m_impl->openPlatformConsole();
 }
 
-void Loader::closePlatfromConsole() {
-    InternalLoader::get()->closePlatformConsole();
+void Loader::closePlatformConsole() {
+    return m_impl->closePlatformConsole();
 }
+
+bool Loader::didLastLaunchCrash() const {
+    return m_impl->didLastLaunchCrash();
+}
\ No newline at end of file
diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp
new file mode 100644
index 00000000..e33cbb43
--- /dev/null
+++ b/loader/src/loader/LoaderImpl.cpp
@@ -0,0 +1,596 @@
+
+#include "LoaderImpl.hpp"
+
+#include <Geode/loader/Dirs.hpp>
+#include <Geode/loader/IPC.hpp>
+#include <Geode/loader/Loader.hpp>
+#include <Geode/loader/Log.hpp>
+#include <Geode/loader/Mod.hpp>
+#include <Geode/utils/file.hpp>
+#include <Geode/utils/map.hpp>
+#include <Geode/utils/ranges.hpp>
+#include <Geode/utils/web.hpp>
+#include <InternalMod.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>
+
+USE_GEODE_NAMESPACE();
+
+Loader::Impl* LoaderImpl::get() {
+    return Loader::get()->m_impl.get();
+}
+
+Loader::Impl::Impl() {}
+
+Loader::Impl::~Impl() {}
+
+// Initialization
+
+void Loader::Impl::createDirectories() {
+#ifdef GEODE_IS_MACOS
+    ghc::filesystem::create_directory(dirs::getSaveDir());
+#endif
+
+    ghc::filesystem::create_directories(dirs::getGeodeResourcesDir());
+    ghc::filesystem::create_directory(dirs::getModConfigDir());
+    ghc::filesystem::create_directory(dirs::getModsDir());
+    ghc::filesystem::create_directory(dirs::getGeodeLogDir());
+    ghc::filesystem::create_directory(dirs::getTempDir());
+    ghc::filesystem::create_directory(dirs::getModRuntimeDir());
+
+    if (!ranges::contains(m_modSearchDirectories, dirs::getModsDir())) {
+        m_modSearchDirectories.push_back(dirs::getModsDir());
+    }
+}
+
+Result<> Loader::Impl::setup() {
+    if (m_isSetup) {
+        return Ok();
+    }
+
+    log::Logs::setup();
+
+    if (crashlog::setupPlatformHandler()) {
+        log::debug("Set up platform crash logger");
+    }
+    else {
+        log::debug("Unable to set up platform crash logger");
+    }
+
+    log::debug("Setting up Loader...");
+
+    this->createDirectories();
+    auto sett = this->loadData();
+    if (!sett) {
+        log::warn("Unable to load loader settings: {}", sett.unwrapErr());
+    }
+    this->refreshModsList();
+
+    this->queueInGDThread([]() {
+        Loader::get()->addSearchPaths();
+    });
+
+    m_isSetup = true;
+
+    return Ok();
+}
+
+void Loader::Impl::addSearchPaths() {
+    CCFileUtils::get()->addPriorityPath(dirs::getGeodeResourcesDir().string().c_str());
+    CCFileUtils::get()->addPriorityPath(dirs::getModRuntimeDir().string().c_str());
+}
+
+void Loader::Impl::updateResources() {
+    log::debug("Adding resources");
+
+    // add own spritesheets
+    this->updateModResources(InternalMod::get());
+
+    // add mods' spritesheets
+    for (auto const& [_, mod] : m_mods) {
+        this->updateModResources(mod);
+    }
+}
+
+std::vector<Mod*> Loader::Impl::getAllMods() {
+    return map::values(m_mods);
+}
+
+Mod* Loader::Impl::getInternalMod() {
+    return InternalMod::get();
+}
+
+std::vector<InvalidGeodeFile> Loader::Impl::getFailedMods() const {
+    return m_invalidMods;
+}
+
+// Version info
+
+VersionInfo Loader::Impl::getVersion() {
+    return LOADER_VERSION;
+}
+
+VersionInfo Loader::Impl::minModVersion() {
+    return VersionInfo { 0, 3, 1 };
+}
+
+VersionInfo Loader::Impl::maxModVersion() {
+    return VersionInfo {
+        this->getVersion().getMajor(),
+        this->getVersion().getMinor(),
+        // todo: dynamic version info (vM.M.*)
+        99999999,
+    };
+}
+
+bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
+    return
+        version >= this->minModVersion() &&
+        version <= this->maxModVersion();
+}
+
+// Data saving
+
+Result<> Loader::Impl::saveData() {
+    // save mods' data
+    for (auto& [_, mod] : m_mods) {
+        auto r = mod->saveData();
+        if (!r) {
+            log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
+        }
+    }
+    // save loader data
+    GEODE_UNWRAP(InternalMod::get()->saveData());
+    
+    return Ok();
+}
+
+Result<> Loader::Impl::loadData() {
+    auto e = InternalMod::get()->loadData();
+    if (!e) {
+        log::warn("Unable to load loader settings: {}", e.unwrapErr());
+    }
+    for (auto& [_, mod] : m_mods) {
+        auto r = mod->loadData();
+        if (!r) {
+            log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
+        }
+    }
+    return Ok();
+}
+
+// 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);
+    m_mods.insert({ info.id, mod });
+    mod->m_enabled = InternalMod::get()->getSavedValue<bool>(
+        "should-load-" + info.id, true
+    );
+
+    // add mod resources
+    this->queueInGDThread([this, mod]() {
+        auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
+
+        CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
+        this->updateModResources(mod);
+    });
+
+    // this loads the mod if its dependencies are resolved
+    GEODE_UNWRAP(mod->updateDependencies());
+
+    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();
+}
+
+Mod* Loader::Impl::getInstalledMod(std::string const& id) const {
+    if (m_mods.count(id) && !m_mods.at(id)->isUninstalled()) {
+        return m_mods.at(id);
+    }
+    return nullptr;
+}
+
+bool Loader::Impl::isModLoaded(std::string const& id) const {
+    return m_mods.count(id) && m_mods.at(id)->isLoaded();
+}
+
+Mod* Loader::Impl::getLoadedMod(std::string const& id) const {
+    if (m_mods.count(id)) {
+        auto mod = m_mods.at(id);
+        if (mod->isLoaded()) {
+            return mod;
+        }
+    }
+    return nullptr;
+}
+
+void Loader::Impl::dispatchScheduledFunctions(Mod* mod) {
+    std::lock_guard _(m_scheduledFunctionsMutex);
+    for (auto& func : m_scheduledFunctions) {
+        func();
+    }
+    m_scheduledFunctions.clear();
+}
+
+void Loader::Impl::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
+    std::lock_guard _(m_scheduledFunctionsMutex);
+    if (mod) {
+        return func();
+    }
+    m_scheduledFunctions.push_back(func);
+}
+
+void Loader::Impl::updateModResources(Mod* mod) {
+    if (!mod->m_info.spritesheets.size()) {
+        return;
+    }
+
+    auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
+
+    log::debug("Adding resources for {}", mod->getID());
+
+    // add spritesheets
+    for (auto const& sheet : mod->m_info.spritesheets) {
+        log::debug("Adding sheet {}", sheet);
+        auto png = sheet + ".png";
+        auto plist = sheet + ".plist";
+        auto ccfu = CCFileUtils::get();
+
+        if (png == std::string(ccfu->fullPathForFilename(png.c_str(), false)) ||
+            plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) {
+            log::warn(
+                "The resource dir of \"{}\" is missing \"{}\" png and/or plist files",
+                mod->m_info.id, sheet
+            );
+        }
+        else {
+            CCTextureCache::get()->addImage(png.c_str(), false);
+            CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
+        }
+    }
+}
+
+// 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;
+        }
+
+        // skip this entry if it's not a file
+        if (!ghc::filesystem::is_regular_file(entry)) {
+            continue;
+        }
+
+        // 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_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::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());
+            }
+        }
+    }
+
+    // UI can be loaded now
+    m_earlyLoadFinished = true;
+
+    // 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_modsToLoad.clear();
+}
+
+void Loader::Impl::updateAllDependencies() {
+    for (auto const& [_, mod] : m_mods) {
+        (void)mod->updateDependencies();
+    }
+}
+
+void Loader::Impl::waitForModsToBeLoaded() {
+    while (!m_earlyLoadFinished) {}
+}
+
+bool Loader::Impl::didLastLaunchCrash() const {
+    return crashlog::didLastLaunchCrash();
+}
+
+
+
+
+void Loader::Impl::reset() {
+    this->closePlatformConsole();
+
+    for (auto& [_, mod] : m_mods) {
+        delete mod;
+    }
+    m_mods.clear();
+    log::Logs::clear();
+    ghc::filesystem::remove_all(dirs::getModRuntimeDir());
+    ghc::filesystem::remove_all(dirs::getTempDir());
+}
+bool Loader::Impl::isReadyToHook() const {
+    return m_readyToHook;
+}
+
+void Loader::Impl::addInternalHook(Hook* hook, Mod* mod) {
+    m_internalHooks.push_back({hook, mod});
+}
+
+bool Loader::Impl::loadHooks() {
+    m_readyToHook = true;
+    auto thereWereErrors = false;
+    for (auto const& hook : m_internalHooks) {
+        auto res = hook.second->addHook(hook.first);
+        if (!res) {
+            log::log(Severity::Error, hook.second, "{}", res.unwrapErr());
+            thereWereErrors = true;
+        }
+    }
+    // free up memory
+    m_internalHooks.clear();
+    return !thereWereErrors;
+}
+
+void Loader::Impl::queueInGDThread(ScheduledFunction func) {
+    std::lock_guard<std::mutex> lock(m_gdThreadMutex);
+    m_gdThreadQueue.push_back(func);
+}
+
+void Loader::Impl::executeGDThreadQueue() {
+    // copy queue to avoid locking mutex if someone is
+    // running addToGDThread inside their function
+    m_gdThreadMutex.lock();
+    auto queue = m_gdThreadQueue;
+    m_gdThreadQueue.clear();
+    m_gdThreadMutex.unlock();
+
+    // call queue
+    for (auto const& func : queue) {
+        func();
+    }
+}
+
+void Loader::Impl::logConsoleMessage(std::string const& msg) {
+    if (m_platformConsoleOpen) {
+        // TODO: make flushing optional
+        std::cout << msg << '\n' << std::flush;
+    }
+}
+
+bool Loader::Impl::platformConsoleOpen() const {
+    return m_platformConsoleOpen;
+}
+
+bool Loader::Impl::shownInfoAlert(std::string const& key) {
+    if (m_shownInfoAlerts.count(key)) {
+        return true;
+    }
+    m_shownInfoAlerts.insert(key);
+    return false;
+}
+
+void Loader::Impl::saveInfoAlerts(nlohmann::json& json) {
+    json["alerts"] = m_shownInfoAlerts;
+}
+
+void Loader::Impl::loadInfoAlerts(nlohmann::json& json) {
+    m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>();
+}
+
+void Loader::Impl::downloadLoaderResources() {
+    auto version = this->getVersion().toString();
+    auto tempResourcesZip = dirs::getTempDir() / "new.zip";
+    auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID();
+
+    web::AsyncWebRequest()
+        .join("update-geode-loader-resources")
+        .fetch(fmt::format(
+            "https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version
+        ))
+        .into(tempResourcesZip)
+        .then([tempResourcesZip, resourcesDir](auto) {
+            // unzip resources zip
+            auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true);
+            if (!unzip) {
+                return ResourceDownloadEvent(
+                    UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr())
+                ).post();
+            }
+            ResourceDownloadEvent(UpdateFinished()).post();
+        })
+        .expect([](std::string const& info) {
+            ResourceDownloadEvent(
+                UpdateFailed("Unable to download resources: " + info)
+            ).post();
+        })
+        .progress([](auto&, double now, double total) {
+            ResourceDownloadEvent(
+                UpdateProgress(
+                    static_cast<uint8_t>(now / total * 100.0),
+                    "Downloading resources"
+                )
+            ).post();
+        });
+}
+
+bool Loader::Impl::verifyLoaderResources() {
+    static std::optional<bool> CACHED = std::nullopt;
+    if (CACHED.has_value()) {
+        return CACHED.value();
+    }
+
+    // geode/resources/geode.loader
+    auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID();
+
+    // if the resources dir doesn't exist, then it's probably incorrect
+    if (!(
+        ghc::filesystem::exists(resourcesDir) &&
+        ghc::filesystem::is_directory(resourcesDir)
+    )) {
+        this->downloadLoaderResources();
+        return false;
+    }
+
+    // make sure every file was covered
+    size_t coverage = 0;
+
+    // verify hashes
+    for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) {
+        auto name = file.path().filename().string();
+        // skip unknown files
+        if (!LOADER_RESOURCE_HASHES.count(name)) {
+            continue;
+        }
+        // verify hash
+        auto hash = calculateSHA256(file.path());
+        if (hash != LOADER_RESOURCE_HASHES.at(name)) {
+            log::debug(
+                "compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name)
+            );
+            this->downloadLoaderResources();
+            return false;
+        }
+        coverage += 1;
+    }
+
+    // make sure every file was found
+    if (coverage != LOADER_RESOURCE_HASHES.size()) {
+        this->downloadLoaderResources();
+        return false;
+    }
+
+    return true;
+}
+
+nlohmann::json Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) {
+    nlohmann::json reply;
+    try {
+        // parse received message
+        auto json = nlohmann::json::parse(buffer);
+        if (!json.contains("mod") || !json["mod"].is_string()) {
+            log::warn("Received IPC message without 'mod' field");
+            return reply;
+        }
+        if (!json.contains("message") || !json["message"].is_string()) {
+            log::warn("Received IPC message without 'message' field");
+            return reply;
+        }
+        nlohmann::json data;
+        if (json.contains("data")) {
+            data = json["data"];
+        }
+        // log::debug("Posting IPC event");
+        // ! warning: if the event system is ever made asynchronous this will break!
+        IPCEvent(rawHandle, json["mod"], json["message"], data, reply).post();
+    } catch(...) {
+        log::warn("Received IPC message that isn't valid JSON");
+    }
+    return reply;
+}
+
+ResourceDownloadEvent::ResourceDownloadEvent(
+    UpdateStatus const& status
+) : status(status) {}
+
+ListenerResult ResourceDownloadFilter::handle(
+    std::function<Callback> fn,
+    ResourceDownloadEvent* event
+) {
+    fn(event);
+    return ListenerResult::Propagate;
+}
+
+ResourceDownloadFilter::ResourceDownloadFilter() {}
\ No newline at end of file
diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp
new file mode 100644
index 00000000..c369368e
--- /dev/null
+++ b/loader/src/loader/LoaderImpl.hpp
@@ -0,0 +1,143 @@
+#include "FileWatcher.hpp"
+
+#include <Geode/external/json/json.hpp>
+#include <Geode/loader/Dirs.hpp>
+#include <Geode/loader/Index.hpp>
+#include <Geode/loader/Loader.hpp>
+#include <Geode/loader/Log.hpp>
+#include <Geode/loader/Mod.hpp>
+#include <Geode/utils/Result.hpp>
+#include <Geode/utils/map.hpp>
+#include <Geode/utils/ranges.hpp>
+#include <InternalMod.hpp>
+#include <about.hpp>
+#include <crashlog.hpp>
+#include <mutex>
+#include <optional>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+struct ResourceDownloadEvent : public Event {
+    const UpdateStatus status;
+    ResourceDownloadEvent(UpdateStatus const& status);
+};
+
+class GEODE_DLL ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> {
+public:
+    using Callback = void(ResourceDownloadEvent*);
+
+    ListenerResult handle(std::function<Callback> fn, ResourceDownloadEvent* event);
+    ResourceDownloadFilter();
+};
+
+// TODO: Find a file convention for impl headers
+namespace geode {
+    class LoaderImpl;
+}
+
+class Loader::Impl {
+public:
+    mutable std::mutex m_mutex;
+
+    std::vector<ghc::filesystem::path> m_modSearchDirectories;
+    std::vector<ModInfo> m_modsToLoad;
+    std::vector<InvalidGeodeFile> m_invalidMods;
+    std::unordered_map<std::string, Mod*> m_mods;
+    std::vector<ghc::filesystem::path> m_texturePaths;
+    std::vector<ScheduledFunction> m_scheduledFunctions;
+    mutable std::mutex m_scheduledFunctionsMutex;
+    bool m_isSetup = false;
+    std::atomic_bool m_earlyLoadFinished = false;
+
+    // InternalLoader
+    std::vector<std::function<void(void)>> m_gdThreadQueue;
+    mutable std::mutex m_gdThreadMutex;
+    bool m_platformConsoleOpen = false;
+    std::unordered_set<std::string> m_shownInfoAlerts;
+
+    std::vector<std::pair<Hook*, Mod*>> m_internalHooks;
+    bool m_readyToHook = false;
+
+    void saveInfoAlerts(nlohmann::json& json);
+    void loadInfoAlerts(nlohmann::json& json);
+
+    void downloadLoaderResources();
+
+    bool loadHooks();
+    void setupIPC();
+
+    Impl();
+    ~Impl();
+
+    void createDirectories();
+
+    void updateModResources(Mod* mod);
+    void addSearchPaths();
+
+    void dispatchScheduledFunctions(Mod* mod);
+    friend void GEODE_CALL ::geode_implicit_load(Mod*);
+
+    Result<Mod*> loadModFromInfo(ModInfo const& info);
+
+    Result<> setup();
+    void reset();
+
+    Result<> saveData();
+    Result<> loadData();
+
+    VersionInfo getVersion();
+    VersionInfo minModVersion();
+    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();
+    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* getInternalMod();
+    void updateAllDependencies();
+    std::vector<InvalidGeodeFile> getFailedMods() const;
+
+    void updateResources();
+
+    void scheduleOnModLoad(Mod* mod, ScheduledFunction func);
+    void waitForModsToBeLoaded();
+
+    bool didLastLaunchCrash() const;
+
+    nlohmann::json processRawIPC(void* rawHandle, std::string const& buffer);
+
+    /**
+     * Check if a one-time event has been shown to the user,
+     * and set it to true if not. Will return the previous
+     * state of the event before setting it to true
+     */
+    bool shownInfoAlert(std::string const& key);
+
+    void queueInGDThread(ScheduledFunction func);
+    void executeGDThreadQueue();
+
+    void logConsoleMessage(std::string const& msg);
+    bool platformConsoleOpen() const;
+    void openPlatformConsole();
+    void closePlatformConsole();
+    void platformMessageBox(char const* title, std::string const& info);
+
+    bool verifyLoaderResources();
+
+    bool isReadyToHook() const;
+    void addInternalHook(Hook* hook, Mod* mod);
+};
+
+namespace geode {
+    class LoaderImpl {
+    public:
+        static Loader::Impl* get();
+    };
+}
\ No newline at end of file
diff --git a/loader/src/loader/Log.cpp b/loader/src/loader/Log.cpp
index f3092882..6a597e8b 100644
--- a/loader/src/loader/Log.cpp
+++ b/loader/src/loader/Log.cpp
@@ -1,9 +1,10 @@
+#include "LoaderImpl.hpp"
+
 #include <Geode/loader/Dirs.hpp>
 #include <Geode/loader/Log.hpp>
 #include <Geode/loader/Mod.hpp>
 #include <Geode/utils/casts.hpp>
 #include <Geode/utils/general.hpp>
-#include <InternalLoader.hpp>
 #include <fmt/chrono.h>
 #include <fmt/format.h>
 #include <iomanip>
@@ -46,8 +47,13 @@ std::string log::parse(CCNode* obj) {
     if (obj) {
         auto bb = obj->boundingBox();
         return fmt::format(
-            "{{ {}, {}, ({}, {} | {} : {}) }}", typeid(*obj).name(), utils::intToHex(obj),
-            bb.origin.x, bb.origin.y, bb.size.width, bb.size.height
+            "{{ {}, {}, ({}, {} | {} : {}) }}",
+            typeid(*obj).name(),
+            utils::intToHex(obj),
+            bb.origin.x,
+            bb.origin.y,
+            bb.size.width,
+            bb.size.height
         );
     }
     else {
@@ -89,10 +95,7 @@ std::string log::parse(cocos2d::ccColor4B const& col) {
     return fmt::format("rgba({}, {}, {}, {})", col.r, col.g, col.b, col.a);
 }
 
-Log::Log(Mod* mod, Severity sev)
-  : m_sender(mod),
-    m_time(log_clock::now()),
-    m_severity(sev) {}
+Log::Log(Mod* mod, Severity sev) : m_sender(mod), m_time(log_clock::now()), m_severity(sev) {}
 
 bool Log::operator==(Log const& l) {
     return this == &l;
@@ -121,7 +124,7 @@ void Logs::setup() {
 void Logs::push(Log&& log) {
     std::string logStr = log.toString(true);
 
-    InternalLoader::get()->logConsoleMessage(logStr);
+    LoaderImpl::get()->logConsoleMessage(logStr);
     s_logStream << logStr << std::endl;
 
     s_logs.emplace_back(std::forward<Log>(log));
diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp
index 330c7d23..56c129c9 100644
--- a/loader/src/loader/Mod.cpp
+++ b/loader/src/loader/Mod.cpp
@@ -1,10 +1,11 @@
+#include "LoaderImpl.hpp"
+
+#include <Geode/loader/Dirs.hpp>
 #include <Geode/loader/Hook.hpp>
 #include <Geode/loader/Loader.hpp>
-#include <Geode/loader/Dirs.hpp>
 #include <Geode/loader/Log.hpp>
 #include <Geode/loader/Mod.hpp>
 #include <Geode/utils/file.hpp>
-#include <InternalLoader.hpp>
 #include <InternalMod.hpp>
 #include <optional>
 #include <string>
@@ -417,7 +418,7 @@ Result<> Mod::disableHook(Hook* hook) {
 }
 
 Result<Hook*> Mod::addHook(Hook* hook) {
-    if (InternalLoader::get()->isReadyToHook()) {
+    if (LoaderImpl::get()->isReadyToHook()) {
         auto res = this->enableHook(hook);
         if (!res) {
             delete hook;
@@ -425,7 +426,7 @@ Result<Hook*> Mod::addHook(Hook* hook) {
         }
     }
     else {
-        InternalLoader::get()->addInternalHook(hook, this);
+        LoaderImpl::get()->addInternalHook(hook, this);
     }
 
     return Ok(hook);
@@ -486,7 +487,7 @@ Result<> Mod::createTempDir() {
     if (!file::createDirectoryAll(tempDir)) {
         return Err("Unable to create mods' runtime directory");
     }
-    
+
     // Create geode/temp/mod.id
     auto tempPath = tempDir / m_info.id;
     if (!file::createDirectoryAll(tempPath)) {
diff --git a/loader/src/loader/ModInfo.cpp b/loader/src/loader/ModInfo.cpp
index 0fec58c1..3dad5578 100644
--- a/loader/src/loader/ModInfo.cpp
+++ b/loader/src/loader/ModInfo.cpp
@@ -133,19 +133,19 @@ Result<ModInfo> ModInfo::create(ModJson const& json) {
             "specified, or it is invalidally formatted (required: \"[v]X.X.X\")!"
         );
     }
-    if (schema < Loader::minModVersion()) {
+    if (schema < Loader::get()->minModVersion()) {
         return Err(
             "[mod.json] is built for an older version (" + schema.toString() +
-            ") of Geode (current: " + Loader::getVersion().toString() +
+            ") of Geode (current: " + Loader::get()->getVersion().toString() +
             "). Please update the mod to the latest version, "
             "and if the problem persists, contact the developer "
             "to update it."
         );
     }
-    if (schema > Loader::maxModVersion()) {
+    if (schema > Loader::get()->maxModVersion()) {
         return Err(
             "[mod.json] is built for a newer version (" + schema.toString() +
-            ") of Geode (current: " + Loader::getVersion().toString() +
+            ") of Geode (current: " + Loader::get()->getVersion().toString() +
             "). You need to update Geode in order to use "
             "this mod."
         );
@@ -195,8 +195,7 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) {
 
     // Read mod.json & parse if possible
     GEODE_UNWRAP_INTO(
-        auto jsonData,
-        unzip.extract("mod.json").expect("Unable to read mod.json: {error}")
+        auto jsonData, unzip.extract("mod.json").expect("Unable to read mod.json: {error}")
     );
     ModJson json;
     try {
@@ -213,10 +212,7 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) {
     auto info = res.unwrap();
     info.path = unzip.getPath();
 
-    GEODE_UNWRAP(
-        info.addSpecialFiles(unzip)
-            .expect("Unable to add extra files: {error}")
-    );
+    GEODE_UNWRAP(info.addSpecialFiles(unzip).expect("Unable to add extra files: {error}"));
 
     return Ok(info);
 }
@@ -225,9 +221,7 @@ Result<> ModInfo::addSpecialFiles(file::Unzip& unzip) {
     // unzip known MD files
     for (auto& [file, target] : getSpecialFiles()) {
         if (unzip.hasEntry(file)) {
-            GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect(
-                "Unable to extract \"{}\"", file
-            ));
+            GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect("Unable to extract \"{}\"", file));
             *target = sanitizeDetailsData(std::string(data.begin(), data.end()));
         }
     }
diff --git a/loader/src/main.cpp b/loader/src/main.cpp
index 210dd917..9694f989 100644
--- a/loader/src/main.cpp
+++ b/loader/src/main.cpp
@@ -1,4 +1,5 @@
 #include "../core/Core.hpp"
+#include "loader/LoaderImpl.hpp"
 
 #include <Geode/loader/IPC.hpp>
 #include <Geode/loader/Loader.hpp>
@@ -6,7 +7,6 @@
 #include <Geode/loader/Mod.hpp>
 #include <Geode/loader/Setting.hpp>
 #include <Geode/loader/SettingEvent.hpp>
-#include <InternalLoader.hpp>
 #include <InternalMod.hpp>
 #include <array>
 
@@ -108,7 +108,7 @@ static auto $_ =
             Loader::get()->openPlatformConsole();
         }
         else {
-            Loader::get()->closePlatfromConsole();
+            Loader::get()->closePlatformConsole();
         }
     });
 
@@ -147,18 +147,18 @@ static auto $_ = listenForIPC("list-mods", [](IPCEvent* event) -> nlohmann::json
 int geodeEntry(void* platformData) {
     // setup internals
 
-    if (!InternalLoader::get()) {
-        InternalLoader::platformMessageBox(
+    if (!Loader::get()) {
+        LoaderImpl::get()->platformMessageBox(
             "Unable to Load Geode!",
             "There was an unknown fatal error setting up "
             "internal tools and Geode can not be loaded. "
-            "(InternalLoader::get returned nullptr)"
+            "(Loader::get returned nullptr)"
         );
         return 1;
     }
 
     if (!geode::core::hook::initialize()) {
-        InternalLoader::platformMessageBox(
+        LoaderImpl::get()->platformMessageBox(
             "Unable to load Geode!",
             "There was an unknown fatal error setting up "
             "internal tools and Geode can not be loaded. "
@@ -169,30 +169,14 @@ int geodeEntry(void* platformData) {
 
     geode_implicit_load(InternalMod::get());
 
-    if (!InternalLoader::get()->setup()) {
-        // if we've made it here, Geode will
-        // be gettable (otherwise the call to
-        // setup would've immediately crashed)
-
-        InternalLoader::platformMessageBox(
-            "Unable to Load Geode!",
-            "There was an unknown fatal error setting up "
-            "internal tools and Geode can not be loaded. "
-            "(InternalLoader::setup) returned false"
-        );
-        return 1;
-    }
-
-    log::debug("Loaded internal Geode class");
-
     // set up loader, load mods, etc.
-    if (!Loader::get()->setup()) {
-        InternalLoader::platformMessageBox(
+    if (!LoaderImpl::get()->setup()) {
+        LoaderImpl::get()->platformMessageBox(
             "Unable to Load Geode!",
             "There was an unknown fatal error setting up "
             "the loader and Geode can not be loaded."
         );
-        delete InternalLoader::get();
+        LoaderImpl::get()->reset();
         return 1;
     }
 
diff --git a/loader/src/platform/ios/InternalLoader.cpp b/loader/src/platform/ios/InternalLoader.cpp
deleted file mode 100644
index a8f592b7..00000000
--- a/loader/src/platform/ios/InternalLoader.cpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#include <InternalLoader.hpp>
-
-#ifdef GEODE_IS_IOS
-
-#include <Geode/loader/Loader.hpp>
-#include <Geode/loader/Log.hpp>
-#include <Geode/loader/Dirs.hpp>
-#include <iostream>
-#include <InternalMod.hpp>
-#include <pwd.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-void InternalLoader::platformMessageBox(char const* title, std::string const& info) {
-    std::cout << title << ": " << info << std::endl;
-}
-
-void InternalLoader::openPlatformConsole() {
-    ghc::filesystem::path(getpwuid(getuid())->pw_dir);
-    freopen(
-        ghc::filesystem::path(dirs::getGeodeDir() / "geode_log.txt").string().c_str(), "w",
-        stdout
-    );
-    InternalLoader::m_platformConsoleOpen = true;
-}
-
-void InternalLoader::closePlatformConsole() {}
-
-void InternalLoader::postIPCReply(
-    void* rawPipeHandle,
-    std::string const& replyID,
-    nlohmann::json const& data
-) {}
-
-void InternalLoader::setupIPC() {
-    #warning "Set up pipes or smth for this platform"
-    log::log(Severity::Warning, InternalMod::get(), "IPC is not supported on this platform");
-}
-
-#endif
diff --git a/loader/src/platform/ios/LoaderImpl.cpp b/loader/src/platform/ios/LoaderImpl.cpp
new file mode 100644
index 00000000..f28c7e88
--- /dev/null
+++ b/loader/src/platform/ios/LoaderImpl.cpp
@@ -0,0 +1,35 @@
+#include <loader/LoaderImpl.hpp>
+
+#ifdef GEODE_IS_IOS
+
+    #include <Geode/loader/Dirs.hpp>
+    #include <Geode/loader/Loader.hpp>
+    #include <Geode/loader/Log.hpp>
+    #include <InternalMod.hpp>
+    #include <iostream>
+    #include <pwd.h>
+    #include <sys/types.h>
+    #include <unistd.h>
+
+void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
+    std::cout << title << ": " << info << std::endl;
+}
+
+void Loader::Impl::openPlatformConsole() {
+    ghc::filesystem::path(getpwuid(getuid())->pw_dir);
+    freopen(ghc::filesystem::path(dirs::getGeodeDir() / "geode_log.txt").string().c_str(), "w", stdout);
+    m_platformConsoleOpen = true;
+}
+
+void Loader::Impl::closePlatformConsole() {}
+
+void Loader::Impl::postIPCReply(
+    void* rawPipeHandle, std::string const& replyID, nlohmann::json const& data
+) {}
+
+void Loader::Impl::setupIPC() {
+    #warning "Set up pipes or smth for this platform"
+    log::warning("IPC is not supported on this platform");
+}
+
+#endif
diff --git a/loader/src/platform/mac/InternalLoader.cpp b/loader/src/platform/mac/LoaderImpl.cpp
similarity index 82%
rename from loader/src/platform/mac/InternalLoader.cpp
rename to loader/src/platform/mac/LoaderImpl.cpp
index 8c791e2a..8d98d12e 100644
--- a/loader/src/platform/mac/InternalLoader.cpp
+++ b/loader/src/platform/mac/LoaderImpl.cpp
@@ -1,14 +1,14 @@
 #include <Geode/loader/IPC.hpp>
 #include <Geode/loader/Log.hpp>
-#include <InternalLoader.hpp>
 #include <InternalMod.hpp>
 #include <iostream>
+#include <loader/LoaderImpl.hpp>
 
 #ifdef GEODE_IS_MACOS
 
     #include <CoreFoundation/CoreFoundation.h>
 
-void InternalLoader::platformMessageBox(char const* title, std::string const& info) {
+void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
     CFStringRef cfTitle = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8);
     CFStringRef cfMessage = CFStringCreateWithCString(NULL, info.c_str(), kCFStringEncodingUTF8);
 
@@ -17,7 +17,7 @@ void InternalLoader::platformMessageBox(char const* title, std::string const& in
     );
 }
 
-void InternalLoader::openPlatformConsole() {
+void Loader::Impl::openPlatformConsole() {
     m_platformConsoleOpen = true;
 
     for (auto const& log : log::Logs::list()) {
@@ -25,7 +25,7 @@ void InternalLoader::openPlatformConsole() {
     }
 }
 
-void InternalLoader::closePlatformConsole() {
+void Loader::Impl::closePlatformConsole() {
     m_platformConsoleOpen = false;
 }
 
@@ -34,11 +34,11 @@ CFDataRef msgPortCallback(CFMessagePortRef port, SInt32 messageID, CFDataRef dat
 
     std::string cdata(reinterpret_cast<char const*>(CFDataGetBytePtr(data)), CFDataGetLength(data));
 
-    std::string reply = InternalLoader::processRawIPC(port, cdata);
+    std::string reply = LoaderImpl::get()->processRawIPC(port, cdata);
     return CFDataCreate(NULL, (UInt8 const*)reply.data(), reply.size());
 }
 
-void InternalLoader::setupIPC() {
+void Loader::Impl::setupIPC() {
     std::thread([]() {
         CFStringRef portName = CFStringCreateWithCString(NULL, IPC_PORT_NAME, kCFStringEncodingUTF8);
 
diff --git a/loader/src/platform/windows/InternalLoader.cpp b/loader/src/platform/windows/LoaderImpl.cpp
similarity index 85%
rename from loader/src/platform/windows/InternalLoader.cpp
rename to loader/src/platform/windows/LoaderImpl.cpp
index dbb691f1..8e1b688d 100644
--- a/loader/src/platform/windows/InternalLoader.cpp
+++ b/loader/src/platform/windows/LoaderImpl.cpp
@@ -1,8 +1,8 @@
 #include <Geode/loader/IPC.hpp>
 #include <Geode/loader/Log.hpp>
-#include <InternalLoader.hpp>
 #include <InternalMod.hpp>
 #include <iostream>
+#include <loader/LoaderImpl.hpp>
 
 USE_GEODE_NAMESPACE();
 
@@ -10,11 +10,11 @@ USE_GEODE_NAMESPACE();
 
 static constexpr auto IPC_BUFFER_SIZE = 512;
 
-void InternalLoader::platformMessageBox(char const* title, std::string const& info) {
+void Loader::Impl::platformMessageBox(char const* title, std::string const& info) {
     MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR);
 }
 
-void InternalLoader::openPlatformConsole() {
+void Loader::Impl::openPlatformConsole() {
     if (m_platformConsoleOpen) return;
     if (AllocConsole() == 0) return;
     SetConsoleCP(CP_UTF8);
@@ -29,7 +29,7 @@ void InternalLoader::openPlatformConsole() {
     }
 }
 
-void InternalLoader::closePlatformConsole() {
+void Loader::Impl::closePlatformConsole() {
     if (!m_platformConsoleOpen) return;
 
     fclose(stdin);
@@ -43,11 +43,13 @@ void ipcPipeThread(HANDLE pipe) {
     char buffer[IPC_BUFFER_SIZE * sizeof(TCHAR)];
     DWORD read;
 
+    std::optional<std::string> replyID = std::nullopt;
+
     // log::debug("Waiting for I/O");
     if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) {
         buffer[read] = '\0';
 
-        auto reply = InternalLoader::processRawIPC((void*)pipe, buffer).dump();
+        std::string reply = LoaderImpl::get()->processRawIPC((void*)pipe, buffer);
 
         DWORD written;
         WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr);
@@ -61,7 +63,7 @@ void ipcPipeThread(HANDLE pipe) {
     // log::debug("Disconnected pipe");
 }
 
-void InternalLoader::setupIPC() {
+void Loader::Impl::setupIPC() {
     std::thread([]() {
         while (true) {
             auto pipe = CreateNamedPipeA(
diff --git a/loader/src/ui/internal/GeodeUI.cpp b/loader/src/ui/internal/GeodeUI.cpp
index 6aefeb64..bf24db94 100644
--- a/loader/src/ui/internal/GeodeUI.cpp
+++ b/loader/src/ui/internal/GeodeUI.cpp
@@ -1,11 +1,12 @@
 
-#include <Geode/loader/Index.hpp>
-#include <Geode/loader/Dirs.hpp>
 #include "info/ModInfoPopup.hpp"
 #include "list/ModListLayer.hpp"
 #include "settings/ModSettingsPopup.hpp"
-#include <Geode/ui/MDPopup.hpp>
+
+#include <Geode/loader/Dirs.hpp>
+#include <Geode/loader/Index.hpp>
 #include <Geode/ui/GeodeUI.hpp>
+#include <Geode/ui/MDPopup.hpp>
 #include <Geode/utils/web.hpp>
 
 void geode::openModsList() {
@@ -38,9 +39,11 @@ void geode::openIssueReportPopup(Mod* mod) {
             "[#support](https://discord.com/channels/911701438269386882/979352389985390603) "
             "channnel in the [Geode Discord Server](https://discord.gg/9e43WMKzhp)\n\n"
             "If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
-            "latest crash log(s) from `" + dirs::getCrashlogsDir().string() + "`",
+            "latest crash log(s) from `" +
+                dirs::getCrashlogsDir().string() + "`",
             "OK"
-        )->show();
+        )
+            ->show();
     }
 }
 
@@ -71,13 +74,11 @@ CCNode* geode::createDefaultLogo(CCSize const& size) {
 
 CCNode* geode::createModLogo(Mod* mod, CCSize const& size) {
     CCNode* spr = nullptr;
-    if (mod == Loader::getInternalMod()) {
+    if (mod == Loader::get()->getInternalMod()) {
         spr = CCSprite::createWithSpriteFrameName("geode-logo.png"_spr);
     }
     else {
-        spr = CCSprite::create(
-            fmt::format("{}/logo.png", mod->getID()).c_str()
-        );
+        spr = CCSprite::create(fmt::format("{}/logo.png", mod->getID()).c_str());
     }
     if (!spr) spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr);
     if (!spr) spr = CCLabelBMFont::create("N/A", "goldFont.fnt");
@@ -101,7 +102,7 @@ CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) {
         auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr);
         logoGlow->setScaleX(glowSize.width / logoGlow->getContentSize().width);
         logoGlow->setScaleY(glowSize.height / logoGlow->getContentSize().height);
-        
+
         // i dont know why + 1 is needed and its too late for me to figure out why
         spr->setPosition(
             logoGlow->getContentSize().width / 2 + 1,
diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp
index 047d5609..8f02f941 100644
--- a/loader/src/ui/internal/info/ModInfoPopup.cpp
+++ b/loader/src/ui/internal/info/ModInfoPopup.cpp
@@ -3,7 +3,6 @@
 #include "../dev/HookListLayer.hpp"
 #include "../list/ModListLayer.hpp"
 #include "../settings/ModSettingsPopup.hpp"
-#include <InternalLoader.hpp>
 #include <Geode/loader/Dirs.hpp>
 
 #include <Geode/binding/ButtonSprite.hpp>
@@ -12,15 +11,17 @@
 #include <Geode/binding/Slider.hpp>
 #include <Geode/binding/SliderThumb.hpp>
 #include <Geode/binding/SliderTouchLogic.hpp>
+#include <Geode/loader/Dirs.hpp>
 #include <Geode/loader/Mod.hpp>
 #include <Geode/ui/BasedButton.hpp>
+#include <Geode/ui/GeodeUI.hpp>
 #include <Geode/ui/IconButtonSprite.hpp>
 #include <Geode/ui/GeodeUI.hpp>
 #include <Geode/ui/MDPopup.hpp>
 #include <Geode/utils/casts.hpp>
 #include <Geode/utils/ranges.hpp>
 #include <Geode/utils/web.hpp>
-#include <InternalLoader.hpp>
+#include <loader/LoaderImpl.hpp>
 
 // TODO: die
 #undef min
@@ -28,7 +29,7 @@
 
 static constexpr int const TAG_CONFIRM_UNINSTALL = 5;
 static constexpr int const TAG_DELETE_SAVEDATA = 6;
-static const CCSize LAYER_SIZE = { 440.f, 290.f };
+static const CCSize LAYER_SIZE = {440.f, 290.f};
 
 bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
     m_noElasticity = true;
@@ -36,11 +37,11 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
 
     auto winSize = CCDirector::sharedDirector()->getWinSize();
 
-    if (!this->initWithColor({ 0, 0, 0, 105 })) return false;
+    if (!this->initWithColor({0, 0, 0, 105})) return false;
     m_mainLayer = CCLayer::create();
     this->addChild(m_mainLayer);
 
-    auto bg = CCScale9Sprite::create("GJ_square01.png", { 0.0f, 0.0f, 80.0f, 80.0f });
+    auto bg = CCScale9Sprite::create("GJ_square01.png", {0.0f, 0.0f, 80.0f, 80.0f});
     bg->setContentSize(LAYER_SIZE);
     bg->setPosition(winSize.width / 2, winSize.height / 2);
     bg->setZOrder(-10);
@@ -57,33 +58,27 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
     nameLabel->limitLabelWidth(200.f, .7f, .1f);
     m_mainLayer->addChild(nameLabel, 2);
 
-    auto logoSpr = this->createLogo({ logoSize, logoSize });
+    auto logoSpr = this->createLogo({logoSize, logoSize});
     m_mainLayer->addChild(logoSpr);
 
     auto developerStr = "by " + info.developer;
     auto developerLabel = CCLabelBMFont::create(developerStr.c_str(), "goldFont.fnt");
     developerLabel->setScale(.5f);
-    developerLabel->setAnchorPoint({ .0f, .5f });
+    developerLabel->setAnchorPoint({.0f, .5f});
     m_mainLayer->addChild(developerLabel);
 
     auto logoTitleWidth =
-        std::max(
-            nameLabel->getScaledContentSize().width,
-            developerLabel->getScaledContentSize().width
-        ) +
+        std::max(nameLabel->getScaledContentSize().width, developerLabel->getScaledContentSize().width) +
         logoSize + logoOffset;
 
     nameLabel->setPosition(
-        winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset,
-        winSize.height / 2 + 125.f
+        winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, winSize.height / 2 + 125.f
+    );
+    logoSpr->setPosition(
+        {winSize.width / 2 - logoTitleWidth / 2 + logoSize / 2, winSize.height / 2 + 115.f}
     );
-    logoSpr->setPosition({
-        winSize.width / 2 - logoTitleWidth / 2 + logoSize / 2,
-        winSize.height / 2 + 115.f 
-    });
     developerLabel->setPosition(
-        winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset,
-        winSize.height / 2 + 105.f
+        winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, winSize.height / 2 + 105.f
     );
 
     auto versionLabel = CCLabelBMFont::create(
@@ -96,7 +91,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
         nameLabel->getPositionX() + nameLabel->getScaledContentSize().width + 5.f,
         winSize.height / 2 + 125.f
     );
-    versionLabel->setColor({ 0, 255, 0 });
+    versionLabel->setColor({0, 255, 0});
     m_mainLayer->addChild(versionLabel);
 
     CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
@@ -123,27 +118,33 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
     if (info.changelog) {
         m_changelogArea = MDTextArea::create(info.changelog.value(), { 350.f, 137.5f });
         m_changelogArea->setPosition(
-            -5000.f, winSize.height / 2 -
-            m_changelogArea->getScaledContentSize().height / 2 - 20.f
+            -5000.f, winSize.height / 2 - m_changelogArea->getScaledContentSize().height / 2 - 20.f
         );
         m_changelogArea->setVisible(false);
         m_mainLayer->addChild(m_changelogArea);
 
         auto changelogBtnOffSpr = ButtonSprite::create(
             CCSprite::createWithSpriteFrameName("changelog.png"_spr),
-            0x20, true, 32.f, "GJ_button_01.png", 1.f
+            0x20,
+            true,
+            32.f,
+            "GJ_button_01.png",
+            1.f
         );
         changelogBtnOffSpr->setScale(.65f);
 
         auto changelogBtnOnSpr = ButtonSprite::create(
             CCSprite::createWithSpriteFrameName("changelog.png"_spr),
-            0x20, true, 32.f, "GJ_button_02.png", 1.f
+            0x20,
+            true,
+            32.f,
+            "GJ_button_02.png",
+            1.f
         );
         changelogBtnOnSpr->setScale(.65f);
 
         auto changelogBtn = CCMenuItemToggler::create(
-            changelogBtnOffSpr, changelogBtnOnSpr,
-            this, menu_selector(ModInfoPopup::onChangelog)
+            changelogBtnOffSpr, changelogBtnOnSpr, this, menu_selector(ModInfoPopup::onChangelog)
         );
         changelogBtn->setPosition(-LAYER_SIZE.width / 2 + 21.5f, .0f);
         m_buttonMenu->addChild(changelogBtn);
@@ -153,51 +154,38 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) {
     auto infoSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png");
     infoSpr->setScale(.85f);
 
-    m_infoBtn = CCMenuItemSpriteExtra::create(
-        infoSpr, this, menu_selector(ModInfoPopup::onInfo)
-    );
-    m_infoBtn->setPosition(
-        LAYER_SIZE.width / 2 - 25.f,
-        LAYER_SIZE.height / 2 - 25.f
-    );
+    m_infoBtn = CCMenuItemSpriteExtra::create(infoSpr, this, menu_selector(ModInfoPopup::onInfo));
+    m_infoBtn->setPosition(LAYER_SIZE.width / 2 - 25.f, LAYER_SIZE.height / 2 - 25.f);
     m_buttonMenu->addChild(m_infoBtn);
 
     // repo button
     if (info.repository) {
         auto repoBtn = CCMenuItemSpriteExtra::create(
-            CCSprite::createWithSpriteFrameName("github.png"_spr), this,
+            CCSprite::createWithSpriteFrameName("github.png"_spr),
+            this,
             menu_selector(ModInfoPopup::onRepository)
         );
-        repoBtn->setPosition(
-            LAYER_SIZE.width / 2 - 25.f,
-            -LAYER_SIZE.height / 2 + 25.f
-        );
+        repoBtn->setPosition(LAYER_SIZE.width / 2 - 25.f, -LAYER_SIZE.height / 2 + 25.f);
         m_buttonMenu->addChild(repoBtn);
     }
 
     // support button
     if (info.supportInfo) {
         auto supportBtn = CCMenuItemSpriteExtra::create(
-            CCSprite::createWithSpriteFrameName("gift.png"_spr), this,
+            CCSprite::createWithSpriteFrameName("gift.png"_spr),
+            this,
             menu_selector(ModInfoPopup::onSupport)
         );
-        supportBtn->setPosition(
-            LAYER_SIZE.width / 2 - 60.f,
-            -LAYER_SIZE.height / 2 + 25.f
-        );
+        supportBtn->setPosition(LAYER_SIZE.width / 2 - 60.f, -LAYER_SIZE.height / 2 + 25.f);
         m_buttonMenu->addChild(supportBtn);
     }
 
     auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png");
     closeSpr->setScale(.8f);
 
-    auto closeBtn = CCMenuItemSpriteExtra::create(
-        closeSpr, this, menu_selector(ModInfoPopup::onClose)
-    );
-    closeBtn->setPosition(
-        -LAYER_SIZE.width / 2 + 3.f,
-        LAYER_SIZE.height / 2 - 3.f
-    );
+    auto closeBtn =
+        CCMenuItemSpriteExtra::create(closeSpr, this, menu_selector(ModInfoPopup::onClose));
+    closeBtn->setPosition(-LAYER_SIZE.width / 2 + 3.f, LAYER_SIZE.height / 2 - 3.f);
     m_buttonMenu->addChild(closeBtn);
 
     this->setKeypadEnabled(true);
@@ -233,8 +221,11 @@ void ModInfoPopup::onInfo(CCObject*) {
             info.developer,
             info.path.string()
         ),
-        "OK", nullptr, 400.f
-    )->show();
+        "OK",
+        nullptr,
+        400.f
+    )
+        ->show();
 }
 
 void ModInfoPopup::onChangelog(CCObject* sender) {
@@ -245,18 +236,16 @@ void ModInfoPopup::onChangelog(CCObject* sender) {
     // as it turns out, cocos2d is stupid and still passes touch
     // events to invisible nodes
     m_detailsArea->setPositionX(
-        toggle->isToggled() ?
-            winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2 :
-            -5000.f
+        toggle->isToggled() ? winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2 :
+                              -5000.f
     );
 
     m_changelogArea->setVisible(!toggle->isToggled());
     // as it turns out, cocos2d is stupid and still passes touch
     // events to invisible nodes
     m_changelogArea->setPositionX(
-        !toggle->isToggled() ?
-            winSize.width / 2 - m_changelogArea->getScaledContentSize().width / 2 :
-            -5000.f
+        !toggle->isToggled() ? winSize.width / 2 - m_changelogArea->getScaledContentSize().width / 2 :
+                               -5000.f
     );
 }
 
@@ -287,24 +276,17 @@ void ModInfoPopup::setInstallStatus(std::optional<UpdateProgress> const& progres
 bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
     m_mod = mod;
 
-    if (!ModInfoPopup::init(mod->getModInfo(), list))
-        return false;
+    if (!ModInfoPopup::init(mod->getModInfo(), list)) return false;
 
     auto winSize = CCDirector::sharedDirector()->getWinSize();
 
     // mod settings
-    auto settingsSpr = CCSprite::createWithSpriteFrameName(
-        "GJ_optionsBtn_001.png"
-    );
+    auto settingsSpr = CCSprite::createWithSpriteFrameName("GJ_optionsBtn_001.png");
     settingsSpr->setScale(.65f);
 
-    auto settingsBtn = CCMenuItemSpriteExtra::create(
-        settingsSpr, this, menu_selector(LocalModInfoPopup::onSettings)
-    );
-    settingsBtn->setPosition(
-        -LAYER_SIZE.width / 2 + 25.f,
-        -LAYER_SIZE.height / 2 + 25.f
-    );
+    auto settingsBtn =
+        CCMenuItemSpriteExtra::create(settingsSpr, this, menu_selector(LocalModInfoPopup::onSettings));
+    settingsBtn->setPosition(-LAYER_SIZE.width / 2 + 25.f, -LAYER_SIZE.height / 2 + 25.f);
     m_buttonMenu->addChild(settingsBtn);
 
     // Check if a config directory for the mod exists
@@ -317,31 +299,23 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
         auto configBtn = CCMenuItemSpriteExtra::create(
             configSpr, this, menu_selector(LocalModInfoPopup::onOpenConfigDir)
         );
-        configBtn->setPosition(
-            -LAYER_SIZE.width / 2 + 65.f,
-            -LAYER_SIZE.height / 2 + 25.f
-        );
+        configBtn->setPosition(-LAYER_SIZE.width / 2 + 65.f, -LAYER_SIZE.height / 2 + 25.f);
         m_buttonMenu->addChild(configBtn);
     }
 
     if (!mod->hasSettings()) {
-        settingsSpr->setColor({ 150, 150, 150 });
+        settingsSpr->setColor({150, 150, 150});
         settingsBtn->setTarget(this, menu_selector(LocalModInfoPopup::onNoSettings));
     }
 
-    auto enableBtnSpr = ButtonSprite::create(
-        "Enable", "bigFont.fnt", "GJ_button_01.png", .6f
-    );
+    auto enableBtnSpr = ButtonSprite::create("Enable", "bigFont.fnt", "GJ_button_01.png", .6f);
     enableBtnSpr->setScale(.6f);
 
-    auto disableBtnSpr = ButtonSprite::create(
-        "Disable", "bigFont.fnt", "GJ_button_06.png", .6f
-    );
+    auto disableBtnSpr = ButtonSprite::create("Disable", "bigFont.fnt", "GJ_button_06.png", .6f);
     disableBtnSpr->setScale(.6f);
 
     auto enableBtn = CCMenuItemToggler::create(
-        disableBtnSpr, enableBtnSpr,
-        this, menu_selector(LocalModInfoPopup::onEnableMod)
+        disableBtnSpr, enableBtnSpr, this, menu_selector(LocalModInfoPopup::onEnableMod)
     );
     enableBtn->setPosition(-155.f, 75.f);
     enableBtn->toggle(!mod->isEnabled());
@@ -349,8 +323,8 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
 
     if (!mod->supportsDisabling()) {
         enableBtn->setTarget(this, menu_selector(LocalModInfoPopup::onDisablingNotSupported));
-        enableBtnSpr->setColor({ 150, 150, 150 });
-        disableBtnSpr->setColor({ 150, 150, 150 });
+        enableBtnSpr->setColor({150, 150, 150});
+        disableBtnSpr->setColor({150, 150, 150});
     }
 
     if (mod != Loader::get()->getInternalMod()) {
@@ -372,21 +346,17 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
             m_installBtnSpr = IconButtonSprite::create(
                 "GE_button_01.png"_spr,
                 CCSprite::createWithSpriteFrameName("install.png"_spr),
-                "Update", "bigFont.fnt"
+                "Update",
+                "bigFont.fnt"
             );
             m_installBtnSpr->setScale(.6f);
 
-            m_installBtn = CCMenuItemSpriteExtra::create(
-                m_installBtnSpr, this, nullptr
-            );
+            m_installBtn = CCMenuItemSpriteExtra::create(m_installBtnSpr, this, nullptr);
             m_installBtn->setPosition(-8.0f, 75.f);
             m_buttonMenu->addChild(m_installBtn);
 
             m_installStatus = DownloadStatusNode::create();
-            m_installStatus->setPosition(
-                winSize.width / 2 + 105.f,
-                winSize.height / 2 + 75.f
-            );
+            m_installStatus->setPosition(winSize.width / 2 + 105.f, winSize.height / 2 + 75.f);
             m_installStatus->setVisible(false);
             m_mainLayer->addChild(m_installStatus);
 
@@ -395,15 +365,13 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) {
                 "bigFont.fnt"
             );
             m_updateVersionLabel->setScale(.35f);
-            m_updateVersionLabel->setAnchorPoint({ .0f, .5f });
-            m_updateVersionLabel->setColor({ 94, 219, 255 });
-            m_updateVersionLabel->setPosition(
-                winSize.width / 2 + 35.f, winSize.height / 2 + 75.f
-            );
+            m_updateVersionLabel->setAnchorPoint({.0f, .5f});
+            m_updateVersionLabel->setColor({94, 219, 255});
+            m_updateVersionLabel->setPosition(winSize.width / 2 + 35.f, winSize.height / 2 + 75.f);
             m_mainLayer->addChild(m_updateVersionLabel);
         }
     }
-    
+
     // issue report button
     if (mod->getModInfo().issues) {
         auto issuesBtnSpr = ButtonSprite::create(
@@ -435,12 +403,11 @@ void LocalModInfoPopup::onIssues(CCObject*) {
 
 void LocalModInfoPopup::onUninstall(CCObject*) {
     auto layer = FLAlertLayer::create(
-        this, "Confirm Uninstall",
-        fmt::format(
-            "Are you sure you want to uninstall <cr>{}</c>?",
-            m_mod->getName()
-        ),
-        "Cancel", "OK"
+        this,
+        "Confirm Uninstall",
+        fmt::format("Are you sure you want to uninstall <cr>{}</c>?", m_mod->getName()),
+        "Cancel",
+        "OK"
     );
     layer->setTag(TAG_CONFIRM_UNINSTALL);
     layer->show();
@@ -462,19 +429,13 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
     if (as<CCMenuItemToggler*>(sender)->isToggled()) {
         auto res = m_mod->loadBinary();
         if (!res) {
-            FLAlertLayer::create(
-                nullptr, "Error Loading Mod",
-                res.unwrapErr(), "OK", nullptr
-            )->show();
+            FLAlertLayer::create(nullptr, "Error Loading Mod", res.unwrapErr(), "OK", nullptr)->show();
         }
     }
     else {
         auto res = m_mod->disable();
         if (!res) {
-            FLAlertLayer::create(
-                nullptr, "Error Disabling Mod",
-                res.unwrapErr(), "OK", nullptr
-            )->show();
+            FLAlertLayer::create(nullptr, "Error Disabling Mod", res.unwrapErr(), "OK", nullptr)->show();
         }
     }
     if (m_layer) m_layer->updateAllStates(nullptr);
@@ -486,11 +447,7 @@ void LocalModInfoPopup::onOpenConfigDir(CCObject*) {
 }
 
 void LocalModInfoPopup::onDisablingNotSupported(CCObject* pSender) {
-    FLAlertLayer::create(
-        "Unsupported",
-        "<cr>Disabling</c> is not supported for this mod.",
-        "OK"
-    )->show();
+    FLAlertLayer::create("Unsupported", "<cr>Disabling</c> is not supported for this mod.", "OK")->show();
     as<CCMenuItemToggler*>(pSender)->toggle(m_mod->isEnabled());
 }
 
@@ -514,14 +471,10 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
         case TAG_DELETE_SAVEDATA: {
             if (btn2) {
                 if (ghc::filesystem::remove_all(m_mod->getSaveDir())) {
-                    FLAlertLayer::create(
-                        "Deleted", "The mod's save data was deleted.", "OK"
-                    )->show();
+                    FLAlertLayer::create("Deleted", "The mod's save data was deleted.", "OK")->show();
                 }
                 else {
-                    FLAlertLayer::create(
-                        "Error", "Unable to delete mod's save directory!", "OK"
-                    )->show();
+                    FLAlertLayer::create("Error", "Unable to delete mod's save directory!", "OK")->show();
                 }
             }
             if (m_layer) m_layer->reloadList();
@@ -533,18 +486,19 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
 void LocalModInfoPopup::doUninstall() {
     auto res = m_mod->uninstall();
     if (!res) {
-        return FLAlertLayer::create(
-            "Uninstall failed :(", res.unwrapErr(), "OK"
-        )->show();
+        return FLAlertLayer::create("Uninstall failed :(", res.unwrapErr(), "OK")->show();
     }
     auto layer = FLAlertLayer::create(
-        this, "Uninstall complete",
+        this,
+        "Uninstall complete",
         "Mod was succesfully uninstalled! :) "
         "(You may need to <cy>restart the game</c> "
         "for the mod to take full effect). "
         "<co>Would you also like to delete the mod's "
         "save data?</c>",
-        "Cancel", "Delete", 350.f
+        "Cancel",
+        "Delete",
+        350.f
     );
     layer->setTag(TAG_DELETE_SAVEDATA);
     layer->show();
@@ -574,12 +528,13 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
 
     auto winSize = CCDirector::sharedDirector()->getWinSize();
 
-    if (!ModInfoPopup::init(item->info, list))
-        return false;
+    if (!ModInfoPopup::init(item->info, list)) return false;
 
     m_installBtnSpr = IconButtonSprite::create(
-        "GE_button_01.png"_spr, CCSprite::createWithSpriteFrameName("install.png"_spr),
-        "Install", "bigFont.fnt"
+        "GE_button_01.png"_spr,
+        CCSprite::createWithSpriteFrameName("install.png"_spr),
+        "Install",
+        "bigFont.fnt"
     );
     m_installBtnSpr->setScale(.6f);
 
@@ -590,13 +545,10 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) {
     m_buttonMenu->addChild(m_installBtn);
 
     m_installStatus = DownloadStatusNode::create();
-    m_installStatus->setPosition(
-        winSize.width / 2 - 25.f,
-        winSize.height / 2 + 75.f
-    );
+    m_installStatus->setPosition(winSize.width / 2 - 25.f, winSize.height / 2 + 75.f);
     m_installStatus->setVisible(false);
     m_mainLayer->addChild(m_installStatus);
-    
+
     return true;
 }
 
diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp
index 838a022a..85814836 100644
--- a/loader/src/ui/internal/list/ModListCell.cpp
+++ b/loader/src/ui/internal/list/ModListCell.cpp
@@ -1,13 +1,14 @@
 #include "ModListCell.hpp"
 #include "ModListLayer.hpp"
 #include "../info/ModInfoPopup.hpp"
-#include <Geode/binding/StatsCell.hpp>
-#include <Geode/binding/FLAlertLayer.hpp>
+
 #include <Geode/binding/ButtonSprite.hpp>
 #include <Geode/binding/CCMenuItemSpriteExtra.hpp>
 #include <Geode/binding/CCMenuItemToggler.hpp>
+#include <Geode/binding/FLAlertLayer.hpp>
+#include <Geode/binding/StatsCell.hpp>
 #include <Geode/ui/GeodeUI.hpp>
-#include <InternalLoader.hpp>
+#include "../../../loader/LoaderImpl.hpp" // how should i include this src/loader/LoaderImpl.hpp
 #include "../info/TagNode.hpp"
 
 template <class T>
@@ -93,11 +94,11 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForTags) {
     m_menu->addChild(creatorBtn);
 
     if (hasDesc) {
-        auto descBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f });
-        descBG->setColor({ 0, 0, 0 });
+        auto descBG = CCScale9Sprite::create("square02b_001.png", {0.0f, 0.0f, 80.0f, 80.0f});
+        descBG->setColor({0, 0, 0});
         descBG->setOpacity(90);
-        descBG->setContentSize({ m_width * 2, 60.f });
-        descBG->setAnchorPoint({ .0f, .5f });
+        descBG->setContentSize({m_width * 2, 60.f});
+        descBG->setAnchorPoint({.0f, .5f});
         descBG->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
         if (spaceForTags) {
             descBG->setPositionY(m_height / 2 - 7.5f);
@@ -211,20 +212,15 @@ bool ModCell::init(
 
     this->setupInfo(mod->getModInfo(), false);
 
-    auto viewSpr = ButtonSprite::create(
-        "View", "bigFont.fnt", "GJ_button_01.png", .8f
-    );
+    auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
     viewSpr->setScale(.65f);
 
-    auto viewBtn = CCMenuItemSpriteExtra::create(
-        viewSpr, this, menu_selector(ModCell::onInfo)
-    );
+    auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ModCell::onInfo));
     m_menu->addChild(viewBtn);
 
     if (m_mod->wasSuccesfullyLoaded() && m_mod->supportsDisabling()) {
-        m_enableToggle = CCMenuItemToggler::createWithStandardSprites(
-            this, menu_selector(ModCell::onEnable), .7f
-        );
+        m_enableToggle =
+            CCMenuItemToggler::createWithStandardSprites(this, menu_selector(ModCell::onEnable), .7f);
         m_enableToggle->setPosition(-45.f, 0.f);
         m_menu->addChild(m_enableToggle);
     }
@@ -232,23 +228,22 @@ bool ModCell::init(
     auto exMark = CCSprite::createWithSpriteFrameName("exMark_001.png");
     exMark->setScale(.5f);
 
-    m_unresolvedExMark = CCMenuItemSpriteExtra::create(
-        exMark, this, menu_selector(ModCell::onUnresolvedInfo)
-    );
+    m_unresolvedExMark =
+        CCMenuItemSpriteExtra::create(exMark, this, menu_selector(ModCell::onUnresolvedInfo));
     m_unresolvedExMark->setPosition(-80.f, 0.f);
     m_unresolvedExMark->setVisible(false);
     m_menu->addChild(m_unresolvedExMark);
 
     // if (m_mod->wasSuccesfullyLoaded()) {
-        // if (Index::get()->isUpdateAvailableForItem(m_obj->m_mod->getID())) {
-        //     viewSpr->updateBGImage("GE_button_01.png"_spr);
+    // if (Index::get()->isUpdateAvailableForItem(m_obj->m_mod->getID())) {
+    //     viewSpr->updateBGImage("GE_button_01.png"_spr);
 
-        //     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);
-        // }
+    //     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();
@@ -300,9 +295,7 @@ bool IndexItemCell::init(
     );
     viewSpr->setScale(.65f);
 
-    auto viewBtn = CCMenuItemSpriteExtra::create(
-        viewSpr, this, menu_selector(IndexItemCell::onInfo)
-    );
+    auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
     m_menu->addChild(viewBtn);
 
     if (item->tags.size()) {
@@ -370,7 +363,8 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
                 "Unable to remove <cy>" + m_info.path.string() + "</c>: <cr>" +
                     std::string(e.what()) + "</c>",
                 "OK"
-            )->show();
+            )
+                ->show();
         }
         Loader::get()->refreshModsList();
         m_layer->reloadList();
@@ -392,7 +386,7 @@ bool InvalidGeodeFileCell::init(
     this->addChild(menu);
 
     auto titleLabel = CCLabelBMFont::create("Failed to Load", "bigFont.fnt");
-    titleLabel->setAnchorPoint({ .0f, .5f });
+    titleLabel->setAnchorPoint({.0f, .5f});
     titleLabel->setScale(.5f);
     titleLabel->setPosition(m_height / 2, m_height / 2 + 7.f);
     this->addChild(titleLabel);
@@ -407,14 +401,11 @@ bool InvalidGeodeFileCell::init(
     pathLabel->setColor({ 255, 255, 0 });
     this->addChild(pathLabel);
 
-    auto whySpr = ButtonSprite::create(
-        "Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f
-    );
+    auto whySpr = ButtonSprite::create("Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f);
     whySpr->setScale(.65f);
 
-    auto viewBtn = CCMenuItemSpriteExtra::create(
-        whySpr, this, menu_selector(InvalidGeodeFileCell::onInfo)
-    );
+    auto viewBtn =
+        CCMenuItemSpriteExtra::create(whySpr, this, menu_selector(InvalidGeodeFileCell::onInfo));
     menu->addChild(viewBtn);
 
     return true;
diff --git a/loader/src/ui/internal/list/ModListLayer.cpp b/loader/src/ui/internal/list/ModListLayer.cpp
index 053fcf22..b8648551 100644
--- a/loader/src/ui/internal/list/ModListLayer.cpp
+++ b/loader/src/ui/internal/list/ModListLayer.cpp
@@ -166,7 +166,7 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
             // something matches query better
             // the sorted list is in reverse so adding it last = adding it at the 
             // top
-            auto imod = Loader::getInternalMod();
+            auto imod = Mod::get();
             if (auto match = queryMatch(query, imod)) {
                 sorted.insert({ match.value(), imod });
             }