diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 8597aa34..a0cdd586 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -146,21 +146,13 @@ jobs:
           files: geode-win/XInput9_1_0.dll geode-win/Geode.dll geode-win/GeodeUpdater.exe geode-win/Geode.lib geode-win/Geode.pdb
           dest: geode-${{ steps.ref.outputs.hash }}-win.zip
 
-      # TODO change in 2.0.0
-      - name: Zip Windows Resources
-        uses: vimtor/action-zip@v1.1
-        with:
-          files: geode-win/resources
-          dest: resources-win.zip
-
-      # This is basically a hack because of line endings. Blame windows.
-      - name: Zip MacOS Resources
+      - name: Zip Resources
         uses: vimtor/action-zip@v1.1
         with:
           files: geode-mac/resources
-          dest: resources-mac.zip
+          dest: resources.zip
 
-      - name: Update Nightly Release
+      - name: Update Development Release
         uses: andelf/nightly-release@main
         env:
           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -174,5 +166,4 @@ jobs:
             ./geode-installer-${{ steps.ref.outputs.hash }}-win.exe
             ./geode-${{ steps.ref.outputs.hash }}-mac.zip
             ./geode-${{ steps.ref.outputs.hash }}-win.zip
-            ./resources-win.zip
-            ./resources-mac.zip
+            ./resources.zip
diff --git a/.github/workflows/draft.yml b/.github/workflows/draft.yml
index 34f757d2..d6051f79 100644
--- a/.github/workflows/draft.yml
+++ b/.github/workflows/draft.yml
@@ -28,8 +28,7 @@ jobs:
           mv dev/geode-installer-*-win.exe geode-installer-v${{ steps.ref.outputs.version }}-win.exe
           mv dev/geode-*-mac.zip geode-v${{ steps.ref.outputs.version }}-mac.zip
           mv dev/geode-*-win.zip geode-v${{ steps.ref.outputs.version }}-win.zip
-          mv dev/resources-win.zip resources-win.zip
-          mv dev/resources-mac.zip resources-mac.zip
+          mv dev/resources.zip resources.zip
 
       - name: Create Draft Release
         uses: softprops/action-gh-release@v1
@@ -49,5 +48,4 @@ jobs:
             ./geode-installer-v${{ steps.ref.outputs.version }}-win.exe
             ./geode-v${{ steps.ref.outputs.version }}-mac.zip
             ./geode-v${{ steps.ref.outputs.version }}-win.zip
-            ./resources-win.zip
-            ./resources-mac.zip
+            ./resources.zip
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f454003e..ed989299 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
 # Geode Changelog
 
-## v1.3.2
+## v1.3.4
+ * Implement string setting character filters (cf8fbba)
+ * Update bindings
+
+## v1.3.3
+ * Reunify resources.zip (81de161)
+
+## v1.3.2
  * Fix alignment of some textures (8f39c38)
  * Bring back unknown problems (0663569)
  * Fix some Windows 7 incompatibility (2d2bdd1)
diff --git a/bindings/Cocos2d.bro b/bindings/Cocos2d.bro
index 4d1d2dab..bde65aff 100644
--- a/bindings/Cocos2d.bro
+++ b/bindings/Cocos2d.bro
@@ -121,6 +121,11 @@ class cocos2d::CCClippingNode {
 //	void updateConnected() = win 0xc7fb0;
 //}
 
+[[link(win)]]
+class cocos2d::CCConfiguration {
+	void gatherGPUInfo() = mac 0x2a6e10;
+}
+
 [[link(win)]]
 class cocos2d::CCDelayTime {
 	static cocos2d::CCDelayTime* create(float) = mac 0x1f4380;
@@ -1256,12 +1261,24 @@ class cocos2d {
 	static auto ccDrawSolidRect(cocos2d::CCPoint, cocos2d::CCPoint, cocos2d::_ccColor4F) = mac 0xecf00;
 	static auto ccGLEnableVertexAttribs(unsigned int) = mac 0x1ae740;
 	static auto ccGLBindTexture2D(GLuint) = mac 0x1ae610;
+	static auto ccGLBindTexture2DN(GLuint, GLuint) = mac 0x1ae650;
 	static float ccpDistance(cocos2d::CCPoint const&, cocos2d::CCPoint const&) = mac 0x1aaf90;
 	static void ccDrawPoly(cocos2d::CCPoint const*, unsigned int, bool) = mac 0xed0a0;
 	static void ccDrawColor4B(GLubyte, GLubyte, GLubyte, GLubyte) = mac 0xeddd0;
 	static void CCMessageBox(const char* msg, const char* title) = mac 0xbabc0;
 }
 
+//uintptr_t macNumberOfDraws() {
+//    return geode::base::get() + 0x69ae90;
+//}
+//void ccIncrementGLDraws(int n) {
+//#ifdef GEODE_IS_MACOS
+//    *reinterpret_cast<int*>(macNumberOfDraws()) += n;
+//#else
+//    CC_INCREMENT_GL_DRAWS(n);
+//#endif
+//}
+
 [[link(win)]]
 class DS_Dictionary {
 	DS_Dictionary() = mac 0xbe9a0;
diff --git a/bindings/GeometryDash.bro b/bindings/GeometryDash.bro
index d67e0028..1a4848bb 100644
--- a/bindings/GeometryDash.bro
+++ b/bindings/GeometryDash.bro
@@ -1628,9 +1628,12 @@ class EffectGameObject : GameObject {
     PAD = mac 0x28, win 0x24;
 }
 
-class EndLevelLayer {
+class EndLevelLayer : cocos2d::CCLayer {
     static EndLevelLayer* create() = mac 0x2787d0, win 0x94b50;
 
+    void customSetup() = win 0x94cb0;
+    const char* getCoinString(void* p0) = win 0x96270;
+    const char* getEndText() = win 0x964A0;
     void onMenu(cocos2d::CCObject* sender) = mac 0x27a500, win 0x96c10;
     void onEdit(cocos2d::CCObject* sender) = mac 0x27a640, win 0x96d30;
 }
@@ -4022,6 +4025,7 @@ class LevelInfoLayer : cocos2d::CCLayer, LevelDownloadDelegate, LevelUpdateDeleg
     virtual void levelDownloadFinished(GJGameLevel*) = mac 0x164C00, win 0x1790C0;
     virtual void levelUpdateFinished(GJGameLevel*, UpdateResponse) = mac 0x164E60, win 0x1792B0;
     void showUpdateAlert(UpdateResponse) = mac 0x164ED0, win 0x179300;
+    void updateLabelValues() = mac 0x164090, win 0x17b170;
 
     PAD = win 0x4, mac 0x8;
     cocos2d::CCMenu* m_playBtnMenu;
@@ -4031,13 +4035,13 @@ class LevelInfoLayer : cocos2d::CCLayer, LevelDownloadDelegate, LevelUpdateDeleg
     CCMenuItemSpriteExtra* m_starRateBtn;
     CCMenuItemSpriteExtra* m_demonRateBtn;
     PAD = win 0x4, mac 0x8;
-    CCMenuItemToggler* m_toggler;
-    cocos2d::CCLabelBMFont* m_label0;
-    cocos2d::CCLabelBMFont* m_label1;
-    cocos2d::CCLabelBMFont* m_label2;
-    cocos2d::CCLabelBMFont* m_label3;
-    cocos2d::CCLabelBMFont* m_label4;
-    cocos2d::CCLabelBMFont* m_label5;
+    CCMenuItemToggler* m_ldmToggler;
+    cocos2d::CCLabelBMFont* m_ldmLabel;
+    cocos2d::CCLabelBMFont* m_lengthLabel;
+    cocos2d::CCLabelBMFont* m_downloadsLabel;
+    cocos2d::CCLabelBMFont* m_likesLabel;
+    cocos2d::CCLabelBMFont* m_orbsLabel;
+    cocos2d::CCLabelBMFont* m_folderLabel;
     CCMenuItemSpriteExtra* m_cloneBtn;
     PAD = win 0x4, mac 0x8;
 }
@@ -4089,6 +4093,15 @@ class LevelSettingsDelegate {
     virtual void levelSettingsUpdated() {}
 }
 
+class SecretLayer2 : cocos2d::CCLayer, TextInputDelegate, FLAlertLayerProtocol, DialogDelegate {
+    static SecretLayer2* create() = win 0x21FD70;
+
+    bool init() = win 0x21FE10;
+    bool onSubmit(cocos2d::CCObject*) = win 0x221ac0;
+    void updateSearchLabel(const char* text) = win 0x222FC0;
+    void showCompletedLevel() = win 0x220C10;
+}
+
 class SecretLayer4 : cocos2d::CCLayer, TextInputDelegate, FLAlertLayerProtocol, DialogDelegate {
     static SecretLayer4* create() = mac 0x1ed500;
     static cocos2d::CCScene* scene() = mac 0x1ed4c0;
@@ -5465,7 +5478,7 @@ class Slider : cocos2d::CCLayer {
     SliderTouchLogic* m_touchLogic;
     cocos2d::CCSprite* m_sliderBar;
     cocos2d::CCSprite* m_groove;
-    float m_unknown;
+    float m_width;
     float m_height;
 }
 
@@ -5542,6 +5555,8 @@ class SpeedObject : cocos2d::CCNode {
     float m_somethingToCompare;
     float m_idk3;
     float m_idk4;
+
+    static SpeedObject* create(GameObject*, int, float) = win 0x20DE70;
 }
 
 class SpritePartDelegate {}
diff --git a/loader/include/Geode/loader/Setting.hpp b/loader/include/Geode/loader/Setting.hpp
index fca764c9..5425687a 100644
--- a/loader/include/Geode/loader/Setting.hpp
+++ b/loader/include/Geode/loader/Setting.hpp
@@ -96,6 +96,11 @@ namespace geode {
          */
         std::optional<std::string> match;
 
+        /**
+         * The CCTextInputNode's allowed character filter
+         */
+        std::optional<std::string> filter;
+
         static Result<StringSetting> parse(JsonMaybeObject& obj);
     };
 
diff --git a/loader/include/Geode/utils/cocos.hpp b/loader/include/Geode/utils/cocos.hpp
index d8a66905..edc4fd2e 100644
--- a/loader/include/Geode/utils/cocos.hpp
+++ b/loader/include/Geode/utils/cocos.hpp
@@ -953,7 +953,7 @@ namespace geode::cocos {
             return m_arr ? m_arr->count() : 0;
         }
 
-        T operator[](size_t index) {
+        T* operator[](size_t index) {
             return static_cast<T*>(m_arr->objectAtIndex(index));
         }
 
diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp
index 8b955b53..26ccc097 100644
--- a/loader/src/loader/LoaderImpl.cpp
+++ b/loader/src/loader/LoaderImpl.cpp
@@ -781,7 +781,7 @@ void Loader::Impl::downloadLoaderResources(bool useLatestRelease) {
             .json()
             .then([this](json::Value const& json) {
                 this->tryDownloadLoaderResources(fmt::format(
-                    "https://github.com/geode-sdk/geode/releases/download/{}/resources-" GEODE_PLATFORM_SHORT_IDENTIFIER ".zip",
+                    "https://github.com/geode-sdk/geode/releases/download/{}/resources.zip",
                     this->getVersion().toString()
                 ), true);  
             })
@@ -809,7 +809,7 @@ void Loader::Impl::downloadLoaderResources(bool useLatestRelease) {
                 // find release asset
                 for (auto asset : root.needs("assets").iterate()) {
                     auto obj = asset.obj();
-                    if (obj.needs("name").template get<std::string>() == "resources-" GEODE_PLATFORM_SHORT_IDENTIFIER ".zip") {
+                    if (obj.needs("name").template get<std::string>() == "resources.zip") {
                         this->tryDownloadLoaderResources(
                             obj.needs("browser_download_url").template get<std::string>(),
                             false
diff --git a/loader/src/loader/Setting.cpp b/loader/src/loader/Setting.cpp
index ccce4622..e5dcda72 100644
--- a/loader/src/loader/Setting.cpp
+++ b/loader/src/loader/Setting.cpp
@@ -62,6 +62,7 @@ Result<StringSetting> StringSetting::parse(JsonMaybeObject& obj) {
     StringSetting sett;
     parseCommon(sett, obj);
     obj.has("match").into(sett.match);
+    obj.has("filter").into(sett.filter);
     return Ok(sett);
 }
 
diff --git a/loader/src/ui/internal/settings/GeodeSettingNode.cpp b/loader/src/ui/internal/settings/GeodeSettingNode.cpp
index 70b14542..28a37e1e 100644
--- a/loader/src/ui/internal/settings/GeodeSettingNode.cpp
+++ b/loader/src/ui/internal/settings/GeodeSettingNode.cpp
@@ -324,6 +324,11 @@ bool StringSettingNode::setup(StringSettingValue* setting, float width) {
     m_input = InputNode::create(width / 2 - 10.f, "Text", "chatFont.fnt");
     m_input->setPosition({ -(width / 2 - 70.f) / 2, .0f });
     m_input->setScale(.65f);
+
+    if (setting->castDefinition().filter.has_value()) {
+        m_input->getInput()->setAllowedChars(setting->castDefinition().filter.value());
+    }
+
     m_input->getInput()->setDelegate(this);
     m_menu->addChild(m_input);