// 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 #include #include #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; }