Files
MBMNET-APP/source/olvpatch.c
T

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;
}