/** * Copyright (c) 2026 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "assetdolphindvd.h" #include "asset/asset.h" #include "util/string.h" #include "util/memory.h" #define ISO_SECTOR_SIZE 2048u #define ISO_PVD_SECTOR 16u errorret_t assetInitDolphinDVD(void) { DVD_Init(); DVD_Mount(); // ISO 9660 Primary Volume Descriptor is at sector 16. u8 *pvd = (u8 *)assetDolphinDVDRead( (s64)ISO_PVD_SECTOR * ISO_SECTOR_SIZE, ISO_SECTOR_SIZE ); if(!pvd) errorThrow("Failed to read ISO 9660 PVD."); // Sanity-check: type=1, identifier="CD001" if(pvd[0] != 1 || pvd[1] != 'C' || pvd[2] != 'D' || pvd[3] != '0' || pvd[4] != '0' || pvd[5] != '1') { memoryFree(pvd); errorThrow("Not a valid ISO 9660 disc."); } // Root Directory Record starts at PVD+156. // ISO 9660 stores multi-byte fields in both byte orders; use the BE copies // (offset +6 for LBA, +14 for size) since the GameCube is big-endian. u32 rootLBA = assetDolphinDVDReadBigEndian32(pvd + 156 + 6); u32 rootSize = assetDolphinDVDReadBigEndian32(pvd + 156 + 14); memoryFree(pvd); u8 *dir = (u8 *)assetDolphinDVDRead( (s64)rootLBA * ISO_SECTOR_SIZE, rootSize ); if(!dir) errorThrow("Failed to read ISO 9660 root directory."); // Scan directory records for dusk.dsk. // ISO 9660 level-1 names are uppercase with a ";1" version suffix, e.g. // "DUSK.DSK;1". We strip the suffix before comparing case-insensitively. u32 fileLBA = 0, fileSize = 0; u32 pos = 0; while(pos < rootSize) { u8 recLen = dir[pos]; if(recLen == 0) { // Sector padding — skip to the start of the next sector. pos = (pos + (ISO_SECTOR_SIZE - 1u)) & ~(ISO_SECTOR_SIZE - 1u); continue; } u8 flags = dir[pos + 25]; u8 nameLen = dir[pos + 32]; if(!(flags & 0x02) && nameLen > 1) { // skip directories and "." / ".." const char_t *isoName = (const char_t *)(dir + pos + 33); // Build a null-terminated copy of the base name (strip ";N" version). char_t baseName[32]; u8 baseLen = 0; while(baseLen < nameLen && isoName[baseLen] != ';' && baseLen < 31) { baseName[baseLen] = isoName[baseLen]; baseLen++; } baseName[baseLen] = '\0'; if(stringCompareInsensitive(baseName, ASSET_FILE_NAME) == 0) { fileLBA = assetDolphinDVDReadBigEndian32(dir + pos + 6); fileSize = assetDolphinDVDReadBigEndian32(dir + pos + 14); break; } } pos += recLen; } memoryFree(dir); if(!fileLBA) errorThrow("Failed to find asset file on ISO."); u8 *data = (u8 *)assetDolphinDVDRead( (s64)fileLBA * ISO_SECTOR_SIZE, fileSize ); if(!data) errorThrow("Failed to read asset file from ISO."); zip_error_t zerr; zip_source_t *src = zip_source_buffer_create(data, fileSize, 1, &zerr); if(!src) { memoryFree(data); errorThrow("Failed to create zip source from DVD buffer."); } ASSET.zip = zip_open_from_source(src, ZIP_RDONLY, &zerr); if(!ASSET.zip) { zip_source_free(src); errorThrow("Failed to open asset zip from DVD."); } errorOk(); } void *assetDolphinDVDRead(const s64 offset, const u32 size) { u32 padded = ASSET_DOLPHIN_DVD_ALIGN_UP(size); void *buf = memoryAlign(ASSET_DOLPHIN_DVD_ALIGN, padded); if(!buf) return NULL; DCInvalidateRange(buf, padded); static dvdcmdblk block; if(DVD_ReadAbs(&block, buf, padded, offset) <= 0) { memoryFree(buf); return NULL; } return buf; } u32 assetDolphinDVDReadBigEndian32(const u8 *p) { return ((u32)p[0] << 24) | ((u32)p[1] << 16) | ((u32)p[2] << 8) | (u32)p[3]; } errorret_t assetDisposeDolphinDVD(void) { errorOk(); }