Files
2026-10-18 17:05:44 +02:00

182 lines
6.7 KiB
C

// MBMNET-FTP-Modul: läuft als eigener Hintergrund-Prozess, auch wenn die MBMNET-App geschlossen ist.
//
// Die App legt dieses Modul als /luma/sysmodules/0004013000F1E002.cxi ab und startet es über den
// Prozess-Manager (Luma lädt Systemmodule mit dieser Kennung von der SD). "FTP aus" in der App
// beendet den Prozess. Nach einem Neustart des 3DS ist es aus, bis die App es wieder startet.
//
// Die aktuelle Adresse steht für die App in /3ds/mbmnet/ftp-adresse.txt.
//
// Beenden immer sauber durch das Modul selbst (nie von außen abschießen - das brachte den 3DS zum
// Absturz): "FTP aus" legt /3ds/mbmnet/ftp-stopp an, beim Neustart/Ausschalten schickt das System
// die Benachrichtigung 0x100. Dann Verbindungen und Netzwerk-Dienst schließen und zurückkehren.
#include <3ds.h>
#include <arpa/inet.h>
#include <malloc.h>
#include <netinet/in.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include "ftp.h"
#include "ftpstatus.h"
// Statusblock für die App (Adresse fest, siehe Makefile -> ftpmodul.sym)
volatile FtpStatus ftpStatus __attribute__((used, aligned(4))) = { .magic = FTP_STATUS_MAGIC };
#define SOC_BUFFER_SIZE 0x20000
#define ADDRESS_FILE "sdmc:/3ds/mbmnet/ftp-adresse.txt"
#define LOG_FILE "sdmc:/3ds/mbmnet/ftp.log"
#define STOP_FILE "sdmc:/3ds/mbmnet/ftp-stopp"
static Handle notificationSemaphore;
static bool quit;
// Protokoll für die Fehlersuche (die App zeigt die letzte Zeile, wenn der Server nicht antwortet)
static void logf_(const char *fmt, ...)
{
FILE *f = fopen(LOG_FILE, "a");
if (!f) return;
va_list ap;
va_start(ap, fmt);
vfprintf(f, fmt, ap);
va_end(ap);
fputc('\n', f);
fclose(f);
}
// Ein Hintergrund-Modul bekommt nur wenig Speicher: Heap fest vorgeben statt "alles, was frei ist"
u32 __ctru_heap_size = 0x80000;
u32 __ctru_linear_heap_size = 0x1000;
// Nur die Dienste, die das Modul braucht (kein Grafik-/HOME-Menü-Dienst wie bei einer App)
void __appInit(void)
{
// srv ist beim Start evtl. noch belegt: kurz wiederholen
for (int i = 0; i < 100 && R_FAILED(ftpStatus.srv = srvInit()); i++) svcSleepThread(50 * 1000 * 1000);
ftpStatus.fs = fsInit();
ftpStatus.sdmc = archiveMountSdmc();
ftpStatus.stage = FTP_STAGE_INIT;
}
void __appExit(void)
{
archiveUnmountAll();
fsExit();
srvExit();
}
bool ftp_should_exit(void)
{
ftpStatus.heartbeat++;
if (ftpStatus.stop) quit = true; // Stopp-Bit der App (geht auch ohne SD)
if (quit) return true;
// Beenden-Aufforderung des Systems (Neustart, Ausschalten)
if (notificationSemaphore && R_SUCCEEDED(svcWaitSynchronization(notificationSemaphore, 0))) {
u32 id = 0;
if (R_SUCCEEDED(srvReceiveNotification(&id)) && id == 0x100) quit = true;
}
// Stopp-Datei der App, höchstens alle 2 Sekunden nachsehen
static u64 lastCheck;
u64 now = osGetTime();
if (!quit && now - lastCheck >= 2000) {
lastCheck = now;
struct stat st;
if (stat(STOP_FILE, &st) == 0) quit = true;
}
return quit;
}
// Wartet ms Millisekunden, bricht aber ab, sobald das Modul aufhören soll
static void sleep_ms(int ms)
{
for (int waited = 0; waited < ms && !ftp_should_exit(); waited += 100) svcSleepThread(100LL * 1000 * 1000);
}
static void write_address(u32 ip)
{
mkdir("sdmc:/3ds", 0777);
mkdir("sdmc:/3ds/mbmnet", 0777);
FILE *f = fopen(ADDRESS_FILE, "w");
if (!f) return;
if (ip) fprintf(f, "%lu.%lu.%lu.%lu:%d\n", (unsigned long)(ip & 0xFF), (unsigned long)((ip >> 8) & 0xFF),
(unsigned long)((ip >> 16) & 0xFF), (unsigned long)(ip >> 24), CONTROL_PORT);
fclose(f);
}
int main(void)
{
remove(LOG_FILE);
remove(STOP_FILE);
srvEnableNotification(&notificationSemaphore); // für die Beenden-Aufforderung 0x100
logf_("MBMNET-FTP-Modul gestartet: srv %08lX, fs %08lX, sdmc %08lX, frei (Base) %lld KB",
(unsigned long)ftpStatus.srv, (unsigned long)ftpStatus.fs, (unsigned long)ftpStatus.sdmc,
osGetMemRegionFree(MEMREGION_BASE) / 1024);
u32 *socBuffer = memalign(0x1000, SOC_BUFFER_SIZE);
if (!socBuffer) { logf_("Fehler: kein Speicher für den Netzwerk-Puffer"); return 1; }
while (!ftp_should_exit()) {
// Netzwerk-Dienst (nach dem Schlafmodus ggf. neu)
Result rc;
int tries = 0;
ftpStatus.stage = FTP_STAGE_SOC;
while (R_FAILED(rc = ftpStatus.soc = socInit(socBuffer, SOC_BUFFER_SIZE))) {
if (ftp_should_exit()) goto done;
if (tries++ % 15 == 0) logf_("Netzwerk-Dienst (socInit) fehlgeschlagen: %08lX", (unsigned long)rc);
sleep_ms(2000);
}
logf_("Netzwerk-Dienst bereit");
int failures = 0, waits = 0;
while (failures < 5 && !ftp_should_exit()) {
u32 ip = (u32)gethostid();
ftpStatus.ip = ip;
if (!ip) { // noch kein WLAN
ftpStatus.stage = FTP_STAGE_WIFI;
if (waits++ % 15 == 0) logf_("Warte auf WLAN (keine IP)");
write_address(0);
sleep_ms(2000);
continue;
}
waits = 0;
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(CONTROL_PORT), .sin_addr.s_addr = INADDR_ANY };
if (fd < 0 || bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0 || listen(fd, 2) != 0) {
logf_("Port %d öffnen fehlgeschlagen (Socket %d, errno %d)", CONTROL_PORT, fd, errno);
ftpStatus.stage = FTP_STAGE_PORT;
ftpStatus.bindErrno = errno;
if (fd >= 0) close(fd);
failures++;
sleep_ms(2000);
continue;
}
failures = 0;
ftpStatus.stage = FTP_STAGE_READY;
write_address(ip);
logf_("Bereit: ftp://%lu.%lu.%lu.%lu:%d", (unsigned long)(ip & 0xFF), (unsigned long)((ip >> 8) & 0xFF),
(unsigned long)((ip >> 16) & 0xFF), (unsigned long)(ip >> 24), CONTROL_PORT);
ftp_serve(fd, ip); // kehrt zurück, wenn das Netz weg ist oder sich die IP ändert
close(fd);
sleep_ms(500);
}
socExit();
if (ftp_should_exit()) break;
// Mehrfach kein Socket möglich (z. B. nach dem Schlafmodus): Netzwerk-Dienst neu aufsetzen
logf_("Netzwerk-Dienst wird neu aufgesetzt");
sleep_ms(2000);
}
done:
ftpStatus.stage = FTP_STAGE_EXIT;
// Sauber beenden: Adresse weg, Stopp-Datei weg; libctru schließt danach fs und srv
remove(ADDRESS_FILE);
remove(STOP_FILE);
logf_("Beendet");
free(socBuffer);
return 0;
}