From 5c138e12194cc4dd92e405b4462172dfc7ac7688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hyarion=20Sany=C3=ABn=C3=B3na?= Date: Sun, 22 Apr 2018 12:28:37 -0500 Subject: [PATCH] Add scripting commands extrcode, cmprcode, cp -p * extrcode already existed, but now has a progress bar and an entry in HelloScript. * cmprcode recompresses a code binary into its original format. It takes 2-3 minutes. * cp -p appends file 2 to the end of file 1 rather than overwriting it. --- arm9/source/filesys/fsutil.c | 44 +++-- arm9/source/filesys/fsutil.h | 1 + arm9/source/game/codelzss.c | 270 +++++++++++++++++++++++++++++++ arm9/source/game/codelzss.h | 1 + arm9/source/utils/gameutil.c | 40 +++++ arm9/source/utils/gameutil.h | 1 + arm9/source/utils/scripting.c | 15 +- resources/sample/HelloScript.gm9 | 20 ++- 8 files changed, 370 insertions(+), 22 deletions(-) diff --git a/arm9/source/filesys/fsutil.c b/arm9/source/filesys/fsutil.c index 8d778b8..2ad16fa 100644 --- a/arm9/source/filesys/fsutil.c +++ b/arm9/source/filesys/fsutil.c @@ -11,8 +11,8 @@ #include "ff.h" #include "ui.h" -#define SKIP_CUR (1UL<< 9) -#define OVERWRITE_CUR (1UL<<10) +#define SKIP_CUR (1UL<<10) +#define OVERWRITE_CUR (1UL<<11) #define _MAX_FS_OPT 8 // max file selector options @@ -439,6 +439,8 @@ bool PathExist(const char* path) { bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer, u32 bufsiz) { bool to_virtual = GetVirtualSource(dest); bool silent = (flags && (*flags & SILENT)); + bool append = (flags && (*flags & APPEND_ALL)); + bool calcsha = (flags && (*flags & CALC_SHA) && !append); bool ret = false; // check destination write permission (special paths only) @@ -465,6 +467,11 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer, DIR pdir; char* fname = orig + strnlen(orig, 256); + if (append) { + if (!silent) ShowPrompt(false, "%s\nError: Cannot append a folder", deststr); + return false; + } + // create the destination folder if it does not already exist if (fvx_opendir(&pdir, dest) != FR_OK) { if (fvx_mkdir(dest) != FR_OK) { @@ -509,7 +516,8 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer, } else { // copying files FIL ofile; FIL dfile; - u64 fsize; + u64 osize; + u64 dsize; if (fvx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK) { if (!FileUnlock(orig) || (fvx_open(&ofile, orig, FA_READ | FA_OPEN_EXISTING) != FR_OK)) @@ -517,48 +525,54 @@ bool PathMoveCopyRec(char* dest, char* orig, u32* flags, bool move, u8* buffer, ShowProgress(0, 0, orig); // reinit progress bar } - if (fvx_open(&dfile, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) { + if ((!append || (fvx_open(&dfile, dest, FA_WRITE | FA_OPEN_EXISTING) != FR_OK)) && + (fvx_open(&dfile, dest, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK)) { if (!silent) ShowPrompt(false, "%s\nError: Cannot open destination file", deststr); fvx_close(&ofile); return false; } ret = true; // destination file exists by now, so we need to handle deletion - fsize = fvx_size(&ofile); // check space via cluster preallocation - if ((fvx_lseek(&dfile, fsize) != FR_OK) || (fvx_sync(&dfile) != FR_OK) || (fvx_tell(&dfile) != fsize)) { + osize = fvx_size(&ofile); + dsize = fvx_size(&dfile); // always 0 if not appending to file + if ((fvx_lseek(&dfile, (osize + dsize)) != FR_OK) || (fvx_sync(&dfile) != FR_OK) || (fvx_tell(&dfile) != (osize + dsize))) { // check space via cluster preallocation if (!silent) ShowPrompt(false, "%s\nError: Not enough space available", deststr); ret = false; } - fvx_lseek(&dfile, 0); + fvx_lseek(&dfile, dsize); fvx_sync(&dfile); fvx_lseek(&ofile, 0); fvx_sync(&ofile); - if (flags && (*flags & CALC_SHA)) sha_init(SHA256_MODE); - for (u64 pos = 0; (pos < fsize) && ret; pos += bufsiz) { + if (calcsha) sha_init(SHA256_MODE); + for (u64 pos = 0; (pos < osize) && ret; pos += bufsiz) { UINT bytes_read = 0; UINT bytes_written = 0; if ((fvx_read(&ofile, buffer, bufsiz, &bytes_read) != FR_OK) || (fvx_write(&dfile, buffer, bytes_read, &bytes_written) != FR_OK) || (bytes_read != bytes_written)) ret = false; - if (ret && !ShowProgress(pos + bytes_read, fsize, orig)) { + + u64 current = pos + bytes_read; + u64 total = osize; + if (ret && !ShowProgress(current, total, orig)) { if (flags && (*flags & NO_CANCEL)) { ShowPrompt(false, "%s\nCancel is not allowed here", deststr); } else ret = !ShowPrompt(true, "%s\nB button detected. Cancel?", deststr); ShowProgress(0, 0, orig); - ShowProgress(pos + bytes_read, fsize, orig); + ShowProgress(current, total, orig); } - if (flags && (*flags & CALC_SHA)) + if (calcsha) sha_update(buffer, bytes_read); } ShowProgress(1, 1, orig); fvx_close(&ofile); fvx_close(&dfile); - if (!ret) fvx_unlink(dest); - else if (!to_virtual && flags && (*flags & CALC_SHA)) { + if (!ret && ((dsize == 0) || (fvx_lseek(&dfile, dsize) != FR_OK) || (f_truncate(&dfile) != FR_OK))) { + fvx_unlink(dest); + } else if (!to_virtual && calcsha) { u8 sha256[0x20]; char* ext_sha = dest + strnlen(dest, 256); strncpy(ext_sha, ".sha", 256 - (ext_sha - dest)); @@ -616,7 +630,7 @@ bool PathMoveCopy(const char* dest, const char* orig, u32* flags, bool move) { } // check if destination exists - if (flags && !(*flags & (OVERWRITE_CUR|OVERWRITE_ALL)) && (fa_stat(ldest, NULL) == FR_OK)) { + if (flags && !(*flags & (OVERWRITE_CUR|OVERWRITE_ALL|APPEND_ALL)) && (fa_stat(ldest, NULL) == FR_OK)) { if (*flags & SKIP_ALL) { *flags |= SKIP_CUR; return true; diff --git a/arm9/source/filesys/fsutil.h b/arm9/source/filesys/fsutil.h index 8578f3a..0760086 100644 --- a/arm9/source/filesys/fsutil.h +++ b/arm9/source/filesys/fsutil.h @@ -12,6 +12,7 @@ #define ASK_ALL (1UL<<6) #define SKIP_ALL (1UL<<7) #define OVERWRITE_ALL (1UL<<8) +#define APPEND_ALL (1UL<<9) // file selector flags #define NO_DIRS (1UL<<0) diff --git a/arm9/source/game/codelzss.c b/arm9/source/game/codelzss.c index 4e14720..7a75393 100644 --- a/arm9/source/game/codelzss.c +++ b/arm9/source/game/codelzss.c @@ -1,4 +1,5 @@ #include "codelzss.h" +#include "ui.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)) @@ -44,6 +45,12 @@ u32 DecompressCodeLzss(u8* code, u32* code_size, u32 max_size) { // main decompression loop while ((ptr_in > comp_start) && (ptr_out > comp_start)) { + if (!ShowProgress(data_end - ptr_out, data_end - data_start, "Decompressing .code...")) { + if (ShowPrompt(true, "Decompressing .code...\nB button detected. Cancel?")) return 1; + ShowProgress(0, data_end - data_start, "Decompressing .code..."); + ShowProgress(data_end - ptr_out, data_end - data_start, "Decompressing .code..."); + } + // sanity check if (ptr_out < ptr_in) return 1; @@ -91,3 +98,266 @@ u32 DecompressCodeLzss(u8* code, u32* code_size, u32 max_size) { *code_size = data_end - data_start; return 0; } + +// see https://github.com/dnasdw/3dstool/blob/master/src/backwardlz77.cpp (GPLv3) +typedef struct { + u16 WindowPos; + u16 WindowLen; + s16* OffsetTable; + s16* ReversedOffsetTable; + s16* ByteTable; + s16* EndTable; +} sCompressInfo; + +void initTable(sCompressInfo* a_pInfo, void* a_pWork) { + a_pInfo->WindowPos = 0; + a_pInfo->WindowLen = 0; + a_pInfo->OffsetTable = (s16*)(a_pWork); + a_pInfo->ReversedOffsetTable = (s16*)(a_pWork) + 4098; + a_pInfo->ByteTable = (s16*)(a_pWork) + 4098 + 4098; + a_pInfo->EndTable = (s16*)(a_pWork) + 4098 + 4098 + 256; + + for (int i = 0; i < 256; i++) { + a_pInfo->ByteTable[i] = -1; + a_pInfo->EndTable[i] = -1; + } +} + +int search(sCompressInfo* a_pInfo, const u8* a_pSrc, int* a_nOffset, int a_nMaxSize) { + if (a_nMaxSize < 3) { + return 0; + } + + const u8* pSearch = NULL; + int nSize = 2; + const u16 uWindowPos = a_pInfo->WindowPos; + const u16 uWindowLen = a_pInfo->WindowLen; + s16* pReversedOffsetTable = a_pInfo->ReversedOffsetTable; + + for (s16 nOffset = a_pInfo->EndTable[*(a_pSrc - 1)]; nOffset != -1; nOffset = pReversedOffsetTable[nOffset]) { + if (nOffset < uWindowPos) { + pSearch = a_pSrc + uWindowPos - nOffset; + } else { + pSearch = a_pSrc + uWindowLen + uWindowPos - nOffset; + } + + if (pSearch - a_pSrc < 3) { + continue; + } + + if (*(pSearch - 2) != *(a_pSrc - 2) || *(pSearch - 3) != *(a_pSrc - 3)) { + continue; + } + + int nMaxSize = (int)((s64)min(a_nMaxSize, pSearch - a_pSrc)); + int nCurrentSize = 3; + + while (nCurrentSize < nMaxSize && *(pSearch - nCurrentSize - 1) == *(a_pSrc - nCurrentSize - 1)) { + nCurrentSize++; + } + + if (nCurrentSize > nSize) { + nSize = nCurrentSize; + *a_nOffset = (int)(pSearch - a_pSrc); + if (nSize == a_nMaxSize) { + break; + } + } + } + + if (nSize < 3) { + return 0; + } + + return nSize; +} + +void slideByte(sCompressInfo* a_pInfo, const u8* a_pSrc) { + u8 uInData = *(a_pSrc - 1); + u16 uInsertOffset = 0; + const u16 uWindowPos = a_pInfo->WindowPos; + const u16 uWindowLen = a_pInfo->WindowLen; + s16* pOffsetTable = a_pInfo->OffsetTable; + s16* pReversedOffsetTable = a_pInfo->ReversedOffsetTable; + s16* pByteTable = a_pInfo->ByteTable; + s16* pEndTable = a_pInfo->EndTable; + + if (uWindowLen == 4098) { + u8 uOutData = *(a_pSrc + 4097); + + if ((pByteTable[uOutData] = pOffsetTable[pByteTable[uOutData]]) == -1) { + pEndTable[uOutData] = -1; + } else { + pReversedOffsetTable[pByteTable[uOutData]] = -1; + } + + uInsertOffset = uWindowPos; + } else { + uInsertOffset = uWindowLen; + } + + s16 nOffset = pEndTable[uInData]; + + if (nOffset == -1) { + pByteTable[uInData] = uInsertOffset; + } else { + pOffsetTable[nOffset] = uInsertOffset; + } + + pEndTable[uInData] = uInsertOffset; + pOffsetTable[uInsertOffset] = -1; + pReversedOffsetTable[uInsertOffset] = nOffset; + + if (uWindowLen == 4098) { + a_pInfo->WindowPos = (uWindowPos + 1) % 4098; + } else { + a_pInfo->WindowLen++; + } +} + +inline void slide(sCompressInfo* a_pInfo, const u8* a_pSrc, int a_nSize) { + for (int i = 0; i < a_nSize; i++) { + slideByte(a_pInfo, a_pSrc--); + } +} + +s64 alignBytes(s64 a_nData, s64 a_nAlignment) { + return (a_nData + a_nAlignment - 1) / a_nAlignment * a_nAlignment; +} + +bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_pCompressed, u32* a_uCompressedSize) { + const int s_nCompressWorkSize = (4098 + 4098 + 256 + 256) * sizeof(s16); + bool bResult = true; + + if (a_uUncompressedSize > sizeof(CodeLzssFooter) && *a_uCompressedSize >= a_uUncompressedSize) { + u8* pWork = malloc(s_nCompressWorkSize * sizeof(u8)); + if (!pWork) return false; + + do { + sCompressInfo info; + initTable(&info, pWork); + + const int nMaxSize = 0xF + 3; + const u8* pSrc = a_pUncompressed + a_uUncompressedSize; + u8* pDest = a_pCompressed + a_uUncompressedSize; + + while (pSrc - a_pUncompressed > 0 && pDest - a_pCompressed > 0) { + if (!ShowProgress((u32)(a_pUncompressed + a_uUncompressedSize - pSrc), a_uUncompressedSize, "Compressing .code...")) { + if (ShowPrompt(true, "Compressing .code...\nB button detected. Cancel?")) { + bResult = false; + break; + } + ShowProgress(0, a_uUncompressedSize, "Compressing .code..."); + ShowProgress((u32)(a_pUncompressed + a_uUncompressedSize - pSrc), a_uUncompressedSize, "Compressing .code..."); + } + + u8* pFlag = --pDest; + *pFlag = 0; + + for (int i = 0; i < 8; i++) { + int nOffset = 0; + int nSize = search(&info, pSrc, &nOffset, (int)((s64)min((s64)min(nMaxSize, pSrc - a_pUncompressed), a_pUncompressed + a_uUncompressedSize - pSrc))); + + if (nSize < 3) { + if (pDest - a_pCompressed < 1) { + bResult = false; + break; + } + + slide(&info, pSrc, 1); + *--pDest = *--pSrc; + } else { + if (pDest - a_pCompressed < 2) { + bResult = false; + break; + } + + *pFlag |= 0x80 >> i; + slide(&info, pSrc, nSize); + pSrc -= nSize; + nSize -= 3; + *--pDest = (nSize << 4 & 0xF0) | ((nOffset - 3) >> 8 & 0x0F); + *--pDest = (nOffset - 3) & 0xFF; + } + + if (pSrc - a_pUncompressed <= 0) { + break; + } + } + + if (!bResult) { + break; + } + } + + if (!bResult) { + break; + } + + *a_uCompressedSize = (u32)(a_pCompressed + a_uUncompressedSize - pDest); + } while (false); + + free(pWork); + } else { + bResult = false; + } + + if (bResult) { + u32 uOrigSize = a_uUncompressedSize; + u8* pCompressBuffer = a_pCompressed + a_uUncompressedSize - *a_uCompressedSize; + u32 uCompressBufferSize = *a_uCompressedSize; + u32 uOrigSafe = 0; + u32 uCompressSafe = 0; + bool bOver = false; + + while (uOrigSize > 0) { + u8 uFlag = pCompressBuffer[--uCompressBufferSize]; + + for (int i = 0; i < 8; i++) { + if ((uFlag << i & 0x80) == 0) { + uCompressBufferSize--; + uOrigSize--; + } else { + int nSize = (pCompressBuffer[--uCompressBufferSize] >> 4 & 0x0F) + 3; + uCompressBufferSize--; + uOrigSize -= nSize; + + if (uOrigSize < uCompressBufferSize) { + uOrigSafe = uOrigSize; + uCompressSafe = uCompressBufferSize; + bOver = true; + break; + } + } + + if (uOrigSize <= 0) { + break; + } + } + + if (bOver) { + break; + } + } + + u32 uCompressedSize = *a_uCompressedSize - uCompressSafe; + u32 uPadOffset = uOrigSafe + uCompressedSize; + u32 uCompFooterOffset = (u32)(alignBytes(uPadOffset, 4)); + *a_uCompressedSize = uCompFooterOffset + sizeof(CodeLzssFooter); + u32 uTop = *a_uCompressedSize - uOrigSafe; + u32 uBottom = *a_uCompressedSize - uPadOffset; + + if (*a_uCompressedSize >= a_uUncompressedSize || uTop > 0xFFFFFF) { + bResult = false; + } else { + memcpy(a_pCompressed, a_pUncompressed, uOrigSafe); + memmove(a_pCompressed + uOrigSafe, pCompressBuffer + uCompressSafe, uCompressedSize); + memset(a_pCompressed + uPadOffset, 0xFF, uCompFooterOffset - uPadOffset); + CodeLzssFooter* pCompFooter = (CodeLzssFooter*)(a_pCompressed + uCompFooterOffset); + pCompFooter->off_size_comp = uTop | (uBottom << 24); + pCompFooter->addsize_dec = a_uUncompressedSize - *a_uCompressedSize; + } + } + + return bResult; +} diff --git a/arm9/source/game/codelzss.h b/arm9/source/game/codelzss.h index fe3266e..4b902c5 100644 --- a/arm9/source/game/codelzss.h +++ b/arm9/source/game/codelzss.h @@ -6,3 +6,4 @@ u32 GetCodeLzssUncompressedSize(void* footer, u32 comp_size); u32 DecompressCodeLzss(u8* code, u32* code_size, u32 max_size); +bool CompressCodeLzss(const u8* a_pUncompressed, u32 a_uUncompressedSize, u8* a_pCompressed, u32* a_uCompressedSize); diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 94828f9..323dcef 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -1615,6 +1615,46 @@ u32 ExtractCodeFromCxiFile(const char* path, const char* path_out, char* extstr) return 0; } +u32 CompressCode(const char* path, const char* path_out) { + char dest[256]; + + strncpy(dest, path_out ? path_out : OUTPUT_PATH, 255); + if (!CheckWritePermissions(dest)) return 1; + if (!path_out && (fvx_rmkdir(OUTPUT_PATH) != FR_OK)) return 1; + + // allocate memory + u32 code_dec_size = fvx_qsize(path); + u8* code_dec = (u8*) malloc(code_dec_size); + u32 code_cmp_size = code_dec_size; + u8* code_cmp = (u8*) malloc(code_cmp_size); + if (!code_dec || !code_cmp) { + if (code_dec != NULL) free(code_dec); + if (code_cmp != NULL) free(code_cmp); + ShowPrompt(false, "Out of memory."); + return 1; + } + + // load code.bin and compress code + if ((fvx_qread(path, code_dec, 0, code_dec_size, NULL) != FR_OK) || + (!CompressCodeLzss(code_dec, code_dec_size, code_cmp, &code_cmp_size))) { + free(code_dec); + free(code_cmp); + return 1; + } + + // write output file + fvx_unlink(dest); + free(code_dec); + if (fvx_qwrite(dest, code_cmp, 0, code_cmp_size, NULL) != FR_OK) { + fvx_unlink(dest); + free(code_cmp); + return 1; + } + + free(code_cmp); + return 0; +} + u32 ExtractDataFromDisaDiff(const char* path) { char dest[256]; u32 ret = 0; diff --git a/arm9/source/utils/gameutil.h b/arm9/source/utils/gameutil.h index b97d34b..5d5bef4 100644 --- a/arm9/source/utils/gameutil.h +++ b/arm9/source/utils/gameutil.h @@ -8,6 +8,7 @@ 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, const char* path_out, char* extstr); +u32 CompressCode(const char* path, const char* path_out); u32 ExtractDataFromDisaDiff(const char* path); u64 GetGameFileTrimmedSize(const char* path); u32 TrimGameFile(const char* path); diff --git a/arm9/source/utils/scripting.c b/arm9/source/utils/scripting.c index 2150c44..4e250fb 100644 --- a/arm9/source/utils/scripting.c +++ b/arm9/source/utils/scripting.c @@ -108,6 +108,7 @@ typedef enum { CMD_ID_ENCRYPT, CMD_ID_BUILDCIA, CMD_ID_EXTRCODE, + CMD_ID_CMPRCODE, CMD_ID_SDUMP, CMD_ID_APPLYIPS, CMD_ID_APPLYBPS, @@ -157,7 +158,7 @@ Gm9ScriptCmd cmd_list[] = { { CMD_ID_STRREP , "strrep" , 3, 0 }, { CMD_ID_CHK , "chk" , 2, _FLG('u') }, { CMD_ID_ALLOW , "allow" , 1, _FLG('a') }, - { CMD_ID_CP , "cp" , 2, _FLG('h') | _FLG('w') | _FLG('k') | _FLG('s') | _FLG('n')}, + { CMD_ID_CP , "cp" , 2, _FLG('h') | _FLG('w') | _FLG('k') | _FLG('s') | _FLG('n') | _FLG('p')}, { CMD_ID_MV , "mv" , 2, _FLG('w') | _FLG('k') | _FLG('s') | _FLG('n') }, { CMD_ID_INJECT , "inject" , 2, _FLG('n') }, { CMD_ID_FILL , "fill" , 2, 0 }, @@ -178,6 +179,7 @@ Gm9ScriptCmd cmd_list[] = { { CMD_ID_ENCRYPT , "encrypt" , 1, 0 }, { CMD_ID_BUILDCIA, "buildcia", 1, _FLG('l') }, { CMD_ID_EXTRCODE, "extrcode", 2, 0 }, + { CMD_ID_CMPRCODE, "cmprcode", 2, 0 }, { CMD_ID_SDUMP , "sdump", 1, _FLG('w') }, { CMD_ID_APPLYIPS, "applyips", 3, 0 }, { CMD_ID_APPLYBPS, "applybps", 3, 0 }, @@ -551,6 +553,7 @@ u32 get_flag(char* str, u32 len, char* err_str) { else if (strncmp(str, "--legit", len) == 0) flag_char = 'l'; else if (strncmp(str, "--no_cancel", len) == 0) flag_char = 'n'; else if (strncmp(str, "--optional", len) == 0) flag_char = 'o'; + else if (strncmp(str, "--append", len) == 0) flag_char = 'p'; else if (strncmp(str, "--recursive", len) == 0) flag_char = 'r'; else if (strncmp(str, "--silent", len) == 0) flag_char = 's'; else if (strncmp(str, "--unequal", len) == 0) flag_char = 'u'; @@ -1107,6 +1110,7 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { if (flags & _FLG('s')) flags_ext |= SILENT; if (flags & _FLG('w')) flags_ext |= OVERWRITE_ALL; else if (flags & _FLG('k')) flags_ext |= SKIP_ALL; + else if (flags & _FLG('p')) flags_ext |= APPEND_ALL; ret = PathMoveCopy(argv[1], argv[0], &flags_ext, false); if (err_str) snprintf(err_str, _ERR_STR_LEN, "copy fail"); } @@ -1298,15 +1302,20 @@ bool run_cmd(cmd_id id, u32 flags, char** argv, char* err_str) { } else if (id == CMD_ID_EXTRCODE) { u64 filetype = IdentifyFileType(argv[0]); - if ((filetype&(GAME_NCCH|FLAG_CXI)) != (GAME_NCCH|FLAG_CXI)) { + if (!FTYPE_HASCODE(filetype)) { ret = false; - if (err_str) snprintf(err_str, _ERR_STR_LEN, "not a CXI file"); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "does not contain .code"); } else { ShowString("Extracting .code, please wait..."); ret = (ExtractCodeFromCxiFile(argv[0], argv[1], NULL) == 0); if (err_str) snprintf(err_str, _ERR_STR_LEN, "extract .code failed"); } } + else if (id == CMD_ID_CMPRCODE) { + ShowString("Compressing .code, please wait..."); + ret = (CompressCode(argv[0], argv[1]) == 0); + if (err_str) snprintf(err_str, _ERR_STR_LEN, "compress .code failed"); + } else if (id == CMD_ID_SDUMP) { ret = false; if (err_str) snprintf(err_str, _ERR_STR_LEN, "build failed"); diff --git a/resources/sample/HelloScript.gm9 b/resources/sample/HelloScript.gm9 index 172c4b3..a8acdf1 100644 --- a/resources/sample/HelloScript.gm9 +++ b/resources/sample/HelloScript.gm9 @@ -141,8 +141,9 @@ imgmount S:/ctrnand_full.bin # 'cp' COMMAND # Use 'cp' to copy a file or directory (recursively) # -h / --hash also adds a .sha file containing the files SHA256 -# -w / --overwite forces overwrite on existing files -# -k / --skip forces skip on existing files +# -w / --overwrite forces overwrite on existing files (disables -k and -p) +# -k / --skip forces skip on existing files (disables -p) +# -p / --append will append copied files to the end of existing files (disables -h) # -n / --no_cancel prevents user cancels (useful on critical operations) cp -h -w -n 7:/dbs/ticket.db $[TESTPATH] @@ -155,8 +156,8 @@ imgumount findnot $[TESTPATH]_???.rn RENPATH # 'mv' COMMAND -# The 'mv command renames or moves a file or directory -# -w / --overwite forces overwrite on existing files +# The 'mv' command renames or moves a file or directory +# -w / --overwrite forces overwrite on existing files (disables -k) # -k / --skip forces skip on existing files # -n / --no_cancel prevents user cancels (useful on critical operations) mv -w -k $[TESTPATH] $[RENPATH] @@ -268,6 +269,17 @@ verify S:/firm1.bin # -l / --legit force CIA to be legit (only works for legit system installed titles) # buildcia 0:/x.ncch +# 'extrcode' COMMAND +# You can extract the binary code from any file that contains it (NCSD, NCCH, CXI). +# Specify the containing file and the file to write to. +# If the code is compressed, it will be decompressed. +# C:/titleid.3ds 0:/gm9/out/titleid.dec.code + +# 'cmprcode' COMMAND +# Attempt to open a file as uncompressed binary code and compress it into the 3DS's reverse LZSS format. +# Specify the source file and the file to write to. +# 0:/gm9/out/titleid.dec.code 0:/gm9/out/titleid.code + # 'sdump' COMMAND # This command dumps a supported file to the standard output directory (0:/gm9/out) # Supported files: encTitleKeys.bin, decTitleKeys.bin, seeddb.bin