mirror of
https://gitlab.com/Theopse/fbi-i18n-zh.git
synced 2025-04-06 03:58:02 +08:00
343 lines
11 KiB
C
343 lines
11 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <3ds.h>
|
|
#include <jansson.h>
|
|
#include <zlib.h>
|
|
|
|
#include "fs.h"
|
|
#include "error.h"
|
|
#include "http.h"
|
|
|
|
#define MAKE_HTTP_USER_AGENT_(major, minor, micro) ("Mozilla/5.0 (Nintendo 3DS; Mobile; rv:10.0) Gecko/20100101 FBI/" #major "." #minor "." #micro)
|
|
#define MAKE_HTTP_USER_AGENT(major, minor, micro) MAKE_HTTP_USER_AGENT_(major, minor, micro)
|
|
#define HTTP_USER_AGENT MAKE_HTTP_USER_AGENT(VERSION_MAJOR, VERSION_MINOR, VERSION_MICRO)
|
|
|
|
#define HTTP_TIMEOUT 15000000000
|
|
|
|
struct http_context_s {
|
|
httpcContext httpc;
|
|
|
|
bool compressed;
|
|
z_stream inflate;
|
|
u8 buffer[32 * 1024];
|
|
u32 bufferSize;
|
|
};
|
|
|
|
Result http_open(http_context* context, const char* url, bool userAgent) {
|
|
return http_open_ranged(context, url, userAgent, 0, 0);
|
|
}
|
|
|
|
Result http_open_ranged(http_context* context, const char* url, bool userAgent, u32 rangeStart, u32 rangeEnd) {
|
|
if(url == NULL) {
|
|
return R_APP_INVALID_ARGUMENT;
|
|
}
|
|
|
|
Result res = 0;
|
|
|
|
http_context ctx = (http_context) calloc(1, sizeof(struct http_context_s));
|
|
if(ctx != NULL) {
|
|
char currUrl[1024];
|
|
strncpy(currUrl, url, sizeof(currUrl));
|
|
|
|
char range[64];
|
|
if(rangeEnd > rangeStart) {
|
|
snprintf(range, sizeof(range), "%lu-%lu", rangeStart, rangeEnd);
|
|
} else {
|
|
snprintf(range, sizeof(range), "%lu-", rangeStart);
|
|
}
|
|
|
|
bool resolved = false;
|
|
u32 redirectCount = 0;
|
|
while(R_SUCCEEDED(res) && !resolved && redirectCount < 32) {
|
|
if(R_SUCCEEDED(res = httpcOpenContext(&ctx->httpc, HTTPC_METHOD_GET, currUrl, 1))) {
|
|
u32 response = 0;
|
|
if(R_SUCCEEDED(res = httpcSetSSLOpt(&ctx->httpc, SSLCOPT_DisableVerify))
|
|
&& (!userAgent || R_SUCCEEDED(res = httpcAddRequestHeaderField(&ctx->httpc, "User-Agent", HTTP_USER_AGENT)))
|
|
&& (rangeStart == 0 || R_SUCCEEDED(res = httpcAddRequestHeaderField(&ctx->httpc, "Range", range)))
|
|
&& R_SUCCEEDED(res = httpcAddRequestHeaderField(&ctx->httpc, "Accept-Encoding", "gzip, deflate"))
|
|
&& R_SUCCEEDED(res = httpcSetKeepAlive(&ctx->httpc, HTTPC_KEEPALIVE_ENABLED))
|
|
&& R_SUCCEEDED(res = httpcBeginRequest(&ctx->httpc))
|
|
&& R_SUCCEEDED(res = httpcGetResponseStatusCodeTimeout(&ctx->httpc, &response, HTTP_TIMEOUT))) {
|
|
if(response == 301 || response == 302 || response == 303) {
|
|
redirectCount++;
|
|
|
|
memset(currUrl, '\0', sizeof(currUrl));
|
|
if(R_SUCCEEDED(res = httpcGetResponseHeader(&ctx->httpc, "Location", currUrl, sizeof(currUrl)))) {
|
|
httpcCloseContext(&ctx->httpc);
|
|
}
|
|
} else {
|
|
resolved = true;
|
|
|
|
if(response == 200) {
|
|
char encoding[32];
|
|
if(R_SUCCEEDED(httpcGetResponseHeader(&ctx->httpc, "Content-Encoding", encoding, sizeof(encoding)))) {
|
|
bool gzip = strncmp(encoding, "gzip", sizeof(encoding)) == 0;
|
|
bool deflate = strncmp(encoding, "deflate", sizeof(encoding)) == 0;
|
|
|
|
ctx->compressed = gzip || deflate;
|
|
|
|
if(ctx->compressed) {
|
|
memset(&ctx->inflate, 0, sizeof(ctx->inflate));
|
|
if(deflate) {
|
|
inflateInit(&ctx->inflate);
|
|
} else if(gzip) {
|
|
inflateInit2(&ctx->inflate, MAX_WBITS | 16);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
res = R_APP_HTTP_ERROR_BASE + response;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(R_FAILED(res)) {
|
|
httpcCloseContext(&ctx->httpc);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(R_SUCCEEDED(res) && redirectCount >= 32) {
|
|
res = R_APP_HTTP_TOO_MANY_REDIRECTS;
|
|
}
|
|
|
|
if(R_FAILED(res)) {
|
|
free(ctx);
|
|
}
|
|
} else {
|
|
res = R_APP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if(R_SUCCEEDED(res)) {
|
|
*context = ctx;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Result http_close(http_context context) {
|
|
if(context == NULL) {
|
|
return R_APP_INVALID_ARGUMENT;
|
|
}
|
|
|
|
if(context->compressed) {
|
|
inflateEnd(&context->inflate);
|
|
}
|
|
|
|
Result res = httpcCloseContext(&context->httpc);
|
|
free(context);
|
|
return res;
|
|
}
|
|
|
|
Result http_get_size(http_context context, u32* size) {
|
|
if(context == NULL || size == NULL) {
|
|
return R_APP_INVALID_ARGUMENT;
|
|
}
|
|
|
|
return httpcGetDownloadSizeState(&context->httpc, NULL, size);
|
|
}
|
|
|
|
Result http_get_file_name(http_context context, char* out, u32 size) {
|
|
if(context == NULL || out == NULL) {
|
|
return R_APP_INVALID_ARGUMENT;
|
|
}
|
|
|
|
Result res = 0;
|
|
|
|
char* header = (char*) calloc(1, size + 64);
|
|
if(header != NULL) {
|
|
if(R_SUCCEEDED(res = httpcGetResponseHeader(&context->httpc, "Content-Disposition", header, size + 64))) {
|
|
char* start = strstr(header, "filename=");
|
|
if(start != NULL) {
|
|
char format[32];
|
|
snprintf(format, sizeof(format), "filename=\"%%%lu[^\"]\"", size);
|
|
if(sscanf(start, format, out) != 1) {
|
|
res = R_APP_BAD_DATA;
|
|
}
|
|
} else {
|
|
res = R_APP_BAD_DATA;
|
|
}
|
|
}
|
|
|
|
free(header);
|
|
} else {
|
|
res = R_APP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Result http_read(http_context context, u32* bytesRead, void* buffer, u32 size) {
|
|
if(context == NULL || buffer == NULL) {
|
|
return R_APP_INVALID_ARGUMENT;
|
|
}
|
|
|
|
Result res = 0;
|
|
|
|
u32 startPos = 0;
|
|
if(R_SUCCEEDED(res = httpcGetDownloadSizeState(&context->httpc, &startPos, NULL))) {
|
|
res = HTTPC_RESULTCODE_DOWNLOADPENDING;
|
|
|
|
u32 outPos = 0;
|
|
if(context->compressed) {
|
|
u32 lastPos = context->bufferSize;
|
|
while(res == HTTPC_RESULTCODE_DOWNLOADPENDING && outPos < size) {
|
|
if((context->bufferSize > 0
|
|
|| R_SUCCEEDED(res = httpcReceiveDataTimeout(&context->httpc, &context->buffer[context->bufferSize], sizeof(context->buffer) - context->bufferSize, HTTP_TIMEOUT))
|
|
|| res == HTTPC_RESULTCODE_DOWNLOADPENDING)) {
|
|
Result posRes = 0;
|
|
u32 currPos = 0;
|
|
if(R_SUCCEEDED(posRes = httpcGetDownloadSizeState(&context->httpc, &currPos, NULL))) {
|
|
context->bufferSize += currPos - lastPos;
|
|
|
|
context->inflate.next_in = context->buffer;
|
|
context->inflate.next_out = buffer + outPos;
|
|
context->inflate.avail_in = context->bufferSize;
|
|
context->inflate.avail_out = size - outPos;
|
|
inflate(&context->inflate, Z_SYNC_FLUSH);
|
|
|
|
memcpy(context->buffer, context->buffer + (context->bufferSize - context->inflate.avail_in), context->inflate.avail_in);
|
|
context->bufferSize = context->inflate.avail_in;
|
|
|
|
lastPos = currPos;
|
|
outPos = size - context->inflate.avail_out;
|
|
} else {
|
|
res = posRes;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
while(res == HTTPC_RESULTCODE_DOWNLOADPENDING && outPos < size) {
|
|
if(R_SUCCEEDED(res = httpcReceiveDataTimeout(&context->httpc, &((u8*) buffer)[outPos], size - outPos, HTTP_TIMEOUT)) || res == HTTPC_RESULTCODE_DOWNLOADPENDING) {
|
|
Result posRes = 0;
|
|
u32 currPos = 0;
|
|
if(R_SUCCEEDED(posRes = httpcGetDownloadSizeState(&context->httpc, &currPos, NULL))) {
|
|
outPos = currPos - startPos;
|
|
} else {
|
|
res = posRes;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(res == HTTPC_RESULTCODE_DOWNLOADPENDING) {
|
|
res = 0;
|
|
}
|
|
|
|
if(R_SUCCEEDED(res) && bytesRead != NULL) {
|
|
*bytesRead = outPos;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Result http_download(const char* url, u32* downloadedSize, void* buf, size_t size) {
|
|
Result res = 0;
|
|
|
|
http_context context = NULL;
|
|
if(R_SUCCEEDED(res = http_open(&context, url, true))) {
|
|
res = http_read(context, downloadedSize, buf, size);
|
|
|
|
Result closeRes = http_close(context);
|
|
if(R_SUCCEEDED(res)) {
|
|
res = closeRes;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Result http_download_json(const char* url, json_t** json, size_t maxSize) {
|
|
if(url == NULL || json == NULL) {
|
|
return R_APP_INVALID_ARGUMENT;
|
|
}
|
|
|
|
Result res = 0;
|
|
|
|
char* text = (char*) calloc(sizeof(char), maxSize);
|
|
if(text != NULL) {
|
|
u32 textSize = 0;
|
|
if(R_SUCCEEDED(res = http_download(url, &textSize, text, maxSize))) {
|
|
json_error_t error;
|
|
json_t* parsed = json_loads(text, 0, &error);
|
|
if(parsed != NULL) {
|
|
*json = parsed;
|
|
} else {
|
|
res = R_APP_PARSE_FAILED;
|
|
}
|
|
}
|
|
|
|
free(text);
|
|
} else {
|
|
res = R_APP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static Result FSUSER_AddSeed(u64 titleId, const void* seed) {
|
|
u32 *cmdbuf = getThreadCommandBuffer();
|
|
|
|
cmdbuf[0] = 0x087A0180;
|
|
cmdbuf[1] = (u32) (titleId & 0xFFFFFFFF);
|
|
cmdbuf[2] = (u32) (titleId >> 32);
|
|
memcpy(&cmdbuf[3], seed, 16);
|
|
|
|
Result ret = 0;
|
|
if(R_FAILED(ret = svcSendSyncRequest(*fsGetSessionHandle()))) return ret;
|
|
|
|
ret = cmdbuf[1];
|
|
return ret;
|
|
}
|
|
|
|
Result http_download_seed(u64 titleId) {
|
|
char pathBuf[64];
|
|
snprintf(pathBuf, 64, "/fbi/seed/%016llX.dat", titleId);
|
|
|
|
Result res = 0;
|
|
|
|
FS_Path* fsPath = fs_make_path_utf8(pathBuf);
|
|
if(fsPath != NULL) {
|
|
u8 seed[16];
|
|
|
|
Handle fileHandle = 0;
|
|
if(R_SUCCEEDED(res = FSUSER_OpenFileDirectly(&fileHandle, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, ""), *fsPath, FS_OPEN_READ, 0))) {
|
|
u32 bytesRead = 0;
|
|
res = FSFILE_Read(fileHandle, &bytesRead, 0, seed, sizeof(seed));
|
|
|
|
FSFILE_Close(fileHandle);
|
|
}
|
|
|
|
fs_free_path_utf8(fsPath);
|
|
|
|
if(R_FAILED(res)) {
|
|
u8 region = CFG_REGION_USA;
|
|
CFGU_SecureInfoGetRegion(®ion);
|
|
|
|
if(region <= CFG_REGION_TWN) {
|
|
static const char* regionStrings[] = {"JP", "US", "GB", "GB", "HK", "KR", "TW"};
|
|
|
|
char url[128];
|
|
snprintf(url, 128, "https://kagiya-ctr.cdn.nintendo.net/title/0x%016llX/ext_key?country=%s", titleId, regionStrings[region]);
|
|
|
|
u32 downloadedSize = 0;
|
|
if(R_SUCCEEDED(res = http_download(url, &downloadedSize, seed, sizeof(seed))) && downloadedSize != sizeof(seed)) {
|
|
res = R_APP_BAD_DATA;
|
|
}
|
|
} else {
|
|
res = R_APP_OUT_OF_RANGE;
|
|
}
|
|
}
|
|
|
|
if(R_SUCCEEDED(res)) {
|
|
res = FSUSER_AddSeed(titleId, seed);
|
|
}
|
|
} else {
|
|
res = R_APP_OUT_OF_MEMORY;
|
|
}
|
|
|
|
return res;
|
|
} |