Fixed PSP trying to load entirely into memory
This commit is contained in:
@@ -9,129 +9,75 @@
|
||||
#include "asset/assetdsk.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.");
|
||||
assertStrLenMin(pbpPath, 1, "PBP path cannot be empty.");
|
||||
assertStrLenMax(pbpPath, ASSET_PBP_PATH_MAX, "PBP path is too long.");
|
||||
|
||||
ASSET.platform.pbpFile = fopen(pbpPath, "rb");
|
||||
if(ASSET.platform.pbpFile == NULL) {
|
||||
FILE *pbpFile = fopen(pbpPath, "rb");
|
||||
if(pbpFile == NULL) {
|
||||
errorThrow("Failed to open PBP file: %s", pbpPath);
|
||||
}
|
||||
|
||||
// Get size of PBP file.
|
||||
if(fseek(ASSET.platform.pbpFile, 0, SEEK_END) != 0) {
|
||||
fclose(ASSET.platform.pbpFile);
|
||||
if(fseek(pbpFile, 0, SEEK_END) != 0) {
|
||||
fclose(pbpFile);
|
||||
errorThrow("Failed to seek to end of PBP file : %s", pbpPath);
|
||||
}
|
||||
|
||||
size_t pbpSize = ftell(ASSET.platform.pbpFile);
|
||||
size_t pbpSize = ftell(pbpFile);
|
||||
if(pbpSize == -1L) {
|
||||
fclose(ASSET.platform.pbpFile);
|
||||
fclose(pbpFile);
|
||||
errorThrow("Failed to get size of PBP file : %s", pbpPath);
|
||||
}
|
||||
|
||||
if(pbpSize < sizeof(assetpbpheader_t)) {
|
||||
fclose(ASSET.platform.pbpFile);
|
||||
fclose(pbpFile);
|
||||
errorThrow("PBP file is too small to be valid: %s", pbpPath);
|
||||
}
|
||||
|
||||
// Rewind to start
|
||||
if(fseek(ASSET.platform.pbpFile, 0, SEEK_SET) != 0) {
|
||||
fclose(ASSET.platform.pbpFile);
|
||||
if(fseek(pbpFile, 0, SEEK_SET) != 0) {
|
||||
fclose(pbpFile);
|
||||
errorThrow("Failed to seek to start of PBP file : %s", pbpPath);
|
||||
}
|
||||
|
||||
// Read the PBP header
|
||||
size_t read = fread(
|
||||
&ASSET.platform.pbpHeader,
|
||||
1,
|
||||
sizeof(assetpbpheader_t),
|
||||
ASSET.platform.pbpFile
|
||||
&ASSET.platform.pbpHeader, 1, sizeof(assetpbpheader_t), pbpFile
|
||||
);
|
||||
fclose(pbpFile);
|
||||
if(read != sizeof(assetpbpheader_t)) {
|
||||
fclose(ASSET.platform.pbpFile);
|
||||
errorThrow("Failed to read PBP header", pbpPath);
|
||||
}
|
||||
|
||||
|
||||
if(memoryCompare(
|
||||
ASSET.platform.pbpHeader.signature,
|
||||
ASSET_PBP_SIGNATURE,
|
||||
sizeof(ASSET_PBP_SIGNATURE)
|
||||
) != 0) {
|
||||
fclose(ASSET.platform.pbpFile);
|
||||
errorThrow("Invalid PBP signature in file: %s", pbpPath);
|
||||
}
|
||||
|
||||
// If we seek to the PSAR offset, we can read the WAD file from there.
|
||||
// I'm not sure what PSAR was intended for, but it holds any user data we
|
||||
// want, so I shoved the entire dusk wad there.
|
||||
if(fseek(
|
||||
ASSET.platform.pbpFile, ASSET.platform.pbpHeader.psarOffset, SEEK_SET
|
||||
) != 0) {
|
||||
fclose(ASSET.platform.pbpFile);
|
||||
errorThrow("Failed to seek to PSAR offset in PBP file: %s", pbpPath);
|
||||
}
|
||||
|
||||
// The PSAR region holds the embedded dusk.dsk (DSK2) archive - I'm not
|
||||
// sure what PSAR was intended for, but it holds any user data we want,
|
||||
// so I shoved the entire dusk.dsk there. Opened lazily/on-demand
|
||||
// straight from the PBP file by path+offset (see
|
||||
// assetDskOpenFromPathRange), the same way every other platform opens
|
||||
// dusk.dsk, rather than reading the whole PSAR into memory up front.
|
||||
size_t psarSize = pbpSize - ASSET.platform.pbpHeader.psarOffset;
|
||||
errorChain(assetDskOpenFromPathRange(
|
||||
pbpPath,
|
||||
ASSET.platform.pbpHeader.psarOffset,
|
||||
psarSize,
|
||||
&ASSET.zip,
|
||||
&ASSET.zipStored
|
||||
));
|
||||
|
||||
// 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;
|
||||
|
||||
errorret_t ret = assetDskOpenFromBuffer(
|
||||
psarData, psarSize, &ASSET.zip, &ASSET.zipStored
|
||||
);
|
||||
if(errorIsNotOk(ret)) {
|
||||
free(psarData);
|
||||
errorChain(ret);
|
||||
}
|
||||
|
||||
ASSET.platform.dskData = psarData;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetDisposePBP(void) {
|
||||
if(ASSET.platform.pbpFile != NULL) {
|
||||
fclose(ASSET.platform.pbpFile);
|
||||
ASSET.platform.pbpFile = NULL;
|
||||
}
|
||||
|
||||
if(ASSET.platform.dskData != NULL) {
|
||||
free(ASSET.platform.dskData);
|
||||
ASSET.platform.dskData = NULL;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -28,13 +28,7 @@ typedef struct {
|
||||
} assetpbpheader_t;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -128,7 +128,7 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
}
|
||||
stream->platform.playRequested = false;
|
||||
|
||||
const size_t totalFrames = stream->dataSize / frameSize;
|
||||
const size_t totalFrames = audioStreamPcmGetTotalFrames(stream);
|
||||
|
||||
// loopEndFrame/loopToFrame define the loop segment [loopToFrame,
|
||||
// loopEndFrame) that a looping pass wraps within, once it's reached -
|
||||
@@ -147,6 +147,14 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
size_t position = stream->platform.startFrame;
|
||||
bool_t reachedEnd = false;
|
||||
|
||||
// Samples are read on demand from the asset (via audioStreamPcmRead(),
|
||||
// sequentially, plus an explicit audioStreamPcmSeek() whenever jumping
|
||||
// backward for a loop wrap) rather than indexed out of a fully
|
||||
// resident buffer - a read/seek failure here (a corrupt or truncated
|
||||
// asset, an I/O error) stops playback cleanly instead of crashing the
|
||||
// thread on bad data.
|
||||
bool_t readFailed = errorIsNotOk(audioStreamPcmSeek(stream, position));
|
||||
|
||||
// Every call always sends a full, constant-size AUDIO_PSP_CHUNK_FRAMES
|
||||
// buffer - the channel is never re-declared to a different length, to
|
||||
// avoid relying on sceAudioSetChannelDataLen's undocumented behavior
|
||||
@@ -157,7 +165,7 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
// engine's finished+looping path is designed for platforms with no
|
||||
// better option (see audiostream.c), but here the thread can just
|
||||
// keep going and call onLoop itself instead.
|
||||
while(!threadShouldStop(thread) && !reachedEnd) {
|
||||
while(!readFailed && !threadShouldStop(thread) && !reachedEnd) {
|
||||
// Normally bounded by loopEndFrame (the loop segment's end), but a
|
||||
// seek can legitimately land past it (e.g. into an outro after the
|
||||
// loop point) - in that case play out to the true end of the buffer
|
||||
@@ -173,9 +181,22 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
const bool_t willLoop = reachesEndThisChunk &&
|
||||
(stream->state & AUDIO_STREAM_STATE_LOOPING);
|
||||
|
||||
memoryCopy(
|
||||
chunk, stream->data + (position * frameSize), framesThisChunk * frameSize
|
||||
);
|
||||
size_t framesRead = 0;
|
||||
if(errorIsNotOk(
|
||||
audioStreamPcmRead(stream, chunk, framesThisChunk, &framesRead)
|
||||
)) {
|
||||
readFailed = true;
|
||||
break;
|
||||
}
|
||||
if(framesRead < framesThisChunk) {
|
||||
// The asset is shorter than its declared header size (corrupt or
|
||||
// truncated) - pad what's missing with silence rather than play
|
||||
// whatever was left in `chunk` from a previous pass.
|
||||
memoryZero(
|
||||
chunk + (framesRead * channels),
|
||||
(framesThisChunk - framesRead) * frameSize
|
||||
);
|
||||
}
|
||||
|
||||
const size_t remainderFrames = AUDIO_PSP_CHUNK_FRAMES - framesThisChunk;
|
||||
size_t wrapFrames = 0;
|
||||
@@ -205,15 +226,22 @@ void audioStreamPSPThreadFeed(thread_t *thread) {
|
||||
wrapFrames = remainderFrames < loopSegmentFrames
|
||||
? remainderFrames
|
||||
: loopSegmentFrames;
|
||||
memoryCopy(
|
||||
chunk + (framesThisChunk * channels),
|
||||
stream->data + (loopToFrame * frameSize),
|
||||
wrapFrames * frameSize
|
||||
);
|
||||
if(wrapFrames < remainderFrames) {
|
||||
|
||||
if(errorIsNotOk(audioStreamPcmSeek(stream, loopToFrame))) {
|
||||
readFailed = true;
|
||||
break;
|
||||
}
|
||||
size_t wrapRead = 0;
|
||||
if(errorIsNotOk(audioStreamPcmRead(
|
||||
stream, chunk + (framesThisChunk * channels), wrapFrames, &wrapRead
|
||||
))) {
|
||||
readFailed = true;
|
||||
break;
|
||||
}
|
||||
if(wrapRead < remainderFrames) {
|
||||
memoryZero(
|
||||
chunk + ((framesThisChunk + wrapFrames) * channels),
|
||||
(remainderFrames - wrapFrames) * frameSize
|
||||
chunk + ((framesThisChunk + wrapRead) * channels),
|
||||
(remainderFrames - wrapRead) * frameSize
|
||||
);
|
||||
}
|
||||
} else if(reachesEndThisChunk) {
|
||||
|
||||
@@ -60,8 +60,9 @@ errorret_t audioStreamPSPInit(audiostream_t *stream);
|
||||
errorret_t audioStreamPSPDispose(audiostream_t *stream);
|
||||
|
||||
/**
|
||||
* Wakes the stream's persistent feeder thread to stream stream->data to
|
||||
* its reserved hardware output channel in small chunks until exhausted.
|
||||
* Wakes the stream's persistent feeder thread to stream PCM data (read on
|
||||
* demand from the stream's asset via audioStreamPcmRead()) to its reserved
|
||||
* hardware output channel in small chunks until exhausted.
|
||||
*
|
||||
* @param stream The audio stream to output.
|
||||
* @return Error state if any.
|
||||
@@ -80,8 +81,9 @@ bool_t audioStreamPSPIsFinished(audiostream_t *stream);
|
||||
/**
|
||||
* Feeder thread entry point, run once for the stream's whole lifetime.
|
||||
* Idles (polling playRequested) until woken by audioStreamPSPBuffer(),
|
||||
* then streams stream->data (passed via thread->data) to its hardware
|
||||
* channel in fixed-size chunks, blocking naturally on each
|
||||
* then reads and streams the stream's asset PCM data (via the audiostream_t
|
||||
* passed as thread->data) to its hardware channel in fixed-size chunks,
|
||||
* blocking naturally on each
|
||||
* sceAudioOutputPannedBlocking() call, until the whole buffer has been
|
||||
* sent - then goes back to idling, ready for the next play request, until
|
||||
* the thread is asked to stop.
|
||||
|
||||
Reference in New Issue
Block a user