// MBMNET Shop installieren, starten und entfernen (der Shop selbst steckt in shop/). // Die CIA kommt vom MBMNET-Server (/app/mbmnet-shop.cia, aus dem Repo MBMNET wie die App-Updates). #include <3ds.h> #include #include #include #include "shopctl.h" static volatile ShopJob job = SHOPJOB_IDLE; static char jobMsg[160]; static char server[64]; static Thread thread; ShopJob shopctl_job(void) { return job; } const char *shopctl_message(void) { return jobMsg; } void shopctl_acknowledge(void) { if (job != SHOPJOB_RUNNING) job = SHOPJOB_IDLE; } int shopctl_version(void) { u64 id = SHOP_TITLE_ID; AM_TitleEntry info; if (R_FAILED(AM_GetTitleInfo(MEDIATYPE_SD, 1, &id, &info))) return -1; return info.version; } static void fail(const char *what, Result rc) { snprintf(jobMsg, sizeof(jobMsg), "MBMNET Shop: %s (%08lX).", what, (unsigned long)rc); job = SHOPJOB_FAILED; } static void install_worker(void *arg) { (void)arg; char url[128]; snprintf(url, sizeof(url), "http://%s/app/mbmnet-shop.cia", server); httpcContext ctx; u32 status = 0; Result rc = httpcOpenContext(&ctx, HTTPC_METHOD_GET, url, 1); if (R_FAILED(rc)) return fail("keine Verbindung", rc); httpcAddRequestHeaderField(&ctx, "User-Agent", "MBMNET-Switcher/" APP_VERSION); httpcSetKeepAlive(&ctx, HTTPC_KEEPALIVE_DISABLED); if (R_FAILED(rc = httpcBeginRequest(&ctx)) || R_FAILED(rc = httpcGetResponseStatusCodeTimeout(&ctx, &status, 15ULL * 1000 * 1000 * 1000))) { httpcCloseContext(&ctx); return fail("keine Verbindung", rc); } if (status != 200) { httpcCloseContext(&ctx); snprintf(jobMsg, sizeof(jobMsg), "MBMNET Shop: Der Server hat ihn gerade nicht (HTTP %lu).", (unsigned long)status); job = SHOPJOB_FAILED; return; } // Direkt in die Installation schreiben (wie FBI) Handle cia; bool started = false; u32 offset = 0, got; u8 *buf = malloc(64 * 1024); if (!buf) { httpcCloseContext(&ctx); return fail("zu wenig Speicher", -1); } do { got = 0; rc = httpcDownloadData(&ctx, buf, 64 * 1024, &got); if (!got) continue; if (!started) { // Eine CIA beginnt mit der Kopfgröße 0x2020 if (got < 4 || buf[0] != 0x20 || buf[1] != 0x20 || buf[2] || buf[3]) { rc = -1; break; } Result arc = AM_StartCiaInstall(MEDIATYPE_SD, &cia); if (R_FAILED(arc)) { rc = arc; break; } started = true; } u32 written = 0; Result wrc = FSFILE_Write(cia, &written, offset, buf, got, 0); if (R_FAILED(wrc) || written != got) { rc = R_FAILED(wrc) ? wrc : -1; break; } offset += got; } while (rc == (Result)HTTPC_RESULTCODE_DOWNLOADPENDING); free(buf); httpcCloseContext(&ctx); if (R_FAILED(rc) || !started) { if (started) AM_CancelCIAInstall(cia); return fail(started ? "Installation abgebrochen" : "Download fehlgeschlagen", rc); } if (R_FAILED(rc = AM_FinishCiaInstall(cia))) return fail("Installation abgelehnt", rc); snprintf(jobMsg, sizeof(jobMsg), "MBMNET Shop installiert!\nTippe unten auf \"Shop öffnen\" oder starte ihn im HOME-Menü."); job = SHOPJOB_DONE; } void shopctl_install(const char *srv) { if (job == SHOPJOB_RUNNING) return; snprintf(server, sizeof(server), "%s", srv); if (thread) { threadJoin(thread, U64_MAX); threadFree(thread); thread = NULL; } job = SHOPJOB_RUNNING; jobMsg[0] = 0; s32 prio = 0x30; svcGetThreadPriority(&prio, CUR_THREAD_HANDLE); thread = threadCreate(install_worker, NULL, 32 * 1024, prio + 1, -2, false); if (!thread) fail("konnte nicht starten", -1); } bool shopctl_remove(char *msg, size_t len) { Result rc = AM_DeleteTitle(MEDIATYPE_SD, SHOP_TITLE_ID); if (R_FAILED(rc) && shopctl_version() >= 0) { snprintf(msg, len, "Der Shop ließ sich nicht entfernen (%08lX).", (unsigned long)rc); return false; } snprintf(msg, len, "MBMNET Shop entfernt.\nApps, die du darüber installiert hast, bleiben auf dem 3DS."); return true; } bool shopctl_launch(char *msg, size_t len) { Result rc = APT_PrepareToDoApplicationJump(0, SHOP_TITLE_ID, MEDIATYPE_SD); if (R_SUCCEEDED(rc)) { static u8 param[0x300], hmac[0x20]; rc = APT_DoApplicationJump(param, sizeof(param), hmac); } if (R_FAILED(rc)) { snprintf(msg, len, "Starte den MBMNET Shop bitte im HOME-Menü (%08lX).", (unsigned long)rc); return false; } return true; } void shopctl_exit(void) { if (thread) { threadJoin(thread, U64_MAX); threadFree(thread); thread = NULL; } }