// Entwickler-Werkzeuge nur für Admins - und nur, wenn der Server es gerade bestätigt hat: // 1. Die App liest aus dem Konto-Dienst (act) die Tokens des angemeldeten MBMNET-Kontos. Die hat nur ein 3DS, // auf dem man sich wirklich mit diesem Konto angemeldet hat (ausgestellt vom MBMNET-Account-Server). // 2. Sie schickt sie per HTTPS (MBMNET-CA) an /mbmnet/admin-check. Der Server prüft sie gegen seine // Datenbank 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) 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 // Liest die Tokens des angemeldeten Kontos aus dem Konto-Dienst (lokal, ohne Server-Anfrage von act) und lässt // sie vom MBMNET-Server prüfen. Die Tokens hat dieser Server beim Anmelden ausgestellt, er kann sie selbst prüfen. // (Früher: Service-Token über act angefordert - act lehnte das mit "ungültiger Zustand" ab, C8A14F4B.) static Result read_token(u8 slot, AccountAccessToken *at) { memset(at, 0, sizeof(*at)); return ACT_GetAccountInfo(at, sizeof(*at), slot, INFO_TYPE_ACCOUNT_ACCESS_TOKEN); } // Konto bei act einloggen (wie Spiele vor dem Online-Gehen): mit dem gespeicherten Passwort, Ergebnis per Ereignis static Result login(u8 slot) { Handle done; Result rc = svcCreateEvent(&done, RESET_ONESHOT); if (R_FAILED(rc)) return rc; rc = ACTA_AcquireAccountTokenEx(slot, NULL, true, done); // true = gespeichertes Passwort benutzen if (R_SUCCEEDED(rc)) rc = svcWaitSynchronization(done, 15ULL * 1000 * 1000 * 1000); if (rc == 0) rc = ACT_GetLastResponseResult(); svcCloseHandle(done); return rc; } static void check_once(void) { lastAct = 0; u8 slot = 0; ACT_GetCommonInfo(&slot, sizeof(slot), INFO_TYPE_COMMON_CURRENT_ACCOUNT_SLOT); if (!slot) ACT_GetCommonInfo(&slot, sizeof(slot), INFO_TYPE_COMMON_DEFAULT_ACCOUNT_SLOT); static AccountAccessToken at; // 1. direkt lesen; 2. sonst Konto laden (falls nach dem Umschalten nicht geladen); 3. sonst einloggen Result r1 = read_token(slot, &at), r2 = 0, r3 = 0, r4 = 0; if (R_FAILED(r1) || !at.refreshToken[0]) { r2 = ACTA_LoadConsoleAccount(slot, false, NULL, false, false); r3 = login(slot); r4 = read_token(slot, &at); } if (!at.accessToken[0] && !at.refreshToken[0]) { lastAct = R_FAILED(r4) ? r4 : r1; snprintf(why, sizeof(why), "Werkzeuge: Konto nicht eingeloggt.\nPlatz %u L %08lX A %08lX T %08lX/%08lX", slot, (unsigned long)r2, (unsigned long)r3, (unsigned long)r1, (unsigned long)r4); return; } static char tokens[0x50]; snprintf(tokens, sizeof(tokens), "%s %s", at.accessToken[0] ? at.accessToken : "-", at.refreshToken); memset(&at, 0, sizeof(at)); // Vom Server prüfen lassen (nur HTTPS, MBMNET-CA) char body[32]; u32 len = 0; Result hrc = 0; u32 code = https_request(server, "/mbmnet/admin-check", NULL, "X-NN-Tokens", tokens, body, sizeof(body), &len, NULL, 0, &hrc); memset(tokens, 0, sizeof(tokens)); 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 < 2; 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)