// Entwickler-Werkzeuge nur für Admins - und nur, wenn der Server es gerade bestätigt hat: // 1. Der Konto-Dienst (act) holt beim MBMNET-Account-Server einen signierten Anmelde-Nachweis (Service-Token) // für das angemeldete MBMNET-Konto. Den kann nur ausstellen, wer mit diesem Konto angemeldet ist. // 2. Die App schickt ihn per HTTPS (MBMNET-CA) an /mbmnet/admin-check. Der Server prüft die Signatur und // antwortet admin=1 nur für Admins. // Nichts wird gemerkt: Ohne Antwort vom Server (offline, Nintendo aktiv, kein MBMNET-Konto) keine Werkzeuge. #include <3ds.h> #include #include #include "admin.h" #include "https.h" #define OLD_CACHE_FILE "sdmc:/3ds/mbmnet/admin" // gemerkte Antwort älterer Versionen (wird gelöscht) // Client-ID für den Nachweis (die von Miiverse; der MBMNET-Server nimmt jede) static char CLIENT_ID[] = "87cd32617f1985439ea608c2746e4610"; static volatile bool isAdmin; static char server[64]; static char why[96] = "Werkzeuge: kein MBMNET-Konto angemeldet."; // warum (noch) keine Werkzeuge static Result lastAct; // letzter Fehler von act (für die Wiederholung) static bool wanted; // MBMNET-Konto angemeldet: prüfen // Holt den Nachweis und lässt ihn prüfen. Läuft im Auffrischen-Thread (main.c) direkt nach dem Konto-Abgleich: // act bearbeitet nur eine Server-Anfrage auf einmal, gleichzeitig gab es "ungültiger Zustand" (C8A14F4B). static void check_once(void) { // 1. Anmelde-Nachweis vom Konto-Dienst (für das gerade angemeldete Konto). "shared": danach für jeden // Prozess abholbar - nur für diesen Prozess angefordert, gab act ihn nicht heraus (E0A14CCE). Handle done; snprintf(why, sizeof(why), "Werkzeuge: Server prüft noch ..."); if (R_FAILED(svcCreateEvent(&done, RESET_ONESHOT))) return; u32 pid = 0; svcGetProcessId(&pid, CUR_PROCESS_HANDLE); Result rc = ACT_AcquireIndependentServiceToken(0xFE, CLIENT_ID, 0, false, true, pid, done); Result wait = R_SUCCEEDED(rc) ? svcWaitSynchronization(done, 15ULL * 1000 * 1000 * 1000) : rc; svcCloseHandle(done); lastAct = 0; if (wait != 0) { lastAct = wait; snprintf(why, sizeof(why), "Werkzeuge: Anmelde-Nachweis nicht erhalten (%08lX).", (unsigned long)wait); return; } // Ergebnis der Anfrage beim Account-Server (z. B. abgelehnt oder keine Verbindung) Result result = ACT_GetLastResponseResult(); static IndependentServiceTokenV1 tok; memset(&tok, 0, sizeof(tok)); rc = ACT_GetIndependentServiceToken(&tok); // Notlösung: aus dem Zwischenspeicher von act (gerade eben geholt) if (R_FAILED(rc) || !tok.token[0]) rc = ACT_GetCachedIndependentServiceToken(&tok, 0xFE, CLIENT_ID, 3600, false, true); if (R_FAILED(rc) || !tok.token[0]) { lastAct = R_FAILED(result) ? result : rc; snprintf(why, sizeof(why), "Werkzeuge: kein Anmelde-Nachweis vom Server (%08lX).", (unsigned long)lastAct); return; } // 2. Vom Server prüfen lassen (nur HTTPS) char body[32]; u32 len = 0; Result hrc = 0; u32 code = https_request(server, "/mbmnet/admin-check", NULL, "X-Service-Token", tok.token, body, sizeof(body), &len, NULL, 0, &hrc); memset(&tok, 0, sizeof(tok)); isAdmin = code == 200 && strncmp(body, "admin=1", 7) == 0; if (isAdmin) why[0] = 0; else if (code == 200) snprintf(why, sizeof(why), "Werkzeuge gibt es nur für Admins."); else if (code) snprintf(why, sizeof(why), "Werkzeuge: Server antwortet HTTP %lu.", (unsigned long)code); else snprintf(why, sizeof(why), "Werkzeuge: keine HTTPS-Verbindung zum Server (%08lX).", (unsigned long)hrc); } const char *admin_why(void) { return why; } void admin_prepare(const char *srv, bool mbmnetLoggedIn) { isAdmin = false; wanted = mbmnetLoggedIn; snprintf(why, sizeof(why), mbmnetLoggedIn ? "Werkzeuge: Server prüft noch ..." : "Werkzeuge: nur bei aktivem MBMNET mit angemeldetem Admin-Konto."); remove(OLD_CACHE_FILE); snprintf(server, sizeof(server), "%s", srv); } void admin_check(void) { if (!wanted) return; // act noch mit etwas anderem beschäftigt ("ungültiger Zustand"): kurz warten und noch einmal for (int attempt = 0; attempt < 4; attempt++) { check_once(); if (isAdmin || !lastAct || ((lastAct >> 21) & 0x3F) != 5) break; // 5 = InvalidState svcSleepThread(1000ULL * 1000 * 1000); } } bool admin_is(void) { return isAdmin; } bool admin_finish(void) { return true; } // kein eigener Thread mehr (läuft im Auffrischen)