Redesign dusk.dsk into a dual-archive DSK2 format

Splits the asset archive into a compressed (DEFLATE) zip and an
uncompressed (STORED) zip back to back behind a small header, instead of
one plain zip. DEFLATE-compressed zip entries aren't reliably seekable in
libzip, which caused locale string lookups (repeated rewind/reopen of the
same entry) to silently skip content on Dolphin specifically. Uncompressed
entries don't have that problem, so locale files now go in the stored
archive without needing to buffer the whole thing in memory.

Adds tools/asset/pack as the new packer (replacing the plain
`tar --format=zip`), a shared assetdsk.h/.c opener used by all platforms,
and a second ASSET.zipStored handle with fallback lookup in assetfile.c.

Confirmed working: Linux, PSP (PPSSPP), and Dolphin-FAT (Wii DOL under
-a LLE) - the original Dolphin locale lookup failure no longer reproduces.
This commit is contained in:
2026-08-31 15:33:45 -05:00
parent 9512c22e1f
commit e47a2c3e5c
17 changed files with 585 additions and 53 deletions
+7
View File
@@ -14,6 +14,13 @@ if(NOT libzip_FOUND)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC zip)
endif()
# assetdsk.c calls crc32() directly (to verify dusk.dsk archive checksums).
# find_package(libzip) above already resolves ZLIB as a side effect, so
# ZLIB_FOUND may already be true without ZLIB::ZLIB having been linked to
# our target - link it unconditionally rather than guarding on ZLIB_FOUND.
find_package(ZLIB REQUIRED)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC ZLIB::ZLIB)
if(NOT stb_image_FOUND)
find_package(stb REQUIRED)
if(STB_IMAGE_FOUND)
+1
View File
@@ -7,6 +7,7 @@
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
asset.c
assetdsk.c
assetfile.c
)
+14 -6
View File
@@ -23,9 +23,11 @@ errorret_t assetInit(void) {
threadMutexInit(&ASSET.loading[i].mutex);
}
// assetInitPlatform must either define ASSET.zip or throw an error.
// assetInitPlatform must either define both ASSET.zip/ASSET.zipStored or
// throw an error.
errorChain(assetInitPlatform());
assertNotNull(ASSET.zip, "Asset zip null without error.");
assertNotNull(ASSET.zipStored, "Asset stored zip null without error.");
threadInit(&ASSET.loadThread, assetUpdateAsync);
threadStart(&ASSET.loadThread);
@@ -35,9 +37,9 @@ errorret_t assetInit(void) {
bool_t assetFileExists(const char_t *filename) {
assertStrLenMax(filename, ASSET_FILE_NAME_MAX, "Filename too long.");
zip_int64_t idx = zip_name_locate(ASSET.zip, filename, 0);
if(idx < 0) return false;
return true;
if(zip_name_locate(ASSET.zip, filename, 0) >= 0) return true;
if(zip_name_locate(ASSET.zipStored, filename, 0) >= 0) return true;
return false;
}
assetentry_t * assetGetEntry(
@@ -427,13 +429,19 @@ errorret_t assetDispose(void) {
errorChain(assetReapUnused());
// Cleanup zip file.
// Cleanup zip files.
if(ASSET.zip != NULL) {
if(zip_close(ASSET.zip) != 0) {
errorThrow("Failed to close asset zip archive.");
errorThrow("Failed to close compressed asset zip archive.");
}
ASSET.zip = NULL;
}
if(ASSET.zipStored != NULL) {
if(zip_close(ASSET.zipStored) != 0) {
errorThrow("Failed to close stored asset zip archive.");
}
ASSET.zipStored = NULL;
}
errorChain(assetDisposePlatform());
errorOk();
+9
View File
@@ -27,7 +27,16 @@
#define ASSET_ENTRY_COUNT_MAX 64
typedef struct asset_s {
// Compressed (DEFLATE) archive - expected to hold the bulk of a game's
// binary assets. Looked up first by assetFileInit().
zip_t *zip;
// Stored (uncompressed) archive - expected to hold small files that need
// reliable repeated seeking/re-opening (e.g. locale strings), which
// libzip only supports reliably for uncompressed entries. Looked up by
// assetFileInit() only if the name isn't found in `zip`.
zip_t *zipStored;
assetplatform_t platform;
// Background loading thread.
+241
View File
@@ -0,0 +1,241 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "assetdsk.h"
#include "util/memory.h"
#include "util/endian.h"
#include "assert/assert.h"
#include <zlib.h>
errorret_t assetDskParseHeader(
const uint8_t *bytes,
const size_t bytesSize,
assetdskheader_t *outHeader
) {
assertNotNull(bytes, "Bytes cannot be NULL.");
assertNotNull(outHeader, "Out header cannot be NULL.");
if(bytesSize < ASSET_DSK_HEADER_SIZE) {
errorThrow("dusk.dsk header is truncated.");
}
if(memoryCompare(bytes, ASSET_DSK_MAGIC, ASSET_DSK_MAGIC_SIZE) != 0) {
errorThrow("dusk.dsk has an invalid magic header.");
}
// Every field is a little-endian uint32_t regardless of host - convert
// to host order (a no-op on little-endian hosts, a real byteswap on
// Dolphin's big-endian PowerPC).
uint32_t fields[7];
memoryCopy(fields, bytes + ASSET_DSK_MAGIC_SIZE, sizeof(fields));
for(uint8_t i = 0; i < 7; i++) {
fields[i] = endianLittleToHost32(fields[i]);
}
const uint32_t version = fields[0];
if(version != ASSET_DSK_VERSION) {
errorThrow("dusk.dsk has an unsupported version: %u", version);
}
outHeader->compressedOffset = fields[1];
outHeader->compressedSize = fields[2];
outHeader->compressedChecksum = fields[3];
outHeader->storedOffset = fields[4];
outHeader->storedSize = fields[5];
outHeader->storedChecksum = fields[6];
errorOk();
}
errorret_t assetDskOpenFromPath(
const char_t *path,
zip_t **outCompressed,
zip_t **outStored
) {
assertNotNull(path, "Path cannot be NULL.");
assertNotNull(outCompressed, "Out compressed cannot be NULL.");
assertNotNull(outStored, "Out stored cannot be NULL.");
*outCompressed = NULL;
*outStored = NULL;
FILE *headerFile = fopen(path, "rb");
if(headerFile == NULL) {
errorThrow("Failed to open dusk.dsk: %s", path);
}
uint8_t headerBytes[ASSET_DSK_HEADER_SIZE];
size_t headerRead = fread(headerBytes, 1, sizeof(headerBytes), headerFile);
fclose(headerFile);
if(headerRead != sizeof(headerBytes)) {
errorThrow("Failed to read dusk.dsk header: %s", path);
}
assetdskheader_t header;
errorChain(assetDskParseHeader(headerBytes, sizeof(headerBytes), &header));
zip_error_t zipError;
zip_error_init(&zipError);
zip_source_t *compressedSource = zip_source_file_create(
path, header.compressedOffset, (zip_int64_t) header.compressedSize, &zipError
);
if(compressedSource == NULL) {
errorThrow(
"Failed to create compressed dusk.dsk source: %s", zip_error_strerror(&zipError)
);
}
*outCompressed = zip_open_from_source(compressedSource, ZIP_RDONLY, &zipError);
if(*outCompressed == NULL) {
zip_source_free(compressedSource);
errorThrow(
"Failed to open compressed dusk.dsk archive: %s", zip_error_strerror(&zipError)
);
}
zip_source_t *storedSource = zip_source_file_create(
path, header.storedOffset, (zip_int64_t) header.storedSize, &zipError
);
if(storedSource == NULL) {
zip_close(*outCompressed);
*outCompressed = NULL;
errorThrow(
"Failed to create stored dusk.dsk source: %s", zip_error_strerror(&zipError)
);
}
*outStored = zip_open_from_source(storedSource, ZIP_RDONLY, &zipError);
if(*outStored == NULL) {
zip_source_free(storedSource);
zip_close(*outCompressed);
*outCompressed = NULL;
errorThrow(
"Failed to open stored dusk.dsk archive: %s", zip_error_strerror(&zipError)
);
}
// The stored archive is small by convention, so verifying its checksum
// here (one extra small read) is cheap; the compressed archive isn't
// checked since it's meant to be read lazily/on-demand from here on.
uint8_t *storedBytes = (uint8_t *) memoryAllocate(header.storedSize);
FILE *storedFile = fopen(path, "rb");
if(storedFile == NULL) {
memoryFree(storedBytes);
zip_close(*outStored);
zip_close(*outCompressed);
*outStored = NULL;
*outCompressed = NULL;
errorThrow("Failed to re-open dusk.dsk to verify stored checksum: %s", path);
}
fseek(storedFile, (long) header.storedOffset, SEEK_SET);
size_t storedRead = fread(storedBytes, 1, header.storedSize, storedFile);
fclose(storedFile);
if(storedRead != header.storedSize) {
memoryFree(storedBytes);
zip_close(*outStored);
zip_close(*outCompressed);
*outStored = NULL;
*outCompressed = NULL;
errorThrow("Failed to read dusk.dsk stored archive to verify checksum: %s", path);
}
uint32_t checksum = (uint32_t) crc32(0L, storedBytes, (uInt) header.storedSize);
memoryFree(storedBytes);
if(checksum != header.storedChecksum) {
zip_close(*outStored);
zip_close(*outCompressed);
*outStored = NULL;
*outCompressed = NULL;
errorThrow("dusk.dsk stored archive failed checksum verification: %s", path);
}
errorOk();
}
errorret_t assetDskOpenFromBuffer(
uint8_t *buffer,
const size_t bufferSize,
zip_t **outCompressed,
zip_t **outStored
) {
assertNotNull(buffer, "Buffer cannot be NULL.");
assertNotNull(outCompressed, "Out compressed cannot be NULL.");
assertNotNull(outStored, "Out stored cannot be NULL.");
*outCompressed = NULL;
*outStored = NULL;
assetdskheader_t header;
errorChain(assetDskParseHeader(buffer, bufferSize, &header));
if(
(size_t) header.compressedOffset + header.compressedSize > bufferSize ||
(size_t) header.storedOffset + header.storedSize > bufferSize
) {
errorThrow("dusk.dsk header describes ranges beyond the buffer.");
}
uint32_t compressedChecksum = (uint32_t) crc32(
0L, buffer + header.compressedOffset, (uInt) header.compressedSize
);
if(compressedChecksum != header.compressedChecksum) {
errorThrow("dusk.dsk compressed archive failed checksum verification.");
}
uint32_t storedChecksum = (uint32_t) crc32(
0L, buffer + header.storedOffset, (uInt) header.storedSize
);
if(storedChecksum != header.storedChecksum) {
errorThrow("dusk.dsk stored archive failed checksum verification.");
}
zip_error_t zipError;
zip_error_init(&zipError);
// freep=0 for both - they're non-owning windows into the same caller-owned
// buffer, not independent allocations libzip should free.
zip_source_t *compressedSource = zip_source_buffer_create(
buffer + header.compressedOffset, header.compressedSize, 0, &zipError
);
if(compressedSource == NULL) {
errorThrow(
"Failed to create compressed dusk.dsk source: %s", zip_error_strerror(&zipError)
);
}
*outCompressed = zip_open_from_source(compressedSource, ZIP_RDONLY, &zipError);
if(*outCompressed == NULL) {
zip_source_free(compressedSource);
errorThrow(
"Failed to open compressed dusk.dsk archive: %s", zip_error_strerror(&zipError)
);
}
zip_source_t *storedSource = zip_source_buffer_create(
buffer + header.storedOffset, header.storedSize, 0, &zipError
);
if(storedSource == NULL) {
zip_close(*outCompressed);
*outCompressed = NULL;
errorThrow(
"Failed to create stored dusk.dsk source: %s", zip_error_strerror(&zipError)
);
}
*outStored = zip_open_from_source(storedSource, ZIP_RDONLY, &zipError);
if(*outStored == NULL) {
zip_source_free(storedSource);
zip_close(*outCompressed);
*outCompressed = NULL;
errorThrow(
"Failed to open stored dusk.dsk archive: %s", zip_error_strerror(&zipError)
);
}
errorOk();
}
+101
View File
@@ -0,0 +1,101 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include <zip.h>
// "DSK2" - distinct from a plain zip's "PK\x03\x04" so a stray plain zip
// never gets misread as a valid dusk.dsk.
#define ASSET_DSK_MAGIC_SIZE 4
#define ASSET_DSK_MAGIC "DSK2"
#define ASSET_DSK_VERSION 1
// magic(4) + version(4) + compressedOffset(4) + compressedSize(4) +
// compressedChecksum(4) + storedOffset(4) + storedSize(4) +
// storedChecksum(4), all little-endian regardless of host - see
// assetDskParseHeader.
#define ASSET_DSK_HEADER_SIZE 32
/**
* Parsed dusk.dsk (DSK2 format) header. dusk.dsk is two independent, back
* to back zip archives (see tools/asset/pack) rather than a single plain
* zip: a "compressed" one (DEFLATE, expected to hold the bulk of a game's
* binary assets, opened lazily/on-demand) and a "stored" one (uncompressed,
* expected to hold small files - locale strings, config - that need
* reliable repeated seeking/re-opening, which libzip only supports for
* uncompressed entries).
*/
typedef struct {
uint32_t compressedOffset;
uint32_t compressedSize;
uint32_t compressedChecksum;
uint32_t storedOffset;
uint32_t storedSize;
uint32_t storedChecksum;
} assetdskheader_t;
/**
* Parses a DSK2 header from a raw byte buffer (at least
* ASSET_DSK_HEADER_SIZE bytes), validating the magic/version and
* byte-swapping the little-endian fields to host order.
*
* @param bytes Buffer containing the header (and beyond).
* @param bytesSize Number of bytes available at `bytes`.
* @param outHeader Filled with the parsed header on success.
* @return OK on success, error if too short, bad magic, or unsupported version.
*/
errorret_t assetDskParseHeader(
const uint8_t *bytes,
const size_t bytesSize,
assetdskheader_t *outHeader
);
/**
* Opens both archives of a dusk.dsk file given a filesystem path, using
* lazy/windowed file-backed zip sources - no more memory used than the
* existing per-platform small read buffers, matching the memory
* characteristics of a plain zip_open() on the whole file. Verifies the
* (small, by convention) stored archive's checksum; the compressed
* archive's checksum is intentionally not verified here since doing so
* would require reading the bulk of the game's assets just to compute it.
*
* @param path Filesystem path to the dusk.dsk file.
* @param outCompressed Set to the opened compressed archive on success.
* @param outStored Set to the opened stored archive on success.
* @return OK on success, error if the file is missing, too short, has a
* bad header, or either archive fails to open/verify.
*/
errorret_t assetDskOpenFromPath(
const char_t *path,
zip_t **outCompressed,
zip_t **outStored
);
/**
* Opens both archives of a dusk.dsk file already fully resident in memory
* (e.g. a PSAR embedded in an EBOOT.PBP, or an ISO-embedded file already
* read via DVD_ReadAbs - platforms that already buffer the whole file for
* reasons unrelated to this format). Neither archive takes ownership of
* `buffer` (both are opened as non-owning sub-ranges of it) - the caller
* remains responsible for freeing it, and must keep it alive for as long
* as either archive stays open. Verifies both archives' checksums, since
* the bytes are already resident.
*
* @param buffer The whole dusk.dsk file's bytes.
* @param bufferSize Number of bytes at `buffer`.
* @param outCompressed Set to the opened compressed archive on success.
* @param outStored Set to the opened stored archive on success.
* @return OK on success, error if too short, has a bad header/checksum, or
* either archive fails to open.
*/
errorret_t assetDskOpenFromBuffer(
uint8_t *buffer,
const size_t bufferSize,
zip_t **outCompressed,
zip_t **outStored
);
+25 -4
View File
@@ -24,9 +24,15 @@ errorret_t assetFileInit(
file->params = params;
file->output = output;
// Stat the file
// Stat the file, trying the compressed archive first and falling back to
// the stored one - remember which matched so assetFileOpen opens it from
// the right archive.
zip_stat_init(&file->stat);
if(!zip_stat(ASSET.zip, filename, 0, &file->stat) == 0) {
if(zip_stat(ASSET.zip, filename, 0, &file->stat) == 0) {
file->sourceZip = ASSET.zip;
} else if(zip_stat(ASSET.zipStored, filename, 0, &file->stat) == 0) {
file->sourceZip = ASSET.zipStored;
} else {
errorThrow("Failed to stat asset file: %s", filename);
}
@@ -47,6 +53,21 @@ errorret_t assetFileRewind(assetfile_t *file) {
errorOk();
}
// Prefer seeking within the still-open handle over closing and
// re-opening it. Repeatedly closing/re-opening the same compressed zip
// entry (once per rewind - e.g. once per locale string lookup) was
// confirmed unreliable on at least one platform's zip backend, silently
// skipping ranges of the decompressed content on some re-opens after
// the first. A real seek avoids that failure mode entirely, with no
// extra memory cost over the close+reopen fallback.
if(zip_file_is_seekable(file->zipFile)) {
if(zip_fseek(file->zipFile, 0, SEEK_SET) != 0) {
errorThrow("Failed to seek asset file: %s", file->filename);
}
file->position = 0;
errorOk();
}
errorChain(assetFileClose(file));
errorChain(assetFileOpen(file));
errorOk();
@@ -55,10 +76,10 @@ errorret_t assetFileRewind(assetfile_t *file) {
errorret_t assetFileOpen(assetfile_t *file) {
assertNotNull(file, "Asset file cannot be NULL.");
assertNotNull(file->filename, "Asset file filename cannot be NULL.");
assertNotNull(ASSET.zip, "Asset zip cannot be NULL.");
assertNotNull(file->sourceZip, "Asset file must be inited before opening.");
assertNull(file->zipFile, "Asset file already open.");
file->zipFile = zip_fopen(ASSET.zip, file->filename, 0);
file->zipFile = zip_fopen(file->sourceZip, file->filename, 0);
if(file->zipFile == NULL) {
errorThrow("Failed to open asset file: %s", file->filename);
}
+5
View File
@@ -32,6 +32,11 @@ typedef struct assetfile_s {
zip_int64_t position;
zip_int64_t lastRead;
zip_file_t *zipFile;
// The archive this file was found in (ASSET.zip or ASSET.zipStored),
// set by assetFileInit and used by assetFileOpen so lookups fall back
// correctly between the two dusk.dsk archives.
zip_t *sourceZip;
} assetfile_t;
/**
+11 -10
View File
@@ -7,6 +7,7 @@
#include "assetdolphindvd.h"
#include "asset/asset.h"
#include "asset/assetdsk.h"
#include "util/string.h"
#include "util/memory.h"
@@ -90,19 +91,15 @@ errorret_t assetInitDolphinDVD(void) {
);
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) {
errorret_t ret = assetDskOpenFromBuffer(
data, fileSize, &ASSET.zip, &ASSET.zipStored
);
if(errorIsNotOk(ret)) {
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.");
errorChain(ret);
}
ASSET.platform.dskData = data;
errorOk();
}
@@ -124,5 +121,9 @@ u32 assetDolphinDVDReadBigEndian32(const u8 *p) {
}
errorret_t assetDisposeDolphinDVD(void) {
if(ASSET.platform.dskData != NULL) {
memoryFree(ASSET.platform.dskData);
ASSET.platform.dskData = NULL;
}
errorOk();
}
+4 -1
View File
@@ -24,7 +24,10 @@
(((u32)(n) + ASSET_DOLPHIN_DVD_ALIGN - 1u) & ~(ASSET_DOLPHIN_DVD_ALIGN - 1u))
typedef struct {
uint8_t nothing;
// Whole dusk.dsk blob read from the ISO, kept alive for as long as
// ASSET.zip/ASSET.zipStored are open since they're non-owning windows
// into it (see assetDskOpenFromBuffer). Freed in assetDisposeDolphinDVD.
uint8_t *dskData;
} assetdolphindvd_t;
/**
+2 -4
View File
@@ -7,6 +7,7 @@
#include "assetdolphinfat.h"
#include "asset/asset.h"
#include "asset/assetdsk.h"
#include "util/string.h"
#include <fat.h>
#include <stdio.h>
@@ -50,10 +51,7 @@ errorret_t assetInitDolphinFAT(void) {
if(foundPath[0] == '\0')
errorThrow("Failed to find asset file on FAT filesystem.");
ASSET.zip = zip_open(foundPath, ZIP_RDONLY, NULL);
if(ASSET.zip == NULL)
errorThrow("Failed to open asset file on FAT filesystem.");
errorChain(assetDskOpenFromPath(foundPath, &ASSET.zip, &ASSET.zipStored));
errorOk();
}
+9 -7
View File
@@ -6,6 +6,7 @@
*/
#include "asset/asset.h"
#include "asset/assetdsk.h"
#include "engine/engine.h"
#include "util/string.h"
#include "assert/assert.h"
@@ -45,10 +46,10 @@ errorret_t assetInitLinux(void) {
stringCopy(ASSET.platform.systemPath, ".", ASSET_SYSTEM_PATH_MAX);
}
// Open zip file
// Open dusk.dsk
char_t searchPath[ASSET_SYSTEM_PATH_MAX];
const char_t **path = ASSET_LINUX_SEARCH_PATHS;
int32_t error;
bool_t opened = false;
do {
char_t temp[ASSET_SYSTEM_PATH_MAX];
snprintf(
@@ -75,17 +76,18 @@ errorret_t assetInitLinux(void) {
printf("Try open asset file: %s\n", searchPath);
// Try open
error = 0;
ASSET.zip = zip_open(searchPath, ZIP_RDONLY, &error);
if(ASSET.zip == NULL) {
printf("Opened asset file with non-zero error code: %d\n", error);
if(errorIsNotOk(
assetDskOpenFromPath(searchPath, &ASSET.zip, &ASSET.zipStored)
)) {
printf("Failed to open asset file: %s\n", searchPath);
continue;
}
opened = true;
break;// Found!
} while(*(++path) != NULL);
// Did we open the asset?
if(ASSET.zip == NULL) {
if(!opened) {
errorThrow("Failed to open asset file.");
}
+11 -14
View File
@@ -6,6 +6,7 @@
*/
#include "asset/asset.h"
#include "asset/assetdsk.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "util/math.h"
@@ -109,24 +110,15 @@ errorret_t assetInitPBP(const char_t *pbpPath) {
fclose(ASSET.platform.pbpFile);
ASSET.platform.pbpFile = NULL;
zip_source_t *psarSource = zip_source_buffer_create(
psarData, (zip_uint64_t)psarSize, 1, NULL
errorret_t ret = assetDskOpenFromBuffer(
psarData, psarSize, &ASSET.zip, &ASSET.zipStored
);
if(psarSource == NULL) {
if(errorIsNotOk(ret)) {
free(psarData);
errorThrow("Failed to create zip source in PBP file: %s", pbpPath);
}
ASSET.zip = zip_open_from_source(
psarSource,
ZIP_RDONLY,
NULL
);
if(ASSET.zip == NULL) {
zip_source_free(psarSource);
errorThrow("Failed to open zip from PBP file: %s", pbpPath);
errorChain(ret);
}
ASSET.platform.dskData = psarData;
errorOk();
}
@@ -136,5 +128,10 @@ errorret_t assetDisposePBP(void) {
ASSET.platform.pbpFile = NULL;
}
if(ASSET.platform.dskData != NULL) {
free(ASSET.platform.dskData);
ASSET.platform.dskData = NULL;
}
errorOk();
}
+5
View File
@@ -30,6 +30,11 @@ typedef struct {
typedef struct {
FILE *pbpFile;
assetpbpheader_t pbpHeader;
// Whole dusk.dsk (PSAR) blob, kept alive for as long as ASSET.zip/
// ASSET.zipStored are open since they're non-owning windows into it (see
// assetDskOpenFromBuffer). Freed in assetDisposePBP.
uint8_t *dskData;
} assetpbp_t;
/**
+4 -5
View File
@@ -6,14 +6,13 @@
*/
#include "asset/asset.h"
#include "asset/assetdsk.h"
#include "assert/assert.h"
errorret_t assetInitVita(void) {
int32_t error;
ASSET.zip = zip_open(ASSET_VITA_DSK_PATH, ZIP_RDONLY, &error);
if(ASSET.zip == NULL) {
errorThrow("Failed to open asset file: " ASSET_VITA_DSK_PATH);
}
errorChain(
assetDskOpenFromPath(ASSET_VITA_DSK_PATH, &ASSET.zip, &ASSET.zipStored)
);
errorOk();
}