diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..d497af2 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,52 @@ +name: runTests + +on: + push: + branches: [ main ] + pull_request: + workflow_dispatch: + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y cmake ninja-build libzstd-dev zlib1g-dev libyaml-dev + + - name: Build Confluence + run: | + git clone https://github.com/Linifadomra/Confluence.git + cmake -B Confluence/build -S Confluence \ + -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=/usr/local + cmake --build Confluence/build + sudo cmake --install Confluence/build + + - name: Configure + run: | + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DSIPHON_BUILD_TESTS=ON + + - name: Build + run: cmake --build build + + - name: Test + run: ctest --test-dir build/test --output-on-failure + + - name: Upload test results on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-output + path: test/out/ + retention-days: 1 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2dd1018..6c4c73a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ **/build*/ +**/out*/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 726838a..93af9a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,9 @@ add_library(gc_disc STATIC src/disc/gc_wia.c src/disc/gc_wbfs.c src/disc/gc_rvz.c + src/disc/aes.c + src/disc/gc_wii.c + src/disc/lzma.c ) target_include_directories(gc_disc PUBLIC include PRIVATE src/disc) @@ -82,13 +85,5 @@ else() endif() if(SIPHON_BUILD_TESTS) - enable_testing() - - add_executable(test_rarc test/test_rarc.c) - target_link_libraries(test_rarc PRIVATE gc_disc) - add_test( - NAME test_rarc - COMMAND test_rarc - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) -endif() + add_subdirectory(test) +endif() \ No newline at end of file diff --git a/src/disc/aes.c b/src/disc/aes.c new file mode 100644 index 0000000..7f5fe90 --- /dev/null +++ b/src/disc/aes.c @@ -0,0 +1,103 @@ +#include +#include "aes.h" +#define Nb 4 +#define Nk 4 +#define Nr 10 +typedef uint8_t state_t[4][4]; +static const uint8_t sbox[256]={ +0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, +0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, +0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, +0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, +0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, +0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, +0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, +0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, +0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, +0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, +0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, +0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, +0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, +0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, +0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, +0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16}; +static const uint8_t rsbox[256]={ +0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb, +0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb, +0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e, +0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25, +0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92, +0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84, +0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06, +0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b, +0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73, +0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e, +0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b, +0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4, +0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f, +0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef, +0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61, +0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d}; +static const uint8_t Rcon[11]={0x8d,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36}; +#define getSBoxValue(num) (sbox[(num)]) +#define getSBoxInvert(num) (rsbox[(num)]) +static void KeyExpansion(uint8_t*RoundKey,const uint8_t*Key){ +unsigned i,j,k;uint8_t tempa[4]; +for(i=0;iRoundKey,key);memcpy(ctx->Iv,iv,AES_BLOCKLEN);} +static void AddRoundKey(uint8_t round,state_t*state,const uint8_t*RoundKey){ +uint8_t i,j;for(i=0;i<4;++i)for(j=0;j<4;++j)(*state)[i][j]^=RoundKey[(round*Nb*4)+(i*Nb)+j]; +} +static uint8_t xtime(uint8_t x){return((x<<1)^(((x>>7)&1)*0x1b));} +#define Multiply(x,y) (((y&1)*x)^((y>>1&1)*xtime(x))^((y>>2&1)*xtime(xtime(x)))^((y>>3&1)*xtime(xtime(xtime(x))))^((y>>4&1)*xtime(xtime(xtime(xtime(x)))))) +static void InvMixColumns(state_t*state){ +int i;uint8_t a,b,c,d; +for(i=0;i<4;++i){ +a=(*state)[i][0];b=(*state)[i][1];c=(*state)[i][2];d=(*state)[i][3]; +(*state)[i][0]=Multiply(a,0x0e)^Multiply(b,0x0b)^Multiply(c,0x0d)^Multiply(d,0x09); +(*state)[i][1]=Multiply(a,0x09)^Multiply(b,0x0e)^Multiply(c,0x0b)^Multiply(d,0x0d); +(*state)[i][2]=Multiply(a,0x0d)^Multiply(b,0x09)^Multiply(c,0x0e)^Multiply(d,0x0b); +(*state)[i][3]=Multiply(a,0x0b)^Multiply(b,0x0d)^Multiply(c,0x09)^Multiply(d,0x0e); +} +} +static void InvSubBytes(state_t*state){ +uint8_t i,j;for(i=0;i<4;++i)for(j=0;j<4;++j)(*state)[j][i]=getSBoxInvert((*state)[j][i]); +} +static void InvShiftRows(state_t*state){ +uint8_t temp; +temp=(*state)[3][1];(*state)[3][1]=(*state)[2][1];(*state)[2][1]=(*state)[1][1];(*state)[1][1]=(*state)[0][1];(*state)[0][1]=temp; +temp=(*state)[0][2];(*state)[0][2]=(*state)[2][2];(*state)[2][2]=temp; +temp=(*state)[1][2];(*state)[1][2]=(*state)[3][2];(*state)[3][2]=temp; +temp=(*state)[0][3];(*state)[0][3]=(*state)[1][3];(*state)[1][3]=(*state)[2][3];(*state)[2][3]=(*state)[3][3];(*state)[3][3]=temp; +} +static void InvCipher(state_t*state,const uint8_t*RoundKey){ +uint8_t round; +AddRoundKey(Nr,state,RoundKey); +for(round=(Nr-1);;--round){ +InvShiftRows(state);InvSubBytes(state);AddRoundKey(round,state,RoundKey); +if(round==0)break; +InvMixColumns(state); +} +} +static void XorWithIv(uint8_t*buf,const uint8_t*Iv){uint8_t i;for(i=0;iRoundKey); +XorWithIv(buf,ctx->Iv); +memcpy(ctx->Iv,storeNextIv,AES_BLOCKLEN); +buf+=AES_BLOCKLEN; +} +} diff --git a/src/disc/aes.h b/src/disc/aes.h new file mode 100644 index 0000000..9d00409 --- /dev/null +++ b/src/disc/aes.h @@ -0,0 +1,10 @@ +#ifndef SIPHON_AES_H +#define SIPHON_AES_H +#include +#include +#define AES_BLOCKLEN 16 +#define AES_keyExpSize 176 +struct AES_ctx{uint8_t RoundKey[AES_keyExpSize];uint8_t Iv[AES_BLOCKLEN];}; +void AES_init_ctx_iv(struct AES_ctx*ctx,const uint8_t*key,const uint8_t*iv); +void AES_CBC_decrypt_buffer(struct AES_ctx*ctx,uint8_t*buf,size_t length); +#endif diff --git a/src/disc/gc_disc.c b/src/disc/gc_disc.c index 9aeca97..1cab562 100644 --- a/src/disc/gc_disc.c +++ b/src/disc/gc_disc.c @@ -67,6 +67,7 @@ GCDisc* gc_disc_open(const char* path) { void gc_disc_close(GCDisc* disc) { if (!disc) return; if (disc->close) disc->close(disc); + gc_wii_free(disc); gc_disc_free_parsed(disc); if (disc->file) fclose(disc->file); free(disc); diff --git a/src/disc/gc_disc_fst.c b/src/disc/gc_disc_fst.c index aeb531d..2415e6e 100644 --- a/src/disc/gc_disc_fst.c +++ b/src/disc/gc_disc_fst.c @@ -30,14 +30,15 @@ static void mkdirs(const char* path) { } int gc_disc_parse_fst(GCDisc* disc) { + if (gc_wii_wrap(disc) != 0) return -1; if (disc->read(disc, 0, disc->boot, 0x440) < 0) return -1; memcpy(disc->gameId, disc->boot, 6); disc->gameId[6] = '\0'; - disc->dolOffset = gc_be32(disc->boot + 0x420); - disc->fstOffset = gc_be32(disc->boot + 0x424); - disc->fstSize = gc_be32(disc->boot + 0x428); + disc->dolOffset = gc_be32(disc->boot + 0x420) << disc->offsetShift; + disc->fstOffset = gc_be32(disc->boot + 0x424) << disc->offsetShift; + disc->fstSize = gc_be32(disc->boot + 0x428) << disc->offsetShift; if (disc->read(disc, 0x440, disc->bi2, 0x2000) < 0) return -1; @@ -128,7 +129,7 @@ int gc_disc_parse_fst(GCDisc* disc) { *pathWrite++ = '\0'; disc->entries[i].name = pathStart; - disc->entries[i].discOffset = gc_be32(e + 4); + disc->entries[i].discOffset = gc_be32(e + 4) << disc->offsetShift; disc->entries[i].size = gc_be32(e + 8); if (e[0]) { diff --git a/src/disc/gc_disc_internal.h b/src/disc/gc_disc_internal.h index aac1446..21c0387 100644 --- a/src/disc/gc_disc_internal.h +++ b/src/disc/gc_disc_internal.h @@ -31,6 +31,9 @@ struct GCDisc { GCEntry* entries; char* pathBuf; + + uint32_t offsetShift; + void* wii; }; static inline uint16_t gc_be16(const uint8_t* p) { @@ -57,5 +60,7 @@ int gc_ciso_open(GCDisc* disc); int gc_gcz_open(GCDisc* disc); int gc_wbfs_open(GCDisc* disc); int gc_wia_open(GCDisc* disc, int isRVZ); +int gc_wii_wrap(GCDisc* disc); +void gc_wii_free(GCDisc* disc); #endif diff --git a/src/disc/gc_wia.c b/src/disc/gc_wia.c index e284db3..cad6fc0 100644 --- a/src/disc/gc_wia.c +++ b/src/disc/gc_wia.c @@ -1,6 +1,7 @@ #include "gc_disc_internal.h" #include "siphon_log.h" #include "gc_rvz.h" +#include "lzma.h" #include #include @@ -35,6 +36,7 @@ typedef struct { int isRVZ; uint32_t compType; uint32_t chunkSize; + uint8_t comprData[7]; uint8_t discHeader[0x80]; uint32_t numRawData; @@ -50,9 +52,19 @@ typedef struct { uint8_t* packedScratch; size_t packedScratchCap; + + int isWii; + uint32_t sectorsPerGroup; + uint32_t exceptLists; + uint32_t pd0Sectors, pd0Group; + uint32_t pd1Sectors, pd1Group; } WIAData; -static int wia_decompress_buf(uint32_t compType, const uint8_t* src, size_t srcLen, +static void* lzma_alloc(ISzAllocPtr p, size_t size) { (void)p; return malloc(size); } +static void lzma_free(ISzAllocPtr p, void* a) { (void)p; free(a); } + +static int wia_decompress_buf(uint32_t compType, const uint8_t* props, + const uint8_t* src, size_t srcLen, uint8_t* dst, size_t dstCap, size_t* outLen) { switch (compType) { case WIA_COMP_NONE: @@ -61,6 +73,17 @@ static int wia_decompress_buf(uint32_t compType, const uint8_t* src, size_t srcL *outLen = srcLen; return 0; + case WIA_COMP_LZMA: { + ISzAlloc alloc = { lzma_alloc, lzma_free }; + SizeT destLen = dstCap; + SizeT srcN = srcLen; + ELzmaStatus status; + if (LzmaDecode(dst, &destLen, src, &srcN, props, LZMA_PROPS_SIZE, + LZMA_FINISH_ANY, &status, &alloc) != SZ_OK) return -1; + *outLen = destLen; + return 0; + } + #if HAS_ZSTD case WIA_COMP_ZSTD: { size_t ret = ZSTD_decompress(dst, dstCap, src, srcLen); @@ -74,7 +97,7 @@ static int wia_decompress_buf(uint32_t compType, const uint8_t* src, size_t srcL } } -static int wia_read_and_decompress(FILE* f, uint32_t compType, +static int wia_read_and_decompress(FILE* f, uint32_t compType, const uint8_t* props, uint64_t fileOff, uint32_t compSize, uint8_t* dst, size_t dstCap, size_t* outLen) { if (compType == WIA_COMP_NONE) { @@ -90,7 +113,7 @@ static int wia_read_and_decompress(FILE* f, uint32_t compType, if (fseek(f, (long)fileOff, SEEK_SET) != 0) { free(compBuf); return -1; } if (fread(compBuf, 1, compSize, f) != compSize) { free(compBuf); return -1; } - int ret = wia_decompress_buf(compType, compBuf, compSize, dst, dstCap, outLen); + int ret = wia_decompress_buf(compType, props, compBuf, compSize, dst, dstCap, outLen); free(compBuf); return ret; } @@ -119,7 +142,7 @@ static int wia_decompress_group(GCDisc* disc, WIAData* wd, uint32_t groupIdx, uint32_t effCompType = isCompressed ? wd->compType : WIA_COMP_NONE; size_t outLen = 0; - if (wia_read_and_decompress(disc->file, effCompType, + if (wia_read_and_decompress(disc->file, effCompType, wd->comprData, grp->fileOffset, compSize, wd->decompBuf, wd->decompBufCap, &outLen) < 0) { return -1; @@ -222,6 +245,62 @@ static int wia_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { return 0; } +static int wia_part_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { + WIAData* wd = (WIAData*)disc->formatData; + uint8_t* out = (uint8_t*)buf; + const uint32_t SD = 0x7C00; + + while (size > 0) { + uint32_t sec = offset / SD; + uint32_t intra = offset % SD; + + uint32_t base, grp0; + if (sec < wd->pd0Sectors) { + base = 0; grp0 = wd->pd0Group; + } else if (sec < wd->pd0Sectors + wd->pd1Sectors) { + base = wd->pd0Sectors; grp0 = wd->pd1Group; + } else { + memset(out, 0, size); + return 0; + } + + uint32_t secInPd = sec - base; + uint32_t groupRel = secInPd / wd->sectorsPerGroup; + uint32_t group = grp0 + groupRel; + uint64_t dataOff = (uint64_t)(base + groupRel * wd->sectorsPerGroup) * SD; + + if (group >= wd->numGroups) { memset(out, 0, size); return 0; } + if (wia_decompress_group(disc, wd, group, dataOff) < 0) return -1; + + uint32_t eo = 0; + for (uint32_t i = 0; i < wd->exceptLists; i++) { + if (eo + 2 > wd->decompBufSize) break; + uint32_t ne = ((uint32_t)wd->decompBuf[eo] << 8) | wd->decompBuf[eo + 1]; + eo += 2 + ne * 22; + } + + uint32_t secInGroup = secInPd % wd->sectorsPerGroup; + uint32_t srcPos = eo + secInGroup * SD + intra; + + size_t chunk = SD - intra; + if (chunk > size) chunk = size; + + if (srcPos + chunk > wd->decompBufSize) { + size_t valid = srcPos < wd->decompBufSize ? wd->decompBufSize - srcPos : 0; + if (valid > chunk) valid = chunk; + if (valid) memcpy(out, wd->decompBuf + srcPos, valid); + if (chunk > valid) memset(out + valid, 0, chunk - valid); + } else { + memcpy(out, wd->decompBuf + srcPos, chunk); + } + + out += chunk; + offset += (uint32_t)chunk; + size -= chunk; + } + return 0; +} + static void wia_close(GCDisc* disc) { WIAData* wd = (WIAData*)disc->formatData; if (wd) { @@ -258,24 +337,24 @@ int gc_wia_open(GCDisc* disc, int isRVZ) { free(wd); return -1; } + memcpy(wd->comprData, h2 + 0xD5, 7); memcpy(wd->discHeader, h2 + 0x10, 0x80); if (wd->compType != WIA_COMP_NONE) { #if !HAS_ZSTD if (wd->compType == WIA_COMP_ZSTD) { free(wd); return -1; } #endif - if (wd->compType == WIA_COMP_BZIP2 || wd->compType == WIA_COMP_LZMA || + if (wd->compType == WIA_COMP_BZIP2 || wd->compType == WIA_COMP_LZMA2 || wd->compType == WIA_COMP_PURGE) { const char* name = "unknown"; switch (wd->compType) { case WIA_COMP_BZIP2: name = "bzip2"; break; - case WIA_COMP_LZMA: name = "lzma"; break; case WIA_COMP_LZMA2: name = "lzma2"; break; case WIA_COMP_PURGE: name = "purge"; break; } fprintf(stderr, "siphon: %s compression is not supported in this build " - "(only NONE and ZSTD are implemented). Re-encode the image to " + "(only NONE, ZSTD, and LZMA are implemented). Re-encode the image to " "zstd with Dolphin if you need to extract it.", name); free(wd); @@ -295,7 +374,7 @@ int gc_wia_open(GCDisc* disc, int isRVZ) { if (!rawBuf) { free(wd); return -1; } size_t rawOutLen = 0; - if (wia_read_and_decompress(disc->file, wd->compType, rawOff, rawSize, + if (wia_read_and_decompress(disc->file, wd->compType, wd->comprData, rawOff, rawSize, rawBuf, rawBufSize, &rawOutLen) < 0) { free(rawBuf); free(wd); return -1; } @@ -322,7 +401,7 @@ int gc_wia_open(GCDisc* disc, int isRVZ) { if (!grpBuf) { wia_close(disc); return -1; } size_t grpOutLen = 0; - if (wia_read_and_decompress(disc->file, wd->compType, grpOff, grpSize, + if (wia_read_and_decompress(disc->file, wd->compType, wd->comprData, grpOff, grpSize, grpBuf, grpBufSize, &grpOutLen) < 0) { free(grpBuf); wia_close(disc); return -1; } @@ -344,9 +423,31 @@ int gc_wia_open(GCDisc* disc, int isRVZ) { wd->decompBuf = (uint8_t*)malloc(wd->decompBufCap); if (!wd->decompBuf) { wia_close(disc); return -1; } + wd->isWii = (gc_be32(h2 + 0x00) == 2); + disc->formatData = wd; - disc->read = wia_read; disc->close = wia_close; + disc->read = wia_read; + + if (wd->isWii) { + uint32_t nPart = gc_be32(h2 + 0x90); + uint64_t partOff = gc_be64(h2 + 0x98); + if (nPart < 1) { wia_close(disc); return -1; } + uint8_t pe[0x30]; + if (fseek(disc->file, (long)partOff, SEEK_SET) != 0 || + fread(pe, 1, sizeof(pe), disc->file) != sizeof(pe)) { + wia_close(disc); return -1; + } + wd->pd0Sectors = gc_be32(pe + 0x14); + wd->pd0Group = gc_be32(pe + 0x18); + wd->pd1Sectors = gc_be32(pe + 0x24); + wd->pd1Group = gc_be32(pe + 0x28); + wd->sectorsPerGroup = wd->chunkSize / 0x8000; + wd->exceptLists = wd->chunkSize / 0x200000; + if (wd->exceptLists == 0) wd->exceptLists = 1; + disc->read = wia_part_read; + disc->offsetShift = 2; + } return gc_disc_parse_fst(disc); } diff --git a/src/disc/gc_wii.c b/src/disc/gc_wii.c new file mode 100644 index 0000000..725c238 --- /dev/null +++ b/src/disc/gc_wii.c @@ -0,0 +1,136 @@ +#include "gc_disc_internal.h" +#include "siphon_log.h" +#include "aes.h" +#include +#include + +#define WII_CLUSTER 0x8000u +#define WII_CLUSTER_HASH 0x0400u +#define WII_CLUSTER_DATA 0x7C00u +#define WII_MAX_PARTITIONS 64 + +typedef struct { + gc_read_fn raw; + uint32_t dataStart; + uint8_t titleKey[16]; + uint8_t clusterEnc[WII_CLUSTER]; + uint8_t clusterPlain[WII_CLUSTER_DATA]; + uint32_t cachedCluster; +} WiiCtx; + +static const uint8_t WII_COMMON_KEY[16] = { + 0xeb,0xe4,0x2a,0x22,0x5e,0x85,0x93,0xe4, + 0x48,0xd9,0xc5,0x45,0x73,0x81,0xaa,0xf7 +}; + +static int hexnib(int c) { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; +} + +static void wii_common_key(uint8_t out[16]) { + const char* hex = getenv("SIPHON_WII_COMMON_KEY"); + if (hex && strlen(hex) >= 32) { + for (int i = 0; i < 16; i++) { + int hi = hexnib(hex[i*2]), lo = hexnib(hex[i*2+1]); + if (hi < 0 || lo < 0) { memcpy(out, WII_COMMON_KEY, 16); return; } + out[i] = (uint8_t)((hi << 4) | lo); + } + return; + } + memcpy(out, WII_COMMON_KEY, 16); +} + +static void wii_decrypt_title_key(const uint8_t* ticket, uint8_t out[16]) { + uint8_t commonKey[16]; + uint8_t iv[16] = {0}; + wii_common_key(commonKey); + memcpy(iv, ticket + 0x1DC, 8); + memcpy(out, ticket + 0x1BF, 16); + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, commonKey, iv); + AES_CBC_decrypt_buffer(&ctx, out, 16); +} + +static int wii_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { + WiiCtx* w = (WiiCtx*)disc->wii; + uint8_t* out = (uint8_t*)buf; + + while (size > 0) { + uint32_t cluster = offset / WII_CLUSTER_DATA; + uint32_t intra = offset % WII_CLUSTER_DATA; + + if (w->cachedCluster != cluster) { + uint32_t phys = w->dataStart + cluster * WII_CLUSTER; + if (w->raw(disc, phys, w->clusterEnc, WII_CLUSTER) < 0) return -1; + uint8_t iv[16]; + memcpy(iv, w->clusterEnc + 0x3D0, 16); + memcpy(w->clusterPlain, w->clusterEnc + WII_CLUSTER_HASH, WII_CLUSTER_DATA); + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, w->titleKey, iv); + AES_CBC_decrypt_buffer(&ctx, w->clusterPlain, WII_CLUSTER_DATA); + w->cachedCluster = cluster; + } + + size_t chunk = WII_CLUSTER_DATA - intra; + if (chunk > size) chunk = size; + memcpy(out, w->clusterPlain + intra, chunk); + out += chunk; + offset += (uint32_t)chunk; + size -= chunk; + } + return 0; +} + +int gc_wii_wrap(GCDisc* disc) { + if (disc->format == GC_FORMAT_WIA || disc->format == GC_FORMAT_RVZ) return 0; + + uint8_t magic[4]; + if (disc->read(disc, 0x18, magic, 4) < 0) return -1; + if (!(magic[0]==0x5D && magic[1]==0x1C && magic[2]==0x9E && magic[3]==0xA3)) + return 0; + + uint8_t grp[32]; + if (disc->read(disc, 0x40000, grp, sizeof(grp)) < 0) return -1; + + uint32_t partOff = 0; + int found = 0; + for (int g = 0; g < 4 && !found; g++) { + uint32_t count = gc_be32(grp + g*8 + 0); + uint32_t infoOff = gc_be32(grp + g*8 + 4) << 2; + if (count > WII_MAX_PARTITIONS) continue; + for (uint32_t p = 0; p < count; p++) { + uint8_t pe[8]; + if (disc->read(disc, infoOff + p*8, pe, 8) < 0) return -1; + uint32_t off = gc_be32(pe + 0) << 2; + uint32_t type = gc_be32(pe + 4); + if (type == 0) { partOff = off; found = 1; break; } + } + } + if (!found) { siphon_log("Wii: no data partition"); return -1; } + + uint8_t ticket[0x2A4]; + if (disc->read(disc, partOff, ticket, sizeof(ticket)) < 0) return -1; + + WiiCtx* w = (WiiCtx*)calloc(1, sizeof(WiiCtx)); + if (!w) return -1; + wii_decrypt_title_key(ticket, w->titleKey); + + uint8_t phdr[0x1C]; + if (disc->read(disc, partOff + 0x2A4, phdr, sizeof(phdr)) < 0) { free(w); return -1; } + uint32_t dataOff = gc_be32(phdr + 0x14) << 2; + w->dataStart = partOff + dataOff; + w->cachedCluster = UINT32_MAX; + w->raw = disc->read; + + disc->wii = w; + disc->read = wii_read; + disc->offsetShift = 2; + return 0; +} + +void gc_wii_free(GCDisc* disc) { + if (disc && disc->wii) { free(disc->wii); disc->wii = NULL; } +} diff --git a/src/disc/lzma.c b/src/disc/lzma.c new file mode 100644 index 0000000..6915aa1 --- /dev/null +++ b/src/disc/lzma.c @@ -0,0 +1,1097 @@ +#include "lzma.h" + +#include + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_INIT_SIZE 5 + +#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); +#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); +#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \ + { UPDATE_0(p); i = (i + i); A0; } else \ + { UPDATE_1(p); i = (i + i) + 1; A1; } + +#define TREE_GET_BIT(probs, i) { GET_BIT2(probs + i, i, ;, ;); } + +#define REV_BIT(p, i, A0, A1) IF_BIT_0(p + i) \ + { UPDATE_0(p + i); A0; } else \ + { UPDATE_1(p + i); A1; } +#define REV_BIT_VAR( p, i, m) REV_BIT(p, i, i += m; m += m, m += m; i += m; ) +#define REV_BIT_CONST(p, i, m) REV_BIT(p, i, i += m; , i += m * 2; ) +#define REV_BIT_LAST( p, i, m) REV_BIT(p, i, i -= m , ; ) + +#define TREE_DECODE(probs, limit, i) \ + { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; } + +#ifdef _LZMA_SIZE_OPT +#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i) +#else +#define TREE_6_DECODE(probs, i) \ + { i = 1; \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + i -= 0x40; } +#endif + +#define NORMAL_LITER_DEC TREE_GET_BIT(prob, symbol) +#define MATCHED_LITER_DEC \ + matchByte += matchByte; \ + bit = offs; \ + offs &= matchByte; \ + probLit = prob + (offs + bit + symbol); \ + GET_BIT2(probLit, symbol, offs ^= bit; , ;) + +#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0_CHECK range = bound; +#define UPDATE_1_CHECK range -= bound; code -= bound; +#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \ + { UPDATE_0_CHECK; i = (i + i); A0; } else \ + { UPDATE_1_CHECK; i = (i + i) + 1; A1; } +#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;) +#define TREE_DECODE_CHECK(probs, limit, i) \ + { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; } + +#define REV_BIT_CHECK(p, i, m) IF_BIT_0_CHECK(p + i) \ + { UPDATE_0_CHECK; i += m; m += m; } else \ + { UPDATE_1_CHECK; m += m; i += m; } + +#define kNumPosBitsMax 4 +#define kNumPosStatesMax (1 << kNumPosBitsMax) + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define LenLow 0 +#define LenHigh (LenLow + 2 * (kNumPosStatesMax << kLenNumLowBits)) +#define kNumLenProbs (LenHigh + kLenNumHighSymbols) + +#define LenChoice LenLow +#define LenChoice2 (LenLow + (1 << kLenNumLowBits)) + +#define kNumStates 12 +#define kNumStates2 16 +#define kNumLitStates 7 + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +#define kNumPosSlotBits 6 +#define kNumLenToPosStates 4 + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) + +#define kMatchMinLen 2 +#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols * 2 + kLenNumHighSymbols) + +#define kStartOffset 1664 +#define GET_PROBS p->probs_1664 + +#define SpecPos (-kStartOffset) +#define IsRep0Long (SpecPos + kNumFullDistances) +#define RepLenCoder (IsRep0Long + (kNumStates2 << kNumPosBitsMax)) +#define LenCoder (RepLenCoder + kNumLenProbs) +#define IsMatch (LenCoder + kNumLenProbs) +#define Align (IsMatch + (kNumStates2 << kNumPosBitsMax)) +#define IsRep (Align + kAlignTableSize) +#define IsRepG0 (IsRep + kNumStates) +#define IsRepG1 (IsRepG0 + kNumStates) +#define IsRepG2 (IsRepG1 + kNumStates) +#define PosSlot (IsRepG2 + kNumStates) +#define Literal (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) +#define NUM_BASE_PROBS (Literal + kStartOffset) + +#if Align != 0 && kStartOffset != 0 + #error Stop_Compiling_Bad_LZMA_kAlign +#endif + +#if NUM_BASE_PROBS != 1984 + #error Stop_Compiling_Bad_LZMA_PROBS +#endif + +#define LZMA_LIT_SIZE 0x300 + +#define LzmaProps_GetNumProbs(p) (NUM_BASE_PROBS + ((UInt32)LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) + +#define CALC_POS_STATE(processedPos, pbMask) (((processedPos) & (pbMask)) << 4) +#define COMBINED_PS_STATE (posState + state) +#define GET_LEN_STATE (posState) + +#define LZMA_DIC_MIN (1 << 12) + +#define LZMA_DECODE_REAL LzmaDec_DecodeReal_3 + +#ifdef _LZMA_DEC_OPT + +int MY_FAST_CALL LZMA_DECODE_REAL(CLzmaDec *p, SizeT limit, const Byte *bufLimit); + +#else + +static +int MY_FAST_CALL LZMA_DECODE_REAL(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + CLzmaProb *probs = GET_PROBS; + unsigned state = (unsigned)p->state; + UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3]; + unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1; + unsigned lc = p->prop.lc; + unsigned lpMask = ((unsigned)0x100 << p->prop.lp) - ((unsigned)0x100 >> lc); + + Byte *dic = p->dic; + SizeT dicBufSize = p->dicBufSize; + SizeT dicPos = p->dicPos; + + UInt32 processedPos = p->processedPos; + UInt32 checkDicSize = p->checkDicSize; + unsigned len = 0; + + const Byte *buf = p->buf; + UInt32 range = p->range; + UInt32 code = p->code; + + do + { + CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = CALC_POS_STATE(processedPos, pbMask); + + prob = probs + IsMatch + COMBINED_PS_STATE; + IF_BIT_0(prob) + { + unsigned symbol; + UPDATE_0(prob); + prob = probs + Literal; + if (processedPos != 0 || checkDicSize != 0) + prob += (UInt32)3 * ((((processedPos << 8) + dic[(dicPos == 0 ? dicBufSize : dicPos) - 1]) & lpMask) << lc); + processedPos++; + + if (state < kNumLitStates) + { + state -= (state < 4) ? state : 3; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do { NORMAL_LITER_DEC } while (symbol < 0x100); + #else + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + #endif + } + else + { + unsigned matchByte = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + unsigned offs = 0x100; + state -= (state < 10) ? 3 : 6; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + } + while (symbol < 0x100); + #else + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + } + #endif + } + + dic[dicPos++] = (Byte)symbol; + continue; + } + + { + UPDATE_1(prob); + prob = probs + IsRep + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + state += kNumStates; + prob = probs + LenCoder; + } + else + { + UPDATE_1(prob); + + prob = probs + IsRepG0 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + prob = probs + IsRep0Long + COMBINED_PS_STATE; + IF_BIT_0(prob) + { + UPDATE_0(prob); + dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + dicPos++; + processedPos++; + state = state < kNumLitStates ? 9 : 11; + continue; + } + UPDATE_1(prob); + } + else + { + UInt32 distance; + UPDATE_1(prob); + prob = probs + IsRepG1 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep1; + } + else + { + UPDATE_1(prob); + prob = probs + IsRepG2 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep2; + } + else + { + UPDATE_1(prob); + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + state = state < kNumLitStates ? 8 : 11; + prob = probs + RepLenCoder; + } + + #ifdef _LZMA_SIZE_OPT + { + unsigned lim, offset; + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + GET_LEN_STATE; + offset = 0; + lim = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + offset = kLenNumLowSymbols; + lim = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + offset = kLenNumLowSymbols * 2; + lim = (1 << kLenNumHighBits); + } + } + TREE_DECODE(probLen, lim, len); + len += offset; + } + #else + { + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + GET_LEN_STATE; + len = 1; + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + len -= 8; + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + len = 1; + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + TREE_DECODE(probLen, (1 << kLenNumHighBits), len); + len += kLenNumLowSymbols * 2; + } + } + } + #endif + + if (state >= kNumStates) + { + UInt32 distance; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); + TREE_6_DECODE(prob, distance); + if (distance >= kStartPosModelIndex) + { + unsigned posSlot = (unsigned)distance; + unsigned numDirectBits = (unsigned)(((distance >> 1) - 1)); + distance = (2 | (distance & 1)); + if (posSlot < kEndPosModelIndex) + { + distance <<= numDirectBits; + prob = probs + SpecPos; + { + UInt32 m = 1; + distance++; + do + { + REV_BIT_VAR(prob, distance, m); + } + while (--numDirectBits); + distance -= m; + } + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE + range >>= 1; + + { + UInt32 t; + code -= range; + t = (0 - ((UInt32)code >> 31)); + distance = (distance << 1) + (t + 1); + code += range & t; + } + + } + while (--numDirectBits); + prob = probs + Align; + distance <<= kNumAlignBits; + { + unsigned i = 1; + REV_BIT_CONST(prob, i, 1); + REV_BIT_CONST(prob, i, 2); + REV_BIT_CONST(prob, i, 4); + REV_BIT_LAST (prob, i, 8); + distance |= i; + } + if (distance == (UInt32)0xFFFFFFFF) + { + len = kMatchSpecLenStart; + state -= kNumStates; + break; + } + } + } + + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance + 1; + state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; + if (distance >= (checkDicSize == 0 ? processedPos: checkDicSize)) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + } + + len += kMatchMinLen; + + { + SizeT rem; + unsigned curLen; + SizeT pos; + + if ((rem = limit - dicPos) == 0) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + + curLen = ((rem < len) ? (unsigned)rem : len); + pos = dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0); + + processedPos += curLen; + + len -= curLen; + if (curLen <= dicBufSize - pos) + { + Byte *dest = dic + dicPos; + ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos; + const Byte *lim = dest + curLen; + dicPos += curLen; + do + *(dest) = (Byte)*(dest + src); + while (++dest != lim); + } + else + { + do + { + dic[dicPos++] = dic[pos]; + if (++pos == dicBufSize) + pos = 0; + } + while (--curLen != 0); + } + } + } + } + while (dicPos < limit && buf < bufLimit); + + NORMALIZE; + + p->buf = buf; + p->range = range; + p->code = code; + p->remainLen = len; + p->dicPos = dicPos; + p->processedPos = processedPos; + p->reps[0] = rep0; + p->reps[1] = rep1; + p->reps[2] = rep2; + p->reps[3] = rep3; + p->state = state; + + return SZ_OK; +} +#endif + +static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit) +{ + if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart) + { + Byte *dic = p->dic; + SizeT dicPos = p->dicPos; + SizeT dicBufSize = p->dicBufSize; + unsigned len = (unsigned)p->remainLen; + SizeT rep0 = p->reps[0]; + SizeT rem = limit - dicPos; + if (rem < len) + len = (unsigned)(rem); + + if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len) + p->checkDicSize = p->prop.dicSize; + + p->processedPos += len; + p->remainLen -= len; + while (len != 0) + { + len--; + dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + dicPos++; + } + p->dicPos = dicPos; + } +} + +#define kRange0 0xFFFFFFFF +#define kBound0 ((kRange0 >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1)) +#define kBadRepCode (kBound0 + (((kRange0 - kBound0) >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1))) +#if kBadRepCode != (0xC0000000 - 0x400) + #error Stop_Compiling_Bad_LZMA_Check +#endif + +static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + do + { + SizeT limit2 = limit; + if (p->checkDicSize == 0) + { + UInt32 rem = p->prop.dicSize - p->processedPos; + if (limit - p->dicPos > rem) + limit2 = p->dicPos + rem; + + if (p->processedPos == 0) + if (p->code >= kBadRepCode) + return SZ_ERROR_DATA; + } + + RINOK(LZMA_DECODE_REAL(p, limit2, bufLimit)); + + if (p->checkDicSize == 0 && p->processedPos >= p->prop.dicSize) + p->checkDicSize = p->prop.dicSize; + + LzmaDec_WriteRem(p, limit); + } + while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart); + + return 0; +} + +typedef enum +{ + DUMMY_ERROR, + DUMMY_LIT, + DUMMY_MATCH, + DUMMY_REP +} ELzmaDummy; + +static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize) +{ + UInt32 range = p->range; + UInt32 code = p->code; + const Byte *bufLimit = buf + inSize; + const CLzmaProb *probs = GET_PROBS; + unsigned state = (unsigned)p->state; + ELzmaDummy res; + + { + const CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = CALC_POS_STATE(p->processedPos, (1 << p->prop.pb) - 1); + + prob = probs + IsMatch + COMBINED_PS_STATE; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK + + prob = probs + Literal; + if (p->checkDicSize != 0 || p->processedPos != 0) + prob += ((UInt32)LZMA_LIT_SIZE * + ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) + + (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc)))); + + if (state < kNumLitStates) + { + unsigned symbol = 1; + do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100); + } + else + { + unsigned matchByte = p->dic[p->dicPos - p->reps[0] + + (p->dicPos < p->reps[0] ? p->dicBufSize : 0)]; + unsigned offs = 0x100; + unsigned symbol = 1; + do + { + unsigned bit; + const CLzmaProb *probLit; + matchByte += matchByte; + bit = offs; + offs &= matchByte; + probLit = prob + (offs + bit + symbol); + GET_BIT2_CHECK(probLit, symbol, offs ^= bit; , ; ) + } + while (symbol < 0x100); + } + res = DUMMY_LIT; + } + else + { + unsigned len; + UPDATE_1_CHECK; + + prob = probs + IsRep + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + state = 0; + prob = probs + LenCoder; + res = DUMMY_MATCH; + } + else + { + UPDATE_1_CHECK; + res = DUMMY_REP; + prob = probs + IsRepG0 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + prob = probs + IsRep0Long + COMBINED_PS_STATE; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + NORMALIZE_CHECK; + return DUMMY_REP; + } + else + { + UPDATE_1_CHECK; + } + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG1 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG2 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + } + } + } + state = kNumStates; + prob = probs + RepLenCoder; + } + { + unsigned limit, offset; + const CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenLow + GET_LEN_STATE; + offset = 0; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenChoice2; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + offset = kLenNumLowSymbols; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenHigh; + offset = kLenNumLowSymbols * 2; + limit = 1 << kLenNumHighBits; + } + } + TREE_DECODE_CHECK(probLen, limit, len); + len += offset; + } + + if (state < 4) + { + unsigned posSlot; + prob = probs + PosSlot + + ((len < kNumLenToPosStates - 1 ? len : kNumLenToPosStates - 1) << + kNumPosSlotBits); + TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot); + if (posSlot >= kStartPosModelIndex) + { + unsigned numDirectBits = ((posSlot >> 1) - 1); + + if (posSlot < kEndPosModelIndex) + { + prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits); + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE_CHECK + range >>= 1; + code -= range & (((code - range) >> 31) - 1); + + } + while (--numDirectBits); + prob = probs + Align; + numDirectBits = kNumAlignBits; + } + { + unsigned i = 1; + unsigned m = 1; + do + { + REV_BIT_CHECK(prob, i, m); + } + while (--numDirectBits); + } + } + } + } + } + NORMALIZE_CHECK; + return res; +} + +void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState) +{ + p->remainLen = kMatchSpecLenStart + 1; + p->tempBufSize = 0; + + if (initDic) + { + p->processedPos = 0; + p->checkDicSize = 0; + p->remainLen = kMatchSpecLenStart + 2; + } + if (initState) + p->remainLen = kMatchSpecLenStart + 2; +} + +void LzmaDec_Init(CLzmaDec *p) +{ + p->dicPos = 0; + LzmaDec_InitDicAndState(p, True, True); +} + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, + ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT inSize = *srcLen; + (*srcLen) = 0; + + *status = LZMA_STATUS_NOT_SPECIFIED; + + if (p->remainLen > kMatchSpecLenStart) + { + for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) + p->tempBuf[p->tempBufSize++] = *src++; + if (p->tempBufSize != 0 && p->tempBuf[0] != 0) + return SZ_ERROR_DATA; + if (p->tempBufSize < RC_INIT_SIZE) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + p->code = + ((UInt32)p->tempBuf[1] << 24) + | ((UInt32)p->tempBuf[2] << 16) + | ((UInt32)p->tempBuf[3] << 8) + | ((UInt32)p->tempBuf[4]); + p->range = 0xFFFFFFFF; + p->tempBufSize = 0; + + if (p->remainLen > kMatchSpecLenStart + 1) + { + SizeT numProbs = LzmaProps_GetNumProbs(&p->prop); + SizeT i; + CLzmaProb *probs = p->probs; + for (i = 0; i < numProbs; i++) + probs[i] = kBitModelTotal >> 1; + p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; + p->state = 0; + } + + p->remainLen = 0; + } + + LzmaDec_WriteRem(p, dicLimit); + + while (p->remainLen != kMatchSpecLenStart) + { + int checkEndMarkNow = 0; + + if (p->dicPos >= dicLimit) + { + if (p->remainLen == 0 && p->code == 0) + { + *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK; + return SZ_OK; + } + if (finishMode == LZMA_FINISH_ANY) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_OK; + } + if (p->remainLen != 0) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + checkEndMarkNow = 1; + } + + if (p->tempBufSize == 0) + { + SizeT processed; + const Byte *bufLimit; + if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, src, inSize); + if (dummyRes == DUMMY_ERROR) + { + memcpy(p->tempBuf, src, inSize); + p->tempBufSize = (unsigned)inSize; + (*srcLen) += inSize; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + bufLimit = src; + } + else + bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX; + p->buf = src; + if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0) + return SZ_ERROR_DATA; + processed = (SizeT)(p->buf - src); + (*srcLen) += processed; + src += processed; + inSize -= processed; + } + else + { + unsigned rem = p->tempBufSize, lookAhead = 0; + while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize) + p->tempBuf[rem++] = src[lookAhead++]; + p->tempBufSize = rem; + if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem); + if (dummyRes == DUMMY_ERROR) + { + (*srcLen) += lookAhead; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + } + p->buf = p->tempBuf; + if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0) + return SZ_ERROR_DATA; + + { + unsigned kkk = (unsigned)(p->buf - p->tempBuf); + if (rem < kkk) + return SZ_ERROR_FAIL; + rem -= kkk; + if (lookAhead < rem) + return SZ_ERROR_FAIL; + lookAhead -= rem; + } + (*srcLen) += lookAhead; + src += lookAhead; + inSize -= lookAhead; + p->tempBufSize = 0; + } + } + + if (p->code != 0) + return SZ_ERROR_DATA; + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return SZ_OK; +} + +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT outSize = *destLen; + SizeT inSize = *srcLen; + *srcLen = *destLen = 0; + for (;;) + { + SizeT inSizeCur = inSize, outSizeCur, dicPos; + ELzmaFinishMode curFinishMode; + SRes res; + if (p->dicPos == p->dicBufSize) + p->dicPos = 0; + dicPos = p->dicPos; + if (outSize > p->dicBufSize - dicPos) + { + outSizeCur = p->dicBufSize; + curFinishMode = LZMA_FINISH_ANY; + } + else + { + outSizeCur = dicPos + outSize; + curFinishMode = finishMode; + } + + res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status); + src += inSizeCur; + inSize -= inSizeCur; + *srcLen += inSizeCur; + outSizeCur = p->dicPos - dicPos; + memcpy(dest, p->dic + dicPos, outSizeCur); + dest += outSizeCur; + outSize -= outSizeCur; + *destLen += outSizeCur; + if (res != 0) + return res; + if (outSizeCur == 0 || outSize == 0) + return SZ_OK; + } +} + +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc) +{ + ISzAlloc_Free(alloc, p->probs); + p->probs = NULL; +} + +static void LzmaDec_FreeDict(CLzmaDec *p, ISzAllocPtr alloc) +{ + ISzAlloc_Free(alloc, p->dic); + p->dic = NULL; +} + +void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr alloc) +{ + LzmaDec_FreeProbs(p, alloc); + LzmaDec_FreeDict(p, alloc); +} + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size) +{ + UInt32 dicSize; + Byte d; + + if (size < LZMA_PROPS_SIZE) + return SZ_ERROR_UNSUPPORTED; + else + dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24); + + if (dicSize < LZMA_DIC_MIN) + dicSize = LZMA_DIC_MIN; + p->dicSize = dicSize; + + d = data[0]; + if (d >= (9 * 5 * 5)) + return SZ_ERROR_UNSUPPORTED; + + p->lc = (Byte)(d % 9); + d /= 9; + p->pb = (Byte)(d / 5); + p->lp = (Byte)(d % 5); + + return SZ_OK; +} + +static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAllocPtr alloc) +{ + UInt32 numProbs = LzmaProps_GetNumProbs(propNew); + if (!p->probs || numProbs != p->numProbs) + { + LzmaDec_FreeProbs(p, alloc); + p->probs = (CLzmaProb *)ISzAlloc_Alloc(alloc, numProbs * sizeof(CLzmaProb)); + if (!p->probs) + return SZ_ERROR_MEM; + p->probs_1664 = p->probs + 1664; + p->numProbs = numProbs; + } + return SZ_OK; +} + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc) +{ + CLzmaProps propNew; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + p->prop = propNew; + return SZ_OK; +} + +SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc) +{ + CLzmaProps propNew; + SizeT dicBufSize; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + + { + UInt32 dictSize = propNew.dicSize; + SizeT mask = ((UInt32)1 << 12) - 1; + if (dictSize >= ((UInt32)1 << 30)) mask = ((UInt32)1 << 22) - 1; + else if (dictSize >= ((UInt32)1 << 22)) mask = ((UInt32)1 << 20) - 1;; + dicBufSize = ((SizeT)dictSize + mask) & ~mask; + if (dicBufSize < dictSize) + dicBufSize = dictSize; + } + + if (!p->dic || dicBufSize != p->dicBufSize) + { + LzmaDec_FreeDict(p, alloc); + p->dic = (Byte *)ISzAlloc_Alloc(alloc, dicBufSize); + if (!p->dic) + { + LzmaDec_FreeProbs(p, alloc); + return SZ_ERROR_MEM; + } + } + p->dicBufSize = dicBufSize; + p->prop = propNew; + return SZ_OK; +} + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAllocPtr alloc) +{ + CLzmaDec p; + SRes res; + SizeT outSize = *destLen, inSize = *srcLen; + *destLen = *srcLen = 0; + *status = LZMA_STATUS_NOT_SPECIFIED; + if (inSize < RC_INIT_SIZE) + return SZ_ERROR_INPUT_EOF; + LzmaDec_Construct(&p); + RINOK(LzmaDec_AllocateProbs(&p, propData, propSize, alloc)); + p.dic = dest; + p.dicBufSize = outSize; + LzmaDec_Init(&p); + *srcLen = inSize; + res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status); + *destLen = p.dicPos; + if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT) + res = SZ_ERROR_INPUT_EOF; + LzmaDec_FreeProbs(&p, alloc); + return res; +} diff --git a/src/disc/lzma.h b/src/disc/lzma.h new file mode 100644 index 0000000..d09946a --- /dev/null +++ b/src/disc/lzma.h @@ -0,0 +1,125 @@ +#ifndef SIPHON_LZMA_H +#define SIPHON_LZMA_H +#include +#ifdef __cplusplus +#define EXTERN_C_BEGIN extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_BEGIN +#define EXTERN_C_END +#endif +typedef unsigned char Byte; +typedef unsigned short UInt16; +typedef unsigned int UInt32; +typedef unsigned long long UInt64; +typedef size_t SizeT; +typedef int Bool; +#define True 1 +#define False 0 +typedef int SRes; +#define SZ_OK 0 +#define SZ_ERROR_DATA 1 +#define SZ_ERROR_MEM 2 +#define SZ_ERROR_CRC 3 +#define SZ_ERROR_UNSUPPORTED 4 +#define SZ_ERROR_PARAM 5 +#define SZ_ERROR_INPUT_EOF 6 +#define SZ_ERROR_OUTPUT_EOF 7 +#define SZ_ERROR_READ 8 +#define SZ_ERROR_WRITE 9 +#define SZ_ERROR_PROGRESS 10 +#define SZ_ERROR_FAIL 11 +#define MY_FAST_CALL +#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } +typedef struct ISzAlloc ISzAlloc; +typedef const ISzAlloc * ISzAllocPtr; +struct ISzAlloc { void *(*Alloc)(ISzAllocPtr p, size_t size); void (*Free)(ISzAllocPtr p, void *address); }; +#define ISzAlloc_Alloc(p, size) (p)->Alloc(p, size) +#define ISzAlloc_Free(p, a) (p)->Free(p, a) + +EXTERN_C_BEGIN + +typedef +#ifdef _LZMA_PROB32 + UInt32 +#else + UInt16 +#endif + CLzmaProb; + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaProps +{ + Byte lc; + Byte lp; + Byte pb; + Byte _pad_; + UInt32 dicSize; +} CLzmaProps; + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); + +#define LZMA_REQUIRED_INPUT_MAX 20 + +typedef struct +{ + + CLzmaProps prop; + CLzmaProb *probs; + CLzmaProb *probs_1664; + Byte *dic; + SizeT dicBufSize; + SizeT dicPos; + const Byte *buf; + UInt32 range; + UInt32 code; + UInt32 processedPos; + UInt32 checkDicSize; + UInt32 reps[4]; + UInt32 state; + UInt32 remainLen; + + UInt32 numProbs; + unsigned tempBufSize; + Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; +} CLzmaDec; + +#define LzmaDec_Construct(p) { (p)->dic = NULL; (p)->probs = NULL; } + +void LzmaDec_Init(CLzmaDec *p); + +typedef enum +{ + LZMA_FINISH_ANY, + LZMA_FINISH_END +} ELzmaFinishMode; + +typedef enum +{ + LZMA_STATUS_NOT_SPECIFIED, + LZMA_STATUS_FINISHED_WITH_MARK, + LZMA_STATUS_NOT_FINISHED, + LZMA_STATUS_NEEDS_MORE_INPUT, + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK +} ELzmaStatus; + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc); + +SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); +void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr alloc); + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAllocPtr alloc); + +EXTERN_C_END + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..26a91bf --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,36 @@ +enable_testing() + +set(SIPHON_TEST_WORKDIR ${CMAKE_SOURCE_DIR}) + +add_executable(test_rarc test_rarc.c) +target_link_libraries(test_rarc PRIVATE siphon) +add_test( + NAME test_rarc + COMMAND test_rarc + WORKING_DIRECTORY ${SIPHON_TEST_WORKDIR} +) + +add_executable(test_aes test_aes.c) +target_link_libraries(test_aes PRIVATE siphon) +target_include_directories(test_aes PRIVATE ${CMAKE_SOURCE_DIR}/src/disc) +add_test(NAME test_aes COMMAND test_aes) + +add_executable(test_lzma test_lzma.c) +target_link_libraries(test_lzma PRIVATE siphon) +target_include_directories(test_lzma PRIVATE ${CMAKE_SOURCE_DIR}/src/disc) +add_test(NAME test_lzma COMMAND test_lzma) + +add_executable(test_disc_local test_disc_local.c) +target_link_libraries(test_disc_local PRIVATE siphon) +add_test(NAME test_disc_local COMMAND test_disc_local) +set_tests_properties(test_disc_local PROPERTIES SKIP_RETURN_CODE 77) + +macro(add_disc_test name) + add_executable(test_${name} test_${name}.c test_disc_common.c) + target_link_libraries(test_${name} PRIVATE siphon) + target_compile_definitions(test_${name} PRIVATE + SIPHON_TEST_ROOT="${CMAKE_SOURCE_DIR}") + add_test(NAME test_${name} COMMAND test_${name}) +endmacro() + +add_disc_test(ciso_gc) diff --git a/test/fixtures/mock/garbage.bin b/test/fixtures/mock/garbage.bin new file mode 100644 index 0000000..3b6f124 Binary files /dev/null and b/test/fixtures/mock/garbage.bin differ diff --git a/test/fixtures/mock/gc/game.ciso b/test/fixtures/mock/gc/game.ciso new file mode 100644 index 0000000..bfbeae4 Binary files /dev/null and b/test/fixtures/mock/gc/game.ciso differ diff --git a/test/test_aes.c b/test/test_aes.c new file mode 100644 index 0000000..6b73fc9 --- /dev/null +++ b/test/test_aes.c @@ -0,0 +1,23 @@ +#include "aes.h" +#include +#include + +/* FIPS-197 AES-128 single-block known answer. IV=0 => CBC == ECB for one block. */ +int main(void) { + uint8_t key[16] = {0x2b,0x7e,0x15,0x16,0x28,0xae,0xd2,0xa6, + 0xab,0xf7,0x15,0x88,0x09,0xcf,0x4f,0x3c}; + uint8_t iv[16] = {0}; + uint8_t buf[16] = {0x39,0x25,0x84,0x1d,0x02,0xdc,0x09,0xfb, + 0xdc,0x11,0x85,0x97,0x19,0x6a,0x0b,0x32}; + uint8_t expect[16] = {0x32,0x43,0xf6,0xa8,0x88,0x5a,0x30,0x8d, + 0x31,0x31,0x98,0xa2,0xe0,0x37,0x07,0x34}; + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, key, iv); + AES_CBC_decrypt_buffer(&ctx, buf, 16); + if (memcmp(buf, expect, 16) != 0) { + fprintf(stderr, "AES CBC decrypt mismatch\n"); + return 1; + } + printf("aes ok\n"); + return 0; +} diff --git a/test/test_ciso_gc.c b/test/test_ciso_gc.c new file mode 100644 index 0000000..cc49306 --- /dev/null +++ b/test/test_ciso_gc.c @@ -0,0 +1,17 @@ +#include "test_disc_common.h" +#include + +int main(void) { + DiscTestConfig cfg = { + .fixture = SIPHON_TEST_ROOT "/test/fixtures/mock/gc/game.ciso", + .extract_dir = SIPHON_TEST_ROOT "/test/out/ciso", + .expected_format = "CISO", + .expected_game_id = "SIPHON", + .expected_entry_count = 1, + }; + + int failures = run_disc_tests(&cfg); + if (failures == 0) + printf("all tests passed\n"); + return failures; +} \ No newline at end of file diff --git a/test/test_disc_common.c b/test/test_disc_common.c new file mode 100644 index 0000000..af1d728 --- /dev/null +++ b/test/test_disc_common.c @@ -0,0 +1,162 @@ +#include "test_disc_common.h" +#include "siphon.h" +#include +#include + +typedef struct { + char buf[2048]; + size_t len; +} LogCapture; + +static void capture_log(void* userdata, const char* msg) { + LogCapture* lc = (LogCapture*)userdata; + size_t room = sizeof(lc->buf) - lc->len - 1; + if (room == 0) return; + size_t n = strlen(msg); + if (n > room) n = room; + memcpy(lc->buf + lc->len, msg, n); + lc->len += n; + lc->buf[lc->len] = '\0'; +} + +static void silent_log(void* u, const char* m) { (void)u; (void)m; } + +static int test_inspect_ok(const DiscTestConfig* cfg) { + SiphonDiscInfo info; + SiphonError err = siphon_disc_inspect(cfg->fixture, &info, silent_log, NULL); + if (err != SIPHON_OK) { + fprintf(stderr, "inspect returned %d, expected SIPHON_OK\n", err); + return 1; + } + return 0; +} + +static int test_inspect_format(const DiscTestConfig* cfg) { + SiphonDiscInfo info; + siphon_disc_inspect(cfg->fixture, &info, silent_log, NULL); + if (strcmp(info.format, cfg->expected_format) != 0) { + fprintf(stderr, "format: got '%s', expected '%s'\n", + info.format, cfg->expected_format); + return 1; + } + return 0; +} + +static int test_inspect_game_id(const DiscTestConfig* cfg) { + if (!cfg->expected_game_id) return 0; + SiphonDiscInfo info; + siphon_disc_inspect(cfg->fixture, &info, silent_log, NULL); + if (strncmp(info.game_id, cfg->expected_game_id, + strlen(cfg->expected_game_id)) != 0) { + fprintf(stderr, "game_id: got '%.6s', expected '%s'\n", + info.game_id, cfg->expected_game_id); + return 1; + } + return 0; +} + +static int test_inspect_entry_count(const DiscTestConfig* cfg) { + if (cfg->expected_entry_count < 0) return 0; + SiphonDiscInfo info; + siphon_disc_inspect(cfg->fixture, &info, silent_log, NULL); + if (info.entry_count != cfg->expected_entry_count) { + fprintf(stderr, "entry_count: got %d, expected %d\n", + info.entry_count, cfg->expected_entry_count); + return 1; + } + return 0; +} + +static int test_extract_ok(const DiscTestConfig* cfg) { + SiphonError err = siphon_disc_extract( + cfg->fixture, cfg->extract_dir, + NULL, 0, + silent_log, NULL + ); + if (err != SIPHON_OK) { + fprintf(stderr, "extract returned %d, expected SIPHON_OK\n", err); + return 1; + } + return 0; +} + +static int test_extract_id_match(const DiscTestConfig* cfg) { + if (!cfg->expected_game_id) return 0; + const char* ids[] = { cfg->expected_game_id }; + SiphonError err = siphon_disc_extract( + cfg->fixture, cfg->extract_dir, + ids, 1, + silent_log, NULL + ); + if (err != SIPHON_OK) { + fprintf(stderr, "extract with matching ID returned %d\n", err); + return 1; + } + return 0; +} + +static int test_extract_id_mismatch(const DiscTestConfig* cfg) { + if (!cfg->expected_game_id) return 0; + const char* ids[] = { "NOTMATCH" }; + SiphonError err = siphon_disc_extract( + cfg->fixture, cfg->extract_dir, + ids, 1, + silent_log, NULL + ); + if (err != SIPHON_ERR_ID_MISMATCH) { + fprintf(stderr, "expected SIPHON_ERR_ID_MISMATCH, got %d\n", err); + return 1; + } + return 0; +} + +static int test_inspect_bad_file(const DiscTestConfig* cfg) { + (void)cfg; + SiphonDiscInfo info; + SiphonError err = siphon_disc_inspect( + SIPHON_TEST_ROOT "/test/fixtures/mock/garbage.bin", + &info, silent_log, NULL + ); + if (err == SIPHON_OK) { + fprintf(stderr, "bad file accepted as SIPHON_OK; should have errored\n"); + return 1; + } + return 0; +} + +static int test_log_on_error(const DiscTestConfig* cfg) { + (void)cfg; + LogCapture lc = {0}; + SiphonDiscInfo info; + siphon_disc_inspect( + SIPHON_TEST_ROOT "/test/fixtures/mock/garbage.bin", + &info, capture_log, &lc + ); + if (lc.len == 0) { + fprintf(stderr, "no log output on bad-file inspect\n"); + return 1; + } + return 0; +} + +#define RUN(fn) do { \ + if ((fn)(cfg) != 0) { failures++; fprintf(stderr, "FAIL " #fn "\n"); } \ +} while (0) + +int run_disc_tests(const DiscTestConfig* cfg) { + int failures = 0; + + RUN(test_inspect_ok); + RUN(test_inspect_format); + RUN(test_inspect_game_id); + RUN(test_inspect_entry_count); + RUN(test_extract_ok); + RUN(test_extract_id_match); + RUN(test_extract_id_mismatch); + RUN(test_inspect_bad_file); + RUN(test_log_on_error); + + return failures; +} + +#undef RUN \ No newline at end of file diff --git a/test/test_disc_common.h b/test/test_disc_common.h new file mode 100644 index 0000000..94c9270 --- /dev/null +++ b/test/test_disc_common.h @@ -0,0 +1,14 @@ +#ifndef TEST_DISC_COMMON_H +#define TEST_DISC_COMMON_H + +typedef struct { + const char* fixture; + const char* extract_dir; + const char* expected_format; + const char* expected_game_id; // NULL to skip + int expected_entry_count; // -1 to skip +} DiscTestConfig; + +int run_disc_tests(const DiscTestConfig* cfg); + +#endif \ No newline at end of file diff --git a/test/test_disc_local.c b/test/test_disc_local.c new file mode 100644 index 0000000..a3d7395 --- /dev/null +++ b/test/test_disc_local.c @@ -0,0 +1,40 @@ +#include "siphon.h" +#include +#include +#include + +static void silent(void* u, const char* m) { (void)u; (void)m; } + +int main(void) { + const char* path = getenv("SIPHON_TEST_DISC"); + if (!path || !*path) { + printf("skipped: set SIPHON_TEST_DISC= to run\n"); + return 77; + } + + SiphonDiscInfo info; + SiphonError err = siphon_disc_inspect(path, &info, silent, NULL); + if (err != SIPHON_OK) { + fprintf(stderr, "inspect(%s) returned %d\n", path, err); + return 1; + } + if (info.format[0] == '\0') { fprintf(stderr, "empty format\n"); return 1; } + if (info.game_id[0] == '\0') { fprintf(stderr, "empty game id\n"); return 1; } + + const char* expid = getenv("SIPHON_TEST_DISC_ID"); + if (expid && *expid && strncmp(info.game_id, expid, strlen(expid)) != 0) { + fprintf(stderr, "game id: got '%.6s', expected '%s'\n", info.game_id, expid); + return 1; + } + + const char* outdir = getenv("SIPHON_TEST_OUT"); + if (!outdir || !*outdir) outdir = "siphon_test_disc_out"; + err = siphon_disc_extract(path, outdir, NULL, 0, silent, NULL); + if (err != SIPHON_OK) { + fprintf(stderr, "extract returned %d\n", err); + return 1; + } + + printf("ok: format=%s id=%.6s entries=%d\n", info.format, info.game_id, info.entry_count); + return 0; +} diff --git a/test/test_lzma.c b/test/test_lzma.c new file mode 100644 index 0000000..76ca838 --- /dev/null +++ b/test/test_lzma.c @@ -0,0 +1,29 @@ +#include "lzma.h" +#include +#include +#include + +static void* a_(ISzAllocPtr p, size_t s) { (void)p; return malloc(s); } +static void f_(ISzAllocPtr p, void* a) { (void)p; free(a); } + +static const unsigned char PROPS[5] = { 93,0,0,1,0 }; +static const unsigned char COMP[] = { 0,58,26,8,206,118,199,229,233,214,7,52,195,209,14,191,206,85,225,170,189,224,228,143,152,1,221,141,229,7,84,158,101,37,95,39,58,106,126,180,211,73,3,137,206,212,125,60,255,154,222,54,28,172,17,101,226,202,251,41,137,38,127,3,137,61,7,131,154,253,191,255,240,56,0,0 }; + +int main(void) { + const char* phrase = "the quick brown fox jumps over the lazy dog. "; + size_t pl = strlen(phrase); + size_t N = 4096; + unsigned char* expect = (unsigned char*)malloc(N); + for (size_t i = 0; i < N; i++) expect[i] = (unsigned char)phrase[i % pl]; + + unsigned char* out = (unsigned char*)malloc(N + 64); + ISzAlloc al = { a_, f_ }; + SizeT destLen = N + 64, srcLen = sizeof(COMP); + ELzmaStatus st; + SRes r = LzmaDecode(out, &destLen, COMP, &srcLen, PROPS, 5, LZMA_FINISH_ANY, &st, &al); + if (r != SZ_OK) { fprintf(stderr, "LzmaDecode rc=%d\n", r); return 1; } + if (destLen != N) { fprintf(stderr, "len: got %lu want %lu\n", (unsigned long)destLen, (unsigned long)N); return 1; } + if (memcmp(out, expect, N) != 0) { fprintf(stderr, "data mismatch\n"); return 1; } + printf("lzma ok (%lu -> %lu)\n", (unsigned long)sizeof(COMP), (unsigned long)N); + return 0; +} diff --git a/test/test_rarc.c b/test/test_rarc.c index 59124d6..ead75b1 100644 --- a/test/test_rarc.c +++ b/test/test_rarc.c @@ -1,230 +1,218 @@ #include #include #include -#include -#include "gc_disc.h" -#include "macros.h" - -/* - * Build a minimal valid RARC blob in memory. - * - * Layout (all big-endian): - * 0x0000 Header (0x20) - * 0x0020 FST info (0x20) - * 0x0040 Dir table (1 dir × 0x10 = 0x10) - * 0x0050 File table (4 entries × 0x14 = 0x50) - * 0x00A0 String table ("ROOT\0.\0..\0file.bin\0" = 0x14) - * 0x00B4 File data (0x04) - * total 0x00B8 - * - * 4 file-table entries: - * 0 – "." (self-ref dir) - * 1 – ".." (parent-ref dir) - * 2 – root dir (dir entry, points back to dir index 0) - * 3 – file.bin (file) - */ +#include "siphon.h" +#include + +#define FIXTURE_ARC "test/fixtures/minimal.arc" +#define FIXTURE_INNER "tex/sw_rope.bti" +#define EXTRACT_DIR "test/out/rarc" +#define COPY_OUT "test/out/rarc_copy/sw_rope.bti" +#define TMP_ARC "test/out/rarc_tmp/minimal_tmp.arc" + +typedef struct { char buf[2048]; size_t len; } LogCapture; + +static void capture_log(void* userdata, const char* msg) { + LogCapture* lc = (LogCapture*)userdata; + size_t room = sizeof(lc->buf) - lc->len - 1; + if (!room) return; + size_t n = strlen(msg); if (n > room) n = room; + memcpy(lc->buf + lc->len, msg, n); + lc->len += n; + lc->buf[lc->len] = '\0'; +} +static void silent_log(void* u, const char* m) { (void)u; (void)m; } + +static int file_exists(const char* path) { + FILE* f = fopen(path, "rb"); + if (!f) return 0; + fclose(f); return 1; +} + +static long file_size(const char* path) { + FILE* f = fopen(path, "rb"); + if (!f) return -1; + fseek(f, 0, SEEK_END); long sz = ftell(f); + fclose(f); return sz; +} + static void wr32(unsigned char* p, unsigned int v) { - p[0] = (v >> 24) & 0xFF; - p[1] = (v >> 16) & 0xFF; - p[2] = (v >> 8) & 0xFF; - p[3] = v & 0xFF; + p[0]=(v>>24)&0xFF; p[1]=(v>>16)&0xFF; p[2]=(v>>8)&0xFF; p[3]=v&0xFF; } static void wr16(unsigned char* p, unsigned short v) { - p[0] = (v >> 8) & 0xFF; - p[1] = v & 0xFF; + p[0]=(v>>8)&0xFF; p[1]=v&0xFF; } -static int build_minimal_rarc(unsigned char** out, size_t* out_size) { - /* offsets */ - const unsigned int HDR = 0x0000; - const unsigned int FST_INFO = 0x0020; - const unsigned int DIR_TBL = 0x0040; - const unsigned int FILE_TBL = 0x0050; - const unsigned int STR_TBL = 0x00A0; - const unsigned int DATA = 0x00B4; - const unsigned int TOTAL = 0x00B8; - - /* string table layout: ROOT\0.\0..\0file.bin\0 */ - const unsigned int STR_ROOT = 0x00; /* "ROOT" */ - const unsigned int STR_DOT = 0x05; /* "." */ - const unsigned int STR_DOTDOT = 0x07; /* ".." */ - const unsigned int STR_FILE = 0x0A; /* "file.bin" */ - const unsigned int STR_LEN = 0x14; - - const unsigned int NUM_DIRS = 1; - const unsigned int NUM_ENTRIES = 4; +static int write_minimal_rarc(const char* path) { + const unsigned int HDR=0,FST_INFO=0x20,DIR_TBL=0x40,FILE_TBL=0x50, + STR_TBL=0xA0,DATA=0xB4,TOTAL=0xB8; + const unsigned int STR_ROOT=0,STR_DOT=5,STR_DOTDOT=7,STR_FILE=0xA,STR_LEN=0x14; unsigned char* buf = (unsigned char*)calloc(1, TOTAL); if (!buf) return -1; - /* ---- Header ---- */ - memcpy(buf + HDR + 0x00, "RARC", 4); - wr32(buf + HDR + 0x04, TOTAL); /* file size */ - wr32(buf + HDR + 0x08, 0x20); /* header size */ - wr32(buf + HDR + 0x0C, DATA - 0x20); /* data offset relative to end-of-header */ - wr32(buf + HDR + 0x10, 0x04); /* data length */ - wr32(buf + HDR + 0x14, 0x04); /* data length copy */ - - /* ---- FST info (at 0x20) ---- */ - /* All offsets here are relative to FST info start (0x20). */ - unsigned char* fst = buf + FST_INFO; - wr32(fst + 0x00, NUM_DIRS); - wr32(fst + 0x04, DIR_TBL - FST_INFO); /* dirs_offset */ - wr32(fst + 0x08, NUM_ENTRIES); - wr32(fst + 0x0C, FILE_TBL - FST_INFO); /* files_offset */ - wr32(fst + 0x10, STR_LEN); - wr32(fst + 0x14, STR_TBL - FST_INFO); /* string_table_offset */ - wr16(fst + 0x18, 1); /* num_files_written (just the real file) */ - - /* ---- Dir table (1 entry at 0x40) ---- */ - unsigned char* dir = buf + DIR_TBL; - /* id = 0xFFFFFFFF (root), name_hash, ?, num_entries, first_entry_index */ - wr32(dir + 0x00, 0xFFFFFFFF); - wr32(dir + 0x04, 0x52415243); /* hash placeholder */ - wr16(dir + 0x08, STR_ROOT); - wr16(dir + 0x0A, NUM_ENTRIES); - wr32(dir + 0x0C, 0); /* first_entry_index = 0 */ - - /* ---- File table (4 entries at 0x50, each 0x14 bytes) ---- */ - unsigned char* fe = buf + FILE_TBL; - - /* entry 0: "." self-ref dir */ - wr16(fe + 0x00, 0xFFFF); /* id = dir */ - wr16(fe + 0x02, 0x0000); - wr16(fe + 0x04, 0x0200); /* type = dir */ - wr16(fe + 0x06, STR_DOT); - wr32(fe + 0x08, 0); /* data_offset = dir index 0 */ - wr32(fe + 0x0C, 0); - fe += 0x14; - - /* entry 1: ".." parent-ref dir */ - wr16(fe + 0x00, 0xFFFF); - wr16(fe + 0x02, 0x0000); - wr16(fe + 0x04, 0x0200); - wr16(fe + 0x06, STR_DOTDOT); - wr32(fe + 0x08, 0xFFFFFFFF); /* no parent = -1 */ - wr32(fe + 0x0C, 0); - fe += 0x14; - - /* entry 2: root dir entry (dir) */ - wr16(fe + 0x00, 0xFFFF); - wr16(fe + 0x02, 0x0000); - wr16(fe + 0x04, 0x0200); - wr16(fe + 0x06, STR_ROOT); - wr32(fe + 0x08, 0); - wr32(fe + 0x0C, 0); - fe += 0x14; - - /* entry 3: "file.bin" actual file */ - wr16(fe + 0x00, 0x0000); /* id = 0 */ - wr16(fe + 0x02, 0x0000); - wr16(fe + 0x04, 0x1100); /* type = file */ - wr16(fe + 0x06, STR_FILE); - wr32(fe + 0x08, 0); /* data_offset from data start */ - wr32(fe + 0x0C, 4); /* size */ - - /* ---- String table (at 0xA0) ---- */ - unsigned char* st = buf + STR_TBL; - memcpy(st + STR_ROOT, "ROOT", 4); st[STR_ROOT + 4] = '\0'; - memcpy(st + STR_DOT, ".", 1); st[STR_DOT + 1] = '\0'; - memcpy(st + STR_DOTDOT, "..", 2); st[STR_DOTDOT + 2] = '\0'; - memcpy(st + STR_FILE, "file.bin", 8); st[STR_FILE + 8] = '\0'; - - /* ---- File data (at 0xB4) ---- */ - buf[DATA + 0] = 0xDE; - buf[DATA + 1] = 0xAD; - buf[DATA + 2] = 0xBE; - buf[DATA + 3] = 0xEF; - - *out = buf; - *out_size = TOTAL; - return 0; + memcpy(buf+HDR, "RARC", 4); + wr32(buf+HDR+0x04, TOTAL); + wr32(buf+HDR+0x08, 0x20); + wr32(buf+HDR+0x0C, DATA-0x20); + wr32(buf+HDR+0x10, 0x04); + wr32(buf+HDR+0x14, 0x04); + + unsigned char* fst = buf+FST_INFO; + wr32(fst+0x00, 1); + wr32(fst+0x04, DIR_TBL-FST_INFO); + wr32(fst+0x08, 4); + wr32(fst+0x0C, FILE_TBL-FST_INFO); + wr32(fst+0x10, STR_LEN); + wr32(fst+0x14, STR_TBL-FST_INFO); + wr16(fst+0x18, 1); + + unsigned char* dir = buf+DIR_TBL; + wr32(dir+0x00, 0xFFFFFFFF); + wr32(dir+0x04, 0x52415243); + wr16(dir+0x08, STR_ROOT); + wr16(dir+0x0A, 4); + wr32(dir+0x0C, 0); + + unsigned char* fe = buf+FILE_TBL; + wr16(fe+0x00,0xFFFF); wr16(fe+0x04,0x0200); wr16(fe+0x06,STR_DOT); wr32(fe+0x08,0); fe+=0x14; + wr16(fe+0x00,0xFFFF); wr16(fe+0x04,0x0200); wr16(fe+0x06,STR_DOTDOT); wr32(fe+0x08,0xFFFFFFFF); fe+=0x14; + wr16(fe+0x00,0xFFFF); wr16(fe+0x04,0x0200); wr16(fe+0x06,STR_ROOT); wr32(fe+0x08,0); fe+=0x14; + wr16(fe+0x00,0x0000); wr16(fe+0x04,0x1100); wr16(fe+0x06,STR_FILE); wr32(fe+0x08,0); wr32(fe+0x0C,4); + + unsigned char* st = buf+STR_TBL; + memcpy(st+STR_ROOT,"ROOT",4); st[STR_ROOT+4]='\0'; + memcpy(st+STR_DOT,".",1); st[STR_DOT+1]='\0'; + memcpy(st+STR_DOTDOT,"..",2); st[STR_DOTDOT+2]='\0'; + memcpy(st+STR_FILE,"file.bin",8); st[STR_FILE+8]='\0'; + + buf[DATA]=0xDE; buf[DATA+1]=0xAD; buf[DATA+2]=0xBE; buf[DATA+3]=0xEF; + + MKDIR_ONE("test/out"); MKDIR_ONE("test/out/rarc_tmp"); + FILE* f = fopen(path, "wb"); + if (!f) { free(buf); return -1; } + int ok = (fwrite(buf, 1, TOTAL, f) == TOTAL); + fclose(f); free(buf); + return ok ? 0 : -1; } -static int test_walk_entries(void) { - unsigned char* buf = NULL; size_t sz = 0; - if (build_minimal_rarc(&buf, &sz) != 0) return 1; - GCArc* arc = gc_arc_open_mem(buf, sz); - if (!arc) { free(buf); return 1; } - - int n = gc_arc_entry_count(arc); - int found_file = 0; - for (int i = 0; i < n; i++) { - const GCEntry* e = gc_arc_entry(arc, i); - if (!e || !e->name) continue; - if (e->type == GC_ENTRY_FILE && e->size > 0) found_file = 1; - } - gc_arc_close(arc); free(buf); - if (!found_file) { fprintf(stderr, "no file entries found\n"); return 1; } +static int test_extract_ok(void) { + if (write_minimal_rarc(TMP_ARC) != 0) { + fprintf(stderr, "failed to write tmp arc\n"); return 1; + } + MKDIR_ONE(EXTRACT_DIR); + SiphonError err = siphon_arc_extract(TMP_ARC, EXTRACT_DIR, silent_log, NULL); + if (err != SIPHON_OK) { + fprintf(stderr, "arc_extract returned %d\n", err); return 1; + } return 0; } -static int test_open_from_mem(void) { - unsigned char* buf = NULL; - size_t sz = 0; - if (build_minimal_rarc(&buf, &sz) != 0) { - fprintf(stderr, "build_minimal_rarc failed\n"); - return 1; - } - GCArc* arc = gc_arc_open_mem(buf, sz); - if (!arc) { free(buf); fprintf(stderr, "open failed\n"); return 1; } - int n = gc_arc_entry_count(arc); - if (n < 2) { - fprintf(stderr, "entry_count=%d, expected >=2\n", n); - gc_arc_close(arc); free(buf); return 1; - } - gc_arc_close(arc); - free(buf); +static int test_extract_all(void) { + if (!file_exists(FIXTURE_ARC)) { + fprintf(stderr, "fixture %s not found\n", FIXTURE_ARC); return 1; + } + MKDIR_ONE(EXTRACT_DIR); + SiphonError err = siphon_arc_extract(FIXTURE_ARC, EXTRACT_DIR, silent_log, NULL); + if (err != SIPHON_OK) { + fprintf(stderr, "arc_extract returned %d\n", err); return 1; + } + if (!file_exists("test/out/rarc/tex/sw_rope.bti")) { + fprintf(stderr, "tex/sw_rope.bti not extracted\n"); return 1; + } + if (file_size("test/out/rarc/tex/sw_rope.bti") != 160) { + fprintf(stderr, "sw_rope.bti wrong size\n"); return 1; + } return 0; } -static int load_file(const char* path, unsigned char** out, size_t* out_size) { - FILE* f = fopen(path, "rb"); - if (!f) return -1; - fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET); - if (sz <= 0) { fclose(f); return -1; } - unsigned char* buf = (unsigned char*)malloc((size_t)sz); - if (!buf) { fclose(f); return -1; } - if (fread(buf, 1, (size_t)sz, f) != (size_t)sz) { free(buf); fclose(f); return -1; } - fclose(f); - *out = buf; *out_size = (size_t)sz; +static int test_list_ok(void) { + if (!file_exists(FIXTURE_ARC)) { + fprintf(stderr, "fixture %s not found\n", FIXTURE_ARC); return 1; + } + LogCapture lc = {0}; + SiphonError err = siphon_arc_list(FIXTURE_ARC, capture_log, &lc); + if (err != SIPHON_OK) { + fprintf(stderr, "arc_list returned %d\n", err); return 1; + } + if (lc.len == 0) { + fprintf(stderr, "arc_list produced no output\n"); return 1; + } return 0; } -static int test_extract_all(void) { - unsigned char* buf = NULL; size_t sz = 0; - if (load_file("test/fixtures/minimal.arc", &buf, &sz) != 0) { - fprintf(stderr, "fixture minimal.arc not found\n"); - return 1; +static int test_list_contains_known_file(void) { + if (!file_exists(FIXTURE_ARC)) { + fprintf(stderr, "fixture %s not found\n", FIXTURE_ARC); return 1; } - GCArc* arc = gc_arc_open_mem(buf, sz); - if (!arc) { free(buf); fprintf(stderr, "open failed\n"); return 1; } - - const char* outdir = "test/out"; - MKDIR_ONE("test"); MKDIR_ONE(outdir); + LogCapture lc = {0}; + siphon_arc_list(FIXTURE_ARC, capture_log, &lc); + if (!strstr(lc.buf, "sw_rope.bti")) { + fprintf(stderr, "arc_list output missing 'sw_rope.bti'\n"); return 1; + } + return 0; +} - int rc = gc_arc_extract_all(arc, outdir); - gc_arc_close(arc); free(buf); - if (rc != 0) { fprintf(stderr, "extract_all returned %d\n", rc); return 1; } +static int test_copy_single_file(void) { + if (!file_exists(FIXTURE_ARC)) { + fprintf(stderr, "fixture %s not found\n", FIXTURE_ARC); return 1; + } + MKDIR_ONE("test/out"); MKDIR_ONE("test/out/rarc_copy"); + SiphonError err = siphon_arc_copy( + FIXTURE_ARC, FIXTURE_INNER, COPY_OUT, silent_log, NULL + ); + if (err != SIPHON_OK) { + fprintf(stderr, "arc_copy returned %d\n", err); return 1; + } + if (!file_exists(COPY_OUT)) { + fprintf(stderr, "%s not created\n", COPY_OUT); return 1; + } + if (file_size(COPY_OUT) != 160) { + fprintf(stderr, "copied file wrong size\n"); return 1; + } + return 0; +} - unsigned char* got = NULL; size_t got_sz = 0; - if (load_file("test/out/tex/sw_rope.bti", &got, &got_sz) != 0) { - fprintf(stderr, "tex/sw_rope.bti not extracted\n"); - return 1; +static int test_copy_missing_inner(void) { + if (!file_exists(FIXTURE_ARC)) { + fprintf(stderr, "fixture %s not found\n", FIXTURE_ARC); return 1; + } + SiphonError err = siphon_arc_copy( + FIXTURE_ARC, "does/not/exist.bin", COPY_OUT, silent_log, NULL + ); + if (err != SIPHON_ERR_NOT_FOUND) { + fprintf(stderr, "expected SIPHON_ERR_NOT_FOUND, got %d\n", err); return 1; } - if (got_sz != 160) { - fprintf(stderr, "extracted size %zu, expected 160\n", got_sz); - free(got); return 1; + return 0; +} + +static int test_bad_file(void) { + SiphonError err = siphon_arc_extract( + "test/fixtures/mock/garbage.bin", EXTRACT_DIR, silent_log, NULL + ); + if (err == SIPHON_OK) { + fprintf(stderr, "bad file accepted as SIPHON_OK\n"); return 1; } - free(got); return 0; } int main(void) { int failures = 0; - if (test_open_from_mem() != 0) { failures++; fprintf(stderr, "FAIL test_open_from_mem\n"); } - if (test_walk_entries() != 0) { failures++; fprintf(stderr, "FAIL test_walk_entries\n"); } - if (test_extract_all() != 0) { failures++; fprintf(stderr, "FAIL test_extract_all\n"); } + +#define RUN(t) do { \ + if ((t)() != 0) { failures++; fprintf(stderr, "FAIL " #t "\n"); } \ +} while (0) + + RUN(test_extract_ok); + RUN(test_extract_all); + RUN(test_list_ok); + RUN(test_list_contains_known_file); + RUN(test_copy_single_file); + RUN(test_copy_missing_inner); + RUN(test_bad_file); + +#undef RUN + if (failures == 0) printf("all tests passed\n"); return failures; -} +} \ No newline at end of file