#include #include #include #include #include <3ds.h> #include "util.h" #include "../ui/error.h" #include "../ui/section/task/task.h" extern void cleanup(); static int util_get_line_length(PrintConsole* console, const char* str) { int lineLength = 0; while(*str != 0) { if(*str == '\n') { break; } lineLength++; if(lineLength >= console->consoleWidth - 1) { break; } str++; } return lineLength; } static int util_get_lines(PrintConsole* console, const char* str) { int lines = 1; int lineLength = 0; while(*str != 0) { if(*str == '\n') { lines++; lineLength = 0; } else { lineLength++; if(lineLength >= console->consoleWidth - 1) { lines++; lineLength = 0; } } str++; } return lines; } void util_panic(const char* s, ...) { va_list list; va_start(list, s); char buf[1024]; vsnprintf(buf, 1024, s, list); va_end(list); gspWaitForVBlank(); u16 width; u16 height; for(int i = 0; i < 2; i++) { memset(gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &width, &height), 0, (size_t) (width * height * 3)); memset(gfxGetFramebuffer(GFX_TOP, GFX_RIGHT, &width, &height), 0, (size_t) (width * height * 3)); memset(gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &width, &height), 0, (size_t) (width * height * 3)); gfxSwapBuffers(); } PrintConsole* console = consoleInit(GFX_TOP, NULL); const char* header = "FBI has encountered a fatal error!"; const char* footer = "Press any button to exit."; printf("\x1b[0;0H"); for(int i = 0; i < console->consoleWidth; i++) { printf("-"); } printf("\x1b[%d;0H", console->consoleHeight - 1); for(int i = 0; i < console->consoleWidth; i++) { printf("-"); } printf("\x1b[0;%dH%s", (console->consoleWidth - util_get_line_length(console, header)) / 2, header); printf("\x1b[%d;%dH%s", console->consoleHeight - 1, (console->consoleWidth - util_get_line_length(console, footer)) / 2, footer); int bufRow = (console->consoleHeight - util_get_lines(console, buf)) / 2; char* str = buf; while(*str != 0) { if(*str == '\n') { bufRow++; str++; continue; } else { int lineLength = util_get_line_length(console, str); char old = *(str + lineLength); *(str + lineLength) = '\0'; printf("\x1b[%d;%dH%s", bufRow, (console->consoleWidth - lineLength) / 2, str); *(str + lineLength) = old; bufRow++; str += lineLength; } } gfxFlushBuffers(); gspWaitForVBlank(); while(aptMainLoop()) { hidScanInput(); if(hidKeysDown() & ~KEY_TOUCH) { break; } gspWaitForVBlank(); } cleanup(); exit(1); } FS_Path* util_make_path_utf8(const char* path) { size_t len = strlen(path); u16* utf16 = (u16*) calloc(len + 1, sizeof(u16)); if(utf16 == NULL) { return NULL; } ssize_t utf16Len = utf8_to_utf16(utf16, (const uint8_t*) path, len); FS_Path* fsPath = (FS_Path*) calloc(1, sizeof(FS_Path)); if(fsPath == NULL) { free(utf16); return NULL; } fsPath->type = PATH_UTF16; fsPath->size = (utf16Len + 1) * sizeof(u16); fsPath->data = utf16; return fsPath; } void util_free_path_utf8(FS_Path* path) { free((void*) path->data); free(path); } FS_Path util_make_binary_path(const void* data, u32 size) { FS_Path path = {PATH_BINARY, size, data}; return path; } bool util_is_dir(FS_Archive archive, const char* path) { Result res = 0; FS_Path* fsPath = util_make_path_utf8(path); if(fsPath != NULL) { Handle dirHandle = 0; if(R_SUCCEEDED(res = FSUSER_OpenDirectory(&dirHandle, archive, *fsPath))) { FSDIR_Close(dirHandle); } util_free_path_utf8(fsPath); } else { res = R_FBI_OUT_OF_MEMORY; } return R_SUCCEEDED(res); } Result util_ensure_dir(FS_Archive archive, const char* path) { Result res = 0; FS_Path* fsPath = util_make_path_utf8(path); if(fsPath != NULL) { Handle dirHandle = 0; if(R_SUCCEEDED(FSUSER_OpenDirectory(&dirHandle, archive, *fsPath))) { FSDIR_Close(dirHandle); } else { FSUSER_DeleteFile(archive, *fsPath); res = FSUSER_CreateDirectory(archive, *fsPath, 0); } util_free_path_utf8(fsPath); } else { res = R_FBI_OUT_OF_MEMORY; } return res; } void util_get_path_file(char* out, const char* path, u32 size) { const char* start = NULL; const char* end = NULL; const char* curr = path - 1; while((curr = strchr(curr + 1, '/')) != NULL) { start = end != NULL ? end : path; end = curr; } if(end != path + strlen(path) - 1) { start = end; end = path + strlen(path); } if(end - start == 0) { strncpy(out, "/", size); } else { u32 terminatorPos = end - start - 1 < size - 1 ? end - start - 1 : size - 1; strncpy(out, start + 1, terminatorPos); out[terminatorPos] = '\0'; } } void util_get_parent_path(char* out, const char* path, u32 size) { size_t pathLen = strlen(path); const char* start = NULL; const char* end = NULL; const char* curr = path - 1; while((curr = strchr(curr + 1, '/')) != NULL && (start == NULL || curr != path + pathLen - 1)) { start = end != NULL ? end : path; end = curr; } u32 terminatorPos = end - path + 1 < size - 1 ? end - path + 1 : size - 1; strncpy(out, path, terminatorPos); out[terminatorPos] = '\0'; } 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 util_import_seed(u64 titleId) { char pathBuf[64]; snprintf(pathBuf, 64, "/fbi/seed/%016llX.dat", titleId); Result res = 0; FS_Path* fsPath = util_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); } util_free_path_utf8(fsPath); if(R_FAILED(res)) { static const char* regionStrings[] = {"JP", "US", "GB", "GB", "HK", "KR", "TW"}; u8 region = CFG_REGION_USA; CFGU_GetSystemLanguage(®ion); if(region <= CFG_REGION_TWN) { char url[128]; snprintf(url, 128, "https://kagiya-ctr.cdn.nintendo.net/title/0x%016llX/ext_key?country=%s", titleId, regionStrings[region]); httpcContext context; if(R_SUCCEEDED(res = httpcOpenContext(&context, HTTPC_METHOD_GET, url, 1))) { httpcSetSSLOpt(&context, SSLCOPT_DisableVerify); u32 responseCode = 0; if(R_SUCCEEDED(res = httpcBeginRequest(&context)) && R_SUCCEEDED(res = httpcGetResponseStatusCode(&context, &responseCode, 0))) { if(responseCode == 200) { u32 bytesRead = 0; res = httpcDownloadData(&context, seed, sizeof(seed), &bytesRead); } else { res = R_FBI_HTTP_RESPONSE_CODE; } } httpcCloseContext(&context); } } else { res = R_FBI_OUT_OF_RANGE; } } if(R_SUCCEEDED(res)) { res = FSUSER_AddSeed(titleId, seed); } } else { res = R_FBI_OUT_OF_MEMORY; } return res; } static u32 sigSizes[6] = {0x240, 0x140, 0x80, 0x240, 0x140, 0x80}; u64 util_get_cia_title_id(u8* cia) { u32 headerSize = *(u32*) &cia[0x00]; u32 certSize = *(u32*) &cia[0x08]; u32 ticketSize = *(u32*) &cia[0x0C]; u8* tmd = &cia[((headerSize + 0x3F) & ~0x3F) + ((certSize + 0x3F) & ~0x3F) + ((ticketSize + 0x3F) & ~0x3F)]; return util_get_tmd_title_id(tmd); } u64 util_get_ticket_title_id(u8* ticket) { return __builtin_bswap64(*(u64*) &ticket[sigSizes[ticket[0x03]] + 0x9C]); } u64 util_get_tmd_title_id(u8* tmd) { return __builtin_bswap64(*(u64*) &tmd[sigSizes[tmd[0x03]] + 0x4C]); } u16 util_get_tmd_content_count(u8* tmd) { return __builtin_bswap16(*(u16*) &tmd[sigSizes[tmd[0x03]] + 0x9E]); } u8* util_get_tmd_content_chunk(u8* tmd, u32 index) { return &tmd[sigSizes[tmd[0x03]] + 0x9C4 + (index * 0x30)]; } bool util_filter_cias(void* data, const char* name, u32 attributes) { if((attributes & FS_ATTRIBUTE_DIRECTORY) != 0) { return false; } size_t len = strlen(name); return len >= 4 && strncasecmp(name + len - 4, ".cia", 4) == 0; } bool util_filter_tickets(void* data, const char* name, u32 attributes) { if((attributes & FS_ATTRIBUTE_DIRECTORY) != 0) { return false; } size_t len = strlen(name); return len >= 4 && strncasecmp(name + len - 4, ".tik", 4) == 0; } static const char* path3dsx = NULL; const char* util_get_3dsx_path() { return path3dsx; } void util_set_3dsx_path(const char* path) { path3dsx = path; }