Fix PSP asset loading: stale EBOOT.PBP packing, unreliable zip file reads
EBOOT.PBP packing was a POST_BUILD step with no dependency on the asset pak, so an assets-only rebuild could silently leave a stale dusk.dsk embedded. cmake/targets/psp.cmake now repacks EBOOT.PBP via a properly tracked custom command depending on the executable, PARAM.SFO, and dusk.dsk (and correctly embeds Dusk.prx rather than the raw ELF when BUILD_PRX is on). Separately, libzip's zip_source_filep_create (lazy seeked FILE* reads) proved unreliable on real PSP hardware, corrupting reads of the embedded PSAR (first EINVAL, then zlib data errors) even though the packaged data was verified byte-perfect. assetInitPBP now reads the whole PSAR into memory once and uses zip_source_buffer_create instead. Confirmed working on real hardware. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,23 @@ if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|||||||
)
|
)
|
||||||
endif()
|
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.
|
# Postbuild, create .pbp file for PSP.
|
||||||
create_pbp_file(
|
create_pbp_file(
|
||||||
TARGET "${DUSK_BINARY_TARGET_NAME}"
|
TARGET "${DUSK_BINARY_TARGET_NAME}"
|
||||||
@@ -74,4 +91,33 @@ create_pbp_file(
|
|||||||
TITLE "${DUSK_BINARY_TARGET_NAME}"
|
TITLE "${DUSK_BINARY_TARGET_NAME}"
|
||||||
PSAR_PATH ${DUSK_ASSETS_ZIP}
|
PSAR_PATH ${DUSK_ASSETS_ZIP}
|
||||||
VERSION 01.00
|
VERSION 01.00
|
||||||
|
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 "$<TARGET_FILE:${DUSK_BINARY_TARGET_NAME}>.prx")
|
||||||
|
else()
|
||||||
|
set(DUSK_PSP_EXECUTABLE "$<TARGET_FILE:${DUSK_BINARY_TARGET_NAME}>")
|
||||||
|
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
|
||||||
|
"$<TARGET_FILE:${DUSK_BINARY_TARGET_NAME}>"
|
||||||
|
"${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}")
|
||||||
@@ -87,13 +87,30 @@ errorret_t assetFileRead(
|
|||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
// I assume zip_fread takes buffer NULL for skipping?
|
// Some zip_fread() implementations (seen on PSP) reject a single call
|
||||||
zip_int64_t bytesRead = zip_fread(file->zipFile, buffer, bufferSize);
|
// asking for the entire (potentially large) file at once with EINVAL;
|
||||||
if(bytesRead < 0) {
|
// the line reader above only ever asks for up to 1024 bytes per call and
|
||||||
errorThrow("Failed to read from asset file: %s", file->filename);
|
// 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->position += totalRead;
|
||||||
file->lastRead = bytesRead;
|
file->lastRead = totalRead;
|
||||||
errorOk();
|
errorOk();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,12 @@
|
|||||||
|
|
||||||
#define ASSET_FILE_NAME_MAX 48
|
#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 struct assetfile_s assetfile_t;
|
||||||
|
|
||||||
typedef errorret_t (*assetfileloader_t)(assetfile_t *file);
|
typedef errorret_t (*assetfileloader_t)(assetfile_t *file);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "asset/asset.h"
|
#include "asset/asset.h"
|
||||||
#include "assert/assert.h"
|
#include "assert/assert.h"
|
||||||
#include "util/memory.h"
|
#include "util/memory.h"
|
||||||
|
#include "util/math.h"
|
||||||
|
|
||||||
errorret_t assetInitPBP(const char_t *pbpPath) {
|
errorret_t assetInitPBP(const char_t *pbpPath) {
|
||||||
assertNotNull(pbpPath, "PBP path cannot be null.");
|
assertNotNull(pbpPath, "PBP path cannot be null.");
|
||||||
@@ -73,21 +74,46 @@ errorret_t assetInitPBP(const char_t *pbpPath) {
|
|||||||
errorThrow("Failed to seek to PSAR offset in PBP file: %s", pbpPath);
|
errorThrow("Failed to seek to PSAR offset in PBP file: %s", pbpPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
zip_uint64_t zipPsarOffset = (zip_uint64_t)(
|
size_t psarSize = pbpSize - ASSET.platform.pbpHeader.psarOffset;
|
||||||
ASSET.platform.pbpHeader.psarOffset
|
|
||||||
);
|
|
||||||
zip_int64_t zipPsarSize = (zip_int64_t)(
|
|
||||||
pbpSize - ASSET.platform.pbpHeader.psarOffset
|
|
||||||
);
|
|
||||||
|
|
||||||
zip_source_t *psarSource = zip_source_filep_create(
|
// Read the whole PSAR (the embedded dusk.dsk zip archive) into memory up
|
||||||
ASSET.platform.pbpFile,
|
// front and hand libzip an in-memory buffer, instead of a lazily-seeked
|
||||||
zipPsarOffset,
|
// FILE source: repeated seeked reads directly against the open PBP file
|
||||||
zipPsarSize,
|
// proved unreliable on PSP (zip_fread() failing with EINVAL, then with
|
||||||
NULL
|
// 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) {
|
if(psarSource == NULL) {
|
||||||
fclose(ASSET.platform.pbpFile);
|
free(psarData);
|
||||||
errorThrow("Failed to create zip source in PBP file: %s", pbpPath);
|
errorThrow("Failed to create zip source in PBP file: %s", pbpPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +124,6 @@ errorret_t assetInitPBP(const char_t *pbpPath) {
|
|||||||
);
|
);
|
||||||
if(ASSET.zip == NULL) {
|
if(ASSET.zip == NULL) {
|
||||||
zip_source_free(psarSource);
|
zip_source_free(psarSource);
|
||||||
fclose(ASSET.platform.pbpFile);
|
|
||||||
errorThrow("Failed to open zip from PBP file: %s", pbpPath);
|
errorThrow("Failed to open zip from PBP file: %s", pbpPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user