diff --git a/bindings/Cocos2d.bro b/bindings/Cocos2d.bro
index 0a34c7c6..7a1fba6c 100644
--- a/bindings/Cocos2d.bro
+++ b/bindings/Cocos2d.bro
@@ -737,6 +737,7 @@ class cocos2d::CCRenderTexture {
 	static cocos2d::CCRenderTexture* create(int, int, cocos2d::CCTexture2DPixelFormat) = mac 0x35c720;
 	auto newCCImage(bool) = mac 0x35d7d0;
 	auto saveToFile(char const*) = mac 0x35dab0;
+	auto beginWithClear(float r, float g, float b, float a) = mac 0x35d010;
 }
 
 class cocos2d::CCRepeat {
diff --git a/loader/launcher/mac/Bootstrapper.cpp b/loader/launcher/mac/Bootstrapper.cpp
index 0fbf8642..1b5c727c 100644
--- a/loader/launcher/mac/Bootstrapper.cpp
+++ b/loader/launcher/mac/Bootstrapper.cpp
@@ -1,77 +1,4 @@
-#include <ghc/filesystem.hpp>
-#include <mach-o/dyld.h>
-#include <unistd.h>
-#include <dlfcn.h>
-#include <array>
-#include <iostream>
-#include <CoreFoundation/CoreFoundation.h>
-
-void displayError(std::string alertMessage) {
-    CFStringRef cfTitle = CFStringCreateWithCString(NULL, "Geode Bootstrapper", kCFStringEncodingUTF8);
-    CFStringRef cfMessage = CFStringCreateWithCString(NULL, alertMessage.c_str(), kCFStringEncodingUTF8);
-
-    CFUserNotificationDisplayNotice(0, kCFUserNotificationCautionAlertLevel, NULL, NULL, NULL, cfTitle, cfMessage, NULL);
-}
-
-void loadGeode() {
-    auto dylib = dlopen("Geode.dylib", RTLD_NOW);
-    if (!dylib)  {
-        displayError(std::string("Couldn't load Geode: ") + dlerror());
-        return;
-    }
-    auto trigger = dlsym(dylib, "dynamicTrigger");
-    if (!trigger)  {
-        displayError(std::string("Couldn't start Geode: ") + dlerror());
-        return;
-    }
-    reinterpret_cast<void(*)()>(trigger)();
-    return;
-}
-
+extern "C" void fake();
 __attribute__((constructor)) void _entry() {
-    std::array<char, PATH_MAX> gddir;
-
-    uint32_t out = PATH_MAX;
-    _NSGetExecutablePath(gddir.data(), &out);
-
-    ghc::filesystem::path gdpath = gddir.data();
-    auto workingDir = gdpath.parent_path().parent_path();
-
-    auto updatesDir = workingDir / "geode" / "update";
-    auto libDir = workingDir / "Frameworks";
-	auto resourcesDir = workingDir / "geode" / "resources";
-
-	auto error = std::error_code();
-
-	if (ghc::filesystem::exists(updatesDir / "Geode.dylib", error) && !error) {
-        ghc::filesystem::rename(
-            updatesDir / "Geode.dylib", 
-            libDir / "Geode.dylib", error
-        );
-        if (error) {
-            displayError(std::string("Couldn't update Geode: ") + error.message());
-            return loadGeode();
-        }
-    }
-
-    if (ghc::filesystem::exists(updatesDir / "resources", error) && !error) {
-        ghc::filesystem::remove_all(resourcesDir / "geode.loader", error);
-
-        if (error) {
-            displayError(std::string("Couldn't update Geode resources: ") + error.message());
-            return loadGeode();
-        }
-
-        ghc::filesystem::rename(
-            updatesDir / "resources", 
-            resourcesDir / "geode.loader", error
-        );
-        
-        if (error) {
-            displayError(std::string("Couldn't update Geode resources: ") + error.message());
-            return loadGeode();
-        }
-    }    
-
-    return loadGeode();
+    fake();
 }
\ No newline at end of file
diff --git a/loader/launcher/mac/CMakeLists.txt b/loader/launcher/mac/CMakeLists.txt
index 77c5c678..a2d91df2 100644
--- a/loader/launcher/mac/CMakeLists.txt
+++ b/loader/launcher/mac/CMakeLists.txt
@@ -10,4 +10,13 @@ set_target_properties(Bootstrapper PROPERTIES
 	RUNTIME_OUTPUT_DIRECTORY "${GEODE_BIN_PATH}/nightly"
 )
 
-target_link_libraries(Bootstrapper PRIVATE "-framework CoreFoundation" ghc_filesystem)
+target_link_libraries(Bootstrapper PRIVATE)
+
+add_library(FakeGeode SHARED FakeGeode.cpp)
+target_compile_features(FakeGeode PUBLIC cxx_std_17)
+set_target_properties(FakeGeode PROPERTIES
+	PREFIX "" 
+	OUTPUT_NAME "Geode"
+)
+
+target_link_libraries(Bootstrapper PRIVATE geode-loader)
diff --git a/loader/launcher/mac/FakeGeode.cpp b/loader/launcher/mac/FakeGeode.cpp
new file mode 100644
index 00000000..bceb6542
--- /dev/null
+++ b/loader/launcher/mac/FakeGeode.cpp
@@ -0,0 +1 @@
+extern "C" void fake() {}
\ No newline at end of file
diff --git a/loader/launcher/mac/Updater.cpp b/loader/launcher/mac/Updater.cpp
new file mode 100644
index 00000000..e69de29b
diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp
index 7edb0b24..57cade24 100644
--- a/loader/src/hooks/MenuLayer.cpp
+++ b/loader/src/hooks/MenuLayer.cpp
@@ -198,12 +198,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
 	}
 
 	void onMissingTextures(CCObject*) {
-		static bool shownInfoPopup = false;
-		if (shownInfoPopup) {
-			return this->onGeode(nullptr);
-		}
-		shownInfoPopup = true;
-
+		
 	#ifdef GEODE_IS_DESKTOP
 
 		try {
@@ -218,7 +213,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
 			"and <cy>unzip its contents</c> into <cb>geode/update/resources</c>.\n"
 			"Afterwards, <cg>restart the game</c>.\n"
 			"You may also continue without installing resources, but be aware that "
-			"the game <cr>will crash</c>.",
+			"you won't be able to open <cr>the Geode menu</c>.",
 			"Dismiss", "Open Github",
 			[](auto, bool btn2) {
 				if (btn2) {
diff --git a/loader/src/platform/Objcpp.mm b/loader/src/platform/Objcpp.mm
index 7e3e9216..8e5d54df 100644
--- a/loader/src/platform/Objcpp.mm
+++ b/loader/src/platform/Objcpp.mm
@@ -5,6 +5,7 @@ using namespace geode::prelude;
 
 #if defined(GEODE_IS_MACOS)
 
+#include "mac/main.mm"
 #include "mac/crashlog.mm"
 #include "mac/FileWatcher.mm"
 #include "mac/util.mm"
diff --git a/loader/src/platform/mac/main.cpp b/loader/src/platform/mac/main.cpp
deleted file mode 100644
index c5763ecb..00000000
--- a/loader/src/platform/mac/main.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-#include <Geode/DefaultInclude.hpp>
-
-#if defined(GEODE_IS_MACOS)
-
-#include "../load.hpp"
-#include <dlfcn.h>
-#include <mach-o/dyld.h>
-#include <unistd.h>
-
-#include <thread>
-
-using namespace geode::prelude;
-
-std::length_error::~length_error() _NOEXCEPT {} // do not ask...
-
-// camila has an old ass macos and this function turned
-// from dynamic to static thats why she needs to define it
-// this is what old versions does to a silly girl
-
-void dynamicEntry() {
-    auto dylib = dlopen("GeodeBootstrapper.dylib", RTLD_NOLOAD);
-    dlclose(dylib);
-
-    auto workingDir = dirs::getGameDir();
-    auto libDir = workingDir / "Frameworks";
-    auto updatesDir = workingDir / "geode" / "update";
-
-    auto error = std::error_code();
-
-    if (ghc::filesystem::exists(updatesDir / "GeodeBootstrapper.dylib", error) && !error) {
-        ghc::filesystem::rename(
-            updatesDir / "GeodeBootstrapper.dylib", libDir / "GeodeBootstrapper.dylib", error
-        );
-        if (error) return;
-    }
-
-    geodeEntry(nullptr);
-}
-
-extern "C" __attribute__((visibility("default"))) void dynamicTrigger() {
-    std::thread(&dynamicEntry).detach();
-}
-
-// remove when we can figure out how to not remove it
-auto dynamicTriggerRef = &dynamicTrigger;
-
-#endif
\ No newline at end of file
diff --git a/loader/src/platform/mac/main.mm b/loader/src/platform/mac/main.mm
new file mode 100644
index 00000000..376c7906
--- /dev/null
+++ b/loader/src/platform/mac/main.mm
@@ -0,0 +1,66 @@
+#include <Geode/DefaultInclude.hpp>
+
+#if defined(GEODE_IS_MACOS)
+
+#import <Cocoa/Cocoa.h>
+#include "../load.hpp"
+#include <dlfcn.h>
+#include <mach-o/dyld.h>
+#include <unistd.h>
+#include <tulip/TulipHook.hpp>
+#include <iostream>
+
+#include <thread>
+
+using namespace geode::prelude;
+
+std::length_error::~length_error() _NOEXCEPT {} // do not ask...
+
+// camila has an old ass macos and this function turned
+// from dynamic to static thats why she needs to define it
+// this is what old versions does to a silly girl
+
+extern "C" void fake() {}
+
+void applicationDidFinishLaunchingHook(void* self, SEL sel, NSNotification* notification) {
+    // updateGeode();
+
+    std::array<uint8_t, 6> patchBytes = {
+        0x55,
+        0x48, 0x89, 0xe5,
+        0x41, 0x57
+    };
+
+    auto res = tulip::hook::writeMemory((void*)(base::get() + 0x69a0), patchBytes.data(), 6);
+    if (!res)
+        return;
+    
+    int exitCode = geodeEntry(nullptr);
+    if (exitCode != 0)
+        return;
+
+    return reinterpret_cast<void(*)(void*, SEL, NSNotification*)>(geode::base::get() + 0x69a0)(self, sel, notification);
+}
+
+
+bool loadGeode() {
+    auto detourAddr = reinterpret_cast<uintptr_t>(&applicationDidFinishLaunchingHook) - geode::base::get() - 0x69a5;
+    auto detourAddrPtr = reinterpret_cast<uint8_t*>(&detourAddr);
+
+    std::array<uint8_t, 5> patchBytes = {
+        0xe9, detourAddrPtr[0], detourAddrPtr[1], detourAddrPtr[2], detourAddrPtr[3]
+    };
+
+    auto res = tulip::hook::writeMemory((void*)(base::get() + 0x69a0), patchBytes.data(), 5);
+    if (!res)
+        return false;
+    
+    return true;
+}
+
+__attribute__((constructor)) void _entry() {
+    if (!loadGeode())
+        return;
+}
+
+#endif
\ No newline at end of file