v3.0.4: eigene Werkzeug-Seite, Logs der MBMNET-Server (HTTPS mit MBMNET-CA), Admin nur mit signiertem Nachweis vom Server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nico Meister
2026-10-26 20:28:29 +01:00
co-authored by Claude Opus 5.5
parent 718c605f6d
commit d1f5f5add3
9 changed files with 817 additions and 111 deletions
+47 -36
View File
@@ -1,56 +1,67 @@
// Entwickler-Werkzeuge nur für Admins: Die App fragt bei MBMNET, ob die im MBMNET-Platz verknüpfte ID einem
// Admin gehört (GET /mbmnet/app-info?pid=...). Ohne verknüpfte MBMNET-ID gibt es keine Werkzeuge.
// Die Antwort wird mit der Kennung (PID) auf der SD gemerkt: Ohne Verbindung gilt sie nur für dasselbe Konto.
// 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 <stdio.h>
#include <string.h>
#include "admin.h"
#include "https.h"
#define CACHE_FILE "sdmc:/3ds/mbmnet/admin"
#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 Thread thread;
static char url[128];
static u32 currentPid;
static void remember(bool on)
{
FILE *f = fopen(CACHE_FILE, "w");
if (f) { fprintf(f, "%lu %d\n", (unsigned long)currentPid, on ? 1 : 0); fclose(f); }
}
static char server[64];
static char why[96] = "Werkzeuge: kein MBMNET-Konto angemeldet."; // warum (noch) keine Werkzeuge
static void worker(void *arg)
{
(void)arg;
httpcContext ctx;
if (R_FAILED(httpcOpenContext(&ctx, HTTPC_METHOD_GET, url, 1))) return;
httpcAddRequestHeaderField(&ctx, "User-Agent", "MBMNET-Switcher/" APP_VERSION);
u32 status = 0, got = 0;
char body[32] = { 0 };
Result rc = httpcBeginRequest(&ctx);
if (R_SUCCEEDED(rc)) rc = httpcGetResponseStatusCodeTimeout(&ctx, &status, 8ULL * 1000 * 1000 * 1000);
if (R_SUCCEEDED(rc) && status == 200) httpcDownloadData(&ctx, (u8 *)body, sizeof(body) - 1, &got);
httpcCloseContext(&ctx);
if (status != 200 || strncmp(body, "admin=", 6) != 0) return; // keine Antwort: gemerkte gilt weiter
isAdmin = body[6] == '1';
remember(isAdmin);
// 1. Anmelde-Nachweis vom Konto-Dienst (für das gerade angemeldete Konto)
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, false, pid, done);
if (R_SUCCEEDED(rc)) rc = svcWaitSynchronization(done, 15ULL * 1000 * 1000 * 1000);
svcCloseHandle(done);
if (rc != 0) { snprintf(why, sizeof(why), "Werkzeuge: Anmelde-Nachweis nicht erhalten (%08lX).", (unsigned long)rc); return; }
static IndependentServiceTokenV1 tok;
memset(&tok, 0, sizeof(tok));
if (R_FAILED(rc = ACT_GetIndependentServiceToken(&tok)) || !tok.token[0]) {
snprintf(why, sizeof(why), "Werkzeuge: Anmelde-Nachweis leer (%08lX).", (unsigned long)rc);
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);
}
void admin_start(const char *server, u32 pid)
const char *admin_why(void) { return why; }
void admin_start(const char *srv, bool mbmnetLoggedIn)
{
currentPid = pid;
isAdmin = false;
if (!pid) { remove(CACHE_FILE); return; } // keine MBMNET-ID verknüpft: keine Werkzeuge
// Gemerkte Antwort nur, wenn sie zu genau diesem Konto gehört (alte Dateien ohne PID zählen nicht)
FILE *f = fopen(CACHE_FILE, "r");
if (f) {
unsigned long cachedPid = 0;
int on = 0;
if (fscanf(f, "%lu %d", &cachedPid, &on) == 2 && cachedPid == pid) isAdmin = on == 1;
fclose(f);
}
snprintf(url, sizeof(url), "http://%s/mbmnet/app-info?pid=%lu", server, (unsigned long)pid);
snprintf(why, sizeof(why), "Werkzeuge: nur bei aktivem MBMNET mit angemeldetem Admin-Konto.");
remove(OLD_CACHE_FILE);
if (!mbmnetLoggedIn) return;
snprintf(server, sizeof(server), "%s", srv);
s32 prio = 0x30;
svcGetThreadPriority(&prio, CUR_THREAD_HANDLE);
thread = threadCreate(worker, NULL, 16 * 1024, prio + 1, -2, false);