// SpotPass (BOSS) für MBMNET-Mitteilungen, siehe spotpass.h. // // Wie in der libctru-Vorlage (examples/network/boss): eigene Aufgabe mit HTTP-URL. Der Server liefert einen // BOSS-Container, dessen Inhalt an das News-Modul (0004013000003502) geht. Die Nintendo-Signatur darin ist // leer, Luma schaltet ihre Prüfung ab. #include <3ds.h> #include #include #include "spotpass.h" #define TASK_NEWS "mbmnews" #define TASK_TEST "mbmtest" #define NEWS_INTERVAL (15 * 60) // Sekunden zwischen zwei Abrufen (alle 2 Minuten zog zu viel Akku) // SpotPass braucht für jede Anwendung mit Aufgaben einen angemeldeten Speicher (Extdata), wie bei Spielen. // Die Mitteilungen selbst landen im News-Modul, der Speicher bleibt praktisch leer. #define EXTDATA_ID 0xFB4E0ULL // = Unique ID der App (cia.rsf: ExtSaveDataId) #define BOSS_STORAGE 0x100000 // 1 MB // Policy-Liste: SpotPass lädt sie vor jeder Aufgabe von Nintendo (nppl.c.app.nintendowifi.net). Der Server // antwortet nicht mehr, ohne Liste bricht jede Aufgabe ab. Darum auf MBMNET umstellen (wie Pretendo). // boss:P-Befehl 0x0427 SetPolicyListUrl, leerer Text = wieder Nintendos Adresse. Klappt nur mit boss:P. static Result set_policy_url(const char *url) { u32 size = strlen(url) + 1; u32 *cmdbuf = getThreadCommandBuffer(); cmdbuf[0] = IPC_MakeHeader(0x427, 1, 2); // 0x04270042 cmdbuf[1] = size; cmdbuf[2] = IPC_Desc_Buffer(size, IPC_BUFFER_R); cmdbuf[3] = (u32)url; Result rc = svcSendSyncRequest(bossGetSessionHandle()); return R_SUCCEEDED(rc) ? (Result)cmdbuf[1] : rc; } static Result use_mbmnet_policy(const char *server) { char url[128]; snprintf(url, sizeof(url), "http://%s/p01/policylist", server); return set_policy_url(url); } // Gibt es die Extdata? Sonst anlegen (mit Ordner /boss, in den SpotPass schreibt). bossSetStorageInfo // prüft das nicht - ohne Extdata scheitert erst die Aufgabe (Fehler 004-6006). static Result ensure_extdata(void) { u32 lowHigh[3] = { MEDIATYPE_SD, (u32)EXTDATA_ID, (u32)(EXTDATA_ID >> 32) }; FS_Path path = { PATH_BINARY, sizeof(lowHigh), lowHigh }; FS_Archive arch; Result rc = fsInit(); if (R_FAILED(rc)) return rc; rc = FSUSER_OpenArchive(&arch, ARCHIVE_EXTDATA, path); if (R_FAILED(rc)) { FS_ExtSaveDataInfo info = { .mediaType = MEDIATYPE_SD, .saveId = EXTDATA_ID }; rc = FSUSER_CreateExtSaveData(info, 32, 256, 0, 0, NULL); if (R_FAILED(rc)) { // Manche Systemversionen wollen ein Icon (SMDH) dazu: leeres mit Kennung reicht static u8 smdh[0x36C0]; memcpy(smdh, "SMDH", 4); rc = FSUSER_CreateExtSaveData(info, 32, 256, 0, sizeof(smdh), smdh); } if (R_SUCCEEDED(rc)) rc = FSUSER_OpenArchive(&arch, ARCHIVE_EXTDATA, path); } if (R_SUCCEEDED(rc)) { FSUSER_CreateDirectory(arch, fsMakePath(PATH_ASCII, "/boss"), 0); // gibt es ihn schon: egal FSUSER_CloseArchive(arch); } fsExit(); return rc; } static Result ensure_storage(void) { return bossSetStorageInfo(EXTDATA_ID, BOSS_STORAGE, MEDIATYPE_SD); } static bool url_for(const char *server, const char *file, char *url, size_t len) { u32 device = 0; bool ok = R_SUCCEEDED(amInit()); if (ok) { s32 internal = 0; ok = R_SUCCEEDED(AM_GetDeviceId(&internal, &device)) && internal >= 0 && device; amExit(); } if (ok) snprintf(url, len, "http://%s/spotpass/%lu/%s", server, (unsigned long)device, file); return ok; } // step bekommt den Namen des Schritts, der fehlschlug static Result setup_task(const char *server, const char *task, const char *url, u32 interval, const char **step) { bool once = strcmp(task, TASK_TEST) == 0; *step = "Policy-Liste umstellen"; Result rc = use_mbmnet_policy(server); if (R_FAILED(rc)) return rc; *step = "Speicher anlegen"; rc = ensure_extdata(); if (R_FAILED(rc)) return rc; *step = "Speicher anmelden"; rc = ensure_storage(); if (R_FAILED(rc)) return rc; bossDeleteTask(task, 0); bossContext ctx; bossSetupContextDefault(&ctx, interval, url); ctx.property[0x4] = once ? 1 : 0; // Count: 0 = dauerhaft (Standard wären 100 Läufe), Test nur einmal *step = "Einstellungen senden"; rc = bossSendContextConfig(&ctx); if (R_FAILED(rc)) return rc; *step = "Aufgabe anmelden"; return bossRegisterTask(task, 0, 0); } bool spotpass_register(const char *server, char *msg, size_t msglen) { char url[256]; if (!url_for(server, "news.boss", url, sizeof(url))) { snprintf(msg, msglen, "SpotPass: Geräte-ID nicht lesbar"); return false; } const char *step = "BOSS starten"; Result rc = bossInit(0, false); if (R_SUCCEEDED(rc)) { rc = setup_task(server, TASK_NEWS, url, NEWS_INTERVAL, &step); if (R_SUCCEEDED(rc)) bossStartBgImmediate(TASK_NEWS); // gleich einmal abholen bossExit(); } if (R_FAILED(rc)) snprintf(msg, msglen, "SpotPass anmelden fehlgeschlagen bei: %s (%08lX)", step, (unsigned long)rc); return R_SUCCEEDED(rc); } void spotpass_unregister(void) { if (R_FAILED(bossInit(0, false))) return; bossDeleteTask(TASK_NEWS, 0); set_policy_url(""); // wieder Nintendos Policy-Liste bossExit(); } // --- Fehlerauswertung (BOSSU-Befehle, die libctru nicht hat) --------------------------------------- static Result boss_send(u32 *cmdbuf) { Result rc = svcSendSyncRequest(bossGetSessionHandle()); return R_SUCCEEDED(rc) ? (Result)cmdbuf[1] : rc; } // Befehl mit Task-ID-Puffer; extra = Werte zwischen Größe und Puffer static Result boss_task_cmd(u16 id, const char *task, int nextra, u32 e1, u32 e2, u32 *out) { u32 size = strlen(task) + 1; u32 *cmd = getThreadCommandBuffer(); cmd[0] = IPC_MakeHeader(id, 1 + nextra, 2); cmd[1] = size; if (nextra > 0) cmd[2] = e1; if (nextra > 1) cmd[3] = e2; cmd[2 + nextra] = IPC_Desc_Buffer(size, IPC_BUFFER_R); cmd[3 + nextra] = (u32)task; Result rc = boss_send(cmd); if (out) *out = cmd[2]; return rc; } static Result boss_receive_property(u16 property, void *buf, u32 size) { u32 *cmd = getThreadCommandBuffer(); cmd[0] = IPC_MakeHeader(0x16, 2, 2); // ReceiveProperty cmd[1] = property; cmd[2] = size; cmd[3] = IPC_Desc_Buffer(size, IPC_BUFFER_W); cmd[4] = (u32)buf; return boss_send(cmd); } // Fehler einer Aufgabe als Text: Nintendo-Fehlercode 004-XXXX, Ergebnis, Dienststatus, HTTP-Code static void task_error_text(const char *task, char *out, size_t len) { u8 result = 0, service = 0, taskErr = 0; u32 http = 0, code = 0; boss_task_cmd(0x23, task, 2, 0, 0, NULL); // GetTaskStatus: lädt die Status-Eigenschaften boss_receive_property(0x20, &result, 1); // TaskResultCode boss_receive_property(0x21, &service, 1); // TaskServiceStatus boss_task_cmd(0x24, task, 1, 0, 0, NULL); // GetTaskError: lädt Eigenschaft 0x30 boss_receive_property(0x30, &taskErr, 1); boss_task_cmd(0x22, task, 0, 0, 0, &http); // GetTaskCommErrorCode (HTTP-Status) u32 *cmd = getThreadCommandBuffer(); cmd[0] = IPC_MakeHeader(0x2E, 1, 0); // GetErrorCode: Ergebnis -> 004-XXXX cmd[1] = result; if (R_SUCCEEDED(boss_send(cmd))) code = cmd[2]; snprintf(out, len, "Fehler %03lu-%04lu (Ergebnis 0x%02X, Dienst 0x%02X, Aufgabe 0x%02X, HTTP %lu)", (unsigned long)(code / 10000), (unsigned long)(code % 10000), result, service, taskErr, (unsigned long)http); } static char testUrl[256]; static bool testSeenRunning; bool spotpass_test_start(const char *server, char *msg, size_t msglen) { if (!url_for(server, "test.boss", testUrl, sizeof(testUrl))) { snprintf(msg, msglen, "SpotPass-Test: Geräte-ID nicht lesbar"); return false; } Result rc = bossInit(0, false); if (R_FAILED(rc)) { snprintf(msg, msglen, "SpotPass-Test: BOSS nicht verfügbar (%08lX)", (unsigned long)rc); return false; } const char *step = ""; rc = setup_task(server, TASK_TEST, testUrl, 60, &step); if (R_SUCCEEDED(rc)) { step = "Aufgabe starten"; rc = bossStartTaskImmediate(TASK_TEST); } bossExit(); if (R_FAILED(rc)) { snprintf(msg, msglen, "SpotPass-Test fehlgeschlagen bei: %s (%08lX)", step, (unsigned long)rc); return false; } testSeenRunning = false; snprintf(msg, msglen, "SpotPass-Test gestartet ..."); return true; } bool spotpass_test_poll(int seconds, char *msg, size_t msglen) { if (R_FAILED(bossInit(0, false))) return true; u8 status = 0, out2 = 0; u32 out1 = 0; Result rc = bossGetTaskState(TASK_TEST, 0, &status, &out1, &out2); bool running = R_SUCCEEDED(rc) && status != BOSSTASKSTATUS_ERROR && !(testSeenRunning && status != BOSSTASKSTATUS_STARTED); if (status == BOSSTASKSTATUS_STARTED) testSeenRunning = true; if (running && seconds < 180) { snprintf(msg, msglen, "SpotPass-Test läuft ... %d s (Status %u)", seconds, status); bossExit(); return true; } if (R_FAILED(rc)) snprintf(msg, msglen, "SpotPass-Test: Status nicht lesbar (%08lX)", (unsigned long)rc); else if (running) snprintf(msg, msglen, "SpotPass-Test nach 3 Min. noch nicht fertig (Status %u).\n" "Er läuft im Hintergrund weiter. Später in die Mitteilungen schauen.", status); else if (status == BOSSTASKSTATUS_ERROR) { char why[128]; task_error_text(TASK_TEST, why, sizeof(why)); snprintf(msg, msglen, "SpotPass-Test: Abruf fehlgeschlagen.\n%s\n%s", why, testUrl); } else snprintf(msg, msglen, "SpotPass-Test abgeholt (Status %u, %lu, %u).\nSteht \"MBMNET SpotPass-Test\" in den Mitteilungen?", status, (unsigned long)out1, out2); if (!running) bossDeleteTask(TASK_TEST, 0); // nur löschen, wenn wirklich fertig bossExit(); return false; }