MBMNET-App v2.8.2: Quellcode (GPLv3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+547
@@ -0,0 +1,547 @@
|
||||
// MBMNET Switcher - schaltet den 3DS zwischen MBMNET und Nintendo (Original) um.
|
||||
//
|
||||
// Unten zwei Kacheln: MBMNET und Nintendo. Antippen schaltet um, unter der aktiven steht "Aktiv".
|
||||
// SELECT blendet die Entwickler-Werkzeuge ein und aus (beim Öffnen immer aus).
|
||||
#include <3ds.h>
|
||||
#include <citro2d.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "patcher.h"
|
||||
#include "update.h"
|
||||
#include "relaunch.h"
|
||||
#include "nnid.h"
|
||||
#include "frdswitch.h"
|
||||
#include "admin.h"
|
||||
#include "ftpctl.h"
|
||||
#include "spotpass.h"
|
||||
|
||||
#define TOP_W 400
|
||||
#define BOT_W 320
|
||||
#define SCR_H 240
|
||||
|
||||
// Farben (gleiche Palette wie Logo und Webseite)
|
||||
#define COL_RED C2D_Color32(0xE4, 0x00, 0x2B, 0xFF)
|
||||
#define COL_RED_DARK C2D_Color32(0x9E, 0x00, 0x1E, 0xFF)
|
||||
#define COL_BG C2D_Color32(0x16, 0x19, 0x1F, 0xFF)
|
||||
#define COL_BG2 C2D_Color32(0x22, 0x26, 0x2E, 0xFF)
|
||||
#define COL_CARD C2D_Color32(0x2A, 0x2F, 0x39, 0xFF)
|
||||
#define COL_CARD_HI C2D_Color32(0x3A, 0x41, 0x4E, 0xFF)
|
||||
#define COL_WHITE C2D_Color32(0xFF, 0xFF, 0xFF, 0xFF)
|
||||
#define COL_MUTED C2D_Color32(0x9A, 0xA3, 0xAF, 0xFF)
|
||||
#define COL_GREEN C2D_Color32(0x22, 0xC5, 0x5E, 0xFF)
|
||||
#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 };
|
||||
|
||||
typedef struct { float x, y, w, h; } Box;
|
||||
|
||||
#define TILE_Y 14
|
||||
#define TILE_W 142
|
||||
#define TILE_H_FULL 210
|
||||
#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 Box box_of(int btn, bool admin)
|
||||
{
|
||||
float h = admin ? TILE_H_ADMIN : TILE_H_FULL;
|
||||
switch (btn) {
|
||||
case BTN_ENABLE: return (Box){ 12, TILE_Y, TILE_W, h };
|
||||
case BTN_DISABLE: return (Box){ 166, TILE_Y, TILE_W, h };
|
||||
case BTN_DIAG: return toolBoxes[0];
|
||||
case BTN_FTP: return toolBoxes[1];
|
||||
case BTN_UNLINK: return toolBoxes[2];
|
||||
case BTN_SPOTPASS: return toolBoxes[3];
|
||||
default: return toolBoxes[4];
|
||||
}
|
||||
}
|
||||
|
||||
static C2D_TextBuf textBuf;
|
||||
static int spotpassTestFrames = -1; // läuft der SpotPass-Test? (Bilder seit dem Start)
|
||||
static bool ftpOn; // läuft das FTP-Modul? (alle 2 s geprüft, nicht in jedem Bild)
|
||||
|
||||
// --- Zeichenhilfen ------------------------------------------------------------
|
||||
|
||||
static void text(const char *s, float x, float y, float size, u32 color, u32 flags)
|
||||
{
|
||||
C2D_Text t;
|
||||
C2D_TextParse(&t, textBuf, s);
|
||||
C2D_TextOptimize(&t);
|
||||
C2D_DrawText(&t, C2D_WithColor | flags, x, y, 0.5f, size, size, color);
|
||||
}
|
||||
|
||||
static void round_rect(float x, float y, float w, float h, float r, u32 color)
|
||||
{
|
||||
C2D_DrawRectSolid(x + r, y, 0.5f, w - 2 * r, h, color);
|
||||
C2D_DrawRectSolid(x, y + r, 0.5f, w, h - 2 * r, color);
|
||||
C2D_DrawCircleSolid(x + r, y + r, 0.5f, r, color);
|
||||
C2D_DrawCircleSolid(x + w - r, y + r, 0.5f, r, color);
|
||||
C2D_DrawCircleSolid(x + r, y + h - r, 0.5f, r, color);
|
||||
C2D_DrawCircleSolid(x + w - r, y + h - r, 0.5f, r, color);
|
||||
}
|
||||
|
||||
// Das MBMNET-Logo: rotes abgerundetes Quadrat mit einem "M" aus verbundenen Netzwerk-Knoten.
|
||||
// Geometrie identisch zu assets/logo.svg (100x100-Raster).
|
||||
static void draw_logo(float x, float y, float size)
|
||||
{
|
||||
float s = size / 100.0f;
|
||||
round_rect(x, y, size, size, 22 * s, COL_RED_DARK);
|
||||
round_rect(x, y, size, size - 4 * s, 22 * s, COL_RED);
|
||||
|
||||
static const float pts[5][2] = { {24, 72}, {24, 30}, {50, 56}, {76, 30}, {76, 72} };
|
||||
for (int i = 0; i < 4; i++) {
|
||||
C2D_DrawLine(x + pts[i][0] * s, y + pts[i][1] * s, COL_WHITE,
|
||||
x + pts[i + 1][0] * s, y + pts[i + 1][1] * s, COL_WHITE, 7 * s, 0.5f);
|
||||
}
|
||||
for (int i = 0; i < 5; i++) {
|
||||
C2D_DrawCircleSolid(x + pts[i][0] * s, y + pts[i][1] * s, 0.5f, 9 * s, COL_WHITE);
|
||||
if (i == 2) C2D_DrawCircleSolid(x + pts[i][0] * s, y + pts[i][1] * s, 0.5f, 4 * s, COL_RED);
|
||||
}
|
||||
}
|
||||
|
||||
// Nintendo-Schriftzug im roten Rahmen (nachgezeichnet, kein Bild)
|
||||
static void draw_nintendo(float cx, float cy, float w)
|
||||
{
|
||||
float h = w * 0.36f, r = h / 2;
|
||||
round_rect(cx - w / 2, cy - h / 2, w, h, r, COL_NINTENDO);
|
||||
round_rect(cx - w / 2 + 3, cy - h / 2 + 3, w - 6, h - 6, r - 3, COL_WHITE);
|
||||
text("Nintendo", cx, cy - h * 0.36f, w / 150.0f, COL_NINTENDO, C2D_AlignCenter);
|
||||
}
|
||||
|
||||
// --- Bildschirme ---------------------------------------------------------------
|
||||
|
||||
static void draw_update_line(bool admin)
|
||||
{
|
||||
char line[64];
|
||||
const char *v = update_new_version();
|
||||
switch (update_state()) {
|
||||
case UPD_CHECKING: snprintf(line, sizeof(line), "v" APP_VERSION); break;
|
||||
case UPD_AVAILABLE: snprintf(line, sizeof(line), "Update auf v%s bereit", v); break;
|
||||
case UPD_DOWNLOADING:
|
||||
if (update_attempt() > 1) snprintf(line, sizeof(line), "lade v%s (Versuch %d) ...", v, update_attempt());
|
||||
else snprintf(line, sizeof(line), "lade v%s ...", v);
|
||||
break;
|
||||
case UPD_INSTALLED: snprintf(line, sizeof(line), "v%s installiert", v); break;
|
||||
case UPD_ERROR: snprintf(line, sizeof(line), admin ? "Update-Server offline" : "v" APP_VERSION); break;
|
||||
default: snprintf(line, sizeof(line), "v" APP_VERSION); break;
|
||||
}
|
||||
u32 color = update_state() == UPD_INSTALLED ? COL_GREEN : update_pending() ? COL_AMBER : COL_MUTED;
|
||||
text(line, TOP_W - 10, 4, 0.4f, color, C2D_AlignRight);
|
||||
}
|
||||
|
||||
static void draw_top(PatchState st, const char *msg, bool admin, bool isAdmin)
|
||||
{
|
||||
C2D_DrawRectangle(0, 0, 0, TOP_W, SCR_H, COL_BG2, COL_BG2, COL_BG, COL_BG);
|
||||
draw_update_line(admin);
|
||||
const char *hint = admin ? "START: Beenden SELECT: Werkzeuge aus" : "START: Beenden SELECT: Werkzeuge";
|
||||
text(hint, 10, 4, 0.4f, COL_MUTED, 0);
|
||||
if (ftpOn) {
|
||||
// FTP-Adresse direkt daneben
|
||||
C2D_Text t;
|
||||
float w = 0;
|
||||
C2D_TextParse(&t, textBuf, hint);
|
||||
C2D_TextGetDimensions(&t, 0.4f, 0.4f, &w, NULL);
|
||||
char ftp[112];
|
||||
const char *a = ftpctl_address();
|
||||
const char *st = ftpctl_status();
|
||||
if (a[0]) snprintf(ftp, sizeof(ftp), "FTP %s", a);
|
||||
else snprintf(ftp, sizeof(ftp), "FTP: %s", st[0] ? st : "startet ...");
|
||||
text(ftp, 10 + w + 12, 4, 0.4f, COL_GREEN, 0);
|
||||
}
|
||||
|
||||
float y = msg[0] || admin ? 26 : 62;
|
||||
draw_logo(TOP_W / 2 - 32, y, 64);
|
||||
text("MBMNET", TOP_W / 2, y + 70, 0.9f, COL_WHITE, C2D_AlignCenter);
|
||||
y += 108;
|
||||
|
||||
// Admins: was gerade wirklich los ist
|
||||
if (admin) {
|
||||
char line[96], id[20];
|
||||
const char *label = st == STATE_MBMNET ? "Patch: MBMNET" : st == STATE_FOREIGN ? "Patch: fremd (z. B. Pretendo)" : "Patch: keiner";
|
||||
snprintf(line, sizeof(line), "%s · NNID: %s%s", label, nnid_linked(id, sizeof(id)) ? id : "keine", isAdmin ? " · Admin" : "");
|
||||
text(line, TOP_W / 2, y, 0.42f, COL_MUTED, C2D_AlignCenter);
|
||||
y += 18;
|
||||
}
|
||||
if (msg[0]) {
|
||||
round_rect(16, y, TOP_W - 32, SCR_H - y - 8, 10, COL_CARD);
|
||||
text(msg, 28, y + 6, 0.46f, COL_WHITE, 0);
|
||||
} else if (!admin) {
|
||||
text("Tippe unten auf MBMNET oder Nintendo.", TOP_W / 2, 200, 0.45f, COL_MUTED, C2D_AlignCenter);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_tile(int btn, bool sel, bool active, bool admin)
|
||||
{
|
||||
Box b = box_of(btn, admin);
|
||||
if (sel) round_rect(b.x - 3, b.y - 3, b.w + 6, b.h + 6, 15, COL_RED);
|
||||
round_rect(b.x, b.y, b.w, b.h, 12, active ? COL_CARD_HI : COL_CARD);
|
||||
|
||||
float cx = b.x + b.w / 2, logoCy = b.y + b.h / 2 - 12;
|
||||
if (btn == BTN_ENABLE) draw_logo(cx - 36, logoCy - 36, 72);
|
||||
else draw_nintendo(cx, logoCy, 116);
|
||||
|
||||
if (active) {
|
||||
C2D_DrawCircleSolid(cx - 26, b.y + b.h - 26, 0.5f, 5, COL_GREEN);
|
||||
text("Aktiv", cx + 6, b.y + b.h - 36, 0.6f, COL_GREEN, 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;
|
||||
|
||||
const char *labels[TOOL_COUNT] = { "Diagnose", ftpOn ? "FTP aus" : "FTP an", "ID lösen", "SpotPass", "Neustart" };
|
||||
for (int i = 0; i < TOOL_COUNT; i++) {
|
||||
Box b = toolBoxes[i];
|
||||
bool sel = selected == BTN_DIAG + i;
|
||||
bool on = i == 1 && ftpOn;
|
||||
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);
|
||||
if (on) C2D_DrawCircleSolid(b.x + 12, b.y + b.h / 2, 0.5f, 4, COL_GREEN);
|
||||
text(labels[i], b.x + b.w / 2, b.y + 13, 0.45f, on ? COL_GREEN : COL_WHITE, C2D_AlignCenter);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Aktionen --------------------------------------------------------------------
|
||||
//
|
||||
// Wie bei Pretendo hat MBMNET ein eigenes Freundekonto (Nr. 3) mit eigenem Konto-Platz. Umschalten =
|
||||
// Patches an/aus + auf das andere Freundekonto wechseln + Neustart. Beide Anmeldungen bleiben dabei
|
||||
// erhalten, die Nintendo-Freundesliste auch.
|
||||
|
||||
// Merker älterer Versionen (bis v2.4): Die MBMNET-ID wurde damals im Nintendo-Platz verknüpft
|
||||
#define UNLINKED_MARKER "sdmc:/3ds/mbmnet/nnid-geloest"
|
||||
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)
|
||||
|
||||
// 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.
|
||||
#define MBMNET_ID_FILE "sdmc:/3ds/mbmnet/mbmnet-id.txt"
|
||||
static void remember_mbmnet_id(const char *id)
|
||||
{
|
||||
FILE *f = fopen(MBMNET_ID_FILE, "w");
|
||||
if (f) { fputs(id, f); fclose(f); }
|
||||
}
|
||||
static void remembered_mbmnet_id(char *out, size_t len)
|
||||
{
|
||||
out[0] = 0;
|
||||
FILE *f = fopen(MBMNET_ID_FILE, "r");
|
||||
if (!f) return;
|
||||
if (fgets(out, len, f)) out[strcspn(out, "\r\n")] = 0;
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// Umschalten auf MBMNET. true = der 3DS muss jetzt neu starten.
|
||||
static bool enable(bool confirmed, char *msg, size_t msglen)
|
||||
{
|
||||
char detail[160] = "", id[20];
|
||||
if (patch_state() == STATE_MBMNET && frdswitch_current() == FRIEND_ACCOUNT_MBMNET) {
|
||||
// Freundekonto mit fremder Kennung (z. B. von Pretendo geholt): neu anlegen, dann holt es sich
|
||||
// beim nächsten Start eine Kennung bei MBMNET
|
||||
u32 pid = frdswitch_my_pid();
|
||||
if (pid && !frdswitch_pid_is_mbmnet(pid)) {
|
||||
if (!confirmed) {
|
||||
armed = BTN_ENABLE;
|
||||
snprintf(msg, msglen, "Dein MBMNET-Freundekonto hat eine fremde Kennung\n(%lu, nicht von MBMNET). Neu anlegen?\n"
|
||||
"Die Verknüpfung mit deinem MBMNET-Konto bleibt.\nZum Bestätigen nochmal antippen.", (unsigned long)pid);
|
||||
return false;
|
||||
}
|
||||
if (!frdswitch_recreate_mbmnet(msg, msglen)) return false;
|
||||
snprintf(msg, msglen, "MBMNET-Freundekonto neu angelegt. Der 3DS startet neu.\n"
|
||||
"Danach holt es sich bei MBMNET deinen Freundescode.");
|
||||
return true;
|
||||
}
|
||||
bool linked = frdswitch_slot_linked(FRIEND_ACCOUNT_MBMNET, id, sizeof(id));
|
||||
snprintf(msg, msglen, linked ? "MBMNET ist schon aktiv (Konto %s)." : "MBMNET ist schon aktiv.\n"
|
||||
"Jetzt dein MBMNET-Konto verknüpfen:\nEinstellungen > Nintendo Network ID", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Umstieg von älteren Versionen: Dort steckte die MBMNET-ID im Nintendo-Platz. Sie muss dort raus,
|
||||
// sonst lässt sie sich im eigenen MBMNET-Platz nicht verknüpfen (Fehler 022-2571).
|
||||
bool migrate = old_mbmnet_link() && frdswitch_slot_linked(FRIEND_ACCOUNT_NINTENDO, id, sizeof(id));
|
||||
// Die gemerkte MBMNET-ID steckt noch in einem anderen Platz (z. B. Pretendo mit gleichem Namen)?
|
||||
char mbmnetId[20];
|
||||
remembered_mbmnet_id(mbmnetId, sizeof(mbmnetId));
|
||||
u8 mbmnetSlot = frdswitch_slot_of(FRIEND_ACCOUNT_MBMNET);
|
||||
bool sameNameElsewhere = !migrate && mbmnetId[0] && nnid_name_elsewhere(mbmnetId, mbmnetSlot);
|
||||
if (sameNameElsewhere && !confirmed) {
|
||||
armed = BTN_ENABLE;
|
||||
snprintf(msg, msglen, "\"%s\" ist noch in einem anderen Platz verknüpft (z. B. Pretendo).\n"
|
||||
"Der 3DS erlaubt sie nur einmal: Sie wird dort gelöst, damit du sie\nbei MBMNET verknüpfen kannst. Zum Bestätigen nochmal antippen.", mbmnetId);
|
||||
return false;
|
||||
}
|
||||
if (migrate && !confirmed) {
|
||||
armed = BTN_ENABLE;
|
||||
snprintf(msg, msglen, "Umstellung auf ein eigenes MBMNET-Freundekonto:\n\"%s\" wird aus dem Nintendo-Platz gelöst\n"
|
||||
"und danach im MBMNET-Platz neu verknüpft.\nZum Bestätigen nochmal antippen.", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
if (!patch_enable(MBMNET_SERVER, &changed, detail, sizeof(detail))) {
|
||||
snprintf(msg, msglen, "%s", detail);
|
||||
return false;
|
||||
}
|
||||
if (migrate && !nnid_unlink(msg, msglen)) return false;
|
||||
remove(UNLINKED_MARKER);
|
||||
if (sameNameElsewhere && !nnid_unlink_named(mbmnetId, mbmnetSlot, msg, msglen)) {
|
||||
char ignore[160];
|
||||
patch_disable(ignore, sizeof(ignore));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool created = false;
|
||||
if (!frdswitch_to(FRIEND_ACCOUNT_MBMNET, &created, msg, msglen)) {
|
||||
char ignore[160];
|
||||
patch_disable(ignore, sizeof(ignore)); // nichts halb umgestellt lassen
|
||||
return false;
|
||||
}
|
||||
bool linked = frdswitch_slot_linked(FRIEND_ACCOUNT_MBMNET, id, sizeof(id));
|
||||
char ignore[96];
|
||||
spotpass_register(MBMNET_SERVER, ignore, sizeof(ignore)); // Mitteilungen per SpotPass
|
||||
snprintf(msg, msglen, "MBMNET aktiv! Der 3DS startet neu.\n%s",
|
||||
created ? "Eigenes MBMNET-Freundekonto angelegt. Danach einmal:\nEinstellungen > Nintendo Network ID > Vorhandene ID"
|
||||
: linked ? "Deine MBMNET-Anmeldung und -Freundesliste sind wieder da."
|
||||
: "Danach einmal: Einstellungen > Nintendo Network ID");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Zurück zu Nintendo. true = der 3DS muss jetzt neu starten.
|
||||
static bool disable(bool confirmed, char *msg, size_t msglen)
|
||||
{
|
||||
(void)confirmed;
|
||||
char detail[160] = "";
|
||||
PatchState st = patch_state();
|
||||
if (st == STATE_ORIGINAL && frdswitch_current() == FRIEND_ACCOUNT_NINTENDO) {
|
||||
snprintf(msg, msglen, "Nintendo ist schon aktiv.");
|
||||
return false;
|
||||
}
|
||||
if (st == STATE_FOREIGN) {
|
||||
snprintf(msg, msglen, "MBMNET ist nicht aktiv (anderer Patch).");
|
||||
return false;
|
||||
}
|
||||
// MBMNET-ID aus dem MBMNET-Platz lösen und merken: Heißt die Pretendo-ID genauso, ließe sie sich sonst
|
||||
// im Pretendo-Platz nicht verknüpfen (022-2571). Beim Wechsel zurück zu MBMNET neu anmelden.
|
||||
char id[20] = "";
|
||||
bool unlinked = false;
|
||||
if (frdswitch_current() == FRIEND_ACCOUNT_MBMNET && nnid_linked(id, sizeof(id))) {
|
||||
remember_mbmnet_id(id);
|
||||
if (!nnid_unlink(msg, msglen)) return false;
|
||||
unlinked = true;
|
||||
}
|
||||
bool created = false;
|
||||
if (frdswitch_current() != FRIEND_ACCOUNT_NINTENDO && !frdswitch_to(FRIEND_ACCOUNT_NINTENDO, &created, msg, msglen)) return false;
|
||||
patch_disable(detail, sizeof(detail));
|
||||
spotpass_unregister();
|
||||
if (unlinked)
|
||||
snprintf(msg, msglen, "Nintendo aktiv! Der 3DS startet neu.\n\"%s\" wurde vom MBMNET-Platz gelöst (für Pretendo).\n"
|
||||
"Zurück zu MBMNET: danach einmal neu anmelden.", id);
|
||||
else
|
||||
snprintf(msg, msglen, "Nintendo aktiv! Der 3DS startet neu.\nDeine Nintendo-Freundesliste ist wieder da.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Führt eine Aktion aus. true = der 3DS muss jetzt neu starten.
|
||||
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()) {
|
||||
snprintf(msg, msglen, "Moment: Das Update auf v%s wird gerade installiert.\nDanach startet die App neu.", update_new_version());
|
||||
return false;
|
||||
}
|
||||
switch (action) {
|
||||
case BTN_ENABLE: return enable(confirmed, msg, msglen);
|
||||
case BTN_DISABLE: return disable(confirmed, msg, msglen);
|
||||
case BTN_DIAG: patch_diagnose(msg, msglen); return false;
|
||||
case BTN_UNLINK: {
|
||||
// Verknüpfte NNID nur auf dem 3DS lösen (ohne Server) - z. B. wenn das Konto gelöscht wurde
|
||||
char id[20];
|
||||
if (!nnid_linked(id, sizeof(id))) { snprintf(msg, msglen, "Hier ist keine ID verknüpft."); return false; }
|
||||
if (!confirmed) {
|
||||
armed = BTN_UNLINK;
|
||||
snprintf(msg, msglen, "\"%s\" vom 3DS lösen?\nNur auf dem 3DS, das Konto selbst bleibt unberührt.\n"
|
||||
"Zum Bestätigen nochmal antippen.", id);
|
||||
return false;
|
||||
}
|
||||
if (!nnid_unlink(msg, msglen)) return false;
|
||||
snprintf(msg, msglen, "\"%s\" gelöst. Der 3DS startet neu.\nDanach kannst du eine andere ID verknüpfen.", id);
|
||||
return true;
|
||||
}
|
||||
case BTN_FTP:
|
||||
if (ftpctl_running()) ftpctl_stop(msg, msglen);
|
||||
else ftpctl_start(msg, msglen);
|
||||
ftpOn = ftpctl_running();
|
||||
return false;
|
||||
case BTN_SPOTPASS:
|
||||
if (spotpassTestFrames >= 0) { snprintf(msg, msglen, "SpotPass-Test läuft schon ..."); return false; }
|
||||
if (spotpass_test_start(MBMNET_SERVER, msg, msglen)) spotpassTestFrames = 0;
|
||||
return false;
|
||||
case BTN_REBOOT: return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool inside(Box b, touchPosition tp) { return tp.px >= b.x && tp.px < b.x + b.w && tp.py >= b.y && tp.py < b.y + b.h; }
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
gfxInitDefault();
|
||||
httpcInit(0);
|
||||
// Als installierte CIA: am:net zum Installieren von Updates
|
||||
bool am = !envIsHomebrew() && R_SUCCEEDED(amInit());
|
||||
C3D_Init(C3D_DEFAULT_CMDBUF_SIZE);
|
||||
C2D_Init(C2D_DEFAULT_MAX_OBJECTS);
|
||||
C2D_Prepare();
|
||||
C3D_RenderTarget *top = C2D_CreateScreenTarget(GFX_TOP, GFX_LEFT);
|
||||
C3D_RenderTarget *bot = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT);
|
||||
textBuf = C2D_TextBufNew(4096);
|
||||
nnid_init();
|
||||
frdswitch_init();
|
||||
|
||||
const char *server = MBMNET_SERVER;
|
||||
char msg[256] = "";
|
||||
|
||||
// Pfad der laufenden App (vom Homebrew Launcher übergeben), damit das Update sie ersetzen kann
|
||||
const char *selfPath = argc > 0 && strncmp(argv[0], "sdmc:/", 6) == 0 ? argv[0] : "sdmc:/3ds/mbmnet.3dsx";
|
||||
update_start(server, selfPath);
|
||||
UpdateState lastUpd = UPD_IDLE;
|
||||
int restartIn = 0; // Frames bis zum automatischen Neustart der App nach einem Update
|
||||
int rebootIn = 0; // Frames bis zum Neustart des 3DS nach dem Umschalten
|
||||
|
||||
// Patches alter Versionen lagen an einer Stelle, an der Luma sie nicht lädt
|
||||
if (patch_migrate())
|
||||
snprintf(msg, sizeof(msg), "Neuer Speicherort für die Patches!\nBitte MBMNET einmal neu antippen.");
|
||||
PatchState st = patch_state();
|
||||
// Nur im MBMNET-Modus ist die verknüpfte NNID ein MBMNET-Konto
|
||||
admin_start(server, st == STATE_MBMNET ? nnid_pid() : 0);
|
||||
|
||||
// MBMNET aktiv, aber das Freundekonto hat eine fremde Kennung: darauf hinweisen
|
||||
if (st == STATE_MBMNET && frdswitch_current() == FRIEND_ACCOUNT_MBMNET) {
|
||||
u32 pid = frdswitch_my_pid();
|
||||
if (pid && !frdswitch_pid_is_mbmnet(pid))
|
||||
snprintf(msg, sizeof(msg), "Dein MBMNET-Freundekonto hat eine fremde Kennung (%lu).\n"
|
||||
"Tippe auf MBMNET, um es neu anzulegen.", (unsigned long)pid);
|
||||
}
|
||||
// Mitteilungen per SpotPass: bei aktivem MBMNET die Hintergrund-Aufgabe (neu) anmelden
|
||||
if (st == STATE_MBMNET) {
|
||||
char ignore[96];
|
||||
spotpass_register(server, ignore, sizeof(ignore));
|
||||
// Patches auffrischen: neue Versionen bringen neue Ziele mit (z. B. Miiverse ab v2.8.0)
|
||||
bool changed = false;
|
||||
char detail[160];
|
||||
if (patch_enable(server, &changed, detail, sizeof(detail)) && changed && !msg[0])
|
||||
snprintf(msg, sizeof(msg), "Neue MBMNET-Patches (Miiverse) geschrieben.\n"
|
||||
"Bitte den 3DS einmal neu starten (Werkzeuge: Neustart).");
|
||||
}
|
||||
// Das Hintergrund-Modul ist abgeschaltet (Kernel-Absturz): Reste wegräumen, nie wieder starten
|
||||
ftpctl_cleanup_module();
|
||||
ftpOn = ftpctl_running();
|
||||
int ftpCheckIn = 120;
|
||||
|
||||
int selected = st == STATE_MBMNET ? BTN_DISABLE : BTN_ENABLE;
|
||||
bool toolsToggled = false;
|
||||
int queued = -1; // Aktion, die nach dem nächsten Bild läuft (damit sofort etwas passiert)
|
||||
|
||||
while (aptMainLoop()) {
|
||||
hidScanInput();
|
||||
u32 down = hidKeysDown();
|
||||
if (--ftpCheckIn <= 0) { ftpCheckIn = 120; ftpOn = ftpctl_running(); }
|
||||
if (down & KEY_START && rebootIn == 0) break; // während des Umschaltens nicht abbrechen
|
||||
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;
|
||||
|
||||
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);
|
||||
if (selected < BTN_DIAG) { if (left || right) selected ^= 1; }
|
||||
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 (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;
|
||||
}
|
||||
if (queued >= 0) {
|
||||
int a = queued;
|
||||
queued = -1;
|
||||
if (run(a, msg, sizeof(msg))) rebootIn = 45; // ca. 0,75 Sekunden zum Lesen
|
||||
st = patch_state();
|
||||
} else if (action >= 0 && rebootIn == 0) {
|
||||
queued = action;
|
||||
snprintf(msg, sizeof(msg), action <= BTN_DISABLE ? "Schalte um ..." : "Einen Moment ...");
|
||||
}
|
||||
|
||||
// Update gefunden: gleich laden, egal ob MBMNET oder Nintendo aktiv ist. Nicht während des
|
||||
// Umschaltens, sonst startet der 3DS mitten in der Installation neu.
|
||||
UpdateState upd = update_state();
|
||||
if (upd == UPD_AVAILABLE && rebootIn == 0 && queued < 0) {
|
||||
update_download();
|
||||
snprintf(msg, sizeof(msg), "Lade Update auf v%s ...", update_new_version());
|
||||
upd = update_state();
|
||||
}
|
||||
// Update fertig: kurz anzeigen, dann neu starten
|
||||
if (upd == UPD_INSTALLED && lastUpd != UPD_INSTALLED) {
|
||||
snprintf(msg, sizeof(msg), "Update auf v%s installiert!\nDie App startet gleich neu ...", update_new_version());
|
||||
restartIn = 90; // ca. 1,5 Sekunden
|
||||
}
|
||||
if (upd == UPD_ERROR && lastUpd == UPD_DOWNLOADING) {
|
||||
char why[96];
|
||||
update_error(why, sizeof(why));
|
||||
snprintf(msg, sizeof(msg), "Update nach 3 Versuchen fehlgeschlagen:\n%s\nProtokoll: /3ds/mbmnet/update.log", why);
|
||||
}
|
||||
lastUpd = upd;
|
||||
|
||||
// SpotPass-Test: jede Sekunde nachsehen
|
||||
if (spotpassTestFrames >= 0 && ++spotpassTestFrames % 60 == 0 &&
|
||||
!spotpass_test_poll(spotpassTestFrames / 60, msg, sizeof(msg)))
|
||||
spotpassTestFrames = -1;
|
||||
|
||||
// Neustart erst, wenn die Freunde-Anmeldung durch ist (höchstens ein paar Sekunden)
|
||||
if (rebootIn > 0 && --rebootIn == 0 && R_FAILED(APT_HardwareResetAsync())) {
|
||||
snprintf(msg, sizeof(msg), "Neustart fehlgeschlagen.\nBitte den 3DS von Hand neu starten.");
|
||||
}
|
||||
|
||||
if (restartIn > 0 && --restartIn == 0) {
|
||||
if (relaunch_prepare(selfPath)) break;
|
||||
snprintf(msg, sizeof(msg), "Update installiert!\nBitte die App von Hand neu starten (START).");
|
||||
}
|
||||
|
||||
C2D_TextBufClear(textBuf);
|
||||
C3D_FrameBegin(C3D_FRAME_SYNCDRAW);
|
||||
C2D_TargetClear(top, COL_BG);
|
||||
C2D_SceneBegin(top);
|
||||
draw_top(st, msg, admin, admin_is());
|
||||
C2D_TargetClear(bot, COL_BG);
|
||||
C2D_SceneBegin(bot);
|
||||
draw_bottom(selected, st, admin);
|
||||
C3D_FrameEnd(0);
|
||||
}
|
||||
|
||||
ftpctl_stop(NULL, 0);
|
||||
admin_finish();
|
||||
update_finish();
|
||||
frdswitch_exit();
|
||||
nnid_exit();
|
||||
httpcExit();
|
||||
if (am) amExit();
|
||||
C2D_TextBufDelete(textBuf);
|
||||
C2D_Fini();
|
||||
C3D_Fini();
|
||||
gfxExit();
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user