// Miiverse-Patch: leitet die Miiverse-App auf MBMNET um und lässt sie der MBMNET-Zertifizierungsstelle (CA) // vertrauen, denn ihre Seiten lädt sie nur per HTTPS. // // Die Code-Änderungen stammen aus Pretendos Nimbus (patches/miiverse, GPLv3, nach Stary und Sono): mit armips // übersetzt, statt Pretendos Discovery-Adresse und GlobalSign-Zertifikat kommen MBMNET-Adresse und -CA hinein. // Sie gelten für die letzte Version der Miiverse-App (alle Regionen gleich). Vorher wird geprüft, dass die // Nintendo-Discovery-Adresse genau an der erwarteten Stelle steht - sonst wird nichts gepatcht. // // 0x176F28 fügt der HTTP-Verbindung die CA hinzu (httpC:AddRootCA), Länge der CA an Byte 72 // 0x1D7FF8 / 0x1D80EC / 0x38DC30 SD-Karte zusätzlich einhängen und wieder aushängen // 0x1E3224 / 0x1E3468 der Browser lädt seine CA aus sdmc:/3ds/mbmnet-ca.pem // 0x38DCB0 die CA selbst (DER, höchstens 889 Bytes) #include <3ds.h> #include #include #include #include "olvpatch.h" #define CODE_BASE 0x100000 #define DISCOVERY_ADDR 0x15748C #define DISCOVERY_URL "https://discovery.olv.nintendo.net/v1/endpoint" #define CA_ADDR 0x38DCB0 #define CA_MAX 889 #define CA_PEM_FILE "sdmc:/3ds/mbmnet-ca.pem" static const u8 olv_176F28[] = { 0x3C, 0x00, 0x9F, 0xE5, 0x70, 0x4F, 0x1D, 0xEE, 0x0C, 0x10, 0x95, 0xE5, 0x14, 0x80, 0x95, 0xE5, 0x80, 0x00, 0xA4, 0xE5, 0x04, 0x10, 0x84, 0xE5, 0x08, 0x00, 0xA0, 0xE1, 0x24, 0x80, 0x9F, 0xE5, 0x24, 0x10, 0x9F, 0xE5, 0x08, 0x80, 0x84, 0xE5, 0x10, 0x10, 0x84, 0xE5, 0x08, 0x82, 0xA0, 0xE1, 0x0A, 0x80, 0x88, 0xE3, 0x0C, 0x80, 0x84, 0xE5, 0x32, 0x00, 0x00, 0xEF, 0x00, 0x00, 0xA0, 0xE1, 0x08, 0x00, 0x00, 0xEA, 0x82, 0x00, 0x24, 0x00, 0x79, 0x03, 0x00, 0x00, 0xB0, 0xDC, 0x38, 0x00, 0x00, 0x00, 0xA0, 0xE1, 0x00, 0x00, 0xA0, 0xE1, 0x00, 0x00, 0xA0, 0xE1 }; static const u8 olv_1D7FF8[] = { 0x0C, 0xD7, 0x06, 0xEA }; static const u8 olv_1D80EC[] = { 0xD4, 0xD6, 0x06, 0xEA }; static const u8 olv_1E3224[] = { 0x01, 0x20, 0xA0, 0xE3 }; static const u8 olv_1E3468[] = "3ds/mbmnet-ca.pem"; // mit Nullbyte: 18 Bytes static const u8 olv_38DC30[] = { 0x00, 0x40, 0x2D, 0xE9, 0x2E, 0x28, 0xF9, 0xEB, 0x18, 0x00, 0x9F, 0xE5, 0x00, 0x40, 0xBD, 0xE8, 0xB6, 0x93, 0xF8, 0xEA, 0x00, 0x40, 0x2D, 0xE9, 0x02, 0x29, 0xF9, 0xEB, 0x04, 0x00, 0x9F, 0xE5, 0x00, 0x40, 0xBD, 0xE8, 0xBB, 0x92, 0xFA, 0xEA, 0x9C, 0x81, 0x1D, 0x00 }; #define CA_LEN_BYTE 72 // in olv_176F28: Länge der CA (Literal-Pool) // Lädt eine Datei vom MBMNET-Server. Gibt die Länge zurück, 0 bei Fehler. static u32 fetch(const char *server, const char *path, u8 *buf, u32 cap) { char url[160]; snprintf(url, sizeof(url), "http://%s%s", server, path); httpcContext ctx; if (R_FAILED(httpcOpenContext(&ctx, HTTPC_METHOD_GET, url, 1))) return 0; httpcAddRequestHeaderField(&ctx, "User-Agent", "MBMNET-Switcher/" APP_VERSION); u32 status = 0, got = 0; Result rc = httpcBeginRequest(&ctx); if (R_SUCCEEDED(rc)) rc = httpcGetResponseStatusCodeTimeout(&ctx, &status, 8ULL * 1000 * 1000 * 1000); if (R_SUCCEEDED(rc) && status == 200) rc = httpcDownloadData(&ctx, buf, cap, &got); httpcCloseContext(&ctx); return R_SUCCEEDED(rc) && status == 200 && got < cap ? got : 0; } // Zwischenspeicher der CA auf der SD: Beim Umschalten nicht jedes Mal den Server fragen (das dauerte // Sekunden, solange das WLAN noch verbindet). Frisch geholt wird beim Auffrischen im Hintergrund. #define CA_DER_CACHE "sdmc:/3ds/mbmnet/cache/olv-ca.der" static u32 read_cached(u8 *der) { FILE *f = fopen(CA_DER_CACHE, "rb"); if (!f) return 0; u32 n = fread(der, 1, CA_MAX + 1, f); fclose(f); FILE *pem = fopen(CA_PEM_FILE, "rb"); // die PEM-Datei für den Browser muss auch da sein if (!pem) return 0; fclose(pem); return n > 0 && n <= CA_MAX ? n : 0; } bool olvpatch_fetch_ca(const char *server, u8 *der, u32 *derLen, bool fresh) { if (!fresh && (*derLen = read_cached(der))) return true; static u8 pem[2048]; u32 n = fetch(server, "/olv/ca.der", der, CA_MAX + 1); if (n == 0 || n > CA_MAX) return !fresh ? false : (*derLen = read_cached(der)) != 0; u32 p = fetch(server, "/olv/ca.pem", pem, sizeof(pem)); if (p == 0) return false; // Der Browser der Miiverse-App liest die CA von der SD-Karte FILE *f = fopen(CA_PEM_FILE, "wb"); bool ok = f && fwrite(pem, 1, p, f) == p; if (f) fclose(f); mkdir("sdmc:/3ds/mbmnet", 0777); mkdir("sdmc:/3ds/mbmnet/cache", 0777); f = fopen(CA_DER_CACHE, "wb"); if (f) { fwrite(der, 1, n, f); fclose(f); } *derLen = n; return ok; } bool olvpatch_supported(const u8 *code, u32 size) { u32 off = DISCOVERY_ADDR - CODE_BASE; return off + sizeof(DISCOVERY_URL) <= size && memcmp(code + off, DISCOVERY_URL, sizeof(DISCOVERY_URL)) == 0; } static u32 ips_record(u8 *out, u32 n, u32 cap, u32 addr, const u8 *data, u32 len) { if (n + 5 + len + 3 > cap) return 0; u32 off = addr - CODE_BASE; u8 head[5] = { off >> 16, off >> 8, off, len >> 8, len }; memcpy(out + n, head, 5); memcpy(out + n + 5, data, len); return n + 5 + len; } u32 olvpatch_build(const char *server, const u8 *der, u32 derLen, u8 *out, u32 cap) { // Discovery: "https://discovery.olv.nintendo.net/v1/endpoint" -> "http:///v1/endpoint", Rest Nullbytes u8 discovery[sizeof(DISCOVERY_URL)] = { 0 }; int len = snprintf((char *)discovery, sizeof(discovery), "http://%s/v1/endpoint", server); if (len <= 0 || len >= (int)sizeof(discovery) || derLen == 0 || derLen > CA_MAX) return 0; u8 caHook[sizeof(olv_176F28)]; memcpy(caHook, olv_176F28, sizeof(caHook)); caHook[CA_LEN_BYTE] = derLen; caHook[CA_LEN_BYTE + 1] = derLen >> 8; u32 n = 5; memcpy(out, "PATCH", 5); struct { u32 addr; const u8 *data; u32 len; } recs[] = { { DISCOVERY_ADDR, discovery, sizeof(discovery) }, { 0x176F28, caHook, sizeof(caHook) }, { 0x1D7FF8, olv_1D7FF8, sizeof(olv_1D7FF8) }, { 0x1D80EC, olv_1D80EC, sizeof(olv_1D80EC) }, { 0x1E3224, olv_1E3224, sizeof(olv_1E3224) }, { 0x1E3468, olv_1E3468, sizeof(olv_1E3468) }, { 0x38DC30, olv_38DC30, sizeof(olv_38DC30) }, { CA_ADDR, der, derLen }, }; for (size_t i = 0; i < sizeof(recs) / sizeof(recs[0]); i++) if (!(n = ips_record(out, n, cap, recs[i].addr, recs[i].data, recs[i].len))) return 0; memcpy(out + n, "EOF", 3); return n + 3; }