Files
MBMNET-APP/source/nnid.c
T
2026-10-18 17:05:44 +02:00

218 lines
8.5 KiB
C

// 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 <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <time.h>
#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(&current, 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;
}
// 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;
}