From 6d12b6ff1a7c70d15f431e5826cea8817a67ed95 Mon Sep 17 00:00:00 2001 From: elasota Date: Fri, 7 May 2021 02:16:25 -0400 Subject: [PATCH] Add house export to room editor --- GpApp/Externs.h | 2 + GpApp/GliderDefines.h | 1 + GpApp/GliderProtos.h | 1 + GpApp/HouseIO.cpp | 1283 ++++++++++++++++++++++++ GpApp/InterfaceInit.cpp | 9 +- GpApp/Menu.cpp | 33 +- GpApp/Sound.cpp | 45 +- GpCommon/GpVector.h | 67 +- PortabilityLayer/GPArchive.cpp | 3 +- PortabilityLayer/GPArchive.h | 4 +- PortabilityLayer/MacBinary2.cpp | 16 +- PortabilityLayer/MacBinary2.h | 5 + PortabilityLayer/MenuManager.cpp | 55 + PortabilityLayer/MenuManager.h | 1 + PortabilityLayer/PLQDraw.cpp | 261 ++++- PortabilityLayer/PLResourceManager.cpp | 149 ++- PortabilityLayer/QDPictHeader.cpp | 4 +- PortabilityLayer/ResourceManager.h | 19 + 18 files changed, 1883 insertions(+), 75 deletions(-) diff --git a/GpApp/Externs.h b/GpApp/Externs.h index a2c4a55..2be06cc 100644 --- a/GpApp/Externs.h +++ b/GpApp/Externs.h @@ -75,6 +75,8 @@ namespace PortabilityLayer #define iObjectWindow 20 #define iCoordinateWindow 21 +#define iExportGliderPROHouse 1 + //-------------------------------------------------------------- Structs /* typedef short SICN[16]; diff --git a/GpApp/GliderDefines.h b/GpApp/GliderDefines.h index 5e167a0..55944ff 100644 --- a/GpApp/GliderDefines.h +++ b/GpApp/GliderDefines.h @@ -187,6 +187,7 @@ #define kGameMenuID 129 #define kOptionsMenuID 130 #define kHouseMenuID 131 +#define kExportMenuID 132 #define kSplashMode 0 #define kEditMode 1 diff --git a/GpApp/GliderProtos.h b/GpApp/GliderProtos.h index a40734a..2f4aa90 100644 --- a/GpApp/GliderProtos.h +++ b/GpApp/GliderProtos.h @@ -119,6 +119,7 @@ void GenerateRetroLinks (void); void DoGoToDialog (void); void ConvertHouseVer1To2 (void); void ShiftWholeHouse (SInt16); +void ExportHouse (void); void DoHouseInfo (void); // --- HouseInfo.c diff --git a/GpApp/HouseIO.cpp b/GpApp/HouseIO.cpp index e0cb0f7..0037179 100644 --- a/GpApp/HouseIO.cpp +++ b/GpApp/HouseIO.cpp @@ -25,6 +25,14 @@ #include "PLStringCompare.h" #include "PLPasStr.h" +#include "MacFileInfo.h" +#include "GpIOStream.h" +#include "GpVector.h" +#include "MacBinary2.h" +#include "QDPictOpcodes.h" +#include "QDPixMap.h" +#include "PLStandardColors.h" + #define kSaveChangesAlert 1002 #define kSaveChanges 1 #define kDiscardChanges 2 @@ -50,6 +58,7 @@ extern short numberRooms, housesFound; extern Boolean noRoomAtAll, quitting, wardBitSet; extern Boolean phoneBitSet, bannerStarCountOn; +bool ParseAndConvertSoundChecked(const THandle &handle, void const*& outDataContents, size_t &outDataSize); //============================================================== Functions @@ -2316,3 +2325,1277 @@ THandle LoadHouseResource(const PortabilityLayer::ResTypeID &resTypeID, in return PortabilityLayer::ResourceManager::GetInstance()->GetAppResource(resTypeID, resID); } + +//-------------------------------------------------------------- ExportHouse + +namespace ExportHouseResults +{ + enum ExportHouseResult + { + kOK, + + kStreamFailed, + kIOError, + kMemError, + kResourceError, + kInternalError, + }; +} + +typedef ExportHouseResults::ExportHouseResult ExportHouseResult_t; + +struct SimpleResource +{ + PortabilityLayer::ResTypeID m_resType; + int m_resourceID; + PortabilityLayer::PascalStr<255> m_name; + size_t m_offsetInResData; + uint8_t m_attributes; +}; + +static bool AppendRaw(GpVector &bytes, const void *data, size_t size) +{ + for (size_t i = 0; i < size; i++) + { + if (!bytes.Append(static_cast(data)[i])) + return false; + } + + return true; +} + +template +static bool AppendRawStruct(GpVector &bytes, const T &value) +{ + return AppendRaw(bytes, &value, sizeof(T)); +} + +static ExportHouseResult_t TryExportSound(GpVector &resData, const THandle &resHandle) +{ + const void *dataContents = nullptr; + size_t dataSize = 0; + if (!ParseAndConvertSoundChecked(resHandle, dataContents, dataSize)) + return ExportHouseResults::kResourceError; + + // Don't ask... + const uint8_t commandStreamPrefix[20] = { 0, 1, 0, 1, 0, 5, 0, 0, 0, 0xa0, 0, 1, 0x80, 0x51, 0, 0, 0, 0, 0, 0x14 }; + + struct BufferHeader + { + BEUInt32_t m_samplePtr; + BEUInt32_t m_length; + BEFixed32_t m_sampleRate; + BEUInt32_t m_loopStart; + BEUInt32_t m_loopEnd; + uint8_t m_encoding; + uint8_t m_baseFrequency; + }; + + BufferHeader bufferHeader; + bufferHeader.m_samplePtr = 0; + bufferHeader.m_length = static_cast(dataSize); + bufferHeader.m_sampleRate.m_intPart = 0x56ee; + bufferHeader.m_sampleRate.m_fracPart = 0x8ba3; + bufferHeader.m_loopStart = static_cast(dataSize - 2); + bufferHeader.m_loopEnd = static_cast(dataSize - 1); + bufferHeader.m_encoding = 0; + bufferHeader.m_baseFrequency = 0x3c; + + if (!resData.Resize(sizeof(bufferHeader) + sizeof(commandStreamPrefix) + dataSize)) + return ExportHouseResults::kMemError; + + memcpy(&resData[0], commandStreamPrefix, sizeof(commandStreamPrefix)); + memcpy(&resData[sizeof(commandStreamPrefix)], &bufferHeader, sizeof(bufferHeader)); + + if (dataSize > 0) + memcpy(&resData[sizeof(commandStreamPrefix) + sizeof(bufferHeader)], dataContents, dataSize); + + return ExportHouseResults::kOK; +} + +static void BitSwap4(GpVector &vec, size_t count) +{ + for (size_t i = 0; i < count; i++) + { + uint8_t v = vec[i]; + v = (((v >> 4) & 0xf) | ((v << 4) & 0xf)); + vec[i] = v; + } +} + +static void BitSwap2(GpVector &vec, size_t count) +{ + for (size_t i = 0; i < count; i++) + { + uint8_t v = vec[i]; + v = (((v >> 2) & 0x33) | ((v << 2) & 0xcc)); + vec[i] = v; + } +} + +static void BitSwap1(GpVector &vec, size_t count) +{ + for (size_t i = 0; i < count; i++) + { + uint8_t v = vec[i]; + v = (((v >> 1) & 0x55) | ((v << 1) & 0xaa)); + vec[i] = v; + } +} + +namespace RLEEncoder +{ + static const size_t kMaxRepeat = 129; + static const size_t kMaxLiteral = 128; + + bool EmitSymbol(GpVector &compressedData, uint8_t sym) + { + return compressedData.Append(sym); + } + + bool EmitSymbol(GpVector &compressedData, uint16_t sym) + { + return compressedData.Append((sym >> 8) & 0xff) && compressedData.Append(sym & 0xff); + } + + template + bool EmitLiterals(GpVector &compressedData, const T *symbols, size_t length) + { + if (length == 0) + return true; + + assert(length <= kMaxLiteral); + if (!compressedData.Append(static_cast(length - 1))) + return false; + + for (size_t i = 0; i < length; i++) + { + if (!EmitSymbol(compressedData, symbols[i])) + return false; + } + + return true; + } + + template + bool EmitRepeat(GpVector &compressedData, const T &symbol, size_t length) + { + if (length < 2) + return EmitLiterals(compressedData, &symbol, length); + + assert(length <= kMaxRepeat); + if (!compressedData.Append(static_cast(257 - length))) + return false; + + if (!EmitSymbol(compressedData, symbol)) + return false; + + return true; + } + + + template + bool PackRLE(GpVector &compressedData, const GpVector &uncompressedData) + { + size_t numUncompressed = uncompressedData.Count(); + const T *uncompressedSymbols = uncompressedData.Buffer(); + + if (!compressedData.Resize(0)) + return false; + + size_t literalStartLoc = 0; + size_t repeatStartLoc = 0; + size_t readPos = 0; + + // Loop/exit invariants: + // repeatStartLoc - literalStartLoc <= kMaxLiteral + // i - repeatStartLoc < kMaxRepeat + for (size_t i = 0; i < numUncompressed; i++) + { + T b = uncompressedSymbols[i]; + if (b != uncompressedSymbols[repeatStartLoc]) + { + // Run terminates at i + const size_t repeatLength = i - repeatStartLoc; + const size_t literalLength = repeatStartLoc - literalStartLoc; + + // Determine if we should flush the repeat or fold it into the literal span. + // There are several situations that can happen here: + // Repeat length is 1: + // Literal span is at limit: + // Emit literal span, start new literal span at repeatStartLoc + // Literal span is below limit: + // Do nothing + // Repeat length is 2: + // Literal span is 0: + // Emit repeat + // Literal span is non-zero and appending repeat to literal span would be at or exceed limit: + // Emit literal span and repeat + // Otherwise: + // Do nothing + // Repeat length is 3+: + // Emit literal span and repeat + if (repeatLength == 1) + { + if (literalLength == kMaxLiteral) + { + if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) + return false; + + literalStartLoc = repeatStartLoc; + } + } + else if (repeatLength == 2) + { + if (literalLength == 0 || (literalLength + repeatLength >= kMaxLiteral)) + { + if (literalLength != 0) + { + if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) + return false; + } + + if (!EmitRepeat(compressedData, uncompressedSymbols[repeatStartLoc], repeatLength)) + return false; + + literalStartLoc = i; + } + } + else if (repeatLength >= 3) + { + if (literalLength != 0) + { + if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) + return false; + } + + if (!EmitRepeat(compressedData, uncompressedSymbols[repeatStartLoc], repeatLength)) + return false; + + literalStartLoc = i; + } + + repeatStartLoc = i; + } + else + { + // i is a repeat character + const size_t repeatLength = i + 1 - repeatStartLoc; + const size_t literalLength = repeatStartLoc - literalStartLoc; + + if (repeatLength == kMaxRepeat) + { + if (literalLength != 0) + { + if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) + return false; + } + + if (!EmitRepeat(compressedData, uncompressedSymbols[repeatStartLoc], repeatLength)) + return false; + + literalStartLoc = i + 1; + repeatStartLoc = i + 1; + } + } + } + + // Final flush + size_t repeatLength = numUncompressed - repeatStartLoc; + size_t literalLength = repeatStartLoc - literalStartLoc; + + if (repeatLength == 1) + { + if (literalLength < kMaxLiteral) + { + literalLength++; + repeatLength = 0; + } + } + + if (literalLength != 0) + { + if (!EmitLiterals(compressedData, uncompressedSymbols + literalStartLoc, literalLength)) + return false; + } + + if (repeatLength == 1) + { + if (!EmitLiterals(compressedData, uncompressedSymbols + repeatStartLoc, 1)) + return false; + } + else if (repeatLength >= 2) + { + if (!EmitRepeat(compressedData, uncompressedSymbols[repeatStartLoc], repeatLength)) + return false; + } + + return true; + } +} + +static ExportHouseResult_t TryExportPictFromSurface(GpVector &resData, DrawSurface *surface) +{ + bool couldBe16Bit = true; + bool couldBe8Bit = true; + int numUniqueColors = 0; + PortabilityLayer::RGBAColor uniqueColors[256]; + for (int i = 0; i < 256; i++) + uniqueColors[i] = PortabilityLayer::RGBAColor::Create(0, 0, 0, 255); + + const Rect rect = surface->m_port.GetRect(); + + const size_t width = rect.Width(); + const size_t height = rect.Height(); + + THandle pixMapHdl = surface->m_port.GetPixMap(); + const PixMap *pixMap = *pixMapHdl; + const uint8_t *imageData = static_cast(pixMap->m_data); + const size_t pixelDataPitch = pixMap->m_pitch; + + assert(pixMap->m_pixelFormat == GpPixelFormats::kRGB32); + + + for (size_t row = 0; row < height; row++) + { + const uint8_t *rowData = imageData + pixMap->m_pitch * row; + + if (!couldBe8Bit && !couldBe16Bit) + break; + + for (size_t col = 0; col < width; col++) + { + const uint8_t *pixelData = rowData + col * 4; + if (couldBe16Bit) + { + for (int ch = 0; ch < 3; ch++) + { + uint8_t channelData = pixelData[ch]; + if ((channelData >> 2) != (channelData & 0x7)) + { + couldBe16Bit = false; + break; + } + } + } + + if (couldBe8Bit) + { + PortabilityLayer::RGBAColor rgbaColor = PortabilityLayer::RGBAColor::Create(pixelData[0], pixelData[1], pixelData[2], pixelData[3]); + + bool matchedColor = false; + for (int i = 0; i < numUniqueColors; i++) + { + if (uniqueColors[i] == rgbaColor) + { + matchedColor = true; + break; + } + } + + if (!matchedColor) + { + if (numUniqueColors == 256) + couldBe8Bit = false; + else + uniqueColors[numUniqueColors++] = rgbaColor; + } + } + } + } + + bool isBWBitmap = false; + + if (numUniqueColors <= 2) + { + isBWBitmap = true; + for (int c = 0; c < numUniqueColors; c++) + { + if (uniqueColors[c] != StdColors::Black() && uniqueColors[c] != StdColors::White()) + isBWBitmap = false; + } + + if (isBWBitmap) + { + numUniqueColors = 2; + uniqueColors[0] = StdColors::White(); + uniqueColors[1] = StdColors::Black(); + } + } + + int bpp = 0; + if (isBWBitmap) + bpp = 1; + else if (couldBe8Bit) + { + if (numUniqueColors <= 2) + bpp = 1; + else if (numUniqueColors <= 4) + bpp = 2; + else if (numUniqueColors <= 16) + bpp = 4; + else + bpp = 8; + } + else if (couldBe16Bit) + bpp = 16; + else + bpp = 32; + + // The typical structure of a PICT is header, ClipRegion, then a raster op. + // We use V1 pict for 1bpp and V2 for all others. + struct PictHeader + { + uint8_t m_size[2]; + + BERect m_rect; + }; + + BERect beRect; + beRect.top = beRect.left = 0; + beRect.right = static_cast(width); + beRect.bottom = static_cast(height); + + PictHeader pictHeader; + pictHeader.m_size[0] = 0; + pictHeader.m_size[1] = 0; + pictHeader.m_rect = beRect; + + if (!AppendRawStruct(resData, pictHeader)) + return ExportHouseResults::kMemError; + + int pictVersion = 1; + if (isBWBitmap) + { + const uint8_t versionTag[2] = { 0x11, 0x01 }; + if (!AppendRaw(resData, versionTag, 2)) + return ExportHouseResults::kMemError; + } + else + { + pictVersion = 2; + + struct PictV2Header + { + BEUInt16_t m_versionTag; + BEUInt16_t m_versionOp; + BEUInt16_t m_headerOp; + BEInt16_t m_v2Version; + BEInt16_t m_reserved1; + BEFixed32_t m_top; + BEFixed32_t m_left; + BEFixed32_t m_bottom; + BEFixed32_t m_right; + BEUInt32_t m_reserved2; + }; + + GP_STATIC_ASSERT(sizeof(PictV2Header) == 30); + + PictV2Header v2Header; + v2Header.m_versionTag = 0x0011; + v2Header.m_versionOp = 0x02ff; + v2Header.m_headerOp = 0x0c00; + v2Header.m_v2Version = -1; + v2Header.m_reserved1 = -1; + v2Header.m_top.m_intPart = beRect.top; + v2Header.m_top.m_fracPart = 0; + v2Header.m_left.m_intPart = beRect.left; + v2Header.m_left.m_fracPart = 0; + v2Header.m_bottom.m_intPart = beRect.bottom; + v2Header.m_bottom.m_fracPart = 0; + v2Header.m_right.m_intPart = beRect.right; + v2Header.m_right.m_fracPart = 0; + + if (!AppendRawStruct(resData, v2Header)) + return ExportHouseResults::kMemError; + } + + // Emit ClipRgn opcode + if (pictVersion == 1) + { + const uint8_t clipRgnOpcode[1] = { PortabilityLayer::QDOpcodes::kClipRegion }; + if (!AppendRaw(resData, clipRgnOpcode, 1)) + return ExportHouseResults::kMemError; + } + else if (pictVersion == 2) + { + const uint8_t clipRgnOpcode[2] = { 0, PortabilityLayer::QDOpcodes::kClipRegion }; + if (!AppendRaw(resData, clipRgnOpcode, 2)) + return ExportHouseResults::kMemError; + } + + struct ClipRgnData + { + BEUInt16_t m_structureSize; + BERect m_rect; + }; + + GP_STATIC_ASSERT(sizeof(ClipRgnData) == 10); + + ClipRgnData clipRgnData; + clipRgnData.m_structureSize = sizeof(ClipRgnData); + clipRgnData.m_rect = beRect; + + if (!AppendRawStruct(resData, clipRgnData)) + return ExportHouseResults::kMemError; + + // Emit image + const size_t bitsPerRow = width * bpp; + const size_t bytesPerRow = (bitsPerRow + 7) / 8; + const size_t expansionCapacity = bytesPerRow + (bytesPerRow / 128) + 16; // Worst-case scenario for RLE failure to compress + + uint16_t bitmapOpcode = 0; + int packType = 0; + bool isDirect = false; + if (bpp <= 8) + { + if (bytesPerRow < 8) + { + packType = 1; + bitmapOpcode = PortabilityLayer::QDOpcodes::kBitsRect; + } + else + { + packType = 0; + bitmapOpcode = PortabilityLayer::QDOpcodes::kPackBitsRect; + } + } + else + { + isDirect = true; + bitmapOpcode = PortabilityLayer::QDOpcodes::kDirectBitsRect; + + if (bpp == 16) + packType = 3; + else if (bpp == 32) + packType = 4; + else + return ExportHouseResults::kInternalError; + } + + if (pictVersion == 1) + { + const uint8_t opcode = bitmapOpcode; + if (!AppendRaw(resData, &opcode, 1)) + return ExportHouseResults::kMemError; + } + else if (pictVersion == 2) + { + const BEUInt16_t opcode(bitmapOpcode); + if (!AppendRaw(resData, &opcode, 2)) + return ExportHouseResults::kMemError; + } + + // Write prelude + const bool isPixmap = !isBWBitmap; + { + if (isDirect) + { + struct DirectPrelude + { + BEUInt32_t m_baseAddress; + BEUInt16_t m_rowSize; + }; + + DirectPrelude prelude; + prelude.m_baseAddress = 0; + prelude.m_rowSize = static_cast(0x8000 | bytesPerRow); + + if (!AppendRawStruct(resData, prelude)) + return ExportHouseResults::kMemError; + } + else + { + uint16_t rowSize = bytesPerRow; + if (!isBWBitmap) + rowSize |= 0x8000; + + BEUInt16_t rowSizeBE(rowSize); + + if (!AppendRawStruct(resData, rowSizeBE)) + return ExportHouseResults::kMemError; + } + } + + // Do actual image packing, we need to do this now so we have the pack size available + GpVector packedImage(PLDrivers::GetAlloc()); + + GpVector uncompressedRowData8(PLDrivers::GetAlloc()); + GpVector uncompressedRowData16(PLDrivers::GetAlloc()); + GpVector compressedRowData(PLDrivers::GetAlloc()); + + if (packType == 4) + { + if (!uncompressedRowData8.Resize(bytesPerRow)) + return ExportHouseResults::kMemError; + } + else + { + if (!uncompressedRowData16.Resize(bytesPerRow / 2)) + return ExportHouseResults::kMemError; + } + + if (!compressedRowData.Resize(expansionCapacity)) + return ExportHouseResults::kMemError; + + for (size_t row = 0; row < height; row++) + { + if (!uncompressedRowData8.Resize(0)) + return ExportHouseResults::kMemError; + + if (!uncompressedRowData16.Resize(0)) + return ExportHouseResults::kMemError; + + const uint8_t *srcRowStart = imageData + pixelDataPitch * row; + + if (packType == 4) + { + // RGB24 images + for (size_t ch = 0; ch < 3; ch++) + { + for (size_t col = 0; col < width; col++) + { + const uint8_t *srcPixelStart = srcRowStart + col * 4; + if (!uncompressedRowData8.Append(srcPixelStart[ch])) + return ExportHouseResults::kMemError; + } + } + } + else if (couldBe16Bit) + { + assert(packType == 3); + for (size_t col = 0; col < width; col++) + { + const uint8_t *srcPixelStart = srcRowStart + col * 4; + uint16_t packed = ((srcPixelStart[0] << 7) & 0x7c00) | ((srcPixelStart[1] << 2) & 0x3e0) | ((srcPixelStart[2] >> 3) & 0x1f); + if (!uncompressedRowData16.Append(packed)) + return ExportHouseResults::kMemError; + } + } + else + { + assert(couldBe8Bit); + + int numBitsSpooled = 0; + uint8_t spooledBits = 0; + for (size_t col = 0; col < width; col++) + { + const uint8_t *srcPixelStart = srcRowStart + col * 4; + PortabilityLayer::RGBAColor color = PortabilityLayer::RGBAColor::Create(srcPixelStart[0], srcPixelStart[1], srcPixelStart[2], srcPixelStart[3]); + int colorIndex = -1; + for (int ci = 0; ci < numUniqueColors; ci++) + { + if (color == uniqueColors[ci]) + { + colorIndex = ci; + break; + } + } + + assert(colorIndex >= 0); + + spooledBits <<= bpp; + spooledBits |= colorIndex; + numBitsSpooled += bpp; + + if (numBitsSpooled == 8) + { + if (!uncompressedRowData8.Append(spooledBits)) + return ExportHouseResults::kMemError; + + numBitsSpooled = 0; + spooledBits = 0; + } + } + + if (numBitsSpooled != 0) + { + spooledBits <<= (8 - numBitsSpooled); + if (!uncompressedRowData8.Append(spooledBits)) + return ExportHouseResults::kMemError; + } + } + + if (!compressedRowData.Resize(0)) + return ExportHouseResults::kMemError; + + bool needsLengthMarker = false; + switch (packType) + { + case 0: + case 4: + // 8-bit RLE + if (!RLEEncoder::PackRLE(compressedRowData, uncompressedRowData8)) + return ExportHouseResults::kMemError; + + needsLengthMarker = true; + break; + case 1: + // Uncompressed + if (!compressedRowData.Resize(bytesPerRow)) + return ExportHouseResults::kMemError; + + for (size_t i = 0; i < bytesPerRow; i++) + compressedRowData[i] = uncompressedRowData8[i]; + + needsLengthMarker = false; + break; + case 3: + // 16-bit RLE + if (!RLEEncoder::PackRLE(compressedRowData, uncompressedRowData16)) + return ExportHouseResults::kMemError; + needsLengthMarker = true; + break; + default: + assert(false); + return ExportHouseResults::kInternalError; + }; + + const size_t compressedSize = compressedRowData.Count(); + if (needsLengthMarker) + { + if (bytesPerRow > 250) + packedImage.Append((compressedSize >> 8) & 0xff); + packedImage.Append(compressedSize & 0xff); + } + + for (size_t i = 0; i < compressedSize; i++) + packedImage.Append(compressedRowData[i]); + } + + + // Write BitMap/PixMap + { + if (isBWBitmap) + { + struct BitMapData + { + BEBitMap m_bitMap; + BERect m_srcRect; + BERect m_destRect; + BEUInt16_t m_transferMode; + }; + + BitMapData bmData; + bmData.m_bitMap.m_bounds = beRect; + bmData.m_srcRect = beRect; + bmData.m_destRect = beRect; + bmData.m_transferMode = 0; + + if (!AppendRawStruct(resData, bmData)) + return ExportHouseResults::kMemError; + } + else + { + BEPixMap pixMap; + pixMap.m_bounds = beRect; + pixMap.m_version = 0; + pixMap.m_packType = packType; + pixMap.m_packSize = static_cast(packedImage.Count()); + pixMap.m_hRes = 0x480000; + pixMap.m_vRes = 0x480000; + pixMap.m_pixelType = (isDirect ? 16 : 0); + pixMap.m_pixelSize = bpp; + pixMap.m_componentCount = isDirect ? 3 : 1; + + if (bpp == 32) + pixMap.m_componentSize = 8; + else if (bpp == 16) + pixMap.m_componentSize = 5; + else + pixMap.m_componentSize = bpp; + + pixMap.m_planeSizeBytes = 0; + pixMap.m_clutHandle = 0; + pixMap.m_unused = 0; + + if (!AppendRawStruct(resData, pixMap)) + return ExportHouseResults::kMemError; + + if (isDirect) + { + } + else + { + BEColorTableHeader clutHeader; + clutHeader.m_resourceID = 0; + clutHeader.m_flags = 0; + clutHeader.m_numItemsMinusOne = numUniqueColors - 1; + + if (!AppendRawStruct(resData, clutHeader)) + return ExportHouseResults::kMemError; + + for (int i = 0; i < numUniqueColors; i++) + { + BEColorTableItem item; + item.m_index = i; + item.m_red[0] = item.m_red[1] = uniqueColors[i].r; + item.m_green[0] = item.m_green[1] = uniqueColors[i].g; + item.m_blue[0] = item.m_blue[1] = uniqueColors[i].b; + + if (!AppendRawStruct(resData, item)) + return ExportHouseResults::kMemError; + } + } + + struct TransferData + { + BERect m_srcRect; + BERect m_destRect; + BEUInt16_t m_transferMode; + }; + + TransferData transferData; + transferData.m_srcRect = beRect; + transferData.m_destRect = beRect; + transferData.m_transferMode = 0; + + if (!AppendRawStruct(resData, transferData)) + return ExportHouseResults::kMemError; + } + } + + // Write image contents + if (!AppendRaw(resData, &packedImage[0], packedImage.Count())) + return ExportHouseResults::kMemError; + + // Write pad byte (??) + if (!isBWBitmap && (resData.Count() & 1) != 0) + { + if (!resData.Append(0)) + return ExportHouseResults::kMemError; + } + + // Emit EOP opcode + if (pictVersion == 1) + { + const uint8_t eopOpcode[1] = { PortabilityLayer::QDOpcodes::kEndOfPicture }; + if (!AppendRaw(resData, eopOpcode, 1)) + return ExportHouseResults::kMemError; + } + else if (pictVersion == 2) + { + const uint8_t eopOpcode[2] = { 0, PortabilityLayer::QDOpcodes::kEndOfPicture }; + if (!AppendRaw(resData, eopOpcode, 2)) + return ExportHouseResults::kMemError; + } + + return ExportHouseResults::kOK; +} + +static ExportHouseResult_t TryExportPICT(GpVector &resData, const THandle &resHandle) +{ + // Parse bitmap header + const THandle bmpHandle = resHandle.StaticCast(); + const BitmapImage *bmp = *bmpHandle; + + const Rect rect = bmp->GetRect(); + DrawSurface *surface = nullptr; + if (NewGWorld(&surface, GpPixelFormats::kRGB32, &rect, nullptr) != PLErrors::kNone) + return ExportHouseResults::kMemError; + + surface->DrawPicture(bmpHandle, rect, false); + + ExportHouseResult_t result = TryExportPictFromSurface(resData, surface); + + DisposeGWorld(surface); + + return result; +} + +static ExportHouseResult_t TryExportResource(GpVector &resData, PortabilityLayer::IResourceArchive *resArchive, const PortabilityLayer::ResTypeID &resTypeID, int16_t resID) +{ + THandle resHandle = resArchive->LoadResource(resTypeID, resID); + if (!resHandle) + return ExportHouseResults::kMemError; + + if (resTypeID == PortabilityLayer::ResTypeID('PICT')) + { + ExportHouseResult_t exportResult = TryExportPICT(resData, resHandle); + resHandle.Dispose(); + return exportResult; + } + + if (resTypeID == PortabilityLayer::ResTypeID('snd ')) + { + ExportHouseResult_t exportResult = TryExportSound(resData, resHandle); + resHandle.Dispose(); + return exportResult; + } + + const size_t size = resHandle.MMBlock()->m_size; + if (!resData.Resize(size)) + { + resHandle.Dispose(); + return ExportHouseResults::kMemError; + } + + if (size > 0) + memcpy(&resData[0], *resHandle, size); + + resHandle.Dispose(); + + return ExportHouseResults::kOK; +} + +ExportHouseResult_t TryExportResources(GpIOStream *stream, PortabilityLayer::IResourceArchive *resArchive) +{ + if (!resArchive) + return ExportHouseResults::kOK; + + IGpAllocator *alloc = PLDrivers::GetAlloc(); + + GpVector resources(alloc); + + GpUFilePos_t resForkStart = stream->Tell(); + + PortabilityLayer::IResourceIterator *iterator = resArchive->EnumerateResources(); + if (!iterator) + return ExportHouseResults::kMemError; + + const GpUFilePos_t resForkHeaderPos = stream->Tell(); + + GpUFilePos_t resForkDataStart = 0; + + PortabilityLayer::ResTypeID resTypeID; + int16_t resID = 0; + bool isFirstResource = true; + while (iterator->GetOne(resTypeID, resID)) + { + if (resTypeID != PortabilityLayer::ResTypeID('PICT') && resTypeID != PortabilityLayer::ResTypeID('snd ')) + continue; + + if (isFirstResource) + { + // Seems to want this much scratch space... + uint8_t headerData[256]; + memset(headerData, 0, sizeof(headerData)); + if (!stream->WriteExact(headerData, sizeof(headerData))) + return ExportHouseResults::kIOError; + + resForkDataStart = stream->Tell(); + isFirstResource = false; + } + + SimpleResource res; + + bool isPurgeable = true; + + res.m_name.Set(0, nullptr); + res.m_resourceID = resID; + res.m_resType = resTypeID; + res.m_attributes = 0; + res.m_offsetInResData = stream->Tell() - resForkDataStart; + + if (isPurgeable) + res.m_attributes |= (1 << 5); + + if (!resources.Append(static_cast(res))) + { + iterator->Destroy(); + return ExportHouseResults::kMemError; + } + + GpVector resData(alloc); + ExportHouseResult_t exportResResult = TryExportResource(resData, resArchive, resTypeID, resID); + if (exportResResult != ExportHouseResults::kOK) + return exportResResult; + + BEUInt32_t packedLen(static_cast(resData.Count())); + + if (!stream->WriteExact(&packedLen, sizeof(packedLen))) + { + iterator->Destroy(); + return ExportHouseResults::kIOError; + } + + if (resData.Count() > 0) + { + if (!stream->WriteExact(&resData[0], resData.Count())) + { + iterator->Destroy(); + return ExportHouseResults::kIOError; + } + } + + const unsigned int unpaddedExcess = ((stream->Tell() - resForkStart) & 0x3); + if (unpaddedExcess > 0) + { + uint8_t padding[4] = { 0, 0, 0, 0 }; + if (!stream->WriteExact(padding, 4 - unpaddedExcess)) + return ExportHouseResults::kIOError; + } + } + + iterator->Destroy(); + + if (!resources.Count()) + return ExportHouseResults::kOK; + + GpVector uniqueResTypes(alloc); + GpVector resTypeCounts(alloc); + + // Generate res map + for (size_t i = 0; i < resources.Count(); i++) + { + const SimpleResource &res = resources[i]; + + size_t uniqueResTypeIndex = uniqueResTypes.Count(); + + for (size_t uri = 0; uri < uniqueResTypes.Count(); uri++) + { + if (uniqueResTypes[uri] == res.m_resType) + { + uniqueResTypeIndex = uri; + break; + } + } + + if (uniqueResTypeIndex == uniqueResTypes.Count()) + { + if (!uniqueResTypes.Append(res.m_resType)) + return ExportHouseResults::kMemError; + + if (!resTypeCounts.Append(1)) + return ExportHouseResults::kMemError; + } + else + resTypeCounts[uniqueResTypeIndex]++; + } + + const GpUFilePos_t resMapPos = stream->Tell(); + const GpUFilePos_t resDataSize = resMapPos - resForkDataStart; + + // Reserved space for resource header copy (16), handle to next res map (4), file ref number (2) + { + char resHeaderCopy[22]; + memset(resHeaderCopy, 0, sizeof(resHeaderCopy)); + + if (!stream->WriteExact(resHeaderCopy, sizeof(resHeaderCopy))) + return ExportHouseResults::kIOError; + } + + uint16_t resForkAttributes = 0; // We don't use any of these + + const size_t typeListEntrySize = 8; + const size_t refListEntrySize = 12; + + const size_t resourceTypeListStartLoc = 28; + const size_t resourceTypeListSize = 2 + uniqueResTypes.Count() * typeListEntrySize; + const size_t resourceRefListStartLoc = resourceTypeListStartLoc + resourceTypeListSize; + const size_t resourceNameListStartLoc = resourceRefListStartLoc + resources.Count() * refListEntrySize; + + struct ResForkHeaderData + { + BEUInt16_t m_attributes; + BEUInt16_t m_resourceTypeListStartLoc; + BEUInt16_t m_resourceNameListStartLoc; + BEUInt16_t m_numResTypesMinusOne; + }; + + ResForkHeaderData headerData; + headerData.m_attributes = resForkAttributes; + headerData.m_resourceTypeListStartLoc = resourceTypeListStartLoc; + headerData.m_resourceNameListStartLoc = resourceNameListStartLoc; + headerData.m_numResTypesMinusOne = uniqueResTypes.Count() - 1; + + if (!stream->WriteExact(&headerData, sizeof(headerData))) + return ExportHouseResults::kIOError; + + GpVector refListStartForType(alloc); + if (!refListStartForType.Resize(resTypeCounts.Count())) + return ExportHouseResults::kMemError; + + if (resTypeCounts.Count() > 0) + { + refListStartForType[0] = 0; + for (size_t i = 1; i < refListStartForType.Count(); i++) + refListStartForType[i] = refListStartForType[i - 1] + resTypeCounts[i - 1]; + } + + struct ResTypeData + { + char m_resType[4]; + BEUInt16_t m_resCountMinusOne; + BEUInt16_t m_refListStart; + }; + + // Write resource type list + for (size_t i = 0; i < uniqueResTypes.Count(); i++) + { + ResTypeData resTypeData; + uniqueResTypes[i].ExportAsChars(resTypeData.m_resType); + resTypeData.m_resCountMinusOne = resTypeCounts[i] - 1; + resTypeData.m_refListStart = static_cast(refListStartForType[i] * refListEntrySize + resourceTypeListSize); + + if (!stream->WriteExact(&resTypeData, sizeof(resTypeData))) + return ExportHouseResults::kIOError; + } + + struct RefListEntry + { + BEInt16_t m_resID; + BEInt16_t m_nameOffset; + uint8_t m_attribs; + uint8_t m_resDataStart[3]; + BEUInt32_t m_reserved; + }; + + // Write reference lists + for (size_t ti = 0; ti < uniqueResTypes.Count(); ti++) + { + PortabilityLayer::ResTypeID resType = uniqueResTypes[ti]; + + for (size_t i = 0; i < resources.Count(); i++) + { + const SimpleResource &res = resources[i]; + if (res.m_resType != resType) + continue; + + RefListEntry refListEntry; + refListEntry.m_resID = static_cast(res.m_resourceID); + refListEntry.m_nameOffset = -1; + refListEntry.m_attribs = res.m_attributes; + + const size_t resDataStart = res.m_offsetInResData; + + refListEntry.m_resDataStart[0] = static_cast((resDataStart >> 16) & 0xff); + refListEntry.m_resDataStart[1] = static_cast((resDataStart >> 8) & 0xff); + refListEntry.m_resDataStart[2] = static_cast((resDataStart >> 0) & 0xff); + refListEntry.m_reserved = 0; + + if (!stream->WriteExact(&refListEntry, sizeof(refListEntry))) + return ExportHouseResults::kIOError; + } + } + + const GpUFilePos_t resForkEnd = stream->Tell(); + const GpUFilePos_t resMapSize = resForkEnd - resMapPos; + + struct ResForkHeader + { + BEUInt32_t m_resForkDataStart; + BEUInt32_t m_resMapPos; + BEUInt32_t m_resDataSize; + BEUInt32_t m_resMapSize; + }; + + ResForkHeader header; + header.m_resForkDataStart = static_cast(resForkDataStart - resForkStart); + header.m_resMapPos = static_cast(resMapPos - resForkStart); + header.m_resDataSize = static_cast(resDataSize); + header.m_resMapSize = static_cast(resMapSize); + + // Write header at the start of the file + if (!stream->SeekStart(resForkHeaderPos)) + return ExportHouseResults::kIOError; + + if (!stream->WriteExact(&header, sizeof(header))) + return ExportHouseResults::kIOError; + + // Write header at the start of the resource map + if (!stream->SeekStart(resMapPos)) + return ExportHouseResults::kIOError; + + if (!stream->WriteExact(&header, sizeof(header))) + return ExportHouseResults::kIOError; + + // Return to the end of the file + if (!stream->SeekStart(resForkEnd)) + return ExportHouseResults::kIOError; + + return ExportHouseResults::kOK; +} + +ExportHouseResult_t TryExportHouseToStream(GpIOStream *stream) +{ + uint8_t mb2Header[PortabilityLayer::MacBinary2::kHeaderSize]; + memset(mb2Header, 0, sizeof(mb2Header)); + + // Write MacBinary header + if (!stream->WriteExact(mb2Header, sizeof(mb2Header))) + return ExportHouseResults::kIOError; + + houseType *house = *thisHouse; + const size_t houseSize = thisHouse.MMBlock()->m_size; + const size_t nRooms = house->nRooms; + const size_t houseDataSize = houseType::kBinaryDataSize + sizeof(roomType) * nRooms; + ByteSwapHouse(house, houseSize, true); + + if (!stream->WriteExact(house, houseType::kBinaryDataSize)) + return ExportHouseResults::kIOError; + + if (!stream->WriteExact(house->rooms, sizeof(roomType) * nRooms)) + return ExportHouseResults::kIOError; + + ByteSwapHouse(house, houseSize, false); + + char padding[128]; + memset(padding, 0, sizeof(padding)); + + const GpUFilePos_t dataAlignExcess = stream->Tell() % 128; + if (dataAlignExcess != 0) + { + if (!stream->WriteExact(padding, 128 - dataAlignExcess)) + return ExportHouseResults::kIOError; + } + + const GpUFilePos_t resForkPos = stream->Tell(); + + // Serialize resources + if (houseResFork != nullptr) + { + ExportHouseResult_t resExportResult = TryExportResources(stream, houseResFork); + if (resExportResult != ExportHouseResults::kOK) + return resExportResult; + } + + const GpUFilePos_t resForkSize = stream->Tell() - resForkPos; + + const GpUFilePos_t resAlignExcess = resForkSize % 128; + if (resForkSize != 0) + { + if (!stream->WriteExact(padding, 128 - resAlignExcess)) + return ExportHouseResults::kIOError; + } + + PortabilityLayer::MacFileInfo fileInfo; + fileInfo.m_fileName.Set(thisHouseName[0], reinterpret_cast(thisHouseName + 1)); + fileInfo.m_commentSize = 0; + fileInfo.m_dataForkSize = houseDataSize; + fileInfo.m_resourceForkSize = resForkSize; + memcpy(fileInfo.m_properties.m_fileType, "gliH", 4); + memcpy(fileInfo.m_properties.m_fileCreator, "ozm5", 4); + fileInfo.m_properties.m_xPos = 0; + fileInfo.m_properties.m_yPos = 0; + fileInfo.m_properties.m_finderFlags = 0; + fileInfo.m_properties.m_protected = 0; + fileInfo.m_properties.m_createdTimeMacEpoch = fileInfo.m_properties.m_modifiedTimeMacEpoch = PLDrivers::GetSystemServices()->GetTime(); + + PortabilityLayer::MacBinary2::SerializeHeader(mb2Header, fileInfo); + + if (!stream->SeekStart(0)) + return ExportHouseResults::kIOError; + + if (!stream->WriteExact(mb2Header, PortabilityLayer::MacBinary2::kHeaderSize)) + return ExportHouseResults::kIOError; + + return ExportHouseResults::kOK; +} + +ExportHouseResult_t TryExportHouse(void) +{ + GpIOStream *stream = nullptr; + if (PortabilityLayer::FileManager::GetInstance()->OpenNonCompositeFile(PortabilityLayer::VirtualDirectories::kSourceExport, thisHouseName, ".bin", PortabilityLayer::EFilePermission_Write, GpFileCreationDispositions::kCreateOrOverwrite, stream)) + return ExportHouseResults::kStreamFailed; + + ExportHouseResult_t result = TryExportHouseToStream(stream); + stream->Close(); + + return result; +} + +void ExportHouse(void) +{ + TryExportHouse(); +} diff --git a/GpApp/InterfaceInit.cpp b/GpApp/InterfaceInit.cpp index 19e9779..c1296de 100644 --- a/GpApp/InterfaceInit.cpp +++ b/GpApp/InterfaceInit.cpp @@ -32,7 +32,7 @@ extern WindowPtr mapWindow, toolsWindow, linkWindow; extern Rect boardSrcRect, localRoomsDest[]; extern IGpCursor *handCursor, *vertCursor, *horiCursor; extern IGpCursor *diagCursor; -extern MenuHandle appleMenu, gameMenu, optionsMenu, houseMenu; +extern MenuHandle appleMenu, gameMenu, optionsMenu, houseMenu, exportMenu; extern long incrementModeTime; extern UInt32 doubleTime; extern short fadeInSequence[], idleMode; @@ -50,6 +50,8 @@ extern Boolean twoPlayerGame, paused, hasMirror, splashDrawn; void InitializeMenus (void) { + PortabilityLayer::MenuManager *mm = PortabilityLayer::MenuManager::GetInstance(); + appleMenu = GetMenu(kAppleMenuID); if (appleMenu == nil) RedAlert(kErrFailedResourceLoad); @@ -70,12 +72,15 @@ void InitializeMenus (void) if (!thisMac.isTouchscreen) { menusUp = true; - PortabilityLayer::MenuManager::GetInstance()->SetMenuVisible(true); + mm->SetMenuVisible(true); } houseMenu = GetMenu(kHouseMenuID); if (houseMenu == nil) RedAlert(kErrFailedResourceLoad); + + exportMenu = mm->CreateMenu(PSTR("Export"), kExportMenuID, true, 100, 16, 0); + mm->AppendMenuItem(exportMenu, 0, 0, 0, 0, true, false, PSTR("Export Glider PRO\xaa House...")); UpdateMenus(false); } diff --git a/GpApp/Menu.cpp b/GpApp/Menu.cpp index 4521450..6b5d8da 100644 --- a/GpApp/Menu.cpp +++ b/GpApp/Menu.cpp @@ -32,7 +32,7 @@ void UpdateMenusHouseClosed (void); void HeyYourPissingAHighScore (void); -MenuHandle appleMenu, gameMenu, optionsMenu, houseMenu; +MenuHandle appleMenu, gameMenu, optionsMenu, houseMenu, exportMenu; Boolean menusUp, resumedSavedGame; @@ -141,6 +141,11 @@ void UpdateMenusHouseOpen (void) EnableMenuItem(houseMenu, iSendBack); } } + + if (houseUnlocked) + EnableMenuItem(exportMenu, iExportGliderPROHouse); + else + DisableMenuItem(exportMenu, iExportGliderPROHouse); } //-------------------------------------------------------------- UpdateMenusHouseClosed @@ -159,6 +164,8 @@ void UpdateMenusHouseClosed (void) DisableMenuItem(houseMenu, iPaste); DisableMenuItem(houseMenu, iClear); DisableMenuItem(houseMenu, iDuplicate); + + DisableMenuItem(exportMenu, iExportGliderPROHouse); } //-------------------------------------------------------------- UpdateClipboardMenus @@ -254,12 +261,19 @@ void UpdateMenus (Boolean newMode) { PortabilityLayer::MenuManager *mm = PortabilityLayer::MenuManager::GetInstance(); if (theMode == kEditMode) + { InsertMenu(houseMenu, 0); + InsertMenu(exportMenu, 0); + } else { THandle houseMenu = mm->GetMenuByID(kHouseMenuID); if (houseMenu) mm->RemoveMenu(houseMenu); + + THandle exportMenu = mm->GetMenuByID(kExportMenuID); + if (exportMenu) + mm->RemoveMenu(exportMenu); } } @@ -465,6 +479,19 @@ void DoOptionsMenu (short theItem) //-------------------------------------------------------------- DoHouseMenu // Handle the user selecting an item from the House menu (only in Edit mode). +void DoExportMenu(short theItem) +{ + switch (theItem) + { + case iExportGliderPROHouse: + ExportHouse(); + break; + }; +} + +//-------------------------------------------------------------- DoHouseMenu +// Handle the user selecting an item from the House menu (only in Edit mode). + void DoHouseMenu (short theItem) { #ifndef COMPILEDEMO @@ -647,6 +674,10 @@ void DoMenuChoice (long menuChoice) case kHouseMenuID: DoHouseMenu(theItem); break; + + case kExportMenuID: + DoExportMenu(theItem); + break; } } diff --git a/GpApp/Sound.cpp b/GpApp/Sound.cpp index 8475d31..5ac7e74 100644 --- a/GpApp/Sound.cpp +++ b/GpApp/Sound.cpp @@ -404,23 +404,23 @@ void TellHerNoSounds (void) //-------------------------------------------------------------- ParseAndConvertSound -IGpAudioBuffer *ParseAndConvertSoundChecked(const THandle &handle) +bool ParseAndConvertSoundChecked(const THandle &handle, void const*& outDataContents, size_t &outDataSize) { const uint8_t *dataStart = static_cast(*handle); const size_t size = handle.MMBlock()->m_size; if (size < sizeof(PortabilityLayer::RIFFTag)) - return nullptr; + return false; PortabilityLayer::RIFFTag mainRiffTag; memcpy(&mainRiffTag, dataStart, sizeof(PortabilityLayer::RIFFTag)); if (mainRiffTag.m_tag != PortabilityLayer::WaveConstants::kRiffChunkID) - return nullptr; + return false; const uint32_t riffSize = mainRiffTag.m_chunkSize; if (riffSize < 4 || riffSize - 4 > size - sizeof(PortabilityLayer::RIFFTag)) - return nullptr; + return false; const uint8_t *riffStart = dataStart + sizeof(PortabilityLayer::RIFFTag); const uint8_t *riffEnd = riffStart + riffSize; @@ -432,7 +432,7 @@ IGpAudioBuffer *ParseAndConvertSoundChecked(const THandle &handle) memcpy(&waveMarker, riffStart, 4); if (waveMarker != PortabilityLayer::WaveConstants::kWaveChunkID) - return nullptr; + return false; const uint8_t *tagSearchLoc = riffStart + 4; @@ -440,7 +440,7 @@ IGpAudioBuffer *ParseAndConvertSoundChecked(const THandle &handle) while (tagSearchLoc != riffEnd) { if (riffEnd - tagSearchLoc < sizeof(PortabilityLayer::RIFFTag)) - return nullptr; + return false; PortabilityLayer::RIFFTag riffTag; memcpy(&riffTag, tagSearchLoc, sizeof(PortabilityLayer::RIFFTag)); @@ -453,20 +453,20 @@ IGpAudioBuffer *ParseAndConvertSoundChecked(const THandle &handle) const uint32_t riffTagSizeUnpadded = riffTag.m_chunkSize; if (riffTagSizeUnpadded == 0xffffffffU) - return nullptr; + return false; const uint32_t riffTagSizePadded = riffTagSizeUnpadded + (riffTagSizeUnpadded & 1); tagSearchLoc += sizeof(PortabilityLayer::RIFFTag); if (riffEnd - tagSearchLoc < riffTagSizePadded) - return nullptr; + return false; tagSearchLoc += riffTagSizePadded; } if (formatTagLoc == nullptr || dataTagLoc == nullptr) - return nullptr; + return false; PortabilityLayer::RIFFTag fmtTag; memcpy(&fmtTag, formatTagLoc, sizeof(PortabilityLayer::RIFFTag)); @@ -500,7 +500,7 @@ IGpAudioBuffer *ParseAndConvertSoundChecked(const THandle &handle) copyableSize = sizeof(PortabilityLayer::WaveFormatChunkV1); } else - return nullptr; + return false; memcpy(&formatChunkV3, formatContents, copyableSize); @@ -508,23 +508,22 @@ IGpAudioBuffer *ParseAndConvertSoundChecked(const THandle &handle) const PortabilityLayer::WaveFormatChunkV1 formatChunkV1 = formatChunkV2.m_v1; if (formatChunkV1.m_bitsPerSample != 8) - return nullptr; + return false; if (formatChunkV1.m_formatCode != PortabilityLayer::WaveConstants::kFormatPCM || formatChunkV1.m_numChannels != 1 || formatChunkV1.m_blockAlignmentBytes != 1 || formatChunkV1.m_bitsPerSample != 8) - return nullptr; + return false; uint32_t dataSize = dataTag.m_chunkSize; if (dataSize > 0x1000000 || dataSize < 1) - return nullptr; + return false; - IGpAudioDriver *audioDriver = PLDrivers::GetAudioDriver(); - if (!audioDriver) - return nullptr; + outDataContents = dataContents; + outDataSize = dataSize; - return audioDriver->CreateBuffer(dataContents, dataSize); + return true; } IGpAudioBuffer *ParseAndConvertSound(const THandle &handle) @@ -532,6 +531,14 @@ IGpAudioBuffer *ParseAndConvertSound(const THandle &handle) if (!handle) return nullptr; - IGpAudioBuffer *buffer = ParseAndConvertSoundChecked(handle); - return buffer; + IGpAudioDriver *audioDriver = PLDrivers::GetAudioDriver(); + if (!audioDriver) + return nullptr; + + const void *dataContents = nullptr; + size_t dataSize = 0; + if (!ParseAndConvertSoundChecked(handle, dataContents, dataSize)) + return nullptr; + + return audioDriver->CreateBuffer(dataContents, dataSize); } diff --git a/GpCommon/GpVector.h b/GpCommon/GpVector.h index 1472363..05f30eb 100644 --- a/GpCommon/GpVector.h +++ b/GpCommon/GpVector.h @@ -37,8 +37,12 @@ public: const T &operator[](size_t index) const; bool Resize(size_t newSize); + bool Reserve(size_t newSize); bool ResizeNoConstruct(size_t newSize); + bool Append(const T &item); + bool Append(T &&item); + T *Buffer(); const T *Buffer() const; @@ -148,7 +152,6 @@ const T &GpVector::operator[](size_t index) const return m_elements[index]; } - template bool GpVector::Resize(size_t newSize) { @@ -163,6 +166,21 @@ bool GpVector::Resize(size_t newSize) return true; } + + +template +bool GpVector::Reserve(size_t newSize) +{ + const size_t oldCount = m_count; + + if (!ResizeNoConstruct(newSize)) + return false; + + m_count = oldCount; + + return true; +} + template bool GpVector::ResizeNoConstruct(size_t newSize) { @@ -211,6 +229,53 @@ bool GpVector::ResizeNoConstruct(size_t newSize) return true; } +template +bool GpVector::Append(const T &item) +{ + const size_t oldCount = m_count; + + if (m_count == m_capacity) + { + size_t newCapacity = m_capacity * 2; + if (newCapacity < 8) + newCapacity = 8; + + if (!Reserve(newCapacity)) + return false; + } + + if (!ResizeNoConstruct(oldCount + 1)) + return false; + + new (m_elements + oldCount) T(item); + + return true; +} + +template +bool GpVector::Append(T &&item) +{ + const size_t oldCount = m_count; + + if (m_count == m_capacity) + { + size_t newCapacity = m_capacity * 2; + if (newCapacity < 8) + newCapacity = 8; + + if (!Reserve(newCapacity)) + return false; + } + + if (!ResizeNoConstruct(oldCount + 1)) + return false; + + new (m_elements + oldCount) T(static_cast(item)); + + return true; +} + + template const size_t GpVector::Count() const { diff --git a/PortabilityLayer/GPArchive.cpp b/PortabilityLayer/GPArchive.cpp index 9dae6d1..b39428b 100644 --- a/PortabilityLayer/GPArchive.cpp +++ b/PortabilityLayer/GPArchive.cpp @@ -91,9 +91,8 @@ namespace PortabilityLayer return output; } - bool GpArcResourceTypeTag::Load(const char *str) + bool GpArcResourceTypeTag::Load(const char *str, size_t l) { - size_t l = strlen(str); if (l < sizeof(m_id)) { memcpy(m_id, str, l); diff --git a/PortabilityLayer/GPArchive.h b/PortabilityLayer/GPArchive.h index 16915ee..253bbca 100644 --- a/PortabilityLayer/GPArchive.h +++ b/PortabilityLayer/GPArchive.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace PortabilityLayer { class ResTypeID; @@ -10,7 +12,7 @@ namespace PortabilityLayer static GpArcResourceTypeTag Encode(const ResTypeID &tag); - bool Load(const char *str); + bool Load(const char *str, size_t strLen); bool Decode(ResTypeID &outTag); }; } diff --git a/PortabilityLayer/MacBinary2.cpp b/PortabilityLayer/MacBinary2.cpp index 61b3c3f..b43f81d 100644 --- a/PortabilityLayer/MacBinary2.cpp +++ b/PortabilityLayer/MacBinary2.cpp @@ -40,13 +40,9 @@ namespace namespace PortabilityLayer { - void MacBinary2::WriteBin(const MacFileMem *file, GpIOStream *stream) + void MacBinary2::SerializeHeader(unsigned char *mb2Header, const MacFileInfo &fileInfo) { - const MacFileInfo &fileInfo = file->FileInfo(); - - uint8_t mb2Header[128]; - - memset(mb2Header, 0, sizeof(mb2Header)); + memset(mb2Header, 0, kHeaderSize); mb2Header[MB2FileOffsets::Version] = 0; @@ -87,7 +83,15 @@ namespace PortabilityLayer mb2Header[MB2FileOffsets::MinVersion] = 129; BytePack::BigUInt16(mb2Header + MB2FileOffsets::Checksum, XModemCRC(mb2Header, 124, 0)); + } + void MacBinary2::WriteBin(const MacFileMem *file, GpIOStream *stream) + { + const MacFileInfo &fileInfo = file->FileInfo(); + + uint8_t mb2Header[128]; + + SerializeHeader(mb2Header, fileInfo); stream->Write(mb2Header, 128); uint8_t *padding = mb2Header; diff --git a/PortabilityLayer/MacBinary2.h b/PortabilityLayer/MacBinary2.h index ec697f4..bcb6bdb 100644 --- a/PortabilityLayer/MacBinary2.h +++ b/PortabilityLayer/MacBinary2.h @@ -5,9 +5,14 @@ class GpIOStream; namespace PortabilityLayer { class MacFileMem; + struct MacFileInfo; namespace MacBinary2 { + static const int kHeaderSize = 128; + + void SerializeHeader(unsigned char *headerBytes, const MacFileInfo &macFileInfo); + void WriteBin(const MacFileMem *file, GpIOStream *stream); MacFileMem *ReadBin(GpIOStream *stream); }; diff --git a/PortabilityLayer/MenuManager.cpp b/PortabilityLayer/MenuManager.cpp index 7ba0c94..2a59969 100644 --- a/PortabilityLayer/MenuManager.cpp +++ b/PortabilityLayer/MenuManager.cpp @@ -128,6 +128,7 @@ namespace PortabilityLayer virtual void Init() override; virtual void Shutdown() override; + THandle CreateMenu(const PLPasStr &title, uint16_t menuID, bool enabled, uint16_t width, uint16_t height, uint16_t commandID) const override; THandle DeserializeMenu(const void *resData) const override; THandle GetMenuByID(int id) const override; @@ -294,6 +295,60 @@ namespace PortabilityLayer // GP TODO: Dispose of menus properly } + THandle MenuManagerImpl::CreateMenu(const PLPasStr &title, uint16_t menuID, bool enabled, uint16_t width, uint16_t height, uint16_t commandID) const + { + PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance(); + + const uint8_t titleLength = title.Length(); + + size_t stringDataLength = 1 + titleLength; + size_t numMenuItems = 0; + + MMHandleBlock *stringData = mm->AllocHandle(stringDataLength); + if (!stringData) + { + mm->ReleaseHandle(stringData); + return nullptr; + } + + MMHandleBlock *menuData = mm->AllocHandle(sizeof(Menu) + sizeof(MenuItem) * (numMenuItems - 1)); + if (!menuData) + { + mm->ReleaseHandle(stringData); + return nullptr; + } + + Menu *menu = static_cast(menuData->m_contents); + menu->menuID = menuID; + menu->width = width; + menu->height = height; + menu->commandID = commandID; + menu->enabled = true; + menu->menuIndex = 0; + menu->cumulativeOffset = 0; + menu->unpaddedTitleWidth = 0; + menu->isIcon = false; + menu->haveMenuLayout = false; + + uint8_t *stringDataStart = static_cast(stringData->m_contents); + uint8_t *stringDest = stringDataStart; + stringDest[0] = title.Length(); + memcpy(stringDest + 1, title.UChars(), title.Length()); + + menu->numMenuItems = numMenuItems; + menu->stringBlobHandle = stringData; + menu->prevMenu = nullptr; + menu->nextMenu = nullptr; + menu->layoutHintHorizontalOffset = 0; + menu->layoutWidth = 0; + menu->layoutBaseHeight = 0; + menu->layoutFinalHeight = 0; + menu->bottomItemsTruncated = 0; + menu->topItemsTruncated = 0; + + return THandle(menuData); + } + THandle MenuManagerImpl::DeserializeMenu(const void *resData) const { PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance(); diff --git a/PortabilityLayer/MenuManager.h b/PortabilityLayer/MenuManager.h index 638cc34..f3d93e5 100644 --- a/PortabilityLayer/MenuManager.h +++ b/PortabilityLayer/MenuManager.h @@ -23,6 +23,7 @@ namespace PortabilityLayer virtual void Init() = 0; virtual void Shutdown() = 0; + virtual THandle CreateMenu(const PLPasStr &title, uint16_t menuID, bool enabled, uint16_t width, uint16_t height, uint16_t commandID) const = 0; virtual THandle DeserializeMenu(const void *resData) const = 0; virtual THandle GetMenuByID(int id) const = 0; diff --git a/PortabilityLayer/PLQDraw.cpp b/PortabilityLayer/PLQDraw.cpp index 0ca50f8..2a3199c 100644 --- a/PortabilityLayer/PLQDraw.cpp +++ b/PortabilityLayer/PLQDraw.cpp @@ -493,6 +493,71 @@ static void RedistributeError(int16_t *errorDiffusionNextRow, int16_t *errorDiff } } +namespace PortabilityLayer +{ + bool FindBitSpan(uint32_t mask, int &outLowBit, int &outHighBit) + { + bool haveAnyBits = false; + for (int i = 0; i < 32; i++) + { + if (mask & (1 << i)) + { + if (!haveAnyBits) + { + haveAnyBits = true; + outLowBit = i; + } + outHighBit = i; + } + } + + return haveAnyBits; + } + + int DecodeMaskRShift1(uint32_t mask) + { + int lowBit, highBit; + if (!FindBitSpan(mask, lowBit, highBit)) + return 0; + + return lowBit; + } + + int DecodeMaskRShift2(uint32_t mask) + { + int lowBit, highBit; + if (!FindBitSpan(mask, lowBit, highBit)) + return 0; + + int numBits = highBit - lowBit + 1; + + int expandedBits = numBits; + while (expandedBits < 8) + expandedBits += numBits; + + return expandedBits - 8; + } + + int DecodeMaskMultiplier(uint32_t mask) + { + int lowBit, highBit; + if (!FindBitSpan(mask, lowBit, highBit)) + return 0; + + int numBits = highBit - lowBit + 1; + + int expandedBits = numBits; + int expansionFactor = 1; + while (expandedBits < 8) + { + expandedBits += numBits; + expansionFactor = (expansionFactor << numBits) | 1; + } + + return expansionFactor; + } +} + void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, bool errorDiffusion) { if (!pictHdl) @@ -572,9 +637,9 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, memcpy(&fileHeader, bmpBytes, sizeof(fileHeader)); memcpy(&infoHeader, bmpBytes + sizeof(fileHeader), sizeof(infoHeader)); - const uint16_t bpp = infoHeader.m_bitsPerPixel; + uint16_t bpp = infoHeader.m_bitsPerPixel; - if (bpp != 1 && bpp != 4 && bpp != 8 && bpp != 16 && bpp != 24) + if (bpp != 1 && bpp != 4 && bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32) return; uint32_t numColors = infoHeader.m_numColors; @@ -584,6 +649,18 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, if (numColors == 0 && bpp <= 8) numColors = (1 << bpp); + LEUInt32_t masks[4]; + int maskRShift1[4]; + int maskRShift2[4]; + int maskMultiplier[4]; + bool haveMasks = false; + if (infoHeader.m_thisStructureSize >= sizeof(PortabilityLayer::BitmapInfoHeader) + sizeof(uint32_t) * 4) + { + const uint8_t *masksLoc = bmpBytes + sizeof(fileHeader) + sizeof(PortabilityLayer::BitmapInfoHeader); + memcpy(masks, masksLoc, 16); + haveMasks = true; + } + const uint8_t *ctabLoc = bmpBytes + sizeof(fileHeader) + infoHeader.m_thisStructureSize; const size_t ctabSize = numColors * sizeof(PortabilityLayer::BitmapColorTableEntry); @@ -592,6 +669,57 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, if (ctabSize > availCTabBytes) return; + const uint32_t compressionCode = static_cast(infoHeader.m_compression); + if (bpp == 16) + { + if (compressionCode == 0) + { + haveMasks = true; + masks[0] = (0x1f << 10); + masks[1] = (0x1f << 5); + masks[2] = (0x1f << 0); + masks[3] = (1 << 15); + } + else if (compressionCode == 3) + { + if (!haveMasks) + return; + } + else + return; + } + else if (bpp == 32) + { + if (compressionCode == 0) + { + haveMasks = true; + masks[0] = (0xff << 16); + masks[1] = (0xff << 8); + masks[2] = (0xff << 0); + masks[3] = (0xff << 24); + } + if (compressionCode == 3) + { + if (!haveMasks) + return; + } + } + else + { + if (compressionCode != 0) + return; + } + + if (haveMasks) + { + for (int mi = 0; mi < 4; mi++) + { + maskRShift1[mi] = PortabilityLayer::DecodeMaskRShift1(masks[mi]); + maskRShift2[mi] = PortabilityLayer::DecodeMaskRShift2(masks[mi]); + maskMultiplier[mi] = PortabilityLayer::DecodeMaskMultiplier(masks[mi]); + } + } + if (bpp <= 8) { // Perform palette mapping @@ -672,7 +800,7 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, int16_t *errorDiffusionNextRow = nullptr; int16_t *errorDiffusionCurrentRow = nullptr; - if ((bpp == 16 || bpp == 24) && errorDiffusion) + if ((bpp == 16 || bpp == 24 || bpp == 32) && errorDiffusion) { errorDiffusionBuffer = static_cast(memManager->Alloc(sizeof(int16_t) * numCopyCols * 2 * 3)); if (!errorDiffusionBuffer) @@ -739,12 +867,13 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, const uint8_t srcLow = currentSourceRow[srcColIndex * 2 + 0]; const uint8_t srcHigh = currentSourceRow[srcColIndex * 2 + 1]; - const unsigned int combinedValue = srcLow | (srcHigh << 8); - const unsigned int b = (combinedValue & 0x1f); - const unsigned int g = ((combinedValue >> 5) & 0x1f); - const unsigned int r = ((combinedValue >> 10) & 0x1f); + const uint32_t combinedValue = srcLow | (srcHigh << 8); - if (r + g + b > 46) + uint8_t rgb[3]; + for (unsigned int ch = 0; ch < 3; ch++) + rgb[ch] = (((combinedValue & masks[ch]) >> maskRShift1[ch]) * maskMultiplier[ch]) >> maskRShift2[ch]; + + if (rgb[0] + rgb[1] + rgb[2] > 382) currentDestRow[destColIndex] = 0; else currentDestRow[destColIndex] = 1; @@ -763,15 +892,12 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, const uint8_t srcHigh = currentSourceRow[srcColIndex * 2 + 1]; const unsigned int combinedValue = srcLow | (srcHigh << 8); - const unsigned int b = (combinedValue & 0x1f); - const unsigned int g = ((combinedValue >> 5) & 0x1f); - const unsigned int r = ((combinedValue >> 10) & 0x1f); - const unsigned int xr = (r << 5) | (r >> 2); - const unsigned int xg = (g << 5) | (g >> 2); - const unsigned int xb = (b << 5) | (b >> 2); + uint8_t rgb[3]; + for (unsigned int ch = 0; ch < 3; ch++) + rgb[ch] = (((combinedValue & masks[ch]) >> maskRShift1[ch]) * maskMultiplier[ch]) >> maskRShift2[ch]; - uint8_t colorIndex = stdPalette->MapColorLUT(PortabilityLayer::RGBAColor::Create(xr, xg, xb, 255)); + uint8_t colorIndex = stdPalette->MapColorLUT(PortabilityLayer::RGBAColor::Create(rgb[0], rgb[1], rgb[2], 255)); currentDestRow[destColIndex] = colorIndex; } @@ -787,15 +913,12 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, const uint8_t srcHigh = currentSourceRow[srcColIndex * 2 + 1]; const unsigned int combinedValue = srcLow | (srcHigh << 8); - const unsigned int b = (combinedValue & 0x1f); - const unsigned int g = ((combinedValue >> 5) & 0x1f); - const unsigned int r = ((combinedValue >> 10) & 0x1f); - const unsigned int xr = (r << 5) | (r >> 2); - const unsigned int xg = (g << 5) | (g >> 2); - const unsigned int xb = (b << 5) | (b >> 2); + uint8_t rgb[3]; + for (unsigned int ch = 0; ch < 3; ch++) + rgb[ch] = (((combinedValue & masks[ch]) >> maskRShift1[ch]) * maskMultiplier[ch]) >> maskRShift2[ch]; - ErrorDiffusionWorkPixel wp = ApplyErrorDiffusion(errorDiffusionCurrentRow, xr, xg, xb, col, numCopyCols); + ErrorDiffusionWorkPixel wp = ApplyErrorDiffusion(errorDiffusionCurrentRow, rgb[0], rgb[1], rgb[2], col, numCopyCols); uint8_t colorIndex = stdPalette->MapColorLUT(PortabilityLayer::RGBAColor::Create(wp.m_8[0], wp.m_8[1], wp.m_8[2], 255)); PortabilityLayer::RGBAColor resultColor = stdPalette->GetColors()[colorIndex]; @@ -816,15 +939,11 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, const size_t srcColIndex = col + firstSourceCol; const size_t destColIndex = col + firstDestCol; - const uint8_t srcLow = currentSourceRow[srcColIndex * 2 + 0]; - const uint8_t srcHigh = currentSourceRow[srcColIndex * 2 + 1]; + const uint8_t r = currentSourceRow[srcColIndex * 3 + 2]; + const uint8_t g = currentSourceRow[srcColIndex * 3 + 1]; + const uint8_t b = currentSourceRow[srcColIndex * 3 + 0]; - const unsigned int combinedValue = srcLow | (srcHigh << 8); - const unsigned int b = (combinedValue & 0x1f); - const unsigned int g = ((combinedValue >> 5) & 0x1f); - const unsigned int r = ((combinedValue >> 10) & 0x1f); - - if (r + g + b > 46) + if (r + g + b > 382) currentDestRow[destColIndex] = 0; else currentDestRow[destColIndex] = 1; @@ -867,6 +986,62 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, } } } + else if (bpp == 32) + { + if (destFormat == GpPixelFormats::kBW1) + { + for (size_t col = 0; col < numCopyCols; col++) + { + const size_t srcColIndex = col + firstSourceCol; + const size_t destColIndex = col + firstDestCol; + + const uint8_t r = currentSourceRow[srcColIndex * 4 + 2]; + const uint8_t g = currentSourceRow[srcColIndex * 4 + 1]; + const uint8_t b = currentSourceRow[srcColIndex * 4 + 0]; + + if (r + g + b > 382) + currentDestRow[destColIndex] = 0; + else + currentDestRow[destColIndex] = 1; + } + } + else + { + if (!errorDiffusion) + { + for (size_t col = 0; col < numCopyCols; col++) + { + const size_t srcColIndex = col + firstSourceCol; + const size_t destColIndex = col + firstDestCol; + + const uint8_t r = currentSourceRow[srcColIndex * 4 + 2]; + const uint8_t g = currentSourceRow[srcColIndex * 4 + 1]; + const uint8_t b = currentSourceRow[srcColIndex * 4 + 0]; + + uint8_t colorIndex = stdPalette->MapColorLUT(PortabilityLayer::RGBAColor::Create(r, g, b, 255)); + + currentDestRow[destColIndex] = colorIndex; + } + } + else + { + for (size_t col = 0; col < numCopyCols; col++) + { + const size_t srcColIndex = col + firstSourceCol; + const size_t destColIndex = col + firstDestCol; + + ErrorDiffusionWorkPixel wp = ApplyErrorDiffusion(errorDiffusionCurrentRow, currentSourceRow[srcColIndex * 4 + 2], currentSourceRow[srcColIndex * 4 + 1], currentSourceRow[srcColIndex * 4 + 0], col, numCopyCols); + + uint8_t colorIndex = stdPalette->MapColorLUT(PortabilityLayer::RGBAColor::Create(wp.m_8[0], wp.m_8[1], wp.m_8[2], 255)); + PortabilityLayer::RGBAColor resultColor = stdPalette->GetColors()[colorIndex]; + + RedistributeError(errorDiffusionNextRow, errorDiffusionCurrentRow, wp.m_16[0], wp.m_16[1], wp.m_16[2], resultColor.r, resultColor.g, resultColor.b, col, numCopyCols); + + currentDestRow[destColIndex] = colorIndex; + } + } + } + } currentSourceRow -= sourcePitch; currentDestRow += destPitch; @@ -907,7 +1082,7 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, const size_t destColIndex = col + firstDestCol; const unsigned int srcIndex = (currentSourceRow[srcColIndex / 8] >> (8 - ((srcColIndex & 7) + 1))) & 0x01; - currentDestRow32[destColIndex] = srcIndex ? blackColor32 : whiteColor32; + currentDestRow32[destColIndex] = unpackedColors[srcIndex]; } } else if (bpp == 4) @@ -943,17 +1118,10 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, const uint8_t srcHigh = currentSourceRow[srcColIndex * 2 + 1]; const unsigned int combinedValue = srcLow | (srcHigh << 8); - const unsigned int b = (combinedValue & 0x1f); - const unsigned int g = ((combinedValue >> 5) & 0x1f); - const unsigned int r = ((combinedValue >> 10) & 0x1f); - const unsigned int xr = (r << 5) | (r >> 2); - const unsigned int xg = (g << 5) | (g >> 2); - const unsigned int xb = (b << 5) | (b >> 2); + for (unsigned int ch = 0; ch < 3; ch++) + currentDestRowBytes[destColIndex * 4 + ch] = (((combinedValue & masks[ch]) >> maskRShift1[ch]) * maskMultiplier[ch]) >> maskRShift2[ch]; - currentDestRowBytes[destColIndex * 4 + 0] = xr; - currentDestRowBytes[destColIndex * 4 + 1] = xg; - currentDestRowBytes[destColIndex * 4 + 2] = xb; currentDestRowBytes[destColIndex * 4 + 3] = 255; } } @@ -970,6 +1138,19 @@ void DrawSurface::DrawPicture(THandle pictHdl, const Rect &bounds, currentDestRowBytes[destColIndex * 4 + 3] = 255; } } + else if (bpp == 32) + { + for (size_t col = 0; col < numCopyCols; col++) + { + const size_t srcColIndex = col + firstSourceCol; + const size_t destColIndex = col + firstDestCol; + + currentDestRowBytes[destColIndex * 4 + 0] = currentSourceRow[srcColIndex * 4 + 2]; + currentDestRowBytes[destColIndex * 4 + 1] = currentSourceRow[srcColIndex * 4 + 1]; + currentDestRowBytes[destColIndex * 4 + 2] = currentSourceRow[srcColIndex * 4 + 0]; + currentDestRowBytes[destColIndex * 4 + 3] = 255; + } + } currentSourceRow -= sourcePitch; currentDestRowBytes += destPitch; diff --git a/PortabilityLayer/PLResourceManager.cpp b/PortabilityLayer/PLResourceManager.cpp index bd685dc..71ff27a 100644 --- a/PortabilityLayer/PLResourceManager.cpp +++ b/PortabilityLayer/PLResourceManager.cpp @@ -40,6 +40,61 @@ static const char *kPICTExtension = ".bmp"; typedef ResourceValidationRules::ResourceValidationRule ResourceValidationRule_t; +namespace PortabilityLayer +{ + class ResourceArchiveZipFileIterator : public IResourceIterator + { + public: + explicit ResourceArchiveZipFileIterator(PortabilityLayer::ZipFileProxy *proxy); + + void Destroy() override; + bool GetOne(ResTypeID &resTypeID, int16_t &outID) override; + + private: + ~ResourceArchiveZipFileIterator(); + + PortabilityLayer::ZipFileProxy *m_proxy; + size_t m_numFiles; + size_t m_currentIndex; + }; +} + + +PortabilityLayer::ResourceArchiveZipFileIterator::ResourceArchiveZipFileIterator(PortabilityLayer::ZipFileProxy *proxy) + : m_proxy(proxy) + , m_numFiles(proxy->NumFiles()) + , m_currentIndex(0) +{ +} + +void PortabilityLayer::ResourceArchiveZipFileIterator::Destroy() +{ + this->~ResourceArchiveZipFileIterator(); + PortabilityLayer::MemoryManager::GetInstance()->Release(this); +} + +bool PortabilityLayer::ResourceArchiveZipFileIterator::GetOne(ResTypeID &resTypeID, int16_t &outID) +{ + while (m_currentIndex != m_numFiles) + { + const size_t index = m_currentIndex++; + + const char *name = nullptr; + size_t nameLength = 0; + m_proxy->GetFileName(index, name, nameLength); + + const bool isParseable = ResourceArchiveZipFile::ParseResFromName(name, nameLength, resTypeID, outID); + if (isParseable) + return true; + } + + return false; +} + +PortabilityLayer::ResourceArchiveZipFileIterator::~ResourceArchiveZipFileIterator() +{ +} + namespace { // Validation here is only intended to be minimal, to ensure later checks can determine the format size and do certain operations @@ -433,6 +488,93 @@ namespace PortabilityLayer return haveAny; } + IResourceIterator *ResourceArchiveZipFile::EnumerateResources() const + { + PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance(); + void *storage = mm->Alloc(sizeof(ResourceArchiveZipFileIterator)); + if (!storage) + return nullptr; + + return new (storage) ResourceArchiveZipFileIterator(m_zipFileProxy); + } + + bool ResourceArchiveZipFile::ParseResFromName(const char *name, size_t nameLength, ResTypeID &outResTypeID, int16_t &outID) + { + size_t slashPos = nameLength; + for (size_t i = 0; i < nameLength; i++) + { + if (name[i] == '/') + { + slashPos = i; + break; + } + } + + if (slashPos == nameLength) + return false; + + GpArcResourceTypeTag resTag; + if (!resTag.Load(name, slashPos)) + return false; + + if (!resTag.Decode(outResTypeID)) + return false; + + int validationRule = 0; + const char *expectedExtension = GetFileExtensionForResType(outResTypeID, validationRule); + + const size_t idStart = slashPos + 1; + if (idStart == nameLength) + return false; + + bool isNegative = false; + size_t digitsStart = idStart; + + if (name[idStart] == '-') + { + isNegative = true; + digitsStart++; + } + + size_t digitsEnd = nameLength; + int32_t resID = 0; + for (size_t i = digitsStart; i < nameLength; i++) + { + if (name[i] == '.') + { + digitsEnd = i; + break; + } + else + { + char digit = name[i]; + if (digit < '0' || digit > '9') + return false; + + int32_t digitNumber = static_cast(digit) - '0'; + resID *= 10; + if (isNegative) + resID -= digitNumber; + else + resID += digitNumber; + + if (resID < -32768 || resID > 32767) + return false; + } + } + + outID = static_cast(resID); + + const size_t extAvailable = nameLength - digitsEnd; + if (strlen(expectedExtension) != extAvailable) + return false; + + if (memcmp(name + digitsEnd, expectedExtension, extAvailable) != 0) + return false; + + return true; + } + bool ResourceArchiveZipFile::IndexResource(const ResTypeID &resTypeID, int id, size_t &outIndex, int &outValidationRule) const { const char *extension = GetFileExtensionForResType(resTypeID, outValidationRule); @@ -461,6 +603,11 @@ namespace PortabilityLayer handle = ref->m_handle; else { + const size_t resSize = m_zipFileProxy->GetFileSize(index); + + if (resSize > kMaxResourceSize) + return THandle(); + handle = MemoryManager::GetInstance()->AllocHandle(0); if (!handle) return THandle(); @@ -468,7 +615,7 @@ namespace PortabilityLayer handle->m_rmSelfRef = ref; ref->m_handle = handle; ref->m_resID = static_cast(id); - ref->m_size = m_zipFileProxy->GetFileSize(index); + ref->m_size = resSize; } if (handle->m_contents == nullptr && load) diff --git a/PortabilityLayer/QDPictHeader.cpp b/PortabilityLayer/QDPictHeader.cpp index d30abcd..fd27226 100644 --- a/PortabilityLayer/QDPictHeader.cpp +++ b/PortabilityLayer/QDPictHeader.cpp @@ -55,9 +55,9 @@ namespace PortabilityLayer memcpy(&v2Version, v2Header + 4, 2); // In version 2 header, v2Version == -1 - // Followed by fixed-point bounding rectangle (16 bytes) and 4 reserved + // Followed by 2-byte reserved (usually -1), fixed-point bounding rectangle (16 bytes) and 4 reserved (usually 0) // In ext. version 2 header, v2Version == -2 - // Followed by 2-byte reserved, horizontal DPI (fixed point, 4 bytes), vertical DPI (fixed point, 4 bytes) optimal source rect (8 bytes), and 2 reserved + // Followed by 2-byte reserved (0), horizontal DPI (fixed point, 4 bytes), vertical DPI (fixed point, 4 bytes) optimal source rect (8 bytes), and 2 reserved } else return false; diff --git a/PortabilityLayer/ResourceManager.h b/PortabilityLayer/ResourceManager.h index 95f0546..f6b884b 100644 --- a/PortabilityLayer/ResourceManager.h +++ b/PortabilityLayer/ResourceManager.h @@ -27,6 +27,12 @@ namespace PortabilityLayer int16_t m_resID; }; + struct IResourceIterator + { + virtual void Destroy() = 0; + virtual bool GetOne(ResTypeID &resTypeID, int16_t &outID) = 0; + }; + struct IResourceArchive { virtual void Destroy() = 0; @@ -35,6 +41,8 @@ namespace PortabilityLayer virtual bool HasAnyResourcesOfType(const ResTypeID &resTypeID) const = 0; virtual bool FindFirstResourceOfType(const ResTypeID &resTypeID, int16_t &outID) const = 0; + + virtual IResourceIterator *EnumerateResources() const = 0; }; class ResourceArchiveBase : public IResourceArchive @@ -43,9 +51,13 @@ namespace PortabilityLayer static const char *GetFileExtensionForResType(const ResTypeID &resTypeID, int &outValidationRule); }; + class ResourceArchiveZipFileIterator; + class ResourceArchiveZipFile final : public ResourceArchiveBase { public: + friend class ResourceArchiveZipFileIterator; + static ResourceArchiveZipFile *Create(ZipFileProxy *zipFileProxy, bool proxyIsShared, GpIOStream *stream); void Destroy() override; @@ -54,7 +66,14 @@ namespace PortabilityLayer bool HasAnyResourcesOfType(const ResTypeID &resTypeID) const override; bool FindFirstResourceOfType(const ResTypeID &resTypeID, int16_t &outID) const override; + IResourceIterator *EnumerateResources() const override; + + static bool ParseResFromName(const char *name, size_t nameLength, ResTypeID &outResTypeID, int16_t &outID); + + private: + static const size_t kMaxResourceSize = 32 * 1024 * 1024; + ResourceArchiveZipFile(ZipFileProxy *zipFileProxy, bool proxyIsShared, GpIOStream *stream, ResourceArchiveRef *resourceHandles); ~ResourceArchiveZipFile();