132 lines
6.3 KiB
C
132 lines
6.3 KiB
C
// 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 <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#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://<server>/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;
|
|
}
|