// 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 #include #include #include #include #include #include #include #include #include #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(¬ificationSemaphore); // 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; }