mirror of
https://github.com/libretro/Play-.git
synced 2024-11-27 18:50:28 +00:00
241 lines
5.8 KiB
C++
241 lines
5.8 KiB
C++
#include <algorithm>
|
|
#include <stdexcept>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include "CsoImageStream.h"
|
|
#include "zlib.h"
|
|
|
|
typedef uint32 uint32_le;
|
|
typedef uint64 uint64_le;
|
|
|
|
static const uint32 CSO_READ_BUFFER_SIZE = 256 * 1024;
|
|
|
|
struct CsoHeader
|
|
{
|
|
uint8 magic[4];
|
|
uint32_le header_size;
|
|
uint64_le total_bytes;
|
|
uint32_le frame_size;
|
|
uint8 ver;
|
|
uint8 align;
|
|
uint8 reserved[2];
|
|
};
|
|
|
|
CCsoImageStream::CCsoImageStream(CStream* baseStream)
|
|
: m_baseStream(baseStream)
|
|
, m_readBuffer(nullptr)
|
|
, m_zlibBuffer(nullptr)
|
|
, m_index(nullptr)
|
|
, m_position(0)
|
|
{
|
|
if(baseStream == nullptr)
|
|
{
|
|
throw std::runtime_error("Null base stream supplied.");
|
|
}
|
|
|
|
ReadFileHeader();
|
|
InitializeBuffers();
|
|
}
|
|
|
|
CCsoImageStream::~CCsoImageStream()
|
|
{
|
|
delete[] m_readBuffer;
|
|
delete[] m_zlibBuffer;
|
|
delete[] m_index;
|
|
}
|
|
|
|
void CCsoImageStream::ReadFileHeader()
|
|
{
|
|
CsoHeader hdr = {};
|
|
|
|
m_baseStream->Seek(0, Framework::STREAM_SEEK_SET);
|
|
if(m_baseStream->Read(&hdr, sizeof(CsoHeader)) != sizeof(CsoHeader))
|
|
{
|
|
throw std::runtime_error("Could not read full CSO header.");
|
|
}
|
|
|
|
if(hdr.magic[0] != 'C' || hdr.magic[1] != 'I' || hdr.magic[2] != 'S' || hdr.magic[3] != 'O')
|
|
{
|
|
throw std::runtime_error("Not a valid CSO file.");
|
|
}
|
|
if(hdr.ver > 1)
|
|
{
|
|
throw std::runtime_error("Only CSOv1 supported right now.");
|
|
}
|
|
|
|
m_frameSize = hdr.frame_size;
|
|
if((m_frameSize & (m_frameSize - 1)) != 0)
|
|
{
|
|
throw std::runtime_error("CSO frame size must be a power of two.");
|
|
}
|
|
if(m_frameSize < 0x800)
|
|
{
|
|
throw std::runtime_error("CSO frame size must be at least one sector.");
|
|
}
|
|
|
|
// Determine the translation from bytes to frame.
|
|
m_frameShift = 0;
|
|
for(uint32 i = m_frameSize; i > 1; i >>= 1)
|
|
{
|
|
++m_frameShift;
|
|
}
|
|
|
|
m_indexShift = hdr.align;
|
|
m_totalSize = hdr.total_bytes;
|
|
}
|
|
|
|
void CCsoImageStream::InitializeBuffers()
|
|
{
|
|
uint32 numFrames = static_cast<uint32>((m_totalSize + m_frameSize - 1) / m_frameSize);
|
|
|
|
// We might read a bit of alignment too, so be prepared.
|
|
if(m_frameSize + (1 << m_indexShift) < CSO_READ_BUFFER_SIZE)
|
|
{
|
|
m_readBuffer = new uint8[CSO_READ_BUFFER_SIZE];
|
|
}
|
|
else
|
|
{
|
|
m_readBuffer = new uint8[m_frameSize + (1 << m_indexShift)];
|
|
}
|
|
m_zlibBuffer = new uint8[m_frameSize + (1 << m_indexShift)];
|
|
m_zlibBufferFrame = numFrames;
|
|
|
|
const uint32 indexSize = numFrames + 1;
|
|
m_index = new uint32[indexSize];
|
|
if(m_baseStream->Read(m_index, sizeof(uint32) * indexSize) != sizeof(uint32) * indexSize)
|
|
{
|
|
throw std::runtime_error("Unable to read CSO index.");
|
|
}
|
|
}
|
|
|
|
void CCsoImageStream::Seek(int64 position, Framework::STREAM_SEEK_DIRECTION origin)
|
|
{
|
|
switch(origin)
|
|
{
|
|
case Framework::STREAM_SEEK_CUR:
|
|
m_position += position;
|
|
break;
|
|
case Framework::STREAM_SEEK_SET:
|
|
m_position = position;
|
|
break;
|
|
case Framework::STREAM_SEEK_END:
|
|
m_position = GetTotalSize() + position;
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint64 CCsoImageStream::Tell()
|
|
{
|
|
return m_position;
|
|
}
|
|
|
|
bool CCsoImageStream::IsEOF()
|
|
{
|
|
return m_position >= GetTotalSize();
|
|
}
|
|
|
|
uint64 CCsoImageStream::Read(void* buffer, uint64 size)
|
|
{
|
|
uint64 remaining = size;
|
|
uint8* dest = reinterpret_cast<uint8*>(buffer);
|
|
|
|
// We read only one frame at a time for now.
|
|
while(remaining > 0 && !IsEOF())
|
|
{
|
|
uint32 bytes = ReadFromNextFrame(dest, remaining);
|
|
remaining -= bytes;
|
|
m_position += bytes;
|
|
dest += bytes;
|
|
}
|
|
|
|
return size - remaining;
|
|
}
|
|
|
|
uint64 CCsoImageStream::Write(const void* buffer, uint64 size)
|
|
{
|
|
throw std::runtime_error("Unable to write to CSO, read only.");
|
|
}
|
|
|
|
uint64 CCsoImageStream::GetTotalSize() const
|
|
{
|
|
return m_totalSize;
|
|
}
|
|
|
|
uint32 CCsoImageStream::ReadFromNextFrame(uint8* dest, uint64 maxBytes)
|
|
{
|
|
assert(!IsEOF());
|
|
|
|
const uint32 frame = static_cast<uint32>(m_position >> m_frameShift);
|
|
const uint32 offset = static_cast<uint32>(m_position - (frame << m_frameShift));
|
|
// This is how many bytes we will actually be reading from this frame.
|
|
const uint32 bytes = static_cast<uint32>(std::min(maxBytes, static_cast<uint64>(m_frameSize - offset)));
|
|
|
|
// Grab the index data for the frame we're about to read.
|
|
const bool compressed = (m_index[frame + 0] & 0x80000000) == 0;
|
|
const uint32 index0 = m_index[frame + 0] & 0x7FFFFFFF;
|
|
const uint32 index1 = m_index[frame + 1] & 0x7FFFFFFF;
|
|
|
|
// Calculate where the compressed payload is (if compressed.)
|
|
const uint64 frameRawPos = static_cast<uint64>(index0) << m_indexShift;
|
|
const uint64 frameRawSize = (index1 - index0) << m_indexShift;
|
|
|
|
if(!compressed)
|
|
{
|
|
// Just read directly, easy.
|
|
if(ReadBaseAt(frameRawPos + offset, dest, bytes) != bytes)
|
|
{
|
|
throw std::runtime_error("Unable to read uncompressed bytes from CSO.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We don't need to decompress if we already did this same frame last time.
|
|
if(m_zlibBufferFrame != frame)
|
|
{
|
|
// This might be less bytes than frameRawSize in case of padding on the last frame.
|
|
// This is because the index positions must be aligned.
|
|
const uint64 readRawBytes = ReadBaseAt(frameRawPos, m_readBuffer, frameRawSize);
|
|
DecompressFrame(frame, readRawBytes);
|
|
}
|
|
|
|
// Now we just copy the offset data from the cache.
|
|
memcpy(dest, m_zlibBuffer + offset, bytes);
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
void CCsoImageStream::DecompressFrame(uint32 frame, uint64 readBufferSize)
|
|
{
|
|
z_stream z;
|
|
z.zalloc = Z_NULL;
|
|
z.zfree = Z_NULL;
|
|
z.opaque = Z_NULL;
|
|
if(inflateInit2(&z, -15) != Z_OK)
|
|
{
|
|
throw std::runtime_error("Unable to initialize zlib for CSO decompression.");
|
|
}
|
|
|
|
z.next_in = m_readBuffer;
|
|
z.avail_in = static_cast<uint32>(readBufferSize);
|
|
z.next_out = m_zlibBuffer;
|
|
z.avail_out = m_frameSize;
|
|
|
|
int status = inflate(&z, Z_FINISH);
|
|
if(status != Z_STREAM_END || z.total_out != m_frameSize)
|
|
{
|
|
inflateEnd(&z);
|
|
throw std::runtime_error("Unable to decompress CSO frame using zlib.");
|
|
}
|
|
inflateEnd(&z);
|
|
|
|
// Our buffer now contains this frame.
|
|
m_zlibBufferFrame = frame;
|
|
}
|
|
|
|
uint64 CCsoImageStream::ReadBaseAt(uint64 pos, uint8* dest, uint64 bytes)
|
|
{
|
|
m_baseStream->Seek(pos, Framework::STREAM_SEEK_SET);
|
|
return m_baseStream->Read(dest, bytes);
|
|
}
|