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
+105
-12
@@ -15,6 +15,7 @@
|
||||
#include "admin.h"
|
||||
#include "ftpctl.h"
|
||||
#include "spotpass.h"
|
||||
#include "shopctl.h"
|
||||
|
||||
#define TOP_W 400
|
||||
#define BOT_W 320
|
||||
@@ -33,18 +34,31 @@
|
||||
#define COL_AMBER C2D_Color32(0xF5, 0x9E, 0x0B, 0xFF)
|
||||
#define COL_NINTENDO C2D_Color32(0xE6, 0x00, 0x12, 0xFF)
|
||||
|
||||
// Bedienelemente auf dem unteren Bildschirm. Die Werkzeuge gibt es nur für Admins.
|
||||
enum { BTN_ENABLE, BTN_DISABLE, BTN_DIAG, BTN_FTP, BTN_UNLINK, BTN_SPOTPASS, BTN_REBOOT, BTN_COUNT };
|
||||
// Bedienelemente auf dem unteren Bildschirm. Die Werkzeuge gibt es nur für Admins (mit SELECT),
|
||||
// die Shop-Leiste nur ohne Werkzeuge.
|
||||
enum { BTN_ENABLE, BTN_DISABLE, BTN_DIAG, BTN_FTP, BTN_UNLINK, BTN_SPOTPASS, BTN_REBOOT, BTN_SHOP, BTN_SHOP_DEL, BTN_COUNT };
|
||||
|
||||
typedef struct { float x, y, w, h; } Box;
|
||||
|
||||
#define TILE_Y 14
|
||||
#define TILE_W 142
|
||||
#define TILE_H_FULL 210
|
||||
#define TILE_H_FULL 166
|
||||
#define TILE_H_ADMIN 160
|
||||
#define TOOL_COUNT 5
|
||||
static const Box toolBoxes[TOOL_COUNT] = { { 12, 184, 56, 42 }, { 72, 184, 56, 42 }, { 132, 184, 56, 42 }, { 192, 184, 56, 42 }, { 252, 184, 56, 42 } };
|
||||
|
||||
static const Box shopBox = { 12, 188, 216, 40 }, shopDelBox = { 232, 188, 76, 40 };
|
||||
static int shopVersion = -1; // installierte Version des MBMNET Shops (-1 = nicht installiert)
|
||||
|
||||
// Welche Knöpfe sind gerade da?
|
||||
static bool visible(int btn, bool admin)
|
||||
{
|
||||
if (btn <= BTN_DISABLE) return true;
|
||||
if (btn <= BTN_REBOOT) return admin;
|
||||
if (btn == BTN_SHOP) return !admin;
|
||||
return !admin && shopVersion >= 0; // Entfernen nur, wenn installiert
|
||||
}
|
||||
|
||||
static Box box_of(int btn, bool admin)
|
||||
{
|
||||
float h = admin ? TILE_H_ADMIN : TILE_H_FULL;
|
||||
@@ -55,6 +69,8 @@ static Box box_of(int btn, bool admin)
|
||||
case BTN_FTP: return toolBoxes[1];
|
||||
case BTN_UNLINK: return toolBoxes[2];
|
||||
case BTN_SPOTPASS: return toolBoxes[3];
|
||||
case BTN_SHOP: return shopBox;
|
||||
case BTN_SHOP_DEL: return shopDelBox;
|
||||
default: return toolBoxes[4];
|
||||
}
|
||||
}
|
||||
@@ -189,12 +205,47 @@ static void draw_tile(int btn, bool sel, bool active, bool admin)
|
||||
}
|
||||
}
|
||||
|
||||
// Kleiner Einkaufswagen (wie das Symbol des MBMNET Shops)
|
||||
static void draw_cart(float x, float y, float size)
|
||||
{
|
||||
float s = size / 100.0f, t = 9 * s;
|
||||
round_rect(x, y, size, size, 22 * s, COL_RED);
|
||||
C2D_DrawLine(x + 13 * s, y + 23 * s, COL_WHITE, x + 23 * s, y + 23 * s, COL_WHITE, t, 0.5f);
|
||||
C2D_DrawLine(x + 23 * s, y + 23 * s, COL_WHITE, x + 33 * s, y + 61 * s, COL_WHITE, t, 0.5f);
|
||||
C2D_DrawLine(x + 33 * s, y + 61 * s, COL_WHITE, x + 30 * s, y + 70 * s, COL_WHITE, t, 0.5f);
|
||||
C2D_DrawLine(x + 30 * s, y + 70 * s, COL_WHITE, x + 78 * s, y + 70 * s, COL_WHITE, t, 0.5f);
|
||||
C2D_DrawTriangle(x + 27 * s, y + 33 * s, COL_WHITE, x + 84 * s, y + 33 * s, COL_WHITE, x + 77 * s, y + 61 * s, COL_WHITE, 0.5f);
|
||||
C2D_DrawTriangle(x + 27 * s, y + 33 * s, COL_WHITE, x + 77 * s, y + 61 * s, COL_WHITE, x + 33 * s, y + 61 * s, COL_WHITE, 0.5f);
|
||||
C2D_DrawCircleSolid(x + 37 * s, y + 80 * s, 0.5f, 7 * s, COL_WHITE);
|
||||
C2D_DrawCircleSolid(x + 72 * s, y + 80 * s, 0.5f, 7 * s, COL_WHITE);
|
||||
}
|
||||
|
||||
static void draw_shop_bar(int selected)
|
||||
{
|
||||
bool busy = shopctl_job() == SHOPJOB_RUNNING;
|
||||
bool outdated = shopVersion >= 0 && shopVersion < APP_TITLE_VERSION;
|
||||
Box b = shopBox;
|
||||
bool sel = selected == BTN_SHOP;
|
||||
round_rect(b.x, b.y, b.w, b.h, 10, sel ? COL_CARD_HI : COL_CARD);
|
||||
if (sel) C2D_DrawRectSolid(b.x, b.y + 10, 0.5f, 4, b.h - 20, COL_RED);
|
||||
draw_cart(b.x + 10, b.y + 7, 26);
|
||||
const char *label = busy ? "Shop wird installiert ..." : shopVersion < 0 ? "MBMNET Shop installieren"
|
||||
: outdated ? "Shop aktualisieren" : "MBMNET Shop öffnen";
|
||||
text(label, b.x + 44, b.y + 11, 0.5f, busy ? COL_AMBER : COL_WHITE, 0);
|
||||
if (shopVersion < 0) return;
|
||||
b = shopDelBox;
|
||||
sel = selected == BTN_SHOP_DEL;
|
||||
round_rect(b.x, b.y, b.w, b.h, 10, sel ? COL_CARD_HI : COL_CARD);
|
||||
if (sel) C2D_DrawRectSolid(b.x, b.y + 10, 0.5f, 4, b.h - 20, COL_RED);
|
||||
text("Entfernen", b.x + b.w / 2, b.y + 12, 0.42f, COL_MUTED, C2D_AlignCenter);
|
||||
}
|
||||
|
||||
static void draw_bottom(int selected, PatchState st, bool admin)
|
||||
{
|
||||
C2D_DrawRectSolid(0, 0, 0, BOT_W, SCR_H, COL_BG);
|
||||
draw_tile(BTN_ENABLE, selected == BTN_ENABLE, st == STATE_MBMNET, admin);
|
||||
draw_tile(BTN_DISABLE, selected == BTN_DISABLE, st == STATE_ORIGINAL, admin);
|
||||
if (!admin) return;
|
||||
if (!admin) { draw_shop_bar(selected); return; }
|
||||
|
||||
const char *labels[TOOL_COUNT] = { "Diagnose", ftpOn ? "FTP aus" : "FTP an", "ID lösen", "SpotPass", "Neustart" };
|
||||
for (int i = 0; i < TOOL_COUNT; i++) {
|
||||
@@ -219,6 +270,7 @@ static void draw_bottom(int selected, PatchState st, bool admin)
|
||||
static bool old_mbmnet_link(void) { FILE *f = fopen(UNLINKED_MARKER, "r"); if (f) fclose(f); return f != NULL; }
|
||||
|
||||
static int armed = -1; // Sicherheitsabfrage (zweimal antippen)
|
||||
static bool launchShop; // Sprung in den MBMNET Shop vorbereitet: App beenden
|
||||
|
||||
// Name der MBMNET-ID, gemerkt beim Wechsel zu Nintendo. Der 3DS erlaubt dieselbe ID nicht in zwei Konto-Plätzen
|
||||
// (022-2571): Heißt die Pretendo-ID genauso, muss sie beim Wechsel zurück zu MBMNET dort gelöst werden.
|
||||
@@ -354,7 +406,7 @@ static bool run(int action, char *msg, size_t msglen)
|
||||
bool confirmed = armed == action;
|
||||
armed = -1;
|
||||
// Während ein Update lädt oder installiert wird, nicht umschalten (der 3DS würde mittendrin neu starten)
|
||||
if (action != BTN_DIAG && action != BTN_FTP && action != BTN_SPOTPASS && update_pending()) {
|
||||
if (action != BTN_DIAG && action != BTN_FTP && action != BTN_SPOTPASS && action < BTN_SHOP && update_pending()) {
|
||||
snprintf(msg, msglen, "Moment: Das Update auf v%s wird gerade installiert.\nDanach startet die App neu.", update_new_version());
|
||||
return false;
|
||||
}
|
||||
@@ -386,6 +438,30 @@ static bool run(int action, char *msg, size_t msglen)
|
||||
if (spotpass_test_start(MBMNET_SERVER, msg, msglen)) spotpassTestFrames = 0;
|
||||
return false;
|
||||
case BTN_REBOOT: return true;
|
||||
case BTN_SHOP:
|
||||
if (shopctl_job() == SHOPJOB_RUNNING) { snprintf(msg, msglen, "Der Shop wird gerade installiert ..."); return false; }
|
||||
if (shopVersion >= 0 && shopVersion >= APP_TITLE_VERSION) {
|
||||
if (shopctl_launch(msg, msglen)) launchShop = true;
|
||||
return false;
|
||||
}
|
||||
if (shopVersion < 0 && !confirmed) {
|
||||
armed = BTN_SHOP;
|
||||
snprintf(msg, msglen, "MBMNET Shop installieren?\nEine eigene App mit Homebrew aus der Universal-DB\n"
|
||||
"(Spiele, Tools, Emulatoren). Zum Bestätigen nochmal antippen.");
|
||||
return false;
|
||||
}
|
||||
shopctl_install(MBMNET_SERVER);
|
||||
snprintf(msg, msglen, shopVersion < 0 ? "Installiere den MBMNET Shop ..." : "Aktualisiere den MBMNET Shop ...");
|
||||
return false;
|
||||
case BTN_SHOP_DEL:
|
||||
if (shopVersion < 0) return false;
|
||||
if (!confirmed) {
|
||||
armed = BTN_SHOP_DEL;
|
||||
snprintf(msg, msglen, "MBMNET Shop entfernen?\nApps, die du darüber installiert hast, bleiben.\nZum Bestätigen nochmal antippen.");
|
||||
return false;
|
||||
}
|
||||
if (shopctl_remove(msg, msglen)) shopVersion = -1;
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -396,8 +472,8 @@ int main(int argc, char **argv)
|
||||
{
|
||||
gfxInitDefault();
|
||||
httpcInit(0);
|
||||
// Als installierte CIA: am:net zum Installieren von Updates
|
||||
bool am = !envIsHomebrew() && R_SUCCEEDED(amInit());
|
||||
// am:net: Updates (als CIA) und den MBMNET Shop installieren. Unter Luma auch als .3dsx erlaubt.
|
||||
bool am = R_SUCCEEDED(amInit());
|
||||
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
|
||||
C2D_Init(C2D_DEFAULT_MAX_OBJECTS);
|
||||
C2D_Prepare();
|
||||
@@ -444,6 +520,9 @@ int main(int argc, char **argv)
|
||||
}
|
||||
// Das Hintergrund-Modul ist abgeschaltet (Kernel-Absturz): Reste wegräumen, nie wieder starten
|
||||
ftpctl_cleanup_module();
|
||||
// MBMNET Shop: installiert? Ältere Version gleich im Hintergrund aktualisieren.
|
||||
shopVersion = am ? shopctl_version() : -1;
|
||||
if (shopVersion >= 0 && shopVersion < APP_TITLE_VERSION) shopctl_install(server);
|
||||
ftpOn = ftpctl_running();
|
||||
int ftpCheckIn = 120;
|
||||
|
||||
@@ -459,22 +538,26 @@ int main(int argc, char **argv)
|
||||
if (down & KEY_SELECT) toolsToggled = !toolsToggled;
|
||||
// Werkzeuge nur mit SELECT, beim Öffnen immer aus
|
||||
bool admin = toolsToggled;
|
||||
if (!admin && selected >= BTN_DIAG) selected = BTN_ENABLE;
|
||||
if (!visible(selected, admin)) selected = admin || selected <= BTN_REBOOT ? BTN_ENABLE : BTN_SHOP;
|
||||
|
||||
int action = -1;
|
||||
// Oben die beiden Kacheln, unten (nur mit Werkzeugen) Diagnose / FTP / ID lösen / SpotPass / Neustart
|
||||
bool left = down & (KEY_LEFT | KEY_CPAD_LEFT), right = down & (KEY_RIGHT | KEY_CPAD_RIGHT);
|
||||
bool up = down & (KEY_UP | KEY_CPAD_UP), dn = down & (KEY_DOWN | KEY_CPAD_DOWN);
|
||||
if (selected < BTN_DIAG) { if (left || right) selected ^= 1; }
|
||||
else if (selected >= BTN_SHOP) { if ((left || right) && shopVersion >= 0) selected = selected == BTN_SHOP ? BTN_SHOP_DEL : BTN_SHOP; }
|
||||
else if (left) selected = selected == BTN_DIAG ? BTN_REBOOT : selected - 1;
|
||||
else if (right) selected = selected == BTN_REBOOT ? BTN_DIAG : selected + 1;
|
||||
if (admin && down & (KEY_DOWN | KEY_CPAD_DOWN) && selected < BTN_DIAG) selected = selected == BTN_ENABLE ? BTN_DIAG : BTN_REBOOT;
|
||||
if (down & (KEY_UP | KEY_CPAD_UP) && selected >= BTN_DIAG) selected = selected == BTN_REBOOT ? BTN_DISABLE : BTN_ENABLE;
|
||||
if (dn && selected < BTN_DIAG) selected = admin ? (selected == BTN_ENABLE ? BTN_DIAG : BTN_REBOOT)
|
||||
: (selected == BTN_DISABLE && shopVersion >= 0 ? BTN_SHOP_DEL : BTN_SHOP);
|
||||
else if (up && selected >= BTN_SHOP) selected = selected == BTN_SHOP_DEL ? BTN_DISABLE : BTN_ENABLE;
|
||||
else if (up && selected >= BTN_DIAG) selected = selected == BTN_REBOOT ? BTN_DISABLE : BTN_ENABLE;
|
||||
if (down & KEY_A) action = selected;
|
||||
if (down & KEY_TOUCH) {
|
||||
touchPosition tp;
|
||||
hidTouchRead(&tp);
|
||||
for (int i = 0; i < (admin ? BTN_COUNT : BTN_DIAG); i++)
|
||||
if (inside(box_of(i, admin), tp)) selected = action = i;
|
||||
for (int i = 0; i < BTN_COUNT; i++)
|
||||
if (visible(i, admin) && inside(box_of(i, admin), tp)) selected = action = i;
|
||||
}
|
||||
if (queued >= 0) {
|
||||
int a = queued;
|
||||
@@ -506,6 +589,15 @@ int main(int argc, char **argv)
|
||||
}
|
||||
lastUpd = upd;
|
||||
|
||||
// MBMNET Shop fertig installiert (oder fehlgeschlagen)
|
||||
ShopJob sj = shopctl_job();
|
||||
if (sj == SHOPJOB_DONE || sj == SHOPJOB_FAILED) {
|
||||
snprintf(msg, sizeof(msg), "%s", shopctl_message());
|
||||
shopctl_acknowledge();
|
||||
shopVersion = shopctl_version();
|
||||
}
|
||||
if (launchShop) break;
|
||||
|
||||
// SpotPass-Test: jede Sekunde nachsehen
|
||||
if (spotpassTestFrames >= 0 && ++spotpassTestFrames % 60 == 0 &&
|
||||
!spotpass_test_poll(spotpassTestFrames / 60, msg, sizeof(msg)))
|
||||
@@ -533,6 +625,7 @@ int main(int argc, char **argv)
|
||||
}
|
||||
|
||||
ftpctl_stop(NULL, 0);
|
||||
shopctl_exit();
|
||||
admin_finish();
|
||||
update_finish();
|
||||
frdswitch_exit();
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <3ds/types.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Der MBMNET Shop ist eine eigene App (shop/). Die MBMNET-App installiert ihn nur, wenn man das will.
|
||||
#define SHOP_TITLE_ID 0x000400000FB4E100ULL
|
||||
|
||||
typedef enum { SHOPJOB_IDLE, SHOPJOB_RUNNING, SHOPJOB_DONE, SHOPJOB_FAILED } ShopJob;
|
||||
|
||||
// Installierte Version (Titelversion) oder -1, wenn der Shop nicht installiert ist
|
||||
int shopctl_version(void);
|
||||
// Lädt den Shop vom MBMNET-Server und installiert ihn (im Hintergrund, auch zum Aktualisieren)
|
||||
void shopctl_install(const char *server);
|
||||
bool shopctl_remove(char *msg, size_t len);
|
||||
// Startet den Shop (die MBMNET-App beendet sich danach). false = ging nicht, Grund in msg.
|
||||
bool shopctl_launch(char *msg, size_t len);
|
||||
ShopJob shopctl_job(void);
|
||||
const char *shopctl_message(void);
|
||||
void shopctl_acknowledge(void);
|
||||
void shopctl_exit(void);
|
||||
Reference in New Issue
Block a user