v2.9.7: Shop - Auswahl CIA oder 3DSX, Katalog-Leser ohne Pufferüberlauf

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nico Meister
2026-10-19 18:40:00 +02:00
co-authored by Claude Opus 5.5
parent 93316ec2c9
commit f05fd2fc16
4 changed files with 126 additions and 34 deletions
+81 -27
View File
@@ -13,6 +13,7 @@
#include "net.h"
#include "store.h"
#define COPY(dst, src) snprintf(dst, sizeof(dst), "%s", src)
#define LIST_FILE SHOP_DIR "/list.txt"
#define INSTALLED_FILE SHOP_DIR "/installed.txt"
#define MAX_SHEETS 8
@@ -37,6 +38,35 @@ App *catalog_app(int i) { return &apps[i]; }
int catalog_sheet_count(void) { return sheetCount; }
const char *catalog_sheet_path(int n) { return n >= 0 && n < sheetCount ? sheetPath[n] : ""; }
// Bevorzugte Variante, wenn es beide gibt (zuletzt gewählt, Standard CIA)
#define KIND_FILE SHOP_DIR "/format.txt"
static char preferredKind[6] = "cia";
static void load_preferred_kind(void)
{
FILE *f = fopen(KIND_FILE, "r");
if (!f) return;
char k[8] = "";
if (fgets(k, sizeof(k), f) && !strncmp(k, "3dsx", 4)) strcpy(preferredKind, "3dsx");
fclose(f);
}
bool app_has_both(const App *a) { return a->ciaFile[0] && a->dsxFile[0]; }
void app_choose(App *a, const char *kind, bool remember)
{
bool cia = !strcmp(kind, "cia");
if (cia ? !a->ciaFile[0] : !a->dsxFile[0]) return;
COPY(a->kind, cia ? "cia" : "3dsx");
COPY(a->file, cia ? a->ciaFile : a->dsxFile);
a->size = cia ? a->ciaSize : a->dsxSize;
if (remember) {
COPY(preferredKind, a->kind);
FILE *f = fopen(KIND_FILE, "w");
if (f) { fputs(preferredKind, f); fclose(f); }
}
}
AppStatus app_status(const App *a)
{
if (!a->installed) return APP_NEW;
@@ -90,15 +120,14 @@ static int split(char *line, char **fields, int max)
return n;
}
#define COPY(dst, src) snprintf(dst, sizeof(dst), "%s", src)
static bool parse_list(char *text, int *revision)
{
char *save = NULL, *line = strtok_r(text, "\n", &save);
char *f[16];
if (!line || split(line, f, 20) < 5 || strcmp(f[0], "MBMNETSHOP") != 0) return false;
char *f[24];
if (!line || split(line, f, 24) < 5 || strcmp(f[0], "MBMNETSHOP") != 0) return false;
int format = atoi(f[1]);
int fields = format >= 2 ? 17 : 13;
int fields = format >= 3 ? 21 : format >= 2 ? 17 : 13;
*revision = atoi(f[2]);
int count = atoi(f[3]);
sheetCount = atoi(f[4]);
@@ -109,7 +138,7 @@ static bool parse_list(char *text, int *revision)
appCount = 0;
while ((line = strtok_r(NULL, "\n", &save)) && appCount < count) {
if (split(line, f, 20) < fields) continue;
if (split(line, f, 24) < fields) continue;
App *a = &apps[appCount++];
COPY(a->slug, f[0]);
COPY(a->title, f[1]);
@@ -129,7 +158,20 @@ static bool parse_list(char *text, int *revision)
a->colorBg = f[14][0] == '#' ? strtoul(f[14] + 1, NULL, 16) : 0;
COPY(a->license, f[15]);
}
if (format >= 3) {
COPY(a->ciaFile, f[16]);
a->ciaSize = strtoul(f[17], NULL, 10);
COPY(a->dsxFile, f[18]);
a->dsxSize = strtoul(f[19], NULL, 10);
} else if (!strcmp(a->kind, "cia")) {
COPY(a->ciaFile, a->file);
a->ciaSize = a->size;
} else {
COPY(a->dsxFile, a->file);
a->dsxSize = a->size;
}
a->description = strdup(f[fields - 1]);
if (app_has_both(a)) app_choose(a, preferredKind, false);
}
return appCount > 0;
}
@@ -167,8 +209,10 @@ static void load_installed(void)
}
a->installed = true;
COPY(a->installedVersion, f[1]);
COPY(a->instKind, f[2]);
a->titleId = strtoull(f[3], NULL, 16);
if (f[4][0]) COPY(a->file, f[4]);
COPY(a->instFile, f[4]);
app_choose(a, a->instKind, false); // die Seite zeigt die installierte Variante
}
free(text);
}
@@ -180,7 +224,7 @@ static void save_installed(void)
for (int i = 0; i < appCount; i++) {
App *a = &apps[i];
if (a->installed)
fprintf(f, "%s\t%s\t%s\t%016llX\t%s\n", a->slug, a->installedVersion, a->kind, a->titleId, a->file);
fprintf(f, "%s\t%s\t%s\t%016llX\t%s\n", a->slug, a->installedVersion, a->instKind, a->titleId, a->instFile);
}
if (extraInstalled) fputs(extraInstalled, f);
fclose(f);
@@ -215,7 +259,8 @@ static void catalog_worker(void *arg)
mkdir(SHOP_DIR, 0777);
snprintf(catMsg, sizeof(catMsg), "Lade den Katalog ...");
snprintf(url, sizeof(url), "http://%s/shop/list2", SHOP_SERVER);
snprintf(url, sizeof(url), "http://%s/shop/list3", SHOP_SERVER);
load_preferred_kind();
u32 status = net_get(url, &data, &size, 4 * 1024 * 1024, &rc);
char *text = NULL;
if (status == 200 && size > 10) {
@@ -372,13 +417,32 @@ static bool install_sink(void *p, const u8 *data, u32 len, u32 total)
return write_part(in, data, len);
}
// Installierte Variante entfernen (CIA über AM, .3dsx von der SD). Schon von Hand gelöscht zählt als Erfolg.
static Result remove_files(const App *a)
{
if (strcmp(a->instKind, "cia") == 0) {
if (!a->titleId) return 0;
Result rc = AM_DeleteTitle(MEDIATYPE_SD, a->titleId);
AM_TitleEntry info;
u64 id = a->titleId;
if (R_FAILED(rc) && R_FAILED(AM_GetTitleInfo(MEDIATYPE_SD, 1, &id, &info))) rc = 0;
return rc;
}
if (a->instFile[0]) {
char path[128];
snprintf(path, sizeof(path), "sdmc:/3ds/%s", a->instFile);
remove(path);
}
return 0;
}
static void install_worker(App *a)
{
char url[192];
Install in = { .cia = strcmp(a->kind, "cia") == 0 };
in.head = malloc(HEAD_SIZE);
if (!in.head) { snprintf(jobMsg, sizeof(jobMsg), "Zu wenig Speicher."); jobState = JOB_FAILED; return; }
snprintf(url, sizeof(url), "http://%s/shop/get/%s", SHOP_SERVER, a->slug);
snprintf(url, sizeof(url), "http://%s/shop/get/%s/%s", SHOP_SERVER, a->slug, a->kind);
Result rc = 0;
u32 status = net_stream(url, install_sink, &in, &rc);
if (status == 200 && !in.started && !in.error) in.error = "Die Datei ist keine gültige App.";
@@ -403,9 +467,13 @@ static void install_worker(App *a)
if (ok) {
bool update = a->installed;
// Vorher die andere Variante installiert? Die alte wegräumen (nur eine von beiden behalten)
if (a->installed && strcmp(a->instKind, a->kind) != 0) remove_files(a);
a->installed = true;
COPY(a->installedVersion, a->version);
if (in.cia) a->titleId = cia_title_id(in.head, in.headLen);
COPY(a->instKind, a->kind);
COPY(a->instFile, a->file);
a->titleId = in.cia ? cia_title_id(in.head, in.headLen) : 0;
save_installed();
snprintf(jobMsg, sizeof(jobMsg), in.cia ? "%s %s!\nDu findest die App jetzt im HOME-Menü."
: "%s %s!\nStarte sie über den Homebrew Launcher (/3ds/%s).",
@@ -423,25 +491,11 @@ static void install_worker(App *a)
static void remove_worker(App *a)
{
bool ok = true;
Result rc = 0;
if (strcmp(a->kind, "cia") == 0) {
if (a->titleId) {
rc = AM_DeleteTitle(MEDIATYPE_SD, a->titleId);
// schon von Hand gelöscht? Dann ist es auch gut.
AM_TitleEntry info;
u64 id = a->titleId;
if (R_FAILED(rc) && R_FAILED(AM_GetTitleInfo(MEDIATYPE_SD, 1, &id, &info))) rc = 0;
ok = R_SUCCEEDED(rc);
}
} else {
char path[128];
snprintf(path, sizeof(path), "sdmc:/3ds/%s", a->file);
remove(path);
}
Result rc = remove_files(a);
bool ok = R_SUCCEEDED(rc);
if (ok) {
a->installed = false;
a->installedVersion[0] = 0;
a->installedVersion[0] = a->instKind[0] = a->instFile[0] = 0;
a->titleId = 0;
save_installed();
snprintf(jobMsg, sizeof(jobMsg), "%s entfernt.", a->title);