diff --git a/CMakeLists.txt b/CMakeLists.txt
index ebb53660..4491b65c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -83,7 +83,6 @@ add_definitions(-DFMT_CONSTEVAL=)
 
 add_subdirectory(loader/include/Geode/external/filesystem)
 add_subdirectory(loader/include/Geode/external/fmt)
-add_subdirectory(loader/include/Geode/external/scnlib)
 
 target_link_libraries(${PROJECT_NAME} INTERFACE filesystem fmt)
 
diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt
index 74f58d10..033fb860 100644
--- a/loader/CMakeLists.txt
+++ b/loader/CMakeLists.txt
@@ -128,7 +128,7 @@ target_link_libraries(${PROJECT_NAME} md4c)
 
 # Lilac (hooking)
 add_subdirectory(lilac)
-target_link_libraries(${PROJECT_NAME} z lilac_hook geode-sdk scn::scn)
+target_link_libraries(${PROJECT_NAME} z lilac_hook geode-sdk)
 
 # Use precompiled headers for faster builds
 target_precompile_headers(${PROJECT_NAME} PRIVATE
diff --git a/loader/include/Geode/UI.hpp b/loader/include/Geode/UI.hpp
index 1c9c39cd..2330064c 100644
--- a/loader/include/Geode/UI.hpp
+++ b/loader/include/Geode/UI.hpp
@@ -6,7 +6,7 @@
 #include "ui/BasedButtonSprite.hpp"
 #include "ui/IconButtonSprite.hpp"
 #include "ui/InputNode.hpp"
-#include "ui/LayerBG.hpp"
+#include "ui/General.hpp"
 #include "ui/ListView.hpp"
 #include "ui/MDPopup.hpp"
 #include "ui/MDTextArea.hpp"
diff --git a/loader/include/Geode/external/scnlib b/loader/include/Geode/external/scnlib
deleted file mode 160000
index 72a4bab9..00000000
--- a/loader/include/Geode/external/scnlib
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 72a4bab9e32f2b44593137fba40c54710e20a623
diff --git a/loader/include/Geode/ui/LayerBG.hpp b/loader/include/Geode/ui/General.hpp
similarity index 60%
rename from loader/include/Geode/ui/LayerBG.hpp
rename to loader/include/Geode/ui/General.hpp
index 123a4f52..941e2dbf 100644
--- a/loader/include/Geode/ui/LayerBG.hpp
+++ b/loader/include/Geode/ui/General.hpp
@@ -10,4 +10,13 @@ namespace geode {
      * packs the ability to override this function.
      */
     GEODE_DLL cocos2d::CCSprite* createLayerBG();
+
+    /**
+     * Add the rounded comment borders to a node
+     */
+    GEODE_DLL void addListBorders(
+        cocos2d::CCNode* to,
+        cocos2d::CCPoint const& center,
+        cocos2d::CCSize const& size
+    );
 }
diff --git a/loader/include/Geode/utils/VersionInfo.hpp b/loader/include/Geode/utils/VersionInfo.hpp
index 8f6c5468..99183860 100644
--- a/loader/include/Geode/utils/VersionInfo.hpp
+++ b/loader/include/Geode/utils/VersionInfo.hpp
@@ -4,6 +4,7 @@
 #include <string_view>
 #include "../external/json/json.hpp"
 #include <tuple>
+#include "../utils/Result.hpp"
 
 namespace geode {
     enum class VersionCompare {
@@ -55,8 +56,8 @@ namespace geode {
             m_patch = patch;
             m_tag = tag;
         }
-        VersionInfo(std::string const& versionString);
-        static bool validate(std::string const& string);
+        
+        static Result<VersionInfo> parse(std::string const& string);
 
         constexpr size_t getMajor() const {
             return m_major;
@@ -114,8 +115,8 @@ namespace geode {
             VersionInfo const& version,
             VersionCompare const& compare
         ) : m_version(version), m_compare(compare) {}
-        ComparableVersionInfo(std::string const& versionString);
-        static bool validate(std::string const& string);
+
+        static Result<ComparableVersionInfo> parse(std::string const& string);
 
         constexpr bool compare(VersionInfo const& version) const {
             switch (m_compare) {
diff --git a/loader/src/loader/ModInfo.cpp b/loader/src/loader/ModInfo.cpp
index 3dad5578..ec4f51ae 100644
--- a/loader/src/loader/ModInfo.cpp
+++ b/loader/src/loader/ModInfo.cpp
@@ -48,7 +48,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
     using nlohmann::detail::value_t;
 
     root.needs("id").validate(&ModInfo::validateID).into(info.id);
-    root.needs("version").validate(&VersionInfo::validate).into(info.version);
+    root.needs("version").into(info.version);
     root.needs("name").into(info.name);
     root.needs("developer").into(info.developer);
     root.has("description").into(info.description);
@@ -62,9 +62,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
 
         auto depobj = Dependency {};
         obj.needs("id").validate(&ModInfo::validateID).into(depobj.id);
-        obj.needs("version")
-            .validate(&ComparableVersionInfo::validate)
-            .into(depobj.version);
+        obj.needs("version").into(depobj.version);
         obj.has("required").into(depobj.required);
         obj.checkUnknownKeys();
 
@@ -116,16 +114,10 @@ Result<ModInfo> ModInfo::create(ModJson const& json) {
     // Check mod.json target version
     auto schema = LOADER_VERSION;
     if (json.contains("geode") && json["geode"].is_string()) {
-        auto ver = json["geode"];
-        if (VersionInfo::validate(ver)) {
-            schema = VersionInfo(ver);
-        }
-        else {
-            return Err(
-                "[mod.json] has no target loader version "
-                "specified, or it is invalidally formatted (required: \"[v]X.X.X\")!"
-            );
-        }
+        GEODE_UNWRAP_INTO(
+            schema, VersionInfo::parse(json["geode"])
+                .expect("[mod.json] has invalid target loader version: {error}")
+        );
     }
     else {
         return Err(
diff --git a/loader/src/ui/internal/info/DevProfilePopup.cpp b/loader/src/ui/internal/info/DevProfilePopup.cpp
new file mode 100644
index 00000000..3f5285ef
--- /dev/null
+++ b/loader/src/ui/internal/info/DevProfilePopup.cpp
@@ -0,0 +1,55 @@
+#include "DevProfilePopup.hpp"
+#include <Geode/ui/ListView.hpp>
+#include <Geode/loader/Index.hpp>
+#include <Geode/ui/General.hpp>
+#include "../list/ModListCell.hpp"
+#include "../list/ModListLayer.hpp"
+
+bool DevProfilePopup::setup(std::string const& developer) {
+    m_noElasticity = true;
+
+    this->setTitle("Mods by " + developer);
+
+    auto winSize = CCDirector::get()->getWinSize();
+
+    auto items = CCArray::create();
+
+    // installed mods
+    for (auto& mod : Loader::get()->getAllMods()) {
+        if (mod->getDeveloper() == developer) {
+            items->addObject(ModCell::create(
+                mod, nullptr, ModListDisplay::Concise, { 358.f, 40.f }
+            ));
+        }
+    }
+
+    // index mods
+    for (auto& item : Index::get()->getItemsByDeveloper(developer)) {
+        if (Loader::get()->isModInstalled(item->info.id)) {
+            continue;
+        }
+        items->addObject(IndexItemCell::create(
+            item, nullptr, ModListDisplay::Concise, { 358.f, 40.f }
+        ));
+    }
+
+    // mods list
+    auto listSize = CCSize { 358.f, 160.f };
+    auto list = ListView::create(items, 40.f, listSize.width, listSize.height);
+    list->setPosition(winSize / 2 - listSize / 2);
+    m_mainLayer->addChild(list);
+
+    addListBorders(m_mainLayer, winSize / 2, listSize);
+
+    return true;
+}
+
+DevProfilePopup* DevProfilePopup::create(std::string const& developer) {
+    auto ret = new DevProfilePopup();
+    if (ret && ret->init(420.f, 260.f, developer)) {
+        ret->autorelease();
+        return ret;
+    }
+    CC_SAFE_DELETE(ret);
+    return nullptr;
+}
diff --git a/loader/src/ui/internal/info/DevProfilePopup.hpp b/loader/src/ui/internal/info/DevProfilePopup.hpp
new file mode 100644
index 00000000..054cc172
--- /dev/null
+++ b/loader/src/ui/internal/info/DevProfilePopup.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#include <Geode/ui/Popup.hpp>
+
+USE_GEODE_NAMESPACE();
+
+class DevProfilePopup : public Popup<std::string const&> {
+protected:
+    bool setup(std::string const& developer) override;
+
+public:
+    static DevProfilePopup* create(std::string const& developer);
+};
diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp
index 8f02f941..195cde78 100644
--- a/loader/src/ui/internal/info/ModInfoPopup.cpp
+++ b/loader/src/ui/internal/info/ModInfoPopup.cpp
@@ -423,7 +423,9 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
             "need to <cg>restart</c> the game to have it fully unloaded.",
             "OK"
         )->show();
-        if (m_layer) m_layer->updateAllStates(nullptr);
+        if (m_layer) {
+            m_layer->updateAllStates(nullptr);
+        }
         return;
     }
     if (as<CCMenuItemToggler*>(sender)->isToggled()) {
@@ -438,7 +440,9 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
             FLAlertLayer::create(nullptr, "Error Disabling Mod", res.unwrapErr(), "OK", nullptr)->show();
         }
     }
-    if (m_layer) m_layer->updateAllStates(nullptr);
+    if (m_layer) {
+        m_layer->updateAllStates(nullptr);
+    }
     as<CCMenuItemToggler*>(sender)->toggle(m_mod->isEnabled());
 }
 
@@ -477,7 +481,9 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) {
                     FLAlertLayer::create("Error", "Unable to delete mod's save directory!", "OK")->show();
                 }
             }
-            if (m_layer) m_layer->reloadList();
+            if (m_layer) {
+                m_layer->reloadList();
+            }
             this->onClose(nullptr);
         } break;
     }
diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp
index 606f484d..f2fa5687 100644
--- a/loader/src/ui/internal/list/ModListCell.cpp
+++ b/loader/src/ui/internal/list/ModListCell.cpp
@@ -1,7 +1,7 @@
+
 #include "ModListCell.hpp"
 #include "ModListLayer.hpp"
 #include "../info/ModInfoPopup.hpp"
-
 #include <Geode/binding/ButtonSprite.hpp>
 #include <Geode/binding/CCMenuItemSpriteExtra.hpp>
 #include <Geode/binding/CCMenuItemToggler.hpp>
@@ -10,6 +10,7 @@
 #include <Geode/ui/GeodeUI.hpp>
 #include "../../../loader/LoaderImpl.hpp" // how should i include this src/loader/LoaderImpl.hpp
 #include "../info/TagNode.hpp"
+#include "../info/DevProfilePopup.hpp"
 
 template <class T>
 static bool tryOrAlert(Result<T> const& res, char const* title) {
@@ -27,7 +28,11 @@ float ModListCell::getLogoSize() const {
     return m_height / 1.5f;
 }
 
-void ModListCell::setupInfo(ModInfo const& info, bool spaceForTags) {
+void ModListCell::setupInfo(
+    ModInfo const& info,
+    bool spaceForTags,
+    ModListDisplay display
+) {
     m_menu = CCMenu::create();
     m_menu->setPosition(m_width - 40.f, m_height / 2);
     this->addChild(m_menu);
@@ -39,7 +44,7 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForTags) {
     this->addChild(logoSpr);
 
     bool hasDesc =
-        m_layer->getDisplay() == ModListDisplay::Expanded && 
+        display == ModListDisplay::Expanded && 
         info.description.has_value();
 
     auto titleLabel = CCLabelBMFont::create(info.name.c_str(), "bigFont.fnt");
@@ -135,8 +140,7 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForTags) {
 }
 
 void ModListCell::onViewDev(CCObject*) {
-    m_layer->getQuery().developer = this->getDeveloper();
-    m_layer->reloadList();
+    DevProfilePopup::create(this->getDeveloper())->show();
 }
 
 bool ModListCell::init(ModListLayer* list, CCSize const& size) {
@@ -153,10 +157,11 @@ bool ModListCell::init(ModListLayer* list, CCSize const& size) {
 ModCell* ModCell::create(
     Mod* mod,
     ModListLayer* list,
+    ModListDisplay display,
     CCSize const& size
 ) {
     auto ret = new ModCell();
-    if (ret && ret->init(mod, list, size)) {
+    if (ret && ret->init(mod, list, display, size)) {
         return ret;
     }
     CC_SAFE_DELETE(ret);
@@ -173,7 +178,9 @@ void ModCell::onEnable(CCObject* sender) {
             "need to <cg>restart</c> the game to have it fully unloaded.",
             "OK"
         )->show();
-        m_layer->updateAllStates(this);
+        if (m_layer) {
+            m_layer->updateAllStates(this);
+        }
         return;
     }
     if (!as<CCMenuItemToggler*>(sender)->isToggled()) {
@@ -182,7 +189,9 @@ void ModCell::onEnable(CCObject* sender) {
     else {
         tryOrAlert(m_mod->disable(), "Error disabling mod");
     }
-    m_layer->updateAllStates(this);
+    if (m_layer) {
+        m_layer->updateAllStates(this);
+    }
 }
 
 void ModCell::onUnresolvedInfo(CCObject*) {
@@ -220,6 +229,7 @@ void ModCell::updateState() {
 bool ModCell::init(
     Mod* mod,
     ModListLayer* list,
+    ModListDisplay display,
     CCSize const& size
 ) {
     if (!ModListCell::init(list, size))
@@ -227,7 +237,7 @@ bool ModCell::init(
 
     m_mod = mod;
 
-    this->setupInfo(mod->getModInfo(), false);
+    this->setupInfo(mod->getModInfo(), false, display);
 
     auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
     viewSpr->setScale(.65f);
@@ -285,10 +295,11 @@ void IndexItemCell::onInfo(CCObject*) {
 IndexItemCell* IndexItemCell::create(
     IndexItemHandle item,
     ModListLayer* list,
+    ModListDisplay display,
     CCSize const& size
 ) {
     auto ret = new IndexItemCell();
-    if (ret && ret->init(item, list, size)) {
+    if (ret && ret->init(item, list, display, size)) {
         return ret;
     }
     CC_SAFE_DELETE(ret);
@@ -298,6 +309,7 @@ IndexItemCell* IndexItemCell::create(
 bool IndexItemCell::init(
     IndexItemHandle item,
     ModListLayer* list,
+    ModListDisplay display,
     CCSize const& size
 ) {
     if (!ModListCell::init(list, size))
@@ -305,7 +317,7 @@ bool IndexItemCell::init(
 
     m_item = item;
 
-    this->setupInfo(item->info, item->tags.size());
+    this->setupInfo(item->info, item->tags.size(), display);
    
     auto viewSpr = ButtonSprite::create(
         "View", "bigFont.fnt", "GJ_button_01.png", .8f
@@ -384,13 +396,16 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
                 ->show();
         }
         Loader::get()->refreshModsList();
-        m_layer->reloadList();
+        if (m_layer) {
+            m_layer->reloadList();
+        }
     }
 }
 
 bool InvalidGeodeFileCell::init(
     InvalidGeodeFile const& info,
     ModListLayer* list,
+    ModListDisplay display,
     CCSize const& size
 ) {
     if (!ModListCell::init(list, size))
@@ -431,10 +446,11 @@ bool InvalidGeodeFileCell::init(
 InvalidGeodeFileCell* InvalidGeodeFileCell::create(
     InvalidGeodeFile const& file,
     ModListLayer* list,
+    ModListDisplay display,
     CCSize const& size
 ) {
     auto ret = new InvalidGeodeFileCell();
-    if (ret && ret->init(file, list, size)) {
+    if (ret && ret->init(file, list, display, size)) {
         ret->autorelease();
         return ret;
     }
diff --git a/loader/src/ui/internal/list/ModListCell.hpp b/loader/src/ui/internal/list/ModListCell.hpp
index bf521651..783633ed 100644
--- a/loader/src/ui/internal/list/ModListCell.hpp
+++ b/loader/src/ui/internal/list/ModListCell.hpp
@@ -25,7 +25,7 @@ protected:
     CCMenuItemSpriteExtra* m_unresolvedExMark;
 
     bool init(ModListLayer* list, CCSize const& size);
-    void setupInfo(ModInfo const& info, bool spaceForTags);
+    void setupInfo(ModInfo const& info, bool spaceForTags, ModListDisplay display);
     void draw() override;
 
     float getLogoSize() const;
@@ -47,6 +47,7 @@ protected:
     bool init(
         Mod* mod,
         ModListLayer* list,
+        ModListDisplay display,
         CCSize const& size
     );
 
@@ -58,6 +59,7 @@ public:
     static ModCell* create(
         Mod* mod,
         ModListLayer* list,
+        ModListDisplay display,
         CCSize const& size
     );
 
@@ -76,6 +78,7 @@ protected:
     bool init(
         IndexItemHandle item,
         ModListLayer* list,
+        ModListDisplay display,
         CCSize const& size
     );
 
@@ -85,6 +88,7 @@ public:
     static IndexItemCell* create(
         IndexItemHandle item,
         ModListLayer* list,
+        ModListDisplay display,
         CCSize const& size
     );
 
@@ -103,6 +107,7 @@ protected:
     bool init(
         InvalidGeodeFile const& file,
         ModListLayer* list,
+        ModListDisplay display,
         CCSize const& size
     );
 
@@ -113,6 +118,7 @@ public:
     static InvalidGeodeFileCell* create(
         InvalidGeodeFile const& file,
         ModListLayer* list,
+        ModListDisplay display,
         CCSize const& size
     );
 
diff --git a/loader/src/ui/internal/list/ModListLayer.cpp b/loader/src/ui/internal/list/ModListLayer.cpp
index b8648551..5ba9386e 100644
--- a/loader/src/ui/internal/list/ModListLayer.cpp
+++ b/loader/src/ui/internal/list/ModListLayer.cpp
@@ -73,12 +73,9 @@ static std::optional<int> queryMatchKeywords(
 }
 
 static std::optional<int> queryMatch(ModListQuery const& query, Mod* mod) {
-    // Only checking keywords and developer makes sense for mods since their 
+    // Only checking keywords makes sense for mods since their 
     // platform always matches, they are always visible and they don't 
     // currently list their tags
-    if (query.developer && query.developer.value() != mod->getDeveloper()) {
-        return std::nullopt;
-    }
     return queryMatchKeywords(query, mod->getModInfo());
 }
 
@@ -88,10 +85,6 @@ static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle
     if (!query.forceVisibility && Loader::get()->isModInstalled(item->info.id)) {
         return std::nullopt;
     }
-    // make sure developer matches
-    if (query.developer && query.developer.value() != item->info.developer) {
-        return std::nullopt;
-    }
     // make sure all tags match
     for (auto& tag : query.tags) {
         if (!item->tags.count(tag)) {
@@ -127,7 +120,6 @@ static std::optional<int> queryMatch(ModListQuery const& query, IndexItemHandle
 static std::optional<int> queryMatch(ModListQuery const& query, InvalidGeodeFile const& info) {
     // if any explicit filters were provided, no match
     if (
-        query.developer.has_value() ||
         query.tags.size() ||
         query.keywords.has_value()
     ) {
@@ -144,7 +136,9 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
             // failed mods first
             for (auto const& mod : Loader::get()->getFailedMods()) {
                 if (!queryMatch(query, mod)) continue;
-                mods->addObject(InvalidGeodeFileCell::create(mod, this, this->getCellSize()));
+                mods->addObject(InvalidGeodeFileCell::create(
+                    mod, this, m_display, this->getCellSize()
+                ));
             }
 
             // sort the mods by match score 
@@ -173,7 +167,9 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
 
             // add the mods sorted
             for (auto& [score, mod] : ranges::reverse(sorted)) {
-                mods->addObject(ModCell::create(mod, this, this->getCellSize()));
+                mods->addObject(ModCell::create(
+                    mod, this, m_display, this->getCellSize()
+                ));
             }
         } break;
 
@@ -189,7 +185,9 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
 
             // add the mods sorted
             for (auto& [score, item] : ranges::reverse(sorted)) {
-                mods->addObject(IndexItemCell::create(item, this, this->getCellSize()));
+                mods->addObject(IndexItemCell::create(
+                    item, this, m_display, this->getCellSize()
+                ));
             }
         } break;
 
@@ -205,7 +203,9 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
 
             // add the mods sorted
             for (auto& [score, item] : ranges::reverse(sorted)) {
-                mods->addObject(IndexItemCell::create(item, this, this->getCellSize()));
+                mods->addObject(IndexItemCell::create(
+                    item, this, m_display, this->getCellSize()
+                ));
             }
         } break;
     }
@@ -489,8 +489,7 @@ void ModListLayer::reloadList(std::optional<ModListQuery> const& query) {
     // and show visual indicator if so
     auto hasQuery =
         (m_searchInput->getString() &&
-        strlen(m_searchInput->getString())) ||
-        m_query.developer;
+        strlen(m_searchInput->getString()));
     m_searchBtn->setVisible(!hasQuery);
     m_searchClearBtn->setVisible(hasQuery);
 
@@ -606,8 +605,6 @@ void ModListLayer::onOpenFolder(CCObject*) {
 }
 
 void ModListLayer::onResetSearch(CCObject*) {
-    // todo: remove when implementing more reasonable developer view
-    m_query.developer = std::nullopt;
     m_searchInput->setString("");
 }
 
diff --git a/loader/src/ui/internal/list/ModListLayer.hpp b/loader/src/ui/internal/list/ModListLayer.hpp
index 323ea6a3..b78d3b98 100644
--- a/loader/src/ui/internal/list/ModListLayer.hpp
+++ b/loader/src/ui/internal/list/ModListLayer.hpp
@@ -34,10 +34,6 @@ struct ModListQuery {
      */
     std::unordered_set<PlatformID> platforms = { GEODE_PLATFORM_TARGET };
     std::unordered_set<std::string> tags;
-    /**
-     * Used to filter by dev if you click their name
-     */
-    std::optional<std::string> developer;
 };
 
 class ModListLayer : public CCLayer, public TextInputDelegate {
diff --git a/loader/src/ui/internal/settings/ModSettingsPopup.cpp b/loader/src/ui/internal/settings/ModSettingsPopup.cpp
index 55873826..931572fc 100644
--- a/loader/src/ui/internal/settings/ModSettingsPopup.cpp
+++ b/loader/src/ui/internal/settings/ModSettingsPopup.cpp
@@ -5,6 +5,7 @@
 #include <Geode/loader/Setting.hpp>
 #include <Geode/ui/ScrollLayer.hpp>
 #include <Geode/utils/cocos.hpp>
+#include <Geode/ui/General.hpp>
 
 bool ModSettingsPopup::setup(Mod* mod) {
     m_noElasticity = true;
@@ -77,29 +78,7 @@ bool ModSettingsPopup::setup(Mod* mod) {
 
     // layer borders
 
-    auto layerTopSpr = CCSprite::createWithSpriteFrameName("GJ_commentTop_001.png");
-    layerTopSpr->setPosition({ winSize.width / 2, winSize.height / 2 + layerSize.height / 2 - 5.f }
-    );
-    m_mainLayer->addChild(layerTopSpr);
-
-    auto layerBottomSpr = CCSprite::createWithSpriteFrameName("GJ_commentTop_001.png");
-    layerBottomSpr->setFlipY(true);
-    layerBottomSpr->setPosition({ winSize.width / 2,
-                                  winSize.height / 2 - layerSize.height / 2 + 5.f });
-    m_mainLayer->addChild(layerBottomSpr);
-
-    auto layerLeftSpr = CCSprite::createWithSpriteFrameName("GJ_commentSide_001.png");
-    layerLeftSpr->setScaleY(6.3f);
-    layerLeftSpr->setPosition({ winSize.width / 2 - layerSize.width / 2 - .5f, winSize.height / 2 }
-    );
-    m_mainLayer->addChild(layerLeftSpr);
-
-    auto layerRightSpr = CCSprite::createWithSpriteFrameName("GJ_commentSide_001.png");
-    layerRightSpr->setScaleY(6.3f);
-    layerRightSpr->setFlipX(true);
-    layerRightSpr->setPosition({ winSize.width / 2 + layerSize.width / 2 + .5f, winSize.height / 2 }
-    );
-    m_mainLayer->addChild(layerRightSpr);
+    addListBorders(m_mainLayer, winSize / 2, layerSize);
 
     // buttons
 
diff --git a/loader/src/ui/nodes/General.cpp b/loader/src/ui/nodes/General.cpp
new file mode 100644
index 00000000..b73b9423
--- /dev/null
+++ b/loader/src/ui/nodes/General.cpp
@@ -0,0 +1,91 @@
+#include <Geode/ui/General.hpp>
+#include <cocos-ext.h>
+
+USE_GEODE_NAMESPACE();
+
+CCSprite* geode::createLayerBG() {
+    auto winSize = CCDirector::get()->getWinSize();
+
+    auto bg = CCSprite::create("GJ_gradientBG.png");
+    auto bgSize = bg->getTextureRect().size;
+
+    bg->setAnchorPoint({ 0.0f, 0.0f });
+    bg->setScaleX((winSize.width + 10.0f) / bgSize.width);
+    bg->setScaleY((winSize.height + 10.0f) / bgSize.height);
+    bg->setPosition({ -5.0f, -5.0f });
+    bg->setColor({ 0, 102, 255 }); // todo: let mods customize this
+
+    return bg;
+}
+
+void geode::addListBorders(CCNode* to, CCPoint const& center, CCSize const& size) {
+    // if the size is 346.f, the top aligns perfectly by default :3
+    if (size.width == 346.f) {
+        auto layerTopSpr = CCSprite::createWithSpriteFrameName("GJ_commentTop_001.png");
+        layerTopSpr->setPosition({
+            center.x,
+            center.y + size.height / 2 - 5.f
+        });
+        to->addChild(layerTopSpr);
+
+        auto layerBottomSpr = CCSprite::createWithSpriteFrameName("GJ_commentTop_001.png");
+        layerBottomSpr->setFlipY(true);
+        layerBottomSpr->setPosition({
+            center.x,
+            center.y - size.height / 2 + 5.f
+        });
+        to->addChild(layerBottomSpr);
+    }
+    // otherwise stretch using CCScale9Sprite
+    else {
+        auto layerTopSpr = CCScale9Sprite::createWithSpriteFrameName(
+            "GJ_commentTop_001.png",
+            { 0, 0, 240, 20 }
+        );
+        layerTopSpr->setContentSize({
+            size.width + 9.f,
+            layerTopSpr->getContentSize().height,
+        });
+        layerTopSpr->setPosition({
+            center.x,
+            center.y + size.height / 2 - 5.f
+        });
+        to->addChild(layerTopSpr);
+
+        auto layerBottomSpr = CCScale9Sprite::createWithSpriteFrameName(
+            "GJ_commentTop_001.png",
+            { 0, 0, 240, 20 }
+        );
+        layerBottomSpr->setScaleY(-1);
+        layerBottomSpr->setContentSize({
+            size.width + 9.f,
+            layerBottomSpr->getContentSize().height,
+        });
+        layerBottomSpr->setPosition({
+            center.x,
+            center.y - size.height / 2 + 5.f
+        });
+        to->addChild(layerBottomSpr);
+    }
+
+    auto layerLeftSpr = CCSprite::createWithSpriteFrameName("GJ_commentSide_001.png");
+    layerLeftSpr->setScaleY(
+        (size.height - 30.f) / layerLeftSpr->getScaledContentSize().height
+    );
+    layerLeftSpr->setPosition({
+        center.x - size.width / 2 - .5f,
+        center.y
+    });
+    to->addChild(layerLeftSpr);
+
+    auto layerRightSpr = CCSprite::createWithSpriteFrameName("GJ_commentSide_001.png");
+    layerRightSpr->setScaleY(
+        (size.height - 30.f) / layerRightSpr->getScaledContentSize().height
+    );
+    layerRightSpr->setFlipX(true);
+    layerRightSpr->setPosition({
+        center.x + size.width / 2 + .5f,
+        center.y
+    });
+    to->addChild(layerRightSpr);
+}
diff --git a/loader/src/ui/nodes/LayerBG.cpp b/loader/src/ui/nodes/LayerBG.cpp
deleted file mode 100644
index 18b6f326..00000000
--- a/loader/src/ui/nodes/LayerBG.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-#include <Geode/ui/LayerBG.hpp>
-
-USE_GEODE_NAMESPACE();
-
-CCSprite* geode::createLayerBG() {
-    auto winSize = CCDirector::get()->getWinSize();
-
-    auto bg = CCSprite::create("GJ_gradientBG.png");
-    auto bgSize = bg->getTextureRect().size;
-
-    bg->setAnchorPoint({ 0.0f, 0.0f });
-    bg->setScaleX((winSize.width + 10.0f) / bgSize.width);
-    bg->setScaleY((winSize.height + 10.0f) / bgSize.height);
-    bg->setPosition({ -5.0f, -5.0f });
-    bg->setColor({ 0, 102, 255 }); // todo: let mods customize this
-
-    return bg;
-}
diff --git a/loader/src/utils/VersionInfo.cpp b/loader/src/utils/VersionInfo.cpp
index b8b5dd61..c564a737 100644
--- a/loader/src/utils/VersionInfo.cpp
+++ b/loader/src/utils/VersionInfo.cpp
@@ -4,16 +4,9 @@
 
 #include <Geode/utils/VersionInfo.hpp>
 #include <Geode/utils/general.hpp>
-#include <Geode/external/scnlib/include/scn/scn.h>
 
 USE_GEODE_NAMESPACE();
 
-#ifdef GEODE_IS_WINDOWS
-    #define GEODE_SSCANF sscanf_s
-#else
-    #define GEODE_SSCANF sscanf
-#endif
-
 // VersionTag
 
 std::optional<VersionTag> geode::versionTagFromString(std::string const& str) {
@@ -45,37 +38,57 @@ std::string geode::versionTagToString(VersionTag tag) {
 
 // VersionInfo
 
-bool VersionInfo::validate(std::string const& string) {
-    std::string copy = string;
-    if (copy.starts_with("v")) {
-        copy = string.substr(1);
-    }
-    
-    int buf0, buf1, buf2;
-    std::string bufT;
-    if (scn::scan(copy, "{}.{}.{}-{}", buf0, buf1, buf2, bufT)) {
-        return versionTagFromString(bufT).has_value();
-    }
-    if (scn::scan(copy, "{}.{}.{}", buf0, buf1, buf2)) {
-        return true;
+Result<VersionInfo> VersionInfo::parse(std::string const& string) {
+    std::stringstream str (string);
+
+    // allow leading v
+    if (str.peek() == 'v') {
+        str.get();
     }
 
-    return false;
-}
-
-VersionInfo::VersionInfo(std::string const& string) {
-    std::string copy = string;
-    if (copy.starts_with("v")) {
-        copy = string.substr(1);
+    size_t major;
+    str >> major;
+    if (str.fail()) {
+        return Err("Unable to parse major");
     }
-    std::string tag;
-    scn::scan(copy, "{}.{}.{}-{}", m_major, m_minor, m_patch, tag) ||
-    scn::scan(copy, "{}.{}.{}", m_major, m_minor, m_patch);
-    if (tag.size()) {
-        if (auto t = versionTagFromString(tag)) {
-            m_tag = t;
+
+    if (str.get() != '.') {
+        return Err("Minor version missing");
+    }
+
+    size_t minor;
+    str >> minor;
+    if (str.fail()) {
+        return Err("Unable to parse minor");
+    }
+
+    if (str.get() != '.') {
+        return Err("Patch version missing");
+    }
+
+    size_t patch;
+    str >> patch;
+    if (str.fail()) {
+        return Err("Unable to parse patch");
+    }
+
+    // tag
+    std::optional<VersionTag> tag;
+    if (str.peek() == '-') {
+        str.get();
+        std::string iden;
+        str >> iden;
+        if (str.fail()) {
+            return Err("Unable to parse tag");
+        }
+        if (auto t = versionTagFromString(iden)) {
+            tag = t;
+        }
+        else {
+            return Err("Invalid tag \"" + iden + "\"");
         }
     }
+    return Ok(VersionInfo(major, minor, patch, tag));
 }
 
 std::string VersionInfo::toString(bool includeTag) const {
@@ -94,7 +107,13 @@ void geode::to_json(nlohmann::json& json, VersionInfo const& info) {
 }
 
 void geode::from_json(nlohmann::json const& json, VersionInfo& info) {
-    info = VersionInfo(json.template get<std::string>());
+    auto ver = VersionInfo::parse(json.template get<std::string>());
+    if (!ver) {
+        throw nlohmann::json::type_error::create(
+            0, "Invalid version format: " + ver.unwrapErr(), json
+        );
+    }
+    info = ver.unwrap();
 }
 
 std::ostream& geode::operator<<(std::ostream& stream, VersionInfo const& version) {
@@ -103,35 +122,23 @@ std::ostream& geode::operator<<(std::ostream& stream, VersionInfo const& version
 
 // ComparableVersionInfo
 
-ComparableVersionInfo::ComparableVersionInfo(std::string const& rawStr) {
-    auto version = rawStr;
-    if (version.starts_with("<=")) {
-        m_compare = VersionCompare::LessEq;
-        version.erase(0, 2);
+Result<ComparableVersionInfo> ComparableVersionInfo::parse(std::string const& rawStr) {
+    VersionCompare compare;
+    auto string = rawStr;
+    if (string.starts_with("<=")) {
+        compare = VersionCompare::LessEq;
+        string.erase(0, 2);
     }
-    else if (version.starts_with(">=")) {
-        m_compare = VersionCompare::MoreEq;
-        version.erase(0, 2);
+    else if (string.starts_with(">=")) {
+        compare = VersionCompare::MoreEq;
+        string.erase(0, 2);
     }
-    else if (version.starts_with("==")) {
-        m_compare = VersionCompare::Exact;
-        version.erase(0, 2);
+    else if (string.starts_with("==")) {
+        compare = VersionCompare::Exact;
+        string.erase(0, 2);
     }
-    m_version = VersionInfo(version);
-}
-
-bool ComparableVersionInfo::validate(std::string const& rawStr) {
-    auto version = rawStr;
-    // remove prefix
-    if (
-        version.starts_with("<=") ||
-        version.starts_with(">=") ||
-        version.starts_with("==")
-    ) {
-        version.erase(0, 2);
-    }
-    // otherwise there's no prefix or it's invalid
-    return VersionInfo::validate(version);
+    GEODE_UNWRAP_INTO(auto version, VersionInfo::parse(string));
+    return Ok(ComparableVersionInfo(version, compare));
 }
 
 std::string ComparableVersionInfo::toString() const {
@@ -149,7 +156,13 @@ void geode::to_json(nlohmann::json& json, ComparableVersionInfo const& info) {
 }
 
 void geode::from_json(nlohmann::json const& json, ComparableVersionInfo& info) {
-    info = ComparableVersionInfo(json.template get<std::string>());
+    auto ver = ComparableVersionInfo::parse(json.template get<std::string>());
+    if (!ver) {
+        throw nlohmann::json::type_error::create(
+            0, "Invalid version format: " + ver.unwrapErr(), json
+        );
+    }
+    info = ver.unwrap();
 }
 
 std::ostream& geode::operator<<(std::ostream& stream, ComparableVersionInfo const& version) {