Add support for loading CSO-compressed ISOs.

This commit is contained in:
Unknown W. Brackets 2015-04-14 10:06:36 -07:00
parent 2da4a5e6bc
commit 8edffd32c8
2 changed files with 325 additions and 8 deletions

View File

@ -17,6 +17,7 @@
#include <wx/stdpaths.h>
#include "AppConfig.h"
#include "AsyncFileReader.h"
#include "Pcsx2Types.h"
#include "zlib_indexed.h"
@ -634,15 +635,331 @@ void GzippedFileReader::Close() {
}
// CompressedFileReader factory - currently there's only GzippedFileReader
struct CsoHeader {
u8 magic[4];
u32 header_size;
u64 total_bytes;
u32 frame_size;
u8 ver;
u8 align;
u8 reserved[2];
};
static const u32 CSO_READ_BUFFER_SIZE = 256 * 1024;
class CsoFileReader : public AsyncFileReader
{
DeclareNoncopyableObject(CsoFileReader);
public:
CsoFileReader(void) :
m_readBuffer(0),
m_zlibBuffer(0),
m_index(0),
m_totalSize(0),
m_src(0) {
m_blocksize = 2048;
};
virtual ~CsoFileReader(void) { Close(); };
static bool CanHandle(const wxString& fileName);
virtual bool Open(const wxString& fileName);
virtual int ReadSync(void* pBuffer, uint sector, uint count);
virtual void BeginRead(void* pBuffer, uint sector, uint count);
virtual int FinishRead(void);
virtual void CancelRead(void);
virtual void Close(void);
virtual uint GetBlockCount(void) const {
return (m_totalSize - m_dataoffset) / m_blocksize;
};
virtual void SetBlockSize(uint bytes) { m_blocksize = bytes; }
virtual void SetDataOffset(int bytes) { m_dataoffset = bytes; }
private:
static bool ValidateHeader(const CsoHeader& hdr);
bool ReadFileHeader();
bool InitializeBuffers();
int ReadFromFrame(u8 *dest, u64 pos, u64 maxBytes);
bool DecompressFrame(u32 frame, u32 readBufferSize);
u32 m_frameSize;
u8 m_frameShift;
u8 m_indexShift;
u8* m_readBuffer;
u8 *m_zlibBuffer;
u32 m_zlibBufferFrame;
u32 *m_index;
u64 m_totalSize;
FILE* m_src;
int mBytesRead; // Temp sync read result when simulating async read
};
bool CsoFileReader::CanHandle(const wxString& fileName) {
bool supported = false;
if (wxFileName::FileExists(fileName) && fileName.Lower().EndsWith(L".cso")) {
FILE* fp = PX_fopen_rb(fileName);
CsoHeader hdr;
if (fp && fread(&hdr, 1, sizeof(hdr), fp) == sizeof(hdr)) {
supported = ValidateHeader(hdr);
}
fclose(fp);
}
return supported;
}
bool CsoFileReader::ValidateHeader(const CsoHeader& hdr) {
if (hdr.magic[0] != 'C' || hdr.magic[1] != 'I' || hdr.magic[2] != 'S' || hdr.magic[3] != 'O') {
// Invalid magic, definitely a bad file.
return false;
}
if (hdr.ver > 1) {
Console.Error(L"Only CSOv1 files are supported.");
return false;
}
if ((hdr.frame_size & (hdr.frame_size - 1)) != 0) {
Console.Error(L"CSO frame size must be a power of two.");
return false;
}
if (hdr.frame_size < 2048) {
Console.Error(L"CSO frame size must be at least one sector.");
return false;
}
// All checks passed, this is a good CSO header.
return true;
}
bool CsoFileReader::Open(const wxString& fileName) {
Close();
m_filename = fileName;
m_src = PX_fopen_rb(m_filename);
bool success = false;
if (m_src && ReadFileHeader() && InitializeBuffers()) {
success = true;
}
if (!success) {
Close();
return false;
}
return true;
}
bool CsoFileReader::ReadFileHeader() {
CsoHeader hdr = {};
PX_fseeko(m_src, m_dataoffset, SEEK_SET);
if (fread(&hdr, 1, sizeof(hdr), m_src) != sizeof(hdr)) {
Console.Error(L"Failed to read CSO file header.");
return false;
}
if (!ValidateHeader(hdr)) {
Console.Error(L"CSO has invalid header.");
return false;
}
m_frameSize = hdr.frame_size;
// Determine the translation from bytes to frame.
m_frameShift = 0;
for (u32 i = m_frameSize; i > 1; i >>= 1) {
++m_frameShift;
}
// This is the index alignment (index values need shifting by this amount.)
m_indexShift = hdr.align;
m_totalSize = hdr.total_bytes;
return true;
}
bool CsoFileReader::InitializeBuffers() {
// Round up, since part of a frame requires a full frame.
u32 numFrames = (u32)((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 u8[CSO_READ_BUFFER_SIZE];
} else {
m_readBuffer = new u8[m_frameSize + (1 << m_indexShift)];
}
// This is a buffer for the most recently decompressed frame.
m_zlibBuffer = new u8[m_frameSize + (1 << m_indexShift)];
m_zlibBufferFrame = numFrames;
const u32 indexSize = numFrames + 1;
m_index = new u32[indexSize];
if (fread(m_index, sizeof(u32), indexSize, m_src) != indexSize) {
Console.Error(L"Unable to read index data from CSO.");
return false;
}
return true;
}
void CsoFileReader::Close() {
m_filename.Empty();
if (m_src) {
fclose(m_src);
m_src = 0;
}
if (m_readBuffer) {
delete[] m_readBuffer;
m_readBuffer = 0;
}
if (m_zlibBuffer) {
delete[] m_zlibBuffer;
m_zlibBuffer = 0;
}
if (m_index) {
delete[] m_index;
m_index = 0;
}
}
int CsoFileReader::ReadSync(void* pBuffer, uint sector, uint count) {
u8* dest = (u8*)pBuffer;
// We do it this way in case m_blocksize is not well aligned to our frame size.
u64 pos = (u64)sector * (u64)m_blocksize;
int remaining = count * m_blocksize;
int bytes = 0;
while (remaining > 0) {
int readBytes = ReadFromFrame(dest + bytes, pos + bytes, remaining);
if (readBytes == 0) {
// We hit EOF.
break;
}
bytes += readBytes;
remaining -= readBytes;
}
return bytes;
}
int CsoFileReader::ReadFromFrame(u8 *dest, u64 pos, u64 maxBytes) {
if (pos >= m_totalSize) {
// Can't read anything passed the end.
return 0;
}
const u32 frame = (u32)(pos >> m_frameShift);
const u32 offset = (u32)(pos - (frame << m_frameShift));
// This is how many bytes we will actually be reading from this frame.
const u32 bytes = (u32)(std::min(m_blocksize, static_cast<uint>(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 u32 index0 = m_index[frame + 0] & 0x7FFFFFFF;
const u32 index1 = m_index[frame + 1] & 0x7FFFFFFF;
// Calculate where the compressed payload is (if compressed.)
const u64 frameRawPos = (u64)index0 << m_indexShift;
const u64 frameRawSize = (index1 - index0) << m_indexShift;
if (!compressed) {
// Just read directly, easy.
if (PX_fseeko(m_src, m_dataoffset + frameRawPos + offset, SEEK_SET) != 0) {
Console.Error("Unable to seek to uncompressed CSO data.");
return 0;
}
return fread(dest, 1, bytes, m_src);
} else {
// We don't need to decompress if we already did this same frame last time.
if (m_zlibBufferFrame != frame) {
if (PX_fseeko(m_src, m_dataoffset + frameRawPos, SEEK_SET) != 0) {
Console.Error("Unable to seek to compressed CSO data.");
return 0;
}
// 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 u32 readRawBytes = fread(m_readBuffer, 1, frameRawSize, m_src);
if (!DecompressFrame(frame, readRawBytes)) {
return 0;
}
}
// Now we just copy the offset data from the cache.
memcpy(dest, m_zlibBuffer + offset, bytes);
}
return bytes;
}
bool CsoFileReader::DecompressFrame(u32 frame, u32 readBufferSize) {
z_stream z;
z.zalloc = Z_NULL;
z.zfree = Z_NULL;
z.opaque = Z_NULL;
if (inflateInit2(&z, -15) != Z_OK) {
Console.Error("Unable to initialize zlib for CSO decompression.");
return false;
}
z.next_in = m_readBuffer;
z.avail_in = 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);
Console.Error("Unable to decompress CSO frame using zlib.");
return false;
}
inflateEnd(&z);
// Our buffer now contains this frame.
m_zlibBufferFrame = frame;
return true;
}
void CsoFileReader::BeginRead(void* pBuffer, uint sector, uint count) {
// TODO: No async support yet, implement as sync.
mBytesRead = ReadSync(pBuffer, sector, count);
}
int CsoFileReader::FinishRead() {
int res = mBytesRead;
mBytesRead = -1;
return res;
}
void CsoFileReader::CancelRead() {
// TODO: No async read support yet.
}
// CompressedFileReader factory.
// Go through available compressed readers
bool CompressedFileReader::DetectCompressed(AsyncFileReader* pReader) {
return GzippedFileReader::CanHandle(pReader->GetFilename());
const wxString& filename = pReader->GetFilename();
if (GzippedFileReader::CanHandle(filename)) {
return true;
}
if (CsoFileReader::CanHandle(filename)) {
return true;
}
return false;
}
// Return a new reader which can handle, or any reader otherwise (which will fail on open)
AsyncFileReader* CompressedFileReader::GetNewReader(const wxString& fileName) {
//if (GzippedFileReader::CanHandle(pReader))
if (GzippedFileReader::CanHandle(fileName)) {
return new GzippedFileReader();
}
if (CsoFileReader::CanHandle(fileName)) {
return new CsoFileReader();
}
// This is the one which will fail on open.
return new GzippedFileReader();
}

View File

@ -260,8 +260,8 @@ bool MainEmuFrame::_DoSelectIsoBrowser( wxString& result )
wxArrayString isoFilterTypes;
isoFilterTypes.Add(pxsFmt(_("All Supported (%s)"), WX_STR((isoSupportedLabel + L" .dump" + L" .gz"))));
isoFilterTypes.Add(isoSupportedList + L";*.dump" + L";*.gz");
isoFilterTypes.Add(pxsFmt(_("All Supported (%s)"), WX_STR((isoSupportedLabel + L" .dump" + L" .gz" + L" .cso"))));
isoFilterTypes.Add(isoSupportedList + L";*.dump" + L";*.gz" + L";*.cso");
isoFilterTypes.Add(pxsFmt(_("Disc Images (%s)"), WX_STR(isoSupportedLabel) ));
isoFilterTypes.Add(isoSupportedList);
@ -269,13 +269,13 @@ bool MainEmuFrame::_DoSelectIsoBrowser( wxString& result )
isoFilterTypes.Add(pxsFmt(_("Blockdumps (%s)"), L".dump" ));
isoFilterTypes.Add(L"*.dump");
isoFilterTypes.Add(pxsFmt(_("Compressed (%s)"), L".gz"));
isoFilterTypes.Add(L"*.gz");
isoFilterTypes.Add(pxsFmt(_("Compressed (%s)"), L".gz .cso"));
isoFilterTypes.Add(L"*.gz;*.cso");
isoFilterTypes.Add(_("All Files (*.*)"));
isoFilterTypes.Add(L"*.*");
wxFileDialog ctrl( this, _("Select disc image, gzip compressed disc image, or block-dump..."), g_Conf->Folders.RunIso.ToString(), wxEmptyString,
wxFileDialog ctrl( this, _("Select disc image, compressed disc image, or block-dump..."), g_Conf->Folders.RunIso.ToString(), wxEmptyString,
JoinString(isoFilterTypes, L"|"), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
if( ctrl.ShowModal() != wxID_CANCEL )