v2.9.0: MBMNET Shop (eigene App, auf Wunsch installierbar)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d264cd93cd
commit
ca77952a9d
@@ -0,0 +1,131 @@
|
||||
// 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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; }
|
||||
}
|
||||
Reference in New Issue
Block a user