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

250 lines
9.8 KiB
C

// 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 <stdio.h>
#include <string.h>
#include "spotpass.h"
#define TASK_NEWS "mbmnews"
#define TASK_TEST "mbmtest"
#define NEWS_INTERVAL (2 * 60) // Sekunden zwischen zwei Abrufen
// 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;
}