From f5fc0861c76e18f58e0cd3122dc7cf763f346e8b Mon Sep 17 00:00:00 2001 From: Nico Meister Date: Mon, 19 Oct 2026 21:13:44 +0200 Subject: [PATCH] =?UTF-8?q?v3.0.0:=20Umschalten=20wartet=20nicht=20mehr=20?= =?UTF-8?q?aufs=20Auffrischen=20(abbrechbar,=20CA-Abruf=20zuletzt),=20schn?= =?UTF-8?q?ellere=20Pr=C3=BCfintervalle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- Makefile | 2 +- source/admin.c | 2 +- source/admin.h | 2 +- source/frdswitch.c | 10 +++--- source/main.c | 78 +++++++++++++++++++++++++++++++++++----------- source/olvpatch.c | 49 ++++++++++++++++++++++------- source/olvpatch.h | 8 +++-- source/patcher.c | 8 ++--- source/patcher.h | 3 -- source/update.c | 2 +- source/update.h | 2 +- 11 files changed, 113 insertions(+), 53 deletions(-) diff --git a/Makefile b/Makefile index aba979e..8eac5cd 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ SOURCES := source INCLUDES := source # Bei jedem Release erhöhen - das Auto-Update vergleicht diese Nummer -VERSION := 2.9.9 +VERSION := 3.0.0 APP_TITLE := MBMNET APP_DESCRIPTION := Dein eigenes 3DS-Netzwerk diff --git a/source/admin.c b/source/admin.c index 6ddf07b..07fd9ee 100644 --- a/source/admin.c +++ b/source/admin.c @@ -52,7 +52,7 @@ bool admin_is(void) { return isAdmin; } bool admin_finish(void) { if (!thread) return true; - if (R_FAILED(threadJoin(thread, 300ULL * 1000 * 1000))) return false; // läuft noch + if (R_FAILED(threadJoin(thread, 100ULL * 1000 * 1000))) return false; // läuft noch threadFree(thread); thread = NULL; return true; diff --git a/source/admin.h b/source/admin.h index 501a240..f876673 100644 --- a/source/admin.h +++ b/source/admin.h @@ -6,4 +6,4 @@ void admin_start(const char *server, u32 pid); // Admin? (gemerkte Antwort, bis der Server geantwortet hat) bool admin_is(void); -bool admin_finish(void); // wartet höchstens 0,3 s, false = läuft noch +bool admin_finish(void); // wartet höchstens 0,1 s, false = läuft noch diff --git a/source/frdswitch.c b/source/frdswitch.c index a5c4d34..e2fba8e 100644 --- a/source/frdswitch.c +++ b/source/frdswitch.c @@ -92,20 +92,20 @@ static Result unload(void) Result rc = step("Offline gehen (exklusiv)", NDMU_EnterExclusiveState(NDM_EXCLUSIVE_STATE_LOCAL_COMMUNICATIONS)); if (R_FAILED(rc)) return rc; - for (int i = 0; i < 150; i++) { // höchstens 15 Sekunden + for (int i = 0; i < 1500; i++) { // höchstens 15 Sekunden, alle 10 ms nachsehen bool online = true; if (R_FAILED(FRD_IsOnline(&online)) || !online) break; - svcSleepThread(100 * 1000 * 1000); + svcSleepThread(10 * 1000 * 1000); } // Hängt der Freunde-Dienst noch an einer Anfrage, kurz wiederholen - for (int attempt = 1; attempt <= 40; attempt++) { - if (attempt == 21) note("Freunde-Dienst erneut abmelden", FRD_Logout()); + for (int attempt = 1; attempt <= 200; attempt++) { // alle 100 ms, höchstens 20 s + if (attempt == 100) note("Freunde-Dienst erneut abmelden", FRD_Logout()); rc = FRDA_UnloadLocalAccount(); if (R_SUCCEEDED(rc)) { if (logf) fprintf(logf, "%-36s %08lX (Versuch %d)\n", "Freundekonto abmelden", (unsigned long)rc, attempt); return rc; } - svcSleepThread(500 * 1000 * 1000); + svcSleepThread(100 * 1000 * 1000); } return step("Freundekonto abmelden (20 s)", rc); } diff --git a/source/main.c b/source/main.c index b80e574..b462ab7 100644 --- a/source/main.c +++ b/source/main.c @@ -18,6 +18,7 @@ #include "ftpctl.h" #include "spotpass.h" #include "shopctl.h" +#include "olvpatch.h" #define TOP_W 400 #define BOT_W 320 @@ -271,32 +272,65 @@ static void draw_bottom(int selected, PatchState st, bool admin) #define UNLINKED_MARKER "sdmc:/3ds/mbmnet/nnid-geloest" static bool old_mbmnet_link(void) { FILE *f = fopen(UNLINKED_MARKER, "r"); if (f) fclose(f); return f != NULL; } -// Auffrischen beim Start (SpotPass anmelden, Patches neu bauen) läuft im Hintergrund: Dabei holt die App -// das Miiverse-Zertifikat vom Server, und solange das WLAN noch verbindet, dauert das Sekunden. -static volatile bool maintaining; +// Auffrischen beim Start läuft im Hintergrund, in Schritten. Tippt man auf Umschalten, bricht es ab, wo es kann: +// Das Umschalten wartet nur, solange Patch-Dateien geschrieben werden oder der Konto-Dienst beschäftigt ist. +static volatile bool maintaining; // Thread läuft noch (evtl. nur noch das Zertifikat vom Server holen) +static volatile bool maintenanceBusy; // schreibt Patches / gleicht das Konto ab: Umschalten muss warten +static volatile bool switchRequested; // jemand will umschalten: nichts Neues mehr anfangen static volatile bool maintenanceChanged; static Thread maintenanceThread; +// Erst "beschäftigt" melden, dann nachsehen, ob umgeschaltet werden soll (sonst könnten beide gleichzeitig schreiben) +static bool begin_busy(void) +{ + maintenanceBusy = true; + if (!switchRequested) return true; + maintenanceBusy = false; + return false; +} + +static bool refresh_patches(void) +{ + char detail[160]; + bool changed = false; + if (!begin_busy()) return false; + if (patch_enable(MBMNET_SERVER, &changed, detail, sizeof(detail)) && changed) maintenanceChanged = true; + maintenanceBusy = false; + return changed; +} + static void maintenance_worker(void *arg) { (void)arg; - char ignore[96], detail[160]; - spotpass_register(MBMNET_SERVER, ignore, sizeof(ignore)); - // Konto mit dem MBMNET-Server abgleichen: holt u. a. den Mii, den man auf der Webseite eingestellt hat + char ignore[96]; + // 1. Patches auffrischen (Miiverse-CA aus dem Zwischenspeicher; nur beim allerersten Mal vom Server) + bool hadCa = olvpatch_ca_cached(); + refresh_patches(); + // 2. SpotPass-Aufgabe anmelden + if (begin_busy()) { + spotpass_register(MBMNET_SERVER, ignore, sizeof(ignore)); + maintenanceBusy = false; + } + // 3. Konto mit dem MBMNET-Server abgleichen (holt u. a. den Mii von der Webseite). Höchstens 5 s, + // bei einem Umschalt-Wunsch sofort abbrechen. u8 slot = frdswitch_slot_of(FRIEND_ACCOUNT_MBMNET); char id[20]; - if (slot && frdswitch_slot_linked(FRIEND_ACCOUNT_MBMNET, id, sizeof(id))) { - Handle done; - if (R_SUCCEEDED(svcCreateEvent(&done, RESET_ONESHOT))) { - if (R_SUCCEEDED(ACTA_SyncAccountInfo(slot, done))) svcWaitSynchronization(done, 15ULL * 1000 * 1000 * 1000); - svcCloseHandle(done); + Handle done; + if (slot && frdswitch_slot_linked(FRIEND_ACCOUNT_MBMNET, id, sizeof(id)) && begin_busy() + && R_SUCCEEDED(svcCreateEvent(&done, RESET_ONESHOT))) { + if (R_SUCCEEDED(ACTA_SyncAccountInfo(slot, done))) { + // Warten in 100-ms-Schritten (eine Zeitüberschreitung ist kein Fehlercode, nur != 0) + for (int i = 0; i < 50 && svcWaitSynchronization(done, 100ULL * 1000 * 1000) != 0; i++) { + if (switchRequested) { ACT_Cancel(); svcWaitSynchronization(done, 2ULL * 1000 * 1000 * 1000); break; } + } } + svcCloseHandle(done); } - bool changed = false; - patchFreshCa = true; // im Hintergrund darf es dauern: Miiverse-CA frisch vom Server - // Patches auffrischen: neue Versionen bringen neue Ziele mit (z. B. Miiverse ab v2.8.0) - if (patch_enable(MBMNET_SERVER, &changed, detail, sizeof(detail)) && changed) maintenanceChanged = true; - patchFreshCa = false; + maintenanceBusy = false; + // 4. Miiverse-CA frisch vom Server (nur Netz; Patches nur neu bauen, wenn sie sich geändert hat) + bool caChanged = false; + if (!switchRequested && olvpatch_refresh_ca(MBMNET_SERVER, &caChanged) && caChanged && hadCa) + refresh_patches(); // prüft selbst noch einmal, ob umgeschaltet werden soll maintaining = false; } @@ -621,10 +655,14 @@ int main(int argc, char **argv) // Umschalten & Co. warten, bis das Auffrischen im Hintergrund durch ist (meist nur einen Moment) bool waitForMaintenance = maintaining && queued >= 0 && queued != BTN_FTP && queued != BTN_SPOTPASS && queued != BTN_SHOP && queued != BTN_SHOP_DEL; + if (waitForMaintenance) { + switchRequested = true; // Auffrischen: nichts Neues mehr anfangen + waitForMaintenance = maintenanceBusy; // nur warten, solange es Dateien schreibt / das Konto abgleicht + } if (queued >= 0 && !waitForMaintenance) { int a = queued; queued = -1; - if (run(a, msg, sizeof(msg))) rebootIn = 45; // ca. 0,75 Sekunden zum Lesen + if (run(a, msg, sizeof(msg))) rebootIn = 15; // ca. 0,25 Sekunden zum Lesen st = patch_state(); } else if (action >= 0 && rebootIn == 0 && queued < 0) { queued = action; @@ -695,8 +733,10 @@ int main(int argc, char **argv) // ist es ohnehin schon fertig, Aktionen warten darauf). bool clean = true; if (maintenanceThread) { - if (R_SUCCEEDED(threadJoin(maintenanceThread, 20ULL * 1000 * 1000 * 1000))) threadFree(maintenanceThread); - else clean = false; + switchRequested = true; + for (int i = 0; i < 2000 && maintenanceBusy; i++) svcSleepThread(10ULL * 1000 * 1000); // höchstens 20 s + if (R_SUCCEEDED(threadJoin(maintenanceThread, 100ULL * 1000 * 1000))) threadFree(maintenanceThread); + else clean = false; // holt nur noch das Zertifikat: hart beenden ist ungefährlich (atomar geschrieben) } clean = admin_finish() && clean; clean = update_finish() && clean; diff --git a/source/olvpatch.c b/source/olvpatch.c index fff9c49..3899339 100644 --- a/source/olvpatch.c +++ b/source/olvpatch.c @@ -64,26 +64,51 @@ static u32 read_cached(u8 *der) return n > 0 && n <= CA_MAX ? n : 0; } -bool olvpatch_fetch_ca(const char *server, u8 *der, u32 *derLen, bool fresh) +// Erst in eine Hilfsdatei schreiben, dann umbenennen: Wird die App mittendrin beendet, bleibt die alte Datei heil +static bool write_atomic(const char *path, const u8 *data, u32 n) { - if (!fresh && (*derLen = read_cached(der))) return true; - static u8 pem[2048]; - u32 n = fetch(server, "/olv/ca.der", der, CA_MAX + 1); - if (n == 0 || n > CA_MAX) return !fresh ? false : (*derLen = read_cached(der)) != 0; + char tmp[96]; + snprintf(tmp, sizeof(tmp), "%s.neu", path); + FILE *f = fopen(tmp, "wb"); + bool ok = f && fwrite(data, 1, n, f) == n; + if (f) fclose(f); + if (ok) { remove(path); ok = rename(tmp, path) == 0; } + if (!ok) remove(tmp); + return ok; +} + +// Holt die CA vom Server in den Zwischenspeicher. *changed: anders als vorher. +bool olvpatch_refresh_ca(const char *server, bool *changed) +{ + static u8 der[CA_MAX + 1], old[CA_MAX + 1], pem[2048]; + *changed = false; + u32 n = fetch(server, "/olv/ca.der", der, sizeof(der)); + if (n == 0 || n > CA_MAX) return false; u32 p = fetch(server, "/olv/ca.pem", pem, sizeof(pem)); if (p == 0) return false; - // Der Browser der Miiverse-App liest die CA von der SD-Karte - FILE *f = fopen(CA_PEM_FILE, "wb"); - bool ok = f && fwrite(pem, 1, p, f) == p; - if (f) fclose(f); + u32 oldLen = read_cached(old); + if (oldLen == n && memcmp(old, der, n) == 0) return true; // unverändert mkdir("sdmc:/3ds/mbmnet", 0777); mkdir("sdmc:/3ds/mbmnet/cache", 0777); - f = fopen(CA_DER_CACHE, "wb"); - if (f) { fwrite(der, 1, n, f); fclose(f); } - *derLen = n; + // Der Browser der Miiverse-App liest die CA (PEM) von der SD-Karte + bool ok = write_atomic(CA_PEM_FILE, pem, p) && write_atomic(CA_DER_CACHE, der, n); + *changed = ok; return ok; } +bool olvpatch_fetch_ca(const char *server, u8 *der, u32 *derLen) +{ + if ((*derLen = read_cached(der))) return true; + bool changed; + return olvpatch_refresh_ca(server, &changed) && (*derLen = read_cached(der)) != 0; +} + +bool olvpatch_ca_cached(void) +{ + static u8 der[CA_MAX + 1]; + return read_cached(der) != 0; +} + bool olvpatch_supported(const u8 *code, u32 size) { u32 off = DISCOVERY_ADDR - CODE_BASE; diff --git a/source/olvpatch.h b/source/olvpatch.h index 8c0f115..069857c 100644 --- a/source/olvpatch.h +++ b/source/olvpatch.h @@ -3,9 +3,11 @@ // Miiverse-Patch (siehe olvpatch.c). Die CA kommt vom MBMNET-Server (/olv/ca.der, /olv/ca.pem). -// Holt die MBMNET-CA (DER, höchstens 889 Bytes) und legt die PEM-Fassung für den Browser auf die SD. -// fresh = false: erst den Zwischenspeicher auf der SD versuchen (schnell), sonst vom Server holen -bool olvpatch_fetch_ca(const char *server, u8 *der, u32 *derLen, bool fresh); +// CA aus dem Zwischenspeicher auf der SD (schnell); ist keiner da, einmal vom Server holen +bool olvpatch_fetch_ca(const char *server, u8 *der, u32 *derLen); +// CA frisch vom Server in den Zwischenspeicher. *changed: hat sich geändert (dann Patches neu bauen) +bool olvpatch_refresh_ca(const char *server, bool *changed); +bool olvpatch_ca_cached(void); // Ist das genau die Miiverse-Version, für die der Patch gebaut ist? (entpackter .code) bool olvpatch_supported(const u8 *code, u32 size); // Baut den IPS-Patch. Gibt die Länge zurück, 0 bei Fehler. diff --git a/source/patcher.c b/source/patcher.c index e2f7537..be450db 100644 --- a/source/patcher.c +++ b/source/patcher.c @@ -414,17 +414,13 @@ static bool write_ips(const char *path, const u8 *data, u32 n, bool *changed) // Miiverse-Patch (olvpatch.c): eigene Code-Änderungen + MBMNET-CA statt einfacher Text-Regeln. // 0 = nicht installiert, andere Version oder CA nicht vom Server zu holen (dann bleibt ein alter Patch stehen). -bool patchFreshCa; // CA frisch vom Server holen (Auffrischen im Hintergrund), sonst aus dem Zwischenspeicher - static u32 build_miiverse(const Target *t, const char *server, u8 *out, u32 cap) { static u8 der[1024]; - static u32 derLen; - static int caState; // 0 = noch nicht geholt, 1 = da, -1 = nicht zu holen + u32 derLen = 0; char fp[64]; fingerprint(t->tid, fp, sizeof(fp)); // leer = kein Zwischenspeicher, gebaut wird trotzdem - if (caState == 0) caState = olvpatch_fetch_ca(server, der, &derLen, patchFreshCa) ? 1 : -1; - if (caState < 0) return 0; + if (!olvpatch_fetch_ca(server, der, &derLen)) return 0; // aus dem Zwischenspeicher (aktualisiert das Auffrischen) u32 sum = 2166136261u; // FNV-1a über die CA: neue CA -> neuer Patch for (u32 i = 0; i < derLen; i++) sum = (sum ^ der[i]) * 16777619u; diff --git a/source/patcher.h b/source/patcher.h index 2cd6276..f405f2e 100644 --- a/source/patcher.h +++ b/source/patcher.h @@ -4,9 +4,6 @@ // Offizieller MBMNET-Server. Über "Server-Adresse" lässt sich ein anderer eintragen (z. B. zum Testen). // Fester MBMNET-Server: einfaches HTTP über den NetBird-TCP-Service auf Port 81. // Höchstens 21 Zeichen passen in den Patch. -// true: patch_enable holt die Miiverse-CA frisch vom Server (sonst aus dem Zwischenspeicher auf der SD) -extern bool patchFreshCa; - #define MBMNET_SERVER "mbmnetapi.mbm.wtf:81" #define APP_DIR "sdmc:/3ds/mbmnet" diff --git a/source/update.c b/source/update.c index 39dd0b7..8900327 100644 --- a/source/update.c +++ b/source/update.c @@ -273,7 +273,7 @@ bool update_finish(void) if (!thread) return true; // Läuft die Abfrage noch, nicht freigeben (sonst schreibt der Thread in freigegebenen Speicher). // false = läuft noch, die App muss sich hart beenden (main.c). - if (R_FAILED(threadJoin(thread, 300ULL * 1000 * 1000))) return false; + if (R_FAILED(threadJoin(thread, 100ULL * 1000 * 1000))) return false; threadFree(thread); thread = NULL; return true; diff --git a/source/update.h b/source/update.h index 40f3519..7c33981 100644 --- a/source/update.h +++ b/source/update.h @@ -21,7 +21,7 @@ void update_download(void); bool update_pending(void); UpdateState update_state(void); const char *update_new_version(void); -// Wartet auf den Hintergrund-Thread (höchstens 0,3 s). false = läuft noch. +// Wartet auf den Hintergrund-Thread (höchstens 0,1 s). false = läuft noch. bool update_finish(void); // Laufender Download-Versuch (1-3) int update_attempt(void);