Files
Aerofoil/PortabilityLayer/ResourceFile.cpp

400 lines
11 KiB
C++

#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 <algorithm>
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<uint32_t>(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<size_t>(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<void> ResourceFile::LoadResource(const ResTypeID &resType, int id)
{
const ResourceCompiledTypeList *tl = GetResourceTypeList(resType);
if (tl == nullptr)
return THandle<void>();
ResourceCompiledRef *refStart = tl->m_firstRef;
ResourceCompiledRef *refEnd = refStart + tl->m_numRefs;
ResourceCompiledRef *ref = BinarySearch(refStart, refEnd, id, CompiledRefSearchPredicate);
if (ref == refEnd)
return THandle<void>();
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<void>(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);
}
}