// Nintendo Network ID auf dem 3DS: anzeigen und lokal lösen. // // Solange eine NNID verknüpft ist, bieten die Systemeinstellungen kein // "Vorhandene ID verknüpfen" an. Damit man sich mit dem MBMNET-Konto anmelden kann, // löst die App die Verknüpfung lokal (ohne Server). Name und PID der alten ID werden // vorher in /3ds/mbmnet/alte-nnid.txt gesichert. #include <3ds.h> #include #include #include #include #include #include "nnid.h" #define OLD_NNID_FILE "sdmc:/3ds/mbmnet/alte-nnid.txt" #define LOG_FILE "sdmc:/3ds/mbmnet/konto.log" static bool ready; Result nnid_init(void) { Result rc = actInit(false); // false = act:a (Verwaltung) ready = R_SUCCEEDED(rc); return rc; } void nnid_exit(void) { if (ready) actExit(); } static u8 current_slot(void) { u8 slot = 0; ACT_GetCommonInfo(&slot, sizeof(slot), INFO_TYPE_COMMON_CURRENT_ACCOUNT_SLOT); return slot; } bool nnid_linked(char *name, size_t len) { name[0] = 0; if (!ready) return false; AccountId id = { 0 }; if (R_FAILED(ACT_GetAccountInfo(id, sizeof(id), current_slot(), INFO_TYPE_ACCOUNT_ID))) return false; snprintf(name, len, "%s", id); return name[0] != 0; } u32 nnid_pid(void) { u32 pid = 0; char name[20]; if (nnid_linked(name, sizeof(name))) ACT_GetAccountInfo(&pid, sizeof(pid), current_slot(), INFO_TYPE_PRINCIPAL_ID); return pid; } // Verknüpfte IDs in ANDEREN Konto-Plätzen (z. B. die Pretendo-ID aus Nimbus' zweitem Platz). // Eine ID, die dort steht, darf der 3DS nicht noch einmal verknüpfen (Fehler 022-2571). int nnid_others(char *names, size_t len) { names[0] = 0; if (!ready) return 0; u8 count = 0, current = current_slot(); if (R_FAILED(ACT_GetCommonInfo(&count, sizeof(count), INFO_TYPE_COMMON_NUM_ACCOUNTS))) return 0; int found = 0; for (u8 slot = 1; slot <= count; slot++) { if (slot == current) continue; AccountId id = { 0 }; if (R_FAILED(ACT_GetAccountInfo(id, sizeof(id), slot, INFO_TYPE_ACCOUNT_ID)) || !id[0]) continue; size_t n = strlen(names); snprintf(names + n, len - n, "%s\"%s\"", found ? ", " : "", id); found++; } return found; } bool nnid_unlink_others(char *msg, size_t msglen) { if (!ready) return false; u8 count = 0, current = current_slot(); if (R_FAILED(ACT_GetCommonInfo(&count, sizeof(count), INFO_TYPE_COMMON_NUM_ACCOUNTS))) return false; mkdir("sdmc:/3ds/mbmnet", 0777); FILE *log = fopen(LOG_FILE, "a"); FILE *old = fopen(OLD_NNID_FILE, "a"); bool ok = true; for (u8 slot = 1; slot <= count; slot++) { if (slot == current) continue; AccountId id = { 0 }; u32 pid = 0; if (R_FAILED(ACT_GetAccountInfo(id, sizeof(id), slot, INFO_TYPE_ACCOUNT_ID)) || !id[0]) continue; ACT_GetAccountInfo(&pid, sizeof(pid), slot, INFO_TYPE_PRINCIPAL_ID); if (old) fprintf(old, "Konto-Platz %u ID: %s PID: %lu (anderer Platz, z. B. Pretendo)\n", slot, id, (unsigned long)pid); Result rc = ACTA_UnbindServerAccount(slot, true); if (log) fprintf(log, "Platz %u (%s) lösen %08lX\n", slot, id, (unsigned long)rc); if (R_FAILED(rc)) { snprintf(msg, msglen, "ID \"%s\" (Platz %u) konnte nicht gelöst werden\n(Fehler %08lX).", id, slot, (unsigned long)rc); ok = false; } } if (old) fclose(old); if (log) fclose(log); return ok; } bool nnid_unlink(char *msg, size_t msglen) { if (!ready) { snprintf(msg, msglen, "Kontodienst nicht verfügbar.\nIst Luma3DS aktuell?"); return false; } u8 slot = current_slot(); AccountId id = { 0 }; u32 pid = 0; ACT_GetAccountInfo(id, sizeof(id), slot, INFO_TYPE_ACCOUNT_ID); ACT_GetAccountInfo(&pid, sizeof(pid), slot, INFO_TYPE_PRINCIPAL_ID); if (!id[0]) return true; // nichts verknüpft mkdir("sdmc:/3ds/mbmnet", 0777); // Alte ID merken, damit der Name nicht verloren geht FILE *f = fopen(OLD_NNID_FILE, "a"); if (f) { time_t now = time(NULL); char date[32]; strftime(date, sizeof(date), "%Y-%m-%d %H:%M", localtime(&now)); fprintf(f, "%s Konto-Platz %u NNID: %s PID: %lu\n", date, slot, id, (unsigned long)pid); fclose(f); } FILE *log = fopen(LOG_FILE, "w"); if (log) fprintf(log, "MBMNET: NNID lösen (App v%s)\nKonto-Platz %u, NNID %s\n\n", APP_VERSION, slot, id); Result rc = ACTA_UnbindServerAccount(slot, true); if (log) fprintf(log, "%-30s %08lX\n", "Verknüpfung lösen", (unsigned long)rc); if (R_FAILED(rc)) { // Manche Zustände erlauben das nur bei abgemeldetem Konto: abmelden und erneut versuchen Result u = ACTA_UnloadConsoleAccount(); if (log) fprintf(log, "%-30s %08lX\n", "Konto abmelden", (unsigned long)u); rc = ACTA_UnbindServerAccount(slot, true); if (log) fprintf(log, "%-30s %08lX\n", "Verknüpfung lösen (2.)", (unsigned long)rc); } if (log) { fprintf(log, "\nErgebnis: %s\n", R_SUCCEEDED(rc) ? "OK" : "FEHLER"); fclose(log); } if (R_FAILED(rc)) { snprintf(msg, msglen, "NNID konnte nicht gelöst werden (Fehler %08lX).\nProtokoll: /3ds/mbmnet/konto.log", (unsigned long)rc); return false; } return true; } // Für die Diagnose: alle Konto-Plätze mit ihren Daten void nnid_dump(FILE *f) { if (!ready) { fprintf(f, " Kontodienst nicht verfügbar\n"); return; } u8 count = 0, current = 0, def = 0; ACT_GetCommonInfo(&count, sizeof(count), INFO_TYPE_COMMON_NUM_ACCOUNTS); ACT_GetCommonInfo(¤t, sizeof(current), INFO_TYPE_COMMON_CURRENT_ACCOUNT_SLOT); ACT_GetCommonInfo(&def, sizeof(def), INFO_TYPE_COMMON_DEFAULT_ACCOUNT_SLOT); u8 friendAccount = 0; FRD_GetMyLocalAccountId(&friendAccount); fprintf(f, " %u Plätze, aktiv: %u, Standard: %u, geladenes Freundekonto: %u (1 = Nintendo, 3 = MBMNET)\n", count, current, def, friendAccount); for (u8 slot = 1; slot <= count; slot++) { AccountId id = { 0 }, assigned = { 0 }; u32 pid = 0, assignedPid = 0; u8 fp = 0; bool committed = false; Result r1 = ACT_GetAccountInfo(id, sizeof(id), slot, INFO_TYPE_ACCOUNT_ID); ACT_GetAccountInfo(assigned, sizeof(assigned), slot, INFO_TYPE_ASSIGNED_ACCOUNT_ID); ACT_GetAccountInfo(&pid, sizeof(pid), slot, INFO_TYPE_PRINCIPAL_ID); ACT_GetAccountInfo(&assignedPid, sizeof(assignedPid), slot, INFO_TYPE_ASSIGNED_PRINCIPAL_ID); ACT_GetAccountInfo(&fp, sizeof(fp), slot, INFO_TYPE_FP_LOCAL_ACCOUNT_ID); ACT_GetAccountInfo(&committed, sizeof(committed), slot, INFO_TYPE_IS_COMMITTED); fprintf(f, " Platz %u: ID \"%s\" (Länge %u, Abfrage %08lX) PID %lu zugewiesen \"%s\"/%lu Freunde-Konto %u gespeichert %d\n", slot, id, (unsigned)strlen(id), (unsigned long)r1, (unsigned long)pid, assigned, (unsigned long)assignedPid, fp, committed); } } // Gibt es die ID "name" in einem Konto-Platz außer exceptSlot? bool nnid_name_elsewhere(const char *name, u8 exceptSlot) { if (!ready || !name[0]) return false; u8 count = 0; if (R_FAILED(ACT_GetCommonInfo(&count, sizeof(count), INFO_TYPE_COMMON_NUM_ACCOUNTS))) return false; for (u8 slot = 1; slot <= count; slot++) { if (slot == exceptSlot) continue; AccountId id = { 0 }; if (R_SUCCEEDED(ACT_GetAccountInfo(id, sizeof(id), slot, INFO_TYPE_ACCOUNT_ID)) && !strcasecmp(id, name)) return true; } return false; } bool nnid_unlink_slot(u8 slot, char *msg, size_t msglen) { if (!ready) return false; AccountId id = { 0 }; ACT_GetAccountInfo(id, sizeof(id), slot, INFO_TYPE_ACCOUNT_ID); if (!id[0]) return true; FILE *log = fopen(LOG_FILE, "a"); Result rc = ACTA_UnbindServerAccount(slot, true); if (log) fprintf(log, "Platz %u (%s) lösen %08lX\n", slot, id, (unsigned long)rc); if (R_FAILED(rc)) { Result u = ACTA_UnloadConsoleAccount(); rc = ACTA_UnbindServerAccount(slot, true); if (log) fprintf(log, " nach Abmelden (%08lX): %08lX\n", (unsigned long)u, (unsigned long)rc); } if (log) fclose(log); if (R_FAILED(rc)) snprintf(msg, msglen, "\"%s\" (Platz %u) ließ sich nicht lösen (Fehler %08lX).", id, slot, (unsigned long)rc); return R_SUCCEEDED(rc); } // Löst die ID "name" in allen Konto-Plätzen außer exceptSlot (nur auf dem 3DS). false + msg bei Fehler. bool nnid_unlink_named(const char *name, u8 exceptSlot, char *msg, size_t msglen) { if (!ready) return false; u8 count = 0; if (R_FAILED(ACT_GetCommonInfo(&count, sizeof(count), INFO_TYPE_COMMON_NUM_ACCOUNTS))) return false; FILE *log = fopen(LOG_FILE, "a"); bool ok = true; for (u8 slot = 1; slot <= count; slot++) { if (slot == exceptSlot) continue; AccountId id = { 0 }; if (R_FAILED(ACT_GetAccountInfo(id, sizeof(id), slot, INFO_TYPE_ACCOUNT_ID)) || strcasecmp(id, name)) continue; Result rc = ACTA_UnbindServerAccount(slot, true); if (log) fprintf(log, "Platz %u (%s) lösen (gleicher Name wie MBMNET) %08lX\n", slot, id, (unsigned long)rc); if (R_FAILED(rc)) { snprintf(msg, msglen, "\"%s\" (Platz %u) ließ sich nicht lösen (Fehler %08lX).", id, slot, (unsigned long)rc); ok = false; } } if (log) fclose(log); return ok; }