128 lines
4.7 KiB
C
128 lines
4.7 KiB
C
// 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 <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#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;
|
|
}
|