mirror of
https://github.com/PSI-Rockin/DobieStation.git
synced 2024-05-20 05:01:02 -04:00
343 lines
8.6 KiB
C++
343 lines
8.6 KiB
C++
/*
|
|
CSO (v0, v1) decoder implementation
|
|
copyleft 2019 a dinosaur
|
|
|
|
Based off reference by unknownbrackets:
|
|
- https://github.com/unknownbrackets/maxcso/blob/master/README_CSO.md
|
|
*/
|
|
|
|
#include "cso_reader.hpp"
|
|
#include <zlib.h>
|
|
#include <cstring>
|
|
#include <cassert>
|
|
|
|
constexpr uint32_t FOURCC(const char chars[4])
|
|
{
|
|
return
|
|
((uint32_t)(unsigned char)chars[0] << 0) |
|
|
((uint32_t)(unsigned char)chars[1] << 8) |
|
|
((uint32_t)(unsigned char)chars[2] << 16) |
|
|
((uint32_t)(unsigned char)chars[3] << 24);
|
|
}
|
|
|
|
struct CSO_Header
|
|
{
|
|
// fourcc "CISO"
|
|
uint32_t magic;
|
|
|
|
// v0, v1: not reliable
|
|
// v2: must be 0x18
|
|
uint32_t header_len;
|
|
|
|
// uncompressed size of original ISO
|
|
uint64_t raw_len;
|
|
|
|
// usually 2048
|
|
uint32_t block_len;
|
|
|
|
uint8_t version;
|
|
uint8_t index_shift;
|
|
uint8_t reserved[2];
|
|
};
|
|
|
|
#define IDX_COMPRESS_BIT (0x80000000)
|
|
|
|
|
|
CSO_Reader::CSO_Reader() :
|
|
m_size(0), m_shift(0), m_blocksize(0), m_version(0), m_virtptr(0),
|
|
m_indices(nullptr),
|
|
m_framesize(0), m_curframe(0xFFFFFFFF),
|
|
m_frame(nullptr), m_readbuf(nullptr) {}
|
|
|
|
CSO_Reader::~CSO_Reader()
|
|
{
|
|
close();
|
|
}
|
|
|
|
|
|
uint8_t CSO_Reader::get_version()
|
|
{
|
|
return m_version;
|
|
}
|
|
|
|
size_t CSO_Reader::get_size()
|
|
{
|
|
return m_size;
|
|
}
|
|
|
|
uint32_t CSO_Reader::get_blocksize()
|
|
{
|
|
return m_blocksize;
|
|
}
|
|
|
|
uint32_t CSO_Reader::get_numblocks()
|
|
{
|
|
return (uint32_t)(m_size / m_blocksize);
|
|
}
|
|
|
|
|
|
bool CSO_Reader::is_open()
|
|
{
|
|
return m_file.is_open();
|
|
}
|
|
|
|
void CSO_Reader::seek(size_t ofs, std::ios::seekdir whence)
|
|
{
|
|
ofs *= 2048;
|
|
if (whence == std::ios::beg)
|
|
{
|
|
if ((uint64_t)ofs < m_size)
|
|
m_virtptr = (uint64_t)ofs;
|
|
}
|
|
else
|
|
if (whence == std::ios::cur)
|
|
{
|
|
if (m_virtptr + ofs < m_size)
|
|
m_virtptr = m_virtptr + ofs;
|
|
}
|
|
else
|
|
if (whence == std::ios::end)
|
|
{
|
|
if (m_size - ofs < m_size)
|
|
m_virtptr = m_size - ofs;
|
|
}
|
|
}
|
|
|
|
uint64_t CSO_Reader::tell()
|
|
{
|
|
return m_virtptr;
|
|
}
|
|
|
|
bool CSO_Reader::read_block_internal(uint32_t block)
|
|
{
|
|
// if this block was decoded last time we don't need to do it again
|
|
if (block == m_curframe)
|
|
return true;
|
|
|
|
uint32_t index = m_indices[block];
|
|
uint64_t ofs = (uint64_t)(index & ~IDX_COMPRESS_BIT) << m_shift;
|
|
uint64_t len = ((uint64_t)(m_indices[block + 1] & ~IDX_COMPRESS_BIT) << m_shift) - ofs;
|
|
|
|
if (index & IDX_COMPRESS_BIT) // if uncompressed
|
|
{
|
|
m_file.seekg(ofs, std::ios::beg);
|
|
m_file.read((char*)m_frame, len);
|
|
if ((uint64_t)m_file.gcount() != len)
|
|
{
|
|
fprintf(stderr, "read error reading (uncompressed) block %d\n", block);
|
|
m_curframe = 0xFFFFFFFF;
|
|
return false;
|
|
}
|
|
}
|
|
else // compressed
|
|
{
|
|
m_file.seekg(ofs, std::ios::beg);
|
|
m_file.read((char*)m_readbuf, len);
|
|
if ((uint64_t)m_file.gcount() != len)
|
|
{
|
|
fprintf(stderr, "read error reading (compressed) block %d\n", block);
|
|
return false;
|
|
}
|
|
|
|
z_stream z;
|
|
z.zalloc = Z_NULL;
|
|
z.zfree = Z_NULL;
|
|
z.opaque = Z_NULL;
|
|
if (inflateInit2(&z, -15) != Z_OK)
|
|
{
|
|
fprintf(stderr, "Unable to initialize inflate: %s\n", (z.msg) ? z.msg : "?");
|
|
return false;
|
|
}
|
|
|
|
z.next_in = m_readbuf;
|
|
z.avail_in = len;
|
|
z.next_out = m_frame;
|
|
z.avail_out = m_framesize;
|
|
|
|
auto res = inflate(&z, Z_FINISH);
|
|
if (res != Z_STREAM_END)
|
|
{
|
|
fprintf(stderr, "zlib error on block %d: %d, %s\n", block, res, (z.msg) ? z.msg: "?");
|
|
m_curframe = 0xFFFFFFFF;
|
|
return false;
|
|
|
|
}
|
|
|
|
size_t read = z.total_out;
|
|
|
|
if (read < m_blocksize)
|
|
{
|
|
fprintf(stderr, "compressed sector %d decoded to less than the blocksize\n", block);
|
|
m_curframe = 0xFFFFFFFF;
|
|
return false;
|
|
}
|
|
|
|
inflateEnd(&z);
|
|
}
|
|
|
|
m_curframe = block;
|
|
return true;
|
|
}
|
|
|
|
size_t CSO_Reader::read(uint8_t* dst, size_t size)
|
|
{
|
|
assert(size);
|
|
assert(m_virtptr + size <= m_size);
|
|
|
|
const uint64_t start = m_virtptr;
|
|
const uint64_t end = start + size;
|
|
const auto start_block = (uint32_t)(start / m_blocksize);
|
|
const auto end_block = (uint32_t)(end / m_blocksize);
|
|
|
|
uint64_t total_read = 0;
|
|
for (uint32_t i = start_block; i <= end_block; ++i)
|
|
{
|
|
if (!read_block_internal(i))
|
|
return total_read;
|
|
|
|
const uint64_t local_ofs = start - (uint64_t)start_block * m_blocksize;
|
|
uint64_t readlen = m_blocksize;
|
|
if (i == start_block)
|
|
readlen -= local_ofs;
|
|
if (i == end_block)
|
|
readlen -= m_blocksize - (end - (uint64_t)end_block * m_blocksize);
|
|
|
|
memcpy(dst, m_frame + local_ofs, readlen);
|
|
total_read += readlen;
|
|
m_virtptr += readlen;
|
|
dst += readlen;
|
|
}
|
|
|
|
return total_read;
|
|
}
|
|
|
|
bool CSO_Reader::open(std::string name)
|
|
{
|
|
close();
|
|
m_file = std::ifstream(name, std::ios::binary | std::ios::ate);
|
|
if (!m_file.is_open())
|
|
{
|
|
fprintf(stderr, "failed to open file\n");
|
|
return false;
|
|
}
|
|
auto file_len = m_file.tellg();
|
|
m_file.seekg(0, std::ios::beg);
|
|
|
|
CSO_Header header;
|
|
m_file.read((char*)&header.magic, sizeof(uint32_t));
|
|
m_file.read((char*)&header.header_len, sizeof(uint32_t));
|
|
m_file.read((char*)&header.raw_len, sizeof(uint64_t));
|
|
m_file.read((char*)&header.block_len, sizeof(uint32_t));
|
|
m_file.read((char*)&header.version, sizeof(uint8_t));
|
|
m_file.read((char*)&header.index_shift, sizeof(uint8_t));
|
|
m_file.read((char*)header.reserved, 2 * sizeof(uint8_t));
|
|
|
|
// validate header
|
|
if (header.magic != FOURCC("CISO"))
|
|
{
|
|
fprintf(stderr, "file is not a CSO!\n");
|
|
return false;
|
|
}
|
|
if (header.version > 1)
|
|
{
|
|
fprintf(stderr, "unsupported CSO version or corrupt file\n");
|
|
return false;
|
|
}
|
|
|
|
// reinstate these if CSOv2 support is ever added
|
|
/*
|
|
if (header.version == 2 && header.header_len != 0x18)
|
|
return false;
|
|
if (header.version == 2 && (header.reserved[0] || header.reserved[1]))
|
|
return false;
|
|
*/
|
|
|
|
// read indices
|
|
auto num_entries = (uint32_t)((header.raw_len + header.block_len - 1) / header.block_len) + 1;
|
|
m_indices = new uint32_t[num_entries];
|
|
m_file.read((char*)m_indices, num_entries * sizeof(uint32_t));
|
|
if ((uint32_t)m_file.gcount() != num_entries * sizeof(uint32_t))
|
|
{
|
|
fprintf(stderr, "failed to read CSO indices\n");
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
// sanity check indices
|
|
uint32_t lastidx = m_indices[0];
|
|
if ((lastidx & ~IDX_COMPRESS_BIT) << header.index_shift < 0x18)
|
|
{
|
|
fprintf(stderr, "CSO indices are corrupted (starts within header)\n");
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
uint32_t framesize = header.block_len + (1 << header.index_shift);
|
|
for (unsigned i = 1; i < num_entries; ++i)
|
|
{
|
|
uint32_t lastpos = (lastidx & ~IDX_COMPRESS_BIT) << header.index_shift;
|
|
if (lastpos > file_len)
|
|
{
|
|
fprintf(stderr, "CSO indices are corrupted (outside file)\n");
|
|
close();
|
|
return false;
|
|
}
|
|
|
|
uint32_t idx = m_indices[i];
|
|
uint32_t pos = (idx & ~IDX_COMPRESS_BIT) << header.index_shift;
|
|
uint32_t len = pos - lastpos;
|
|
if (len <= 0)
|
|
{
|
|
fprintf(stderr, "CSO indices are corrupted (out of order)\n");
|
|
close();
|
|
return false;
|
|
}
|
|
else if (len > framesize)
|
|
{
|
|
fprintf(stderr, "CSO indices are corrupted (index too large)\n");
|
|
close();
|
|
return false;
|
|
}
|
|
else if ((lastidx & IDX_COMPRESS_BIT) && len < header.block_len)
|
|
{
|
|
fprintf(stderr, "CSO indices are corrupted (uncompressed index smaller than block size)\n");
|
|
close();
|
|
return false;
|
|
}
|
|
lastidx = idx;
|
|
}
|
|
|
|
m_version = header.version;
|
|
m_size = header.raw_len;
|
|
m_shift = header.index_shift;
|
|
m_blocksize = header.block_len;
|
|
m_framesize = framesize;
|
|
m_frame = new uint8_t[m_framesize];
|
|
m_readbuf = new uint8_t[m_framesize];
|
|
|
|
return true;
|
|
}
|
|
|
|
void CSO_Reader::close()
|
|
{
|
|
delete[] m_readbuf;
|
|
m_readbuf = nullptr;
|
|
|
|
delete[] m_frame;
|
|
m_frame = nullptr;
|
|
|
|
delete[] m_indices;
|
|
m_indices = nullptr;
|
|
|
|
if (m_file.is_open())
|
|
m_file.close();
|
|
|
|
m_version = 0;
|
|
m_virtptr = 0;
|
|
m_size = 0;
|
|
m_shift = 0;
|
|
m_blocksize = 0;
|
|
m_framesize = 0;
|
|
m_curframe = 0xFFFFFFFF;
|
|
}
|