#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)) #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 } PACKED_ALIGN(4) CodeLzssFooter; u32 GetCodeLzssUncompressedSize(void* footer, u32 comp_size) { if (comp_size < sizeof(CodeLzssFooter)) return 0; CodeLzssFooter* f = (CodeLzssFooter*) footer; if ((CODE_COMP_SIZE(f) > comp_size) || (CODE_COMP_END(f) < 0)) return 0; return CODE_DEC_SIZE(f) + (comp_size - CODE_COMP_SIZE(f)); } // see: https://github.com/zoogie/DSP1/blob/master/source/main.c#L44 u32 DecompressCodeLzss(u8* code, u32* code_size, u32 max_size) { u8* data_start = code; 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)) { 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; // 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; } // 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++; } } static 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 = (void*)(a_pCompressed + uCompFooterOffset); pCompFooter->off_size_comp = uTop | (uBottom << 24); pCompFooter->addsize_dec = a_uUncompressedSize - *a_uCompressedSize; } } return bResult; }