From c8fed17ea428ce245155bc9efe23fe3392cace66 Mon Sep 17 00:00:00 2001
From: altalk23 <45172705+altalk23@users.noreply.github.com>
Date: Sat, 10 Dec 2022 17:08:25 +0300
Subject: [PATCH 1/4] add pad to playerobject

---
 bindings/GeometryDash.bro | 1 +
 1 file changed, 1 insertion(+)

diff --git a/bindings/GeometryDash.bro b/bindings/GeometryDash.bro
index cf9fafb1..6e374aab 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;

From 3a31efe1132f8383c5362f6954545544fd5aafbe Mon Sep 17 00:00:00 2001
From: altalk23 <45172705+altalk23@users.noreply.github.com>
Date: Sat, 10 Dec 2022 19:30:14 +0300
Subject: [PATCH 2/4] implement pimpl for loader

---
 loader/CMakeLists.txt                         |   2 +
 loader/include/Geode/loader/Loader.hpp        |  56 +-
 loader/include/Geode/loader/Mod.hpp           |  52 +-
 loader/include/Geode/loader/Types.hpp         |   6 +-
 loader/include/Geode/utils/cocos.hpp          |   2 +-
 loader/src/hooks/LoadingLayer.cpp             |  64 +-
 loader/src/hooks/MenuLayer.cpp                | 207 +++---
 loader/src/hooks/save.cpp                     |   6 +-
 loader/src/hooks/update.cpp                   |   4 +-
 loader/src/internal/InternalLoader.cpp        | 250 --------
 loader/src/internal/InternalLoader.hpp        |  89 ---
 loader/src/internal/InternalMod.cpp           |  10 +-
 loader/src/loader/Dirs.cpp                    |   3 +-
 loader/src/loader/Hook.cpp                    |   1 -
 loader/src/loader/IPC.cpp                     |  29 +-
 loader/src/loader/Loader.cpp                  | 514 ++++-----------
 loader/src/loader/LoaderImpl.cpp              | 590 ++++++++++++++++++
 loader/src/loader/LoaderImpl.hpp              | 142 +++++
 loader/src/loader/Log.cpp                     |  19 +-
 loader/src/loader/Mod.cpp                     |  17 +-
 loader/src/loader/ModInfo.cpp                 |  30 +-
 loader/src/main.cpp                           |  34 +-
 loader/src/platform/ios/InternalLoader.cpp    |  40 --
 loader/src/platform/ios/LoaderImpl.cpp        |  35 ++
 .../{InternalLoader.cpp => LoaderImpl.cpp}    |  12 +-
 .../{InternalLoader.cpp => LoaderImpl.cpp}    |  12 +-
 loader/src/ui/internal/GeodeUI.cpp            |  36 +-
 loader/src/ui/internal/info/ModInfoPopup.cpp  | 284 ++++-----
 loader/src/ui/internal/list/ModListCell.cpp   | 155 ++---
 loader/src/ui/internal/list/ModListView.cpp   |  22 +-
 30 files changed, 1358 insertions(+), 1365 deletions(-)
 delete mode 100644 loader/src/internal/InternalLoader.cpp
 delete mode 100644 loader/src/internal/InternalLoader.hpp
 create mode 100644 loader/src/loader/LoaderImpl.cpp
 create mode 100644 loader/src/loader/LoaderImpl.hpp
 delete mode 100644 loader/src/platform/ios/InternalLoader.cpp
 create mode 100644 loader/src/platform/ios/LoaderImpl.cpp
 rename loader/src/platform/mac/{InternalLoader.cpp => LoaderImpl.cpp} (82%)
 rename loader/src/platform/windows/{InternalLoader.cpp => LoaderImpl.cpp} (86%)

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 3139f3e2..2e38cc1c 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 m_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);
-        Result<> loadModsFromDirectory(
-            ghc::filesystem::path const& dir,
-            bool recursive = true
-        );
+        Result<> loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
         Result<> 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 d55147cf..bf96122d 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();
@@ -108,7 +107,6 @@ namespace geode {
 
         friend class ::InternalMod;
         friend class Loader;
-        friend class ::InternalLoader;
         friend struct ModInfo;
 
         template <class = void>
@@ -173,46 +171,50 @@ 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
          */
-        template<class T>
+        template <class T>
         void setSavedValue(std::string const& key, T const& value) {
             m_saved[key] = value;
         }
@@ -338,9 +340,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
@@ -388,7 +390,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 aabf344b..c7542983 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 {
@@ -149,7 +149,7 @@ namespace geode {
     };
 
     constexpr std::string_view GEODE_MOD_EXTENSION = ".geode";
-    
+
     class Mod;
     class Setting;
     class Loader;
@@ -165,7 +165,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 e37d749b..7121519c 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();
 
@@ -19,23 +19,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");
         }
@@ -48,30 +46,30 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> {
     }
 
     void updateResourcesProgress(ResourceDownloadEvent* event) {
-        std::visit(makeVisitor {
-            [&](UpdateProgress const& progress) {
-                this->setUpdateText(fmt::format(
-                    "Downloading Resources: {}%", progress.first
-                ));
-            },
-            [&](UpdateFinished) {
-                this->setUpdateText("Resources Downloaded");
-                m_fields->m_updatingResources = false;
-                this->loadAssets();
-            },
-            [&](UpdateError const& error) {
-                InternalLoader::platformMessageBox(
-                    "Error updating resources",
-                    "Unable to update Geode resources: " + 
-                    error + ".\n"
-                    "The game will be loaded as normal, but please be aware "
-                    "that it may very likely crash."
-                );
-                this->setUpdateText("Resource Download Failed");
-                m_fields->m_updatingResources = false;
-                this->loadAssets();
-            }
-        }, event->status);
+        std::visit(
+            makeVisitor{
+                [&](UpdateProgress const& progress) {
+                    this->setUpdateText(fmt::format("Downloading Resources: {}%", progress.first));
+                },
+                [&](UpdateFinished) {
+                    this->setUpdateText("Resources Downloaded");
+                    m_fields->m_updatingResources = false;
+                    this->loadAssets();
+                },
+                [&](UpdateError const& error) {
+                    LoaderImpl::get()->platformMessageBox(
+                        "Error updating resources",
+                        "Unable to update Geode resources: " + error +
+                            ".\n"
+                            "The game will be loaded as normal, but please be aware "
+                            "that it may very likely crash."
+                    );
+                    this->setUpdateText("Resource Download Failed");
+                    m_fields->m_updatingResources = false;
+                    this->loadAssets();
+                }},
+            event->status
+        );
     }
 
     void loadAssets() {
diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp
index af80cdad..9bc6cac2 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();
 
@@ -23,116 +22,112 @@ static Ref<Notification> INDEX_UPDATE_NOTIF = nullptr;
 static Ref<CCSprite> g_geodeButton = nullptr;
 
 struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
-	void destructor() {
-		g_geodeButton = nullptr;
-		MenuLayer::~MenuLayer();
-	}
+    void destructor() {
+        g_geodeButton = nullptr;
+        MenuLayer::~MenuLayer();
+    }
 
-	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);
 
-		// add geode button
-		
-		g_geodeButton = SafeCreate<CCSprite>()
-			.with(CircleButtonSprite::createWithSpriteFrameName(
-				"geode-logo-outline-gold.png"_spr,
-				1.0f,
-				CircleBaseColor::Green,
-				CircleBaseSize::Medium2
-			))
-			.orMake<ButtonSprite>("!!");
+        auto winSize = CCDirector::sharedDirector()->getWinSize();
 
-		auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
+        // add geode button
 
-		auto btn = CCMenuItemSpriteExtra::create(
-			g_geodeButton.data(), this, menu_selector(CustomMenuLayer::onGeode)
-		);
-		btn->setID("geode-button"_spr);
-		bottomMenu->addChild(btn);
+        g_geodeButton =
+            SafeCreate<CCSprite>()
+                .with(CircleButtonSprite::createWithSpriteFrameName(
+                    "geode-logo-outline-gold.png"_spr, 1.0f, CircleBaseColor::Green, CircleBaseSize::Medium2
+                ))
+                .orMake<ButtonSprite>("!!");
 
-		bottomMenu->updateLayout();
+        auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
 
-		if (auto node = this->getChildByID("settings-gamepad-icon")) {
-			node->setPositionX(bottomMenu->getChildByID(
-				"settings-button"
-			)->getPositionX() + winSize.width / 2);
-		}
+        auto btn = CCMenuItemSpriteExtra::create(
+            g_geodeButton.data(), this, menu_selector(CustomMenuLayer::onGeode)
+        );
+        btn->setID("geode-button"_spr);
+        bottomMenu->addChild(btn);
 
-		// 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();
-			}
+        bottomMenu->updateLayout();
+
+        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();
+            }
+        }
 
-		// update mods index
-		if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
-			this->addChild(EventListenerNode<IndexUpdateFilter>::create(
-				this, &CustomMenuLayer::onIndexUpdate
-			));
-			INDEX_UPDATE_NOTIF = Notification::create(
-				"Updating Index", NotificationIcon::Loading, 0
-			);
-			INDEX_UPDATE_NOTIF->show();
-			Index::get()->update();
-		}
-	
-		return true;
-	}
+        // 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();
+        }
 
-	void onIndexUpdate(IndexUpdateEvent* event) {
-		if (!INDEX_UPDATE_NOTIF) return;
-		std::visit(makeVisitor {
-			[](UpdateProgress const& prog) {},
-			[](UpdateFinished const&) {
-				INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Success);
-				INDEX_UPDATE_NOTIF->setString("Index Up-to-Date");
-				INDEX_UPDATE_NOTIF->waitAndHide();
-				INDEX_UPDATE_NOTIF = nullptr;
-			},
-			[](UpdateError const& info) {
-				INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Error);
-				INDEX_UPDATE_NOTIF->setString(info);
-				INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
-				INDEX_UPDATE_NOTIF = nullptr;
-			},
-		}, event->status);
-	}
+        // update mods index
+        if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
+            this->addChild(
+                EventListenerNode<IndexUpdateFilter>::create(this, &CustomMenuLayer::onIndexUpdate)
+            );
+            INDEX_UPDATE_NOTIF = Notification::create("Updating Index", NotificationIcon::Loading, 0);
+            INDEX_UPDATE_NOTIF->show();
+            Index::get()->update();
+        }
 
-	void onGeode(CCObject*) {
-		ModListLayer::scene();
-	}
+        return true;
+    }
+
+    void onIndexUpdate(IndexUpdateEvent* event) {
+        if (!INDEX_UPDATE_NOTIF) return;
+        std::visit(
+            makeVisitor{
+                [](UpdateProgress const& prog) {},
+                [](UpdateFinished const&) {
+                    INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Success);
+                    INDEX_UPDATE_NOTIF->setString("Index Up-to-Date");
+                    INDEX_UPDATE_NOTIF->waitAndHide();
+                    INDEX_UPDATE_NOTIF = nullptr;
+                },
+                [](UpdateError const& info) {
+                    INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Error);
+                    INDEX_UPDATE_NOTIF->setString(info);
+                    INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
+                    INDEX_UPDATE_NOTIF = nullptr;
+                },
+            },
+            event->status
+        );
+    }
+
+    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 1353eb27..00000000
--- a/loader/src/internal/InternalLoader.cpp
+++ /dev/null
@@ -1,250 +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;
-}
-
-bool InternalLoader::shownInfoAlert(std::string const& key) {
-    if (m_shownInfoAlerts.count(key)) {
-        return true;
-    }
-    m_shownInfoAlerts.insert(key);
-    return false;
-}
-
-void InternalLoader::saveInfoAlerts(nlohmann::json& json) {
-    json["alerts"] = m_shownInfoAlerts;
-}
-
-void InternalLoader::loadInfoAlerts(nlohmann::json& json) {
-    m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>();
-}
-
-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(
-                    UpdateError("Unable to unzip new resources: " + unzip.unwrapErr())
-                ).post();
-            }
-            ResourceDownloadEvent(UpdateFinished()).post();
-        })
-        .expect([](std::string const& info) {
-            ResourceDownloadEvent(
-                UpdateError("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;
-}
-
-std::string InternalLoader::processRawIPC(void* rawHandle, std::string const& buffer) {
-    std::string reply;
-
-    try {
-        std::optional<std::string> replyID = std::nullopt;
-
-        // 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;
-        }
-        if (json.contains("reply") && json["reply"].is_string()) {
-            replyID = json["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 57707acc..00000000
--- a/loader/src/internal/InternalLoader.hpp
+++ /dev/null
@@ -1,89 +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::unordered_set<std::string> m_shownInfoAlerts;
-
-    std::vector<std::pair<Hook*, Mod*>> m_internalHooks;
-    bool m_readyToHook;
-
-    void saveInfoAlerts(nlohmann::json& json);
-    void loadInfoAlerts(nlohmann::json& json);
-
-    void downloadLoaderResources();
-
-    bool loadHooks();
-    void setupIPC();
-
-    InternalLoader();
-    ~InternalLoader();
-
-    friend class Loader;
-
-public:
-    static InternalLoader* get();
-
-    bool setup();
-
-    static std::string 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();
-    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 9575ebdb..cab08cd1 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..275802fc 100644
--- a/loader/src/loader/Dirs.cpp
+++ b/loader/src/loader/Dirs.cpp
@@ -14,8 +14,9 @@ ghc::filesystem::path dirs::getSaveDir() {
         // 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(
-            ghc::filesystem::weakly_canonical(
+            std::filesystem::weakly_canonical(
                 CCFileUtils::sharedFileUtils()->getWritablePath().c_str()
             ).string()
         );
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 ff1d0293..74e7d225 100644
--- a/loader/src/loader/IPC.cpp
+++ b/loader/src/loader/IPC.cpp
@@ -1,24 +1,17 @@
 #include <Geode/loader/IPC.hpp>
-#include <InternalLoader.hpp>
 
 USE_GEODE_NAMESPACE();
 
 IPCEvent::IPCEvent(
-    void* rawPipeHandle,
-    std::string const& targetModID,
-    std::string const& messageID,
-    nlohmann::json const& messageData,
-    std::string* replyString
-) : m_rawPipeHandle(rawPipeHandle),
-    m_targetModID(targetModID),
-    m_messageID(messageID),
-    m_replyString(replyString),
+    void* rawPipeHandle, std::string const& targetModID, std::string const& messageID,
+    nlohmann::json const& messageData, std::string* replyString
+) :
+    m_rawPipeHandle(rawPipeHandle),
+    m_targetModID(targetModID), m_messageID(messageID), m_replyString(replyString),
     m_messageData(messageData) {}
 
 IPCEvent::~IPCEvent() {}
 
-
-
 std::string IPCEvent::getTargetModID() const {
     return m_targetModID;
 }
@@ -40,18 +33,12 @@ nlohmann::json IPCEvent::getMessageData() const {
 }
 
 ListenerResult IPCFilter::handle(std::function<Callback> fn, IPCEvent* event) {
-    if (
-        event->getTargetModID() == m_modID &&
-        event->getMessageID() == m_messageID
-    ) {
+    if (event->getTargetModID() == m_modID && event->getMessageID() == m_messageID) {
         event->setReplyString(fn(event));
         return ListenerResult::Stop;
     }
     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 5d542fdb..e4193e2d 100644
--- a/loader/src/loader/Loader.cpp
+++ b/loader/src/loader/Loader.cpp
@@ -1,388 +1,126 @@
-
-#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>
-
-USE_GEODE_NAMESPACE();
-
-Loader* Loader::get() {
-    return InternalLoader::get();
-}
-
-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());
-    }
-    GEODE_UNWRAP(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.m_id)) {
-        return Err(fmt::format("Mod with ID '{}' already loaded", info.m_id));
-    }
-
-    // create Mod instance
-    auto mod = new Mod(info);
-    m_mods.insert({ info.m_id, mod });
-    mod->m_enabled = InternalMod::get()->getSavedValue<bool>(
-        "should-load-" + info.m_id, true
-    );
-    // this loads the mod if its dependencies are resolved
-    mod->updateDependencyStates();
-
-    // add mod resources
-    this->queueInGDThread([this, mod]() {
-        auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
-
-        CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
-        this->updateModResources(mod);
-    });
-
-    return Ok(mod);
-}
-
-Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) {
-    auto res = ModInfo::createFromGeodeFile(file);
-    if (!res) {
-        m_invalidMods.push_back(InvalidGeodeFile {
-            .m_path = file,
-            .m_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);
-}
-
-void Loader::updateModResources(Mod* mod) {
-    if (!mod->m_info.m_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.m_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.m_id, sheet
-            );
-        }
-        else {
-            CCTextureCache::get()->addImage(png.c_str(), false);
-            CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
-        }
-    }
-}
-
-// Dependencies and refreshing
-
-Result<> 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) {
-            GEODE_UNWRAP(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.m_path == entry.path();
-        })) {
-            continue;
-        }
-
-        // if mods should be loaded immediately, do that
-        if (m_earlyLoadFinished) {
-            GEODE_UNWRAP(this->loadModFromFile(entry));
-        }
-        // 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 {
-                    .m_path = entry.path(),
-                    .m_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);
-        }
-    }
-    return Ok();
-}
-
-Result<> Loader::refreshModsList() {
-    log::debug("Loading mods...");
-
-    // find mods
-    for (auto& dir : m_modSearchDirectories) {
-        GEODE_UNWRAP(this->loadModsFromDirectory(dir));
-    }
-    
-    // load early-load mods first
-    for (auto& mod : m_modsToLoad) {
-        if (mod.m_needsEarlyLoad) {
-            GEODE_UNWRAP(this->loadModFromInfo(mod));
-        }
-    }
-
-    // UI can be loaded now
-    m_earlyLoadFinished = true;
-
-    // load the rest of the mods
-    for (auto& mod : m_modsToLoad) {
-        if (!mod.m_needsEarlyLoad) {
-            GEODE_UNWRAP(this->loadModFromInfo(mod));
-        }
-    }
-    m_modsToLoad.clear();
-
-    return Ok();
-}
-
-void Loader::updateAllDependencies() {
-    for (auto const& [_, mod] : m_mods) {
-        mod->updateDependencyStates();
-    }
-}
-
-void Loader::waitForModsToBeLoaded() {
-    while (!m_earlyLoadFinished) {}
-}
-
-// Misc
-
-void Loader::queueInGDThread(ScheduledFunction func) {
-    InternalLoader::get()->queueInGDThread(func);
-}
-
-bool Loader::didLastLaunchCrash() const {
-    return crashlog::didLastLaunchCrash();
-}
-
-void Loader::openPlatformConsole() {
-    InternalLoader::get()->openPlatformConsole();
-}
-
-void Loader::closePlatfromConsole() {
-    InternalLoader::get()->closePlatformConsole();
-}
+#include "LoaderImpl.hpp"
+
+Loader::Loader() : m_impl(new Impl) {}
+
+Loader::~Loader() {}
+
+Loader* Loader::get() {
+    static auto g_geode = new Loader;
+    return g_geode;
+}
+
+void Loader::createDirectories() {
+    return m_impl->createDirectories();
+}
+
+void Loader::updateModResources(Mod* mod) {
+    return m_impl->updateModResources(mod);
+}
+
+void Loader::addSearchPaths() {
+    return m_impl->addSearchPaths();
+}
+
+void Loader::dispatchScheduledFunctions(Mod* mod) {
+    return m_impl->dispatchScheduledFunctions(mod);
+}
+
+Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) {
+    return m_impl->loadModFromInfo(info);
+}
+
+Result<> Loader::saveData() {
+    return m_impl->saveData();
+}
+
+Result<> Loader::loadData() {
+    return m_impl->loadData();
+}
+
+VersionInfo Loader::getVersion() {
+    return m_impl->getVersion();
+}
+
+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);
+}
+
+Result<> Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) {
+    return m_impl->loadModsFromDirectory(dir, recursive);
+}
+
+Result<> Loader::refreshModsList() {
+    return m_impl->refreshModsList();
+}
+
+bool Loader::isModInstalled(std::string const& id) const {
+    return m_impl->isModInstalled(id);
+}
+
+Mod* Loader::getInstalledMod(std::string const& id) const {
+    return m_impl->getInstalledMod(id);
+}
+
+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() {
+    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() {
+    return m_impl->waitForModsToBeLoaded();
+}
+
+void Loader::openPlatformConsole() {
+    return m_impl->openPlatformConsole();
+}
+
+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..f6bd4212
--- /dev/null
+++ b/loader/src/loader/LoaderImpl.cpp
@@ -0,0 +1,590 @@
+
+#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() {}
+
+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());
+}
+
+// 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::debug("Set up internal mod representation");
+    log::debug("Loading hooks... ");
+
+    if (!this->loadHooks()) {
+        log::error("There were errors loading some hooks, see console for details");
+        return Err("There were errors loading some hooks, see console for details");
+    }
+
+    log::debug("Loaded hooks");
+
+    log::debug("Setting up IPC...");
+
+    this->setupIPC();
+
+    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());
+    }
+    GEODE_UNWRAP(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.m_id)) {
+        return Err(fmt::format("Mod with ID '{}' already loaded", info.m_id));
+    }
+
+    // create Mod instance
+    auto mod = new Mod(info);
+    m_mods.insert({info.m_id, mod});
+    mod->m_enabled = InternalMod::get()->getSavedValue<bool>("should-load-" + info.m_id, true);
+    // this loads the mod if its dependencies are resolved
+    mod->updateDependencyStates();
+
+    // add mod resources
+    this->queueInGDThread([this, mod]() {
+        auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
+
+        CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
+        this->updateModResources(mod);
+    });
+
+    return Ok(mod);
+}
+
+Result<Mod*> Loader::Impl::loadModFromFile(ghc::filesystem::path const& file) {
+    auto res = ModInfo::createFromGeodeFile(file);
+    if (!res) {
+        m_invalidMods.push_back(InvalidGeodeFile{
+            .m_path = file,
+            .m_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.m_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.m_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.m_id,
+                sheet
+            );
+        }
+        else {
+            CCTextureCache::get()->addImage(png.c_str(), false);
+            CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str());
+        }
+    }
+}
+
+// Dependencies and refreshing
+
+Result<> 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) {
+            GEODE_UNWRAP(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.m_path == entry.path();
+            })) {
+            continue;
+        }
+
+        // if mods should be loaded immediately, do that
+        if (m_earlyLoadFinished) {
+            GEODE_UNWRAP(this->loadModFromFile(entry));
+        }
+        // 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{
+                    .m_path = entry.path(),
+                    .m_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);
+        }
+    }
+    return Ok();
+}
+
+Result<> Loader::Impl::refreshModsList() {
+    log::debug("Loading mods...");
+
+    // find mods
+    for (auto& dir : m_modSearchDirectories) {
+        GEODE_UNWRAP(this->loadModsFromDirectory(dir));
+    }
+
+    // load early-load mods first
+    for (auto& mod : m_modsToLoad) {
+        if (mod.m_needsEarlyLoad) {
+            GEODE_UNWRAP(this->loadModFromInfo(mod));
+        }
+    }
+
+    // UI can be loaded now
+    m_earlyLoadFinished = true;
+
+    // load the rest of the mods
+    for (auto& mod : m_modsToLoad) {
+        if (!mod.m_needsEarlyLoad) {
+            GEODE_UNWRAP(this->loadModFromInfo(mod));
+        }
+    }
+    m_modsToLoad.clear();
+
+    return Ok();
+}
+
+void Loader::Impl::updateAllDependencies() {
+    for (auto const& [_, mod] : m_mods) {
+        mod->updateDependencyStates();
+    }
+}
+
+void Loader::Impl::waitForModsToBeLoaded() {
+    while (!m_earlyLoadFinished) {}
+}
+
+bool Loader::Impl::didLastLaunchCrash() const {
+    return crashlog::didLastLaunchCrash();
+}
+
+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(
+                           UpdateError("Unable to unzip new resources: " + unzip.unwrapErr())
+                )
+                    .post();
+            }
+            ResourceDownloadEvent(UpdateFinished()).post();
+        })
+        .expect([](std::string const& info) {
+            ResourceDownloadEvent(UpdateError("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;
+}
+
+std::string Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) {
+    std::string reply;
+
+    try {
+        std::optional<std::string> replyID = std::nullopt;
+
+        // 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;
+        }
+        if (json.contains("reply") && json["reply"].is_string()) {
+            replyID = json["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() {}
diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp
new file mode 100644
index 00000000..6643f9ab
--- /dev/null
+++ b/loader/src/loader/LoaderImpl.hpp
@@ -0,0 +1,142 @@
+#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);
+    Result<> loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
+    Result<> 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 queueInGDThread(ScheduledFunction func);
+    void scheduleOnModLoad(Mod* mod, ScheduledFunction func);
+    void waitForModsToBeLoaded();
+
+    bool didLastLaunchCrash() const;
+
+    std::string 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 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 caa771bb..ac79f202 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>
@@ -408,7 +409,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;
@@ -416,7 +417,7 @@ Result<Hook*> Mod::addHook(Hook* hook) {
         }
     }
     else {
-        InternalLoader::get()->addInternalHook(hook, this);
+        LoaderImpl::get()->addInternalHook(hook, this);
     }
 
     return Ok(hook);
@@ -477,7 +478,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.m_id;
     if (!file::createDirectoryAll(tempPath)) {
@@ -487,9 +488,9 @@ Result<> Mod::createTempDir() {
     // Unzip .geode file into temp dir
     GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_info.m_path));
     if (!unzip.hasEntry(m_info.m_binaryName)) {
-        return Err(fmt::format(
-            "Unable to find platform binary under the name \"{}\"", m_info.m_binaryName
-        ));
+        return Err(
+            fmt::format("Unable to find platform binary under the name \"{}\"", m_info.m_binaryName)
+        );
     }
     GEODE_UNWRAP(unzip.extractAllTo(tempPath));
 
diff --git a/loader/src/loader/ModInfo.cpp b/loader/src/loader/ModInfo.cpp
index c2c9f1e9..375291e1 100644
--- a/loader/src/loader/ModInfo.cpp
+++ b/loader/src/loader/ModInfo.cpp
@@ -49,7 +49,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
     for (auto& dep : root.has("dependencies").iterate()) {
         auto obj = dep.obj();
 
-        auto depobj = Dependency {};
+        auto depobj = Dependency{};
         obj.needs("id").validate(&ModInfo::validateID).into(depobj.m_id);
         obj.needs("version").validate(&VersionInfo::validate).intoAs<std::string>(depobj.m_version);
         obj.has("required").into(depobj.m_required);
@@ -61,7 +61,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
     for (auto& [key, value] : root.has("settings").items()) {
         GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, value.json()));
         sett->m_modID = info.m_id;
-        info.m_settings.push_back({ key, sett });
+        info.m_settings.push_back({key, sett});
     }
 
     if (auto resources = root.has("resources").obj()) {
@@ -120,19 +120,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."
         );
@@ -182,8 +182,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 {
@@ -200,10 +199,7 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) {
     auto info = res.unwrap();
     info.m_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);
 }
@@ -212,9 +208,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()));
         }
     }
@@ -237,9 +231,9 @@ Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) {
 
 std::vector<std::pair<std::string, std::optional<std::string>*>> ModInfo::getSpecialFiles() {
     return {
-        { "about.md", &m_details },
-        { "changelog.md", &m_changelog },
-        { "support.md", &m_supportInfo },
+        {"about.md", &m_details},
+        {"changelog.md", &m_changelog},
+        {"support.md", &m_supportInfo},
     };
 }
 
diff --git a/loader/src/main.cpp b/loader/src/main.cpp
index 2a57c3a6..e36d4b83 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 86%
rename from loader/src/platform/windows/InternalLoader.cpp
rename to loader/src/platform/windows/LoaderImpl.cpp
index ad07754f..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);
@@ -49,7 +49,7 @@ void ipcPipeThread(HANDLE pipe) {
     if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) {
         buffer[read] = '\0';
 
-        std::string reply = InternalLoader::processRawIPC((void*)pipe, buffer);
+        std::string reply = LoaderImpl::get()->processRawIPC((void*)pipe, buffer);
 
         DWORD written;
         WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr);
@@ -63,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 807a0e45..0dd11431 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() {
@@ -21,12 +22,11 @@ void geode::openIssueReportPopup(Mod* mod) {
                 "If your issue relates to a <cr>game crash</c>, <cb>please include</c> the "
                 "latest crash log(s) from `" +
                 dirs::getCrashlogsDir().string() + "`",
-            "OK", (mod->getModInfo().m_issues.value().m_url ? "Open URL" : ""),
+            "OK",
+            (mod->getModInfo().m_issues.value().m_url ? "Open URL" : ""),
             [mod](bool btn2) {
                 if (btn2) {
-                    web::openLinkInBrowser(
-                        mod->getModInfo().m_issues.value().m_url.value()
-                    );
+                    web::openLinkInBrowser(mod->getModInfo().m_issues.value().m_url.value());
                 }
             }
         )->show();
@@ -38,9 +38,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();
     }
 }
 
@@ -49,9 +51,7 @@ void geode::openInfoPopup(Mod* mod) {
 }
 
 void geode::openIndexPopup(Mod* mod) {
-    if (auto item = Index::get()->getItem(
-        mod->getID(), mod->getVersion().getMajor()
-    )) {
+    if (auto item = Index::get()->getItem(mod->getID(), mod->getVersion().getMajor())) {
         IndexItemInfoPopup::create(item, nullptr)->show();
     }
 }
@@ -73,13 +73,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");
@@ -103,11 +101,9 @@ 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, logoGlow->getContentSize().height / 2
-        );
+        spr->setPosition(logoGlow->getContentSize().width / 2, logoGlow->getContentSize().height / 2);
         // scary mathematics
         spr->setScaleX(size.width / spr->getContentSize().width / logoGlow->getScaleX());
         spr->setScaleY(size.height / spr->getContentSize().height / logoGlow->getScaleY());
diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp
index 50ce37d7..986dc478 100644
--- a/loader/src/ui/internal/info/ModInfoPopup.cpp
+++ b/loader/src/ui/internal/info/ModInfoPopup.cpp
@@ -2,10 +2,8 @@
 
 #include "../dev/HookListLayer.hpp"
 #include "../list/ModListView.hpp"
-#include "../settings/ModSettingsPopup.hpp"
 #include "../settings/AdvancedSettingsPopup.hpp"
-#include <InternalLoader.hpp>
-#include <Geode/loader/Dirs.hpp>
+#include "../settings/ModSettingsPopup.hpp"
 
 #include <Geode/binding/ButtonSprite.hpp>
 #include <Geode/binding/CCTextInputNode.hpp>
@@ -13,14 +11,15 @@
 #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/IconButtonSprite.hpp>
 #include <Geode/ui/GeodeUI.hpp>
+#include <Geode/ui/IconButtonSprite.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 +27,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, ModListView* list) {
     m_noElasticity = true;
@@ -36,11 +35,11 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* 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);
@@ -53,58 +52,48 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* list) {
     constexpr float logoOffset = 10.f;
 
     auto nameLabel = CCLabelBMFont::create(info.m_name.c_str(), "bigFont.fnt");
-    nameLabel->setAnchorPoint({ .0f, .5f });
+    nameLabel->setAnchorPoint({.0f, .5f});
     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.m_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(
-        info.m_version.toString().c_str(),
-        "bigFont.fnt"
-    );
-    versionLabel->setAnchorPoint({ .0f, .5f });
+    auto versionLabel = CCLabelBMFont::create(info.m_version.toString().c_str(), "bigFont.fnt");
+    versionLabel->setAnchorPoint({.0f, .5f});
     versionLabel->setScale(.4f);
     versionLabel->setPosition(
         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);
     this->registerWithTouchDispatcher();
 
     m_detailsArea = MDTextArea::create(
-        (info.m_details ? info.m_details.value() : "### No description provided."),
-        { 350.f, 137.5f }
+        (info.m_details ? info.m_details.value() : "### No description provided."), {350.f, 137.5f}
     );
     m_detailsArea->setPosition(
         winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2,
@@ -121,29 +110,35 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* list) {
 
     // changelog
     if (info.m_changelog) {
-        m_changelogArea = MDTextArea::create(info.m_changelog.value(), { 350.f, 137.5f });
+        m_changelogArea = MDTextArea::create(info.m_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 +148,38 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* 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.m_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.m_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);
@@ -208,10 +190,9 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* list) {
 
 void ModInfoPopup::onSupport(CCObject*) {
     MDPopup::create(
-        "Support " + this->getModInfo().m_name,
-        this->getModInfo().m_supportInfo.value(),
-        "OK"
-    )->show();
+        "Support " + this->getModInfo().m_name, this->getModInfo().m_supportInfo.value(), "OK"
+    )
+        ->show();
 }
 
 void ModInfoPopup::onRepository(CCObject*) {
@@ -233,8 +214,11 @@ void ModInfoPopup::onInfo(CCObject*) {
             info.m_developer,
             info.m_path.string()
         ),
-        "OK", nullptr, 400.f
-    )->show();
+        "OK",
+        nullptr,
+        400.f
+    )
+        ->show();
 }
 
 void ModInfoPopup::onChangelog(CCObject* sender) {
@@ -245,18 +229,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
     );
 }
 
@@ -277,24 +259,17 @@ void ModInfoPopup::onClose(CCObject* pSender) {
 bool LocalModInfoPopup::init(Mod* mod, ModListView* 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
@@ -307,31 +282,23 @@ bool LocalModInfoPopup::init(Mod* mod, ModListView* 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());
@@ -339,8 +306,8 @@ bool LocalModInfoPopup::init(Mod* mod, ModListView* 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()) {
@@ -351,15 +318,11 @@ bool LocalModInfoPopup::init(Mod* mod, ModListView* list) {
         auto advSettBtn = CCMenuItemSpriteExtra::create(
             advSettSpr, this, menu_selector(LocalModInfoPopup::onAdvancedSettings)
         );
-        advSettBtn->setPosition(
-            m_infoBtn->getPositionX() - 30.f,
-            m_infoBtn->getPositionY()
-        );
+        advSettBtn->setPosition(m_infoBtn->getPositionX() - 30.f, m_infoBtn->getPositionY());
         m_buttonMenu->addChild(advSettBtn);
 
-        auto uninstallBtnSpr = ButtonSprite::create(
-            "Uninstall", "bigFont.fnt", "GJ_button_05.png", .6f
-        );
+        auto uninstallBtnSpr =
+            ButtonSprite::create("Uninstall", "bigFont.fnt", "GJ_button_05.png", .6f);
         uninstallBtnSpr->setScale(.6f);
 
         auto uninstallBtn = CCMenuItemSpriteExtra::create(
@@ -375,43 +338,35 @@ bool LocalModInfoPopup::init(Mod* mod, ModListView* 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);
 
             m_updateVersionLabel = CCLabelBMFont::create(
-                ("Available: " + indexItem->info.m_version.toString()).c_str(),
-                "bigFont.fnt"
+                ("Available: " + indexItem->info.m_version.toString()).c_str(), "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().m_issues) {
-        auto issuesBtnSpr = ButtonSprite::create(
-            "Report an Issue", "goldFont.fnt", "GJ_button_04.png", .8f
-        );
+        auto issuesBtnSpr =
+            ButtonSprite::create("Report an Issue", "goldFont.fnt", "GJ_button_04.png", .8f);
         issuesBtnSpr->setScale(.75f);
 
         auto issuesBtn = CCMenuItemSpriteExtra::create(
@@ -438,19 +393,18 @@ 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();
 }
 
 void LocalModInfoPopup::onEnableMod(CCObject* sender) {
-    if (!InternalLoader::get()->shownInfoAlert("mod-disable-vs-unload")) {
+    if (!LoaderImpl::get()->shownInfoAlert("mod-disable-vs-unload")) {
         FLAlertLayer::create(
             "Notice",
             "You may still see some effects of the mod left, and you may "
@@ -464,19 +418,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_list) m_list->updateAllStates(nullptr);
@@ -488,11 +436,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());
 }
 
@@ -501,8 +445,7 @@ void LocalModInfoPopup::onSettings(CCObject*) {
 }
 
 void LocalModInfoPopup::onNoSettings(CCObject*) {
-    FLAlertLayer::create("No Settings Found", "This mod has no customizable settings.", "OK")
-        ->show();
+    FLAlertLayer::create("No Settings Found", "This mod has no customizable settings.", "OK")->show();
 }
 
 void LocalModInfoPopup::onAdvancedSettings(CCObject*) {
@@ -520,14 +463,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_list) m_list->refreshList();
@@ -539,18 +478,19 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
 void LocalModInfoPopup::uninstall() {
     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();
@@ -573,29 +513,25 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListView* 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);
 
-    m_installBtn = CCMenuItemSpriteExtra::create(
-        m_installBtnSpr, this, nullptr
-    );
+    m_installBtn = CCMenuItemSpriteExtra::create(m_installBtnSpr, this, nullptr);
     m_installBtn->setPosition(-143.0f, 75.f);
     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;
 }
 
@@ -607,9 +543,7 @@ ModInfo IndexItemInfoPopup::getModInfo() const {
     return m_item->info;
 }
 
-IndexItemInfoPopup* IndexItemInfoPopup::create(
-    IndexItemHandle item, ModListView* list
-) {
+IndexItemInfoPopup* IndexItemInfoPopup::create(IndexItemHandle item, ModListView* list) {
     auto ret = new IndexItemInfoPopup;
     if (ret && ret->init(item, list)) {
         ret->autorelease();
diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp
index d5e58030..81e5166b 100644
--- a/loader/src/ui/internal/list/ModListCell.cpp
+++ b/loader/src/ui/internal/list/ModListCell.cpp
@@ -1,13 +1,15 @@
 #include "ModListCell.hpp"
-#include "ModListView.hpp"
+
 #include "../info/ModInfoPopup.hpp"
-#include <Geode/binding/StatsCell.hpp>
-#include <Geode/binding/FLAlertLayer.hpp>
+#include "ModListView.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>
 
 template <class T>
 static bool tryOrAlert(Result<T> const& res, char const* title) {
@@ -17,8 +19,8 @@ static bool tryOrAlert(Result<T> const& res, char const* title) {
     return res.isOk();
 }
 
-ModListCell::ModListCell(char const* name, CCSize const& size)
-  : TableViewCell(name, size.width, size.height) {}
+ModListCell::ModListCell(char const* name, CCSize const& size) :
+    TableViewCell(name, size.width, size.height) {}
 
 void ModListCell::draw() {
     reinterpret_cast<StatsCell*>(this)->StatsCell::draw();
@@ -34,16 +36,14 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) {
 
     auto logoSize = m_height / 1.5f;
 
-    auto logoSpr = this->createLogo({ logoSize, logoSize });
-    logoSpr->setPosition({ logoSize / 2 + 12.f, m_height / 2 });
+    auto logoSpr = this->createLogo({logoSize, logoSize});
+    logoSpr->setPosition({logoSize / 2 + 12.f, m_height / 2});
     m_mainLayer->addChild(logoSpr);
 
-    bool hasDesc =
-        m_display == ModListDisplay::Expanded && 
-        info.m_description.has_value();
+    bool hasDesc = m_display == ModListDisplay::Expanded && info.m_description.has_value();
 
     auto titleLabel = CCLabelBMFont::create(info.m_name.c_str(), "bigFont.fnt");
-    titleLabel->setAnchorPoint({ .0f, .5f });
+    titleLabel->setAnchorPoint({.0f, .5f});
     titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
     if (hasDesc && spaceForCategories) {
         titleLabel->setPositionY(m_height / 2 + 20.f);
@@ -58,18 +58,18 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) {
     m_mainLayer->addChild(titleLabel);
 
     auto versionLabel = CCLabelBMFont::create(info.m_version.toString().c_str(), "bigFont.fnt");
-    versionLabel->setAnchorPoint({ .0f, .5f });
+    versionLabel->setAnchorPoint({.0f, .5f});
     versionLabel->setScale(.3f);
     versionLabel->setPosition(
         titleLabel->getPositionX() + titleLabel->getScaledContentSize().width + 5.f,
         titleLabel->getPositionY() - 1.f
     );
-    versionLabel->setColor({ 0, 255, 0 });
+    versionLabel->setColor({0, 255, 0});
     m_mainLayer->addChild(versionLabel);
 
     auto creatorStr = "by " + info.m_developer;
     auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
-    creatorLabel->setAnchorPoint({ .0f, .5f });
+    creatorLabel->setAnchorPoint({.0f, .5f});
     creatorLabel->setScale(.43f);
     creatorLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f);
     if (hasDesc && spaceForCategories) {
@@ -84,11 +84,11 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) {
     m_mainLayer->addChild(creatorLabel);
 
     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 (spaceForCategories) {
             descBG->setPositionY(m_height / 2 - 7.5f);
@@ -100,7 +100,7 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) {
         m_mainLayer->addChild(descBG);
 
         auto descText = CCLabelBMFont::create(info.m_description.value().c_str(), "chatFont.fnt");
-        descText->setAnchorPoint({ .0f, .5f });
+        descText->setAnchorPoint({.0f, .5f});
         descText->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY());
         descText->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f);
         m_mainLayer->addChild(descText);
@@ -123,13 +123,9 @@ bool ModListCell::init(ModListView* list, ModListDisplay display) {
 
 // ModCell
 
-ModCell::ModCell(const char* name, CCSize const& size)
-  : ModListCell(name, size) {}
+ModCell::ModCell(char const* name, CCSize const& size) : ModListCell(name, size) {}
 
-ModCell* ModCell::create(
-    ModListView* list, ModListDisplay display,
-    const char* key, CCSize const& size
-) {
+ModCell* ModCell::create(ModListView* list, ModListDisplay display, char const* key, CCSize const& size) {
     auto ret = new ModCell(key, size);
     if (ret && ret->init(list, display)) {
         return ret;
@@ -139,7 +135,7 @@ ModCell* ModCell::create(
 }
 
 void ModCell::onEnable(CCObject* sender) {
-    if (!InternalLoader::get()->shownInfoAlert("mod-disable-vs-unload")) {
+    if (!LoaderImpl::get()->shownInfoAlert("mod-disable-vs-unload")) {
         FLAlertLayer::create(
             "Notice",
             "<cb>Disabling</c> a <cy>mod</c> removes its hooks & patches and "
@@ -147,7 +143,8 @@ void ModCell::onEnable(CCObject* sender) {
             "still see some effects of the mod left however, and you may "
             "need to <cg>restart</c> the game to have it fully unloaded.",
             "OK"
-        )->show();
+        )
+            ->show();
         m_list->updateAllStates(this);
         return;
     }
@@ -165,10 +162,7 @@ void ModCell::onUnresolvedInfo(CCObject*) {
         "This mod has the following "
         "<cr>unresolved dependencies</c>: ";
     for (auto const& dep : m_mod->getUnresolvedDependencies()) {
-        info += fmt::format(
-            "<cg>{}</c> (<cy>{}</c>), ",
-            dep.m_id, dep.m_version.toString()
-        );
+        info += fmt::format("<cg>{}</c> (<cy>{}</c>), ", dep.m_id, dep.m_version.toString());
     }
     info.pop_back();
     info.pop_back();
@@ -197,20 +191,15 @@ void ModCell::loadFromMod(Mod* mod) {
 
     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);
     }
@@ -218,23 +207,22 @@ void ModCell::loadFromMod(Mod* mod) {
     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();
@@ -246,16 +234,14 @@ CCNode* ModCell::createLogo(CCSize const& size) {
 
 // IndexItemCell
 
-IndexItemCell::IndexItemCell(char const* name, CCSize const& size)
-  : ModListCell(name, size) {}
+IndexItemCell::IndexItemCell(char const* name, CCSize const& size) : ModListCell(name, size) {}
 
 void IndexItemCell::onInfo(CCObject*) {
     IndexItemInfoPopup::create(m_item, m_list)->show();
 }
 
 IndexItemCell* IndexItemCell::create(
-    ModListView* list, ModListDisplay display,
-    const char* key, CCSize const& size
+    ModListView* list, ModListDisplay display, char const* key, CCSize const& size
 ) {
     auto ret = new IndexItemCell(key, size);
     if (ret && ret->init(list, display)) {
@@ -269,15 +255,11 @@ void IndexItemCell::loadFromItem(IndexItemHandle item) {
     m_item = item;
 
     this->setupInfo(item->info, true);
-   
-    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(IndexItemCell::onInfo)
-    );
+    auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
     m_menu->addChild(viewBtn);
 
     // if (hasCategories) {
@@ -298,7 +280,7 @@ void IndexItemCell::loadFromItem(IndexItemHandle item) {
     //         x += node->getScaledContentSize().width + 5.f;
     //     }
     // }
-    
+
     this->updateState();
 }
 
@@ -310,15 +292,11 @@ CCNode* IndexItemCell::createLogo(CCSize const& size) {
 
 // InvalidGeodeFileCell
 
-InvalidGeodeFileCell::InvalidGeodeFileCell(const char* name, CCSize const& size)
-  : ModListCell(name, size) {}
+InvalidGeodeFileCell::InvalidGeodeFileCell(char const* name, CCSize const& size) :
+    ModListCell(name, size) {}
 
 void InvalidGeodeFileCell::onInfo(CCObject*) {
-    FLAlertLayer::create(
-        this, "Error Info",
-        m_info.m_reason,
-        "OK", "Remove file", 360.f
-    )->show();
+    FLAlertLayer::create(this, "Error Info", m_info.m_reason, "OK", "Remove file", 360.f)->show();
 }
 
 void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
@@ -327,13 +305,16 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
             if (ghc::filesystem::remove(m_info.m_path)) {
                 FLAlertLayer::create(
                     "File removed", "Removed <cy>" + m_info.m_path.string() + "</c>", "OK"
-                )->show();
+                )
+                    ->show();
             }
             else {
                 FLAlertLayer::create(
                     "Unable to remove file",
-                    "Unable to remove <cy>" + m_info.m_path.string() + "</c>", "OK"
-                )->show();
+                    "Unable to remove <cy>" + m_info.m_path.string() + "</c>",
+                    "OK"
+                )
+                    ->show();
             }
         }
         catch (std::exception& e) {
@@ -342,7 +323,8 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
                 "Unable to remove <cy>" + m_info.m_path.string() + "</c>: <cr>" +
                     std::string(e.what()) + "</c>",
                 "OK"
-            )->show();
+            )
+                ->show();
         }
         (void)Loader::get()->refreshModsList();
         m_list->refreshList();
@@ -350,8 +332,7 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
 }
 
 InvalidGeodeFileCell* InvalidGeodeFileCell::create(
-    ModListView* list, ModListDisplay display,
-    char const* key, CCSize const& size
+    ModListView* list, ModListDisplay display, char const* key, CCSize const& size
 ) {
     auto ret = new InvalidGeodeFileCell(key, size);
     if (ret && ret->init(list, display)) {
@@ -371,29 +352,23 @@ void InvalidGeodeFileCell::loadFromInfo(InvalidGeodeFile const& info) {
     m_mainLayer->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);
     m_mainLayer->addChild(titleLabel);
 
-    auto pathLabel = CCLabelBMFont::create(
-        m_info.m_path.string().c_str(),
-        "chatFont.fnt"
-    );
-    pathLabel->setAnchorPoint({ .0f, .5f });
+    auto pathLabel = CCLabelBMFont::create(m_info.m_path.string().c_str(), "chatFont.fnt");
+    pathLabel->setAnchorPoint({.0f, .5f});
     pathLabel->setScale(.43f);
     pathLabel->setPosition(m_height / 2, m_height / 2 - 7.f);
-    pathLabel->setColor({ 255, 255, 0 });
+    pathLabel->setColor({255, 255, 0});
     m_mainLayer->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);
 }
 
diff --git a/loader/src/ui/internal/list/ModListView.cpp b/loader/src/ui/internal/list/ModListView.cpp
index 87a115c5..725217b6 100644
--- a/loader/src/ui/internal/list/ModListView.cpp
+++ b/loader/src/ui/internal/list/ModListView.cpp
@@ -1,20 +1,19 @@
 #include "ModListView.hpp"
 
 #include "../info/CategoryNode.hpp"
-#include "ModListLayer.hpp"
 #include "ModListCell.hpp"
+#include "ModListLayer.hpp"
 
 #include <Geode/binding/ButtonSprite.hpp>
-#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
-#include <Geode/binding/TableView.hpp>
-#include <Geode/binding/CCMenuItemToggler.hpp>
 #include <Geode/binding/CCContentLayer.hpp>
+#include <Geode/binding/CCMenuItemSpriteExtra.hpp>
+#include <Geode/binding/CCMenuItemToggler.hpp>
+#include <Geode/binding/TableView.hpp>
+#include <Geode/loader/Index.hpp>
 #include <Geode/loader/Mod.hpp>
 #include <Geode/utils/casts.hpp>
 #include <Geode/utils/cocos.hpp>
 #include <Geode/utils/string.hpp>
-#include <Geode/loader/Index.hpp>
-#include <InternalLoader.hpp>
 
 void ModListView::updateAllStates(ModListCell* toggled) {
     for (auto cell : CCArrayExt<ModListCell>(m_tableView->m_cellArray)) {
@@ -34,10 +33,9 @@ void ModListView::setupList() {
     // fix content layer content size so the
     // list is properly aligned to the top
     auto coverage = calculateChildCoverage(m_tableView->m_contentLayer);
-    m_tableView->m_contentLayer->setContentSize({
-        -coverage.origin.x + coverage.size.width,
-        -coverage.origin.y + coverage.size.height
-    });
+    m_tableView->m_contentLayer->setContentSize(
+        {-coverage.origin.x + coverage.size.width, -coverage.origin.y + coverage.size.height}
+    );
 
     if (m_entries->count() == 1) {
         m_tableView->moveToTopWithOffset(m_itemSeparation * 2);
@@ -51,7 +49,7 @@ void ModListView::setupList() {
 }
 
 TableViewCell* ModListView::getListCell(char const* key) {
-    return ModCell::create(this, m_display, key, { m_width, m_itemSeparation });
+    return ModCell::create(this, m_display, key, {m_width, m_itemSeparation});
 }
 
 void ModListView::loadCell(TableViewCell* cell, unsigned int index) {
@@ -112,7 +110,7 @@ CCArray* ModListView::modsForType(ModListType type) {
                 mods->addObject(new InvalidGeodeFileObject(mod));
             }
             // internal geode representation always at the top
-            auto imod = Loader::getInternalMod();
+            auto imod = Loader::get()->getInternalMod();
             mods->addObject(new ModObject(imod));
 
             // then other mods

From a137fd96374f638b94320f7ac23d7e0d321a306a Mon Sep 17 00:00:00 2001
From: altalk23 <45172705+altalk23@users.noreply.github.com>
Date: Sun, 11 Dec 2022 14:37:57 +0300
Subject: [PATCH 3/4] fix filesystem for windows

---
 loader/src/loader/Dirs.cpp | 38 +++++++++++++++++---------------------
 1 file changed, 17 insertions(+), 21 deletions(-)

diff --git a/loader/src/loader/Dirs.cpp b/loader/src/loader/Dirs.cpp
index 275802fc..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,31 +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)
-        // 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
+#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() {

From d5e0582934fd460b7ba2e815fac1faf35992400f Mon Sep 17 00:00:00 2001
From: altalk23 <45172705+altalk23@users.noreply.github.com>
Date: Mon, 12 Dec 2022 14:42:20 +0300
Subject: [PATCH 4/4] Fix compilation caused from merging

---
 loader/include/Geode/loader/Loader.hpp       |   4 +-
 loader/src/hooks/LoadingLayer.cpp            |   2 +-
 loader/src/loader/Loader.cpp                 |   4 +-
 loader/src/loader/LoaderImpl.cpp             | 184 ++++++++++---------
 loader/src/loader/LoaderImpl.hpp             |   9 +-
 loader/src/ui/internal/info/ModInfoPopup.cpp |   1 -
 loader/src/ui/internal/list/ModListCell.cpp  |   3 +-
 loader/src/ui/internal/list/ModListLayer.cpp |   2 +-
 8 files changed, 107 insertions(+), 102 deletions(-)

diff --git a/loader/include/Geode/loader/Loader.hpp b/loader/include/Geode/loader/Loader.hpp
index c64e72f9..82eab6cd 100644
--- a/loader/include/Geode/loader/Loader.hpp
+++ b/loader/include/Geode/loader/Loader.hpp
@@ -50,8 +50,8 @@ namespace geode {
         bool isModVersionSupported(VersionInfo const& version);
 
         Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
-        Result<> loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
-        Result<> refreshModsList();
+        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;
diff --git a/loader/src/hooks/LoadingLayer.cpp b/loader/src/hooks/LoadingLayer.cpp
index 20eb3adb..62c87b2d 100644
--- a/loader/src/hooks/LoadingLayer.cpp
+++ b/loader/src/hooks/LoadingLayer.cpp
@@ -58,7 +58,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/loader/Loader.cpp b/loader/src/loader/Loader.cpp
index b533eb5c..5d3e783c 100644
--- a/loader/src/loader/Loader.cpp
+++ b/loader/src/loader/Loader.cpp
@@ -57,11 +57,11 @@ Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) {
     return m_impl->loadModFromFile(file);
 }
 
-Result<> Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) {
+void Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) {
     return m_impl->loadModsFromDirectory(dir, recursive);
 }
 
-Result<> Loader::refreshModsList() {
+void Loader::refreshModsList() {
     return m_impl->refreshModsList();
 }
 
diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp
index f6bd4212..e33cbb43 100644
--- a/loader/src/loader/LoaderImpl.cpp
+++ b/loader/src/loader/LoaderImpl.cpp
@@ -32,18 +32,6 @@ Loader::Impl::Impl() {}
 
 Loader::Impl::~Impl() {}
 
-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());
-}
-
 // Initialization
 
 void Loader::Impl::createDirectories() {
@@ -68,20 +56,6 @@ Result<> Loader::Impl::setup() {
         return Ok();
     }
 
-    log::debug("Set up internal mod representation");
-    log::debug("Loading hooks... ");
-
-    if (!this->loadHooks()) {
-        log::error("There were errors loading some hooks, see console for details");
-        return Err("There were errors loading some hooks, see console for details");
-    }
-
-    log::debug("Loaded hooks");
-
-    log::debug("Setting up IPC...");
-
-    this->setupIPC();
-
     log::Logs::setup();
 
     if (crashlog::setupPlatformHandler()) {
@@ -98,7 +72,7 @@ Result<> Loader::Impl::setup() {
     if (!sett) {
         log::warn("Unable to load loader settings: {}", sett.unwrapErr());
     }
-    GEODE_UNWRAP(this->refreshModsList());
+    this->refreshModsList();
 
     this->queueInGDThread([]() {
         Loader::get()->addSearchPaths();
@@ -145,11 +119,11 @@ VersionInfo Loader::Impl::getVersion() {
 }
 
 VersionInfo Loader::Impl::minModVersion() {
-    return VersionInfo{0, 3, 1};
+    return VersionInfo { 0, 3, 1 };
 }
 
 VersionInfo Loader::Impl::maxModVersion() {
-    return VersionInfo{
+    return VersionInfo {
         this->getVersion().getMajor(),
         this->getVersion().getMinor(),
         // todo: dynamic version info (vM.M.*)
@@ -158,7 +132,9 @@ VersionInfo Loader::Impl::maxModVersion() {
 }
 
 bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
-    return version >= this->minModVersion() && version <= this->maxModVersion();
+    return
+        version >= this->minModVersion() &&
+        version <= this->maxModVersion();
 }
 
 // Data saving
@@ -173,7 +149,7 @@ Result<> Loader::Impl::saveData() {
     }
     // save loader data
     GEODE_UNWRAP(InternalMod::get()->saveData());
-
+    
     return Ok();
 }
 
@@ -194,16 +170,16 @@ Result<> Loader::Impl::loadData() {
 // Mod loading
 
 Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
-    if (m_mods.count(info.m_id)) {
-        return Err(fmt::format("Mod with ID '{}' already loaded", info.m_id));
+    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.m_id, mod});
-    mod->m_enabled = InternalMod::get()->getSavedValue<bool>("should-load-" + info.m_id, true);
-    // this loads the mod if its dependencies are resolved
-    mod->updateDependencyStates();
+    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]() {
@@ -213,15 +189,18 @@ Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
         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{
-            .m_path = file,
-            .m_reason = res.unwrapErr(),
+        m_invalidMods.push_back(InvalidGeodeFile {
+            .path = file,
+            .reason = res.unwrapErr(),
         });
         return Err(res.unwrapErr());
     }
@@ -270,7 +249,7 @@ void Loader::Impl::scheduleOnModLoad(Mod* mod, ScheduledFunction func) {
 }
 
 void Loader::Impl::updateModResources(Mod* mod) {
-    if (!mod->m_info.m_spritesheets.size()) {
+    if (!mod->m_info.spritesheets.size()) {
         return;
     }
 
@@ -279,7 +258,7 @@ void Loader::Impl::updateModResources(Mod* mod) {
     log::debug("Adding resources for {}", mod->getID());
 
     // add spritesheets
-    for (auto const& sheet : mod->m_info.m_spritesheets) {
+    for (auto const& sheet : mod->m_info.spritesheets) {
         log::debug("Adding sheet {}", sheet);
         auto png = sheet + ".png";
         auto plist = sheet + ".plist";
@@ -289,8 +268,7 @@ void Loader::Impl::updateModResources(Mod* mod) {
             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.m_id,
-                sheet
+                mod->m_info.id, sheet
             );
         }
         else {
@@ -302,12 +280,15 @@ void Loader::Impl::updateModResources(Mod* mod) {
 
 // Dependencies and refreshing
 
-Result<> Loader::Impl::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) {
+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) {
-            GEODE_UNWRAP(this->loadModsFromDirectory(entry.path(), true));
+            this->loadModsFromDirectory(entry.path(), true);
             continue;
         }
 
@@ -322,24 +303,27 @@ Result<> Loader::Impl::loadModsFromDirectory(ghc::filesystem::path const& dir, b
         }
         // skip this entry if it's already loaded
         if (map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool {
-                return p->m_info.m_path == entry.path();
-            })) {
+            return p->m_info.path == entry.path();
+        })) {
             continue;
         }
 
         // if mods should be loaded immediately, do that
         if (m_earlyLoadFinished) {
-            GEODE_UNWRAP(this->loadModFromFile(entry));
+            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
+        // 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{
-                    .m_path = entry.path(),
-                    .m_reason = res.unwrapErr(),
+                m_invalidMods.push_back(InvalidGeodeFile {
+                    .path = entry.path(),
+                    .reason = res.unwrapErr(),
                 });
                 continue;
             }
@@ -354,21 +338,23 @@ Result<> Loader::Impl::loadModsFromDirectory(ghc::filesystem::path const& dir, b
             m_modsToLoad.push_back(info);
         }
     }
-    return Ok();
 }
 
-Result<> Loader::Impl::refreshModsList() {
+void Loader::Impl::refreshModsList() {
     log::debug("Loading mods...");
 
     // find mods
     for (auto& dir : m_modSearchDirectories) {
-        GEODE_UNWRAP(this->loadModsFromDirectory(dir));
+        this->loadModsFromDirectory(dir);
     }
-
+    
     // load early-load mods first
     for (auto& mod : m_modsToLoad) {
-        if (mod.m_needsEarlyLoad) {
-            GEODE_UNWRAP(this->loadModFromInfo(mod));
+        if (mod.needsEarlyLoad) {
+            auto load = this->loadModFromInfo(mod);
+            if (!load) {
+                log::error("Unable to load {}: {}", mod.id, load.unwrapErr());
+            }
         }
     }
 
@@ -377,18 +363,19 @@ Result<> Loader::Impl::refreshModsList() {
 
     // load the rest of the mods
     for (auto& mod : m_modsToLoad) {
-        if (!mod.m_needsEarlyLoad) {
-            GEODE_UNWRAP(this->loadModFromInfo(mod));
+        if (!mod.needsEarlyLoad) {
+            auto load = this->loadModFromInfo(mod);
+            if (!load) {
+                log::error("Unable to load {}: {}", mod.id, load.unwrapErr());
+            }
         }
     }
     m_modsToLoad.clear();
-
-    return Ok();
 }
 
 void Loader::Impl::updateAllDependencies() {
     for (auto const& [_, mod] : m_mods) {
-        mod->updateDependencyStates();
+        (void)mod->updateDependencies();
     }
 }
 
@@ -400,6 +387,20 @@ 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;
 }
@@ -485,20 +486,23 @@ void Loader::Impl::downloadLoaderResources() {
             auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true);
             if (!unzip) {
                 return ResourceDownloadEvent(
-                           UpdateError("Unable to unzip new resources: " + unzip.unwrapErr())
-                )
-                    .post();
+                    UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr())
+                ).post();
             }
             ResourceDownloadEvent(UpdateFinished()).post();
         })
         .expect([](std::string const& info) {
-            ResourceDownloadEvent(UpdateError("Unable to download resources: " + info)).post();
+            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();
+                UpdateProgress(
+                    static_cast<uint8_t>(now / total * 100.0),
+                    "Downloading resources"
+                )
+            ).post();
         });
 }
 
@@ -512,7 +516,10 @@ bool Loader::Impl::verifyLoaderResources() {
     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))) {
+    if (!(
+        ghc::filesystem::exists(resourcesDir) &&
+        ghc::filesystem::is_directory(resourcesDir)
+    )) {
         this->downloadLoaderResources();
         return false;
     }
@@ -530,7 +537,9 @@ bool Loader::Impl::verifyLoaderResources() {
         // 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));
+            log::debug(
+                "compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name)
+            );
             this->downloadLoaderResources();
             return false;
         }
@@ -546,12 +555,9 @@ bool Loader::Impl::verifyLoaderResources() {
     return true;
 }
 
-std::string Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) {
-    std::string reply;
-
+nlohmann::json Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) {
+    nlohmann::json reply;
     try {
-        std::optional<std::string> replyID = std::nullopt;
-
         // parse received message
         auto json = nlohmann::json::parse(buffer);
         if (!json.contains("mod") || !json["mod"].is_string()) {
@@ -562,29 +568,29 @@ std::string Loader::Impl::processRawIPC(void* rawHandle, std::string const& buff
             log::warn("Received IPC message without 'message' field");
             return reply;
         }
-        if (json.contains("reply") && json["reply"].is_string()) {
-            replyID = json["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 (...) {
+        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) {}
+ResourceDownloadEvent::ResourceDownloadEvent(
+    UpdateStatus const& status
+) : status(status) {}
 
-ListenerResult ResourceDownloadFilter::handle(std::function<Callback> fn, ResourceDownloadEvent* event) {
+ListenerResult ResourceDownloadFilter::handle(
+    std::function<Callback> fn,
+    ResourceDownloadEvent* event
+) {
     fn(event);
     return ListenerResult::Propagate;
 }
 
-ResourceDownloadFilter::ResourceDownloadFilter() {}
+ResourceDownloadFilter::ResourceDownloadFilter() {}
\ No newline at end of file
diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp
index 6643f9ab..c369368e 100644
--- a/loader/src/loader/LoaderImpl.hpp
+++ b/loader/src/loader/LoaderImpl.hpp
@@ -93,8 +93,8 @@ public:
     bool isModVersionSupported(VersionInfo const& version);
 
     Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
-    Result<> loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
-    Result<> refreshModsList();
+    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;
@@ -106,13 +106,12 @@ public:
 
     void updateResources();
 
-    void queueInGDThread(ScheduledFunction func);
     void scheduleOnModLoad(Mod* mod, ScheduledFunction func);
     void waitForModsToBeLoaded();
 
     bool didLastLaunchCrash() const;
 
-    std::string processRawIPC(void* rawHandle, std::string const& buffer);
+    nlohmann::json processRawIPC(void* rawHandle, std::string const& buffer);
 
     /**
      * Check if a one-time event has been shown to the user,
@@ -120,6 +119,8 @@ public:
      * 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);
diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp
index eba0f408..31272273 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>
diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp
index 3c691978..861b812b 100644
--- a/loader/src/ui/internal/list/ModListCell.cpp
+++ b/loader/src/ui/internal/list/ModListCell.cpp
@@ -1,7 +1,6 @@
 #include "ModListCell.hpp"
 #include "ModListLayer.hpp"
 #include "../info/ModInfoPopup.hpp"
-#include "ModListView.hpp"
 
 #include <Geode/binding/ButtonSprite.hpp>
 #include <Geode/binding/CCMenuItemSpriteExtra.hpp>
@@ -9,7 +8,7 @@
 #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>
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 });
             }