Files
MBMNET-APP/source/admin.c
T

121 lines
5.9 KiB
C

// 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 <stdio.h>
#include <string.h>
#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);
}
// Leeres Passwort-Feld: libctru kopiert das Passwort immer - NULL ließ die App abstürzen (Luma-Dump, far=0)
static AccountPassword noPassword;
// 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, &noPassword, 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
// Fortschritt in "why": SELECT zeigt, bei welchem Schritt es gerade ist (oder hängt)
snprintf(why, sizeof(why), "Werkzeuge: Schritt 1/4 - Anmeldung lesen (Platz %u) ...", slot);
Result r1 = read_token(slot, &at), r2 = 0, r3 = 0, r4 = 0;
if (R_FAILED(r1) || !at.refreshToken[0]) {
snprintf(why, sizeof(why), "Werkzeuge: Schritt 2/4 - Konto laden ...\n(Lesen: %08lX)", (unsigned long)r1);
r2 = ACTA_LoadConsoleAccount(slot, false, &noPassword, false, false);
snprintf(why, sizeof(why), "Werkzeuge: Schritt 3/4 - Konto einloggen ...\n(Lesen %08lX, Laden %08lX)", (unsigned long)r1, (unsigned long)r2);
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));
snprintf(why, sizeof(why), "Werkzeuge: Schritt 4/4 - Server fragen (HTTPS) ...");
// 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; }
static bool checked; // Prüfung gelaufen: Ergebnis/Fehler in "why" nicht mehr überschreiben
void admin_progress(const char *step) { if (wanted && !isAdmin && !checked) snprintf(why, sizeof(why), "Werkzeuge: warte auf %s ...", step); }
void admin_skipped(void) { if (wanted && !isAdmin) snprintf(why, sizeof(why), "Werkzeuge: Prüfung ausgefallen (umgeschaltet). App neu öffnen."); }
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;
checked = true;
// 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)