/* 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 . * */ #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