1806 lines
56 KiB
C++
1806 lines
56 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/wscript/ws_load.h"
|
|
#include "m4/wscript/ws_machine.h"
|
|
#include "m4/wscript/wst_regs.h"
|
|
#include "m4/core/errors.h"
|
|
#include "m4/core/imath.h"
|
|
#include "m4/graphics/graphics.h"
|
|
#include "m4/mem/mem.h"
|
|
#include "m4/vars.h"
|
|
|
|
namespace M4 {
|
|
|
|
#define CHUNK_MACH 0x4D414348 //'MACH'
|
|
#define CHUNK_SEQU 0x53455155 //'SEQU'
|
|
#define CHUNK_DATA 0x44415441 //'DATA'
|
|
#define CHUNK_CELS 0x43454C53 //'CELS'
|
|
|
|
#define CHUNK_NECS 0x4E454353 //INTEL 'SCEN'
|
|
#define CHUNK_HCAM 0x4843414D //INTEL 'MACH'
|
|
#define CHUNK_UQES 0x55514553 //INTEL 'SEQU'
|
|
#define CHUNK_SLEC 0x534C4543 //INTEL 'CELS'
|
|
#define CHUNK_ATAD 0x41544144 //INTEL 'DATA'
|
|
|
|
#define MACH_NUM_STATES 0
|
|
#define MACH_OFFSETS 1
|
|
|
|
#define SEQU_NUM_VARS 0
|
|
#define SEQU_SEQU_START 1
|
|
|
|
#define DATA_REC_COUNT 0
|
|
#define DATA_REC_SIZE 1
|
|
#define DATA_REC_START 2
|
|
|
|
#define MAX_ASSET_HASH 255
|
|
|
|
/**
|
|
* This tries to encapsulate an uint32 * that also does endian conversion automatically.
|
|
* GetNextInt32 can't just return values directly, because in the case of some chunk
|
|
* types, the underlying data being pointed to gets byte swapped.
|
|
*/
|
|
class IntPointer {
|
|
private:
|
|
int32 *_ptr = nullptr;
|
|
|
|
public:
|
|
void set(int32 *ptr) { _ptr = ptr; }
|
|
void swap() { *_ptr = SWAP_INT32(*_ptr); }
|
|
int32 *ptr() const { return _ptr; }
|
|
int32 operator*() const { return READ_LE_INT32(_ptr); }
|
|
};
|
|
|
|
static int32 ProcessCELS(const char * /*assetName*/, char **parseAssetPtr, char * /*mainAssetPtr*/, char *endOfAssetBlock,
|
|
int32 **dataOffset, int32 **palDataOffset, RGB8 *myPalette);
|
|
static void RestoreSSPaletteInfo(RGB8 *myPalette, int32 *palPtr);
|
|
|
|
static bool GetNextint32(char **assetPtr, char *endOfAssetBlock, IntPointer &returnVal) {
|
|
// Check to see if we still have an int32 available
|
|
if ((endOfAssetBlock - *assetPtr) < 4) {
|
|
return false;
|
|
}
|
|
|
|
// Get the next int32
|
|
returnVal.set((int32 *)*assetPtr);
|
|
*assetPtr += 4;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool InitWSAssets() {
|
|
int32 i;
|
|
|
|
// Make sure this is only called once.
|
|
if (_GWS(wsloaderInitialized)) {
|
|
error_show(FL, 'WSSN');
|
|
}
|
|
|
|
// Allocate space for the tables used by the loader and the resource io MACHine tables
|
|
if ((_GWS(globalMACHnames) = (char **)mem_alloc(sizeof(char *) * 256, "MACH resource table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalMACHHandles) = (MemHandle *)mem_alloc(sizeof(MemHandle) * 256, "CELS Handles table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalMACHoffsets) = (int32 *)mem_alloc(sizeof(int32) * 256, "MACH offsets table")) == nullptr) {
|
|
return false;
|
|
}
|
|
for (i = 0; i < 256; i++) {
|
|
_GWS(globalMACHnames)[i] = nullptr;
|
|
_GWS(globalMACHHandles)[i] = nullptr;
|
|
_GWS(globalMACHoffsets)[i] = -1;
|
|
}
|
|
|
|
// SEQUence tables
|
|
if ((_GWS(globalSEQUnames) = (char **)mem_alloc(sizeof(char *) * 256, "SEQU resource table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalSEQUHandles) = (MemHandle *)mem_alloc(sizeof(MemHandle) * 256, "CELS Handles table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalSEQUoffsets) = (int32 *)mem_alloc(sizeof(int32) * 256, "SEQU offsets table")) == nullptr) {
|
|
return false;
|
|
}
|
|
for (i = 0; i < 256; i++) {
|
|
_GWS(globalSEQUnames)[i] = nullptr;
|
|
_GWS(globalSEQUHandles)[i] = nullptr;
|
|
_GWS(globalSEQUoffsets)[i] = -1;
|
|
}
|
|
|
|
// DATA tables
|
|
if ((_GWS(globalDATAnames) = (char **)mem_alloc(sizeof(char *) * 256, "DATA resource table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalDATAHandles) = (MemHandle *)mem_alloc(sizeof(MemHandle) * 256, "CELS Handles table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalDATAoffsets) = (int32 *)mem_alloc(sizeof(int32) * 256, "DATA offsets table")) == nullptr) {
|
|
return false;
|
|
}
|
|
for (i = 0; i < 256; i++) {
|
|
_GWS(globalDATAnames)[i] = nullptr;
|
|
_GWS(globalDATAHandles)[i] = nullptr;
|
|
_GWS(globalDATAoffsets)[i] = -1;
|
|
}
|
|
|
|
// CELS tables
|
|
if ((_GWS(globalCELSnames) = (char **)mem_alloc(sizeof(char *) * 256, "CELS resource table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalCELSHandles) = (MemHandle *)mem_alloc(sizeof(MemHandle) * 256, "CELS Handles table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalCELSoffsets) = (int32 *)mem_alloc(sizeof(int32) * 256, "CELS offsets table")) == nullptr) {
|
|
return false;
|
|
}
|
|
if ((_GWS(globalCELSPaloffsets) = (int32 *)mem_alloc(sizeof(int32) * 256, "CELS pal offsets table")) == nullptr) {
|
|
return false;
|
|
}
|
|
for (i = 0; i < 256; i++) {
|
|
_GWS(globalCELSnames)[i] = nullptr;
|
|
_GWS(globalCELSHandles)[i] = nullptr;
|
|
_GWS(globalCELSoffsets)[i] = -1;
|
|
_GWS(globalCELSPaloffsets)[i] = -1;
|
|
}
|
|
|
|
// Set the global to indicate the loader is active
|
|
_GWS(wsloaderInitialized) = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ClearWSAssets(uint32 assetType, int32 minHash, int32 maxHash) {
|
|
int32 i;
|
|
assert(maxHash >= minHash);
|
|
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
return false;
|
|
}
|
|
|
|
// Bounds checking
|
|
if (minHash < 0)
|
|
minHash = 0;
|
|
if (maxHash > MAX_ASSET_HASH)
|
|
maxHash = MAX_ASSET_HASH;
|
|
|
|
switch (assetType) {
|
|
case _WS_ASSET_MACH:
|
|
// Clear the machines table for entries [minHash, maxHash]
|
|
for (i = minHash; i <= maxHash; i++) {
|
|
terminateMachinesByHash(i);
|
|
if (_GWS(globalMACHnames)[i]) {
|
|
rtoss(_GWS(globalMACHnames)[i]);
|
|
mem_free(_GWS(globalMACHnames)[i]);
|
|
_GWS(globalMACHnames)[i] = nullptr;
|
|
_GWS(globalMACHHandles)[i] = nullptr;
|
|
_GWS(globalMACHoffsets)[i] = -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case _WS_ASSET_SEQU:
|
|
// Clear the sequences table for entries [minHash, maxHash]
|
|
for (i = minHash; i <= maxHash; i++) {
|
|
if (_GWS(globalSEQUnames)[i]) {
|
|
rtoss(_GWS(globalSEQUnames)[i]);
|
|
mem_free(_GWS(globalSEQUnames)[i]);
|
|
_GWS(globalSEQUnames)[i] = nullptr;
|
|
_GWS(globalSEQUHandles)[i] = nullptr;
|
|
_GWS(globalSEQUoffsets)[i] = -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case _WS_ASSET_DATA:
|
|
// Clear the data table for entries [minHash, maxHash]
|
|
for (i = minHash; i <= maxHash; i++) {
|
|
if (_GWS(globalDATAnames)[i]) {
|
|
rtoss(_GWS(globalDATAnames)[i]);
|
|
mem_free(_GWS(globalDATAnames)[i]);
|
|
_GWS(globalDATAnames)[i] = nullptr;
|
|
_GWS(globalDATAHandles)[i] = nullptr;
|
|
_GWS(globalDATAoffsets)[i] = -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case _WS_ASSET_CELS:
|
|
// Clear the cels tables for entries [minHash, maxHash]
|
|
for (i = minHash; i <= maxHash; i++) {
|
|
if (_GWS(globalCELSnames)[i]) {
|
|
rtoss(_GWS(globalCELSnames)[i]);
|
|
mem_free(_GWS(globalCELSnames)[i]);
|
|
_GWS(globalCELSnames)[i] = nullptr;
|
|
_GWS(globalCELSHandles)[i] = nullptr;
|
|
_GWS(globalCELSoffsets)[i] = -1;
|
|
_GWS(globalCELSPaloffsets)[i] = -1;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void ShutdownWSAssets(void) {
|
|
if (!_GWS(wsloaderInitialized))
|
|
return;
|
|
|
|
// For each asset type, clear the entire table
|
|
ClearWSAssets(_WS_ASSET_MACH, 0, MAX_ASSET_HASH);
|
|
ClearWSAssets(_WS_ASSET_SEQU, 0, MAX_ASSET_HASH);
|
|
ClearWSAssets(_WS_ASSET_CELS, 0, MAX_ASSET_HASH);
|
|
ClearWSAssets(_WS_ASSET_DATA, 0, MAX_ASSET_HASH);
|
|
|
|
// Deallocate all tables
|
|
if (_GWS(globalMACHnames)) mem_free(_GWS(globalMACHnames));
|
|
if (_GWS(globalSEQUnames)) mem_free(_GWS(globalSEQUnames));
|
|
if (_GWS(globalDATAnames)) mem_free(_GWS(globalDATAnames));
|
|
if (_GWS(globalCELSnames)) mem_free(_GWS(globalCELSnames));
|
|
|
|
if (_GWS(globalMACHHandles)) mem_free(_GWS(globalMACHHandles));
|
|
if (_GWS(globalMACHoffsets)) mem_free(_GWS(globalMACHoffsets));
|
|
if (_GWS(globalSEQUHandles)) mem_free(_GWS(globalSEQUHandles));
|
|
if (_GWS(globalSEQUoffsets)) mem_free(_GWS(globalSEQUoffsets));
|
|
if (_GWS(globalDATAHandles)) mem_free(_GWS(globalDATAHandles));
|
|
if (_GWS(globalDATAoffsets)) mem_free(_GWS(globalDATAoffsets));
|
|
if (_GWS(globalCELSHandles)) mem_free(_GWS(globalCELSHandles));
|
|
if (_GWS(globalCELSoffsets)) mem_free(_GWS(globalCELSoffsets));
|
|
if (_GWS(globalCELSPaloffsets)) mem_free(_GWS(globalCELSPaloffsets));
|
|
|
|
_GWS(wsloaderInitialized) = false;
|
|
}
|
|
|
|
bool LoadWSAssets(const char *wsAssetName) {
|
|
return LoadWSAssets(wsAssetName, _G(master_palette));
|
|
}
|
|
|
|
bool LoadWSAssets(const char *wsAssetName, RGB8 *myPalette) {
|
|
char *parseAssetPtr;
|
|
uint32 *tempPtr;
|
|
IntPointer chunkType, chunkSize, chunkHash;
|
|
int32 *celsPtr, *palPtr;
|
|
int32 i;
|
|
int32 assetSize;
|
|
|
|
// Check that the loader has been initialized
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
error_show(FL, 'WSLI');
|
|
}
|
|
|
|
// Use the resource io manager to read in the entire block
|
|
const MemHandle workHandle = rget(wsAssetName, &assetSize);
|
|
if (workHandle == nullptr) {
|
|
error_show(FL, 'FNF!', "Asset Name: %s", wsAssetName);
|
|
}
|
|
|
|
// Lock the handle so we can step through the chunk
|
|
HLock(workHandle);
|
|
char *mainAssetPtr = (char *)(*workHandle);
|
|
|
|
char *endOfAssetBlock = (char *)((intptr)mainAssetPtr + assetSize);
|
|
|
|
parseAssetPtr = mainAssetPtr;
|
|
|
|
// Set the finished flag
|
|
bool finished = false;
|
|
|
|
// Get the first chunkType
|
|
if (!GetNextint32(&parseAssetPtr, endOfAssetBlock, chunkType)) {
|
|
finished = true;
|
|
}
|
|
|
|
// Process each chunk according to type
|
|
while (!finished) {
|
|
// Read in the chunk size and hash number
|
|
if (!GetNextint32(&parseAssetPtr, endOfAssetBlock, chunkSize)) {
|
|
error_show(FL, 'WSLE', "Asset Name: %s", wsAssetName);
|
|
}
|
|
if (!GetNextint32(&parseAssetPtr, endOfAssetBlock, chunkHash)) {
|
|
error_show(FL, 'WSLE', "Asset Name: %s", wsAssetName);
|
|
}
|
|
|
|
// Process the chunk according to type
|
|
bool chunkSwap = false;
|
|
switch (*chunkType) {
|
|
// Chunk is a machine chunk
|
|
case CHUNK_HCAM:
|
|
// Byte swap the type, size and hash and continue through case CHUNK_MACH.
|
|
chunkType.swap();
|
|
chunkSize.swap();
|
|
chunkHash.swap();
|
|
chunkSwap = true;
|
|
// Fall through
|
|
|
|
case CHUNK_MACH:
|
|
// Check the validity of the machine hash number, and clear it
|
|
if (*chunkHash > MAX_ASSET_HASH) {
|
|
error_show(FL, 'WSLA', "Asset Name: %s, MACH hash was: %d", wsAssetName, *chunkHash);
|
|
}
|
|
ClearWSAssets(_WS_ASSET_MACH, *chunkHash, *chunkHash);
|
|
|
|
// Store the resource name, and the offset into the resource block
|
|
_GWS(globalMACHnames)[*chunkHash] = mem_strdup(wsAssetName);
|
|
_GWS(globalMACHHandles)[*chunkHash] = workHandle;
|
|
_GWS(globalMACHoffsets)[*chunkHash] = parseAssetPtr - mainAssetPtr;
|
|
|
|
// Check that the assetblocksize is big enough that the chunk body was read in...
|
|
if ((endOfAssetBlock - parseAssetPtr) < (int)(*chunkSize - 12)) {
|
|
error_show(FL, 'WSLE', "Asset Name: %s, MACH hash was: %d", wsAssetName, *chunkHash);
|
|
}
|
|
|
|
// Byteswap the entire machine if necessary
|
|
if (chunkSwap) {
|
|
tempPtr = (uint32 *)parseAssetPtr;
|
|
for (i = 0; i < (*chunkSize - 12) >> 2; i++) { //>>2 - chunkSize is bytes, not int32s
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
}
|
|
|
|
// Update the assetPtr to the beginning of the next chunk
|
|
parseAssetPtr += *chunkSize - 12;
|
|
break;
|
|
|
|
case CHUNK_UQES:
|
|
// Chunk is a machine chunk
|
|
// Byte swap the type, size and hash and continue through case CHUNK_SEQU.
|
|
chunkType.swap();
|
|
chunkSize.swap();
|
|
chunkHash.swap();
|
|
chunkSwap = true;
|
|
// Fall through
|
|
|
|
case CHUNK_SEQU:
|
|
// Check the validity of the sequence hash number, and clear it
|
|
if (*chunkHash > MAX_ASSET_HASH) {
|
|
error_show(FL, 'WSLA', "Asset Name: %s, SEQU hash was: %d", wsAssetName, *chunkHash);
|
|
}
|
|
ClearWSAssets(_WS_ASSET_SEQU, *chunkHash, *chunkHash);
|
|
|
|
// Store the resource name, and the offset into the resource block
|
|
_GWS(globalSEQUnames)[*chunkHash] = mem_strdup(wsAssetName);
|
|
_GWS(globalSEQUHandles)[*chunkHash] = workHandle;
|
|
_GWS(globalSEQUoffsets)[*chunkHash] = (intptr)parseAssetPtr - (intptr)mainAssetPtr;
|
|
|
|
// Check that the assetblocksize is big enough that the chunk body was read in...
|
|
if ((endOfAssetBlock - parseAssetPtr) < (int)(*chunkSize - 12)) {
|
|
error_show(FL, 'WSLE', "Asset Name: %s, SEQU hash was: %d", wsAssetName, *chunkHash);
|
|
}
|
|
|
|
// Byteswap the entire sequence if necessary
|
|
if (chunkSwap) {
|
|
tempPtr = (uint32 *)parseAssetPtr;
|
|
for (i = 0; i < (*chunkSize - 12) >> 2; i++) { //>>2 - chunkSize is bytes, not int32s
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
}
|
|
|
|
// Update the assetPtr to the beginning of the next chunk
|
|
parseAssetPtr += *chunkSize - 12;
|
|
break;
|
|
|
|
case CHUNK_ATAD:
|
|
// Chunk is a data chunk
|
|
// Byte swap the type, size and hash and continue through case CHUNK_DATA.
|
|
chunkType.swap();
|
|
chunkSize.swap();
|
|
chunkHash.swap();
|
|
chunkSwap = true;
|
|
// Fall through
|
|
|
|
case CHUNK_DATA:
|
|
// Check the validity of the data block hash number, and clear it
|
|
if (*chunkHash > MAX_ASSET_HASH) {
|
|
error_show(FL, 'WSLA', "Asset Name: %s, DATA hash was: %d", wsAssetName, *chunkHash);
|
|
}
|
|
ClearWSAssets(_WS_ASSET_DATA, *chunkHash, *chunkHash);
|
|
|
|
// Store the resource name, and the offset into the resource block
|
|
_GWS(globalDATAnames)[*chunkHash] = mem_strdup(wsAssetName);
|
|
_GWS(globalDATAHandles)[*chunkHash] = workHandle;
|
|
_GWS(globalDATAoffsets)[*chunkHash] = (intptr)parseAssetPtr - (intptr)mainAssetPtr;
|
|
|
|
// Check that the assetblocksize is big enough that the chunk body was read in...
|
|
if ((endOfAssetBlock - parseAssetPtr) < (int)(*chunkSize - 12)) {
|
|
error_show(FL, 'WSLE', "Asset Name: %s, DATA hash was: %d", wsAssetName, *chunkHash);
|
|
}
|
|
|
|
// Byteswap the entire data block if necessary
|
|
if (chunkSwap) {
|
|
tempPtr = (uint32 *)parseAssetPtr;
|
|
for (i = 0; i < (*chunkSize - 12) >> 2; i++) { //>>2 - chunkSize is bytes, not int32s
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
}
|
|
|
|
// Update the assetPtr to the beginning of the next chunk
|
|
parseAssetPtr += *chunkSize - 12;
|
|
break;
|
|
|
|
case CHUNK_SLEC:
|
|
// Byte swap the type, size and hash and continue through case CHUNK_CELS.
|
|
chunkType.swap();
|
|
chunkSize.swap();
|
|
chunkHash.swap();
|
|
chunkSwap = true;
|
|
// Fall through
|
|
|
|
case CHUNK_CELS: {
|
|
// Check the validity of the cels hash number, and clear it
|
|
if (*chunkHash > MAX_ASSET_HASH) {
|
|
error_show(FL, 'WSLA', "Asset Name: %s, CELS hash was: %d", wsAssetName, *chunkHash);
|
|
}
|
|
|
|
ClearWSAssets(_WS_ASSET_CELS, *chunkHash, *chunkHash);
|
|
|
|
// Store the resource name
|
|
_GWS(globalCELSnames)[*chunkHash] = mem_strdup(wsAssetName);
|
|
|
|
// Process the SS from the stream file
|
|
if (ProcessCELS(wsAssetName, &parseAssetPtr, mainAssetPtr, endOfAssetBlock, &celsPtr, &palPtr, myPalette) < 0) {
|
|
error_show(FL, 'WSLP', "Asset Name: %s, CELS hash was: %d", wsAssetName, *chunkHash);
|
|
}
|
|
|
|
// At this point, celsPtr points to the beginning of the cels data, palPtr to the pal data
|
|
// Store the Handle, and calculate the offsets
|
|
_GWS(globalCELSHandles)[*chunkHash] = workHandle;
|
|
if (celsPtr) {
|
|
_GWS(globalCELSoffsets)[*chunkHash] = (intptr)celsPtr - (intptr)mainAssetPtr;
|
|
} else {
|
|
_GWS(globalCELSoffsets)[*chunkHash] = -1;
|
|
}
|
|
if (palPtr) {
|
|
_GWS(globalCELSPaloffsets)[*chunkHash] = (intptr)palPtr - (intptr)mainAssetPtr;
|
|
} else {
|
|
_GWS(globalCELSPaloffsets)[*chunkHash] = -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
error_show(FL, 'WSLT', "Asset Name: %s, %d bytes into the file.", wsAssetName,
|
|
(intptr)parseAssetPtr - 12 - (intptr)mainAssetPtr);
|
|
break;
|
|
}
|
|
|
|
// Read the next chunkType, or signal we are finished
|
|
if (!GetNextint32(&parseAssetPtr, endOfAssetBlock, chunkType)) {
|
|
finished = true;
|
|
}
|
|
}
|
|
|
|
// Unlock the handle now
|
|
HUnLock(workHandle);
|
|
return true;
|
|
}
|
|
|
|
M4sprite *CreateSprite(MemHandle resourceHandle, int32 handleOffset, int32 index, M4sprite *mySprite, bool *streamSeries) {
|
|
// Parameter verification
|
|
if ((!resourceHandle) || (!*resourceHandle)) {
|
|
ws_LogErrorMsg(FL, "No sprite source in memory.");
|
|
return nullptr;
|
|
}
|
|
|
|
if (!mySprite) {
|
|
mySprite = (M4sprite *)mem_alloc(sizeof(M4sprite), "Sprite");
|
|
if (!mySprite) {
|
|
ws_LogErrorMsg(FL, "Out of memory - mem requested: %d.", sizeof(M4sprite));
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Find the cels source from the asset block
|
|
HLock(resourceHandle);
|
|
uint32 *celsPtr = (uint32 *)((intptr)*resourceHandle + handleOffset);
|
|
|
|
// Check that the index into the series requested is within a valid range
|
|
const uint32 numCels = FROM_LE_32(celsPtr[CELS_COUNT]);
|
|
if (index >= (int32)numCels) {
|
|
ws_LogErrorMsg(FL, "CreateSprite: Sprite index out of range - max index: %d, requested index: %d", numCels - 1, index);
|
|
return nullptr;
|
|
}
|
|
|
|
// Find the offset table, and the beginning of the data for all sprites
|
|
uint32 *offsets = &celsPtr[CELS_OFFSETS];
|
|
uint32 *data = &celsPtr[CELS_OFFSETS + numCels];
|
|
|
|
// Find the sprite data for the specific sprite in the series
|
|
uint32 *myCelSource = (uint32 *)((intptr)data + FROM_LE_32(offsets[index]));
|
|
|
|
// Set the stream boolean
|
|
if (streamSeries) {
|
|
if (FROM_LE_32(myCelSource[CELS_STREAM]))
|
|
*streamSeries = true;
|
|
else
|
|
*streamSeries = false;
|
|
}
|
|
|
|
// Initialize the sprite struct and return it
|
|
mySprite->next = mySprite->prev = nullptr;
|
|
mySprite->sourceHandle = resourceHandle;
|
|
mySprite->xOffset = FROM_LE_32(myCelSource[CELS_X]);
|
|
mySprite->yOffset = FROM_LE_32(myCelSource[CELS_Y]);
|
|
mySprite->w = FROM_LE_32(myCelSource[CELS_W]);
|
|
mySprite->h = FROM_LE_32(myCelSource[CELS_H]);
|
|
mySprite->encoding = (uint8)FROM_LE_32(myCelSource[CELS_COMP]);
|
|
mySprite->data = (uint8 *)&myCelSource[CELS_DATA];
|
|
|
|
if ((mySprite->w > 0) && (mySprite->h > 0)) {
|
|
mySprite->sourceOffset = (int32)((intptr)mySprite->data - (intptr)*resourceHandle);
|
|
} else {
|
|
mySprite->sourceOffset = 0;
|
|
}
|
|
|
|
// This value MUST be set before sprite draws are called after the block has been locked
|
|
mySprite->data = nullptr;
|
|
|
|
// Unlock the handle
|
|
HUnLock(resourceHandle);
|
|
|
|
return mySprite;
|
|
}
|
|
|
|
int32 LoadSpriteSeries(const char *assetName, MemHandle *seriesHandle, int32 *celsOffset, int32 *palOffset, RGB8 *myPalette) {
|
|
MemHandle workHandle;
|
|
int32 *celsPtr, *palPtr;
|
|
char *parseAssetPtr;
|
|
int32 assetSize;
|
|
|
|
// This loads a sprite series into the provided vars, rather than the WS tables.
|
|
// The WS loader is not involved with this procedure.
|
|
|
|
// Load in the sprite series
|
|
if ((workHandle = rget(assetName, &assetSize)) == nullptr)
|
|
error_show(FL, 'FNF!', "Sprite series: %s", assetName);
|
|
|
|
HLock(workHandle);
|
|
|
|
char *mainAssetPtr = (char *)*workHandle;
|
|
char *endOfAssetBlock = (char *)((intptr)mainAssetPtr + assetSize);
|
|
parseAssetPtr = mainAssetPtr;
|
|
|
|
// Process the SS from the stream file
|
|
const int32 celsSize = ProcessCELS(assetName, &parseAssetPtr, mainAssetPtr, endOfAssetBlock, &celsPtr, &palPtr, myPalette);
|
|
if (celsSize < 0) {
|
|
error_show(FL, 'WSLP', "series: %s", assetName);
|
|
}
|
|
|
|
// Store the handle and offsets
|
|
*seriesHandle = workHandle;
|
|
*celsOffset = (intptr)celsPtr - (intptr)mainAssetPtr;
|
|
*palOffset = (intptr)palPtr - (intptr)mainAssetPtr;
|
|
|
|
HUnLock(workHandle);
|
|
|
|
return celsSize;
|
|
}
|
|
|
|
int32 LoadSpriteSeriesDirect(const char *assetName, MemHandle *seriesHandle, int32 *celsOffset, int32 *palOffset, RGB8 *myPalette) {
|
|
Common::File f;
|
|
int32 *celsPtr, *palPtr;
|
|
char *parseAssetPtr;
|
|
|
|
// This loads a sprite series into the provided vars, rather than the WS tables.
|
|
// The WS loader is not involved with this procedure.
|
|
|
|
// First open the file
|
|
if (!f.open(assetName))
|
|
return -1;
|
|
|
|
// Get the file size
|
|
const uint32 assetSize = f.size();
|
|
|
|
// Create a handle big enough to hold the contents of the file
|
|
const MemHandle workHandle = NewHandle(assetSize, "ss file");
|
|
|
|
// Lock the handle and read the contents of the file into it
|
|
HLock(workHandle);
|
|
char *mainAssetPtr = (char *)*workHandle;
|
|
if (f.read(mainAssetPtr, assetSize) < assetSize) {
|
|
mem_free(workHandle);
|
|
return -1;
|
|
}
|
|
|
|
// Close the file
|
|
f.close();
|
|
|
|
// Set up some pointers
|
|
char *endOfAssetBlock = (char *)((intptr)mainAssetPtr + assetSize);
|
|
parseAssetPtr = mainAssetPtr;
|
|
|
|
// Process the SS from the stream file
|
|
const int32 celsSize = ProcessCELS(assetName, &parseAssetPtr, mainAssetPtr, endOfAssetBlock, &celsPtr, &palPtr, myPalette);
|
|
if (celsSize < 0) {
|
|
error_show(FL, 'WSLP', "series: %s", assetName);
|
|
}
|
|
|
|
// Store the handle and offsets
|
|
*seriesHandle = workHandle;
|
|
*celsOffset = (intptr)celsPtr - (intptr)mainAssetPtr;
|
|
*palOffset = (intptr)palPtr - (intptr)mainAssetPtr;
|
|
HUnLock(workHandle);
|
|
|
|
return celsSize;
|
|
}
|
|
|
|
bool ws_GetSSMaxWH(MemHandle ssHandle, int32 ssOffset, int32 *maxW, int32 *maxH) {
|
|
// Parameter verification
|
|
if ((!ssHandle) || (!*ssHandle)) {
|
|
ws_LogErrorMsg(FL, "nullptr Handle given.");
|
|
return false;
|
|
}
|
|
|
|
// Lock the handle, and get the cels source
|
|
HLock(ssHandle);
|
|
int32 *celsPtr = (int32 *)((intptr)*ssHandle + ssOffset);
|
|
|
|
// Return the values
|
|
if (maxW) {
|
|
*maxW = FROM_LE_32(celsPtr[CELS_SS_MAX_W]);
|
|
}
|
|
|
|
if (maxH) {
|
|
*maxH = FROM_LE_32(celsPtr[CELS_SS_MAX_H]);
|
|
}
|
|
|
|
// unlock the handle
|
|
HUnLock(ssHandle);
|
|
|
|
return true;
|
|
}
|
|
|
|
int32 AddWSAssetCELS(const char *wsAssetName, int32 hash, RGB8 *myPalette) {
|
|
MemHandle workHandle;
|
|
char *parseAssetPtr;
|
|
int32 i, assetSize, *celsPtr, *palPtr;
|
|
|
|
// Check that the loader has been initialized
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
error_show(FL, 'WSLI', "Asset Name: %s", wsAssetName);
|
|
}
|
|
|
|
int32 emptySlot = -1;
|
|
|
|
// If hash is < 0, find the first available slot
|
|
if (hash < 0) {
|
|
// Search through the SS names table
|
|
for (i = 0; i <= MAX_ASSET_HASH; i++) {
|
|
// See if there is something loaded in this slot
|
|
if (_GWS(globalCELSnames)[i]) {
|
|
if (!strcmp(_GWS(globalCELSnames)[i], wsAssetName)) {
|
|
break;
|
|
}
|
|
} else if (emptySlot < 0) {
|
|
// Else we found an empty slot
|
|
emptySlot = i;
|
|
}
|
|
}
|
|
} else {
|
|
// Else the SS must be stored in the given hash, replacing any previous contents.
|
|
// Index checking
|
|
if (hash > MAX_ASSET_HASH) {
|
|
error_show(FL, 'WSLA', "Asset Name: %s, hash given was %d", wsAssetName, hash);
|
|
}
|
|
|
|
// Check to see if the SS is already loaded in the given hash slot
|
|
if (_GWS(globalCELSnames)[hash] && (!strcmp(_GWS(globalCELSnames)[hash], wsAssetName))) {
|
|
if (_GWS(globalCELSPaloffsets)[hash] >= 0) {
|
|
// Get the pointer to the pal data
|
|
workHandle = _GWS(globalCELSHandles)[hash];
|
|
palPtr = (int32 *)((intptr)*workHandle + _GWS(globalCELSPaloffsets)[hash]);
|
|
|
|
// Restore the palette and unlock the handle
|
|
RestoreSSPaletteInfo(myPalette, palPtr);
|
|
HUnLock(workHandle);
|
|
}
|
|
|
|
// Since the SS is already loaded, return the slot
|
|
return hash;
|
|
} else {
|
|
// The series is not already loaded, set up values for the next if statement
|
|
i = MAX_ASSET_HASH + 1;
|
|
emptySlot = hash;
|
|
}
|
|
}
|
|
|
|
// If we've searched the entire table and not found the series, but
|
|
// we found an empty slot to load the SS into
|
|
if ((i > MAX_ASSET_HASH) && (emptySlot >= 0)) {
|
|
workHandle = rget(wsAssetName, &assetSize);
|
|
if (workHandle == nullptr) {
|
|
error_show(FL, 'FNF!', wsAssetName);
|
|
}
|
|
|
|
// Lock the handle so we can step through the chunk
|
|
HLock(workHandle);
|
|
char *mainAssetPtr = (char *)(*workHandle);
|
|
|
|
parseAssetPtr = mainAssetPtr;
|
|
char *endOfAssetBlock = (char *)((intptr)mainAssetPtr + assetSize);
|
|
|
|
ClearWSAssets(_WS_ASSET_CELS, emptySlot, emptySlot);
|
|
|
|
// Store the resource name
|
|
_GWS(globalCELSnames)[emptySlot] = mem_strdup(wsAssetName);
|
|
|
|
// Process the SS from the stream file
|
|
if (ProcessCELS(wsAssetName, &parseAssetPtr, mainAssetPtr, endOfAssetBlock, &celsPtr, &palPtr, myPalette) < 0) {
|
|
error_show(FL, 'WSLP', "Asset Name: %s", wsAssetName);
|
|
}
|
|
|
|
// At this point, celsPtr points to the beginning of the cels data, palPtr to the pal data
|
|
// Store the Handle, and calculate the offsets
|
|
_GWS(globalCELSHandles)[emptySlot] = workHandle;
|
|
if (celsPtr) {
|
|
_GWS(globalCELSoffsets)[emptySlot] = (intptr)celsPtr - (intptr)mainAssetPtr;
|
|
} else {
|
|
_GWS(globalCELSoffsets)[emptySlot] = -1;
|
|
}
|
|
if (palPtr) {
|
|
_GWS(globalCELSPaloffsets)[emptySlot] = (intptr)palPtr - (intptr)mainAssetPtr;
|
|
} else {
|
|
_GWS(globalCELSPaloffsets)[emptySlot] = -1;
|
|
}
|
|
|
|
// Unlock the handle
|
|
HUnLock(workHandle);
|
|
|
|
return emptySlot;
|
|
}
|
|
|
|
if (i < MAX_ASSET_HASH) {
|
|
// Else if we found the SS already loaded
|
|
if (_GWS(globalCELSPaloffsets)[i] >= 0) {
|
|
// Get the pointer to the pal data
|
|
workHandle = _GWS(globalCELSHandles)[i];
|
|
HLock(workHandle);
|
|
palPtr = (int32 *)((intptr)*workHandle + _GWS(globalCELSPaloffsets)[i]);
|
|
|
|
// Restore the palette and unlock the handle
|
|
RestoreSSPaletteInfo(myPalette, palPtr);
|
|
HUnLock(workHandle);
|
|
}
|
|
|
|
// Return the hash number for the series
|
|
return i;
|
|
} else {
|
|
// Else we searched the entire table, it was not already loaded, and there are no empty slots
|
|
error_show(FL, 'WSLF', "Asset Name: %s", wsAssetName);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int32 ProcessCELS(const char * /*assetName*/, char **parseAssetPtr, char * /*mainAssetPtr*/, char *endOfAssetBlock,
|
|
int32 **dataOffset, int32 **palDataOffset, RGB8 *myPalette) {
|
|
IntPointer celsType, numColors, celsSize;
|
|
int32 *tempPtr, i, j;
|
|
IntPointer header, format;
|
|
|
|
if (!_GWS(wsloaderInitialized))
|
|
return -1;
|
|
|
|
*dataOffset = nullptr;
|
|
*palDataOffset = nullptr;
|
|
|
|
// Get the header and the format
|
|
if (!GetNextint32(parseAssetPtr, endOfAssetBlock, header)) {
|
|
ws_LogErrorMsg(FL, "Unable to get the SS header");
|
|
return -1;
|
|
}
|
|
if (!GetNextint32(parseAssetPtr, endOfAssetBlock, format)) {
|
|
ws_LogErrorMsg(FL, "Unable to get the SS format");
|
|
return -1;
|
|
}
|
|
|
|
// Make sure the file is tagged "M4SS" (or "SS4M")
|
|
if (*header == HEAD_SS4M) {
|
|
header.swap();
|
|
format.swap();
|
|
} else if (*header != HEAD_M4SS) {
|
|
ws_LogErrorMsg(FL, "SS chunk is not a valid M4SS chunk.");
|
|
return -1;
|
|
}
|
|
|
|
// Verify the format is recent. This is a version control
|
|
if (*format < SS_FORMAT) {
|
|
ws_LogErrorMsg(FL, "Format is antique and cannot be read - rebuild series.");
|
|
return -1;
|
|
}
|
|
|
|
// Get the CELS chunk type - either PAL information, or the actual SS info
|
|
if (!GetNextint32(parseAssetPtr, endOfAssetBlock, celsType)) {
|
|
ws_LogErrorMsg(FL, "Unable to read the SS chunk type.");
|
|
return -1;
|
|
}
|
|
|
|
bool byteSwap = false;
|
|
// If the chunk is PAL info - celsType == CELS_LAP_ indicates the chunk needs to be byte-swapped.
|
|
if ((*celsType == CELS__PAL) || (*celsType == CELS_LAP_)) {
|
|
// Read the chunk size, and the number of palette colors, and byte-swap if necessary
|
|
if (!GetNextint32(parseAssetPtr, endOfAssetBlock, celsSize)) {
|
|
ws_LogErrorMsg(FL, "Unable to read the SS PAL chunk size.");
|
|
return -1;
|
|
}
|
|
if (!GetNextint32(parseAssetPtr, endOfAssetBlock, numColors)) {
|
|
ws_LogErrorMsg(FL, "Unable to read the SS PAL number of colors.");
|
|
return -1;
|
|
}
|
|
|
|
if (*celsType == CELS_LAP_) {
|
|
celsType.swap();
|
|
celsSize.swap();
|
|
numColors.swap();
|
|
byteSwap = true;
|
|
}
|
|
|
|
// Verify that we actually got legitimate values
|
|
if (((int32)(*celsSize) <= 0) || ((int32)(*numColors) <= 0)) {
|
|
ws_LogErrorMsg(FL, "Pal info has been corrupted");
|
|
return -1;
|
|
}
|
|
|
|
// The asset block offset for palData should begin with the number of colors
|
|
*palDataOffset = numColors.ptr();
|
|
int32 *palData = numColors.ptr();
|
|
|
|
if (((intptr)endOfAssetBlock - (intptr)(*parseAssetPtr)) < ((int32)(*celsSize) - 8)) {
|
|
ws_LogErrorMsg(FL, "Pal info is larger than asset block.");
|
|
return -1;
|
|
}
|
|
|
|
// If the chunk is in the wrong format, byte-swap the entire chunk
|
|
// Note: we do this because we want the data stored in nrgb format
|
|
// The data is always read in low byte first, but we need it high byte first
|
|
// regardless of the endianness of the machine.
|
|
if (byteSwap) {
|
|
tempPtr = numColors.ptr() + 1;
|
|
for (i = 0; i < *numColors; i++) {
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
}
|
|
|
|
*parseAssetPtr += *numColors << 2;
|
|
|
|
// The palette info has been processed, now it can be stored
|
|
if (myPalette) {
|
|
tempPtr = (int32 *)(&palData[1]);
|
|
for (i = 0; i < *numColors; i++) {
|
|
uint rgb = READ_LE_UINT32(tempPtr);
|
|
j = (rgb & 0xff000000) >> 24;
|
|
myPalette[j].r = (rgb & 0x00ff0000) >> 14;
|
|
myPalette[j].g = (rgb & 0x0000ff00) >> 6;
|
|
myPalette[j].b = (rgb & 0x000000ff) << 2;
|
|
tempPtr++;
|
|
}
|
|
}
|
|
|
|
byteSwap = false;
|
|
// Pal chunk has been processed, get the next chunk type
|
|
if (!GetNextint32(parseAssetPtr, endOfAssetBlock, celsType)) {
|
|
ws_LogErrorMsg(FL, "Unable to read the SS chunk type.");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// The chunk header must now be the SS information (possibly byte-swapped)
|
|
if ((*celsType != CELS___SS) && (*celsType != CELS_SS__)) {
|
|
ws_LogErrorMsg(FL, "SS chunk type is invalid.");
|
|
return -1;
|
|
}
|
|
|
|
// Read in the chunk size
|
|
if (!GetNextint32(parseAssetPtr, endOfAssetBlock, celsSize)) {
|
|
ws_LogErrorMsg(FL, "Unable to read the SS chunk size.");
|
|
return -1;
|
|
}
|
|
|
|
// Byteswap if necessary
|
|
if (*celsType == CELS_SS__) {
|
|
celsType.swap();
|
|
celsSize.swap();
|
|
byteSwap = true;
|
|
}
|
|
|
|
// The asset block offset for the cel should begin with the celsType
|
|
*dataOffset = (int32 *)celsType.ptr();
|
|
int32 *data = (int32 *)celsType.ptr();
|
|
|
|
// Verify that we actually got legitimate values
|
|
if ((int32)(*celsSize) <= 0) {
|
|
ws_LogErrorMsg(FL, "SS info has been corrupted");
|
|
return -1;
|
|
}
|
|
|
|
if (((intptr)endOfAssetBlock - (intptr)data) < (int32)*celsSize) {
|
|
ws_LogErrorMsg(FL, "SS info is larger than asset block.");
|
|
return -1;
|
|
}
|
|
|
|
// Check to see if we need to byte-swap the header information.
|
|
if (byteSwap) {
|
|
// The chunk header begins at (*data)[2]
|
|
// byte-swap the entire chunk header
|
|
tempPtr = &(data[2]);
|
|
for (i = 0; i < SS_HEAD_SIZE - 2; i++) {
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
|
|
if (FROM_LE_32(data[CELS_COUNT]) <= 0) {
|
|
ws_LogErrorMsg(FL, "SS info has been corrupted");
|
|
return -1;
|
|
}
|
|
|
|
// The chunk header has been byteswapped, now we must byte-swap the table of
|
|
// offsets into the chunk, which indicate where each individual sprite can be found.
|
|
int32 *offsetPtr = &(data[CELS_OFFSETS]);
|
|
tempPtr = offsetPtr;
|
|
for (i = 0; i < (int32)FROM_LE_32(data[CELS_COUNT]); i++) {
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
|
|
// dataPtr points to the beginning of the block which is a concatenation of
|
|
// all the sprites. Loop through and byteswap each individual sprite header.
|
|
int32 *dataPtr = tempPtr;
|
|
for (i = 0; i < (int32)FROM_LE_32(data[CELS_COUNT]); i++) {
|
|
// The beginning of sprite i is the dataPtr + the number of bytes in the offset table
|
|
tempPtr = (int32 *)((intptr)dataPtr + offsetPtr[i]);
|
|
|
|
// Byteswap the individual sprite's header
|
|
for (j = 0; j < SS_INDV_HEAD; j++) {
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the size of the chunk containing the sprite series info
|
|
return *celsSize;
|
|
}
|
|
|
|
static void RestoreSSPaletteInfo(RGB8 *myPalette, int32 *palPtr) {
|
|
// Parameter verification
|
|
if ((!myPalette) || (!palPtr))
|
|
return;
|
|
|
|
// Set up a pointer that can step through the pal info for the SS, and restore each color
|
|
if (myPalette) {
|
|
uint32 *tempPtr = (uint32 *)(&palPtr[1]);
|
|
for (uint32 i = 0; i < FROM_LE_32(palPtr[0]); i++) {
|
|
uint32 rgb = READ_LE_UINT32(tempPtr);
|
|
uint32 j = (rgb & 0xff000000) >> 24;
|
|
myPalette[j].r = (rgb & 0x00ff0000) >> 14;
|
|
myPalette[j].g = (rgb & 0x0000ff00) >> 6;
|
|
myPalette[j].b = (rgb & 0x000000ff) << 2;
|
|
tempPtr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
M4sprite *GetWSAssetSprite(char *spriteName, uint32 hash, uint32 index, M4sprite *mySprite, bool *streamSeries) {
|
|
int32 i;
|
|
|
|
// Ensure wsloader has been initialized
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return nullptr;
|
|
}
|
|
|
|
// If spriteName is specified, we search by name, otherwise hash is assumed to be correct
|
|
if (spriteName) {
|
|
if (!_GWS(globalCELSnames)) return nullptr;
|
|
for (i = 0; i <= MAX_ASSET_HASH; i++) {
|
|
if (!strcmp(spriteName, _GWS(globalCELSnames)[i])) {
|
|
break;
|
|
}
|
|
}
|
|
hash = i;
|
|
}
|
|
|
|
// Check for valid index, and sprite loaded at that index
|
|
if (hash > MAX_ASSET_HASH) {
|
|
if (spriteName) {
|
|
ws_LogErrorMsg(FL, "Sprite series is not in memory: %s.", spriteName);
|
|
} else {
|
|
ws_LogErrorMsg(FL, "Series number out of range: requested num: %d.", hash);
|
|
}
|
|
}
|
|
|
|
// Get the resource handle
|
|
MemHandle workHandle = _GWS(globalCELSHandles)[hash];
|
|
|
|
// Create the sprite
|
|
mySprite = CreateSprite(workHandle, _GWS(globalCELSoffsets)[hash], index, mySprite, streamSeries);
|
|
|
|
// Check the sprite
|
|
if (!mySprite) {
|
|
ws_LogErrorMsg(FL, "Series: %s, Hash: %d, index: %d", _GWS(globalCELSnames)[hash], hash, index);
|
|
}
|
|
|
|
return mySprite;
|
|
}
|
|
|
|
|
|
int32 LoadSpriteSeries(const char *assetName, Handle *seriesHandle, int32 *celsOffset, int32 *palOffset, RGB8 *myPalette) {
|
|
int32 *celsPtr, *palPtr;
|
|
char *parseAssetPtr;
|
|
int32 assetSize;
|
|
|
|
//This loads a sprite series into the provided vars, rather than the WS tables.
|
|
//The WS loader is not involved with this procedure.
|
|
|
|
// Load in the sprite series
|
|
const MemHandle workHandle = rget(assetName, &assetSize);
|
|
if (workHandle == nullptr)
|
|
error_show(FL, 'FNF!', "Sprite series: %s", assetName);
|
|
|
|
HLock(workHandle);
|
|
|
|
char *mainAssetPtr = (char *)*workHandle;
|
|
char *endOfAssetBlock = (char *)((intptr)mainAssetPtr + assetSize);
|
|
parseAssetPtr = mainAssetPtr;
|
|
|
|
// Process the SS from the stream file
|
|
const int32 celsSize = ProcessCELS(assetName, &parseAssetPtr, mainAssetPtr, endOfAssetBlock, &celsPtr, &palPtr, myPalette);
|
|
if (celsSize < 0) {
|
|
error_show(FL, 'WSLP', "series: %s", assetName);
|
|
}
|
|
|
|
// Store the handle and offsets
|
|
*seriesHandle = workHandle;
|
|
*celsOffset = (intptr)celsPtr - (intptr)mainAssetPtr;
|
|
*palOffset = (intptr)palPtr - (intptr)mainAssetPtr;
|
|
|
|
HUnLock(workHandle);
|
|
|
|
return celsSize;
|
|
}
|
|
|
|
int32 LoadSpriteSeriesDirect(const char *assetName, Handle *seriesHandle, int32 *celsOffset, int32 *palOffset, RGB8 *myPalette) {
|
|
Common::File f;
|
|
int32 *celsPtr, *palPtr;
|
|
char *parseAssetPtr;
|
|
|
|
// This loads a sprite series into the provided vars, rather than the WS tables.
|
|
// The WS loader is not involved with this procedure.
|
|
|
|
// First open the file
|
|
if (!f.open(assetName))
|
|
return -1;
|
|
|
|
// Get the size
|
|
const uint32 assetSize = f.size();
|
|
|
|
// Create a handle big enough to hold the contents of the file
|
|
MemHandle workHandle = NewHandle(assetSize, "ss file");
|
|
|
|
// Lock the handle and read the contents of the file intoit
|
|
HLock(workHandle);
|
|
char *mainAssetPtr = (char *)*workHandle;
|
|
if (f.read(mainAssetPtr, assetSize) < assetSize) {
|
|
f.close();
|
|
mem_free(workHandle);
|
|
return -1;
|
|
}
|
|
|
|
// Close the file
|
|
f.close();
|
|
|
|
// Set up some pointers
|
|
char *endOfAssetBlock = (char *)((intptr)mainAssetPtr + assetSize);
|
|
parseAssetPtr = mainAssetPtr;
|
|
|
|
// Process the SS from the stream file
|
|
const int32 celsSize = ProcessCELS(assetName, &parseAssetPtr, mainAssetPtr, endOfAssetBlock, &celsPtr, &palPtr, myPalette);
|
|
if (celsSize < 0) {
|
|
error_show(FL, 'WSLP', "series: %s", assetName);
|
|
}
|
|
|
|
// Store the handle and offsets
|
|
*seriesHandle = workHandle;
|
|
*celsOffset = (intptr)celsPtr - (intptr)mainAssetPtr;
|
|
*palOffset = (intptr)palPtr - (intptr)mainAssetPtr;
|
|
HUnLock(workHandle);
|
|
|
|
return celsSize;
|
|
}
|
|
|
|
CCB *GetWSAssetCEL(uint32 hash, uint32 index, CCB *myCCB) {
|
|
bool streamSeries;
|
|
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return nullptr;
|
|
}
|
|
|
|
//If a memory location to store the CCB has not been provided...
|
|
if (!myCCB) {
|
|
// Create the CCB struct
|
|
if ((myCCB = (CCB *)mem_alloc(sizeof(CCB), "CCB")) == nullptr) {
|
|
ws_LogErrorMsg(FL, "Out of memory - mem requested: %d bytes", sizeof(CCB));
|
|
return nullptr;
|
|
}
|
|
|
|
// Create the CCB current location and new location rectangles
|
|
if ((myCCB->currLocation = (M4Rect *)mem_alloc(sizeof(M4Rect), "M4Rect")) == nullptr) {
|
|
ws_LogErrorMsg(FL, "Out of memory - mem requested: %d bytes", sizeof(M4Rect));
|
|
return nullptr;
|
|
}
|
|
myCCB->currLocation->x1 = 0;
|
|
myCCB->currLocation->y1 = 0;
|
|
myCCB->currLocation->x2 = 0;
|
|
myCCB->currLocation->y2 = 0;
|
|
if ((myCCB->newLocation = (M4Rect *)mem_alloc(sizeof(M4Rect), "M4Rect")) == nullptr) {
|
|
ws_LogErrorMsg(FL, "Out of memory - mem requested: %d bytes", sizeof(M4Rect));
|
|
return nullptr;
|
|
}
|
|
myCCB->maxArea = nullptr;
|
|
myCCB->source = nullptr;
|
|
}
|
|
|
|
//The source for a CCB is a sprite. create the sprite from the WS tables hash, index
|
|
M4sprite *mySprite = myCCB->source;
|
|
mySprite = GetWSAssetSprite(nullptr, hash, index, mySprite, &streamSeries);
|
|
if (mySprite == nullptr) {
|
|
// Term messages for whatever went wrong are printed from within GetWSAssetSprite()
|
|
return nullptr;
|
|
}
|
|
myCCB->source = mySprite;
|
|
if (streamSeries) {
|
|
myCCB->flags |= CCB_STREAM;
|
|
}
|
|
|
|
//Initialize the CCB and return
|
|
myCCB->newLocation->x1 = 0;
|
|
myCCB->newLocation->y1 = 0;
|
|
myCCB->newLocation->x2 = 0;
|
|
myCCB->newLocation->y2 = 0;
|
|
myCCB->scaleX = 0;
|
|
myCCB->scaleY = 0;
|
|
myCCB->seriesName = _GWS(globalCELSnames)[hash];
|
|
return myCCB;
|
|
}
|
|
|
|
int32 GetWSAssetCELCount(uint32 hash) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return -1;
|
|
}
|
|
|
|
// Verify the hash is valid, and a SS for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "Series number out of range: requested num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Make sure the series is still in memory
|
|
if ((!_GWS(globalCELSHandles)[hash]) || (!*_GWS(globalCELSHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "Series not in memory series num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Find and return the number of sprites in the SS
|
|
uint32 *celsPtr = (uint32 *)((intptr)*(_GWS(globalCELSHandles)[hash]) + (uint32)(_GWS(globalCELSoffsets)[hash]));
|
|
return FROM_LE_32(celsPtr[CELS_COUNT]);
|
|
}
|
|
|
|
|
|
int32 GetWSAssetCELFrameRate(uint32 hash) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return -1;
|
|
}
|
|
|
|
// Verify the hash is valid, and a SS for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "Series number out of range: requested num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Make sure the series is still in memory
|
|
if ((!_GWS(globalCELSHandles)[hash]) || (!*_GWS(globalCELSHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "Series not in memory series num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Find and return the frame rate for the SS
|
|
uint32 *celsPtr = (uint32 *)((intptr)*(_GWS(globalCELSHandles)[hash]) + (uint32)(_GWS(globalCELSoffsets)[hash]));
|
|
return FROM_LE_32(celsPtr[CELS_FRAME_RATE]);
|
|
}
|
|
|
|
int32 GetWSAssetCELPixSpeed(uint32 hash) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return -1;
|
|
}
|
|
|
|
// Verify the hash is valid, and a SS for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "Series number out of range: requested num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Make sure the series is still in memory
|
|
if ((!_GWS(globalCELSHandles)[hash]) || (!*_GWS(globalCELSHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "Series not in memory series num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Find and return the pix speed for the SS
|
|
uint32 *celsPtr = (uint32 *)((intptr)*(_GWS(globalCELSHandles)[hash]) + (uint32)(_GWS(globalCELSoffsets)[hash]));
|
|
return FROM_LE_32(celsPtr[CELS_PIX_SPEED]);
|
|
}
|
|
|
|
int32 ws_get_sprite_width(uint32 hash, int32 index) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return -1;
|
|
}
|
|
|
|
// Verify the hash is valid, and a SS for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "Series number out of range: requested num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Make sure the series is still in memory
|
|
if ((!_GWS(globalCELSHandles)[hash]) || (!*_GWS(globalCELSHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "Series not in memory series num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Find the source for the SS
|
|
uint32 *celsPtr = (uint32 *)((intptr)*(_GWS(globalCELSHandles)[hash]) + (uint32)(_GWS(globalCELSoffsets)[hash]));
|
|
|
|
// Check that the index into the series requested is within a valid range
|
|
int32 numCels = FROM_LE_32(celsPtr[CELS_COUNT]);
|
|
if (index >= numCels) {
|
|
ws_LogErrorMsg(FL, "ws_get_sprite_width: Sprite index out of range - max index: %d, requested index: %d, hash: %d",
|
|
numCels - 1, index, hash);
|
|
return -1;
|
|
}
|
|
|
|
// Find the offset table in the SS header
|
|
uint32 *offsets = &celsPtr[CELS_OFFSETS];
|
|
|
|
// Find the beginning of the data for all sprites in the SS
|
|
uint32 *data = &celsPtr[CELS_OFFSETS + numCels];
|
|
|
|
// Find the sprite data for the specific sprite in the series
|
|
uint32 *myCelSource = (uint32 *)((intptr)data + FROM_LE_32(offsets[index]));
|
|
|
|
return FROM_LE_32(myCelSource[CELS_W]);
|
|
}
|
|
|
|
int32 ws_get_sprite_height(uint32 hash, int32 index) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return -1;
|
|
}
|
|
|
|
// Verify the hash is valid, and a SS for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "Series number out of range: requested num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Make sure the series is still in memory
|
|
if ((!_GWS(globalCELSHandles)[hash]) || (!*_GWS(globalCELSHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "Series not in memory series num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Find the source for the SS
|
|
uint32 *celsPtr = (uint32 *)((intptr)*(_GWS(globalCELSHandles)[hash]) + (uint32)(_GWS(globalCELSoffsets)[hash]));
|
|
|
|
// Check that the index into the series requested is within a valid range
|
|
const int32 numCels = FROM_LE_32(celsPtr[CELS_COUNT]);
|
|
if (index >= numCels) {
|
|
ws_LogErrorMsg(FL, "ws_get_sprite_height: Sprite index out of range - max index: %d, requested index: %d, hash: %d",
|
|
numCels - 1, index, hash);
|
|
return -1;
|
|
}
|
|
|
|
// Find the offset table in the SS header
|
|
uint32 *offsets = &celsPtr[CELS_OFFSETS];
|
|
|
|
// Find the beginning of the data for all sprites in the SS
|
|
uint32 *data = &celsPtr[CELS_OFFSETS + numCels];
|
|
|
|
// Find the sprite data for the specific sprite in the series
|
|
uint32 *myCelSource = (uint32 *)((intptr)data + FROM_LE_32(offsets[index]));
|
|
|
|
return FROM_LE_32(myCelSource[CELS_H]);
|
|
}
|
|
|
|
MemHandle ws_GetSEQU(uint32 hash, int32 *numLocalVars, int32 *offset) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return nullptr;
|
|
}
|
|
|
|
// Verify the hash is valid, and a SEQU for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "SEQU number out of range: requested num: %d", hash);
|
|
return nullptr;
|
|
}
|
|
|
|
// Make sure the SEQU is still in memory
|
|
if ((!_GWS(globalSEQUHandles)[hash]) || (!*_GWS(globalSEQUHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "SEQU not in memory: sequence num: %d", hash);
|
|
return nullptr;
|
|
}
|
|
|
|
// Find the sequence chunk
|
|
uint32 *sequPtr = (uint32 *)((intptr)*(_GWS(globalSEQUHandles)[hash]) + (uint32)(_GWS(globalSEQUoffsets)[hash]));
|
|
|
|
// Return the offset into the resource chunk, and the number of local vars used by the sequence
|
|
*offset = (intptr)(&sequPtr[SEQU_SEQU_START]) - (intptr)*(_GWS(globalSEQUHandles)[hash]);
|
|
*numLocalVars = FROM_LE_32(sequPtr[SEQU_NUM_VARS]);
|
|
|
|
// Return the resource handle
|
|
return _GWS(globalSEQUHandles)[hash];
|
|
}
|
|
|
|
MemHandle ws_GetMACH(uint32 hash, int32 *numStates, int32 *stateTableOffset, int32 *machInstrOffset) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return nullptr;
|
|
}
|
|
|
|
// Verify the hash is valid, and a MACH for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "MACH number out of range: requested num: %d", hash);
|
|
return nullptr;
|
|
}
|
|
|
|
// Make sure the MACH is still in memory
|
|
if ((!_GWS(globalMACHHandles)[hash]) || (!*_GWS(globalMACHHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "MACH not in memory: machine num: %d", hash);
|
|
return nullptr;
|
|
}
|
|
|
|
// Lock the handle
|
|
HLock(_GWS(globalMACHHandles)[hash]);
|
|
|
|
// Find the machine chunk
|
|
uint32 *machPtr = (uint32 *)((intptr)*(_GWS(globalMACHHandles)[hash]) + (uint32)(_GWS(globalMACHoffsets)[hash]));
|
|
|
|
// Set the number of states, the state offset table, the start of the mach instructions
|
|
*numStates = FROM_LE_32(machPtr[MACH_NUM_STATES]);
|
|
*stateTableOffset = (intptr)(&machPtr[MACH_OFFSETS]) - (intptr)(*_GWS(globalMACHHandles)[hash]);
|
|
*machInstrOffset = ((intptr)machPtr + ((*numStates + 1) << 2)) - (intptr)(*_GWS(globalMACHHandles)[hash]);
|
|
|
|
// Unlock and return the handle
|
|
HUnLock(_GWS(globalMACHHandles)[hash]);
|
|
return _GWS(globalMACHHandles)[hash];
|
|
}
|
|
|
|
MemHandle ws_GetDATA(uint32 hash, uint32 index, int32 *rowOffset) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return nullptr;
|
|
}
|
|
|
|
// Verify the hash is valid, and a SS for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "DATA number out of range: requested num: %d", hash);
|
|
return nullptr;
|
|
}
|
|
|
|
// Make sure the DATA is still in memory
|
|
if ((!_GWS(globalDATAHandles)[hash]) || (!*_GWS(globalDATAHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "DATA not in memory: data num: %d", hash);
|
|
return nullptr;
|
|
}
|
|
|
|
// Find the data block chunk
|
|
uint32 *dataPtr = (uint32 *)((intptr)*(_GWS(globalDATAHandles)[hash]) + (uint32)(_GWS(globalDATAoffsets)[hash]));
|
|
|
|
// Verify the row index of the data block is valid
|
|
if (index > FROM_LE_32(dataPtr[DATA_REC_COUNT])) {
|
|
term_message("File: %s, line: %d, ws_GetDATA() failed:", FL);
|
|
term_message("Data block num: %d", hash);
|
|
term_message("Data row out of range - max row index: %d, requested row index: %d",
|
|
FROM_LE_32(dataPtr[DATA_REC_COUNT]), index);
|
|
return nullptr;
|
|
}
|
|
|
|
*rowOffset = (int32)((intptr)(&dataPtr[DATA_REC_START]) +
|
|
((index * FROM_LE_32(dataPtr[DATA_REC_SIZE])) << 2)
|
|
- (intptr)(*_GWS(globalDATAHandles)[hash]));
|
|
// Return the data handle
|
|
return _GWS(globalDATAHandles)[hash];
|
|
}
|
|
|
|
int32 ws_GetDATACount(uint32 hash) {
|
|
// Ensure the WS loader has been initialized.
|
|
if (!_GWS(wsloaderInitialized)) {
|
|
ws_LogErrorMsg(FL, "WS loader has not been initialized.");
|
|
return -1;
|
|
}
|
|
|
|
// Verify the hash is valid, and a SS for that hash has been loaded
|
|
if (hash > MAX_ASSET_HASH) {
|
|
ws_LogErrorMsg(FL, "DATA number out of range: requested num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Make sure the DATA is still in memory
|
|
if ((!_GWS(globalDATAHandles)[hash]) || (!*_GWS(globalDATAHandles)[hash])) {
|
|
ws_LogErrorMsg(FL, "DATA not in memory: data num: %d", hash);
|
|
return -1;
|
|
}
|
|
|
|
// Find the data block chunk
|
|
uint32 *dataPtr = (uint32 *)((intptr)*(_GWS(globalDATAHandles)[hash]) + (uint32)(_GWS(globalDATAoffsets)[hash]));
|
|
|
|
// Return the number of rows in the data block
|
|
return FROM_LE_32(dataPtr[DATA_REC_COUNT]);
|
|
}
|
|
|
|
static int32 GetSSHeaderInfo(SysFile *sysFile, uint32 **data, RGB8 *myPalette) {
|
|
uint32 celsSize;
|
|
uint32 *tempPtr, i;
|
|
bool byteSwap;
|
|
|
|
if (!sysFile) {
|
|
ws_LogErrorMsg(FL, "nullptr FILE POINTER given.");
|
|
return -1;
|
|
}
|
|
|
|
// Read in the series header and the format number
|
|
const uint32 header = sysFile->readUint32LE();
|
|
uint32 format = sysFile->readUint32LE();
|
|
|
|
// Make sure the header is "M4SS", and that the format is not antique
|
|
if (header == HEAD_SS4M) {
|
|
format = SWAP_INT32(format);
|
|
} else if (header != HEAD_M4SS) {
|
|
ws_LogErrorMsg(FL, "Series is not a valid M4SS series.");
|
|
return -1;
|
|
}
|
|
if (format < SS_FORMAT) {
|
|
ws_LogErrorMsg(FL, "Format is antique and cannot be read - rebuild series.");
|
|
return -1;
|
|
}
|
|
|
|
// Read in the SS chunk type - either PAL or SS info
|
|
uint32 celsType = sysFile->readUint32LE();
|
|
|
|
if ((celsType == CELS__PAL) || (celsType == CELS_LAP_)) {
|
|
// PAL info, read in the size of the PAL chunk
|
|
celsSize = sysFile->readUint32LE();
|
|
|
|
// Now read in the number of colors to be inserted into the PAL
|
|
uint32 numColors = sysFile->readUint32LE();
|
|
|
|
// Make sure the info is in the correct format (swap between Motorola and Intel formats)
|
|
if (celsType == CELS_LAP_) {
|
|
celsSize = SWAP_INT32(celsSize);
|
|
numColors = SWAP_INT32(numColors);
|
|
byteSwap = true;
|
|
} else {
|
|
byteSwap = false;
|
|
}
|
|
|
|
// If there is at least one color specified in this block
|
|
if (numColors > 0) {
|
|
uint32 *myColors = (uint32 *)mem_alloc(celsSize - 12, "ss pal info");
|
|
if (myColors == nullptr) {
|
|
ws_LogErrorMsg(FL, "Failed to mem_alloc() %d bytes.", celsSize - 12);
|
|
return -1;
|
|
}
|
|
|
|
// Read in the color info into a temp buffer
|
|
for (i = 0; i < numColors; ++i)
|
|
myColors[i] = sysFile->readUint32LE();
|
|
|
|
|
|
// If the chunk is in the wrong format, byte-swap the entire chunk
|
|
// note: we do this because we want the data stored in nrgb format
|
|
// The data is always read in low byte first, but we need it high byte first
|
|
// regardless of the endianness of the machine.
|
|
if (byteSwap) {
|
|
tempPtr = (uint32 *)&myColors[0];
|
|
for (i = 0; i < numColors; i++) {
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
}
|
|
|
|
// If we have a place to store the color info
|
|
if (myPalette) {
|
|
tempPtr = (uint32 *)(&myColors[0]);
|
|
for (i = 0; i < numColors; i++) {
|
|
uint32 j = (*tempPtr & 0xff000000) >> 24;
|
|
myPalette[j].r = (*tempPtr & 0x00ff0000) >> 14;
|
|
myPalette[j].g = (*tempPtr & 0x0000ff00) >> 6;
|
|
myPalette[j].b = (*tempPtr & 0x000000ff) << 2;
|
|
tempPtr++;
|
|
}
|
|
}
|
|
|
|
// Turf the temp buffer
|
|
mem_free((void *)myColors);
|
|
}
|
|
|
|
// Read in the next chunk type
|
|
celsType = sysFile->readUint32LE();
|
|
}
|
|
|
|
// Make sure the chunk type is Sprite Series info
|
|
if ((celsType != CELS___SS) && (celsType != CELS_SS__)) {
|
|
ws_LogErrorMsg(FL, "Series chunk type is not labelled as SS info.");
|
|
return -1;
|
|
}
|
|
|
|
// Read in the size of the entire chunk
|
|
celsSize = sysFile->readUint32LE();
|
|
|
|
// If the chunk is the wrong format, byte-swap (between motorola and intel formats)
|
|
if (celsType == CELS_SS__) {
|
|
celsSize = SWAP_INT32(celsSize);
|
|
}
|
|
|
|
// *data contains header + offsets, therefore, we must scan ahead
|
|
// and find out how many cels are here...
|
|
if (!(*sysFile).seek_ahead((CELS_COUNT - CELS_SRC_SIZE - 1) << 2)) {
|
|
ws_LogErrorMsg(FL, "Failed to seek ahead in the stream.");
|
|
return -1;
|
|
}
|
|
|
|
// Read how many sprites are in the series
|
|
int32 numCels = sysFile->readUint32LE();
|
|
|
|
// Again, byte-swap if the chunk is in the wrong format
|
|
if (celsType == CELS_SS__) {
|
|
numCels = SWAP_INT32(numCels);
|
|
}
|
|
|
|
// Now, seek backwards to where we left off
|
|
if (!(*sysFile).seek_ahead((CELS_SRC_SIZE - CELS_COUNT) * 4)) {
|
|
ws_LogErrorMsg(FL, "Failed to seek backwards in the stream.");
|
|
return -1;
|
|
}
|
|
|
|
// Allocate a block to hold both the series header, and the sprite offset table
|
|
if ((*data = (uint32 *)mem_alloc((SS_HEAD_SIZE + numCels) * 4, "ss header")) == nullptr) {
|
|
ws_LogErrorMsg(FL, "Failed to mem_alloc() %d bytes.", (SS_HEAD_SIZE + numCels) << 2);
|
|
return -1;
|
|
}
|
|
|
|
// Read in the series header and the sprite offset table
|
|
// Since we already read in celsType and celsSize, SS_HEAD_SIZE-2
|
|
for (i = 0; i < SS_HEAD_SIZE + (uint)numCels - 2; ++i)
|
|
(*data)[2 + i] = sysFile->readUint32LE();
|
|
|
|
// Set the celsType and the celsSize
|
|
(*data)[0] = celsType;
|
|
(*data)[1] = celsSize;
|
|
|
|
// If the chunk is in the wrong format, byte-swap the series header
|
|
if (celsType == CELS_SS__) {
|
|
tempPtr = &((*data)[2]);
|
|
for (i = 0; i < (uint)(SS_HEAD_SIZE + numCels - 2); i++) {
|
|
*tempPtr = SWAP_INT32(*tempPtr);
|
|
tempPtr++;
|
|
}
|
|
}
|
|
|
|
// Find out how far into the stream we are, and return that value
|
|
const int32 dataOffset = (*sysFile).get_pos();
|
|
return dataOffset;
|
|
}
|
|
|
|
bool ws_OpenSSstream(SysFile *sysFile, Anim8 *anim8) {
|
|
int32 obesest_frame = 0;
|
|
|
|
// Verify the parameters
|
|
if (!sysFile || !anim8 || !anim8->myCCB) {
|
|
ws_LogErrorMsg(FL, "SysFile* streamFile invalid.");
|
|
return false;
|
|
}
|
|
|
|
CCB *myCCB = anim8->myCCB;
|
|
frac16 *myRegs = anim8->myRegs;
|
|
int32 ssDataOffset = 0;
|
|
|
|
// Read in the SS stream header
|
|
if ((ssDataOffset = GetSSHeaderInfo(sysFile, &(myCCB->streamSSHeader), &_G(master_palette)[0])) <= 0) {
|
|
return false;
|
|
}
|
|
|
|
// Automatically set some of the sequence registers
|
|
uint32 *celsPtr = myCCB->streamSSHeader;
|
|
const int32 numSprites = celsPtr[CELS_COUNT];
|
|
myRegs[IDX_CELS_INDEX] = -(1 << 16); // First frame inc will make it 0
|
|
myRegs[IDX_CELS_COUNT] = numSprites << 16;
|
|
myRegs[IDX_CELS_FRAME_RATE] = celsPtr[CELS_FRAME_RATE] << 16;
|
|
|
|
// Here we convert the offset table to become the actual size of the data for each sprite
|
|
// This is so the stream can be optimized to always read in on sprite boundaries
|
|
// Get the beginning of the offset table
|
|
uint32 *offsets = &celsPtr[CELS_OFFSETS];
|
|
|
|
uint32 maxFrameSize = 0;
|
|
// For all but the last frame, the frame size is the difference in offset values
|
|
for (int32 i = 0; i < numSprites - 1; i++) {
|
|
offsets[i] = offsets[i + 1] - offsets[i];
|
|
|
|
if (offsets[i] > maxFrameSize) {
|
|
maxFrameSize = offsets[i];
|
|
obesest_frame = i;
|
|
}
|
|
}
|
|
|
|
// For the last sprite we take the entire chunk size - the chunk header - the offset for that sprite
|
|
offsets[numSprites - 1] = celsPtr[CELS_SRC_SIZE] -
|
|
((SS_HEAD_SIZE + celsPtr[CELS_COUNT]) << 2) -
|
|
offsets[numSprites - 1];
|
|
|
|
if (offsets[numSprites - 1] > maxFrameSize) {
|
|
maxFrameSize = offsets[numSprites - 1];
|
|
obesest_frame = numSprites - 1;
|
|
}
|
|
|
|
// Calculate the maximum size a sprite could be
|
|
maxFrameSize += SS_INDV_HEAD << 2;
|
|
|
|
if (!myCCB->source) {
|
|
myCCB->source = (M4sprite *)mem_alloc(sizeof(M4sprite), "Sprite");
|
|
if (!myCCB->source) {
|
|
ws_LogErrorMsg(FL, "Failed to mem_alloc() %d bytes.", sizeof(M4sprite));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
term_message("Biggest frame was: %d, size: %d bytes (compressed)", obesest_frame, maxFrameSize);
|
|
|
|
// Access the streamer to recognize the new client
|
|
myCCB->myStream = (void *)f_stream_Open(sysFile, ssDataOffset, maxFrameSize, maxFrameSize << 4, numSprites, (int32 *)offsets, 4, false);
|
|
if (myCCB->myStream == nullptr) {
|
|
ws_LogErrorMsg(FL, "Failed to open a stream.");
|
|
return false;
|
|
}
|
|
|
|
// Tag the CCB as being streamed
|
|
myCCB->flags |= CCB_DISC_STREAM;
|
|
myCCB->seriesName = nullptr;
|
|
|
|
// Get the first frame
|
|
if (!ws_GetNextSSstreamCel(anim8)) {
|
|
ws_LogErrorMsg(FL, "Failed to get the first stream frame.");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ws_GetNextSSstreamCel(Anim8 *anim8) {
|
|
// Verify the parameters
|
|
if (!anim8) {
|
|
ws_LogErrorMsg(FL, "nullptr Anim8* given");
|
|
return false;
|
|
}
|
|
|
|
CCB *myCCB = anim8->myCCB;
|
|
if ((!anim8->myCCB) || (!myCCB->streamSSHeader) || (!myCCB->myStream)) {
|
|
ws_LogErrorMsg(FL, "Invalid Anim8* given.");
|
|
return false;
|
|
}
|
|
if (!(myCCB->flags & CCB_DISC_STREAM)) {
|
|
ws_LogErrorMsg(FL, "Anim8* given has not previously opened a stream");
|
|
return false;
|
|
}
|
|
|
|
// Find the SS source and the offset table into the source
|
|
uint32 *celsPtr = myCCB->streamSSHeader;
|
|
uint32 *offsets = &celsPtr[CELS_OFFSETS];
|
|
|
|
// Automatically increment the sequence register
|
|
anim8->myRegs[IDX_CELS_INDEX] += 0x10000;
|
|
|
|
// Check whether the end of the SS has been streamed
|
|
const uint32 frameNum = anim8->myRegs[IDX_CELS_INDEX] >> 16;
|
|
if (frameNum >= celsPtr[CELS_COUNT]) {
|
|
ws_LogErrorMsg(FL, "No more frames available to stream");
|
|
return false;
|
|
}
|
|
|
|
// Read the next sprite from the stream. Note the offset table was converted to absolute size when the stream was opened.
|
|
if (f_stream_Read((strmRequest *)myCCB->myStream, (uint8 **)(&myCCB->streamSpriteSource), offsets[frameNum])
|
|
< (int32)offsets[frameNum]) {
|
|
ws_LogErrorMsg(FL, "Unable to read the next stream frame");
|
|
return false;
|
|
}
|
|
|
|
// Flag the CCB if the sprite series is a delta-streaming sprite series
|
|
if (myCCB->streamSpriteSource[CELS_STREAM]) {
|
|
myCCB->flags |= CCB_STREAM;
|
|
}
|
|
|
|
// Initialize the sprite structure
|
|
uint32 *myCelSource = myCCB->streamSpriteSource;
|
|
M4sprite *mySprite = myCCB->source;
|
|
|
|
mySprite->xOffset = FROM_LE_32(myCelSource[CELS_X]);
|
|
mySprite->yOffset = FROM_LE_32(myCelSource[CELS_Y]);
|
|
mySprite->w = FROM_LE_32(myCelSource[CELS_W]);
|
|
mySprite->h = FROM_LE_32(myCelSource[CELS_H]);
|
|
|
|
mySprite->encoding = (uint8)FROM_LE_32(myCelSource[CELS_COMP]);
|
|
|
|
mySprite->data = (uint8 *)&myCelSource[CELS_DATA];
|
|
|
|
// Initialize the CCB structure
|
|
myCCB->newLocation->x1 = 0;
|
|
myCCB->newLocation->y1 = 0;
|
|
myCCB->newLocation->x2 = 0;
|
|
myCCB->newLocation->y2 = 0;
|
|
myCCB->scaleX = 0;
|
|
myCCB->scaleY = 0;
|
|
|
|
return true;
|
|
}
|
|
|
|
void ws_CloseSSstream(CCB *myCCB) {
|
|
// Verify the parameters
|
|
if ((!myCCB) || (!(myCCB->flags & CCB_DISC_STREAM))) {
|
|
ws_LogErrorMsg(FL, "Invalid CCB* given.");
|
|
return;
|
|
}
|
|
|
|
// Remove the CCB_DISC_STREAM flag
|
|
myCCB->flags &= ~CCB_DISC_STREAM;
|
|
|
|
// Free up the CCB pointers which store streaming information
|
|
if (myCCB->streamSSHeader) {
|
|
mem_free((char *)myCCB->streamSSHeader);
|
|
}
|
|
|
|
// Close the stream
|
|
if (myCCB->myStream) {
|
|
f_stream_Close((strmRequest *)myCCB->myStream);
|
|
myCCB->myStream = nullptr;
|
|
}
|
|
}
|
|
|
|
} // End of namespace M4
|