From a6a15eb70d66e3c96bbc164598f9482bb545b3f9 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Fri, 8 Sep 2017 03:12:04 +0200 Subject: [PATCH] Added ability to extract uncompressed .code --- source/common/ui.c | 1 + source/filesys/filetype.c | 6 +-- source/filesys/filetype.h | 2 +- source/game/codelzss.c | 82 +++++++++++++++++++++++++++++++++++++++ source/game/codelzss.h | 7 ++++ source/game/game.h | 1 + source/game/ncch.h | 2 +- source/godmode.c | 13 ++++++- source/utils/gameutil.c | 46 ++++++++++++++++++---- source/utils/gameutil.h | 1 + 10 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 source/game/codelzss.c create mode 100644 source/game/codelzss.h diff --git a/source/common/ui.c b/source/common/ui.c index 4c6f3bc..04d3d15 100644 --- a/source/common/ui.c +++ b/source/common/ui.c @@ -701,6 +701,7 @@ bool ShowProgress(u64 current, u64 total, const char* opstr) DrawRectangle(MAIN_SCREEN, bar_pos_x + 1, bar_pos_y + 1, bar_width - 2, bar_height - 2, COLOR_STD_BG); } DrawRectangle(MAIN_SCREEN, bar_pos_x + 2, bar_pos_y + 2, prog_width, bar_height - 4, COLOR_STD_FONT); + DrawRectangle(MAIN_SCREEN, bar_pos_x + 2 + prog_width, bar_pos_y + 2, (bar_width-4) - prog_width, bar_height - 4, COLOR_STD_BG); TruncateString(progstr, opstr, (bar_width / FONT_WIDTH_EXT) - 7, 8); snprintf(tempstr, 64, "%s (%lu%%)", progstr, prog_percent); diff --git a/source/filesys/filetype.c b/source/filesys/filetype.c index 0d7c32f..50513da 100644 --- a/source/filesys/filetype.c +++ b/source/filesys/filetype.c @@ -86,11 +86,11 @@ u32 IdentifyFileType(const char* path) { strncpy(ext_cetk, "cetk", 5); if (FileGetSize(path_cetk) > 0) return GAME_NUSCDN; // NUS/CDN type 2 - } else if (strncasecmp(fname, TIKDB_NAME_ENC, sizeof(TIKDB_NAME_ENC)) == 0) { + } else if (strncasecmp(fname, TIKDB_NAME_ENC, sizeof(TIKDB_NAME_ENC)+1) == 0) { return BIN_TIKDB | FLAG_ENC; // titlekey database / encrypted - } else if (strncasecmp(fname, TIKDB_NAME_DEC, sizeof(TIKDB_NAME_DEC)) == 0) { + } else if (strncasecmp(fname, TIKDB_NAME_DEC, sizeof(TIKDB_NAME_DEC)+1) == 0) { return BIN_TIKDB; // titlekey database / decrypted - } else if (strncasecmp(fname, KEYDB_NAME, sizeof(KEYDB_NAME)) == 0) { + } else if (strncasecmp(fname, KEYDB_NAME, sizeof(KEYDB_NAME)+1) == 0) { return BIN_KEYDB; // key database } else if ((sscanf(fname, "slot%02lXKey", &id) == 1) && (strncasecmp(ext, "bin", 4) == 0) && (fsize = 16) && (id < 0x40)) { return BIN_LEGKEY; // legacy key file diff --git a/source/filesys/filetype.h b/source/filesys/filetype.h index f868062..31bfd26 100644 --- a/source/filesys/filetype.h +++ b/source/filesys/filetype.h @@ -42,7 +42,7 @@ #define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS)) #define FTYPE_RENAMABLE(tp) (tp&(GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_NDS)) #define FTYPE_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR)) -#define FTYPE_HSINJECTABLE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) +#define FTYPE_HASCODE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI)) #define FTYPE_RESTORABLE(tp) (tp&(IMG_NAND)) #define FTYPE_EBACKUP(tp) (tp&(IMG_NAND)) #define FTYPE_XORPAD(tp) (tp&(BIN_NCCHNFO)) diff --git a/source/game/codelzss.c b/source/game/codelzss.c new file mode 100644 index 0000000..be21570 --- /dev/null +++ b/source/game/codelzss.c @@ -0,0 +1,82 @@ +#include "codelzss.h" + +#define CODE_COMP_SIZE(f) ((f)->off_size_comp & 0xFFFFFF) +#define CODE_COMP_END(f) ((int) CODE_COMP_SIZE(f) - (int) (((f)->off_size_comp >> 24) % 0xFF)) +#define CODE_DEC_SIZE(f) (CODE_COMP_SIZE(f) + (f)->addsize_dec) + +#define CODE_SEG_OFFSET(s) (((s) & 0x0FFF) + 2) +#define CODE_SEG_SIZE(s) ((((s) >> 12) & 0xF) + 3) + +typedef struct { + u32 off_size_comp; // 0xOOSSSSSS, where O == reverse offset and S == size + u32 addsize_dec; // decompressed size - compressed size +} __attribute__((packed)) CodeLzssFooter; + +// see: https://github.com/zoogie/DSP1/blob/master/source/main.c#L44 +u32 DecompressCodeLzss(u8* data_start, u32* code_size, u32 max_size) { + u8* comp_start = data_start; + + // get footer, fix comp_start offset + if ((*code_size < sizeof(CodeLzssFooter)) || (*code_size > max_size)) return 1; + CodeLzssFooter* footer = (CodeLzssFooter*) (void*) (data_start + *code_size - sizeof(CodeLzssFooter)); + if (CODE_COMP_SIZE(footer) <= *code_size) comp_start += *code_size - CODE_COMP_SIZE(footer); + else return 1; + + // more sanity checks + if ((CODE_COMP_END(footer) < 0) || (CODE_DEC_SIZE(footer) > max_size)) + return 1; // not reverse LZSS compressed code or too big uncompressed + + // set pointers + u8* data_end = (u8*) comp_start + CODE_DEC_SIZE(footer); + u8* ptr_in = (u8*) comp_start + CODE_COMP_END(footer); + u8* ptr_out = data_end; + + // main decompression loop + while ((ptr_in > comp_start) && (ptr_out > comp_start)) { + // sanity check + if (ptr_out < ptr_in) return 1; + + // read and process control byte + u8 ctrlbyte = *(--ptr_in); + for (int i = 7; i >= 0; i--) { + // end conditions met? + if ((ptr_in <= comp_start) || (ptr_out <= comp_start)) + break; + + // process control byte + if ((ctrlbyte >> i) & 0x1) { + // control bit set, read segment code + ptr_in -= 2; + u16 seg_code = getle16(ptr_in); + if (ptr_in < comp_start) return 1; // corrupted code + u32 seg_off = CODE_SEG_OFFSET(seg_code); + u32 seg_len = CODE_SEG_SIZE(seg_code); + + // sanity check for output pointer + if ((ptr_out - seg_len < comp_start) || (ptr_out + seg_off >= data_end)) + return 1; + + // copy data to the correct place + for (u32 c = 0; c < seg_len; c++) { + u8 byte = *(ptr_out + seg_off); + *(--ptr_out) = byte; + } + } else { + // sanity check for both pointers + if ((ptr_out == comp_start) || (ptr_in == comp_start)) + return 1; + + // control bit not set, copy byte verbatim + *(--ptr_out) = *(--ptr_in); + } + } + } + + // check pointers + if ((ptr_in != comp_start) || (ptr_out != comp_start)) + return 1; + + // all fine if arriving here - return the result + *code_size = data_end - data_start; + return 0; +} diff --git a/source/game/codelzss.h b/source/game/codelzss.h new file mode 100644 index 0000000..7b928da --- /dev/null +++ b/source/game/codelzss.h @@ -0,0 +1,7 @@ +#pragma once + +#include "common.h" + +#define EXEFS_CODE_NAME ".code" + +u32 DecompressCodeLzss(u8* data_start, u32* code_size, u32 max_size); diff --git a/source/game/game.h b/source/game/game.h index b5f858f..3ec667c 100644 --- a/source/game/game.h +++ b/source/game/game.h @@ -8,5 +8,6 @@ #include "firm.h" #include "boss.h" #include "smdh.h" +#include "codelzss.h" #include "nds.h" #include "ncchinfo.h" diff --git a/source/game/ncch.h b/source/game/ncch.h index 4d6b5be..24789a8 100644 --- a/source/game/ncch.h +++ b/source/game/ncch.h @@ -32,7 +32,7 @@ typedef struct { char name[8]; u8 reserved[0x5]; - u8 flag; // bit 1 for SD + u8 flag; // bit 1 for SD, bit 0 for compressed .code u16 remaster_version; u8 sci_data[0x30]; u8 dependencies[0x180]; diff --git a/source/godmode.c b/source/godmode.c index 732867a..224b8a6 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -706,7 +706,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool titleinfo = (FTYPE_TITLEINFO(filetype)); bool renamable = (FTYPE_RENAMABLE(filetype)); bool transferable = (FTYPE_TRANSFERABLE(filetype) && IS_A9LH && (drvtype & DRV_FAT)); - bool hsinjectable = (FTYPE_HSINJECTABLE(filetype)); + bool hsinjectable = (FTYPE_HASCODE(filetype)); + bool extrcodeable = (FTYPE_HASCODE(filetype)); bool restorable = (FTYPE_RESTORABLE(filetype) && IS_A9LH && !(drvtype & DRV_SYSNAND)); bool ebackupable = (FTYPE_EBACKUP(filetype)); bool xorpadable = (FTYPE_XORPAD(filetype)); @@ -714,7 +715,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur bool scriptable = (FTYPE_SCRIPT(filetype)); bool bootable = (FTYPE_BOOTABLE(filetype) && !(drvtype & DRV_VIRTUAL)); bool installable = (FTYPE_INSTALLABLE(filetype) && !(drvtype & DRV_VIRTUAL)); - bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || keyinitable || bootable || scriptable || installable; + bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || extrcodeable || keyinitable || bootable || scriptable || installable; char pathstr[32+1]; TruncateString(pathstr, curr_entry->path, 32, 8); @@ -866,6 +867,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur int verify = (verificable) ? ++n_opt : -1; int ctrtransfer = (transferable) ? ++n_opt : -1; int hsinject = (hsinjectable) ? ++n_opt : -1; + int extrcode = (extrcodeable) ? ++n_opt : -1; int rename = (renamable) ? ++n_opt : -1; int xorpad = (xorpadable) ? ++n_opt : -1; int xorpad_inplace = (xorpadable) ? ++n_opt : -1; @@ -891,6 +893,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur if (rename > 0) optionstr[rename-1] = "Rename file"; if (xorpad > 0) optionstr[xorpad-1] = "Build XORpads (SD output)"; if (xorpad_inplace > 0) optionstr[xorpad_inplace-1] = "Build XORpads (inplace)"; + if (extrcode > 0) optionstr[extrcode-1] = "Extract " EXEFS_CODE_NAME; if (keyinit > 0) optionstr[keyinit-1] = "Init " KEYDB_NAME; if (install > 0) optionstr[install-1] = "Install FIRM"; if (boot > 0) optionstr[boot-1] = "Boot FIRM"; @@ -1181,6 +1184,12 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur (InjectHealthAndSafety(curr_entry->path, destdrv[user_select-1]) == 0) ? "success" : "failed"); } return 0; + } else if (user_select == extrcode) { // -> Extract code + ShowString("%s\nExtracting .code,\nplease wait...", pathstr); + if (ExtractCodeFromCxiFile(curr_entry->path) == 0) { + ShowPrompt(false, "%s\n.code extracted to " OUTPUT_PATH, pathstr); + } else ShowPrompt(false, "%s\n.code extract failed", pathstr); + return 0; } else if (user_select == ctrtransfer) { // -> transfer CTRNAND image to SysNAND char* destdrv[2] = { NULL }; n_opt = 0; diff --git a/source/utils/gameutil.c b/source/utils/gameutil.c index 8608443..e6684a2 100644 --- a/source/utils/gameutil.c +++ b/source/utils/gameutil.c @@ -121,7 +121,7 @@ u32 LoadCiaStub(CiaStub* stub, const char* path) { return 0; } -u32 LoadExeFsFile(void* data, const char* path, u32 offset, const char* name, u32 size_max) { +u32 LoadExeFsFile(void* data, const char* path, u32 offset, const char* name, u32 size_max, u32* bytes_read) { NcchHeader ncch; ExeFsHeader exefs; FIL file; @@ -161,6 +161,7 @@ u32 LoadExeFsFile(void* data, const char* path, u32 offset, const char* name, u3 } } else ret = 1; + if (bytes_read) *bytes_read = btr; fvx_close(&file); return ret; } @@ -172,7 +173,7 @@ u32 LoadNcchMeta(CiaMeta* meta, const char* path, u64 offset) { // get dependencies from exthdr, icon from exeFS if ((LoadNcchHeaders(&ncch, &exthdr, NULL, path, offset) != 0) || (BuildCiaMeta(meta, &exthdr, NULL) != 0) || - (LoadExeFsFile(meta->smdh, path, offset, "icon", sizeof(meta->smdh)))) + (LoadExeFsFile(meta->smdh, path, offset, "icon", sizeof(meta->smdh), NULL))) return 1; return 0; @@ -1256,7 +1257,7 @@ u32 BuildCiaFromNcchFile(const char* path_ncch, const char* path_cia) { // optional stuff (proper titlekey / meta data) FindTitleKey((&cia->ticket), title_id); if (exthdr && (BuildCiaMeta(meta, exthdr, NULL) == 0) && - (LoadExeFsFile(meta->smdh, path_ncch, 0, "icon", sizeof(meta->smdh)) == 0) && + (LoadExeFsFile(meta->smdh, path_ncch, 0, "icon", sizeof(meta->smdh), NULL) == 0) && (InsertCiaMeta(path_cia, meta) == 0)) cia->header.size_meta = CIA_META_SIZE; @@ -1321,7 +1322,7 @@ u32 BuildCiaFromNcsdFile(const char* path_ncsd, const char* path_cia) { // optional stuff (proper titlekey / meta data) FindTitleKey(&(cia->ticket), title_id); if ((BuildCiaMeta(meta, exthdr, NULL) == 0) && - (LoadExeFsFile(meta->smdh, path_ncsd, NCSD_CNT0_OFFSET, "icon", sizeof(meta->smdh)) == 0) && + (LoadExeFsFile(meta->smdh, path_ncsd, NCSD_CNT0_OFFSET, "icon", sizeof(meta->smdh), NULL) == 0) && (InsertCiaMeta(path_cia, meta) == 0)) cia->header.size_meta = CIA_META_SIZE; @@ -1401,6 +1402,37 @@ u32 DumpCxiSrlFromTmdFile(const char* path) { return 0; } +u32 ExtractCodeFromCxiFile(const char* path) { + u8* code = (u8*) TEMP_BUFFER; + u32 code_max_size = TEMP_BUFFER_SIZE; + + NcchHeader ncch; + NcchExtHeader exthdr; + + // load ncch, exthdr, .code + u32 code_size; + if ((LoadNcchHeaders(&ncch, &exthdr, NULL, path, 0) != 0) || + (LoadExeFsFile(code, path, 0, EXEFS_CODE_NAME, code_max_size, &code_size))) + return 1; + + // decompress code (only if required) + if ((exthdr.flag & 0x1) && (DecompressCodeLzss(code, &code_size, code_max_size) != 0)) + return 1; + + // build output path + char dest[256]; + snprintf(dest, 256, OUTPUT_PATH "/%016llX%s%s", ncch.programId, (exthdr.flag & 0x1) ? ".dec" : "", EXEFS_CODE_NAME); + + // write output file + fvx_unlink(dest); + if (fvx_qwrite(dest, code, 0, code_size, NULL) != FR_OK) { + fvx_unlink(dest); + return 1; + } + + return 0; +} + u32 LoadSmdhFromGameFile(const char* path, Smdh* smdh) { u32 filetype = IdentifyFileType(path); @@ -1408,9 +1440,9 @@ u32 LoadSmdhFromGameFile(const char* path, Smdh* smdh) { UINT btr; if ((fvx_qread(path, smdh, 0, sizeof(Smdh), &btr) == FR_OK) || (btr == sizeof(Smdh))) return 0; } else if (filetype & GAME_NCCH) { // NCCH file - if (LoadExeFsFile(smdh, path, 0, "icon", sizeof(Smdh)) == 0) return 0; + if (LoadExeFsFile(smdh, path, 0, "icon", sizeof(Smdh), NULL) == 0) return 0; } else if (filetype & GAME_NCSD) { // NCSD file - if (LoadExeFsFile(smdh, path, NCSD_CNT0_OFFSET, "icon", sizeof(Smdh)) == 0) return 0; + if (LoadExeFsFile(smdh, path, NCSD_CNT0_OFFSET, "icon", sizeof(Smdh), NULL) == 0) return 0; } else if (filetype & GAME_CIA) { // CIA file CiaInfo info; UINT btr; @@ -1419,7 +1451,7 @@ u32 LoadSmdhFromGameFile(const char* path, Smdh* smdh) { (GetCiaInfo(&info, (CiaHeader*) &info) != 0)) return 1; if ((info.offset_meta) && (fvx_qread(path, smdh, info.offset_meta + 0x400, sizeof(Smdh), &btr) == FR_OK) && (btr == sizeof(Smdh))) return 0; - else if (LoadExeFsFile(smdh, path, info.offset_content, "icon", sizeof(Smdh)) == 0) return 0; + else if (LoadExeFsFile(smdh, path, info.offset_content, "icon", sizeof(Smdh), NULL) == 0) return 0; } else if (filetype & GAME_TMD) { char path_content[256]; if (GetTmdContentPath(path_content, path) != 0) return 1; diff --git a/source/utils/gameutil.h b/source/utils/gameutil.h index 0d233e0..47232a2 100644 --- a/source/utils/gameutil.h +++ b/source/utils/gameutil.h @@ -7,6 +7,7 @@ u32 CheckEncryptedGameFile(const char* path); u32 CryptGameFile(const char* path, bool inplace, bool encrypt); u32 BuildCiaFromGameFile(const char* path, bool force_legit); u32 DumpCxiSrlFromTmdFile(const char* path); +u32 ExtractCodeFromCxiFile(const char* path); u32 ShowGameFileTitleInfo(const char* path); u32 BuildNcchInfoXorpads(const char* destdir, const char* path); u32 CheckHealthAndSafetyInject(const char* hsdrv);