diff --git a/loader/include/Geode/modify/Field.hpp b/loader/include/Geode/modify/Field.hpp
index 4e1ddecb..1f210e22 100644
--- a/loader/include/Geode/modify/Field.hpp
+++ b/loader/include/Geode/modify/Field.hpp
@@ -106,37 +106,6 @@ namespace geode::modifier {
             }
         }
 
-        [[deprecated("Fields are now done using an explicit `Fields` struct. Please refer to https://docs.geode-sdk.org/tutorials/fields/ for more information.")]]
-        auto deprecatedSelf() {
-            // get the this pointer of the base
-            // field intermediate is the first member of Modify
-            // meaning we canget the base from ourself
-            auto node = reinterpret_cast<Parent*>(reinterpret_cast<std::byte*>(this) - sizeof(Base));
-            static_assert(sizeof(Base) == offsetof(Parent, m_fields), "offsetof not correct");
-
-            // generating the container if it doesn't exist
-            auto container = FieldContainer::from(node, typeid(Base).name());
-
-            // the index is global across all mods, so the
-            // function is defined in the loader source
-            static size_t index = getFieldIndexForClass(typeid(Base).name());
-
-            // the fields are actually offset from their original
-            // offset, this is done to save on allocation and space
-            auto offsetField = container->getField(index);
-            if (!offsetField) {
-                offsetField = container->setField(
-                    index, sizeof(Parent) - sizeof(Intermediate), &FieldIntermediate::fieldDestructor
-                );
-
-                FieldIntermediate::fieldConstructor(offsetField);
-            }
-
-            return reinterpret_cast<Parent*>(
-                reinterpret_cast<std::byte*>(offsetField) - sizeof(Intermediate)
-            );
-        }
-
         auto self() {
             if constexpr (HasFields<Parent>) {
                 // get the this pointer of the base
@@ -166,7 +135,7 @@ namespace geode::modifier {
                 return reinterpret_cast<typename Parent::Fields*>(offsetField);
             }
             else {
-                return this->deprecatedSelf();
+                static_assert(!HasFields<Parent>, "Parent must have a Fields struct");
             }
         }
 
diff --git a/loader/src/internal/crashlog.cpp b/loader/src/internal/crashlog.cpp
index 649a27ff..279a73d8 100644
--- a/loader/src/internal/crashlog.cpp
+++ b/loader/src/internal/crashlog.cpp
@@ -20,7 +20,7 @@ std::string crashlog::getDateString(bool filesafe) {
 }
 
 void crashlog::printGeodeInfo(std::stringstream& stream) {
-    stream << "Loader Version: " << Loader::get()->getVersion().toString() << "\n"
+    stream << "Loader Version: " << Loader::get()->getVersion().toVString() << "\n"
            << "Loader Commit: " << about::getLoaderCommitHash() << "\n"
            << "Bindings Commit: " << about::getBindingsCommitHash() << "\n"
            << "Installed mods: " << Loader::get()->getAllMods().size() << "\n"
@@ -47,7 +47,7 @@ void crashlog::printMods(std::stringstream& stream) {
             mod->hasProblems() ? "!"sv : // thank you for this bug report
             mod->shouldLoad() ? "~"sv : 
             " "sv,
-            mod->getVersion().toString(), mod->getID()
+            mod->getVersion().toVString(), mod->getID()
         );
     }
 }
diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp
index 6b6420cc..4ecc8ea8 100644
--- a/loader/src/loader/LoaderImpl.cpp
+++ b/loader/src/loader/LoaderImpl.cpp
@@ -455,8 +455,8 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
                 node,
                 fmt::format(
                     "Geode version {}\nis required to run this mod\n(installed: {})",
-                    node->getMetadata().getGeodeVersion().toString(),
-                    this->getVersion().toString()
+                    node->getMetadata().getGeodeVersion().toVString(),
+                    this->getVersion().toVString()
                 )
             });
             log::error("Unsupported Geode version: {}", node->getMetadata().getGeodeVersion());
diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp
index a70dc032..fea729ad 100644
--- a/loader/src/loader/ModMetadataImpl.cpp
+++ b/loader/src/loader/ModMetadataImpl.cpp
@@ -119,7 +119,7 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
         checkerRoot = fmt::format(
             "[{}/{}/mod.json]",
             rawJson.contains("id") ? rawJson["id"].as_string() : "unknown.mod",
-            rawJson.contains("version") ? rawJson["version"].as<VersionInfo>().toString() : "v0.0.0"
+            rawJson.contains("version") ? rawJson["version"].as<VersionInfo>().toVString() : "v0.0.0"
         );
     }
     catch (...) { }
@@ -349,7 +349,7 @@ Result<ModMetadata> ModMetadata::Impl::create(ModJson const& json) {
     // Handle mod.json data based on target
     if (schema < VersionInfo(0, 1, 0)) {
         return Err(
-            "[mod.json] targets a version (" + schema.toString() +
+            "[mod.json] targets a version (" + schema.toVString() +
             ") that isn't supported by this version (v" +
             about::getLoaderVersionStr() +
             ") of geode. This is probably a bug; report it to "
diff --git a/loader/src/loader/updater.cpp b/loader/src/loader/updater.cpp
index 025d1319..d2af0f79 100644
--- a/loader/src/loader/updater.cpp
+++ b/loader/src/loader/updater.cpp
@@ -56,7 +56,7 @@ void updater::fetchLatestGithubRelease(
         Mod::get()->getSavedValue("latest-version-auto-update-check", std::string("0.0.0"))
     );
 
-    log::debug("Last update check result: {}", version.unwrap().toString());
+    log::debug("Last update check result: {}", version.unwrap().toVString());
 
     std::string modifiedSince;
     if (!force && version && version.unwrap() <= Mod::get()->getVersion() && version.unwrap() != VersionInfo(0, 0, 0)) {
@@ -99,7 +99,7 @@ void updater::fetchLatestGithubRelease(
 }
 
 void updater::downloadLatestLoaderResources() {
-    log::debug("Downloading latest resources", Loader::get()->getVersion().toString());
+    log::debug("Downloading latest resources", Loader::get()->getVersion().toVString());
     fetchLatestGithubRelease(
         [](matjson::Value const& raw) {
             auto json = raw;
@@ -201,7 +201,7 @@ void updater::downloadLoaderResources(bool useLatestRelease) {
     req.userAgent("github_api/1.0");
     RUNNING_REQUESTS.emplace(
         "@downloadLoaderResources",
-        req.get("https://api.github.com/repos/geode-sdk/geode/releases/tags/" + Loader::get()->getVersion().toString()).map(
+        req.get("https://api.github.com/repos/geode-sdk/geode/releases/tags/" + Loader::get()->getVersion().toVString()).map(
         [useLatestRelease](web::WebResponse* response) {
             RUNNING_REQUESTS.erase("@downloadLoaderResources");
             if (response->ok()) {
@@ -227,11 +227,11 @@ void updater::downloadLoaderResources(bool useLatestRelease) {
                 }
             }
             if (useLatestRelease) {
-                log::debug("Loader version {} does not exist, trying to download latest resources", Loader::get()->getVersion().toString());
+                log::debug("Loader version {} does not exist, trying to download latest resources", Loader::get()->getVersion().toVString());
                 downloadLatestLoaderResources();
             }
             else {
-                log::debug("Loader version {} does not exist on GitHub, not downloading the resources", Loader::get()->getVersion().toString());
+                log::debug("Loader version {} does not exist on GitHub, not downloading the resources", Loader::get()->getVersion().toVString());
                 ResourceDownloadEvent(UpdateFinished()).post();
             }
             return *response;
@@ -369,8 +369,8 @@ void updater::checkForLoaderUpdates() {
             VersionInfo ver { 0, 0, 0 };
             root.needs("tag_name").into(ver);
 
-            log::info("Latest version is {}", ver.toString());
-            Mod::get()->setSavedValue("latest-version-auto-update-check", ver.toString());
+            log::info("Latest version is {}", ver.toVString());
+            Mod::get()->setSavedValue("latest-version-auto-update-check", ver.toVString());
 
             // make sure release is newer
             if (ver <= Loader::get()->getVersion()) {
diff --git a/loader/src/platform/windows/util.cpp b/loader/src/platform/windows/util.cpp
index 0b83e8a6..03ece676 100644
--- a/loader/src/platform/windows/util.cpp
+++ b/loader/src/platform/windows/util.cpp
@@ -5,7 +5,7 @@ using namespace geode::prelude;
 #include <Geode/loader/Dirs.hpp>
 #include <Geode/binding/AppDelegate.hpp>
 #include "nfdwin.hpp"
-#include <filesystem>
+#include <ghc/fs_fwd.hpp>
 #include <Windows.h>
 #include <processthreadsapi.h>
 #include <iostream>
@@ -77,7 +77,7 @@ std::string utils::clipboard::read() {
     return text;
 }
 
-bool utils::file::openFolder(std::filesystem::path const& path) {
+bool utils::file::openFolder(ghc::filesystem::path const& path) {
     // mods can (and do) keep CoInitializeEx initialized on the main thread
     // which results in this function just not doing anything
     // which is why we're using a separate thread
@@ -86,9 +86,9 @@ bool utils::file::openFolder(std::filesystem::path const& path) {
     auto thread = std::thread([](auto const& path, bool& success) {
         if (CoInitializeEx(nullptr, COINIT_MULTITHREADED) == S_OK) {
             if (auto id = ILCreateFromPathW(path.wstring().c_str())) {
-                std::filesystem::path selectPath = path / ".";
+                ghc::filesystem::path selectPath = path / ".";
                 std::error_code whatever;
-                if (!std::filesystem::is_directory(path, whatever)) {
+                if (!ghc::filesystem::is_directory(path, whatever)) {
                     selectPath = path;
                 }
                 auto selectEntry = ILCreateFromPathW(selectPath.wstring().c_str());
@@ -104,7 +104,7 @@ bool utils::file::openFolder(std::filesystem::path const& path) {
     return success;
 }
 
-Result<std::filesystem::path> utils::file::pickFile(
+Result<ghc::filesystem::path> utils::file::pickFile(
     file::PickMode mode, file::FilePickOptions const& options
 ) {
     #define TURN_INTO_NFDMODE(mode) \
@@ -117,14 +117,14 @@ Result<std::filesystem::path> utils::file::pickFile(
         TURN_INTO_NFDMODE(OpenFolder);
         default: return Err<std::string>("Unknown open mode");
     }
-    std::filesystem::path path;
+    ghc::filesystem::path path;
     GEODE_UNWRAP(nfdPick(nfdMode, options, &path));
     return Ok(path);
 }
 
 void file::pickFile(
     PickMode mode, FilePickOptions const& options,
-    MiniFunction<void(std::filesystem::path)> callback,
+    MiniFunction<void(ghc::filesystem::path)> callback,
     MiniFunction<void()> failed
 ) {
     auto result = file::pickFile(mode, options);
@@ -137,21 +137,21 @@ void file::pickFile(
         }
     }
 }
-Task<Result<std::filesystem::path>> file::pick(PickMode mode, FilePickOptions const& options) {
-    return Task<Result<std::filesystem::path>>::immediate(std::move(file::pickFile(mode, options)));
+Task<Result<ghc::filesystem::path>> file::pick(PickMode mode, FilePickOptions const& options) {
+    return Task<Result<ghc::filesystem::path>>::immediate(std::move(file::pickFile(mode, options)));
 }
 
-Result<std::vector<std::filesystem::path>> utils::file::pickFiles(
+Result<std::vector<ghc::filesystem::path>> utils::file::pickFiles(
     file::FilePickOptions const& options
 ) {
-    std::vector<std::filesystem::path> paths;
+    std::vector<ghc::filesystem::path> paths;
     GEODE_UNWRAP(nfdPick(NFDMode::OpenFiles, options, &paths));
     return Ok(paths);
 }
 
 void file::pickFiles(
     FilePickOptions const& options,
-    MiniFunction<void(std::vector<std::filesystem::path>)> callback,
+    MiniFunction<void(std::vector<ghc::filesystem::path>)> callback,
     MiniFunction<void()> failed
 ) {
     auto result = file::pickFiles(options);
@@ -164,8 +164,8 @@ void file::pickFiles(
         }
     }
 }
-Task<Result<std::vector<std::filesystem::path>>> file::pickMany(FilePickOptions const& options) {
-    return Task<Result<std::vector<std::filesystem::path>>>::immediate(std::move(file::pickFiles(options)));
+Task<Result<std::vector<ghc::filesystem::path>>> file::pickMany(FilePickOptions const& options) {
+    return Task<Result<std::vector<ghc::filesystem::path>>>::immediate(std::move(file::pickFiles(options)));
 }
 
 void utils::web::openLinkInBrowser(std::string const& url) {
@@ -181,33 +181,33 @@ CCPoint cocos::getMousePos() {
     return ccp(mouse.x, 1.f - mouse.y) * winSize;
 }
 
-std::filesystem::path dirs::getGameDir() {
+ghc::filesystem::path dirs::getGameDir() {
     // only fetch the path once, since ofc it'll never change
     // throughout the execution
     static const auto path = [] {
         std::array<WCHAR, MAX_PATH> buffer;
         GetModuleFileNameW(NULL, buffer.data(), MAX_PATH);
 
-        const std::filesystem::path path(buffer.data());
+        const ghc::filesystem::path path(buffer.data());
         return std::filesystem::weakly_canonical(path.parent_path().wstring()).wstring();
     }();
 
     return path;
 }
 
-std::filesystem::path dirs::getSaveDir() {
+ghc::filesystem::path dirs::getSaveDir() {
     // only fetch the path once, since ofc it'll never change
     // throughout the execution
     static const auto path = [] {
         std::array<WCHAR, MAX_PATH + 1> buffer;
         GetModuleFileNameW(NULL, buffer.data(), MAX_PATH + 1);
 
-        auto executablePath = std::filesystem::path(buffer.data());
+        auto executablePath = ghc::filesystem::path(buffer.data());
         auto executableName = executablePath.filename().wstring();
         executableName = executableName.substr(0, executableName.find_last_of(L"."));
 
         if (SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA, NULL, SHGFP_TYPE_CURRENT, buffer.data()) >= 0) {
-            auto appdataPath = std::filesystem::path(buffer.data());
+            auto appdataPath = ghc::filesystem::path(buffer.data());
             auto savePath = appdataPath / executableName;
 
             if (SHCreateDirectoryExW(NULL, savePath.wstring().c_str(), NULL) >= 0) {
@@ -221,7 +221,7 @@ std::filesystem::path dirs::getSaveDir() {
     return path;
 }
 
-std::filesystem::path dirs::getModRuntimeDir() {
+ghc::filesystem::path dirs::getModRuntimeDir() {
     return dirs::getGeodeDir() / "unzipped";
 }
 
@@ -258,7 +258,7 @@ void geode::utils::game::restart() {
 
     wchar_t buffer[MAX_PATH];
     GetModuleFileNameW(nullptr, buffer, MAX_PATH);
-    const auto gdName = fmt::format("\"{}\"", std::filesystem::path(buffer).filename().string());
+    const auto gdName = fmt::format("\"{}\"", ghc::filesystem::path(buffer).filename().string());
 
     // launch updater
     const auto updaterPath = (workingDir / "GeodeUpdater.exe").string();
diff --git a/loader/src/ui/mods/list/ModItem.cpp b/loader/src/ui/mods/list/ModItem.cpp
index 2632abad..f8ea4c13 100644
--- a/loader/src/ui/mods/list/ModItem.cpp
+++ b/loader/src/ui/mods/list/ModItem.cpp
@@ -55,7 +55,7 @@ bool ModItem::init(ModSource&& source) {
 
     m_versionLabel = CCLabelBMFont::create("", "bigFont.fnt");
     m_versionLabel->setID("version-label");
-    m_versionLabel->setLayoutOptions(AxisLayoutOptions::create()->setMaxScale(.7f));
+    m_versionLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .7f));
     m_titleContainer->addChild(m_versionLabel);
     
     m_infoContainer->addChild(m_titleContainer);
@@ -88,7 +88,7 @@ bool ModItem::init(ModSource&& source) {
         }}
     );
     m_restartRequiredLabel->setID("restart-required-label");
-    m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setMaxScale(.75f));
+    m_restartRequiredLabel->setLayoutOptions(AxisLayoutOptions::create()->setScaleLimits(std::nullopt, .75f));
     m_infoContainer->addChild(m_restartRequiredLabel);
 
     m_downloadBarContainer = CCNode::create();
@@ -347,7 +347,7 @@ void ModItem::updateState() {
     }
     else {
         m_updateBtn->setVisible(false);
-        m_versionLabel->setString(m_source.getMetadata().getVersion().toString().c_str());
+        m_versionLabel->setString(m_source.getMetadata().getVersion().toVString().c_str());
         m_versionLabel->setColor(to3B(ColorProvider::get()->color("mod-list-version-label"_spr)));
     }
     m_viewMenu->updateLayout();
diff --git a/loader/src/ui/mods/popups/ModPopup.cpp b/loader/src/ui/mods/popups/ModPopup.cpp
index f6e483a3..aed7a3b3 100644
--- a/loader/src/ui/mods/popups/ModPopup.cpp
+++ b/loader/src/ui/mods/popups/ModPopup.cpp
@@ -158,7 +158,7 @@ bool ModPopup::setup(ModSource&& src) {
         { "GJ_downloadsIcon_001.png", "Downloads", "downloads", std::nullopt, "stats" },
         { "GJ_timeIcon_001.png", "Released", "release-date", std::nullopt, "stats" },
         { "GJ_timeIcon_001.png", "Updated", "update-date", std::nullopt, "stats" },
-        { "version.png"_spr, "Version", "version", m_source.getMetadata().getVersion().toString(), "client" },
+        { "version.png"_spr, "Version", "version", m_source.getMetadata().getVersion().toVString(), "client" },
         { nullptr, "Checking for updates", "update-check", std::nullopt, "updates" },
     }) {
         auto container = CCNode::create();
@@ -749,7 +749,7 @@ void ModPopup::onCheckUpdates(typename server::ServerRequest<std::optional<serve
                 updatesStat, "Update Found", false,
                 ColorProvider::get()->color3b("mod-list-version-label-updates-available"_spr)
             );
-            this->setStatValue(updatesStat, resolved.value().version.toString());
+            this->setStatValue(updatesStat, resolved.value().version.toVString());
             this->updateState();
         }
         else {
diff --git a/loader/src/utils/VersionInfo.cpp b/loader/src/utils/VersionInfo.cpp
index 308e450c..a2271344 100644
--- a/loader/src/utils/VersionInfo.cpp
+++ b/loader/src/utils/VersionInfo.cpp
@@ -131,7 +131,7 @@ std::string VersionInfo::toNonVString(bool includeTag) const {
 }
 
 std::string geode::format_as(VersionInfo const& version) {
-    return version.toString();
+    return version.toVString();
 }
 
 // ComparableVersionInfo
@@ -182,7 +182,7 @@ std::string ComparableVersionInfo::toString() const {
         case VersionCompare::More: prefix = ">"; break;
         case VersionCompare::Any: return "*";
     }
-    return prefix + m_version.toString();
+    return prefix + m_version.toVString();
 }
 
 std::string geode::format_as(ComparableVersionInfo const& version) {
diff --git a/loader/test/main/main.cpp b/loader/test/main/main.cpp
index 64dba2ef..6603f687 100644
--- a/loader/test/main/main.cpp
+++ b/loader/test/main/main.cpp
@@ -65,10 +65,10 @@ struct $modify(MenuLayer) {
 #include <Geode/modify/GJGarageLayer.hpp>
 
 struct GJGarageLayerTest : Modify<GJGarageLayerTest, GJGarageLayer> {
-    GJGarageLayerTest() : myValue(1907) {}
-
-    int myValue;
-    std::string myString = "yeah have fun finding a better thing for this";
+    struct Fields {
+        int myValue = 1907;
+        std::string myString = "yeah have fun finding a better thing for this";
+    };
 
     bool init() {
         if (!GJGarageLayer::init()) return false;