diff --git a/CMakeLists.txt b/CMakeLists.txt index 93af9a9..c44a3a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,13 @@ add_library(gc_disc STATIC target_include_directories(gc_disc PUBLIC include PRIVATE src/disc) target_link_libraries(gc_disc PRIVATE ZLIB::ZLIB PUBLIC gc_util Confluence::confluence) +if(APPLE) + option(SIPHON_USE_COMMONCRYPTO "Use Apple CommonCrypto for Wii AES" ON) + if(SIPHON_USE_COMMONCRYPTO) + target_compile_definitions(gc_disc PRIVATE SIPHON_USE_COMMONCRYPTO=1) + endif() +endif() + if(ZSTD_FOUND) target_include_directories(gc_disc PRIVATE ${ZSTD_INCLUDE_DIRS}) target_link_directories(gc_disc PUBLIC ${ZSTD_LIBRARY_DIRS}) diff --git a/include/siphon.h b/include/siphon.h index 8db5987..7a1bf59 100644 --- a/include/siphon.h +++ b/include/siphon.h @@ -35,6 +35,8 @@ SiphonError siphon_disc_extract( void* userdata ); +const char* siphon_last_log(void); + SiphonError siphon_arc_extract(const char* archive, const char* outdir, SiphonLogFn log, void* userdata); SiphonError siphon_arc_list(const char* archive, SiphonLogFn log, void* userdata); SiphonError siphon_arc_copy(const char* archive, const char* inner, const char* out_path, SiphonLogFn log, void* userdata); diff --git a/src/disc/aes.c b/src/disc/aes.c index 7f5fe90..87e6f76 100644 --- a/src/disc/aes.c +++ b/src/disc/aes.c @@ -55,7 +55,18 @@ j=i*4;k=(i-Nk)*4; RoundKey[j+0]=RoundKey[k+0]^tempa[0];RoundKey[j+1]=RoundKey[k+1]^tempa[1];RoundKey[j+2]=RoundKey[k+2]^tempa[2];RoundKey[j+3]=RoundKey[k+3]^tempa[3]; } } -void AES_init_ctx_iv(struct AES_ctx*ctx,const uint8_t*key,const uint8_t*iv){KeyExpansion(ctx->RoundKey,key);memcpy(ctx->Iv,iv,AES_BLOCKLEN);} +void AES_init_ctx_key(struct AES_ctx* ctx, const uint8_t* key) { + KeyExpansion(ctx->RoundKey, key); +} + +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) { + memcpy(ctx->Iv, iv, AES_BLOCKLEN); +} + +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) { + AES_init_ctx_key(ctx, key); + AES_ctx_set_iv(ctx, iv); +} 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]; } diff --git a/src/disc/aes.h b/src/disc/aes.h index 9d00409..6530c42 100644 --- a/src/disc/aes.h +++ b/src/disc/aes.h @@ -5,6 +5,8 @@ #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_key(struct AES_ctx* ctx, const uint8_t* key); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); 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_ciso.c b/src/disc/gc_ciso.c index 7fd9bba..1c1bd04 100644 --- a/src/disc/gc_ciso.c +++ b/src/disc/gc_ciso.c @@ -14,7 +14,7 @@ typedef struct { uint32_t* presentBefore; } CISOData; -static int ciso_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { +static int ciso_read(GCDisc* disc, uint64_t offset, void* buf, size_t size) { CISOData* cd = (CISOData*)disc->formatData; uint8_t* out = (uint8_t*)buf; size_t remaining = size; @@ -36,7 +36,7 @@ static int ciso_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { } out += chunk; - offset += (uint32_t)chunk; + offset += chunk; remaining -= chunk; } diff --git a/src/disc/gc_disc.c b/src/disc/gc_disc.c index 1cab562..1c9c997 100644 --- a/src/disc/gc_disc.c +++ b/src/disc/gc_disc.c @@ -1,4 +1,5 @@ #include "gc_disc_internal.h" +#include "siphon_log.h" #include #include @@ -8,6 +9,18 @@ static const uint8_t MAGIC_RVZ[4] = {'R','V','Z',0x01}; static const uint8_t MAGIC_GCZ[4] = {0x01,0xC0,0x0B,0xB1}; static const uint8_t MAGIC_WBFS[4] = {'W','B','F','S'}; +static const char* format_name(GCDiscFormat fmt) { + switch (fmt) { + case GC_FORMAT_ISO: return "ISO"; + case GC_FORMAT_CISO: return "CISO"; + case GC_FORMAT_GCZ: return "GCZ"; + case GC_FORMAT_WIA: return "WIA"; + case GC_FORMAT_RVZ: return "RVZ"; + case GC_FORMAT_WBFS: return "WBFS"; + default: return "unknown"; + } +} + GCDiscFormat gc_disc_detect_format(const char* path) { FILE* f = fopen(path, "rb"); if (!f) return GC_FORMAT_UNKNOWN; @@ -56,6 +69,7 @@ GCDisc* gc_disc_open(const char* path) { } if (err != 0) { + siphon_log("gc_disc_open: %s opener failed for %s", format_name(fmt), path); fclose(disc->file); free(disc); return NULL; diff --git a/src/disc/gc_disc_fst.c b/src/disc/gc_disc_fst.c index 2415e6e..f6b4ab7 100644 --- a/src/disc/gc_disc_fst.c +++ b/src/disc/gc_disc_fst.c @@ -30,8 +30,14 @@ 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; + if (gc_wii_wrap(disc) != 0) { + siphon_log("FST: gc_wii_wrap failed"); + return -1; + } + if (disc->read(disc, 0, disc->boot, 0x440) < 0) { + siphon_log("FST: boot.bin read failed"); + return -1; + } memcpy(disc->gameId, disc->boot, 6); disc->gameId[6] = '\0'; @@ -40,23 +46,44 @@ int gc_disc_parse_fst(GCDisc* disc) { 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; + if (disc->read(disc, 0x440, disc->bi2, 0x2000) < 0) { + siphon_log("FST: bi2 read failed"); + return -1; + } uint8_t appHdr[0x20]; - if (disc->read(disc, 0x2440, appHdr, 0x20) < 0) return -1; + if (disc->read(disc, 0x2440, appHdr, 0x20) < 0) { + siphon_log("FST: apploader header read failed"); + return -1; + } uint32_t appCode = gc_be32(appHdr + 0x14); uint32_t appTrailer = gc_be32(appHdr + 0x18); disc->apploaderSize = 0x20 + appCode + appTrailer; disc->apploader = (uint8_t*)malloc(disc->apploaderSize); - if (!disc->apploader) return -1; - if (disc->read(disc, 0x2440, disc->apploader, disc->apploaderSize) < 0) return -1; + if (!disc->apploader) { + siphon_log("FST: apploader alloc failed (size=%u)", disc->apploaderSize); + return -1; + } + if (disc->read(disc, 0x2440, disc->apploader, disc->apploaderSize) < 0) { + siphon_log("FST: apploader read failed"); + return -1; + } disc->fstData = (uint8_t*)malloc(disc->fstSize); - if (!disc->fstData) return -1; - if (disc->read(disc, disc->fstOffset, disc->fstData, disc->fstSize) < 0) return -1; + if (!disc->fstData) { + siphon_log("FST: fst alloc failed (size=0x%X)", disc->fstSize); + return -1; + } + if (disc->read(disc, disc->fstOffset, disc->fstData, disc->fstSize) < 0) { + siphon_log("FST: fst read failed at 0x%X size=0x%X", disc->fstOffset, disc->fstSize); + return -1; + } disc->entryCount = gc_be32(disc->fstData + 8); - if (disc->entryCount == 0 || disc->entryCount > 100000) return -1; + if (disc->entryCount == 0 || disc->entryCount > 100000) { + siphon_log("FST: invalid entry count %u", disc->entryCount); + return -1; + } size_t strOff = disc->entryCount * 12; disc->stringTable = (const char*)(disc->fstData + strOff); @@ -284,18 +311,24 @@ int gc_disc_extract_all(GCDisc* disc, const char* outputDir) { if (disc->entries[i].type == GC_ENTRY_FILE) fileCount++; } + if (fileCount == 0) { + free(buf); + return -1; + } + GCEntry* sorted = (GCEntry*)malloc(fileCount * sizeof(GCEntry)); if (!sorted) { free(buf); return -1; } int si = 0; for (uint32_t i = 1; i < disc->entryCount; i++) { if (disc->entries[i].type == GC_ENTRY_FILE) - sorted[si++] = disc->entries[i]; + sorted[si++] = disc->entries[i]; } qsort(sorted, fileCount, sizeof(GCEntry), cmp_by_offset); char lastDir[4096] = {0}; + uint64_t bytesDone = 0; int ret = 0; for (int i = 0; i < fileCount; i++) { @@ -346,9 +379,19 @@ int gc_disc_extract_all(GCDisc* disc, const char* outputDir) { } fclose(out); } + bytesDone += e->size; + if ((i + 1) % 100 == 0 || i + 1 == fileCount) { + siphon_log("extract progress: %d/%d (%.1f MB written)", + i + 1, fileCount, (double)bytesDone / (1024.0 * 1024.0)); + } } done: + if (ret == 0) { + siphon_log("extract done: %d files, %.1f MB", fileCount, + (double)bytesDone / (1024.0 * 1024.0)); + } + free(sorted); free(buf); return ret; diff --git a/src/disc/gc_disc_internal.h b/src/disc/gc_disc_internal.h index 21c0387..4a5b744 100644 --- a/src/disc/gc_disc_internal.h +++ b/src/disc/gc_disc_internal.h @@ -4,7 +4,7 @@ #include "gc_disc.h" #include -typedef int (*gc_read_fn)(GCDisc* disc, uint32_t offset, void* buf, size_t size); +typedef int (*gc_read_fn)(GCDisc* disc, uint64_t offset, void* buf, size_t size); typedef void (*gc_close_fn)(GCDisc* disc); struct GCDisc { diff --git a/src/disc/gc_gcz.c b/src/disc/gc_gcz.c index 389a062..0eb75e6 100644 --- a/src/disc/gc_gcz.c +++ b/src/disc/gc_gcz.c @@ -60,7 +60,7 @@ static int gcz_decompress_block(GCDisc* disc, GCZData* gz, uint32_t blockIdx) { return 0; } -static int gcz_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { +static int gcz_read(GCDisc* disc, uint64_t offset, void* buf, size_t size) { GCZData* gz = (GCZData*)disc->formatData; uint8_t* out = (uint8_t*)buf; size_t remaining = size; @@ -79,7 +79,7 @@ static int gcz_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { } out += chunk; - offset += (uint32_t)chunk; + offset += chunk; remaining -= chunk; } diff --git a/src/disc/gc_iso.c b/src/disc/gc_iso.c index c2a26cd..8758f52 100644 --- a/src/disc/gc_iso.c +++ b/src/disc/gc_iso.c @@ -1,8 +1,9 @@ #include "gc_disc_internal.h" +#include #include -static int iso_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { - if (fseek(disc->file, offset, SEEK_SET) != 0) return -1; +static int iso_read(GCDisc* disc, uint64_t offset, void* buf, size_t size) { + if (fseeko(disc->file, (off_t)offset, SEEK_SET) != 0) return -1; if (fread(buf, 1, size, disc->file) != size) return -1; return 0; } diff --git a/src/disc/gc_wbfs.c b/src/disc/gc_wbfs.c index 7a0b030..9808a80 100644 --- a/src/disc/gc_wbfs.c +++ b/src/disc/gc_wbfs.c @@ -3,7 +3,8 @@ #include #include -#define GC_DISC_SIZE 0x57058000u +#define GC_DISC_SIZE 0x57058000u +#define WII_DISC_SIZE 0x118580000u typedef struct { uint32_t wbfsSectorSize; @@ -11,14 +12,14 @@ typedef struct { uint32_t wlbaCount; } WBFSData; -static int wbfs_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { +static int wbfs_read(GCDisc* disc, uint64_t offset, void* buf, size_t size) { WBFSData* wb = (WBFSData*)disc->formatData; uint8_t* out = (uint8_t*)buf; size_t remaining = size; while (remaining > 0) { - uint32_t blockIdx = offset / wb->wbfsSectorSize; - uint32_t blockOff = offset % wb->wbfsSectorSize; + uint64_t blockIdx = offset / wb->wbfsSectorSize; + uint32_t blockOff = (uint32_t)(offset % wb->wbfsSectorSize); size_t chunk = wb->wbfsSectorSize - blockOff; if (chunk > remaining) chunk = remaining; @@ -26,12 +27,15 @@ static int wbfs_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { memset(out, 0, chunk); } else { uint64_t fileOff = (uint64_t)wb->wlbaTable[blockIdx] * wb->wbfsSectorSize + blockOff; - if (fseek(disc->file, (long)fileOff, SEEK_SET) != 0) return -1; + off_t cur = ftello(disc->file); + if (cur != (off_t)fileOff) { + if (fseeko(disc->file, (off_t)fileOff, SEEK_SET) != 0) return -1; + } if (fread(out, 1, chunk, disc->file) != chunk) return -1; } out += chunk; - offset += (uint32_t)chunk; + offset += chunk; remaining -= chunk; } @@ -51,7 +55,7 @@ int gc_wbfs_open(GCDisc* disc) { uint8_t hdr[12]; if (fseek(disc->file, 0, SEEK_SET) != 0) return -1; if (fread(hdr, 1, 12, disc->file) != 12) { - siphon_log("WBFS header truncated"); + siphon_log("WBFS: header truncated"); return -1; } @@ -59,24 +63,39 @@ int gc_wbfs_open(GCDisc* disc) { uint8_t wbfsShift = hdr[9]; if (hdShift > 30 || wbfsShift > 30) { - siphon_log("WBFS invalid sector shifts (hd=%u wbfs=%u)", hdShift, wbfsShift); + siphon_log("WBFS: invalid sector shifts (hd=%u wbfs=%u)", hdShift, wbfsShift); return -1; } uint32_t hdSectorSize = 1u << hdShift; uint32_t wbfsSectorSize = 1u << wbfsShift; + if (fseek(disc->file, 0, SEEK_END) != 0) return -1; + long fileSize = ftell(disc->file); + if (fileSize < 0) return -1; + + uint64_t wlbaOff = (uint64_t)hdSectorSize + 0x100; + if ((uint64_t)fileSize <= wlbaOff) { + siphon_log("WBFS: file too small for WLBA table (size=%ld wlbaOff=0x%llX)", + fileSize, (unsigned long long)wlbaOff); + return -1; + } + + uint64_t tableBytes = (uint64_t)fileSize - wlbaOff; + uint32_t wlbaFromFile = (uint32_t)(tableBytes / 2); + uint32_t wlbaMaxWii = (WII_DISC_SIZE + wbfsSectorSize - 1) / wbfsSectorSize; + uint32_t wlbaMaxGc = (GC_DISC_SIZE + wbfsSectorSize - 1) / wbfsSectorSize; + uint32_t wlbaCount = wlbaFromFile; + if (wlbaCount > wlbaMaxWii) wlbaCount = wlbaMaxWii; + WBFSData* wb = (WBFSData*)calloc(1, sizeof(WBFSData)); if (!wb) return -1; wb->wbfsSectorSize = wbfsSectorSize; - - wb->wlbaCount = (GC_DISC_SIZE + wbfsSectorSize - 1) / wbfsSectorSize; - if (wb->wlbaCount == 0) { free(wb); return -1; } + wb->wlbaCount = wlbaCount; wb->wlbaTable = (uint16_t*)calloc(wb->wlbaCount, sizeof(uint16_t)); if (!wb->wlbaTable) { free(wb); return -1; } - uint64_t wlbaOff = (uint64_t)hdSectorSize + 0x100; if (fseek(disc->file, (long)wlbaOff, SEEK_SET) != 0) { free(wb->wlbaTable); free(wb); return -1; } @@ -85,7 +104,7 @@ int gc_wbfs_open(GCDisc* disc) { if (!rawTable) { free(wb->wlbaTable); free(wb); return -1; } if (fread(rawTable, 1, wb->wlbaCount * 2, disc->file) != wb->wlbaCount * 2) { - siphon_log("WBFS wlba table truncated"); + siphon_log("WBFS: wlba table truncated (expected %u entries)", wb->wlbaCount); free(rawTable); free(wb->wlbaTable); free(wb); return -1; } @@ -98,17 +117,8 @@ int gc_wbfs_open(GCDisc* disc) { disc->read = wbfs_read; disc->close = wbfs_close; - if (gc_disc_parse_fst(disc) != 0) return -1; - - // WBFS disc_size is fixed at GC single-layer size; a Wii or dual-layer disc - // won't have its real boot ID parse correctly. Cheap sanity: game ID starts - // with a letter A-Z. - if (disc->gameId[0] < 'A' || disc->gameId[0] > 'Z') { - siphon_log("WBFS disc doesn't look like a GC game (id='%s'); " - "siphon assumes GC single-layer (0x%X bytes)", - disc->gameId, GC_DISC_SIZE); + if (gc_disc_parse_fst(disc) != 0) return -1; - } return 0; } diff --git a/src/disc/gc_wia.c b/src/disc/gc_wia.c index cad6fc0..c29262b 100644 --- a/src/disc/gc_wia.c +++ b/src/disc/gc_wia.c @@ -56,8 +56,12 @@ typedef struct { int isWii; uint32_t sectorsPerGroup; uint32_t exceptLists; - uint32_t pd0Sectors, pd0Group; - uint32_t pd1Sectors, pd1Group; + uint32_t activePart; + uint32_t pd0FirstSector; + uint32_t pd0Sectors, pd0Group, pd0NumGroups; + uint32_t pd1FirstSector; + uint32_t pd1Sectors, pd1Group, pd1NumGroups; + uint64_t dataStart; } WIAData; static void* lzma_alloc(ISzAllocPtr p, size_t size) { (void)p; return malloc(size); } @@ -118,8 +122,21 @@ static int wia_read_and_decompress(FILE* f, uint32_t compType, const uint8_t* pr return ret; } +static size_t wia_except_header_size(WIAData* wd, const uint8_t* data, size_t len) { + size_t eo = 0; + for (uint32_t i = 0; i < wd->exceptLists; i++) { + if (eo + 2 > len) return (size_t)-1; + uint32_t ne = ((uint32_t)data[eo] << 8) | data[eo + 1]; + eo += 2 + ne * 22; + } + if (wd->compType == WIA_COMP_NONE || wd->compType == WIA_COMP_PURGE) { + while (eo % 4 != 0) eo++; + } + return eo; +} + static int wia_decompress_group(GCDisc* disc, WIAData* wd, uint32_t groupIdx, - uint64_t data_offset) { + uint64_t data_offset, int isPartitionGroup) { if (wd->cachedGroupIdx == groupIdx) return 0; WIAGroup* grp = &wd->groups[groupIdx]; @@ -148,21 +165,31 @@ static int wia_decompress_group(GCDisc* disc, WIAData* wd, uint32_t groupIdx, return -1; } - if (wd->isRVZ && grp->rvzPackedSize != 0 && isCompressed) { - if (outLen > wd->packedScratchCap) { - uint8_t* grown = (uint8_t*)realloc(wd->packedScratch, outLen); + size_t exceptSkip = 0; + if (wd->isWii && isPartitionGroup) { + exceptSkip = wia_except_header_size(wd, wd->decompBuf, outLen); + if (exceptSkip == (size_t)-1) return -1; + } + + if (wd->isRVZ && grp->rvzPackedSize != 0) { + size_t packedLen = outLen - exceptSkip; + const uint8_t* packed = wd->decompBuf + exceptSkip; + + if (packedLen > wd->packedScratchCap) { + uint8_t* grown = (uint8_t*)realloc(wd->packedScratch, packedLen); if (!grown) return -1; wd->packedScratch = grown; - wd->packedScratchCap = outLen; + wd->packedScratchCap = packedLen; } - memcpy(wd->packedScratch, wd->decompBuf, outLen); + memcpy(wd->packedScratch, packed, packedLen); size_t unpacked = 0; - if (gc_rvz_unpack(wd->packedScratch, outLen, data_offset, - wd->decompBuf, wd->decompBufCap, &unpacked) != 0) { + if (gc_rvz_unpack(wd->packedScratch, packedLen, data_offset, + wd->decompBuf + exceptSkip, wd->decompBufCap - exceptSkip, + &unpacked) != 0) { return -1; } - outLen = unpacked; + outLen = exceptSkip + unpacked; } wd->decompBufSize = (uint32_t)outLen; @@ -170,7 +197,7 @@ static int wia_decompress_group(GCDisc* disc, WIAData* wd, uint32_t groupIdx, return 0; } -static int wia_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { +static int wia_read(GCDisc* disc, uint64_t offset, void* buf, size_t size) { WIAData* wd = (WIAData*)disc->formatData; uint8_t* out = (uint8_t*)buf; size_t remaining = size; @@ -216,7 +243,7 @@ static int wia_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { if (chunk > adjSize - localOff) chunk = (size_t)(adjSize - localOff); uint64_t groupDiscOffset = adjOffset + (uint64_t)groupRel * wd->chunkSize; - if (wia_decompress_group(disc, wd, groupIdx, groupDiscOffset) < 0) return -1; + if (wia_decompress_group(disc, wd, groupIdx, groupDiscOffset, 0) < 0) return -1; if (inGroup + chunk > wd->decompBufSize) { size_t valid = wd->decompBufSize > inGroup ? wd->decompBufSize - inGroup : 0; @@ -245,7 +272,7 @@ 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) { +static int wia_part_read(GCDisc* disc, uint64_t offset, void* buf, size_t size) { WIAData* wd = (WIAData*)disc->formatData; uint8_t* out = (uint8_t*)buf; const uint32_t SD = 0x7C00; @@ -270,17 +297,13 @@ static int wia_part_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) 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; + if (wia_decompress_group(disc, wd, group, dataOff, 1) < 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; - } + size_t eo = wia_except_header_size(wd, wd->decompBuf, wd->decompBufSize); + if (eo == (size_t)-1) return -1; uint32_t secInGroup = secInPd % wd->sectorsPerGroup; - uint32_t srcPos = eo + secInGroup * SD + intra; + uint32_t srcPos = (uint32_t)eo + secInGroup * SD + intra; size_t chunk = SD - intra; if (chunk > size) chunk = size; @@ -301,6 +324,77 @@ static int wia_part_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) return 0; } +static int wia_find_data_start(GCDisc* disc, WIAData* wd, uint64_t* out) { + gc_read_fn raw = disc->read; + uint8_t magic[4]; + if (raw(disc, 0x18, magic, 4) < 0) return -1; + if (!(magic[0] == 0x5D && magic[1] == 0x1C && magic[2] == 0x9E && magic[3] == 0xA3)) + return -1; + + uint8_t grp[32]; + if (raw(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); + uint32_t infoOff = gc_be32(grp + g * 8 + 4) << 2; + if (count > 64) continue; + for (uint32_t p = 0; p < count; p++) { + uint8_t pe[8]; + if (raw(disc, infoOff + p * 8, pe, 8) < 0) return -1; + if (gc_be32(pe + 4) == 0) { + partOff = gc_be32(pe + 0) << 2; + found = 1; + break; + } + } + } + if (!found) return -1; + + uint8_t dataOffRaw[4]; + if (raw(disc, partOff + 0x2B8, dataOffRaw, 4) < 0) return -1; + *out = (uint64_t)partOff + ((uint64_t)gc_be32(dataOffRaw) << 2); + return 0; +} + +static int wia_load_active_partition(GCDisc* disc, WIAData* wd, const uint8_t* h2) { + uint32_t nPart = gc_be32(h2 + 0x90); + uint64_t partOff = gc_be64(h2 + 0x98); + uint32_t partSize = gc_be32(h2 + 0x94); + if (partSize == 0) partSize = 0x30; + if (nPart == 0) return -1; + + int found = -1; + for (uint32_t i = 0; i < nPart; i++) { + uint8_t pe[0x30]; + if (fseek(disc->file, (long)(partOff + (uint64_t)i * partSize), SEEK_SET) != 0 || + fread(pe, 1, sizeof(pe), disc->file) != sizeof(pe)) { + return -1; + } + uint32_t first = gc_be32(pe + 0x10); + uint64_t pd0Disc = (uint64_t)first * 0x8000; + if (pd0Disc == wd->dataStart) { + wd->activePart = i; + wd->pd0FirstSector = first; + wd->pd0Sectors = gc_be32(pe + 0x14); + wd->pd0Group = gc_be32(pe + 0x18); + wd->pd0NumGroups = gc_be32(pe + 0x1C); + wd->pd1FirstSector = gc_be32(pe + 0x20); + wd->pd1Sectors = gc_be32(pe + 0x24); + wd->pd1Group = gc_be32(pe + 0x28); + wd->pd1NumGroups = gc_be32(pe + 0x2C); + found = (int)i; + break; + } + } + return found < 0 ? -1 : 0; +} + +static int wia_wii_read(GCDisc* disc, uint64_t offset, void* buf, size_t size) { + return wia_part_read(disc, offset, buf, size); +} + static void wia_close(GCDisc* disc) { WIAData* wd = (WIAData*)disc->formatData; if (wd) { @@ -430,22 +524,15 @@ int gc_wia_open(GCDisc* disc, int isRVZ) { 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; + if (wia_find_data_start(disc, wd, &wd->dataStart) < 0 || + wia_load_active_partition(disc, wd, h2) < 0) { + wia_close(disc); + return -1; + } + disc->read = wia_wii_read; disc->offsetShift = 2; } diff --git a/src/disc/gc_wii.c b/src/disc/gc_wii.c index 725c238..9a1382e 100644 --- a/src/disc/gc_wii.c +++ b/src/disc/gc_wii.c @@ -4,6 +4,10 @@ #include #include +#if defined(SIPHON_USE_COMMONCRYPTO) +#include +#endif + #define WII_CLUSTER 0x8000u #define WII_CLUSTER_HASH 0x0400u #define WII_CLUSTER_DATA 0x7C00u @@ -15,10 +19,18 @@ typedef struct { uint8_t titleKey[16]; uint8_t clusterEnc[WII_CLUSTER]; uint8_t clusterPlain[WII_CLUSTER_DATA]; - uint32_t cachedCluster; + uint64_t cachedCluster; +#if !defined(SIPHON_USE_COMMONCRYPTO) + struct AES_ctx aes; +#endif } WiiCtx; -static const uint8_t WII_COMMON_KEY[16] = { +static const uint8_t WII_COMMON_KEYS[2][16] = { + { 0xbe,0xc0,0x7b,0x4e,0x9a,0xe0,0x85,0xbc,0x90,0x56,0x92,0x71,0xcd,0x17,0x79,0x10 }, + { 0xeb,0xe4,0x2a,0x22,0x5e,0x85,0x93,0xe4,0x48,0xd9,0xc5,0x45,0x73,0x81,0xaa,0xf7 }, +}; + +static const uint8_t WII_COMMON_KEY_DEFAULT[16] = { 0xeb,0xe4,0x2a,0x22,0x5e,0x85,0x93,0xe4, 0x48,0xd9,0xc5,0x45,0x73,0x81,0xaa,0xf7 }; @@ -30,47 +42,130 @@ static int hexnib(int c) { return -1; } -static void wii_common_key(uint8_t out[16]) { +static void wii_common_key_by_index(uint8_t idx, uint8_t out[16]) { + if (idx < 2) { + memcpy(out, WII_COMMON_KEYS[idx], 16); + } else { + memcpy(out, WII_COMMON_KEY_DEFAULT, 16); + } +} + +static int wii_boot_valid(const uint8_t* boot) { + return boot[0] >= 'A' && boot[0] <= 'Z' + && boot[1] >= 'A' && boot[1] <= 'Z' + && boot[2] >= 'A' && boot[2] <= 'Z'; +} + +static int wii_probe_boot(gc_read_fn raw, GCDisc* disc, uint32_t dataStart, + const uint8_t* titleKey, char preview[7]) { + uint8_t cluster[WII_CLUSTER]; + uint8_t plain[WII_CLUSTER_DATA]; + uint8_t iv[16]; + + if (raw(disc, dataStart, cluster, WII_CLUSTER) < 0) return -1; + memcpy(iv, cluster + 0x3D0, 16); + memcpy(plain, cluster + WII_CLUSTER_HASH, WII_CLUSTER_DATA); + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, titleKey, iv); + AES_CBC_decrypt_buffer(&ctx, plain, WII_CLUSTER_DATA); + memcpy(preview, plain, 6); + preview[6] = '\0'; + return wii_boot_valid(plain) ? 0 : 1; +} + +static int wii_find_title_key(GCDisc* disc, gc_read_fn raw, const uint8_t* ticket, + uint32_t dataStart, uint8_t out[16]) { const char* hex = getenv("SIPHON_WII_COMMON_KEY"); if (hex && strlen(hex) >= 32) { + uint8_t commonKey[16]; 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); + if (hi < 0 || lo < 0) break; + commonKey[i] = (uint8_t)((hi << 4) | lo); + } + if (hex[31]) { + uint8_t iv[16] = {0}; + 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); + return 0; } - 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); + + uint8_t order[3]; + int orderCount = 0; + uint8_t idx = ticket[0x1F1]; + if (idx < 2) order[orderCount++] = idx; + if (idx != 1) order[orderCount++] = 1; + if (idx != 0) order[orderCount++] = 0; + + for (int i = 0; i < orderCount; i++) { + uint8_t commonKey[16]; + char preview[7]; + wii_common_key_by_index(order[i], commonKey); + memcpy(out, ticket + 0x1BF, 16); + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, commonKey, iv); + AES_CBC_decrypt_buffer(&ctx, out, 16); + if (wii_probe_boot(raw, disc, dataStart, out, preview) == 0) + return 0; + } + + siphon_log("Wii: could not derive a valid title key"); + return -1; +} + +static void wii_decrypt_cluster(const uint8_t titleKey[16], +#if !defined(SIPHON_USE_COMMONCRYPTO) + struct AES_ctx* aes, +#endif + const uint8_t* enc, uint8_t* plain) { + uint8_t iv[16]; + memcpy(iv, enc + 0x3D0, 16); + +#if defined(SIPHON_USE_COMMONCRYPTO) + size_t outLen = 0; + CCStatus st = CCCrypt(kCCDecrypt, kCCAlgorithmAES128, 0, + titleKey, kCCKeySizeAES128, iv, + enc + WII_CLUSTER_HASH, WII_CLUSTER_DATA, + plain, WII_CLUSTER_DATA, &outLen); + if (st != kCCSuccess || outLen != WII_CLUSTER_DATA) { + siphon_log("Wii: CCCrypt failed status=%d outLen=%zu", (int)st, outLen); + memset(plain, 0, WII_CLUSTER_DATA); + } +#else + memcpy(plain, enc + WII_CLUSTER_HASH, WII_CLUSTER_DATA); + AES_ctx_set_iv(aes, iv); + AES_CBC_decrypt_buffer(aes, plain, WII_CLUSTER_DATA); +#endif } -static int wii_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { +static int wii_read(GCDisc* disc, uint64_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; + uint64_t cluster = offset / WII_CLUSTER_DATA; + uint32_t intra = (uint32_t)(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); + uint64_t phys = (uint64_t)w->dataStart + cluster * (uint64_t)WII_CLUSTER; + if (w->raw(disc, phys, w->clusterEnc, WII_CLUSTER) < 0) { + siphon_log("Wii: cluster read failed phys=0x%llX cluster=%llu", + (unsigned long long)phys, (unsigned long long)cluster); + return -1; + } + + wii_decrypt_cluster(w->titleKey, +#if !defined(SIPHON_USE_COMMONCRYPTO) + &w->aes, +#endif + w->clusterEnc, w->clusterPlain); w->cachedCluster = cluster; } @@ -78,22 +173,31 @@ static int wii_read(GCDisc* disc, uint32_t offset, void* buf, size_t size) { if (chunk > size) chunk = size; memcpy(out, w->clusterPlain + intra, chunk); out += chunk; - offset += (uint32_t)chunk; + offset += chunk; size -= chunk; } return 0; } int gc_wii_wrap(GCDisc* disc) { - if (disc->format == GC_FORMAT_WIA || disc->format == GC_FORMAT_RVZ) return 0; + 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)) + if (disc->read(disc, 0x18, magic, 4) < 0) { + siphon_log("Wii: failed to read disc magic at 0x18"); + 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; + if (disc->read(disc, 0x40000, grp, sizeof(grp)) < 0) { + siphon_log("Wii: failed to read partition group table at 0x40000"); + return -1; + } uint32_t partOff = 0; int found = 0; @@ -103,28 +207,49 @@ int gc_wii_wrap(GCDisc* disc) { 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; + if (disc->read(disc, infoOff + p*8, pe, 8) < 0) { + siphon_log("Wii: failed reading partition entry g=%d p=%u", g, p); + 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; } + if (!found) { + siphon_log("Wii: no type-0 data partition found"); + return -1; + } uint8_t ticket[0x2A4]; - if (disc->read(disc, partOff, ticket, sizeof(ticket)) < 0) return -1; + if (disc->read(disc, partOff, ticket, sizeof(ticket)) < 0) { + siphon_log("Wii: failed to read ticket at partition base"); + 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; + uint8_t dataOffRaw[4]; + if (disc->read(disc, partOff + 0x2B8, dataOffRaw, 4) < 0) { + siphon_log("Wii: failed to read data offset at partition+0x2B8"); + free(w); + return -1; + } + uint32_t dataOff = gc_be32(dataOffRaw) << 2; w->dataStart = partOff + dataOff; - w->cachedCluster = UINT32_MAX; + w->cachedCluster = (uint64_t)-1; w->raw = disc->read; + if (wii_find_title_key(disc, disc->read, ticket, w->dataStart, w->titleKey) != 0) { + free(w); + return -1; + } + +#if !defined(SIPHON_USE_COMMONCRYPTO) + AES_init_ctx_key(&w->aes, w->titleKey); +#endif + disc->wii = w; disc->read = wii_read; disc->offsetShift = 2;