Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})
Expand Down
2 changes: 2 additions & 0 deletions include/siphon.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 12 additions & 1 deletion src/disc/aes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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];
}
Expand Down
2 changes: 2 additions & 0 deletions src/disc/aes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions src/disc/gc_ciso.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
14 changes: 14 additions & 0 deletions src/disc/gc_disc.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "gc_disc_internal.h"
#include "siphon_log.h"
#include <stdlib.h>
#include <string.h>

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
63 changes: 53 additions & 10 deletions src/disc/gc_disc_fst.c
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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);
Expand Down Expand Up @@ -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++) {
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion src/disc/gc_disc_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include "gc_disc.h"
#include <stdio.h>

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 {
Expand Down
4 changes: 2 additions & 2 deletions src/disc/gc_gcz.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
5 changes: 3 additions & 2 deletions src/disc/gc_iso.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
#include "gc_disc_internal.h"
#include <stdio.h>
#include <string.h>

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;
}
Expand Down
56 changes: 33 additions & 23 deletions src/disc/gc_wbfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,39 @@
#include <stdlib.h>
#include <string.h>

#define GC_DISC_SIZE 0x57058000u
#define GC_DISC_SIZE 0x57058000u
#define WII_DISC_SIZE 0x118580000u

typedef struct {
uint32_t wbfsSectorSize;
uint16_t* wlbaTable;
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;

if (blockIdx >= wb->wlbaCount || wb->wlbaTable[blockIdx] == 0) {
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;
}

Expand All @@ -51,32 +55,47 @@ 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;
}

uint8_t hdShift = hdr[8];
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;
}
Expand All @@ -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;
}

Expand All @@ -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;
}
Loading
Loading