Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
/* 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/extensions.h"
namespace M4 {
Common::String f_extension_add(const Common::String &name, const Common::String &ext) {
size_t dot = name.findFirstOf('.');
if (dot == Common::String::npos)
return name + "." + ext;
else
return name;
}
Common::String f_extension_new(const Common::String &name, const Common::String &ext) {
size_t dot = name.findFirstOf('.');
if (dot != Common::String::npos)
return Common::String(name.c_str(), name.c_str() + dot + 1) + ext;
else
return name + "." + ext;
}
} // namespace M4

View File

@@ -0,0 +1,35 @@
/* 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/>.
*
*/
#ifndef M4_FILEIO_EXTENSIONS_H
#define M4_FILEIO_EXTENSIONS_H
#include "common/str.h"
namespace M4 {
Common::String f_extension_add(const Common::String &name, const Common::String &ext);
Common::String f_extension_new(const Common::String &name, const Common::String &ext);
} // namespace M4
#endif

View File

@@ -0,0 +1,50 @@
/* 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 "common/file.h"
#include "common/savefile.h"
#include "common/system.h"
#include "m4/fileio/fileio.h"
namespace M4 {
Common::Stream *f_io_open(const Common::Path &filename, const Common::String &mode) {
if (mode.hasPrefix("r")) {
if (filename.empty())
return nullptr;
Common::File *f = new Common::File();
if (f->open(filename))
return f;
delete f;
return nullptr;
} else {
return g_system->getSavefileManager()->openForSaving(filename.baseName());
}
}
void f_io_close(Common::Stream *stream) {
delete stream;
}
} // namespace M4

View File

@@ -0,0 +1,58 @@
/* 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/>.
*
*/
#ifndef M4_FILEIO_FILEIO_H
#define M4_FILEIO_FILEIO_H
#include "common/stream.h"
namespace M4 {
enum AccessMode {
UNOPENED,
READ
};
enum FileMode {
BINARY,
TEXT
};
struct Hag_Name_Record {
char filename[33];
byte hagfile;
struct Hag_Name_Record *next;
};
struct Hag_Record {
char hag_name[33];
byte hagfile;
Common::Stream *hag_fp;
uint32 hag_pos;
Hag_Record *next;
};
Common::Stream *f_io_open(const Common::Path &filename, const Common::String &mode);
void f_io_close(Common::Stream *stream);
} // namespace M4
#endif

View File

@@ -0,0 +1,517 @@
/* 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

View File

@@ -0,0 +1,74 @@
/* 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/>.
*
*/
#ifndef M4_FILEIO_FSTREAM_H
#define M4_FILEIO_FSTREAM_H
#include "common/file.h"
#include "m4/fileio/sys_file.h"
#include "m4/mem/reloc.h"
#include "m4/m4_types.h"
namespace M4 {
class StreamFile {
private:
Common::File _file;
public:
StreamFile(const Common::Path &filename);
~StreamFile() {}
int32 read(Handle bufferHandle, int32 n);
bool seek(uint32 n);
bool seek_ahead(int32 n);
uint32 get_pos();
};
struct strmRequest {
strmRequest *next;
strmRequest *prev;
SysFile *srcFile;
int32 strmSize;
MemHandle strmHandle;
uint8 *strmBuff;
uint8 *endStrmBuff;
uint8 *strmHead;
uint8 *strmTail;
uint8 *strmWrap;
uint8 *strmLastRead;
int32 nextReadSize;
int32 numBlocksToRead;
int32 *blockSizeArray;
bool wrapStream;
};
bool f_stream_Init();
void f_stream_Shutdown();
strmRequest *f_stream_Open(SysFile *srcFile, int32 fileOffset, int32 strmMinBuffSize, int32 strmBuffSize,
int32 numBlocksToRead, int32 *blockSizeArray, int32 initialRead, bool wrapStream);
int32 f_stream_Read(strmRequest *myStream, uint8 **dest, int32 numBytes);
void f_stream_Close(strmRequest *myStream);
void f_stream_Process(int32 numToProcess);
} // namespace M4
#endif

View File

@@ -0,0 +1,35 @@
/* 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/info.h"
namespace M4 {
bool f_info_exists(const Common::Path &filename) {
return Common::File::exists(filename);
}
size_t f_info_get_file_size(const Common::Path &filename) {
Common::File f;
return f.open(filename) ? f.size() : 0;
}
} // namespace M4

41
engines/m4/fileio/info.h Normal file
View File

@@ -0,0 +1,41 @@
/* 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/>.
*
*/
#ifndef M4_FILEIO_INFO_H
#define M4_FILEIO_INFO_H
#include "common/file.h"
namespace M4 {
/**
* Returns true if a given file exists
*/
bool f_info_exists(const Common::Path &filename);
/**
* Returns the size of a given file
*/
size_t f_info_get_file_size(const Common::Path &filename);
} // namespace M4
#endif

View File

@@ -0,0 +1,668 @@
/* 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/sys_file.h"
#include "m4/fileio/extensions.h"
#include "m4/fileio/fileio.h"
#include "m4/adv_r/db_env.h"
#include "m4/mem/memman.h"
#include "m4/vars.h"
#include "m4/m4.h"
namespace M4 {
// Hash entry format:
// Filename 33 bytes, Hagfile 1 byte, Disks 1 byte, Offset 4 bytes,
// Size 4 bytes Next_record 4 Bytes, Total is 47 bytes.
#define HASH_RECORD_LENGTH 47
SysFile::SysFile(const Common::String &fname, FileMode myfmode) :
filename(fname), fmode(myfmode) {
}
bool SysFile::exists() {
show_error_flag = false;
open_read_low_level();
show_error_flag = true;
if (!_G(hag).hag_flag)
return _fp != nullptr;
return (hag_success);
}
uint32 SysFile::size() {
uint32 fsize;
open_read();
if (!_G(hag).hag_flag) {
fsize = rs()->size();
} else {
if (hag_success)
fsize = curr_hash_record.size;
else
fsize = 0;
}
return fsize;
}
uint32 SysFile::get_pos() {
if (!_G(hag).hag_flag) {
if (!_fp)
return 0;
return rs()->pos();
}
if (hag_success)
return (uint32)(curr_hag_record->hag_pos - curr_hash_record.offset);
return 0;
}
void SysFile::open_read_low_level() {
Common::File temp_fp;
char hag_name[33];
char *temp_name;
Common::String resource_hag;
Common::File hagfile_fp;
if (filename.empty()) {
_fp = nullptr;
return;
}
if (!_G(hag).first_read_flag) {
// First time to read
if (_G(hag).hag_flag) {
// Use hag file
// Get hagfile table list here
if (!temp_fp.open(_G(hag).hash_file))
error("Hash file not found: %s", _G(hag).hash_file.toString().c_str());
const uint32 hash_table_size = temp_fp.readUint32LE();
if (!temp_fp.seek(hash_table_size * HASH_RECORD_LENGTH, SEEK_CUR))
error("fail to seek");
_G(hag).hag_name_list = nullptr;
while (!temp_fp.eos()) {
if (temp_fp.read(hag_name, 33) != 33)
break;
const byte hagfile = temp_fp.readByte();
Hag_Name_Record *temp_ptr = (Hag_Name_Record *)mem_alloc(sizeof(Hag_Name_Record), "hag_name_list");
assert(temp_ptr);
// Check hag file exists or not
Common::Path local_name(f_extension_new(hag_name, "HAG"));
if (!Common::File::exists(local_name))
error("couldn't find hag file: %s", local_name.toString().c_str());
// put into hag_name_list //
Common::strcpy_s(temp_ptr->filename, hag_name);
temp_ptr->hagfile = hagfile;
temp_ptr->next = _G(hag).hag_name_list;
_G(hag).hag_name_list = temp_ptr;
}
temp_fp.close();
} else {
_G(hag).hag_flag = false;
}
_G(hag).first_read_flag = true;
}
switch (mode) {
case UNOPENED:
switch (fmode) {
case TEXT:
temp_name = env_find(filename);
if (!_G(hag).hag_flag) {
if (temp_name) {
filename = temp_name;
_fp = f_io_open(Common::Path(filename), "rb");
if (!_fp && show_error_flag)
error("Failed opening - %s", filename.c_str());
} else {
_fp = open_by_first_char();
}
} else {
// Hag mode
filename = get_last_string(filename);
if (!open_hash_file() && show_error_flag)
error("not in hag file: %s", filename.c_str());
}
break;
case BINARY:
// is it a font file? They're stored in a *special* place oo-la-la
temp_name = env_find(filename);
if (!_G(hag).hag_flag) {
if (temp_name)
filename = temp_name;
open_by_first_char();
} else {
Common::String last_string = get_last_string(filename);
if (!open_hash_file()) {
if (show_error_flag)
error("not in hag file: %s", filename.c_str());
}
}
break;
default:
break;
}
break;
case READ:
default:
break;
}
mode = READ;
}
void SysFile::open_read() {
open_read_low_level();
if (_G(hag).hag_flag && curr_hag_record) {
if (!curr_hag_record->hag_fp)
error("hag file not open for: %s", filename.c_str());
}
if (!_G(hag).hag_flag && !_fp) {
error("Error opening - %s", filename.c_str());
}
if (_G(hag).hag_flag && !hag_success) {
error("Error opening - %s", filename.c_str());
}
}
void SysFile::open_write() {
error("open_write is not implemented in ScummVM");
}
Common::String SysFile::get_last_string(const Common::String &src) {
int len = src.size();
Common::String result;
int j;
for (j = len - 1; j >= 0; j--) {
if (src[j] == '\\' || src[j] == ':')
break;
}
if (j >= 0) {
for (int k = j + 1; k < len; k++)
result += src[k];
return result;
}
return src;
}
bool SysFile::open_hash_file() {
Common::SeekableReadStream *hashfp = dynamic_cast<Common::SeekableReadStream *>(f_io_open(_G(hag).hash_file, "rb"));
if (!hashfp) {
warning("open_hash_file: %s", _G(hag).hash_file.toString().c_str());
hag_success = false;
return false;
}
uint32 hash_table_size = hashfp->readUint32LE();
uint32 hash_address = key_to_hash_address(filename, hash_table_size);
if (!hash_search(filename, &curr_hash_record, curr_hag_record, hash_address, hashfp, hash_table_size, show_error_flag)) {
hag_success = 0;
return false;
}
// How to open hag file
// Calculate Hagfile name - depends on hagfile field in curr_hash_record
Common::String local_name;
if (!get_local_name_from_hagfile(local_name, curr_hash_record.hagfile)) {
hag_success = 0;
return false;
}
// Check if this Hag file already open or not
local_name = f_extension_new(local_name, "HAG");
Common::String temp_name = local_name;
Common::String hag_name = local_name; // Original used in cd_resource + name
bool found = false;
Hag_Record *temp_ptr = _G(hag).hag_file_list;
// Search local open files for hag file...
while (temp_ptr) {
if (hag_name.equalsIgnoreCase(temp_ptr->hag_name)) {
found = true;
break;
}
temp_ptr = temp_ptr->next;
}
// Search resource directory open files for hag file
if (!found) {
temp_ptr = _G(hag).hag_file_list;
found = false;
while (temp_ptr) {
if (temp_name.equalsIgnoreCase(temp_ptr->hag_name)) {
found = true;
break;
}
temp_ptr = temp_ptr->next;
}
if (!found) {
// hag file is not open, try the current directory first, then RESOURCE_PATH
Common::Stream *temp_fp = f_io_open(Common::Path(hag_name), "rb");
if (!temp_fp) {
// hag_file is not in current directory, search for RESOURCE_PATH
temp_fp = f_io_open(Common::Path(temp_name), "rb");
if (!temp_fp) {
error("hag file not found: %s", hag_name.c_str());
hag_success = 0;
return 0;
}
// Add this new open hag file in resource dir into open hag file list
temp_ptr = (Hag_Record *)mem_alloc(sizeof(Hag_Record), "Hag_File_List");
if (!temp_ptr) {
f_io_close(temp_fp);
error("creating Hag_record");
hag_success = 0;
return 0;
}
Common::strcpy_s(temp_ptr->hag_name, temp_name.c_str());
temp_ptr->hag_fp = temp_fp;
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(temp_fp);
assert(rs);
if (!rs->seek(curr_hash_record.offset))
term_message("fail to fseek");
last_head_pos = rs->pos();
temp_ptr->hag_pos = curr_hash_record.offset;
temp_ptr->hagfile = curr_hash_record.hagfile;
// insert the element into list
temp_ptr->next = _G(hag).hag_file_list;
_G(hag).hag_file_list = temp_ptr;
}
// we just opened a previously unopened hag file
else {
// add this new open hag file in exec dir into its list
temp_ptr = (Hag_Record *)mem_alloc(sizeof(Hag_Record), "Hag_File_List");
if (!temp_ptr) {
f_io_close(temp_fp);
error("creating hag_record");
hag_success = 0;
return 0;
}
Common::strcpy_s(temp_ptr->hag_name, hag_name.c_str());
temp_ptr->hag_fp = temp_fp;
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(temp_fp);
assert(rs);
if (!rs->seek(curr_hash_record.offset))
term_message("fail to fseek");
last_head_pos = rs->pos();
temp_ptr->hag_pos = curr_hash_record.offset;
temp_ptr->hagfile = curr_hash_record.hagfile;
// Insert the element into list
temp_ptr->next = _G(hag).hag_file_list;
_G(hag).hag_file_list = temp_ptr;
}
}
}
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(temp_ptr->hag_fp);
assert(rs);
// set hag file pointer to current file position //
if (!rs->seek(curr_hash_record.offset))
term_message("fail to fseek");
last_head_pos = rs->pos();
temp_ptr->hag_pos = curr_hash_record.offset;
curr_hag_record = temp_ptr;
hag_success = true;
return true;
}
uint32 SysFile::key_to_hash_address(const Common::String &src, uint32 hash_table_size) {
Common::String key = src;
key.toUppercase();
if (key.empty())
return 0;
uint32 h = key[0];
const int len = key.size();
for (int i = 1; i < len; i++)
h = ((h * 256) + key[i]) % hash_table_size; // * 256 is because one char is 8 bits
return h;
}
int SysFile::hash_search(const Common::String &fname, Hash_Record *current_hash_record_ptr, Hag_Record *current_hag_record, uint32 hash_address,
Common::SeekableReadStream *hashfp, uint32 hash_table_size, bool show_errors) {
bool found = false;
char myfilename[33];
Common::String local_name;
uint32 next_entry = hash_address;
// 4 bytes is header of hash file, store hash_table_size
uint32 offset = HASH_RECORD_LENGTH * next_entry + 4;
uint32 best_dist = 0x7fffffff;
uint32 find_offset = offset;
myfilename[0] = '\0';
while (!found) {
if (!hashfp->seek(offset))
term_message("fail to fseek");
hashfp->read(myfilename, 33);
if (myfilename[0] == '\0') {
// this hash table is empty, insert new record here
f_io_close(hashfp);
if (show_errors)
error("not found in hag file: %s", fname.c_str());
else
term_message("fclass: file not found '%s', in hag file", fname.c_str());
return 0;
}
if (fname.equalsIgnoreCase(myfilename)) {
// The new record already in hash table, do nothing
auto &r = *current_hash_record_ptr;
r.hagfile = hashfp->readByte();
r.disks = hashfp->readByte();
r.offset = hashfp->readUint32LE();
r.size = hashfp->readUint32LE();
uint32 next_record = hashfp->readUint32LE();
r.filename = myfilename;
// As long as we find a hag file, use it immedeiately
get_local_name_from_hagfile(local_name, current_hash_record_ptr->hagfile);
Common::String local_hag_name = f_extension_new(local_name, "HAG");
local_name = local_hag_name;
if (!Common::File::exists(Common::Path(local_name))) {
found = true;
find_offset = offset;
break;
}
if (current_hag_record && current_hag_record->hagfile == current_hash_record_ptr->hagfile) {
// in same hag file
if (best_dist > (uint32)ABS((int32)current_hag_record->hag_pos - (int32)current_hash_record_ptr->offset)) {
best_dist = ABS((int32)current_hag_record->hag_pos - (int32)current_hash_record_ptr->offset);
find_offset = offset;
}
} else {
find_offset = offset;
}
if (next_record == offset) {
// only one record of fname in hash table
found = true;
} else {
offset = next_record;
}
} else { // collision here, search the next entry to see if it is empty until find a empty one
next_entry = (next_entry + 1) % hash_table_size; // search the hash table a round way
offset = HASH_RECORD_LENGTH * next_entry + 4; // 4 bytes is header of hash file, store hash_table_size
}
}
// get the best close one of hag file for multiple same fname
if (find_offset != offset) {
if (!hashfp->seek(find_offset))
term_message("fail to fseek");
auto &r = *current_hash_record_ptr;
hashfp->read(myfilename, 33);
r.filename = myfilename;
r.hagfile = hashfp->readByte();
r.disks = hashfp->readByte();
r.offset = hashfp->readUint32LE();
r.size = hashfp->readUint32LE();
}
f_io_close(hashfp);
if (!found) {
error("not in hag file: %s", fname.c_str());
}
return true;
}
Common::Stream *SysFile::open_by_first_char() {
if (filename.hasPrefix("*")) {
// MADS folder file in original
_fp = f_io_open(Common::Path(filename.c_str() + 1, '/'), "rb");
} else {
_fp = f_io_open(Common::Path(filename, '/'), "rb");
}
if (!_fp) {
if (show_error_flag)
error("fclass: file not found '%s'", filename.c_str());
else
term_message("fclass: file not found '%s'", filename.c_str());
}
return nullptr;
}
bool SysFile::get_local_name_from_hagfile(Common::String &local_name, byte hagfile) {
bool found = false;
Hag_Name_Record *temp_ptr = _G(hag).hag_name_list;
while (temp_ptr && !found) {
if (temp_ptr->hagfile == hagfile) {
found = true;
local_name = temp_ptr->filename;
} else {
temp_ptr = temp_ptr->next;
}
}
return found;
}
bool SysFile::seek(uint32 pos) {
if (!_G(hag).hag_flag) {
return rs()->seek(pos);
}
if (hag_success) {
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(curr_hag_record->hag_fp);
assert(rs);
if (!rs->seek(curr_hash_record.offset + pos - curr_hag_record->hag_pos, SEEK_CUR))
term_message("fail to fseek");
last_head_pos = rs->pos();
curr_hag_record->hag_pos = curr_hash_record.offset + pos; // Change file position
return true;
}
return false;
}
bool SysFile::seek_ahead(int32 amount) {
if (!_G(hag).hag_flag) {
return rs()->seek(amount, SEEK_CUR);
}
if (hag_success) {
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(curr_hag_record->hag_fp);
assert(rs);
if (!rs->seek(amount, SEEK_CUR))
term_message("fail to fseek");
last_head_pos = rs->pos();
curr_hag_record->hag_pos += amount; // Change file position
return true;
}
return false;
}
uint32 SysFile::read(MemHandle bufferHandle) {
const int32 bytesToRead = size() - get_pos();
if (bytesToRead < 0)
error("SysFile::read - %s", filename.c_str());
return read(bufferHandle, bytesToRead);
}
int32 SysFile::read(byte *bufferHandle, int32 n) {
void *h = bufferHandle;
return read((MemHandle)&h, n);
}
int32 SysFile::read(MemHandle bufferHandle, int32 n) {
if (!bufferHandle)
error("reading %s", filename.c_str());
open_read();
if (!*bufferHandle)
mem_ReallocateHandle(bufferHandle, n, "SysFile");
if (!*bufferHandle)
error("Needed %d to read info", n);
if (!_G(hag).hag_flag) {
return (uint32)rs()->read(*bufferHandle, n);
}
// Hag mode
if (hag_success) {
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(curr_hag_record->hag_fp);
assert(rs);
rs->seek(last_head_pos);
uint32 temp_myfpos = rs->pos();
uint32 temp_size = (uint32)rs->read(*bufferHandle, n);
curr_hag_record->hag_pos = temp_myfpos + temp_size; // Change file position
last_head_pos = rs->pos();
return temp_size;
}
return 0;
}
byte SysFile::readByte() {
byte buf[1];
void *ptr = (void *)buf;
read(&ptr, 1);
return buf[0];
}
uint16 SysFile::readUint16LE() {
byte buf[2];
void *ptr = (void *)buf;
read(&ptr, 2);
return READ_LE_UINT16(buf);
}
uint32 SysFile::readUint32LE() {
byte buf[4];
void *ptr = (void *)buf;
read(&ptr, 4);
return READ_LE_UINT32(buf);
}
Common::SeekableReadStream *SysFile::rs() const {
Common::SeekableReadStream *rs = dynamic_cast<Common::SeekableReadStream *>(_fp);
assert(rs);
return rs;
}
void SysFile::close() {
delete _fp;
_fp = nullptr;
}
void sysfile_init(bool in_hag_mode) {
_G(hag).hag_flag = in_hag_mode;
if (in_hag_mode) {
const char *name = "burger";
if (g_engine->getGameType() == GType_Riddle)
name = "ripley";
else if (g_engine->isDemo() == GStyle_NonInteractiveDemo)
name = "overview";
_G(hag).hash_file = Common::Path(Common::String::format("%s.has", name));
term_message("Initialized in hag mode");
} else {
term_message("Initialized in file mode");
}
}
void sysfile_shutdown() {
Hag_Record *temp_ptr = _G(hag).hag_file_list;
while (temp_ptr) {
_G(hag).hag_file_list = _G(hag).hag_file_list->next;
f_io_close(temp_ptr->hag_fp);
mem_free(temp_ptr);
temp_ptr = _G(hag).hag_file_list;
}
}
} // namespace M4

View File

@@ -0,0 +1,162 @@
/* 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/>.
*
*/
#ifndef M4_FILEIO_SYS_FILE_H
#define M4_FILEIO_SYS_FILE_H
#include "common/file.h"
#include "m4/fileio/fileio.h"
#include "m4/mem/reloc.h"
namespace M4 {
struct Hash_Record {
Common::String filename;
byte hagfile;
byte disks;
uint32 offset;
uint32 size;
};
struct Hag_Statics {
bool hag_flag = false;
bool first_read_flag = false;
Common::Path hash_file;
Hag_Name_Record *hag_name_list = nullptr;
Hag_Record *hag_file_list = nullptr;
};
class SysFile {
private:
Common::Stream *_fp = nullptr;
FileMode fmode;
AccessMode mode = UNOPENED;
int hag_success = 0;
bool show_error_flag = true;
Hag_Record *curr_hag_record = nullptr;
Hash_Record curr_hash_record;
uint32 last_head_pos = 0;
private:
void open_read();
void open_write();
void open_read_low_level();
bool open_hash_file();
Common::String get_last_string(const Common::String &src);
/**
* Calculate key (which is string) into hash table address ie. hash table entry
*/
uint32 key_to_hash_address(const Common::String &src, uint32 hash_table_size);
/**
* Insert a hash record into HASH file depending hash_address. The algorithm is
* if hash_address entry is empty record, put the new record into this entry,
* if the hash_address entry is not empty, search next record of it, util find
* one empty entry and put the new record into the empty entry.
*/
int hash_search(const Common::String &fname, Hash_Record *current_hash_record_ptr, Hag_Record *curr_hag_record, uint32 hash_address,
Common::SeekableReadStream *hashfp, uint32 hash_table_size, bool show_error_flag);
/**
* Opens the file for access, paying attention to the first character..
* if it's a '*', it indicates a MADS folder file
*/
Common::Stream *open_by_first_char();
bool get_local_name_from_hagfile(Common::String &local_name, byte hagfile);
Common::SeekableReadStream *rs() const;
public:
Common::String filename;
public:
SysFile(const Common::String &fname, FileMode myfmode = BINARY);
/**
* Return true if the file exists
*/
bool exists();
/**
* Returns the file size
*/
uint32 size();
/**
* Gets the current position
*/
uint32 get_pos();
/**
* Seek to a given position
*/
bool seek(uint32 pos);
/**
* Seek ahead by a given amount
*/
bool seek_ahead(int32 amount);
/**
* Read data
*/
uint32 read(MemHandle bufferHandle);
int32 read(MemHandle bufferHandle, int32 n);
int32 read(byte *bufferHandle, int32 n);
/**
* Read in a 8-bit value
*/
byte readByte();
int8 readSByte() {
return (int8)readByte();
}
/**
* Read in a 16-bit value
*/
uint16 readUint16LE();
int16 readSint16LE() {
return (int16)readUint16LE();
}
/**
* Read in a 32-bit value
*/
uint32 readUint32LE();
int32 readSint32LE() {
return (int32)readUint32LE();
}
/**
* Close the file
*/
void close();
};
void sysfile_init(bool in_hag_mode);
void sysfile_shutdown();
} // namespace M4
#endif