#include "ResourceFile.h" #include "BinarySearch.h" #include "ByteSwap.h" #include "MacFileMem.h" #include "MemoryManager.h" #include "MemReaderStream.h" #include "ResourceCompiledRef.h" #include "ResourceCompiledTypeList.h" #include namespace PortabilityLayer { ResourceFile::ResourceFile() : m_resDataBlob(nullptr) , m_resDataBlobSize(0) , m_resNameBlob(nullptr) , m_resNameBlobSize(0) , m_compiledRefBlob(nullptr) , m_numResources(0) , m_compiledTypeListBlob(nullptr) , m_numResourceTypes(0) { } ResourceFile::~ResourceFile() { if (m_resNameBlob) delete[] m_resNameBlob; if (m_resDataBlob) delete[] m_resDataBlob; if (m_compiledRefBlob) delete[] m_compiledRefBlob; if (m_compiledTypeListBlob) delete[] m_compiledTypeListBlob; } bool ResourceFile::Load(IOStream *stream) { struct ResourceHeader { uint32_t m_resDataOffset; uint32_t m_resMapOffset; uint32_t m_resDataSize; uint32_t m_resMapSize; }; const UFilePos_t streamSize = stream->Size(); if (streamSize > UINT32_MAX) return false; uint32_t resForkSize = static_cast(streamSize); ResourceHeader resourceHeader; if (stream->Read(&resourceHeader, sizeof(ResourceHeader)) != sizeof(ResourceHeader)) return false; ByteSwap::BigUInt32(resourceHeader.m_resDataOffset); ByteSwap::BigUInt32(resourceHeader.m_resMapOffset); ByteSwap::BigUInt32(resourceHeader.m_resDataSize); ByteSwap::BigUInt32(resourceHeader.m_resMapSize); if (resourceHeader.m_resDataOffset > resForkSize) return false; if (resourceHeader.m_resMapOffset > resForkSize) return false; if (resForkSize - resourceHeader.m_resDataOffset < resourceHeader.m_resDataSize) return false; if (resForkSize - resourceHeader.m_resMapOffset < resourceHeader.m_resMapSize) return false; if (!stream->SeekStart(resourceHeader.m_resDataOffset)) return false; m_resDataBlob = new uint8_t[resourceHeader.m_resDataSize]; m_resDataBlobSize = resourceHeader.m_resDataSize; if (stream->Read(m_resDataBlob, resourceHeader.m_resDataSize) != resourceHeader.m_resDataSize) return false; // The format of this is slightly different from the documented format: // The type list offset is the offset of the type COUNT, which takes up 2 bytes. // Usually the offset is 28, even though the size of the resource map including the // count would appear to be 30. struct ResourceMap { uint8_t m_reserved[16 + 4 + 2]; uint16_t m_attributes; uint16_t m_typeListOffset; uint16_t m_nameListOffset; }; struct ResourceTypeListEntry { ResTypeID m_resType; uint16_t m_numResourcesMinusOne; uint16_t m_refListOffset; }; struct ResourceRefListEntry { int16_t m_resID; int16_t m_resourceNameOffset; uint8_t m_attributes; uint8_t m_packedResDataOffset[3]; uint32_t m_reserved; }; if (!stream->SeekStart(resourceHeader.m_resMapOffset)) return false; ResourceMap resourceMap; if (stream->Read(&resourceMap, sizeof(ResourceMap)) != sizeof(ResourceMap)) return false; ByteSwap::BigUInt16(resourceMap.m_attributes); ByteSwap::BigUInt16(resourceMap.m_typeListOffset); ByteSwap::BigUInt16(resourceMap.m_nameListOffset); const size_t sizeFromStartOfResMap = resForkSize - resourceHeader.m_resMapOffset; if (resourceMap.m_typeListOffset > sizeFromStartOfResMap) return false; if (resourceMap.m_nameListOffset > sizeFromStartOfResMap) return false; const size_t typeListOffset = resourceHeader.m_resMapOffset + resourceMap.m_typeListOffset; const size_t sizeFromStartOfTypeList = resForkSize - typeListOffset; // First pass: Count the number of references we need if (!stream->SeekStart(typeListOffset)) return false; uint16_t numTypesMinusOne; if (stream->Read(&numTypesMinusOne, 2) != 2) return false; ByteSwap::BigUInt16(numTypesMinusOne); // numTypesMinusOne can sometimes be -1, in which case there are no resources m_numResourceTypes = (numTypesMinusOne + 1) & 0xffff; if (sizeFromStartOfTypeList < 2 || (sizeFromStartOfTypeList - 2) / 8 < m_numResourceTypes) return false; uint32_t numResourcesTotal = 0; for (uint32_t i = 0; i < m_numResourceTypes; i++) { ResourceTypeListEntry tlEntry; if (stream->Read(&tlEntry, sizeof(ResourceTypeListEntry)) != sizeof(ResourceTypeListEntry)) return false; ByteSwap::BigUInt16(tlEntry.m_numResourcesMinusOne); const uint32_t numResourcesOfThisType = tlEntry.m_numResourcesMinusOne + 1; if (UINT32_MAX - numResourcesTotal < numResourcesOfThisType) return false; numResourcesTotal += numResourcesOfThisType; } if (numResourcesTotal > 0) m_compiledRefBlob = new ResourceCompiledRef[numResourcesTotal]; m_numResources = numResourcesTotal; // Second pass: Compile references ResourceCompiledRef *refToCompile = m_compiledRefBlob; if (m_numResourceTypes > 0) m_compiledTypeListBlob = new ResourceCompiledTypeList[m_numResourceTypes]; for (uint32_t i = 0; i < m_numResourceTypes; i++) { if (!stream->SeekStart(typeListOffset + 2 + i * 8)) return false; ResourceTypeListEntry tlEntry; if (stream->Read(&tlEntry, sizeof(ResourceTypeListEntry)) != sizeof(ResourceTypeListEntry)) return false; ByteSwap::BigUInt16(tlEntry.m_numResourcesMinusOne); ByteSwap::BigUInt16(tlEntry.m_refListOffset); ResourceCompiledTypeList &ctl = m_compiledTypeListBlob[i]; ctl.m_resType = tlEntry.m_resType; ctl.m_firstRef = refToCompile; ctl.m_numRefs = tlEntry.m_numResourcesMinusOne + 1; if (sizeFromStartOfTypeList < tlEntry.m_refListOffset) return false; // Start reading the ref list if (!stream->SeekStart(typeListOffset + tlEntry.m_refListOffset)) return false; const uint32_t numResourcesOfThisType = tlEntry.m_numResourcesMinusOne + 1; ResourceCompiledRef *firstRefOfThisType = refToCompile; for (uint32_t i = 0; i < numResourcesOfThisType; i++) { ResourceRefListEntry refListEntry; if (stream->Read(&refListEntry, sizeof(ResourceRefListEntry)) != sizeof(ResourceRefListEntry)) return false; ByteSwap::BigInt16(refListEntry.m_resID); ByteSwap::BigInt16(refListEntry.m_resourceNameOffset); const uint8_t *packedOffset = refListEntry.m_packedResDataOffset; const size_t dataSizeOffset = (packedOffset[0] << 16) + (packedOffset[1] << 8) + packedOffset[2]; if (dataSizeOffset > resourceHeader.m_resDataSize) return false; // Needs to be at least size 4 to decode the resource size if (resourceHeader.m_resDataSize - dataSizeOffset < 4) return false; const size_t dataOffset = dataSizeOffset + 4; refToCompile->m_attributes = refListEntry.m_attributes; refToCompile->m_resData = m_resDataBlob + dataOffset; refToCompile->m_resID = refListEntry.m_resID; refToCompile->m_resNameOffset = refListEntry.m_resourceNameOffset; uint32_t resSize; memcpy(&resSize, m_resDataBlob + dataSizeOffset, 4); ByteSwap::BigUInt32(resSize); if (resSize > resourceHeader.m_resDataSize) return false; if (resourceHeader.m_resDataSize - resSize < dataOffset) return false; refToCompile++; } std::sort(firstRefOfThisType, refToCompile, CompiledRefSortPredicate); for (uint32_t i = 1; i < numResourcesOfThisType; i++) { if (firstRefOfThisType[i - 1].m_resID == firstRefOfThisType[i].m_resID) return false; } } if (m_numResources > 0) { bool anyNamed = false; size_t lastNameStart = 0; for (size_t i = 0; i < m_numResources; i++) { const ResourceCompiledRef &ref = m_compiledRefBlob[i]; if (ref.m_resNameOffset < 0) { if (ref.m_resNameOffset != -1) return false; // Non-compliant } else { anyNamed = true; const size_t candidateOffset = static_cast(ref.m_resNameOffset); if (candidateOffset > lastNameStart) lastNameStart = candidateOffset; } } if (anyNamed) { const size_t nameListCapacity = sizeFromStartOfResMap - resourceMap.m_nameListOffset; if (lastNameStart >= nameListCapacity) return false; m_resNameBlobSize = lastNameStart + 1 + 256; m_resNameBlob = new uint8_t[m_resNameBlobSize]; memset(m_resNameBlob, 0, m_resNameBlobSize); if (!stream->SeekStart(resourceHeader.m_resMapOffset + resourceMap.m_nameListOffset)) return false; if (stream->Read(m_resNameBlob, lastNameStart + 1) != lastNameStart + 1) return false; // Figure out the length of the final string const size_t lastStringLength = m_resNameBlob[lastNameStart]; if (lastStringLength > 0) { if (stream->Read(m_resNameBlob + lastNameStart + 1, lastStringLength) != lastStringLength) return false; } } } std::sort(m_compiledTypeListBlob, m_compiledTypeListBlob + m_numResourceTypes, CompiledTypeListSortPredicate); for (uint32_t i = 1; i < m_numResourceTypes; i++) { if (m_compiledTypeListBlob[i - 1].m_resType == m_compiledTypeListBlob[i].m_resType) return false; } return true; } bool ResourceFile::CompiledRefSortPredicate(const ResourceCompiledRef &a, const ResourceCompiledRef &b) { return a.m_resID < b.m_resID; } bool ResourceFile::CompiledTypeListSortPredicate(const ResourceCompiledTypeList &a, const ResourceCompiledTypeList &b) { return memcmp(&a.m_resType, &b.m_resType, sizeof(ResTypeID)) < 0; } int ResourceFile::CompiledRefSearchPredicate(int resID, const ResourceCompiledRef &ref) { return resID - ref.m_resID; } int ResourceFile::CompiledTypeListSearchPredicate(const ResTypeID &resTypeID, const ResourceCompiledTypeList &typeList) { return memcmp(&resTypeID, &typeList.m_resType, 4); } void ResourceFile::GetAllResourceTypeLists(ResourceCompiledTypeList *&outTypeLists, size_t &outCount) const { outTypeLists = m_compiledTypeListBlob; outCount = m_numResourceTypes; } const ResourceCompiledTypeList *ResourceFile::GetResourceTypeList(const ResTypeID &resType) { const ResourceCompiledTypeList *tlStart = m_compiledTypeListBlob; const ResourceCompiledTypeList *tlEnd = tlStart + m_numResourceTypes; const ResourceCompiledTypeList *tl = BinarySearch(tlStart, tlEnd, resType, CompiledTypeListSearchPredicate); if (tl == tlEnd) return nullptr; return tl; } THandle ResourceFile::LoadResource(const ResTypeID &resType, int id) { const ResourceCompiledTypeList *tl = GetResourceTypeList(resType); if (tl == nullptr) return THandle(); ResourceCompiledRef *refStart = tl->m_firstRef; ResourceCompiledRef *refEnd = refStart + tl->m_numRefs; ResourceCompiledRef *ref = BinarySearch(refStart, refEnd, id, CompiledRefSearchPredicate); if (ref == refEnd) return THandle(); MMHandleBlock *handle = MemoryManager::GetInstance()->AllocHandle(0); const uint32_t resSize = ref->GetSize(); if (resSize > 0) { void *contents = MemoryManager::GetInstance()->Alloc(resSize); handle->m_contents = contents; handle->m_size = resSize; memcpy(handle->m_contents, ref->m_resData, resSize); } return THandle(handle); } ResourceFile *ResourceFile::Create() { void *storage = PortabilityLayer::MemoryManager::GetInstance()->Alloc(sizeof(ResourceFile)); if (!storage) return nullptr; return new (storage) ResourceFile(); } void ResourceFile::Destroy() { this->~ResourceFile(); PortabilityLayer::MemoryManager::GetInstance()->Release(this); } }