// Entwickler-Werkzeuge nur für Admins - und nur, wenn der Server es gerade bestätigt hat. // // Die Konsole hat einen geheimen Geräte-Schlüssel (32 Zufallsbytes, /3ds/mbmnet/geraete-schluessel). Die App // schickt ihn per HTTPS (MBMNET-CA) an /mbmnet/admin-check: // - freigeschaltete Konsole eines Admins -> admin=1 // - unbekannt -> pair=123456: Diesen Code gibt ein Admin auf der Webseite unter Admin > Konsolen ein. // Kein Passwort am 3DS, kein Konto-Dienst nötig. Ohne Antwort vom Server gibt es keine Werkzeuge. // (Früher über die NNID-Anmeldung im Konto-Dienst - die war im MBMNET-Platz durcheinander und ließ sich // nicht zuverlässig nutzen.) #include <3ds.h> #include #include #include #include "admin.h" #include "https.h" #define KEY_FILE "sdmc:/3ds/mbmnet/geraete-schluessel" #define OLD_CACHE_FILE "sdmc:/3ds/mbmnet/admin" // gemerkte Antwort älterer Versionen (wird gelöscht) static volatile bool isAdmin, running; static Thread thread; static char server[64]; static char why[160] = "Werkzeuge: Server prüft noch ..."; static char key[65]; // Geräte-Schlüssel laden oder einmal erzeugen (Zufall vom SSL-Dienst des 3DS) static bool load_key(void) { FILE *f = fopen(KEY_FILE, "r"); if (f) { if (fgets(key, sizeof(key), f)) key[strcspn(key, "\r\n")] = 0; fclose(f); if (strlen(key) == 64) return true; } u8 raw[32]; bool ok = false; if (R_SUCCEEDED(sslcInit(0))) { ok = R_SUCCEEDED(sslcGenerateRandomData(raw, sizeof(raw))); sslcExit(); } if (!ok) { key[0] = 0; return false; } for (int i = 0; i < 32; i++) snprintf(key + i * 2, 3, "%02x", raw[i]); mkdir("sdmc:/3ds/mbmnet", 0777); f = fopen(KEY_FILE, "w"); if (!f) { key[0] = 0; return false; } fputs(key, f); fclose(f); return true; } static void check(void *arg) { (void)arg; if (!key[0] && !load_key()) { snprintf(why, sizeof(why), "Werkzeuge: Geräte-Schlüssel nicht anlegbar (SD-Karte?)."); running = false; return; } // Geräte-ID nur zur Anzeige auf der Webseite (welche Konsole) char devid[16] = ""; u32 id = 0; s32 internal = 0; if (R_SUCCEEDED(amInit())) { if (R_SUCCEEDED(AM_GetDeviceId(&internal, &id))) snprintf(devid, sizeof(devid), "%lu", (unsigned long)id); amExit(); } char body[48]; u32 len = 0; Result rc = 0; u32 code = https_request(server, "/mbmnet/admin-check", NULL, "X-Admin-Device", key, body, sizeof(body), &len, NULL, 0, &rc); (void)devid; if (code == 200 && !strncmp(body, "admin=1", 7)) { isAdmin = true; why[0] = 0; } else if (code == 200 && !strncmp(body, "pair=", 5) && body[5]) { char c[8]; snprintf(c, sizeof(c), "%.6s", body + 5); snprintf(why, sizeof(why), "Werkzeuge freischalten: Code %.3s %.3s\nauf mbmnet.mbm.wtf unter Admin > Konsolen eingeben,\n" "dann nochmal SELECT drücken (10 Minuten gültig).", c, c + 3); } else if (code == 200 && !strncmp(body, "pair=", 5)) { snprintf(why, sizeof(why), "Werkzeuge: zu viele offene Freischaltungen, später nochmal."); } else if (code == 200) { snprintf(why, sizeof(why), "Werkzeuge: Diese Konsole ist nicht (mehr) freigeschaltet."); } 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)rc); } running = false; } void admin_prepare(const char *srv, bool unused1, u8 unused2) { (void)unused1; (void)unused2; isAdmin = false; remove(OLD_CACHE_FILE); snprintf(server, sizeof(server), "%s", srv); snprintf(why, sizeof(why), "Werkzeuge: Server prüft noch ..."); } // Prüfen im Hintergrund (beim Start und bei SELECT, solange noch kein Admin). Läuft schon eine: nichts tun. void admin_check_async(void) { if (running || isAdmin) return; if (thread) { threadJoin(thread, U64_MAX); threadFree(thread); thread = NULL; } running = true; s32 prio = 0x30; svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); thread = threadCreate(check, NULL, 16 * 1024, prio + 1, -2, false); if (!thread) running = false; } void admin_check(void) { admin_check_async(); } bool admin_is(void) { return isAdmin; } const char *admin_why(void) { return why; } void admin_progress(const char *step) { (void)step; } void admin_skipped(void) {} bool admin_finish(void) { if (!thread) return true; if (R_FAILED(threadJoin(thread, 100ULL * 1000 * 1000))) return false; // läuft noch threadFree(thread); thread = NULL; return true; }