diff --git a/loader/launcher/windows/Updater.cpp b/loader/launcher/windows/Updater.cpp
index 37fe9fd5..b6c7d8be 100644
--- a/loader/launcher/windows/Updater.cpp
+++ b/loader/launcher/windows/Updater.cpp
@@ -69,6 +69,14 @@ int main(int argc, char* argv[]) {
 		}
 	}
 
+	if (ghc::filesystem::exists(workingDir / "GeodeBootstrapper.dll", error) && !error) {
+		ghc::filesystem::remove(workingDir / "GeodeBootstrapper.dll", error);
+		if (error) {
+			showError("Unable to update Geode: Unable to delete GeodeBootstrapper.dll - " + error.message());
+			return error.value();
+		}
+	}
+
 	if(argc < 2)
 		return 0;
 
diff --git a/loader/src/main.cpp b/loader/src/main.cpp
index 2c27a728..b14026f0 100644
--- a/loader/src/main.cpp
+++ b/loader/src/main.cpp
@@ -66,14 +66,15 @@ extern "C" [[gnu::visibility("default")]] jint JNI_OnLoad(JavaVM* vm, void* rese
 #elif defined(GEODE_IS_WINDOWS)
     #include <Windows.h>
 
-int WINAPI gdMainHook(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {
+int updateGeode() {
     auto workingDir = dirs::getGameDir();
     auto updatesDir = workingDir / "geode" / "update";
 
     auto error = std::error_code();
 
-    if (ghc::filesystem::exists(updatesDir / "GeodeUpdater.exe", error) && !error) {
-        ghc::filesystem::rename(updatesDir / "GeodeUpdater.exe", workingDir / "GeodeUpdater.exe", error);
+    if (!ghc::filesystem::is_empty(updatesDir, error) && !error) {
+        if (ghc::filesystem::exists(updatesDir / "GeodeUpdater.exe", error) && !error)
+            ghc::filesystem::rename(updatesDir / "GeodeUpdater.exe", workingDir / "GeodeUpdater.exe", error);
         if (error)
             return error.value();
 
@@ -89,9 +90,21 @@ int WINAPI gdMainHook(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmd
         exit(0);
         return 0;
     }
-    int exitCode = geodeEntry(hInstance);
+    if (error)
+        return error.value();
+
+    return 0;
+}
+
+int WINAPI gdMainHook(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow) {
+    int exitCode = updateGeode();
     if (exitCode != 0)
         return exitCode;
+
+    exitCode = geodeEntry(hInstance);
+    if (exitCode != 0)
+        return exitCode;
+
     return reinterpret_cast<decltype(&wWinMain)>(geode::base::get() + 0x260ff8)(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
 }
 
@@ -128,6 +141,25 @@ extern "C" __declspec(dllexport) DWORD WINAPI loadGeode(void* arg) {
 
     return 0;
 }
+
+DWORD WINAPI upgradeThread(void*) {
+    return updateGeode() == 0;
+}
+BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID) {
+    if (reason == DLL_PROCESS_ATTACH) {
+        DisableThreadLibraryCalls(module);
+
+        // if we find the old bootstrapper dll, don't load geode, copy new updater and let it do the rest
+        auto workingDir = dirs::getGameDir();
+        auto error = std::error_code();
+        if (ghc::filesystem::exists(workingDir / "GeodeBootstrapper.dll", error) && !error)
+            CreateThread(nullptr, 0, upgradeThread, nullptr, 0, nullptr);
+        if (error)
+            return FALSE;
+    }
+    return TRUE;
+}
+
 #endif
 
 $execute {