diff --git a/cmake/targets/psp.cmake b/cmake/targets/psp.cmake index 32a2b5a5..13b9bb85 100644 --- a/cmake/targets/psp.cmake +++ b/cmake/targets/psp.cmake @@ -65,6 +65,23 @@ if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") ) endif() +# Generate PARAM.SFO as a normal tracked build output (instead of letting +# create_pbp_file() auto-generate + delete it) so it can be reused below by +# a properly dependency-tracked EBOOT.PBP repack step. +set(DUSK_PSP_PARAM_SFO "${DUSK_BUILD_DIR}/PARAM.SFO") +add_custom_command( + OUTPUT "${DUSK_PSP_PARAM_SFO}" + COMMAND "$ENV{PSPDEV}/bin/mksfoex" "-d" "MEMSIZE=1" "-s" "APP_VER=01.00" + "${DUSK_BINARY_TARGET_NAME}" "${DUSK_PSP_PARAM_SFO}" + COMMENT "Generating PARAM.SFO for ${DUSK_BINARY_TARGET_NAME}" + VERBATIM +) +add_custom_target(DuskPspParamSfo DEPENDS "${DUSK_PSP_PARAM_SFO}") + +# create_pbp_file()'s own POST_BUILD chain (below) also consumes +# DUSK_PSP_PARAM_SFO, so make sure it exists before that chain runs. +add_dependencies(${DUSK_BINARY_TARGET_NAME} DuskPspParamSfo) + # Postbuild, create .pbp file for PSP. create_pbp_file( TARGET "${DUSK_BINARY_TARGET_NAME}" @@ -74,4 +91,33 @@ create_pbp_file( TITLE "${DUSK_BINARY_TARGET_NAME}" PSAR_PATH ${DUSK_ASSETS_ZIP} VERSION 01.00 -) \ No newline at end of file + SFO_PATH "${DUSK_PSP_PARAM_SFO}" + OUTPUT_DIR "${DUSK_BUILD_DIR}" +) + +# CreatePBP.cmake's pack-pbp step is a POST_BUILD command tied to the +# executable target, so it only reruns when the ELF itself relinks. That +# means regenerating dusk.dsk (assets) alone, without touching any C +# source, silently leaves EBOOT.PBP embedding a stale asset pak. Repack it +# here as a normal file-tracked custom command depending on both the +# executable and the asset zip, so EBOOT.PBP always reflects the current +# assets even when nothing else about the build changed. +set(DUSK_PSP_EBOOT "${DUSK_BUILD_DIR}/EBOOT.PBP") +if(BUILD_PRX) + set(DUSK_PSP_EXECUTABLE "$.prx") +else() + set(DUSK_PSP_EXECUTABLE "$") +endif() +add_custom_command( + OUTPUT "${DUSK_PSP_EBOOT}" + COMMAND "$ENV{PSPDEV}/bin/pack-pbp" "${DUSK_PSP_EBOOT}" "${DUSK_PSP_PARAM_SFO}" + "NULL" "NULL" "NULL" "NULL" "NULL" + "${DUSK_PSP_EXECUTABLE}" "${DUSK_ASSETS_ZIP}" + DEPENDS + "$" + "${DUSK_PSP_PARAM_SFO}" + "${DUSK_ASSETS_ZIP}" + COMMENT "Repacking EBOOT.PBP (tracks executable + asset pak freshness)" + VERBATIM +) +add_custom_target(DuskPspEbootRepack ALL DEPENDS "${DUSK_PSP_EBOOT}") \ No newline at end of file diff --git a/src/dusk/asset/assetfile.c b/src/dusk/asset/assetfile.c index a348eb0d..fafc8028 100644 --- a/src/dusk/asset/assetfile.c +++ b/src/dusk/asset/assetfile.c @@ -87,13 +87,30 @@ errorret_t assetFileRead( errorOk(); } - // I assume zip_fread takes buffer NULL for skipping? - zip_int64_t bytesRead = zip_fread(file->zipFile, buffer, bufferSize); - if(bytesRead < 0) { - errorThrow("Failed to read from asset file: %s", file->filename); + // Some zip_fread() implementations (seen on PSP) reject a single call + // asking for the entire (potentially large) file at once with EINVAL; + // the line reader above only ever asks for up to 1024 bytes per call and + // works fine, so read in bounded chunks here too. + size_t totalRead = 0; + uint8_t *dest = (uint8_t *)buffer; + while(totalRead < bufferSize) { + size_t chunkSize = mathMin( + bufferSize - totalRead, ASSET_FILE_READ_CHUNK_MAX + ); + zip_int64_t bytesRead = zip_fread( + file->zipFile, dest + totalRead, chunkSize + ); + if(bytesRead < 0) { + errorThrow( + "Failed to read from asset file: %s (%s)", + file->filename, zip_file_strerror(file->zipFile) + ); + } + if(bytesRead == 0) break; + totalRead += (size_t)bytesRead; } - file->position += bytesRead; - file->lastRead = bytesRead; + file->position += totalRead; + file->lastRead = totalRead; errorOk(); } diff --git a/src/dusk/asset/assetfile.h b/src/dusk/asset/assetfile.h index 806b9cb9..679baac5 100644 --- a/src/dusk/asset/assetfile.h +++ b/src/dusk/asset/assetfile.h @@ -11,6 +11,12 @@ #define ASSET_FILE_NAME_MAX 48 +// Max bytes requested per zip_fread() call in assetFileRead(). Some +// zip_fread() implementations (seen on PSP) reject a single call asking +// for very large amounts of data at once; the locale line reader has +// always used 1024-byte reads successfully, so that's the proven-safe cap. +#define ASSET_FILE_READ_CHUNK_MAX 1024 + typedef struct assetfile_s assetfile_t; typedef errorret_t (*assetfileloader_t)(assetfile_t *file); diff --git a/src/duskpsp/asset/assetpbp.c b/src/duskpsp/asset/assetpbp.c index 4092bfda..1aeb3669 100644 --- a/src/duskpsp/asset/assetpbp.c +++ b/src/duskpsp/asset/assetpbp.c @@ -8,6 +8,7 @@ #include "asset/asset.h" #include "assert/assert.h" #include "util/memory.h" +#include "util/math.h" errorret_t assetInitPBP(const char_t *pbpPath) { assertNotNull(pbpPath, "PBP path cannot be null."); @@ -73,24 +74,49 @@ errorret_t assetInitPBP(const char_t *pbpPath) { errorThrow("Failed to seek to PSAR offset in PBP file: %s", pbpPath); } - zip_uint64_t zipPsarOffset = (zip_uint64_t)( - ASSET.platform.pbpHeader.psarOffset - ); - zip_int64_t zipPsarSize = (zip_int64_t)( - pbpSize - ASSET.platform.pbpHeader.psarOffset - ); + size_t psarSize = pbpSize - ASSET.platform.pbpHeader.psarOffset; - zip_source_t *psarSource = zip_source_filep_create( - ASSET.platform.pbpFile, - zipPsarOffset, - zipPsarSize, - NULL + // Read the whole PSAR (the embedded dusk.dsk zip archive) into memory up + // front and hand libzip an in-memory buffer, instead of a lazily-seeked + // FILE source: repeated seeked reads directly against the open PBP file + // proved unreliable on PSP (zip_fread() failing with EINVAL, then with + // zlib data corruption, depending on the read chunk size used). Reading + // once, straight through, and letting libzip operate on memory from then + // on avoids that read path entirely. psarSize is small (tens of KB) so + // holding it fully in RAM is cheap. + uint8_t *psarData = (uint8_t *)malloc(psarSize); + if(psarData == NULL) { + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to allocate PSAR buffer for file: %s", pbpPath); + } + + size_t totalRead = 0; + while(totalRead < psarSize) { + size_t chunkSize = mathMin( + psarSize - totalRead, ASSET_FILE_READ_CHUNK_MAX + ); + size_t chunkRead = fread( + psarData + totalRead, 1, chunkSize, ASSET.platform.pbpFile + ); + if(chunkRead == 0) { + free(psarData); + fclose(ASSET.platform.pbpFile); + errorThrow("Failed to read PSAR data from file: %s", pbpPath); + } + totalRead += chunkRead; + } + + fclose(ASSET.platform.pbpFile); + ASSET.platform.pbpFile = NULL; + + zip_source_t *psarSource = zip_source_buffer_create( + psarData, (zip_uint64_t)psarSize, 1, NULL ); if(psarSource == NULL) { - fclose(ASSET.platform.pbpFile); + free(psarData); errorThrow("Failed to create zip source in PBP file: %s", pbpPath); } - + ASSET.zip = zip_open_from_source( psarSource, ZIP_RDONLY, @@ -98,7 +124,6 @@ errorret_t assetInitPBP(const char_t *pbpPath) { ); if(ASSET.zip == NULL) { zip_source_free(psarSource); - fclose(ASSET.platform.pbpFile); errorThrow("Failed to open zip from PBP file: %s", pbpPath); }