MBMNET-App v2.8.2: Quellcode (GPLv3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nico Meister
2026-10-18 17:05:44 +02:00
co-authored by Claude Opus 5.5
commit d264cd93cd
42 changed files with 7054 additions and 0 deletions
+245
View File
@@ -0,0 +1,245 @@
// Eigenes MBMNET-Freundekonto, wie bei Pretendos Nimbus:
//
// Freundekonto 1: Nintendo - mit Konto-Platz 1 (Nintendo Network ID) und der Nintendo-Freundesliste
// Freundekonto 3: MBMNET - mit eigenem Konto-Platz (MBMNET-Konto) und eigener Freundesliste
//
// (Freundekonto 2 benutzt Pretendos Nimbus, das bleibt unangetastet.)
//
// Umschalten: beide Konten abmelden, das andere laden, seinen Konto-Platz zum Standard machen, Neustart.
// Beim ersten Mal legt die App das MBMNET-Freundekonto an. Es hat noch keine Kennung; beim ersten
// Login holt es sich bei MBMNET ein neues Konto (NASC "passwd"), der Server gibt ihm den Freundescode
// des MBMNET-Kontos von der Webseite.
#include <3ds.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "frdswitch.h"
#define RESULT_LOCAL_ACCOUNT_NOT_EXISTS 0xC880C4ED
#define LOG_FILE "sdmc:/3ds/mbmnet/konto.log"
static bool ready;
static FILE *logf;
static const char *failedStep;
static Result step(const char *name, Result rc)
{
if (logf) fprintf(logf, "%-36s %08lX%s\n", name, (unsigned long)rc, R_FAILED(rc) ? " <- FEHLER" : "");
if (R_FAILED(rc) && !failedStep) failedStep = name;
return rc;
}
static void note(const char *name, Result rc)
{
if (logf) fprintf(logf, "%-36s %08lX%s\n", name, (unsigned long)rc, R_FAILED(rc) ? " (ignoriert)" : "");
}
Result frdswitch_init(void)
{
Result rc = ndmuInit();
if (R_SUCCEEDED(rc)) rc = frdInit(false); // frd:a (Verwaltung)
if (R_SUCCEEDED(rc)) rc = actInit(false); // act:a
// Ohne diese SDK-Version lehnt der Freunde-Dienst Anlegen und Umschalten ab (so macht es Nimbus;
// daran scheiterte unser erster Versuch mit C960C4F6)
if (R_SUCCEEDED(rc)) rc = FRD_SetClientSdkVersion(0x70000c8);
ready = R_SUCCEEDED(rc);
return rc;
}
void frdswitch_exit(void)
{
actExit();
frdExit();
ndmuExit();
}
u8 frdswitch_current(void)
{
u8 id = 0;
if (ready) FRD_GetMyLocalAccountId(&id);
return id;
}
// Konto-Platz (act) zu einem Freundekonto. 0 = keiner.
static u32 act_slot_of(u8 friendAccount)
{
u8 count = 0;
if (R_FAILED(ACT_GetCommonInfo(&count, sizeof(count), INFO_TYPE_COMMON_NUM_ACCOUNTS))) return 0;
for (u8 slot = 1; slot <= count; slot++) {
u8 fp = 0;
if (R_SUCCEEDED(ACT_GetAccountInfo(&fp, sizeof(fp), slot, INFO_TYPE_FP_LOCAL_ACCOUNT_ID)) && fp == friendAccount) return slot;
}
return 0;
}
bool frdswitch_slot_linked(u8 friendAccount, char *id, size_t len)
{
id[0] = 0;
u32 slot = ready ? act_slot_of(friendAccount) : 0;
if (!slot) return false;
AccountId acc = { 0 };
if (R_FAILED(ACT_GetAccountInfo(acc, sizeof(acc), slot, INFO_TYPE_ACCOUNT_ID))) return false;
snprintf(id, len, "%s", acc);
return id[0] != 0;
}
// Beide Konten abmelden. Das Freundekonto lässt sich nur offline entladen.
static Result unload(void)
{
note("Konto (act) abmelden", ACTA_UnloadConsoleAccount()); // ohne geladenes Konto: "ungültiger Zustand"
Result rc = step("Offline gehen (exklusiv)", NDMU_EnterExclusiveState(NDM_EXCLUSIVE_STATE_LOCAL_COMMUNICATIONS));
if (R_FAILED(rc)) return rc;
for (int i = 0; i < 150; i++) { // höchstens 15 Sekunden
bool online = true;
if (R_FAILED(FRD_IsOnline(&online)) || !online) break;
svcSleepThread(100 * 1000 * 1000);
}
// Hängt der Freunde-Dienst noch an einer Anfrage, kurz wiederholen
for (int attempt = 1; attempt <= 20; attempt++) {
rc = FRDA_UnloadLocalAccount();
if (R_SUCCEEDED(rc)) {
if (logf) fprintf(logf, "%-36s %08lX (Versuch %d)\n", "Freundekonto abmelden", (unsigned long)rc, attempt);
return rc;
}
svcSleepThread(500 * 1000 * 1000);
}
return step("Freundekonto abmelden (10 s)", rc);
}
// Freundekonto laden. "Schon geladen" (z. B. direkt nach dem Anlegen) zählt als Erfolg.
static Result load_friend_account(u8 friendAccount)
{
Result rc = FRDA_LoadLocalAccount(friendAccount);
if (R_FAILED(rc) && frdswitch_current() == friendAccount) {
note("Freundekonto laden (war schon geladen)", rc);
return 0;
}
return step("Freundekonto laden", rc);
}
// Konto-Platz (act) des Freundekontos zum Standard machen; fehlt er, anlegen. *created: neu angelegt.
static Result use_act_slot(u8 friendAccount, u32 *slotOut, bool *created)
{
Result rc = 0;
u32 slot = act_slot_of(friendAccount);
*created = false;
if (!slot) {
u8 count = 0;
rc = step("Konto-Plätze zählen", ACT_GetCommonInfo(&count, sizeof(count), INFO_TYPE_COMMON_NUM_ACCOUNTS));
if (R_SUCCEEDED(rc)) rc = step("Konto-Platz anlegen", ACTA_CreateConsoleAccount());
if (R_FAILED(rc)) return rc;
slot = count + 1;
rc = step("Konto-Platz speichern", ACTA_CommitConsoleAccount(slot));
if (R_FAILED(rc)) return rc;
*created = true;
}
if (logf) fprintf(logf, " -> Konto-Platz %lu\n", (unsigned long)slot);
*slotOut = slot;
return step("Standard-Konto setzen", ACTA_SetDefaultAccount(slot));
}
bool frdswitch_to(u8 target, bool *created, char *msg, size_t msglen)
{
*created = false;
if (!ready) {
snprintf(msg, msglen, "Kontodienste nicht verfügbar.\nIst Luma3DS aktuell?");
return false;
}
mkdir("sdmc:/3ds/mbmnet", 0777);
logf = fopen(LOG_FILE, "w");
failedStep = NULL;
u8 from = frdswitch_current();
if (logf) fprintf(logf, "MBMNET: Freundekonto %u -> %u (App v%s)\n\n", from, target, APP_VERSION);
u32 slot = 0;
bool newSlot = false;
Result rc = unload();
if (R_SUCCEEDED(rc)) {
rc = FRDA_LoadLocalAccount(target);
if (rc == (Result)RESULT_LOCAL_ACCOUNT_NOT_EXISTS && target == FRIEND_ACCOUNT_MBMNET) {
// Erstes Mal: eigenes MBMNET-Freundekonto anlegen (normale Umgebung, die Patches lenken sie um)
note("Freundekonto laden (gibt es noch nicht)", rc);
rc = step("MBMNET-Freundekonto anlegen", FRDA_CreateLocalAccount(FRIEND_ACCOUNT_MBMNET, 0, 0, 1));
if (R_SUCCEEDED(rc)) { *created = true; rc = load_friend_account(target); }
} else if (R_FAILED(rc) && frdswitch_current() == target) {
note("Freundekonto laden (war schon geladen)", rc);
rc = 0;
} else {
step("Freundekonto laden", rc);
}
if (R_SUCCEEDED(rc)) rc = use_act_slot(target, &slot, &newSlot);
// Neuer Platz: frisch zurücksetzen, damit "Vorhandene ID verknüpfen" angeboten wird (darf scheitern)
if (R_SUCCEEDED(rc) && newSlot) note("Konto-Platz zurücksetzen", ACTA_UnbindServerAccount(slot, true));
}
if (R_FAILED(rc)) {
// Zurück auf das vorherige Freundekonto, damit der 3DS nicht ohne/mit falschem Konto dasteht.
// Erst entladen - ein geladenes Konto lässt sich nicht einfach ersetzen.
u8 back = from ? from : FRIEND_ACCOUNT_NINTENDO;
if (frdswitch_current() != back) {
note("Rückweg: Freundekonto abmelden", FRDA_UnloadLocalAccount());
note("Rückweg: altes Freundekonto laden", FRDA_LoadLocalAccount(back));
}
NDMU_LeaveExclusiveState();
}
if (logf) { fprintf(logf, "\nErgebnis: %s\n", R_SUCCEEDED(rc) ? "OK" : "FEHLER"); fclose(logf); logf = NULL; }
if (R_FAILED(rc)) {
snprintf(msg, msglen, "Umschalten fehlgeschlagen bei:\n%s (Fehler %08lX)\nProtokoll: /3ds/mbmnet/konto.log",
failedStep ? failedStep : "?", (unsigned long)rc);
return false;
}
return true;
}
u32 frdswitch_my_pid(void)
{
FriendKey key = { 0 };
if (!ready || R_FAILED(FRD_GetMyFriendKey(&key))) return 0;
return key.principalId;
}
bool frdswitch_pid_is_mbmnet(u32 pid)
{
return pid >= MBMNET_PID_FIRST && pid <= MBMNET_PID_LAST;
}
// Das MBMNET-Freundekonto neu anlegen (es hat eine Kennung von einem anderen Server, z. B. Pretendo).
// Der Konto-Platz mit der verknüpften MBMNET-ID bleibt, nur das Freundekonto wird ersetzt. Danach
// Neustart: Beim ersten Login holt es sich bei MBMNET eine neue Kennung.
bool frdswitch_recreate_mbmnet(char *msg, size_t msglen)
{
if (!ready || frdswitch_current() != FRIEND_ACCOUNT_MBMNET) {
snprintf(msg, msglen, "MBMNET-Freundekonto ist nicht geladen.");
return false;
}
mkdir("sdmc:/3ds/mbmnet", 0777);
logf = fopen(LOG_FILE, "w");
failedStep = NULL;
if (logf) fprintf(logf, "MBMNET: Freundekonto 3 neu anlegen, alte Kennung %lu (App v%s)\n\n",
(unsigned long)frdswitch_my_pid(), APP_VERSION);
u32 slot = 0;
bool newSlot = false;
Result rc = unload();
if (R_SUCCEEDED(rc)) rc = step("MBMNET-Freundekonto neu anlegen", FRDA_CreateLocalAccount(FRIEND_ACCOUNT_MBMNET, 0, 0, 1));
if (R_SUCCEEDED(rc)) rc = load_friend_account(FRIEND_ACCOUNT_MBMNET);
if (R_SUCCEEDED(rc)) rc = use_act_slot(FRIEND_ACCOUNT_MBMNET, &slot, &newSlot);
if (R_FAILED(rc)) {
if (frdswitch_current() != FRIEND_ACCOUNT_MBMNET) note("Rückweg: Freundekonto laden", FRDA_LoadLocalAccount(FRIEND_ACCOUNT_MBMNET));
NDMU_LeaveExclusiveState();
}
if (logf) { fprintf(logf, "\nErgebnis: %s\n", R_SUCCEEDED(rc) ? "OK" : "FEHLER"); fclose(logf); logf = NULL; }
if (R_FAILED(rc)) {
snprintf(msg, msglen, "Neu anlegen fehlgeschlagen bei:\n%s (Fehler %08lX)\nProtokoll: /3ds/mbmnet/konto.log",
failedStep ? failedStep : "?", (unsigned long)rc);
return false;
}
return true;
}
u8 frdswitch_slot_of(u8 friendAccount)
{
return ready ? (u8)act_slot_of(friendAccount) : 0;
}