250 lines
9.8 KiB
C
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 (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;
|
|
}
|