diff --git a/loader/include/Geode/Utils.hpp b/loader/include/Geode/Utils.hpp
index 8e49d268..9aadbbee 100644
--- a/loader/include/Geode/Utils.hpp
+++ b/loader/include/Geode/Utils.hpp
@@ -1,17 +1,12 @@
 #pragma once
 
-#include "utils/Ref.hpp"
+#include "DefaultInclude.hpp"
 #include "utils/Result.hpp"
 #include "utils/VersionInfo.hpp"
 #include "utils/casts.hpp"
 #include "utils/cocos.hpp"
-#include "utils/convert.hpp"
-#include "utils/ext.hpp"
+#include "utils/map.hpp"
+#include "utils/string.hpp"
+#include "utils/file.hpp"
 #include "utils/general.hpp"
-#include "utils/json.hpp"
-#include "utils/operators.hpp"
-#include "utils/platform.hpp"
 #include "utils/timer.hpp"
-#include "utils/types.hpp"
-
-#include <Geode/DefaultInclude.hpp>
diff --git a/loader/include/Geode/utils/json.hpp b/loader/include/Geode/external/json/json.hpp
similarity index 100%
rename from loader/include/Geode/utils/json.hpp
rename to loader/include/Geode/external/json/json.hpp
diff --git a/loader/include/Geode/loader/Hook.hpp b/loader/include/Geode/loader/Hook.hpp
index 322c340b..f8c57ee1 100644
--- a/loader/include/Geode/loader/Hook.hpp
+++ b/loader/include/Geode/loader/Hook.hpp
@@ -1,11 +1,11 @@
 #pragma once
 
-#include <Geode/DefaultInclude.hpp>
-#include <Geode/hook-core/Hook.hpp>
-#include <Geode/utils/types.hpp>
+#include "../DefaultInclude.hpp"
+#include "../hook-core/Hook.hpp"
+#include "../utils/general.hpp"
+#include "../external/json/json.hpp"
 #include <inttypes.h>
 #include <string_view>
-#include "../utils/json.hpp"
 
 namespace geode {
     class Mod;
diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp
index 2399853f..d454968f 100644
--- a/loader/include/Geode/loader/Mod.hpp
+++ b/loader/include/Geode/loader/Mod.hpp
@@ -4,11 +4,12 @@
 #include "Setting.hpp"
 #include "Types.hpp"
 
-#include <Geode/DefaultInclude.hpp>
-#include <Geode/utils/Result.hpp>
-#include <Geode/utils/VersionInfo.hpp>
-#include <Geode/utils/json.hpp>
-#include <Geode/utils/types.hpp>
+#include "../DefaultInclude.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 <optional>
 #include <string_view>
 #include <type_traits>
diff --git a/loader/include/Geode/loader/Setting.hpp b/loader/include/Geode/loader/Setting.hpp
index 05fb66d2..498e0cc1 100644
--- a/loader/include/Geode/loader/Setting.hpp
+++ b/loader/include/Geode/loader/Setting.hpp
@@ -1,13 +1,12 @@
 #pragma once
 
-#include <Geode/DefaultInclude.hpp>
-#include <Geode/utils/JsonValidation.hpp>
-#include <Geode/utils/Result.hpp>
-#include <Geode/utils/container.hpp>
-#include <Geode/utils/convert.hpp>
-#include <Geode/utils/json.hpp>
-#include <Geode/utils/platform.hpp>
-#include <Geode/utils/ranges.hpp>
+#include "../DefaultInclude.hpp"
+#include "../utils/JsonValidation.hpp"
+#include "../utils/Result.hpp"
+#include "../external/json/json.hpp"
+#include "../utils/file.hpp"
+#include "../utils/ranges.hpp"
+#include "../utils/cocos.hpp"
 #include <optional>
 #include <regex>
 #include <unordered_set>
diff --git a/loader/include/Geode/ui/Notification.hpp b/loader/include/Geode/ui/Notification.hpp
index b9467ce6..bb9b7d58 100644
--- a/loader/include/Geode/ui/Notification.hpp
+++ b/loader/include/Geode/ui/Notification.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include "../utils/Ref.hpp"
+#include "../utils/cocos.hpp"
 #include "SceneManager.hpp"
 
 #include <chrono>
diff --git a/loader/include/Geode/utils/JsonValidation.hpp b/loader/include/Geode/utils/JsonValidation.hpp
index a5df7ed9..db9c011b 100644
--- a/loader/include/Geode/utils/JsonValidation.hpp
+++ b/loader/include/Geode/utils/JsonValidation.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include "../loader/Log.hpp"
-#include "json.hpp"
+#include "../external/json/json.hpp"
 
 #include <set>
 #include <variant>
diff --git a/loader/include/Geode/utils/Ref.hpp b/loader/include/Geode/utils/Ref.hpp
deleted file mode 100644
index f8d01cf7..00000000
--- a/loader/include/Geode/utils/Ref.hpp
+++ /dev/null
@@ -1,132 +0,0 @@
-#pragma once
-
-#include <cocos2d.h>
-
-namespace geode {
-    /**
-     * A smart pointer to a managed CCObject-deriving class. Retains shared
-     * ownership over the managed instance. Releases the object when the Ref
-     * is destroyed, or assigned another object or nullptr.
-     *
-     * Use-cases include, for example, non-CCNode class members, or nodes that
-     * are not always in the scene tree.
-     *
-     * @example class MyNode : public CCNode {
-     * protected:
-     *      // no need to manually call retain or
-     *      // release on this array; Ref manages it
-     *      // for you :3
-     *      Ref<CCArray> m_list = CCArray::create();
-     * };
-     */
-    template <class T>
-    class Ref final {
-        static_assert(
-            std::is_base_of_v<cocos2d::CCObject, T>,
-            "Ref can only be used with a CCObject-inheriting class!"
-        );
-
-        T* m_obj = nullptr;
-
-    public:
-        /**
-         * Construct a Ref of an object. The object will be retained and
-         * managed until Ref goes out of scope
-         */
-        Ref(T* obj) : m_obj(obj) {
-            CC_SAFE_RETAIN(obj);
-        }
-
-        Ref(Ref<T> const& other) : Ref(other.data()) {}
-
-        Ref(Ref<T>&& other) : m_obj(other.m_obj) {
-            other.m_obj = nullptr;
-        }
-
-        /**
-         * Construct an empty Ref (the managed object will be null)
-         */
-        Ref() = default;
-
-        ~Ref() {
-            CC_SAFE_RELEASE(m_obj);
-        }
-
-        /**
-         * Swap the managed object with another object. The managed object
-         * will be released, and the new object retained
-         * @param other The new object to swap to
-         */
-        void swap(T* other) {
-            CC_SAFE_RELEASE(m_obj);
-            m_obj = other;
-            CC_SAFE_RETAIN(other);
-        }
-
-        /**
-         * Return the managed object
-         * @returns The managed object
-         */
-        T* data() const {
-            return m_obj;
-        }
-
-        operator T*() const {
-            return m_obj;
-        }
-
-        T* operator*() const {
-            return m_obj;
-        }
-
-        T* operator->() const {
-            return m_obj;
-        }
-
-        T* operator=(T* obj) {
-            this->swap(obj);
-            return obj;
-        }
-
-        Ref<T>& operator=(Ref<T> const& other) {
-            this->swap(other.data());
-            return *this;
-        }
-
-        Ref<T>& operator=(Ref<T>&& other) {
-            this->swap(other.data());
-            return *this;
-        }
-
-        bool operator==(T* other) const {
-            return m_obj == other;
-        }
-        bool operator==(Ref<T> const& other) const {
-            return m_obj == other.m_obj;
-        }
-
-        bool operator!=(T* other) const {
-            return m_obj != other;
-        }
-        bool operator!=(Ref<T> const& other) const {
-            return m_obj != other.m_obj;
-        }
-
-        // for containers
-        bool operator<(Ref<T> const& other) const {
-            return m_obj < other.m_obj;
-        }
-        bool operator>(Ref<T> const& other) const {
-            return m_obj > other.m_obj;
-        }
-    };
-}
-
-namespace std {
-    template<class T>
-    struct hash<geode::Ref<T>> {
-        size_t operator()(geode::Ref<T> const& ref) const {
-            return std::hash<T*>()(ref.data());
-        }
-    };
-}
diff --git a/loader/include/Geode/utils/VersionInfo.hpp b/loader/include/Geode/utils/VersionInfo.hpp
index 22119e4d..45d21699 100644
--- a/loader/include/Geode/utils/VersionInfo.hpp
+++ b/loader/include/Geode/utils/VersionInfo.hpp
@@ -1,8 +1,8 @@
 #pragma once
 
-#include <Geode/DefaultInclude.hpp>
+#include "../DefaultInclude.hpp"
 #include <string_view>
-#include "json.hpp"
+#include "../external/json/json.hpp"
 
 namespace geode {
     /**
diff --git a/loader/include/Geode/utils/WackyGeodeMacros.hpp b/loader/include/Geode/utils/WackyGeodeMacros.hpp
deleted file mode 100644
index 76d4fcd3..00000000
--- a/loader/include/Geode/utils/WackyGeodeMacros.hpp
+++ /dev/null
@@ -1,14 +0,0 @@
-#pragma once
-
-#define CCARRAY_FOREACH_B_BASE(__array__, __obj__, __type__, __index__)                      \
-    if ((__array__) && (__array__)->count())                                                 \
-        for (auto [__index__, __obj__] = std::tuple<unsigned int, __type__> { 0u, nullptr }; \
-             (__index__ < (__array__)->count() &&                                            \
-              (__obj__ = static_cast<__type__>((__array__)->objectAtIndex(__index__))));     \
-             __index__++)
-
-#define CCARRAY_FOREACH_B_TYPE(__array__, __obj__, __type__) \
-    CCARRAY_FOREACH_B_BASE(__array__, __obj__, __type__*, ccArray_forEach_b_base_index)
-
-#define CCARRAY_FOREACH_B(__array__, __obj__) \
-    CCARRAY_FOREACH_B_BASE(__array__, __obj__, cocos2d::CCObject*, ccArray_forEach_b_index)
diff --git a/loader/include/Geode/utils/cocos.hpp b/loader/include/Geode/utils/cocos.hpp
index aa07d817..acbc9603 100644
--- a/loader/include/Geode/utils/cocos.hpp
+++ b/loader/include/Geode/utils/cocos.hpp
@@ -1,13 +1,310 @@
 #pragma once
 
-#include "Ref.hpp"
 #include "casts.hpp"
-
+#include "../external/json/json.hpp"
+#include "general.hpp"
 #include <Geode/DefaultInclude.hpp>
 #include <cocos2d.h>
 #include <functional>
 #include <type_traits>
 
+// support converting ccColor3B / ccColor4B to / from json
+namespace cocos2d {
+    void GEODE_DLL to_json(nlohmann::json& json, cocos2d::ccColor3B const& color);
+    void GEODE_DLL from_json(nlohmann::json const& json, cocos2d::ccColor3B& color);
+    void GEODE_DLL to_json(nlohmann::json& json, cocos2d::ccColor4B const& color);
+    void GEODE_DLL from_json(nlohmann::json const& json, cocos2d::ccColor4B& color);
+}
+
+// operators for CC geometry
+namespace geode {
+    static cocos2d::CCPoint& operator*=(cocos2d::CCPoint& pos, float mul) {
+        pos.x *= mul;
+        pos.y *= mul;
+        return pos;
+    }
+
+    static cocos2d::CCSize& operator*=(cocos2d::CCSize& size, float mul) {
+        size.width *= mul;
+        size.height *= mul;
+        return size;
+    }
+
+    static cocos2d::CCSize operator*(cocos2d::CCSize const& size, cocos2d::CCPoint const& point) {
+        return {
+            size.width * point.x,
+            size.height * point.y,
+        };
+    }
+
+    static cocos2d::CCRect operator*=(cocos2d::CCRect& rect, float mul) {
+        rect.origin *= mul;
+        rect.size *= mul;
+        return rect;
+    }
+
+    static cocos2d::CCRect operator*(cocos2d::CCRect const& rect, float mul) {
+        return {
+            rect.origin.x * mul,
+            rect.origin.y * mul,
+            rect.size.width * mul,
+            rect.size.height * mul,
+        };
+    }
+
+    static cocos2d::CCPoint operator/=(cocos2d::CCPoint& pos, float div) {
+        pos.x /= div;
+        pos.y /= div;
+        return pos;
+    }
+
+    static cocos2d::CCSize operator/=(cocos2d::CCSize& size, float div) {
+        size.width /= div;
+        size.height /= div;
+        return size;
+    }
+
+    static cocos2d::CCRect operator/=(cocos2d::CCRect& rect, float div) {
+        rect.origin /= div;
+        rect.size /= div;
+        return rect;
+    }
+
+    static cocos2d::CCPoint operator+=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) {
+        pos.x += add.x;
+        pos.y += add.y;
+        return pos;
+    }
+
+    static cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) {
+        size.width += add.x;
+        size.height += add.y;
+        return size;
+    }
+
+    static cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCSize const& add) {
+        size.width += add.width;
+        size.height += add.height;
+        return size;
+    }
+
+    static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) {
+        rect.origin += add;
+        return rect;
+    }
+
+    static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) {
+        rect.size += add;
+        return rect;
+    }
+
+    static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) {
+        rect.origin += add.origin;
+        rect.size += add.size;
+        return rect;
+    }
+
+    static cocos2d::CCPoint operator-=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) {
+        pos.x -= add.x;
+        pos.y -= add.y;
+        return pos;
+    }
+
+    static cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) {
+        size.width -= add.x;
+        size.height -= add.y;
+        return size;
+    }
+
+    static cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCSize const& add) {
+        size.width -= add.width;
+        size.height -= add.height;
+        return size;
+    }
+
+    static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) {
+        rect.origin -= add;
+        return rect;
+    }
+
+    static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) {
+        rect.size -= add;
+        return rect;
+    }
+
+    static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) {
+        rect.origin -= add.origin;
+        rect.size -= add.size;
+        return rect;
+    }
+
+    static cocos2d::CCSize operator-(cocos2d::CCSize const& size, float f) {
+        return { size.width - f, size.height - f };
+    }
+
+    static cocos2d::CCSize operator-(cocos2d::CCSize const& size) {
+        return { -size.width, -size.height };
+    }
+
+    static bool operator==(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) {
+        return p1.x == p2.x && p1.y == p2.y;
+    }
+
+    static bool operator!=(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) {
+        return p1.x != p2.x || p1.y != p2.y;
+    }
+
+    static bool operator==(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
+        return s1.width == s2.width && s1.height == s2.height;
+    }
+
+    static bool operator!=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
+        return s1.width != s2.width || s1.height != s2.height;
+    }
+
+    static bool operator==(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) {
+        return r1.origin == r2.origin && r1.size == r2.size;
+    }
+
+    static bool operator!=(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) {
+        return r1.origin != r2.origin || r1.size != r2.size;
+    }
+
+    static bool operator==(cocos2d::ccColor4B const& c1, cocos2d::ccColor4B const& c2) {
+        return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b && c1.a == c2.a;
+    }
+
+    static bool operator!=(cocos2d::ccColor4B const& c1, cocos2d::ccColor4B const& c2) {
+        return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b || c1.a != c2.a;
+    }
+
+    static bool operator!=(cocos2d::ccColor3B const& c1, cocos2d::ccColor3B const& c2) {
+        return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b;
+    }
+}
+
+// Ref
+namespace geode {
+    /**
+     * A smart pointer to a managed CCObject-deriving class. Retains shared
+     * ownership over the managed instance. Releases the object when the Ref
+     * is destroyed, or assigned another object or nullptr.
+     *
+     * Use-cases include, for example, non-CCNode class members, or nodes that
+     * are not always in the scene tree.
+     *
+     * @example class MyNode : public CCNode {
+     * protected:
+     *      // no need to manually call retain or
+     *      // release on this array; Ref manages it
+     *      // for you :3
+     *      Ref<CCArray> m_list = CCArray::create();
+     * };
+     */
+    template <class T>
+    class Ref final {
+        static_assert(
+            std::is_base_of_v<cocos2d::CCObject, T>,
+            "Ref can only be used with a CCObject-inheriting class!"
+        );
+
+        T* m_obj = nullptr;
+
+    public:
+        /**
+         * Construct a Ref of an object. The object will be retained and
+         * managed until Ref goes out of scope
+         */
+        Ref(T* obj) : m_obj(obj) {
+            CC_SAFE_RETAIN(obj);
+        }
+
+        Ref(Ref<T> const& other) : Ref(other.data()) {}
+
+        Ref(Ref<T>&& other) : m_obj(other.m_obj) {
+            other.m_obj = nullptr;
+        }
+
+        /**
+         * Construct an empty Ref (the managed object will be null)
+         */
+        Ref() = default;
+
+        ~Ref() {
+            CC_SAFE_RELEASE(m_obj);
+        }
+
+        /**
+         * Swap the managed object with another object. The managed object
+         * will be released, and the new object retained
+         * @param other The new object to swap to
+         */
+        void swap(T* other) {
+            CC_SAFE_RELEASE(m_obj);
+            m_obj = other;
+            CC_SAFE_RETAIN(other);
+        }
+
+        /**
+         * Return the managed object
+         * @returns The managed object
+         */
+        T* data() const {
+            return m_obj;
+        }
+
+        operator T*() const {
+            return m_obj;
+        }
+
+        T* operator*() const {
+            return m_obj;
+        }
+
+        T* operator->() const {
+            return m_obj;
+        }
+
+        T* operator=(T* obj) {
+            this->swap(obj);
+            return obj;
+        }
+
+        Ref<T>& operator=(Ref<T> const& other) {
+            this->swap(other.data());
+            return *this;
+        }
+
+        Ref<T>& operator=(Ref<T>&& other) {
+            this->swap(other.data());
+            return *this;
+        }
+
+        bool operator==(T* other) const {
+            return m_obj == other;
+        }
+        bool operator==(Ref<T> const& other) const {
+            return m_obj == other.m_obj;
+        }
+
+        bool operator!=(T* other) const {
+            return m_obj != other;
+        }
+        bool operator!=(Ref<T> const& other) const {
+            return m_obj != other.m_obj;
+        }
+
+        // for containers
+        bool operator<(Ref<T> const& other) const {
+            return m_obj < other.m_obj;
+        }
+        bool operator>(Ref<T> const& other) const {
+            return m_obj > other.m_obj;
+        }
+    };
+}
+
+// Cocos2d utils
 namespace geode::cocos {
     /**
      * Get child at index. Checks bounds. A negative
@@ -300,9 +597,141 @@ namespace geode::cocos {
         }
     };
 
+    inline void ccDrawColor4B(cocos2d::ccColor4B const& color) {
+        cocos2d::ccDrawColor4B(color.r, color.g, color.b, color.a);
+    }
+
+    inline cocos2d::ccColor4B invert4B(cocos2d::ccColor4B const& color) {
+        return { static_cast<GLubyte>(255 - color.r), static_cast<GLubyte>(255 - color.g),
+                 static_cast<GLubyte>(255 - color.b), color.a };
+    }
+
+    inline cocos2d::ccColor3B invert3B(cocos2d::ccColor3B const& color) {
+        return { static_cast<GLubyte>(255 - color.r), static_cast<GLubyte>(255 - color.g),
+                 static_cast<GLubyte>(255 - color.b) };
+    }
+
+    inline cocos2d::ccColor3B lighten3B(cocos2d::ccColor3B const& color, int amount) {
+        return {
+            static_cast<GLubyte>(utils::clamp(color.r + amount, 0, 255)),
+            static_cast<GLubyte>(utils::clamp(color.g + amount, 0, 255)),
+            static_cast<GLubyte>(utils::clamp(color.b + amount, 0, 255)),
+        };
+    }
+
+    inline cocos2d::ccColor3B darken3B(cocos2d::ccColor3B const& color, int amount) {
+        return lighten3B(color, -amount);
+    }
+
+    inline cocos2d::ccColor3B to3B(cocos2d::ccColor4B const& color) {
+        return { color.r, color.g, color.b };
+    }
+
+    inline cocos2d::ccColor4B to4B(cocos2d::ccColor3B const& color, GLubyte alpha = 255) {
+        return { color.r, color.g, color.b, alpha };
+    }
+
+    inline cocos2d::ccColor4F to4F(cocos2d::ccColor4B const& color) {
+        return { color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f };
+    }
+
+    constexpr cocos2d::ccColor3B cc3x(int hexValue) {
+        if (hexValue <= 0xf)
+            return cocos2d::ccColor3B { static_cast<GLubyte>(hexValue * 17),
+                                        static_cast<GLubyte>(hexValue * 17),
+                                        static_cast<GLubyte>(hexValue * 17) };
+        if (hexValue <= 0xff)
+            return cocos2d::ccColor3B { static_cast<GLubyte>(hexValue),
+                                        static_cast<GLubyte>(hexValue),
+                                        static_cast<GLubyte>(hexValue) };
+        if (hexValue <= 0xfff)
+            return cocos2d::ccColor3B { static_cast<GLubyte>((hexValue >> 8 & 0xf) * 17),
+                                        static_cast<GLubyte>((hexValue >> 4 & 0xf) * 17),
+                                        static_cast<GLubyte>((hexValue >> 0 & 0xf) * 17) };
+        else
+            return cocos2d::ccColor3B { static_cast<GLubyte>(hexValue >> 16 & 0xff),
+                                        static_cast<GLubyte>(hexValue >> 8 & 0xff),
+                                        static_cast<GLubyte>(hexValue >> 0 & 0xff) };
+    }
+
+    GEODE_DLL Result<cocos2d::ccColor3B> cc3bFromHexString(std::string const& hexValue);
+    GEODE_DLL Result<cocos2d::ccColor4B> cc4bFromHexString(std::string const& hexValue);
+    GEODE_DLL std::string cc3bToHexString(cocos2d::ccColor3B const& color);
+    GEODE_DLL std::string cc4bToHexString(cocos2d::ccColor4B const& color);
+
+    template <typename T, typename = std::enable_if_t<std::is_pointer_v<T>>>
+    static cocos2d::CCArray* vectorToCCArray(std::vector<T> const& vec) {
+        auto res = cocos2d::CCArray::createWithCapacity(vec.size());
+        for (auto const& item : vec)
+            res->addObject(item);
+        return res;
+    }
+
+    template <typename T, typename C, typename = std::enable_if_t<std::is_pointer_v<C>>>
+    static cocos2d::CCArray* vectorToCCArray(
+        std::vector<T> const& vec, std::function<C(T)> convFunc
+    ) {
+        auto res = cocos2d::CCArray::createWithCapacity(vec.size());
+        for (auto const& item : vec)
+            res->addObject(convFunc(item));
+        return res;
+    }
+
+    template <typename T, typename = std::enable_if_t<std::is_pointer_v<T>>>
+    std::vector<T> ccArrayToVector(cocos2d::CCArray* arr) {
+        return std::vector<T>(
+            reinterpret_cast<T*>(arr->data->arr),
+            reinterpret_cast<T*>(arr->data->arr) + arr->data->num
+        );
+    }
+
+    template <
+        typename K, typename V,
+        typename = std::enable_if_t<std::is_same_v<K, std::string> || std::is_same_v<K, intptr_t>>>
+    static cocos2d::CCDictionary* mapToCCDict(std::map<K, V> const& map) {
+        auto res = cocos2d::CCDictionary::create();
+        for (auto const& [key, value] : map)
+            res->setObject(value, key);
+        return res;
+    }
+
+    template <
+        typename K, typename V, typename C,
+        typename = std::enable_if_t<std::is_same_v<C, std::string> || std::is_same_v<C, intptr_t>>>
+    static cocos2d::CCDictionary* mapToCCDict(
+        std::map<K, V> const& map, std::function<C(K)> convFunc
+    ) {
+        auto res = cocos2d::CCDictionary::create();
+        for (auto const& [key, value] : map)
+            res->setObject(value, convFunc(key));
+        return res;
+    }
+
+    //   template<typename K, typename V,
+    // typename = std::enable_if_t<std::is_same_v<K, std::string> || std::is_same_v<K, intptr_t>> >
+    //   static std::map<K, V> ccDictToMap(cocos2d::CCDictionary* dict) {
+    //       auto res = std::map<K, V>();
+    //       cocos2d::CCDictElement* element = nullptr;
+    //       CCDICT_FOREACH(dict, element) {
+    //       	if constexpr (std::is_same_v<K, std::string>)
+    //       		res[element->getStrKey()] = element->getObject();
+    //       	if constexpr (std::is_same_v<K, intptr_t>)
+    //       		res[element->getIntKey()] = element->getObject();
+    //       }
+    //       return res;
+    //   }
 }
 
+// std specializations
 namespace std {
+    // enables using Ref as the key in unordered_map etc.
+    template<class T>
+    struct hash<geode::Ref<T>> {
+        size_t operator()(geode::Ref<T> const& ref) const {
+            return std::hash<T*>()(ref.data());
+        }
+    };
+
     template <typename T>
     struct iterator_traits<geode::cocos::CCArrayIterator<T>> {
         using difference_type = ptrdiff_t;
@@ -314,8 +743,8 @@ namespace std {
     };
 }
 
+// more utils
 namespace geode::cocos {
-
     struct GEODE_DLL CCArrayInserter {
     public:
         CCArrayInserter(cocos2d::CCArray* p) : m_array(p) {}
diff --git a/loader/include/Geode/utils/conststring.hpp b/loader/include/Geode/utils/conststring.hpp
deleted file mode 100644
index 169a759c..00000000
--- a/loader/include/Geode/utils/conststring.hpp
+++ /dev/null
@@ -1,57 +0,0 @@
-#pragma once
-
-#include <array>
-#include <string_view>
-
-using namespace std::string_view_literals;
-
-namespace geode::utils {
-    static constexpr const std::string_view sv_null = "\0";
-    static constexpr const std::string_view sv_none = "";
-    static constexpr const std::string_view sv_path = "/";
-
-    // straight off
-    // https://stackoverflow.com/questions/38955940/how-to-concatenate-static-strings-at-compile-time
-    template <std::string_view const& Separator, std::string_view const&... Strs>
-    struct ConstStringJoin {
-        static constexpr auto impl() noexcept {
-            constexpr std::size_t len =
-                (Strs.size() + ... + 0) + (sizeof...(Strs) - 1) * Separator.size();
-            std::array<char, len + 1> arr {};
-            auto append = [len, i = 0, &arr](auto const& s) mutable {
-                for (auto c : s)
-                    arr[i++] = c;
-                if (i < static_cast<int>(len - 1)) {
-                    for (auto c : Separator)
-                        arr[i++] = c;
-                }
-            };
-            (append(Strs), ...);
-            arr[len] = 0;
-            return arr;
-        }
-
-        static constexpr auto arr = impl();
-        static constexpr std::string_view value { arr.data(), arr.size() - 1 };
-    };
-
-    template <std::string_view const& Separator, std::string_view const&... Strs>
-    static constexpr auto const_join_sep = ConstStringJoin<Separator, Strs...>::value;
-
-    template <std::string_view const&... Strs>
-    static constexpr auto const_join = ConstStringJoin<sv_none, Strs...>::value;
-
-    template <std::string_view const&... Strs>
-    static constexpr auto const_join_path = ConstStringJoin<sv_path, Strs...>::value;
-
-    template <std::string_view const& Separator, std::string_view const&... Strs>
-    static constexpr char const* const_join_sep_c_str =
-        ConstStringJoin<Separator, Strs...>::value.data();
-
-    template <std::string_view const&... Strs>
-    static constexpr char const* const_join_c_str = ConstStringJoin<sv_none, Strs...>::value.data();
-
-    template <std::string_view const&... Strs>
-    static constexpr char const* const_join_path_c_str =
-        ConstStringJoin<sv_path, Strs...>::value.data();
-}
diff --git a/loader/include/Geode/utils/container.hpp b/loader/include/Geode/utils/container.hpp
deleted file mode 100644
index c60e53c6..00000000
--- a/loader/include/Geode/utils/container.hpp
+++ /dev/null
@@ -1,84 +0,0 @@
-#pragma once
-
-#include <Geode/DefaultInclude.hpp>
-#include <algorithm>
-#include <string>
-
-namespace geode::utils::container {
-    /**
-     * Check if a container contains an element by value.
-     * @param vec The vector to check.
-     * @param elem The element to find.
-     * @returns True if element is in `vec`, false if not.
-     */
-    template <class C, class T>
-    [[deprecated("Use geode::utils::ranges::contains instead")]] bool contains(
-        C const& cont, T const& elem
-    ) {
-        return std::find(cont.begin(), cont.end(), elem) != cont.end();
-    }
-
-    /**
-     * Check if a vector contains an element via a function.
-     * @param vec The vector to check.
-     * @param containFunc A function that returns bool if the
-     * element parameter is what is looked for.
-     * @returns True if an element matching `containFunc` is
-     * in `vec`, false if not.
-     */
-    template <class C, class T>
-    [[deprecated("Use geode::utils::ranges::contains instead")]] bool contains(
-        C const& cont, std::function<bool(T)> containFunc
-    ) {
-        for (auto const& item : cont) {
-            if (containFunc(item)) return true;
-        }
-        return false;
-    }
-
-    /**
-     * Turn a vector into a string. T must be either a string,
-     * or convertible via `std::to_string`.
-     * @param vec The vector to add to.
-     * @param sep Separator string.
-     * @returns Joined string.
-     */
-    template <class C>
-    [[deprecated("Use geode::utils::ranges::join instead")]] std::string join(
-        C const& cont, std::string const& sep
-    ) {
-        std::string res = "";
-        bool first = true;
-        for (auto p : cont) {
-            if (!first) {
-                res += sep;
-            }
-            else {
-                first = false;
-            }
-            if constexpr (std::is_same_v<decltype(p), std::string>) {
-                res += p;
-            }
-            else {
-                res += std::to_string(p);
-            }
-        }
-        return res;
-    }
-
-    /**
-     * Map a container of items type T to a new container of items
-     * type T2.
-     * @param vec The container to map.
-     * @param mapFunc Function that converts an element from T to T2.
-     * @returns Mapped container.
-     */
-    template <class C, class C2, class T, class T2>
-    [[deprecated("Use geode::utils::ranges::map instead")]] C2 map(
-        C const& cont, std::function<T2(T)> mapFunc
-    ) {
-        C2 res;
-        std::transform(cont.begin(), cont.end(), res.end(), mapFunc);
-        return res;
-    }
-}
diff --git a/loader/include/Geode/utils/convert.hpp b/loader/include/Geode/utils/convert.hpp
deleted file mode 100644
index f95f06ed..00000000
--- a/loader/include/Geode/utils/convert.hpp
+++ /dev/null
@@ -1,140 +0,0 @@
-#pragma once
-
-#include "general.hpp"
-#include "json.hpp"
-
-#include <cocos2d.h>
-
-// support converting ccColor3B / ccColor4B to / from json
-namespace cocos2d {
-    void GEODE_DLL to_json(nlohmann::json& json, cocos2d::ccColor3B const& color);
-    void GEODE_DLL from_json(nlohmann::json const& json, cocos2d::ccColor3B& color);
-    void GEODE_DLL to_json(nlohmann::json& json, cocos2d::ccColor4B const& color);
-    void GEODE_DLL from_json(nlohmann::json const& json, cocos2d::ccColor4B& color);
-}
-
-namespace geode::cocos {
-    inline void ccDrawColor4B(cocos2d::ccColor4B const& color) {
-        cocos2d::ccDrawColor4B(color.r, color.g, color.b, color.a);
-    }
-
-    inline cocos2d::ccColor4B invert4B(cocos2d::ccColor4B const& color) {
-        return { static_cast<GLubyte>(255 - color.r), static_cast<GLubyte>(255 - color.g),
-                 static_cast<GLubyte>(255 - color.b), color.a };
-    }
-
-    inline cocos2d::ccColor3B invert3B(cocos2d::ccColor3B const& color) {
-        return { static_cast<GLubyte>(255 - color.r), static_cast<GLubyte>(255 - color.g),
-                 static_cast<GLubyte>(255 - color.b) };
-    }
-
-    inline cocos2d::ccColor3B lighten3B(cocos2d::ccColor3B const& color, int amount) {
-        return {
-            static_cast<GLubyte>(utils::clamp(color.r + amount, 0, 255)),
-            static_cast<GLubyte>(utils::clamp(color.g + amount, 0, 255)),
-            static_cast<GLubyte>(utils::clamp(color.b + amount, 0, 255)),
-        };
-    }
-
-    inline cocos2d::ccColor3B darken3B(cocos2d::ccColor3B const& color, int amount) {
-        return lighten3B(color, -amount);
-    }
-
-    inline cocos2d::ccColor3B to3B(cocos2d::ccColor4B const& color) {
-        return { color.r, color.g, color.b };
-    }
-
-    inline cocos2d::ccColor4B to4B(cocos2d::ccColor3B const& color, GLubyte alpha = 255) {
-        return { color.r, color.g, color.b, alpha };
-    }
-
-    inline cocos2d::ccColor4F to4F(cocos2d::ccColor4B const& color) {
-        return { color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f };
-    }
-
-    constexpr cocos2d::ccColor3B cc3x(int hexValue) {
-        if (hexValue <= 0xf)
-            return cocos2d::ccColor3B { static_cast<GLubyte>(hexValue * 17),
-                                        static_cast<GLubyte>(hexValue * 17),
-                                        static_cast<GLubyte>(hexValue * 17) };
-        if (hexValue <= 0xff)
-            return cocos2d::ccColor3B { static_cast<GLubyte>(hexValue),
-                                        static_cast<GLubyte>(hexValue),
-                                        static_cast<GLubyte>(hexValue) };
-        if (hexValue <= 0xfff)
-            return cocos2d::ccColor3B { static_cast<GLubyte>((hexValue >> 8 & 0xf) * 17),
-                                        static_cast<GLubyte>((hexValue >> 4 & 0xf) * 17),
-                                        static_cast<GLubyte>((hexValue >> 0 & 0xf) * 17) };
-        else
-            return cocos2d::ccColor3B { static_cast<GLubyte>(hexValue >> 16 & 0xff),
-                                        static_cast<GLubyte>(hexValue >> 8 & 0xff),
-                                        static_cast<GLubyte>(hexValue >> 0 & 0xff) };
-    }
-
-    GEODE_DLL Result<cocos2d::ccColor3B> cc3bFromHexString(std::string const& hexValue);
-    GEODE_DLL Result<cocos2d::ccColor4B> cc4bFromHexString(std::string const& hexValue);
-    GEODE_DLL std::string cc3bToHexString(cocos2d::ccColor3B const& color);
-    GEODE_DLL std::string cc4bToHexString(cocos2d::ccColor4B const& color);
-
-    template <typename T, typename = std::enable_if_t<std::is_pointer_v<T>>>
-    static cocos2d::CCArray* vectorToCCArray(std::vector<T> const& vec) {
-        auto res = cocos2d::CCArray::createWithCapacity(vec.size());
-        for (auto const& item : vec)
-            res->addObject(item);
-        return res;
-    }
-
-    template <typename T, typename C, typename = std::enable_if_t<std::is_pointer_v<C>>>
-    static cocos2d::CCArray* vectorToCCArray(
-        std::vector<T> const& vec, std::function<C(T)> convFunc
-    ) {
-        auto res = cocos2d::CCArray::createWithCapacity(vec.size());
-        for (auto const& item : vec)
-            res->addObject(convFunc(item));
-        return res;
-    }
-
-    template <typename T, typename = std::enable_if_t<std::is_pointer_v<T>>>
-    std::vector<T> ccArrayToVector(cocos2d::CCArray* arr) {
-        return std::vector<T>(
-            reinterpret_cast<T*>(arr->data->arr),
-            reinterpret_cast<T*>(arr->data->arr) + arr->data->num
-        );
-    }
-
-    template <
-        typename K, typename V,
-        typename = std::enable_if_t<std::is_same_v<K, std::string> || std::is_same_v<K, intptr_t>>>
-    static cocos2d::CCDictionary* mapToCCDict(std::map<K, V> const& map) {
-        auto res = cocos2d::CCDictionary::create();
-        for (auto const& [key, value] : map)
-            res->setObject(value, key);
-        return res;
-    }
-
-    template <
-        typename K, typename V, typename C,
-        typename = std::enable_if_t<std::is_same_v<C, std::string> || std::is_same_v<C, intptr_t>>>
-    static cocos2d::CCDictionary* mapToCCDict(
-        std::map<K, V> const& map, std::function<C(K)> convFunc
-    ) {
-        auto res = cocos2d::CCDictionary::create();
-        for (auto const& [key, value] : map)
-            res->setObject(value, convFunc(key));
-        return res;
-    }
-
-    //   template<typename K, typename V,
-    // typename = std::enable_if_t<std::is_same_v<K, std::string> || std::is_same_v<K, intptr_t>> >
-    //   static std::map<K, V> ccDictToMap(cocos2d::CCDictionary* dict) {
-    //       auto res = std::map<K, V>();
-    //       cocos2d::CCDictElement* element = nullptr;
-    //       CCDICT_FOREACH(dict, element) {
-    //       	if constexpr (std::is_same_v<K, std::string>)
-    //       		res[element->getStrKey()] = element->getObject();
-    //       	if constexpr (std::is_same_v<K, intptr_t>)
-    //       		res[element->getIntKey()] = element->getObject();
-    //       }
-    //       return res;
-    //   }
-}
diff --git a/loader/include/Geode/utils/ext.hpp b/loader/include/Geode/utils/ext.hpp
deleted file mode 100644
index 2c9273da..00000000
--- a/loader/include/Geode/utils/ext.hpp
+++ /dev/null
@@ -1,8 +0,0 @@
-#pragma once
-
-#include "conststring.hpp"
-#include "container.hpp"
-#include "file.hpp"
-#include "map.hpp"
-#include "string.hpp"
-#include "vector.hpp"
diff --git a/loader/include/Geode/utils/file.hpp b/loader/include/Geode/utils/file.hpp
index fd4b24b3..43c0d896 100644
--- a/loader/include/Geode/utils/file.hpp
+++ b/loader/include/Geode/utils/file.hpp
@@ -1,48 +1,21 @@
 #pragma once
 
 #include "Result.hpp"
-#include "types.hpp"
+#include "general.hpp"
 
 #include <Geode/DefaultInclude.hpp>
 #include <fs/filesystem.hpp>
 #include <string>
+#include <unordered_set>
 
 namespace geode::utils::file {
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<std::string>
-    readString(std::string const& path);
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<std::string>
-    readString(std::wstring const& path);
     GEODE_DLL Result<std::string> readString(ghc::filesystem::path const& path);
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<byte_array> readBinary(
-        std::string const& path
-    );
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<byte_array> readBinary(
-        std::wstring const& path
-    );
     GEODE_DLL Result<byte_array> readBinary(ghc::filesystem::path const& path);
 
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<> writeString(
-        std::string const& path, std::string const& data
-    );
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<> writeString(
-        std::wstring const& path, std::string const& data
-    );
     GEODE_DLL Result<> writeString(ghc::filesystem::path const& path, std::string const& data);
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<> writeBinary(
-        std::string const& path, byte_array const& data
-    );
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<> writeBinary(
-        std::wstring const& path, byte_array const& data
-    );
     GEODE_DLL Result<> writeBinary(ghc::filesystem::path const& path, byte_array const& data);
 
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<> createDirectory(
-        std::string const& path
-    );
     GEODE_DLL Result<> createDirectory(ghc::filesystem::path const& path);
-    [[deprecated("Use the ghc::filesystem::path version")]] GEODE_DLL Result<> createDirectoryAll(
-        std::string const& path
-    );
     GEODE_DLL Result<> createDirectoryAll(ghc::filesystem::path const& path);
     GEODE_DLL Result<std::vector<std::string>> listFiles(std::string const& path);
     GEODE_DLL Result<std::vector<std::string>> listFilesRecursively(std::string const& path);
@@ -54,4 +27,28 @@ namespace geode::utils::file {
      * @returns Ok on success, Error on error
      */
     GEODE_DLL Result<> unzipTo(ghc::filesystem::path const& from, ghc::filesystem::path const& to);
+
+    GEODE_DLL ghc::filesystem::path geodeRoot();
+    GEODE_DLL bool openFolder(ghc::filesystem::path const& path);
+
+    enum class PickMode {
+        OpenFile,
+        SaveFile,
+        OpenFolder,
+    };
+
+    struct FilePickOptions {
+        struct Filter {
+            // Name of the filter
+            std::string description;
+            // Extensions (*.txt, *.doc, *.mp3, etc.)
+            std::unordered_set<std::string> files;
+        };
+
+        ghc::filesystem::path defaultPath;
+        std::vector<Filter> filters;
+    };
+
+    GEODE_DLL Result<ghc::filesystem::path> pickFile(PickMode mode, FilePickOptions const& options);
+    GEODE_DLL Result<std::vector<ghc::filesystem::path>> pickFiles(FilePickOptions const& options);
 }
diff --git a/loader/include/Geode/utils/general.hpp b/loader/include/Geode/utils/general.hpp
index 367d5598..554e67b7 100644
--- a/loader/include/Geode/utils/general.hpp
+++ b/loader/include/Geode/utils/general.hpp
@@ -7,79 +7,105 @@
 #include <iomanip>
 #include <sstream>
 #include <string>
+#include <vector>
 
 #undef snprintf
 
-namespace geode::utils {
-    constexpr unsigned int hash(char const* str, int h = 0) {
-        return !str[h] ? 5381 : (hash(str, h + 1) * 33) ^ str[h];
-    }
-
-    constexpr unsigned int hash(wchar_t const* str, int h = 0) {
-        return !str[h] ? 5381 : (hash(str, h + 1) * 33) ^ str[h];
-    }
-
-    constexpr size_t operator"" _h(char const* txt, size_t) {
-        return geode::utils::hash(txt);
-    }
-
-    constexpr size_t operator"" _h(wchar_t const* txt, size_t) {
-        return geode::utils::hash(txt);
-    }
+namespace geode {
+    using byte_array = std::vector<uint8_t>;
 
     template <typename T>
-    constexpr const T& clamp(const T& value, const T& minValue, const T& maxValue) {
-        return value < minValue ? minValue : maxValue < value ? maxValue : value;
+    byte_array to_byte_array(T const& a) {
+        byte_array out;
+        out.resize(sizeof(T));
+        std::memcpy(out.data(), &a, sizeof(T));
+        return out;
     }
 
-    // from https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf
-    template <typename... Args>
-    std::string strfmt(std::string const& format, Args... args) {
-        int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
-        if (size_s <= 0) {
-            throw std::runtime_error("Error during formatting.");
+    template <class T>
+    struct TypeIdentity {
+        using type = T;
+    };
+
+    template <class T>
+    using TypeIdentityType = typename TypeIdentity<T>::type;
+        
+    namespace utils {
+        constexpr unsigned int hash(char const* str, int h = 0) {
+            return !str[h] ? 5381 : (hash(str, h + 1) * 33) ^ str[h];
         }
-        auto size = static_cast<size_t>(size_s);
-        auto buf = std::make_unique<char[]>(size);
-        std::snprintf(buf.get(), size, format.c_str(), args...);
-        return std::string(buf.get(), buf.get() + size - 1);
-    }
 
-    /**
-     * Format a string
-     * @returns Pointer to char array. MAKE SURE TO CALL DELETE[]!
-     */
-    template <typename... Args>
-    char const* cstrfmt(char const* fmt, Args... args) {
-        auto str = strfmt(fmt, args...);
-        char* ptr = new char[str.size() + 1];
-        strcpy_s(ptr, str.c_str());
-        return ptr;
-    }
-
-    template <typename T>
-    std::string intToHex(T i) {
-        std::stringstream stream;
-        stream << std::showbase << std::setbase(16) << (uint64_t)i;
-        return stream.str();
-    }
-
-    /**
-     * Turn a number into a string, with support for specifying precision
-     * (unlike std::to_string).
-     * @param num Number to convert to string
-     * @param precision Precision of the converted number
-     * @returns Number as string
-     */
-    template <class Num>
-    std::string numToString(Num num, size_t precision = 0) {
-        std::stringstream ss;
-        if (precision) {
-            ss << std::fixed << std::setprecision(precision);
+        constexpr unsigned int hash(wchar_t const* str, int h = 0) {
+            return !str[h] ? 5381 : (hash(str, h + 1) * 33) ^ str[h];
         }
-        ss << num;
-        return ss.str();
-    }
 
-    GEODE_DLL std::string timePointAsString(std::chrono::system_clock::time_point const& tp);
+        constexpr size_t operator"" _h(char const* txt, size_t) {
+            return geode::utils::hash(txt);
+        }
+
+        constexpr size_t operator"" _h(wchar_t const* txt, size_t) {
+            return geode::utils::hash(txt);
+        }
+
+        template <typename T>
+        constexpr const T& clamp(const T& value, const T& minValue, const T& maxValue) {
+            return value < minValue ? minValue : maxValue < value ? maxValue : value;
+        }
+
+        // from https://stackoverflow.com/questions/2342162/stdstring-formatting-like-sprintf
+        template <typename... Args>
+        std::string strfmt(std::string const& format, Args... args) {
+            int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
+            if (size_s <= 0) {
+                throw std::runtime_error("Error during formatting.");
+            }
+            auto size = static_cast<size_t>(size_s);
+            auto buf = std::make_unique<char[]>(size);
+            std::snprintf(buf.get(), size, format.c_str(), args...);
+            return std::string(buf.get(), buf.get() + size - 1);
+        }
+
+        /**
+         * Format a string
+         * @returns Pointer to char array. MAKE SURE TO CALL DELETE[]!
+         */
+        template <typename... Args>
+        char const* cstrfmt(char const* fmt, Args... args) {
+            auto str = strfmt(fmt, args...);
+            char* ptr = new char[str.size() + 1];
+            strcpy_s(ptr, str.c_str());
+            return ptr;
+        }
+
+        template <typename T>
+        std::string intToHex(T i) {
+            std::stringstream stream;
+            stream << std::showbase << std::setbase(16) << (uint64_t)i;
+            return stream.str();
+        }
+
+        /**
+         * Turn a number into a string, with support for specifying precision
+         * (unlike std::to_string).
+         * @param num Number to convert to string
+         * @param precision Precision of the converted number
+         * @returns Number as string
+         */
+        template <class Num>
+        std::string numToString(Num num, size_t precision = 0) {
+            std::stringstream ss;
+            if (precision) {
+                ss << std::fixed << std::setprecision(precision);
+            }
+            ss << num;
+            return ss.str();
+        }
+
+        GEODE_DLL std::string timePointAsString(std::chrono::system_clock::time_point const& tp);
+    }
+}
+
+namespace geode::utils::clipboard {
+    GEODE_DLL bool write(std::string const& data);
+    GEODE_DLL std::string read();
 }
diff --git a/loader/include/Geode/utils/operators.hpp b/loader/include/Geode/utils/operators.hpp
deleted file mode 100644
index ea9c43ff..00000000
--- a/loader/include/Geode/utils/operators.hpp
+++ /dev/null
@@ -1,169 +0,0 @@
-#pragma once
-
-#include <cocos2d.h>
-
-namespace geode {
-    static cocos2d::CCPoint& operator*=(cocos2d::CCPoint& pos, float mul) {
-        pos.x *= mul;
-        pos.y *= mul;
-        return pos;
-    }
-
-    static cocos2d::CCSize& operator*=(cocos2d::CCSize& size, float mul) {
-        size.width *= mul;
-        size.height *= mul;
-        return size;
-    }
-
-    static cocos2d::CCSize operator*(cocos2d::CCSize const& size, cocos2d::CCPoint const& point) {
-        return {
-            size.width * point.x,
-            size.height * point.y,
-        };
-    }
-
-    static cocos2d::CCRect operator*=(cocos2d::CCRect& rect, float mul) {
-        rect.origin *= mul;
-        rect.size *= mul;
-        return rect;
-    }
-
-    static cocos2d::CCRect operator*(cocos2d::CCRect const& rect, float mul) {
-        return {
-            rect.origin.x * mul,
-            rect.origin.y * mul,
-            rect.size.width * mul,
-            rect.size.height * mul,
-        };
-    }
-
-    static cocos2d::CCPoint operator/=(cocos2d::CCPoint& pos, float div) {
-        pos.x /= div;
-        pos.y /= div;
-        return pos;
-    }
-
-    static cocos2d::CCSize operator/=(cocos2d::CCSize& size, float div) {
-        size.width /= div;
-        size.height /= div;
-        return size;
-    }
-
-    static cocos2d::CCRect operator/=(cocos2d::CCRect& rect, float div) {
-        rect.origin /= div;
-        rect.size /= div;
-        return rect;
-    }
-
-    static cocos2d::CCPoint operator+=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) {
-        pos.x += add.x;
-        pos.y += add.y;
-        return pos;
-    }
-
-    static cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) {
-        size.width += add.x;
-        size.height += add.y;
-        return size;
-    }
-
-    static cocos2d::CCSize operator+=(cocos2d::CCSize& size, cocos2d::CCSize const& add) {
-        size.width += add.width;
-        size.height += add.height;
-        return size;
-    }
-
-    static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) {
-        rect.origin += add;
-        return rect;
-    }
-
-    static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) {
-        rect.size += add;
-        return rect;
-    }
-
-    static cocos2d::CCRect operator+=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) {
-        rect.origin += add.origin;
-        rect.size += add.size;
-        return rect;
-    }
-
-    static cocos2d::CCPoint operator-=(cocos2d::CCPoint& pos, cocos2d::CCPoint const& add) {
-        pos.x -= add.x;
-        pos.y -= add.y;
-        return pos;
-    }
-
-    static cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCPoint const& add) {
-        size.width -= add.x;
-        size.height -= add.y;
-        return size;
-    }
-
-    static cocos2d::CCSize operator-=(cocos2d::CCSize& size, cocos2d::CCSize const& add) {
-        size.width -= add.width;
-        size.height -= add.height;
-        return size;
-    }
-
-    static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCPoint const& add) {
-        rect.origin -= add;
-        return rect;
-    }
-
-    static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCSize const& add) {
-        rect.size -= add;
-        return rect;
-    }
-
-    static cocos2d::CCRect operator-=(cocos2d::CCRect& rect, cocos2d::CCRect const& add) {
-        rect.origin -= add.origin;
-        rect.size -= add.size;
-        return rect;
-    }
-
-    static cocos2d::CCSize operator-(cocos2d::CCSize const& size, float f) {
-        return { size.width - f, size.height - f };
-    }
-
-    static cocos2d::CCSize operator-(cocos2d::CCSize const& size) {
-        return { -size.width, -size.height };
-    }
-
-    static bool operator==(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) {
-        return p1.x == p2.x && p1.y == p2.y;
-    }
-
-    static bool operator!=(cocos2d::CCPoint const& p1, cocos2d::CCPoint const& p2) {
-        return p1.x != p2.x || p1.y != p2.y;
-    }
-
-    static bool operator==(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
-        return s1.width == s2.width && s1.height == s2.height;
-    }
-
-    static bool operator!=(cocos2d::CCSize const& s1, cocos2d::CCSize const& s2) {
-        return s1.width != s2.width || s1.height != s2.height;
-    }
-
-    static bool operator==(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) {
-        return r1.origin == r2.origin && r1.size == r2.size;
-    }
-
-    static bool operator!=(cocos2d::CCRect const& r1, cocos2d::CCRect const& r2) {
-        return r1.origin != r2.origin || r1.size != r2.size;
-    }
-
-    static bool operator==(cocos2d::ccColor4B const& c1, cocos2d::ccColor4B const& c2) {
-        return c1.r == c2.r && c1.g == c2.g && c1.b == c2.b && c1.a == c2.a;
-    }
-
-    static bool operator!=(cocos2d::ccColor4B const& c1, cocos2d::ccColor4B const& c2) {
-        return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b || c1.a != c2.a;
-    }
-
-    static bool operator!=(cocos2d::ccColor3B const& c1, cocos2d::ccColor3B const& c2) {
-        return c1.r != c2.r || c1.g != c2.g || c1.b != c2.b;
-    }
-}
diff --git a/loader/include/Geode/utils/platform.hpp b/loader/include/Geode/utils/platform.hpp
deleted file mode 100644
index 7915ca9a..00000000
--- a/loader/include/Geode/utils/platform.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include "Result.hpp"
-#include "fs/filesystem.hpp"
-
-#include <Geode/DefaultInclude.hpp>
-#include <functional>
-#include <string>
-#include <unordered_set>
-#include <vector>
-
-namespace geode::utils::clipboard {
-    GEODE_DLL bool write(std::string const& data);
-    GEODE_DLL std::string read();
-}
-
-namespace geode::utils::file {
-    GEODE_DLL ghc::filesystem::path geodeRoot();
-    GEODE_DLL bool openFolder(ghc::filesystem::path const& path);
-
-    enum class PickMode {
-        OpenFile,
-        SaveFile,
-        OpenFolder,
-    };
-
-    struct FilePickOptions {
-        struct Filter {
-            // Name of the filter
-            std::string description;
-            // Extensions (*.txt, *.doc, *.mp3, etc.)
-            std::unordered_set<std::string> files;
-        };
-
-        ghc::filesystem::path defaultPath;
-        std::vector<Filter> filters;
-    };
-
-    GEODE_DLL Result<ghc::filesystem::path> pickFile(PickMode mode, FilePickOptions const& options);
-    GEODE_DLL Result<std::vector<ghc::filesystem::path>> pickFiles(FilePickOptions const& options);
-}
-
-namespace geode::utils::web {
-    GEODE_DLL void openLinkInBrowser(std::string const& url);
-}
diff --git a/loader/include/Geode/utils/types.hpp b/loader/include/Geode/utils/types.hpp
deleted file mode 100644
index 484fcde0..00000000
--- a/loader/include/Geode/utils/types.hpp
+++ /dev/null
@@ -1,23 +0,0 @@
-#pragma once
-
-#include <vector>
-
-namespace geode {
-    using byte_array = std::vector<uint8_t>;
-
-    template <typename T>
-    byte_array to_byte_array(T const& a) {
-        byte_array out;
-        out.resize(sizeof(T));
-        std::memcpy(out.data(), &a, sizeof(T));
-        return out;
-    }
-
-    template <class T>
-    struct TypeIdentity {
-        using type = T;
-    };
-
-    template <class T>
-    using TypeIdentityType = typename TypeIdentity<T>::type;
-}
diff --git a/loader/include/Geode/utils/vector.hpp b/loader/include/Geode/utils/vector.hpp
deleted file mode 100644
index 7b24d855..00000000
--- a/loader/include/Geode/utils/vector.hpp
+++ /dev/null
@@ -1,282 +0,0 @@
-#pragma once
-
-#include <Geode/DefaultInclude.hpp>
-#include <algorithm>
-#include <functional>
-#include <string>
-#include <vector>
-
-namespace geode::utils::vector {
-    /**
-     * Check if a vector contains an element by value.
-     * @param vec The vector to check.
-     * @param elem The element to find.
-     * @returns True if element is in `vec`, false if not.
-     */
-    template <class T>
-    [[deprecated("Use geode::utils::ranges::contains instead")]] bool contains(
-        std::vector<T> const& vec, T const& elem
-    ) {
-        return std::find(vec.begin(), vec.end(), elem) != vec.end();
-    }
-
-    /**
-     * Get the index of an element in a vector by value.
-     * @param vec The vector to check.
-     * @param elem The element to get the index of.
-     * @returns Iterator to the index of element, or
-     * std::vector::end if the item is not in the vector.
-     */
-    template <class T>
-    [[deprecated(
-        "Useless utility, if you want it added to geode::utils::ranges open an Issue on GitHub"
-    )]] typename std::vector<T>::const_iterator
-    indexOf(std::vector<T> const& vec, T const& elem) {
-        return std::find(vec.begin(), vec.end(), elem);
-    }
-
-    /**
-     * Check if a vector contains an element via a function.
-     * @param vec The vector to check.
-     * @param containFunc A function that returns bool if the
-     * element parameter is what is looked for.
-     * @returns True if an element matching `containFunc` is
-     * in `vec`, false if not.
-     */
-    template <class T>
-    [[deprecated("Use geode::utils::ranges::contains instead")]] bool contains(
-        std::vector<T> const& vec, std::function<bool(T)> containFunc
-    ) {
-        for (auto const& item : vec) {
-            if (containFunc(item)) return true;
-        }
-        return false;
-    }
-
-    /**
-     * Add a vector to the end of another vector.
-     * @param vec The vector to add to.
-     * @param subVec The vector to add.
-     */
-    template <class T>
-    [[deprecated("Use geode::utils::ranges::push instead")]] void push(
-        std::vector<T>& vec, std::vector<T> const& subVec
-    ) {
-        vec.insert(vec.begin(), subVec.begin(), subVec.end());
-    }
-
-    /**
-     * Turn a vector into a string. T must be either a string,
-     * or convertible via `std::to_string`.
-     * @param vec The vector to add to.
-     * @param sep Separator string.
-     * @returns Joined string.
-     */
-    template <class T>
-    [[deprecated("Use geode::utils::ranges::join instead")]] std::string join(
-        std::vector<T> const& vec, std::string const& sep
-    ) {
-        std::string res = "";
-
-        for (auto p : vec) {
-            if constexpr (std::is_same_v<T, std::string>) {
-                res += p + sep;
-            }
-            else {
-                res += std::to_string(p) + sep;
-            }
-        }
-
-        res = res.substr(0, res.length() - sep.length());
-
-        return res;
-    }
-
-    /**
-     * Map a vector of items type T to a new vector of items
-     * type T2.
-     * @param vec The vector to map.
-     * @param mapFunc Function that converts an element from T to T2.
-     * @returns Mapped vector.
-     */
-    template <class T, class T2>
-    [[deprecated("Use geode::utils::ranges::map instead")]] std::vector<T2> map(
-        std::vector<T> const& vec, std::function<T2(T)> mapFunc
-    ) {
-        std::vector<T2> res;
-        std::transform(vec.begin(), vec.end(), res.end(), mapFunc);
-        return res;
-    }
-
-    /**
-     * Remove items from a vector that don't match the filter
-     * predicate.
-     * @param vec The vector to filter.
-     * @param filterFunc Predicate function that returns true
-     * if element should be kept in vector.
-     * @returns Filtered vector.
-     */
-    template <class T>
-    [[deprecated(
-        "Useless utility, if you want it added to geode::utils::ranges open an Issue on GitHub"
-    )]] std::vector<T>&
-    filterIP(std::vector<T>& vec, std::function<bool(T)> filterFunc) {
-        std::vector<T> res;
-        for (auto m : vec) {
-            if (filterFunc(m)) {
-                res.push_back(m);
-            }
-        }
-        vec = res;
-        return vec;
-    }
-
-    /**
-     * Return a copy of the vector that has items not
-     * matching `filterFunc` removed.
-     * @param vec The vector to filter.
-     * @param filterFunc Predicate function that returns true
-     * if element should be kept in vector.
-     * @returns Filtered vector.
-     */
-    template <class T>
-    [[deprecated("Use geode::utils::ranges::filter instead")]] std::vector<T> filter(
-        std::vector<T> const& vec, std::function<bool(T)> filterFunc
-    ) {
-        std::vector<T> res;
-        for (auto m : vec) {
-            if (filterFunc(m)) {
-                res.push_back(m);
-            }
-        }
-        return res;
-    }
-
-    /**
-     * See if an item in the vector matching the predicate
-     * `selectFunc` exists, and return it if it does.
-     * @param vec The vector to select from.
-     * @param selectFunc Predicate function to see if a
-     * given item is what is looked for.
-     * @returns Found item, or the default of T if it was
-     * not found. If T is pointer type and item was not
-     * found, the return type is nullptr.
-     */
-    template <class T>
-    [[deprecated(
-        "Useless utility, if you want it added to geode::utils::ranges open an Issue on GitHub"
-    )]] T
-    select(std::vector<T> const& vec, std::function<bool(T)> selectFunc) {
-        for (auto const& v : vec) {
-            if (selectFunc(v)) {
-                return v;
-            }
-        }
-        if (std::is_pointer<T>::value) {
-            return nullptr;
-        }
-        return T();
-    }
-
-    /**
-     * Alias for `filter`.
-     * @param vec The vector to filter.
-     * @param selectFunc Predicate function that returns true
-     * if element should be kept in vector.
-     * @returns Filtered vector.
-     */
-    template <class T>
-    [[deprecated(
-        "Useless utility, if you want it added to geode::utils::ranges open an Issue on GitHub"
-    )]] std::vector<T>
-    selectAll(std::vector<T> const& vec, std::function<bool(T)> selectFunc) {
-        return filter<T>(vec, selectFunc);
-    }
-
-    /**
-     * Remove elements from a vector by value.
-     * @param vec The vector to remove from.
-     * @param element Elements to remove.
-     * @returns Reference to vector.
-     */
-    template <class T>
-    [[deprecated("Use geode::utils::ranges::remove instead")]] std::vector<T>& erase(
-        std::vector<T>& vec, T const& element
-    ) {
-        vec.erase(std::remove(vec.begin(), vec.end(), element), vec.end());
-        return vec;
-    }
-
-    /**
-     * Remove elements from a vector via a function.
-     * @param vec The vector to remove from.
-     * @param eraseFunc Predicate function to decide whether
-     * to remove an element or not.
-     * @returns Reference to vector.
-     */
-    template <class T>
-    [[deprecated("Use geode::utils::ranges::remove instead")]] std::vector<T>& erase(
-        std::vector<T>& vec, std::function<bool(T)> eraseFunc
-    ) {
-        vec.erase(std::remove_if(vec.begin(), vec.end(), eraseFunc), vec.end());
-        return vec;
-    }
-
-    /**
-     * Reduce vector of elements to single value.
-     * @param vec The vector to reduce.
-     * @param reduceFunc Function to handle the
-     * creation of the reduced value. First parameter
-     * is a reference to the accumulator and second
-     * is the current item. Modify the value in the
-     * accumulator, for example with R += T, to
-     * compute the reduced value.
-     * @example ```cpp
-     * std::vector<int> numbers = { 1, 3, 7, 8, };
-     * int total = reduce<int, int>(numbers, [](int& acc, int val) -> void {
-     *      acc += val;
-     * }); // 19
-     * ```
-     * @returns Reduced value.
-     */
-    template <class R, class T>
-    [[deprecated("Use geode::utils::ranges::reduce instead")]] R reduce(
-        std::vector<T> const& vec, std::function<void(R&, T)> reduceFunc
-    ) {
-        R res = R();
-        for (auto const& item : vec) {
-            reduceFunc(res, item);
-        }
-        return res;
-    }
-
-    /**
-     * Insert an element in a vector before another
-     * element.
-     * @param vec Vector to insert into.
-     * @param item Element to insert.
-     * @param after Element to insert before.
-     */
-    template <class T>
-    [[deprecated(
-        "Useless utility, if you want it added to geode::utils::ranges open an Issue on GitHub"
-    )]] void
-    insertBefore(std::vector<T>& vec, T const& item, T const& before) {
-        vec.insert(utils::vector::indexOf(vec, before), item);
-    }
-
-    /**
-     * Insert an element in a vector after another
-     * element.
-     * @param vec Vector to insert into.
-     * @param item Element to insert.
-     * @param after Element to insert after.
-     */
-    template <class T>
-    [[deprecated(
-        "Useless utility, if you want it added to geode::utils::ranges open an Issue on GitHub"
-    )]] void
-    insertAfter(std::vector<T>& vec, T const& item, T const& after) {
-        vec.insert(utils::vector::indexOf(vec, after) + 1, item);
-    }
-}
diff --git a/loader/include/Geode/utils/fetch.hpp b/loader/include/Geode/utils/web.hpp
similarity index 96%
rename from loader/include/Geode/utils/fetch.hpp
rename to loader/include/Geode/utils/web.hpp
index 0950bd25..94b08f4a 100644
--- a/loader/include/Geode/utils/fetch.hpp
+++ b/loader/include/Geode/utils/web.hpp
@@ -2,13 +2,15 @@
 
 #include "../DefaultInclude.hpp"
 #include "Result.hpp"
-#include "json.hpp"
-#include "types.hpp"
+#include "../external/json/json.hpp"
+#include "general.hpp"
 
 #include <fs/filesystem.hpp>
 #include <mutex>
 
 namespace geode::utils::web {
+    GEODE_DLL void openLinkInBrowser(std::string const& url);
+    
     using FileProgressCallback = std::function<bool(double, double)>;
 
     /**
diff --git a/loader/src/hooks/GeodeNodeMetadata.cpp b/loader/src/hooks/GeodeNodeMetadata.cpp
index b0e72662..70a0f1a8 100644
--- a/loader/src/hooks/GeodeNodeMetadata.cpp
+++ b/loader/src/hooks/GeodeNodeMetadata.cpp
@@ -1,8 +1,6 @@
 #include <Geode/modify/Field.hpp>
-#include <Geode/utils/Ref.hpp>
 #include <Geode/utils/cocos.hpp>
 #include <Geode/modify/Field.hpp>
-#include <Geode/utils/WackyGeodeMacros.hpp>
 #include <Geode/modify/CCNode.hpp>
 #include <cocos2d.h>
 
@@ -133,7 +131,7 @@ void CCNode::updateLayout() {
     if (auto layout = GeodeNodeMetadata::set(this)->m_layout.get()) {
         // nodes with absolute position should never be rearranged
         auto filtered = CCArray::create();
-        CCARRAY_FOREACH_B_TYPE(m_pChildren, child, CCNode) {
+        for (auto& child : CCArrayExt<CCNode>(m_pChildren)) {
             if (child->getPositionHint() != PositionHint::Absolute) {
                 filtered->addObject(child);
             }
diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp
index c14b959d..ee446617 100644
--- a/loader/src/hooks/MenuLayer.cpp
+++ b/loader/src/hooks/MenuLayer.cpp
@@ -1,7 +1,5 @@
 
-#include <Geode/utils/WackyGeodeMacros.hpp>
 #include <Geode/utils/cocos.hpp>
-#include <Geode/utils/Ref.hpp>
 #include "../ui/internal/info/ModInfoLayer.hpp"
 #include "../ui/internal/list/ModListLayer.hpp"
 #include <Geode/ui/BasedButtonSprite.hpp>
diff --git a/loader/src/index/Index.cpp b/loader/src/index/Index.cpp
index 32bf78f2..11852e92 100644
--- a/loader/src/index/Index.cpp
+++ b/loader/src/index/Index.cpp
@@ -4,14 +4,13 @@
 #include <Geode/loader/Loader.hpp>
 #include <Geode/loader/Mod.hpp>
 #include <Geode/utils/JsonValidation.hpp>
-#include <Geode/utils/fetch.hpp>
+#include <Geode/utils/web.hpp>
 #include <Geode/utils/file.hpp>
 #include <Geode/utils/general.hpp>
-#include <Geode/utils/json.hpp>
+#include <Geode/external/json/json.hpp>
 #include <Geode/utils/map.hpp>
 #include <Geode/utils/ranges.hpp>
 #include <Geode/utils/string.hpp>
-#include <Geode/utils/vector.hpp>
 #include <fmt/format.h>
 #include <hash.hpp>
 #include <thread>
diff --git a/loader/src/index/Index.hpp b/loader/src/index/Index.hpp
index 1bcb7d84..1b548628 100644
--- a/loader/src/index/Index.hpp
+++ b/loader/src/index/Index.hpp
@@ -1,6 +1,6 @@
 #pragma once
 
-#include <Geode/utils/fetch.hpp>
+#include <Geode/utils/web.hpp>
 #include <mutex>
 #include <optional>
 #include <unordered_set>
diff --git a/loader/src/internal/FileWatcher.hpp b/loader/src/internal/FileWatcher.hpp
index 8869e1b0..92aeb769 100644
--- a/loader/src/internal/FileWatcher.hpp
+++ b/loader/src/internal/FileWatcher.hpp
@@ -1,7 +1,7 @@
 #pragma once
 
 #include <Geode/DefaultInclude.hpp>
-#include <Geode/utils/types.hpp>
+#include <Geode/utils/general.hpp>
 #include <fs/filesystem.hpp>
 #include <functional>
 #include <string>
diff --git a/loader/src/internal/InternalLoader.cpp b/loader/src/internal/InternalLoader.cpp
index e5ad867e..b9e7ef86 100644
--- a/loader/src/internal/InternalLoader.cpp
+++ b/loader/src/internal/InternalLoader.cpp
@@ -5,7 +5,7 @@
 
 #include <Geode/loader/Loader.hpp>
 #include <Geode/loader/Log.hpp>
-#include <Geode/utils/fetch.hpp>
+#include <Geode/utils/web.hpp>
 #include <Geode/utils/file.hpp>
 #include <fmt/format.h>
 #include <hash.hpp>
diff --git a/loader/src/internal/InternalLoader.hpp b/loader/src/internal/InternalLoader.hpp
index 144c172e..8ac2170b 100644
--- a/loader/src/internal/InternalLoader.hpp
+++ b/loader/src/internal/InternalLoader.hpp
@@ -6,7 +6,7 @@
 #include <Geode/loader/Loader.hpp>
 #include <Geode/loader/Log.hpp>
 #include <Geode/utils/Result.hpp>
-#include <Geode/utils/json.hpp>
+#include <Geode/external/json/json.hpp>
 #include <mutex>
 #include <optional>
 #include <unordered_map>
diff --git a/loader/src/internal/ios/FileWatcher.mm b/loader/src/internal/ios/FileWatcher.mm
index 00d3ba2e..9b1983bc 100644
--- a/loader/src/internal/ios/FileWatcher.mm
+++ b/loader/src/internal/ios/FileWatcher.mm
@@ -1,11 +1,10 @@
 #include <FileWatcher.hpp>
-#include <Geode/utils/platform.hpp>
 
 #ifdef GEODE_IS_IOS
 
-    #import <UIKit/UIKit.h>
-    #include <fcntl.h>
-    #include <iostream>
+#import <UIKit/UIKit.h>
+#include <fcntl.h>
+#include <iostream>
 
 // static constexpr const auto notifyAttributes = FILE_NOTIFY_CHANGE_LAST_WRITE |
 // FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE;
diff --git a/loader/src/internal/mac/FileWatcher.mm b/loader/src/internal/mac/FileWatcher.mm
index 270bde03..44f6a9b6 100644
--- a/loader/src/internal/mac/FileWatcher.mm
+++ b/loader/src/internal/mac/FileWatcher.mm
@@ -1,5 +1,4 @@
 #include <FileWatcher.hpp>
-#include <Geode/utils/platform.hpp>
 
 #ifdef GEODE_IS_MACOS
 
diff --git a/loader/src/internal/mac/crashlog.mm b/loader/src/internal/mac/crashlog.mm
index e0d773ac..41696cf0 100644
--- a/loader/src/internal/mac/crashlog.mm
+++ b/loader/src/internal/mac/crashlog.mm
@@ -2,7 +2,7 @@
 
 #ifdef GEODE_IS_MACOS
 
-    #include "../../../../filesystem/fs/filesystem.hpp"
+    #include <fs/filesystem.hpp>
 
     #include <Foundation/Foundation.h>
 
diff --git a/loader/src/load/Event.cpp b/loader/src/load/Event.cpp
index 7b4fbbef..2b4244e5 100644
--- a/loader/src/load/Event.cpp
+++ b/loader/src/load/Event.cpp
@@ -1,5 +1,4 @@
 #include <Geode/loader/Event.hpp>
-#include <Geode/utils/vector.hpp>
 
 USE_GEODE_NAMESPACE();
 
diff --git a/loader/src/load/Hook.cpp b/loader/src/load/Hook.cpp
index d673a6e6..47693bb4 100644
--- a/loader/src/load/Hook.cpp
+++ b/loader/src/load/Hook.cpp
@@ -3,7 +3,6 @@
 #include <Geode/loader/Mod.hpp>
 #include <Geode/utils/casts.hpp>
 #include <Geode/utils/ranges.hpp>
-#include <Geode/utils/vector.hpp>
 #include <vector>
 // #include <hook/hook.hpp>
 #include "InternalLoader.hpp"
diff --git a/loader/src/load/Loader.cpp b/loader/src/load/Loader.cpp
index 00445cfc..48da918d 100644
--- a/loader/src/load/Loader.cpp
+++ b/loader/src/load/Loader.cpp
@@ -2,11 +2,9 @@
 #include <Geode/loader/Loader.hpp>
 #include <Geode/loader/Log.hpp>
 #include <Geode/loader/Mod.hpp>
-#include <Geode/utils/conststring.hpp>
 #include <Geode/utils/file.hpp>
 #include <Geode/utils/map.hpp>
 #include <Geode/utils/ranges.hpp>
-#include <Geode/utils/types.hpp>
 #include <InternalLoader.hpp>
 #include <InternalMod.hpp>
 #include <about.hpp>
diff --git a/loader/src/load/Mod.cpp b/loader/src/load/Mod.cpp
index 9705447d..3edb77e8 100644
--- a/loader/src/load/Mod.cpp
+++ b/loader/src/load/Mod.cpp
@@ -5,12 +5,10 @@
 #include <Geode/loader/Mod.hpp>
 #include <Geode/loader/Setting.hpp>
 #include <Geode/utils/JsonValidation.hpp>
-#include <Geode/utils/conststring.hpp>
 #include <Geode/utils/file.hpp>
 #include <Geode/utils/map.hpp>
 #include <Geode/utils/ranges.hpp>
 #include <Geode/utils/string.hpp>
-#include <Geode/utils/vector.hpp>
 #include <InternalMod.hpp>
 #include <about.hpp>
 
diff --git a/loader/src/load/Patch.cpp b/loader/src/load/Patch.cpp
index d3d73811..99a1b4be 100644
--- a/loader/src/load/Patch.cpp
+++ b/loader/src/load/Patch.cpp
@@ -3,7 +3,6 @@
 #include <Geode/loader/Mod.hpp>
 #include <Geode/utils/casts.hpp>
 #include <Geode/utils/ranges.hpp>
-#include <Geode/utils/vector.hpp>
 #include <InternalLoader.hpp>
 #include <lilac/include/geode/core/hook/hook.hpp>
 #include <vector>
diff --git a/loader/src/load/ios/Mod.mm b/loader/src/load/ios/Mod.mm
index dd2ca74a..c7585dee 100644
--- a/loader/src/load/ios/Mod.mm
+++ b/loader/src/load/ios/Mod.mm
@@ -1,4 +1,3 @@
-#include <Geode/utils/platform.hpp>
 
 #ifdef GEODE_IS_IOS
 
diff --git a/loader/src/mac/gdstdlib.cpp b/loader/src/mac/gdstdlib.cpp
index 2e15527b..fe3a8515 100644
--- a/loader/src/mac/gdstdlib.cpp
+++ b/loader/src/mac/gdstdlib.cpp
@@ -1,5 +1,4 @@
 #include <Geode/c++stl/gdstdlib.hpp>
-#include <Geode/utils/platform.hpp>
 
 #ifdef GEODE_IS_MACOS
 
diff --git a/loader/src/ui/internal/info/ModInfoLayer.cpp b/loader/src/ui/internal/info/ModInfoLayer.cpp
index dc8ea7d8..e0ad9d31 100644
--- a/loader/src/ui/internal/info/ModInfoLayer.cpp
+++ b/loader/src/ui/internal/info/ModInfoLayer.cpp
@@ -18,7 +18,6 @@
 #include <Geode/ui/MDPopup.hpp>
 #include <Geode/utils/casts.hpp>
 #include <Geode/utils/ranges.hpp>
-#include <Geode/utils/vector.hpp>
 #include <InternalLoader.hpp>
 
 // TODO: die
diff --git a/loader/src/ui/internal/settings/GeodeSettingNode.cpp b/loader/src/ui/internal/settings/GeodeSettingNode.cpp
index d2bd15a1..ef3a7546 100644
--- a/loader/src/ui/internal/settings/GeodeSettingNode.cpp
+++ b/loader/src/ui/internal/settings/GeodeSettingNode.cpp
@@ -4,7 +4,6 @@
 #include <Geode/binding/CCTextInputNode.hpp>
 #include <Geode/binding/ColorChannelSprite.hpp>
 #include <Geode/binding/Slider.hpp>
-#include <Geode/utils/platform.hpp>
 
 // BoolSettingNode
 
diff --git a/loader/src/ui/internal/settings/GeodeSettingNode.hpp b/loader/src/ui/internal/settings/GeodeSettingNode.hpp
index a12e7e99..0b5bc499 100644
--- a/loader/src/ui/internal/settings/GeodeSettingNode.hpp
+++ b/loader/src/ui/internal/settings/GeodeSettingNode.hpp
@@ -11,8 +11,6 @@
 #include <Geode/ui/InputNode.hpp>
 #include <Geode/ui/Popup.hpp>
 #include <Geode/utils/cocos.hpp>
-#include <Geode/utils/convert.hpp>
-#include <Geode/utils/operators.hpp>
 #include <Geode/utils/string.hpp>
 
 USE_GEODE_NAMESPACE();
diff --git a/loader/src/ui/internal/settings/ModSettingsPopup.cpp b/loader/src/ui/internal/settings/ModSettingsPopup.cpp
index 1de0a9fe..55873826 100644
--- a/loader/src/ui/internal/settings/ModSettingsPopup.cpp
+++ b/loader/src/ui/internal/settings/ModSettingsPopup.cpp
@@ -5,7 +5,6 @@
 #include <Geode/loader/Setting.hpp>
 #include <Geode/ui/ScrollLayer.hpp>
 #include <Geode/utils/cocos.hpp>
-#include <Geode/utils/convert.hpp>
 
 bool ModSettingsPopup::setup(Mod* mod) {
     m_noElasticity = true;
diff --git a/loader/src/ui/internal/settings/ModSettingsPopup.hpp b/loader/src/ui/internal/settings/ModSettingsPopup.hpp
index f9cfa653..82d9eef6 100644
--- a/loader/src/ui/internal/settings/ModSettingsPopup.hpp
+++ b/loader/src/ui/internal/settings/ModSettingsPopup.hpp
@@ -2,7 +2,7 @@
 
 #include <Geode/loader/SettingNode.hpp>
 #include <Geode/ui/Popup.hpp>
-#include <Geode/utils/Ref.hpp>
+#include <Geode/utils/cocos.hpp>
 
 USE_GEODE_NAMESPACE();
 
diff --git a/loader/src/ui/nodes/ColorPickPopup.cpp b/loader/src/ui/nodes/ColorPickPopup.cpp
index 3bfee86e..b77a0c4a 100644
--- a/loader/src/ui/nodes/ColorPickPopup.cpp
+++ b/loader/src/ui/nodes/ColorPickPopup.cpp
@@ -3,7 +3,7 @@
 #include <Geode/binding/Slider.hpp>
 #include <Geode/binding/SliderThumb.hpp>
 #include <Geode/ui/ColorPickPopup.hpp>
-#include <Geode/utils/operators.hpp>
+#include <Geode/utils/cocos.hpp>
 
 USE_GEODE_NAMESPACE();
 
diff --git a/loader/src/ui/nodes/MDTextArea.cpp b/loader/src/ui/nodes/MDTextArea.cpp
index a3650afd..f01afc26 100644
--- a/loader/src/ui/nodes/MDTextArea.cpp
+++ b/loader/src/ui/nodes/MDTextArea.cpp
@@ -3,9 +3,7 @@
 #include <Geode/ui/MDTextArea.hpp>
 #include <Geode/utils/casts.hpp>
 #include <Geode/utils/cocos.hpp>
-#include <Geode/utils/convert.hpp>
-#include <Geode/utils/fetch.hpp>
-#include <Geode/utils/platform.hpp>
+#include <Geode/utils/web.hpp>
 #include <Geode/utils/ranges.hpp>
 #include <Geode/utils/string.hpp>
 #include <md4c.h>
diff --git a/loader/src/ui/nodes/Notification.cpp b/loader/src/ui/nodes/Notification.cpp
index b812793a..f7554158 100644
--- a/loader/src/ui/nodes/Notification.cpp
+++ b/loader/src/ui/nodes/Notification.cpp
@@ -3,7 +3,6 @@
 #include <Geode/ui/Notification.hpp>
 #include <Geode/ui/TextRenderer.hpp>
 #include <Geode/utils/cocos.hpp>
-#include <Geode/utils/container.hpp>
 #include <Geode/utils/ranges.hpp>
 
 USE_GEODE_NAMESPACE();
diff --git a/loader/src/ui/nodes/Scrollbar.cpp b/loader/src/ui/nodes/Scrollbar.cpp
index 6c645b27..1e71abd8 100644
--- a/loader/src/ui/nodes/Scrollbar.cpp
+++ b/loader/src/ui/nodes/Scrollbar.cpp
@@ -1,6 +1,5 @@
 #include <Geode/ui/Scrollbar.hpp>
 #include <Geode/utils/cocos.hpp>
-#include <Geode/utils/operators.hpp>
 
 // TODO: die
 #undef min
diff --git a/loader/src/ui/nodes/TextRenderer.cpp b/loader/src/ui/nodes/TextRenderer.cpp
index 813f7174..121431a8 100644
--- a/loader/src/ui/nodes/TextRenderer.cpp
+++ b/loader/src/ui/nodes/TextRenderer.cpp
@@ -1,7 +1,6 @@
 #include <Geode/ui/TextRenderer.hpp>
 #include <Geode/utils/casts.hpp>
 #include <Geode/utils/cocos.hpp>
-#include <Geode/utils/operators.hpp>
 #include <Geode/utils/string.hpp>
 #undef max
 #undef min
diff --git a/loader/src/utils/cocos.cpp b/loader/src/utils/cocos.cpp
index 88cda9d1..36cb10f0 100644
--- a/loader/src/utils/cocos.cpp
+++ b/loader/src/utils/cocos.cpp
@@ -1,10 +1,245 @@
 #include <Geode/utils/cocos.hpp>
-#include <Geode/utils/operators.hpp>
-#include <Geode/utils/WackyGeodeMacros.hpp>
 #include <Geode/modify/LoadingLayer.hpp>
 
 USE_GEODE_NAMESPACE();
 
+void cocos2d::to_json(nlohmann::json& json, ccColor3B const& color) {
+    json = nlohmann::json {
+        { "r", color.r },
+        { "g", color.g },
+        { "b", color.b },
+    };
+}
+
+void cocos2d::from_json(nlohmann::json const& json, ccColor3B& color) {
+    // array
+    if (json.is_array()) {
+        if (json.size() == 3) {
+            json.at(0).get_to(color.r);
+            json.at(1).get_to(color.g);
+            json.at(2).get_to(color.b);
+        }
+        else {
+            throw nlohmann::json::type_error::create(
+                0, "Expected color array to have 3 items", json
+            );
+        }
+    }
+    // object
+    else if (json.is_object()) {
+        json.at("r").get_to(color.r);
+        json.at("g").get_to(color.g);
+        json.at("b").get_to(color.b);
+    }
+    // hex string
+    else if (json.is_string()) {
+        std::string str = json;
+        if (str[0] == '#') {
+            str.erase(str.begin());
+        }
+        if (str.size() > 6) {
+            throw nlohmann::json::type_error::create(0, "Hex string for color too long", json);
+        }
+        auto c = cc3bFromHexString(str);
+        if (!c) {
+            throw nlohmann::json::type_error::create(
+                0, "Invalid color hex string: " + c.unwrapErr(), json
+            );
+        }
+        color = c.unwrap();
+    }
+    // bad
+    else {
+        throw nlohmann::json::type_error::create(
+            0, "Expected color to be array, object or hex string", json
+        );
+    }
+}
+
+void cocos2d::to_json(nlohmann::json& json, ccColor4B const& color) {
+    json = nlohmann::json {
+        { "r", color.r },
+        { "g", color.g },
+        { "b", color.b },
+        { "a", color.a },
+    };
+}
+
+void cocos2d::from_json(nlohmann::json const& json, ccColor4B& color) {
+    // array
+    if (json.is_array()) {
+        if (json.size() == 4) {
+            json.at(0).get_to(color.r);
+            json.at(1).get_to(color.g);
+            json.at(2).get_to(color.b);
+            json.at(3).get_to(color.a);
+        }
+        else {
+            throw nlohmann::json::type_error::create(
+                0, "Expected color array to have 4 items", json
+            );
+        }
+    }
+    // object
+    else if (json.is_object()) {
+        json.at("r").get_to(color.r);
+        json.at("g").get_to(color.g);
+        json.at("b").get_to(color.b);
+        json.at("a").get_to(color.a);
+    }
+    // hex string
+    else if (json.is_string()) {
+        std::string str = json;
+        if (str[0] == '#') {
+            str.erase(str.begin());
+        }
+        if (str.size() > 8) {
+            throw nlohmann::json::type_error::create(0, "Hex string for color too long", json);
+        }
+        auto c = cc4bFromHexString(str);
+        if (!c) {
+            throw nlohmann::json::type_error::create(
+                0, "Invalid color hex string: " + c.unwrapErr(), json
+            );
+        }
+        color = c.unwrap();
+    }
+    // bad
+    else {
+        throw nlohmann::json::type_error::create(
+            0, "Expected color to be array, object or hex string", json
+        );
+    }
+}
+
+Result<ccColor3B> geode::cocos::cc3bFromHexString(std::string const& hexValue) {
+    if (hexValue.empty()) {
+        return Ok(ccc3(255, 255, 255));
+    }
+    if (hexValue.size() > 6) {
+        return Err("Hex value too large");
+    }
+    int numValue;
+    try {
+        numValue = std::stoi(hexValue, 0, 16);
+    }
+    catch (...) {
+        return Err("Invalid hex value");
+    }
+    switch (hexValue.size()) {
+        case 6: {
+            auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
+            auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
+            auto b = static_cast<uint8_t>((numValue & 0x0000FF));
+            return Ok(ccc3(r, g, b));
+        } break;
+
+        case 3: {
+            auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
+            auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
+            auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
+            return Ok(ccc3(r, g, b));
+        } break;
+
+        case 2: {
+            auto num = static_cast<uint8_t>(numValue);
+            return Ok(ccc3(num, num, num));
+        } break;
+
+        case 1: {
+            auto num = static_cast<uint8_t>(numValue) * 17;
+            return Ok(ccc3(num, num, num));
+        } break;
+
+        default: return Err("Invalid hex size, expected 1, 2, 3, or 6");
+    }
+}
+
+Result<ccColor4B> geode::cocos::cc4bFromHexString(std::string const& hexValue) {
+    if (hexValue.empty()) {
+        return Ok(ccc4(255, 255, 255, 255));
+    }
+    if (hexValue.size() > 8) {
+        return Err("Hex value too large");
+    }
+    int numValue;
+    try {
+        numValue = std::stoi(hexValue, 0, 16);
+    }
+    catch (...) {
+        return Err("Invalid hex value");
+    }
+    switch (hexValue.size()) {
+        case 8: {
+            auto r = static_cast<uint8_t>((numValue & 0xFF000000) >> 24);
+            auto g = static_cast<uint8_t>((numValue & 0x00FF0000) >> 16);
+            auto b = static_cast<uint8_t>((numValue & 0x0000FF00) >> 8);
+            auto a = static_cast<uint8_t>((numValue & 0x000000FF));
+            return Ok(ccc4(r, g, b, a));
+        } break;
+
+        case 6: {
+            auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
+            auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
+            auto b = static_cast<uint8_t>((numValue & 0x0000FF));
+            return Ok(ccc4(r, g, b, 255));
+        } break;
+
+        case 4: {
+            auto r = static_cast<uint8_t>(((numValue & 0xF000) >> 12) * 17);
+            auto g = static_cast<uint8_t>(((numValue & 0x0F00) >> 8) * 17);
+            auto b = static_cast<uint8_t>(((numValue & 0x00F0) >> 4) * 17);
+            auto a = static_cast<uint8_t>(((numValue & 0x000F)) * 17);
+            return Ok(ccc4(r, g, b, a));
+        } break;
+
+        case 3: {
+            auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
+            auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
+            auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
+            return Ok(ccc4(r, g, b, 255));
+        } break;
+
+        case 2: {
+            auto num = static_cast<uint8_t>(numValue);
+            return Ok(ccc4(num, num, num, 255));
+        } break;
+
+        case 1: {
+            auto num = static_cast<uint8_t>(numValue) * 17;
+            return Ok(ccc4(num, num, num, 255));
+        } break;
+
+        default: return Err("Invalid hex size, expected 1, 2, 3, 4, 6, or 8");
+    }
+}
+
+std::string geode::cocos::cc3bToHexString(ccColor3B const& color) {
+    static constexpr auto digits = "0123456789ABCDEF";
+    std::string output;
+    output += digits[color.r >> 4 & 0xF];
+    output += digits[color.r & 0xF];
+    output += digits[color.g >> 4 & 0xF];
+    output += digits[color.g & 0xF];
+    output += digits[color.b >> 4 & 0xF];
+    output += digits[color.b & 0xF];
+    return output;
+}
+
+std::string geode::cocos::cc4bToHexString(ccColor4B const& color) {
+    static constexpr auto digits = "0123456789ABCDEF";
+    std::string output;
+    output += digits[color.r >> 4 & 0xF];
+    output += digits[color.r & 0xF];
+    output += digits[color.g >> 4 & 0xF];
+    output += digits[color.g & 0xF];
+    output += digits[color.b >> 4 & 0xF];
+    output += digits[color.b & 0xF];
+    output += digits[color.a >> 4 & 0xF];
+    output += digits[color.a & 0xF];
+    return output;
+}
+
 CCRect geode::cocos::calculateNodeCoverage(std::vector<CCNode*> const& nodes) {
     CCRect coverage;
     for (auto child : nodes) {
diff --git a/loader/src/utils/convert.cpp b/loader/src/utils/convert.cpp
deleted file mode 100644
index 007fed17..00000000
--- a/loader/src/utils/convert.cpp
+++ /dev/null
@@ -1,240 +0,0 @@
-#include <Geode/utils/convert.hpp>
-
-USE_GEODE_NAMESPACE();
-
-void cocos2d::to_json(nlohmann::json& json, ccColor3B const& color) {
-    json = nlohmann::json {
-        { "r", color.r },
-        { "g", color.g },
-        { "b", color.b },
-    };
-}
-
-void cocos2d::from_json(nlohmann::json const& json, ccColor3B& color) {
-    // array
-    if (json.is_array()) {
-        if (json.size() == 3) {
-            json.at(0).get_to(color.r);
-            json.at(1).get_to(color.g);
-            json.at(2).get_to(color.b);
-        }
-        else {
-            throw nlohmann::json::type_error::create(
-                0, "Expected color array to have 3 items", json
-            );
-        }
-    }
-    // object
-    else if (json.is_object()) {
-        json.at("r").get_to(color.r);
-        json.at("g").get_to(color.g);
-        json.at("b").get_to(color.b);
-    }
-    // hex string
-    else if (json.is_string()) {
-        std::string str = json;
-        if (str[0] == '#') {
-            str.erase(str.begin());
-        }
-        if (str.size() > 6) {
-            throw nlohmann::json::type_error::create(0, "Hex string for color too long", json);
-        }
-        auto c = cc3bFromHexString(str);
-        if (!c) {
-            throw nlohmann::json::type_error::create(
-                0, "Invalid color hex string: " + c.unwrapErr(), json
-            );
-        }
-        color = c.unwrap();
-    }
-    // bad
-    else {
-        throw nlohmann::json::type_error::create(
-            0, "Expected color to be array, object or hex string", json
-        );
-    }
-}
-
-void cocos2d::to_json(nlohmann::json& json, ccColor4B const& color) {
-    json = nlohmann::json {
-        { "r", color.r },
-        { "g", color.g },
-        { "b", color.b },
-        { "a", color.a },
-    };
-}
-
-void cocos2d::from_json(nlohmann::json const& json, ccColor4B& color) {
-    // array
-    if (json.is_array()) {
-        if (json.size() == 4) {
-            json.at(0).get_to(color.r);
-            json.at(1).get_to(color.g);
-            json.at(2).get_to(color.b);
-            json.at(3).get_to(color.a);
-        }
-        else {
-            throw nlohmann::json::type_error::create(
-                0, "Expected color array to have 4 items", json
-            );
-        }
-    }
-    // object
-    else if (json.is_object()) {
-        json.at("r").get_to(color.r);
-        json.at("g").get_to(color.g);
-        json.at("b").get_to(color.b);
-        json.at("a").get_to(color.a);
-    }
-    // hex string
-    else if (json.is_string()) {
-        std::string str = json;
-        if (str[0] == '#') {
-            str.erase(str.begin());
-        }
-        if (str.size() > 8) {
-            throw nlohmann::json::type_error::create(0, "Hex string for color too long", json);
-        }
-        auto c = cc4bFromHexString(str);
-        if (!c) {
-            throw nlohmann::json::type_error::create(
-                0, "Invalid color hex string: " + c.unwrapErr(), json
-            );
-        }
-        color = c.unwrap();
-    }
-    // bad
-    else {
-        throw nlohmann::json::type_error::create(
-            0, "Expected color to be array, object or hex string", json
-        );
-    }
-}
-
-Result<ccColor3B> geode::cocos::cc3bFromHexString(std::string const& hexValue) {
-    if (hexValue.empty()) {
-        return Ok(ccc3(255, 255, 255));
-    }
-    if (hexValue.size() > 6) {
-        return Err("Hex value too large");
-    }
-    int numValue;
-    try {
-        numValue = std::stoi(hexValue, 0, 16);
-    }
-    catch (...) {
-        return Err("Invalid hex value");
-    }
-    switch (hexValue.size()) {
-        case 6: {
-            auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
-            auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
-            auto b = static_cast<uint8_t>((numValue & 0x0000FF));
-            return Ok(ccc3(r, g, b));
-        } break;
-
-        case 3: {
-            auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
-            auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
-            auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
-            return Ok(ccc3(r, g, b));
-        } break;
-
-        case 2: {
-            auto num = static_cast<uint8_t>(numValue);
-            return Ok(ccc3(num, num, num));
-        } break;
-
-        case 1: {
-            auto num = static_cast<uint8_t>(numValue) * 17;
-            return Ok(ccc3(num, num, num));
-        } break;
-
-        default: return Err("Invalid hex size, expected 1, 2, 3, or 6");
-    }
-}
-
-Result<ccColor4B> geode::cocos::cc4bFromHexString(std::string const& hexValue) {
-    if (hexValue.empty()) {
-        return Ok(ccc4(255, 255, 255, 255));
-    }
-    if (hexValue.size() > 8) {
-        return Err("Hex value too large");
-    }
-    int numValue;
-    try {
-        numValue = std::stoi(hexValue, 0, 16);
-    }
-    catch (...) {
-        return Err("Invalid hex value");
-    }
-    switch (hexValue.size()) {
-        case 8: {
-            auto r = static_cast<uint8_t>((numValue & 0xFF000000) >> 24);
-            auto g = static_cast<uint8_t>((numValue & 0x00FF0000) >> 16);
-            auto b = static_cast<uint8_t>((numValue & 0x0000FF00) >> 8);
-            auto a = static_cast<uint8_t>((numValue & 0x000000FF));
-            return Ok(ccc4(r, g, b, a));
-        } break;
-
-        case 6: {
-            auto r = static_cast<uint8_t>((numValue & 0xFF0000) >> 16);
-            auto g = static_cast<uint8_t>((numValue & 0x00FF00) >> 8);
-            auto b = static_cast<uint8_t>((numValue & 0x0000FF));
-            return Ok(ccc4(r, g, b, 255));
-        } break;
-
-        case 4: {
-            auto r = static_cast<uint8_t>(((numValue & 0xF000) >> 12) * 17);
-            auto g = static_cast<uint8_t>(((numValue & 0x0F00) >> 8) * 17);
-            auto b = static_cast<uint8_t>(((numValue & 0x00F0) >> 4) * 17);
-            auto a = static_cast<uint8_t>(((numValue & 0x000F)) * 17);
-            return Ok(ccc4(r, g, b, a));
-        } break;
-
-        case 3: {
-            auto r = static_cast<uint8_t>(((numValue & 0xF00) >> 8) * 17);
-            auto g = static_cast<uint8_t>(((numValue & 0x0F0) >> 4) * 17);
-            auto b = static_cast<uint8_t>(((numValue & 0x00F)) * 17);
-            return Ok(ccc4(r, g, b, 255));
-        } break;
-
-        case 2: {
-            auto num = static_cast<uint8_t>(numValue);
-            return Ok(ccc4(num, num, num, 255));
-        } break;
-
-        case 1: {
-            auto num = static_cast<uint8_t>(numValue) * 17;
-            return Ok(ccc4(num, num, num, 255));
-        } break;
-
-        default: return Err("Invalid hex size, expected 1, 2, 3, 4, 6, or 8");
-    }
-}
-
-std::string geode::cocos::cc3bToHexString(ccColor3B const& color) {
-    static constexpr auto digits = "0123456789ABCDEF";
-    std::string output;
-    output += digits[color.r >> 4 & 0xF];
-    output += digits[color.r & 0xF];
-    output += digits[color.g >> 4 & 0xF];
-    output += digits[color.g & 0xF];
-    output += digits[color.b >> 4 & 0xF];
-    output += digits[color.b & 0xF];
-    return output;
-}
-
-std::string geode::cocos::cc4bToHexString(ccColor4B const& color) {
-    static constexpr auto digits = "0123456789ABCDEF";
-    std::string output;
-    output += digits[color.r >> 4 & 0xF];
-    output += digits[color.r & 0xF];
-    output += digits[color.g >> 4 & 0xF];
-    output += digits[color.g & 0xF];
-    output += digits[color.b >> 4 & 0xF];
-    output += digits[color.b & 0xF];
-    output += digits[color.a >> 4 & 0xF];
-    output += digits[color.a & 0xF];
-    return output;
-}
diff --git a/loader/src/utils/fetch.cpp b/loader/src/utils/fetch.cpp
index 40db4a95..2def41b3 100644
--- a/loader/src/utils/fetch.cpp
+++ b/loader/src/utils/fetch.cpp
@@ -1,8 +1,7 @@
 #include <Geode/cocos/platform/IncludeCurl.h>
 #include <Geode/loader/Loader.hpp>
 #include <Geode/utils/casts.hpp>
-#include <Geode/utils/fetch.hpp>
-#include <Geode/utils/vector.hpp>
+#include <Geode/utils/web.hpp>
 #include <thread>
 
 USE_GEODE_NAMESPACE();
diff --git a/loader/src/utils/file.cpp b/loader/src/utils/file.cpp
index 971c6e9f..ed91f6ee 100644
--- a/loader/src/utils/file.cpp
+++ b/loader/src/utils/file.cpp
@@ -5,36 +5,6 @@
 
 USE_GEODE_NAMESPACE();
 
-Result<std::string> utils::file::readString(std::string const& path) {
-    std::ifstream in(path, std::ios::in | std::ios::binary);
-    if (in) {
-        std::string contents;
-        in.seekg(0, std::ios::end);
-        contents.resize((const size_t)in.tellg());
-        in.seekg(0, std::ios::beg);
-        in.read(&contents[0], contents.size());
-        in.close();
-        return Ok(contents);
-    }
-    return Err("Unable to open file");
-}
-
-#if _WIN32
-Result<std::string> utils::file::readString(std::wstring const& path) {
-    std::ifstream in(path, std::ios::in | std::ios::binary);
-    if (in) {
-        std::string contents;
-        in.seekg(0, std::ios::end);
-        contents.resize((const size_t)in.tellg());
-        in.seekg(0, std::ios::beg);
-        in.read(&contents[0], contents.size());
-        in.close();
-        return Ok(contents);
-    }
-    return Err("Unable to open file");
-}
-#endif
-
 Result<std::string> utils::file::readString(ghc::filesystem::path const& path) {
 #if _WIN32
     std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
@@ -53,24 +23,6 @@ Result<std::string> utils::file::readString(ghc::filesystem::path const& path) {
     return Err("Unable to open file");
 }
 
-Result<byte_array> utils::file::readBinary(std::string const& path) {
-    std::ifstream in(path, std::ios::in | std::ios::binary);
-    if (in) {
-        return Ok(byte_array(std::istreambuf_iterator<char>(in), {}));
-    }
-    return Err("Unable to open file");
-}
-
-#if _WIN32
-Result<byte_array> utils::file::readBinary(std::wstring const& path) {
-    std::ifstream in(path, std::ios::in | std::ios::binary);
-    if (in) {
-        return Ok(byte_array(std::istreambuf_iterator<char>(in), {}));
-    }
-    return Err("Unable to open file");
-}
-#endif
-
 Result<byte_array> utils::file::readBinary(ghc::filesystem::path const& path) {
 #if _WIN32
     std::ifstream in(path.wstring(), std::ios::in | std::ios::binary);
@@ -83,34 +35,6 @@ Result<byte_array> utils::file::readBinary(ghc::filesystem::path const& path) {
     return Err("Unable to open file");
 }
 
-Result<> utils::file::writeString(std::string const& path, std::string const& data) {
-    std::ofstream file;
-    file.open(path);
-    if (file.is_open()) {
-        file << data;
-        file.close();
-
-        return Ok();
-    }
-    file.close();
-    return Err("Unable to open file");
-}
-
-#if _WIN32
-Result<> utils::file::writeString(std::wstring const& path, std::string const& data) {
-    std::ofstream file;
-    file.open(path);
-    if (file.is_open()) {
-        file << data;
-        file.close();
-
-        return Ok();
-    }
-    file.close();
-    return Err("Unable to open file");
-}
-#endif
-
 Result<> utils::file::writeString(ghc::filesystem::path const& path, std::string const& data) {
     std::ofstream file;
 #if _WIN32
@@ -128,34 +52,6 @@ Result<> utils::file::writeString(ghc::filesystem::path const& path, std::string
     return Err("Unable to open file");
 }
 
-Result<> utils::file::writeBinary(std::string const& path, byte_array const& data) {
-    std::ofstream file;
-    file.open(path, std::ios::out | std::ios::binary);
-    if (file.is_open()) {
-        file.write(reinterpret_cast<char const*>(data.data()), data.size());
-        file.close();
-
-        return Ok();
-    }
-    file.close();
-    return Err("Unable to open file");
-}
-
-#if _WIN32
-Result<> utils::file::writeBinary(std::wstring const& path, byte_array const& data) {
-    std::ofstream file;
-    file.open(path, std::ios::out | std::ios::binary);
-    if (file.is_open()) {
-        file.write(reinterpret_cast<char const*>(data.data()), data.size());
-        file.close();
-
-        return Ok();
-    }
-    file.close();
-    return Err("Unable to open file");
-}
-#endif
-
 Result<> utils::file::writeBinary(ghc::filesystem::path const& path, byte_array const& data) {
     std::ofstream file;
 #if _WIN32
@@ -173,17 +69,6 @@ Result<> utils::file::writeBinary(ghc::filesystem::path const& path, byte_array
     return Err("Unable to open file");
 }
 
-Result<> utils::file::createDirectory(std::string const& path) {
-    try {
-        if (ghc::filesystem::create_directory(path)) {
-            return Ok();
-        }
-    }
-    catch (...) {
-    }
-    return Err("Unable to create directory");
-}
-
 Result<> utils::file::createDirectory(ghc::filesystem::path const& path) {
     try {
         if (ghc::filesystem::create_directory(path)) {
@@ -195,17 +80,6 @@ Result<> utils::file::createDirectory(ghc::filesystem::path const& path) {
     return Err("Unable to create directory");
 }
 
-Result<> utils::file::createDirectoryAll(std::string const& path) {
-    try {
-        if (ghc::filesystem::create_directories(path)) {
-            return Ok();
-        }
-    }
-    catch (...) {
-    }
-    return Err("Unable to create directories");
-}
-
 Result<> utils::file::createDirectoryAll(ghc::filesystem::path const& path) {
     try {
         if (ghc::filesystem::create_directories(path)) {
diff --git a/loader/src/utils/ios/util.mm b/loader/src/utils/ios/util.mm
index 3f9fe906..91ee0294 100644
--- a/loader/src/utils/ios/util.mm
+++ b/loader/src/utils/ios/util.mm
@@ -1,4 +1,3 @@
-#include <Geode/utils/platform.hpp>
 
 #ifdef GEODE_IS_IOS
 
diff --git a/loader/src/utils/mac/util.mm b/loader/src/utils/mac/util.mm
index dc15bde1..5e2578aa 100644
--- a/loader/src/utils/mac/util.mm
+++ b/loader/src/utils/mac/util.mm
@@ -1,4 +1,3 @@
-#include <Geode/utils/platform.hpp>
 
 #ifdef GEODE_IS_MACOS
 
diff --git a/loader/src/utils/string.cpp b/loader/src/utils/string.cpp
index 0702dced..27a7ecd1 100644
--- a/loader/src/utils/string.cpp
+++ b/loader/src/utils/string.cpp
@@ -1,4 +1,4 @@
-#include <Geode/utils/ext.hpp>
+#include <Geode/utils/string.hpp>
 #include <algorithm>
 
 USE_GEODE_NAMESPACE();
diff --git a/loader/src/utils/version.cpp b/loader/src/utils/version.cpp
index 55f9b6d9..7270fe28 100644
--- a/loader/src/utils/version.cpp
+++ b/loader/src/utils/version.cpp
@@ -3,7 +3,6 @@
 #endif
 
 #include <Geode/utils/VersionInfo.hpp>
-#include <Geode/utils/ext.hpp>
 #include <Geode/utils/general.hpp>
 
 USE_GEODE_NAMESPACE();
diff --git a/loader/src/utils/windows/nfdwin.hpp b/loader/src/utils/windows/nfdwin.hpp
index 7f194586..56537644 100644
--- a/loader/src/utils/windows/nfdwin.hpp
+++ b/loader/src/utils/windows/nfdwin.hpp
@@ -8,7 +8,7 @@
  * Modified to be modern Geode-fitting C++
  */
 
-#include <Geode/utils/platform.hpp>
+#include <Geode/DefaultInclude.hpp>
 
 #ifdef GEODE_IS_WINDOWS
 
diff --git a/loader/src/utils/windows/util.cpp b/loader/src/utils/windows/util.cpp
index 840827f0..47d9dd09 100644
--- a/loader/src/utils/windows/util.cpp
+++ b/loader/src/utils/windows/util.cpp
@@ -1,17 +1,17 @@
-#include <Geode/utils/platform.hpp>
 #include <fs/filesystem.hpp>
 
 #ifdef GEODE_IS_WINDOWS
 
 USE_GEODE_NAMESPACE();
 
-    #include "nfdwin.hpp"
+#include "nfdwin.hpp"
 
-    #include <Windows.h>
-    #include <iostream>
-    #include <shlwapi.h>
-    #include <shobjidl.h>
-    #include <sstream>
+#include <Windows.h>
+#include <iostream>
+#include <shlwapi.h>
+#include <shobjidl.h>
+#include <sstream>
+#include <Geode/utils/web.hpp>
 
 bool utils::clipboard::write(std::string const& data) {
     if (!OpenClipboard(nullptr)) return false;