Files
2026-02-02 04:50:13 +01:00

518 lines
17 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "m4/fileio/fstream.h"
#include "m4/core/errors.h"
#include "m4/core/imath.h"
#include "m4/vars.h"
namespace M4 {
#define STR_STRMREQ "stream request"
StreamFile::StreamFile(const Common::Path &filename) {
if (!_file.open(filename))
error("Could not open - %s", filename.toString().c_str());
}
int32 StreamFile::read(Handle bufferHandle, int32 n) {
return _file.read(bufferHandle, n);
}
bool StreamFile::seek(uint32 n) {
return _file.seek(n);
}
bool StreamFile::seek_ahead(int32 n) {
return _file.skip(n);
}
uint32 StreamFile::get_pos() {
return _file.pos();
}
bool f_stream_Init() {
_G(firstStream) = nullptr;
_G(lastStream) = nullptr;
return true;
}
void f_stream_Shutdown(void) {
// Loop through the list, closing all stream requests, which also deallocs the request
strmRequest *myStream = _G(firstStream);
while (myStream) {
_G(firstStream) = _G(firstStream)->next;
f_stream_Close(myStream);
myStream = _G(firstStream);
}
}
strmRequest *f_stream_Open(SysFile *srcFile, int32 fileOffset, int32 strmMinBuffSize, int32 strmBuffSize,
int32 numBlocksToRead, int32 *blockSizeArray, int32 initialRead, bool wrapStream) {
int32 bytesToRead;
// Parameter verification
if (!srcFile) {
error_show(FL, 'FSF!');
}
if (strmMinBuffSize < 0) {
error_show(FL, 'FSF1', "neg min buffsize: %d", strmMinBuffSize);
}
// Allocate a new stream request struct
strmRequest *newStream = (strmRequest *)mem_alloc(sizeof(strmRequest), STR_STRMREQ);
if (newStream == nullptr) {
error_show(FL, 'OOM!', "%d", sizeof(strmRequest));
}
// Get memory. If there's not enough memory, a exception will be triggered in NewHandle
newStream->strmHandle = NewHandle(strmBuffSize, "stream buff");
//lock the buffer - to be locked until the stream is closed
HLock(newStream->strmHandle);
newStream->strmBuff = (uint8 *) *(newStream->strmHandle);
// Initialize the stream request
newStream->strmSize = strmBuffSize;
newStream->strmHead = newStream->strmBuff;
newStream->strmTail = newStream->strmBuff;
newStream->endStrmBuff = newStream->strmBuff + strmBuffSize;
newStream->strmWrap = newStream->endStrmBuff;
newStream->strmLastRead = newStream->endStrmBuff;
newStream->numBlocksToRead = numBlocksToRead;
newStream->blockSizeArray = blockSizeArray;
newStream->wrapStream = wrapStream;
newStream->srcFile = srcFile;
// If the streaming should begin part way into the file, seek to the beginning of where to start streaming
if (fileOffset > 0) {
if (!newStream->srcFile->seek(fileOffset)) {
HUnLock(newStream->strmHandle);
DisposeHandle(newStream->strmHandle);
delete newStream->srcFile;
mem_free(newStream);
return nullptr;
}
}
// Check if we are to initially read the stream
if (initialRead > 0) {
// If the blockSizeArray exists, then initialRead is the number of blocks to read
if (newStream->blockSizeArray) {
// Calculate the total number of bytes to read in initially
initialRead = (int32)imath_min(initialRead, numBlocksToRead);
bool finished = false;
bytesToRead = 0;
int32 i = 0;
while ((i < initialRead) && (!finished)) {
if ((bytesToRead + blockSizeArray[i]) <= strmBuffSize) {
bytesToRead += blockSizeArray[i];
i++;
} else {
finished = true;
}
}
// Update the blockSizeArray, and numBlocksToRead entries. We plan to read in "i" blocks so far.
newStream->numBlocksToRead -= i;
newStream->blockSizeArray += i;
}
// Else the initialRead refers to the number of bytes to initially read
else {
//bounds check the initialRead and set the nextReadSize field
bytesToRead = (int32)imath_min(initialRead, strmBuffSize);
newStream->nextReadSize = bytesToRead;
}
// Make sure we still have something to read
if (bytesToRead > 0) {
// Read in the initial bytes to read
int32 bytesRead = newStream->srcFile->read(newStream->strmHead, bytesToRead);
//did we actually read that many? If not, close the file
if (bytesRead < bytesToRead) {
delete newStream->srcFile;
newStream->srcFile = nullptr;
}
// Update the strmHead pointer
newStream->strmHead += bytesRead;
}
}
//link the stream request into the list of requests
newStream->prev = nullptr;
newStream->next = _G(firstStream);
if (_G(firstStream)) {
_G(firstStream)->prev = newStream;
} else {
_G(lastStream) = newStream;
}
_G(firstStream) = newStream;
// Return the stream request
return newStream;
}
static bool UnwrapStream(strmRequest *myStream) {
int32 bytesToMove = 0;
// Using tempBuff as a flag to determine whether data needs to be temporarily stored
uint8 *tempBuff = nullptr;
// Since strmTail is never allowed to be equal to strmWrap if it is > strmHead, there must be a
// Non-zero amount of data at the end which we must move.
//Therefore, we may have to temporarily store anything at the beginning of the buffer
if (myStream->strmHead > myStream->strmBuff) {
// Calculate how many bytes to store and copy to a temporary buffer
bytesToMove = (byte *)myStream->strmHead - (byte *)myStream->strmBuff;
if ((tempBuff = (uint8 *)mem_alloc(bytesToMove, "stream temp buff")) == nullptr)
error_show(FL, 'OOM!', "UnwrapStream() failed - temp buff avail: %d", bytesToMove);
memcpy(tempBuff, myStream->strmBuff, bytesToMove);
}
// Move the data at the end of the buffer to the beginning and reset the strmWrap pointer
int32 bytesAvail = (byte *)myStream->strmWrap - (byte *)myStream->strmTail;
memmove(myStream->strmBuff, myStream->strmTail, bytesAvail);
myStream->strmTail = myStream->strmBuff;
myStream->strmHead = (uint8 *)((byte *)(myStream->strmTail) + bytesAvail);
myStream->strmWrap = myStream->endStrmBuff;
// Now check if we temporarily store data. if so, copy it back to the stream and turf the temp buffer
if (tempBuff) {
memcpy(myStream->strmHead, tempBuff, bytesToMove);
myStream->strmHead += bytesToMove;
mem_free(tempBuff);
}
return true;
}
void f_stream_DumpPreviouslyRead(strmRequest *myStream) {
// This is used to allow the f_stream_Process() function to overwrite the stream buffer space where
// the previously read data was stored. ie. If you call f_stream_Read(), and then make a copy,
// you wouldn't care if the data in the stream buffer was overwritten, so call this procedure.
if (myStream) {
myStream->strmLastRead = myStream->strmTail;
if (myStream->strmTail == myStream->strmHead) {
myStream->strmTail = myStream->strmBuff;
myStream->strmHead = myStream->strmBuff;
}
}
}
int32 f_stream_Read(strmRequest *myStream, uint8 **dest, int32 numBytes) {
int32 bytesAvail;
// Parameter verification
if (!myStream)
error_show(FL, 'FSIS', "f_stream_Read() failed - invalid stream request");
if ((numBytes <= 0) || (numBytes >= myStream->strmSize))
error_show(FL, 'FSR!', "%d stream size %d", numBytes, myStream->strmSize);
// If the stream tail is > the stream head, and the number of bytes at the end of the buffer is < numBytes
// we must unwrap the stream, moving the data at the end of the buffer to the beginning, and slide the beginning down
if ((myStream->strmTail > myStream->strmHead) && (((byte *)myStream->strmWrap - (byte *)myStream->strmTail) < numBytes)) {
UnwrapStream(myStream);
}
// Now either the strmHead is >= the strmTail, or there is enough data at the end of the buffer to fulfill numBytes
// Calculate the number of bytes available
if (myStream->strmTail <= myStream->strmHead) {
bytesAvail = (int32)(myStream->strmHead - myStream->strmTail);
} else {
// No extra data is available at the beginning of the stream buffer, since we "unwrapped" the stream
bytesAvail = (int32)(myStream->strmWrap - myStream->strmTail);
}
// Now check and see if we have enough bytes available
if (bytesAvail >= numBytes) {
// Set the destination pointer
*dest = (uint8 *)myStream->strmTail;
myStream->strmLastRead = myStream->strmTail;
// Update the strmTail pointer
myStream->strmTail += numBytes;
// If there is no data left at the end of the stream buffer, reset the strmTail and strmWrap pointers
if (myStream->strmTail == myStream->strmWrap) {
myStream->strmTail = myStream->strmBuff;
myStream->strmWrap = myStream->endStrmBuff;
}
return numBytes;
}
// Else we will have to read more data from disc
// If this has happened, since we "unwrapped" the stream buff, we can guarantee that strmTail < strmHead
// Calculate how much more must be read in
int32 bytesNeeded = numBytes - bytesAvail;
// Make sure we have enough room at the end of the buffer to accommodate
if ((int32)(myStream->endStrmBuff - myStream->strmHead) < bytesNeeded) {
// We need to memmove the contents of the stream to the beginning of the buff to allow
// F_stream_read() to return a pointer to a contiguous block
// Move the data to the beginning of the stream buffer, and reset the head and tail pointers
memmove((void *)myStream->strmBuff, myStream->strmTail, bytesAvail);
myStream->strmTail = myStream->strmBuff;
myStream->strmHead = (uint8 *)((byte *)myStream->strmTail + bytesAvail);
}
// If the client is using a blockSizeArray, hopefully bytesNeeded will be equal to the next blockSize
if (myStream->blockSizeArray && (*myStream->blockSizeArray == bytesNeeded) && (myStream->numBlocksToRead > 0)) {
myStream->blockSizeArray++;
myStream->numBlocksToRead--;
} else {
// Otherwise we just trashed the whole point of using a calculated blockSizeArray
myStream->blockSizeArray = nullptr;
myStream->numBlocksToRead = -1;
myStream->nextReadSize = numBytes;
}
// Read in the bytesNeeded
int32 bytesRead = myStream->srcFile->read(myStream->strmHead, bytesNeeded);
if (bytesRead < bytesNeeded) {
// If we could not read that much in, close the srcFile
delete myStream->srcFile;
myStream->srcFile = nullptr;
}
// Set the destination pointer and update the stream pointers
*dest = (uint8 *)myStream->strmTail;
myStream->strmLastRead = myStream->strmTail;
myStream->strmHead += bytesRead;
myStream->strmTail = myStream->strmHead;
// Return the number of bytes successfully available
return (bytesRead + bytesAvail);
}
void f_stream_Close(strmRequest *myStream) {
// Parameter verification
if (!myStream) {
return;
}
// Close the stream and throw out the stream buffer
delete myStream->srcFile;
// Kill the stream buffer
HUnLock(myStream->strmHandle);
DisposeHandle(myStream->strmHandle);
myStream->strmBuff = nullptr;
// Remove the stream request from the list of requests
if (myStream->next) {
myStream->next->prev = myStream->prev;
} else {
_G(lastStream) = myStream->prev;
}
if (myStream->prev) {
myStream->prev->next = myStream->next;
} else {
_G(firstStream) = myStream->next;
}
// Final, turf the stream request
mem_free(myStream);
}
void f_stream_Process(int32 numToProcess) {
int32 buffEndBytesAvail = 0, buffStartBytesAvail = 0;
int32 bytesRead, bytesAvail, nextReadSize;
// No sense wasting time if there are no stream requests to process
if (!_G(firstStream)) {
return;
}
// Loop through until either the end of the list of requests, or we've serviced the "numToProcess"
strmRequest *myStream = _G(firstStream);
while (myStream && (numToProcess > 0)) {
// Make sure we still have an open srcFile
if (myStream->srcFile && (myStream->numBlocksToRead != 0)) {
bool buffWrap = false;
bool useBlockSizeArray = false;
// Calculate the amount of empty space in the stream buff
// If all the empty space in the stream buff is between the head and the lastRead...
if (myStream->strmLastRead >= myStream->strmHead) {
bytesAvail = (byte *)myStream->strmLastRead - (byte *)myStream->strmHead;
// strmTail and strmHead can never equal unless the buffer is completely empty, therefore,
// make sure the amout of bytes available won't cause strmHead to become equal to strmTail
if ((bytesAvail > 0) && (myStream->strmLastRead == myStream->strmTail)) {
bytesAvail--;
}
} else {
// Else all the empty space is wrapped around the end of the buffer
buffWrap = true;
// Calculate how much space is available at the start and at the end of the buffer
buffEndBytesAvail = (byte *)myStream->endStrmBuff - (byte *)myStream->strmHead;
buffStartBytesAvail = (byte *)myStream->strmLastRead - (byte *)myStream->strmBuff;
// As above, ensure strmHead won't become equal to strmTail
if ((buffStartBytesAvail > 0) && (myStream->strmLastRead == myStream->strmTail)) {
buffStartBytesAvail--;
}
// Calculate the total bytes available
bytesAvail = buffEndBytesAvail + buffStartBytesAvail;
}
// Now find the number of bytes to read - either from the blockSizeArray...
if (myStream->blockSizeArray) {
useBlockSizeArray = true;
nextReadSize = *myStream->blockSizeArray;
} else {
// ...or directly from the nextReadSize field of the stream request
nextReadSize = myStream->nextReadSize;
}
// See if we can simply read the next chunk into the strmHead, without worrying about "wrapping" the buffer
if ((buffWrap && (buffEndBytesAvail >= nextReadSize)) ||
((!buffWrap) && (bytesAvail >= nextReadSize))) {
// Read the bytes into the stream buffer
bytesRead = myStream->srcFile->read(myStream->strmHead, nextReadSize);
// If we could not read that much in, close the srcFile
if (bytesRead < nextReadSize) {
delete myStream->srcFile;
myStream->srcFile = nullptr;
}
// Update the stream head
myStream->strmHead += bytesRead;
// Update the blockSizeArray pointer if necessary
if (useBlockSizeArray) {
myStream->blockSizeArray++;
myStream->numBlocksToRead--;
}
} else if (buffWrap) {
// Else if the empty space is wrapped, we may still be able to store the next data chunk, otherwise no more room
// See if we can wrap the next data chunk around
if (!myStream->wrapStream) {
// No wrapping allowed, so do we have room for it at the beginning of the stream
if (buffStartBytesAvail >= nextReadSize) {
//we can read it in at the beginning, so set the strmWrap pointer
myStream->strmWrap = myStream->strmHead;
// Read the bytes into the stream buffer
bytesRead = myStream->srcFile->read(myStream->strmBuff, nextReadSize);
// If we could not read that much in, close the srcFile
if (bytesRead < nextReadSize) {
delete myStream->srcFile;
myStream->srcFile = nullptr;
}
// Update the stream head
myStream->strmHead = (uint8 *)((byte *)myStream->strmBuff + bytesRead);
// Update the blockSizeArray pointer if necessary
if (useBlockSizeArray) {
myStream->blockSizeArray++;
myStream->numBlocksToRead--;
}
}
} else if (bytesAvail >= nextReadSize) {
// Else we might have to read in part at the end, and part at the beginning of the stream buffer
// Read into the end of the stream buffer
if (buffEndBytesAvail > 0) {
// Read into the end of the buffer
bytesRead = (int32)myStream->srcFile->read(myStream->strmHead, buffEndBytesAvail);
// If we could not read that much in, close the srcFile and update the head pointer
if (bytesRead < buffEndBytesAvail) {
delete myStream->srcFile;
myStream->srcFile = nullptr;
myStream->strmHead += bytesRead;
}
}
// Make sure we didn't close the srcFile in the last read
if (myStream->srcFile) {
// Read into the beginning of the buffer
bytesRead = myStream->srcFile->read(myStream->strmBuff, nextReadSize - buffEndBytesAvail);
// If we could not read that much in, close the srcFile
if (bytesRead < (nextReadSize - buffEndBytesAvail)) {
delete myStream->srcFile;
myStream->srcFile = nullptr;
}
// Update the head pointer
myStream->strmHead = (uint8 *)((byte *)myStream->strmBuff + bytesRead);
// Update the blockSizeArray pointer if necessary
if (useBlockSizeArray) {
myStream->blockSizeArray++;
myStream->numBlocksToRead--;
}
}
}
}
}
// If we were able, we serviced the above stream request. Get the next request and decriment the counter
myStream = myStream->next;
numToProcess--;
}
// See if we ran out of processes or if the counter ran out
if (myStream) {
// This implies the counter ran out. Move the front of the list to myStream->prev to the end of the list
myStream->prev->next = nullptr;
_G(lastStream)->next = _G(firstStream);
_G(firstStream)->prev = _G(lastStream);
_G(lastStream) = myStream->prev;
myStream->prev = nullptr;
_G(firstStream) = myStream;
}
}
} // namespace M4