mirror of https://github.com/PCSX2/pcsx2.git
Separate compressed file types into separate files.
Cleaner this way.
This commit is contained in:
parent
4ffbd3765b
commit
334f648eaa
|
@ -97,22 +97,6 @@ public:
|
||||||
virtual void SetDataOffset(int bytes) { m_dataoffset = bytes; }
|
virtual void SetDataOffset(int bytes) { m_dataoffset = bytes; }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Factory - creates an AsyncFileReader derived instance which can read a compressed file
|
|
||||||
class CompressedFileReader {
|
|
||||||
public:
|
|
||||||
// Checks if any of the available compressed file handlers can open this
|
|
||||||
static bool DetectCompressed(AsyncFileReader* pReader);
|
|
||||||
|
|
||||||
// fileName is only used to choose the compressed reader.
|
|
||||||
// If no matching handler is found then an arbitrary handlers will be returned.
|
|
||||||
// The returned instance still needs ->Open(filename) before usage.
|
|
||||||
// Open(filename) may still fail.
|
|
||||||
static AsyncFileReader* GetNewReader(const wxString& fileName);
|
|
||||||
|
|
||||||
private:
|
|
||||||
virtual ~CompressedFileReader() = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MultipartFileReader : public AsyncFileReader
|
class MultipartFileReader : public AsyncFileReader
|
||||||
{
|
{
|
||||||
DeclareNoncopyableObject( MultipartFileReader );
|
DeclareNoncopyableObject( MultipartFileReader );
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2014 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
#include "ChunksCache.h"
|
||||||
|
|
||||||
|
void ChunksCache::SetLimit(uint megabytes) {
|
||||||
|
m_limit = (PX_off_t)megabytes * 1024 * 1024;
|
||||||
|
MatchLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChunksCache::MatchLimit(bool removeAll) {
|
||||||
|
std::list<CacheEntry*>::reverse_iterator rit;
|
||||||
|
while (m_entries.size() && (removeAll || m_size > m_limit)) {
|
||||||
|
rit = m_entries.rbegin();
|
||||||
|
m_size -= (*rit)->size;
|
||||||
|
delete(*rit);
|
||||||
|
m_entries.pop_back();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChunksCache::Take(void* pMallocedSrc, PX_off_t offset, int length, int coverage) {
|
||||||
|
m_entries.push_front(new CacheEntry(pMallocedSrc, offset, length, coverage));
|
||||||
|
m_size += length;
|
||||||
|
MatchLimit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// By design, succeed only if the entire request is in a single cached chunk
|
||||||
|
int ChunksCache::Read(void* pDest, PX_off_t offset, int length) {
|
||||||
|
for (auto it = m_entries.begin(); it != m_entries.end(); it++) {
|
||||||
|
CacheEntry* e = *it;
|
||||||
|
if (e && offset >= e->offset && (offset + length) <= (e->offset + e->coverage)) {
|
||||||
|
if (it != m_entries.begin())
|
||||||
|
m_entries.splice(m_entries.begin(), m_entries, it); // Move to top (MRU)
|
||||||
|
return CopyAvailable(e->data, e->offset, e->size, pDest, offset, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2014 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "zlib_indexed.h"
|
||||||
|
|
||||||
|
#define CLAMP(val, minval, maxval) (std::min(maxval, std::max(minval, val)))
|
||||||
|
|
||||||
|
class ChunksCache {
|
||||||
|
public:
|
||||||
|
ChunksCache(uint initialLimitMb) : m_entries(0), m_size(0), m_limit(initialLimitMb * 1024 * 1024) {};
|
||||||
|
~ChunksCache() { Clear(); };
|
||||||
|
void SetLimit(uint megabytes);
|
||||||
|
void Clear() { MatchLimit(true); };
|
||||||
|
|
||||||
|
void Take(void* pMallocedSrc, PX_off_t offset, int length, int coverage);
|
||||||
|
int Read(void* pDest, PX_off_t offset, int length);
|
||||||
|
|
||||||
|
static int CopyAvailable(void* pSrc, PX_off_t srcOffset, int srcSize,
|
||||||
|
void* pDst, PX_off_t dstOffset, int maxCopySize) {
|
||||||
|
int available = CLAMP(maxCopySize, 0, (int)(srcOffset + srcSize - dstOffset));
|
||||||
|
memcpy(pDst, (char*)pSrc + (dstOffset - srcOffset), available);
|
||||||
|
return available;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
class CacheEntry {
|
||||||
|
public:
|
||||||
|
CacheEntry(void* pMallocedSrc, PX_off_t offset, int length, int coverage) :
|
||||||
|
data(pMallocedSrc),
|
||||||
|
offset(offset),
|
||||||
|
coverage(coverage),
|
||||||
|
size(length)
|
||||||
|
{};
|
||||||
|
|
||||||
|
~CacheEntry() { if (data) free(data); };
|
||||||
|
|
||||||
|
void* data;
|
||||||
|
PX_off_t offset;
|
||||||
|
int coverage;
|
||||||
|
int size;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::list<CacheEntry*> m_entries;
|
||||||
|
void MatchLimit(bool removeAll = false);
|
||||||
|
PX_off_t m_size;
|
||||||
|
PX_off_t m_limit;
|
||||||
|
};
|
||||||
|
|
||||||
|
#undef CLAMP
|
|
@ -14,931 +14,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "PrecompiledHeader.h"
|
#include "PrecompiledHeader.h"
|
||||||
#include <wx/stdpaths.h>
|
|
||||||
#include "AppConfig.h"
|
|
||||||
#include "AsyncFileReader.h"
|
#include "AsyncFileReader.h"
|
||||||
#include "Pcsx2Types.h"
|
#include "CompressedFileReader.h"
|
||||||
|
#include "CsoFileReader.h"
|
||||||
#include "zlib_indexed.h"
|
#include "GzippedFileReader.h"
|
||||||
|
|
||||||
/////////// Some complementary utilities for zlib_indexed.c //////////
|
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
// This is ugly, but it's hard to find something which will work/compile for both
|
|
||||||
// windows and *nix and work with non-english file names.
|
|
||||||
// Maybe some day we'll convert all file related ops to wxWidgets, which means also the
|
|
||||||
// instances at zlib_indexed.h (which use plain stdio FILE*)
|
|
||||||
#ifdef WIN32
|
|
||||||
# define PX_wfilename(name_wxstr) (WX_STR(name_wxstr))
|
|
||||||
# define PX_fopen_rb(name_wxstr) (_wfopen(PX_wfilename(name_wxstr), L"rb"))
|
|
||||||
#else
|
|
||||||
# define PX_wfilename(name_wxstr) (name_wxstr.mbc_str())
|
|
||||||
# define PX_fopen_rb(name_wxstr) (fopen(PX_wfilename(name_wxstr), "rb"))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
static s64 fsize(const wxString& filename) {
|
|
||||||
if (!wxFileName::FileExists(filename))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
std::ifstream f(PX_wfilename(filename), std::ifstream::binary);
|
|
||||||
f.seekg(0, f.end);
|
|
||||||
s64 size = f.tellg();
|
|
||||||
f.close();
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define GZIP_ID "PCSX2.index.gzip.v1|"
|
|
||||||
#define GZIP_ID_LEN (sizeof(GZIP_ID) - 1) /* sizeof includes the \0 terminator */
|
|
||||||
|
|
||||||
// File format is:
|
|
||||||
// - [GZIP_ID_LEN] GZIP_ID (no \0)
|
|
||||||
// - [sizeof(Access)] index (should be allocated, contains various sizes)
|
|
||||||
// - [rest] the indexed data points (should be allocated, index->list should then point to it)
|
|
||||||
static Access* ReadIndexFromFile(const wxString& filename) {
|
|
||||||
s64 size = fsize(filename);
|
|
||||||
if (size <= 0) {
|
|
||||||
Console.Error(L"Error: Can't open index file: '%s'", WX_STR(filename));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
std::ifstream infile(PX_wfilename(filename), std::ifstream::binary);
|
|
||||||
|
|
||||||
char fileId[GZIP_ID_LEN + 1] = { 0 };
|
|
||||||
infile.read(fileId, GZIP_ID_LEN);
|
|
||||||
if (wxString::From8BitData(GZIP_ID) != wxString::From8BitData(fileId)) {
|
|
||||||
Console.Error(L"Error: Incompatible gzip index, please delete it manually: '%s'", WX_STR(filename));
|
|
||||||
infile.close();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Access* index = (Access*)malloc(sizeof(Access));
|
|
||||||
infile.read((char*)index, sizeof(Access));
|
|
||||||
|
|
||||||
s64 datasize = size - GZIP_ID_LEN - sizeof(Access);
|
|
||||||
if (datasize != index->have * sizeof(Point)) {
|
|
||||||
Console.Error(L"Error: unexpected size of gzip index, please delete it manually: '%s'.", WX_STR(filename));
|
|
||||||
infile.close();
|
|
||||||
free(index);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* buffer = (char*)malloc(datasize);
|
|
||||||
infile.read(buffer, datasize);
|
|
||||||
infile.close();
|
|
||||||
index->list = (Point*)buffer; // adjust list pointer
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void WriteIndexToFile(Access* index, const wxString filename) {
|
|
||||||
if (wxFileName::FileExists(filename)) {
|
|
||||||
Console.Warning(L"WARNING: Won't write index - file name exists (please delete it manually): '%s'", WX_STR(filename));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ofstream outfile(PX_wfilename(filename), std::ofstream::binary);
|
|
||||||
outfile.write(GZIP_ID, GZIP_ID_LEN);
|
|
||||||
|
|
||||||
Point* tmp = index->list;
|
|
||||||
index->list = 0; // current pointer is useless on disk, normalize it as 0.
|
|
||||||
outfile.write((char*)index, sizeof(Access));
|
|
||||||
index->list = tmp;
|
|
||||||
|
|
||||||
outfile.write((char*)index->list, sizeof(Point) * index->have);
|
|
||||||
outfile.close();
|
|
||||||
|
|
||||||
// Verify
|
|
||||||
if (fsize(filename) != (s64)GZIP_ID_LEN + sizeof(Access) + sizeof(Point) * index->have) {
|
|
||||||
Console.Warning(L"Warning: Can't write index file to disk: '%s'", WX_STR(filename));
|
|
||||||
} else {
|
|
||||||
Console.WriteLn(Color_Green, L"OK: Gzip quick access index file saved to disk: '%s'", WX_STR(filename));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////// End of complementary utilities for zlib_indexed.c //////////
|
|
||||||
#define CLAMP(val, minval, maxval) (std::min(maxval, std::max(minval, val)))
|
|
||||||
|
|
||||||
class ChunksCache {
|
|
||||||
public:
|
|
||||||
ChunksCache(uint initialLimitMb) : m_entries(0), m_size(0), m_limit(initialLimitMb * 1024 * 1024) {};
|
|
||||||
~ChunksCache() { Clear(); };
|
|
||||||
void SetLimit(uint megabytes);
|
|
||||||
void Clear() { MatchLimit(true); };
|
|
||||||
|
|
||||||
void Take(void* pMallocedSrc, PX_off_t offset, int length, int coverage);
|
|
||||||
int Read(void* pDest, PX_off_t offset, int length);
|
|
||||||
|
|
||||||
static int CopyAvailable(void* pSrc, PX_off_t srcOffset, int srcSize,
|
|
||||||
void* pDst, PX_off_t dstOffset, int maxCopySize) {
|
|
||||||
int available = CLAMP(maxCopySize, 0, (int)(srcOffset + srcSize - dstOffset));
|
|
||||||
memcpy(pDst, (char*)pSrc + (dstOffset - srcOffset), available);
|
|
||||||
return available;
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
class CacheEntry {
|
|
||||||
public:
|
|
||||||
CacheEntry(void* pMallocedSrc, PX_off_t offset, int length, int coverage) :
|
|
||||||
data(pMallocedSrc),
|
|
||||||
offset(offset),
|
|
||||||
coverage(coverage),
|
|
||||||
size(length)
|
|
||||||
{};
|
|
||||||
|
|
||||||
~CacheEntry() { if (data) free(data); };
|
|
||||||
|
|
||||||
void* data;
|
|
||||||
PX_off_t offset;
|
|
||||||
int coverage;
|
|
||||||
int size;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::list<CacheEntry*> m_entries;
|
|
||||||
void MatchLimit(bool removeAll = false);
|
|
||||||
PX_off_t m_size;
|
|
||||||
PX_off_t m_limit;
|
|
||||||
};
|
|
||||||
|
|
||||||
void ChunksCache::SetLimit(uint megabytes) {
|
|
||||||
m_limit = (PX_off_t)megabytes * 1024 * 1024;
|
|
||||||
MatchLimit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChunksCache::MatchLimit(bool removeAll) {
|
|
||||||
std::list<CacheEntry*>::reverse_iterator rit;
|
|
||||||
while (m_entries.size() && (removeAll || m_size > m_limit)) {
|
|
||||||
rit = m_entries.rbegin();
|
|
||||||
m_size -= (*rit)->size;
|
|
||||||
delete(*rit);
|
|
||||||
m_entries.pop_back();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ChunksCache::Take(void* pMallocedSrc, PX_off_t offset, int length, int coverage) {
|
|
||||||
m_entries.push_front(new CacheEntry(pMallocedSrc, offset, length, coverage));
|
|
||||||
m_size += length;
|
|
||||||
MatchLimit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// By design, succeed only if the entire request is in a single cached chunk
|
|
||||||
int ChunksCache::Read(void* pDest, PX_off_t offset, int length) {
|
|
||||||
for (auto it = m_entries.begin(); it != m_entries.end(); it++) {
|
|
||||||
CacheEntry* e = *it;
|
|
||||||
if (e && offset >= e->offset && (offset + length) <= (e->offset + e->coverage)) {
|
|
||||||
if (it != m_entries.begin())
|
|
||||||
m_entries.splice(m_entries.begin(), m_entries, it); // Move to top (MRU)
|
|
||||||
return CopyAvailable(e->data, e->offset, e->size, pDest, offset, length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static wxString INDEX_TEMPLATE_KEY(L"$(f)");
|
|
||||||
// template:
|
|
||||||
// must contain one and only one instance of '$(f)' (without the quotes)
|
|
||||||
// if if !canEndWithKey -> must not end with $(f)
|
|
||||||
// if starts with $(f) then it expands to the full path + file name.
|
|
||||||
// if doesn't start with $(f) then it's expanded to file name only (with extension)
|
|
||||||
// if doesn't start with $(f) and ends up relative,
|
|
||||||
// then it's relative to base (not to cwd)
|
|
||||||
// No checks are performed if the result file name can be created.
|
|
||||||
// If this proves useful, we can move it into Path:: . Right now there's no need.
|
|
||||||
static wxString ApplyTemplate(const wxString &name, const wxDirName &base,
|
|
||||||
const wxString &fileTemplate, const wxString &filename,
|
|
||||||
bool canEndWithKey)
|
|
||||||
{
|
|
||||||
wxString tem(fileTemplate);
|
|
||||||
wxString key = INDEX_TEMPLATE_KEY;
|
|
||||||
tem = tem.Trim(true).Trim(false); // both sides
|
|
||||||
|
|
||||||
int first = tem.find(key);
|
|
||||||
if (first < 0 // not found
|
|
||||||
|| first != tem.rfind(key) // more than one instance
|
|
||||||
|| !canEndWithKey && first == tem.length() - key.length())
|
|
||||||
{
|
|
||||||
Console.Error(L"Invalid %s template '%s'.\n"
|
|
||||||
L"Template must contain exactly one '%s' and must not end with it. Abotring.",
|
|
||||||
WX_STR(name), WX_STR(tem), WX_STR(key));
|
|
||||||
return L"";
|
|
||||||
}
|
|
||||||
|
|
||||||
wxString fname(filename);
|
|
||||||
if (first > 0)
|
|
||||||
fname = Path::GetFilename(fname); // without path
|
|
||||||
|
|
||||||
tem.Replace(key, fname);
|
|
||||||
if (first > 0)
|
|
||||||
tem = Path::Combine(base, tem); // ignores appRoot if tem is absolute
|
|
||||||
|
|
||||||
return tem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
static void TestTemplate(const wxDirName &base, const wxString &fname, bool canEndWithKey)
|
|
||||||
{
|
|
||||||
const char *ins[] = {
|
|
||||||
"$(f).pindex.tmp", // same folder as the original file
|
|
||||||
" $(f).pindex.tmp ", // same folder as the original file (trimmed silently)
|
|
||||||
"cache/$(f).pindex", // relative to base
|
|
||||||
"../$(f).pindex", // relative to base
|
|
||||||
"%appdata%/pcsx2/cache/$(f).pindex", // c:/Users/<user>/AppData/Roaming/pcsx2/cache/ ...
|
|
||||||
"c:\\pcsx2-cache/$(f).pindex", // absolute
|
|
||||||
"~/.cache/$(f).pindex", // TODO: check if this works on *nix. It should...
|
|
||||||
// (on windows ~ isn't recognized as special)
|
|
||||||
"cache/$(f)/$(f).index", // invalid: appears twice
|
|
||||||
"hello", // invalid: doesn't contain $(f)
|
|
||||||
"hello$(f)", // invalid, can't end with $(f)
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
for (int i = 0; ins[i]; i++) {
|
|
||||||
wxString tem(wxString::From8BitData(ins[i]));
|
|
||||||
Console.WriteLn(Color_Green, L"test: '%s' -> '%s'",
|
|
||||||
WX_STR(tem),
|
|
||||||
WX_STR(ApplyTemplate(L"test", base, tem, fname, canEndWithKey)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
static wxString iso2indexname(const wxString& isoname) {
|
|
||||||
//testTemplate(isoname);
|
|
||||||
wxDirName appRoot = // TODO: have only one of this in PCSX2. Right now have few...
|
|
||||||
(wxDirName)(wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath());
|
|
||||||
//TestTemplate(appRoot, isoname, false);
|
|
||||||
return ApplyTemplate(L"gzip index", appRoot, g_Conf->GzipIsoIndexTemplate, isoname, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define SPAN_DEFAULT (1048576L * 4) /* distance between direct access points when creating a new index */
|
|
||||||
#define READ_CHUNK_SIZE (256 * 1024) /* zlib extraction chunks size (at 0-based boundaries) */
|
|
||||||
#define CACHE_SIZE_MB 200 /* cache size for extracted data. must be at least READ_CHUNK_SIZE (in MB)*/
|
|
||||||
|
|
||||||
class GzippedFileReader : public AsyncFileReader
|
|
||||||
{
|
|
||||||
DeclareNoncopyableObject(GzippedFileReader);
|
|
||||||
public:
|
|
||||||
GzippedFileReader(void) :
|
|
||||||
m_pIndex(0),
|
|
||||||
m_zstates(0),
|
|
||||||
m_src(0),
|
|
||||||
m_cache(CACHE_SIZE_MB) {
|
|
||||||
m_blocksize = 2048;
|
|
||||||
AsyncPrefetchReset();
|
|
||||||
};
|
|
||||||
|
|
||||||
virtual ~GzippedFileReader(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 {
|
|
||||||
// type and formula copied from FlatFileReader
|
|
||||||
// FIXME? : Shouldn't it be uint and (size - m_dataoffset) / m_blocksize ?
|
|
||||||
return (int)((m_pIndex ? m_pIndex->uncompressed_size : 0) / m_blocksize);
|
|
||||||
};
|
|
||||||
|
|
||||||
virtual void SetBlockSize(uint bytes) { m_blocksize = bytes; }
|
|
||||||
virtual void SetDataOffset(int bytes) { m_dataoffset = bytes; }
|
|
||||||
private:
|
|
||||||
class Czstate {
|
|
||||||
public:
|
|
||||||
Czstate() { state.isValid = 0; };
|
|
||||||
~Czstate() { Kill(); };
|
|
||||||
void Kill() {
|
|
||||||
if (state.isValid)
|
|
||||||
inflateEnd(&state.strm);
|
|
||||||
state.isValid = 0;
|
|
||||||
}
|
|
||||||
Zstate state;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool OkIndex(); // Verifies that we have an index, or try to create one
|
|
||||||
PX_off_t GetOptimalExtractionStart(PX_off_t offset);
|
|
||||||
int _ReadSync(void* pBuffer, PX_off_t offset, uint bytesToRead);
|
|
||||||
void InitZstates();
|
|
||||||
|
|
||||||
int mBytesRead; // Temp sync read result when simulating async read
|
|
||||||
Access* m_pIndex; // Quick access index
|
|
||||||
Czstate* m_zstates;
|
|
||||||
FILE* m_src;
|
|
||||||
|
|
||||||
ChunksCache m_cache;
|
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
// Used by async prefetch
|
|
||||||
HANDLE hOverlappedFile;
|
|
||||||
OVERLAPPED asyncOperationContext;
|
|
||||||
bool asyncInProgress;
|
|
||||||
byte mDummyAsyncPrefetchTarget[READ_CHUNK_SIZE];
|
|
||||||
#endif
|
|
||||||
|
|
||||||
void AsyncPrefetchReset();
|
|
||||||
void AsyncPrefetchOpen();
|
|
||||||
void AsyncPrefetchClose();
|
|
||||||
void AsyncPrefetchChunk(PX_off_t dummy);
|
|
||||||
void AsyncPrefetchCancel();
|
|
||||||
};
|
|
||||||
|
|
||||||
void GzippedFileReader::InitZstates() {
|
|
||||||
if (m_zstates) {
|
|
||||||
delete[] m_zstates;
|
|
||||||
m_zstates = 0;
|
|
||||||
}
|
|
||||||
if (!m_pIndex)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// having another extra element helps avoiding logic for last (so 2+ instead of 1+)
|
|
||||||
int size = 2 + m_pIndex->uncompressed_size / m_pIndex->span;
|
|
||||||
m_zstates = new Czstate[size]();
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef WIN32
|
|
||||||
void GzippedFileReader::AsyncPrefetchReset() {};
|
|
||||||
void GzippedFileReader::AsyncPrefetchOpen() {};
|
|
||||||
void GzippedFileReader::AsyncPrefetchClose() {};
|
|
||||||
void GzippedFileReader::AsyncPrefetchChunk(PX_off_t dummy) {};
|
|
||||||
void GzippedFileReader::AsyncPrefetchCancel() {};
|
|
||||||
#else
|
|
||||||
// AsyncPrefetch works as follows:
|
|
||||||
// ater extracting a chunk from the compressed file, ask the OS to asynchronously
|
|
||||||
// read the next chunk from the file, and then completely ignore the result and
|
|
||||||
// cancel the async read before the next extract. the next extract then reads the
|
|
||||||
// data from the disk buf if it's overlapping/contained within the chunk we've
|
|
||||||
// asked the OS to prefetch, then the OS is likely to already have it cached.
|
|
||||||
// This procedure is frequently able to overcome seek time due to fragmentation of the
|
|
||||||
// compressed file on disk without any meaningful penalty.
|
|
||||||
// This system is only enabled for win32 where we have this async read request.
|
|
||||||
void GzippedFileReader::AsyncPrefetchReset() {
|
|
||||||
hOverlappedFile = INVALID_HANDLE_VALUE;
|
|
||||||
asyncInProgress = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GzippedFileReader::AsyncPrefetchOpen() {
|
|
||||||
hOverlappedFile = CreateFile(
|
|
||||||
m_filename,
|
|
||||||
GENERIC_READ,
|
|
||||||
FILE_SHARE_READ,
|
|
||||||
NULL,
|
|
||||||
OPEN_EXISTING,
|
|
||||||
FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED,
|
|
||||||
NULL);
|
|
||||||
};
|
|
||||||
|
|
||||||
void GzippedFileReader::AsyncPrefetchClose()
|
|
||||||
{
|
|
||||||
AsyncPrefetchCancel();
|
|
||||||
|
|
||||||
if (hOverlappedFile != INVALID_HANDLE_VALUE)
|
|
||||||
CloseHandle(hOverlappedFile);
|
|
||||||
|
|
||||||
AsyncPrefetchReset();
|
|
||||||
};
|
|
||||||
|
|
||||||
void GzippedFileReader::AsyncPrefetchChunk(PX_off_t start)
|
|
||||||
{
|
|
||||||
if (hOverlappedFile == INVALID_HANDLE_VALUE || asyncInProgress) {
|
|
||||||
Console.Warning(L"Unexpected file handle or progress state. Aborting prefetch.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
LARGE_INTEGER offset;
|
|
||||||
offset.QuadPart = start;
|
|
||||||
|
|
||||||
DWORD bytesToRead = READ_CHUNK_SIZE;
|
|
||||||
|
|
||||||
ZeroMemory(&asyncOperationContext, sizeof(asyncOperationContext));
|
|
||||||
asyncOperationContext.hEvent = 0;
|
|
||||||
asyncOperationContext.Offset = offset.LowPart;
|
|
||||||
asyncOperationContext.OffsetHigh = offset.HighPart;
|
|
||||||
|
|
||||||
ReadFile(hOverlappedFile, mDummyAsyncPrefetchTarget, bytesToRead, NULL, &asyncOperationContext);
|
|
||||||
asyncInProgress = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
void GzippedFileReader::AsyncPrefetchCancel()
|
|
||||||
{
|
|
||||||
if (!asyncInProgress)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!CancelIo(hOverlappedFile)) {
|
|
||||||
Console.Warning("canceling gz prefetch failed. following prefetching will not work.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
asyncInProgress = false;
|
|
||||||
};
|
|
||||||
#endif /* WIN32 */
|
|
||||||
|
|
||||||
// TODO: do better than just checking existance and extension
|
|
||||||
bool GzippedFileReader::CanHandle(const wxString& fileName) {
|
|
||||||
return wxFileName::FileExists(fileName) && fileName.Lower().EndsWith(L".gz");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GzippedFileReader::OkIndex() {
|
|
||||||
if (m_pIndex)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Try to read index from disk
|
|
||||||
wxString indexfile = iso2indexname(m_filename);
|
|
||||||
if (indexfile.length() == 0)
|
|
||||||
return false; // iso2indexname(...) will print errors if it can't apply the template
|
|
||||||
|
|
||||||
if (wxFileName::FileExists(indexfile) && (m_pIndex = ReadIndexFromFile(indexfile))) {
|
|
||||||
Console.WriteLn(Color_Green, L"OK: Gzip quick access index read from disk: '%s'", WX_STR(indexfile));
|
|
||||||
if (m_pIndex->span != SPAN_DEFAULT) {
|
|
||||||
Console.Warning(L"Note: This index has %1.1f MB intervals, while the current default for new indexes is %1.1f MB.",
|
|
||||||
(float)m_pIndex->span / 1024 / 1024, (float)SPAN_DEFAULT / 1024 / 1024);
|
|
||||||
Console.Warning(L"It will work fine, but if you want to generate a new index with default intervals, delete this index file.");
|
|
||||||
Console.Warning(L"(smaller intervals mean bigger index file and quicker but more frequent decompressions)");
|
|
||||||
}
|
|
||||||
InitZstates();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No valid index file. Generate an index
|
|
||||||
Console.Warning(L"This may take a while (but only once). Scanning compressed file to generate a quick access index...");
|
|
||||||
|
|
||||||
Access *index;
|
|
||||||
FILE* infile = PX_fopen_rb(m_filename);
|
|
||||||
int len = build_index(infile, SPAN_DEFAULT, &index);
|
|
||||||
printf("\n"); // build_index prints progress without \n's
|
|
||||||
fclose(infile);
|
|
||||||
|
|
||||||
if (len >= 0) {
|
|
||||||
m_pIndex = index;
|
|
||||||
WriteIndexToFile((Access*)m_pIndex, indexfile);
|
|
||||||
} else {
|
|
||||||
Console.Error(L"ERROR (%d): index could not be generated for file '%s'", len, WX_STR(m_filename));
|
|
||||||
InitZstates();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
InitZstates();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GzippedFileReader::Open(const wxString& fileName) {
|
|
||||||
Close();
|
|
||||||
m_filename = fileName;
|
|
||||||
if (!(m_src = PX_fopen_rb(m_filename)) || !CanHandle(fileName) || !OkIndex()) {
|
|
||||||
Close();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
AsyncPrefetchOpen();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
void GzippedFileReader::BeginRead(void* pBuffer, uint sector, uint count) {
|
|
||||||
// No a-sync support yet, implement as sync
|
|
||||||
mBytesRead = ReadSync(pBuffer, sector, count);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
int GzippedFileReader::FinishRead(void) {
|
|
||||||
int res = mBytesRead;
|
|
||||||
mBytesRead = -1;
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
#define PTT clock_t
|
|
||||||
#define NOW() (clock() / (CLOCKS_PER_SEC / 1000))
|
|
||||||
|
|
||||||
int GzippedFileReader::ReadSync(void* pBuffer, uint sector, uint count) {
|
|
||||||
PX_off_t offset = (s64)sector * m_blocksize + m_dataoffset;
|
|
||||||
int bytesToRead = count * m_blocksize;
|
|
||||||
int res = _ReadSync(pBuffer, offset, bytesToRead);
|
|
||||||
if (res < 0)
|
|
||||||
Console.Error(L"Error: iso-gzip read unsuccessful.");
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a valid and adequate zstate for this span, use it, else, use the index
|
|
||||||
PX_off_t GzippedFileReader::GetOptimalExtractionStart(PX_off_t offset) {
|
|
||||||
int span = m_pIndex->span;
|
|
||||||
Czstate& cstate = m_zstates[offset / span];
|
|
||||||
PX_off_t stateOffset = cstate.state.isValid ? cstate.state.out_offset : 0;
|
|
||||||
if (stateOffset && stateOffset <= offset)
|
|
||||||
return stateOffset; // state is faster than indexed
|
|
||||||
|
|
||||||
// If span is not exact multiples of READ_CHUNK_SIZE (because it was configured badly),
|
|
||||||
// we fallback to always READ_CHUNK_SIZE boundaries
|
|
||||||
if (span % READ_CHUNK_SIZE)
|
|
||||||
return offset / READ_CHUNK_SIZE * READ_CHUNK_SIZE;
|
|
||||||
|
|
||||||
return span * (offset / span); // index direct access boundaries
|
|
||||||
}
|
|
||||||
|
|
||||||
int GzippedFileReader::_ReadSync(void* pBuffer, PX_off_t offset, uint bytesToRead) {
|
|
||||||
if (!OkIndex())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
// Without all the caching, chunking and states, this would be enough:
|
|
||||||
// return extract(m_src, m_pIndex, offset, (unsigned char*)pBuffer, bytesToRead);
|
|
||||||
|
|
||||||
// Split request to READ_CHUNK_SIZE chunks at READ_CHUNK_SIZE boundaries
|
|
||||||
uint maxInChunk = READ_CHUNK_SIZE - offset % READ_CHUNK_SIZE;
|
|
||||||
if (bytesToRead > maxInChunk) {
|
|
||||||
int first = _ReadSync(pBuffer, offset, maxInChunk);
|
|
||||||
if (first != maxInChunk)
|
|
||||||
return first; // EOF or failure
|
|
||||||
|
|
||||||
int rest = _ReadSync((char*)pBuffer + maxInChunk, offset + maxInChunk, bytesToRead - maxInChunk);
|
|
||||||
if (rest < 0)
|
|
||||||
return rest;
|
|
||||||
|
|
||||||
return first + rest;
|
|
||||||
}
|
|
||||||
|
|
||||||
// From here onwards it's guarenteed that the request is inside a single READ_CHUNK_SIZE boundaries
|
|
||||||
|
|
||||||
int res = m_cache.Read(pBuffer, offset, bytesToRead);
|
|
||||||
if (res >= 0)
|
|
||||||
return res;
|
|
||||||
|
|
||||||
// Not available from cache. Decompress from optimal starting
|
|
||||||
// point in READ_CHUNK_SIZE chunks and cache each chunk.
|
|
||||||
PTT s = NOW();
|
|
||||||
PX_off_t extractOffset = GetOptimalExtractionStart(offset); // guaranteed in READ_CHUNK_SIZE boundaries
|
|
||||||
int size = offset + maxInChunk - extractOffset;
|
|
||||||
unsigned char* extracted = (unsigned char*)malloc(size);
|
|
||||||
|
|
||||||
int span = m_pIndex->span;
|
|
||||||
int spanix = extractOffset / span;
|
|
||||||
AsyncPrefetchCancel();
|
|
||||||
res = extract(m_src, m_pIndex, extractOffset, extracted, size, &(m_zstates[spanix].state));
|
|
||||||
if (res < 0) {
|
|
||||||
free(extracted);
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
AsyncPrefetchChunk(getInOffset(&(m_zstates[spanix].state)));
|
|
||||||
|
|
||||||
int copied = ChunksCache::CopyAvailable(extracted, extractOffset, res, pBuffer, offset, bytesToRead);
|
|
||||||
|
|
||||||
if (m_zstates[spanix].state.isValid && (extractOffset + res) / span != offset / span) {
|
|
||||||
// The state no longer matches this span.
|
|
||||||
// move the state to the appropriate span because it will be faster than using the index
|
|
||||||
int targetix = (extractOffset + res) / span;
|
|
||||||
m_zstates[targetix].Kill();
|
|
||||||
m_zstates[targetix] = m_zstates[spanix]; // We have elements for the entire file, and another one.
|
|
||||||
m_zstates[spanix].state.isValid = 0; // Not killing because we need the state.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size <= READ_CHUNK_SIZE)
|
|
||||||
m_cache.Take(extracted, extractOffset, res, size);
|
|
||||||
else { // split into cacheable chunks
|
|
||||||
for (int i = 0; i < size; i += READ_CHUNK_SIZE) {
|
|
||||||
int available = CLAMP(res - i, 0, READ_CHUNK_SIZE);
|
|
||||||
void* chunk = available ? malloc(available) : 0;
|
|
||||||
if (available)
|
|
||||||
memcpy(chunk, extracted + i, available);
|
|
||||||
m_cache.Take(chunk, extractOffset + i, available, std::min(size - i, READ_CHUNK_SIZE));
|
|
||||||
}
|
|
||||||
free(extracted);
|
|
||||||
}
|
|
||||||
|
|
||||||
int duration = NOW() - s;
|
|
||||||
if (duration > 10)
|
|
||||||
Console.WriteLn(Color_Gray, L"gunzip: chunk #%5d-%2d : %1.2f MB - %d ms",
|
|
||||||
(int)(offset / 4 / 1024 / 1024),
|
|
||||||
(int)(offset % (4 * 1024 * 1024) / READ_CHUNK_SIZE),
|
|
||||||
(float)size / 1024 / 1024,
|
|
||||||
duration);
|
|
||||||
|
|
||||||
return copied;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GzippedFileReader::Close() {
|
|
||||||
m_filename.Empty();
|
|
||||||
if (m_pIndex) {
|
|
||||||
free_index((Access*)m_pIndex);
|
|
||||||
m_pIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
InitZstates(); // results in delete because no index
|
|
||||||
m_cache.Clear();
|
|
||||||
|
|
||||||
if (m_src) {
|
|
||||||
fclose(m_src);
|
|
||||||
m_src = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncPrefetchClose();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Implementation of CSO compressed ISO reading, based on:
|
|
||||||
// https://github.com/unknownbrackets/maxcso/blob/master/README_CSO.md
|
|
||||||
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.
|
// CompressedFileReader factory.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2014 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Factory - creates an AsyncFileReader derived instance which can read a compressed file
|
||||||
|
class CompressedFileReader {
|
||||||
|
public:
|
||||||
|
// Checks if any of the available compressed file handlers can open this
|
||||||
|
static bool DetectCompressed(AsyncFileReader* pReader);
|
||||||
|
|
||||||
|
// fileName is only used to choose the compressed reader.
|
||||||
|
// If no matching handler is found then an arbitrary handlers will be returned.
|
||||||
|
// The returned instance still needs ->Open(filename) before usage.
|
||||||
|
// Open(filename) may still fail.
|
||||||
|
static AsyncFileReader* GetNewReader(const wxString& fileName);
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual ~CompressedFileReader() = 0;
|
||||||
|
};
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2014 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/////////// Some complementary utilities for zlib_indexed.c //////////
|
||||||
|
|
||||||
|
// This is ugly, but it's hard to find something which will work/compile for both
|
||||||
|
// windows and *nix and work with non-english file names.
|
||||||
|
// Maybe some day we'll convert all file related ops to wxWidgets, which means also the
|
||||||
|
// instances at zlib_indexed.h (which use plain stdio FILE*)
|
||||||
|
#ifdef WIN32
|
||||||
|
# define PX_wfilename(name_wxstr) (WX_STR(name_wxstr))
|
||||||
|
# define PX_fopen_rb(name_wxstr) (_wfopen(PX_wfilename(name_wxstr), L"rb"))
|
||||||
|
#else
|
||||||
|
# define PX_wfilename(name_wxstr) (name_wxstr.mbc_str())
|
||||||
|
# define PX_fopen_rb(name_wxstr) (fopen(PX_wfilename(name_wxstr), "rb"))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/////////// End of complementary utilities for zlib_indexed.c //////////
|
|
@ -0,0 +1,277 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2014 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
#include "AsyncFileReader.h"
|
||||||
|
#include "CompressedFileReaderUtils.h"
|
||||||
|
#include "CsoFileReader.h"
|
||||||
|
#include "Pcsx2Types.h"
|
||||||
|
#include "zlib_indexed.h"
|
||||||
|
|
||||||
|
// Implementation of CSO compressed ISO reading, based on:
|
||||||
|
// https://github.com/unknownbrackets/maxcso/blob/master/README_CSO.md
|
||||||
|
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;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (!m_src) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2014 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "AsyncFileReader.h"
|
||||||
|
|
||||||
|
struct CsoHeader;
|
||||||
|
|
||||||
|
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;
|
||||||
|
// The actual source cso file handle.
|
||||||
|
FILE* m_src;
|
||||||
|
// The result of a read is stored here between BeginRead() and FinishRead().
|
||||||
|
int mBytesRead;
|
||||||
|
};
|
|
@ -0,0 +1,474 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2014 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "PrecompiledHeader.h"
|
||||||
|
#include <fstream>
|
||||||
|
#include <wx/stdpaths.h>
|
||||||
|
#include "AppConfig.h"
|
||||||
|
#include "ChunksCache.h"
|
||||||
|
#include "CompressedFileReaderUtils.h"
|
||||||
|
#include "GzippedFileReader.h"
|
||||||
|
#include "zlib_indexed.h"
|
||||||
|
|
||||||
|
#define CLAMP(val, minval, maxval) (std::min(maxval, std::max(minval, val)))
|
||||||
|
|
||||||
|
static s64 fsize(const wxString& filename) {
|
||||||
|
if (!wxFileName::FileExists(filename))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
std::ifstream f(PX_wfilename(filename), std::ifstream::binary);
|
||||||
|
f.seekg(0, f.end);
|
||||||
|
s64 size = f.tellg();
|
||||||
|
f.close();
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define GZIP_ID "PCSX2.index.gzip.v1|"
|
||||||
|
#define GZIP_ID_LEN (sizeof(GZIP_ID) - 1) /* sizeof includes the \0 terminator */
|
||||||
|
|
||||||
|
// File format is:
|
||||||
|
// - [GZIP_ID_LEN] GZIP_ID (no \0)
|
||||||
|
// - [sizeof(Access)] index (should be allocated, contains various sizes)
|
||||||
|
// - [rest] the indexed data points (should be allocated, index->list should then point to it)
|
||||||
|
static Access* ReadIndexFromFile(const wxString& filename) {
|
||||||
|
s64 size = fsize(filename);
|
||||||
|
if (size <= 0) {
|
||||||
|
Console.Error(L"Error: Can't open index file: '%s'", WX_STR(filename));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
std::ifstream infile(PX_wfilename(filename), std::ifstream::binary);
|
||||||
|
|
||||||
|
char fileId[GZIP_ID_LEN + 1] = { 0 };
|
||||||
|
infile.read(fileId, GZIP_ID_LEN);
|
||||||
|
if (wxString::From8BitData(GZIP_ID) != wxString::From8BitData(fileId)) {
|
||||||
|
Console.Error(L"Error: Incompatible gzip index, please delete it manually: '%s'", WX_STR(filename));
|
||||||
|
infile.close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Access* index = (Access*)malloc(sizeof(Access));
|
||||||
|
infile.read((char*)index, sizeof(Access));
|
||||||
|
|
||||||
|
s64 datasize = size - GZIP_ID_LEN - sizeof(Access);
|
||||||
|
if (datasize != index->have * sizeof(Point)) {
|
||||||
|
Console.Error(L"Error: unexpected size of gzip index, please delete it manually: '%s'.", WX_STR(filename));
|
||||||
|
infile.close();
|
||||||
|
free(index);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* buffer = (char*)malloc(datasize);
|
||||||
|
infile.read(buffer, datasize);
|
||||||
|
infile.close();
|
||||||
|
index->list = (Point*)buffer; // adjust list pointer
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void WriteIndexToFile(Access* index, const wxString filename) {
|
||||||
|
if (wxFileName::FileExists(filename)) {
|
||||||
|
Console.Warning(L"WARNING: Won't write index - file name exists (please delete it manually): '%s'", WX_STR(filename));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream outfile(PX_wfilename(filename), std::ofstream::binary);
|
||||||
|
outfile.write(GZIP_ID, GZIP_ID_LEN);
|
||||||
|
|
||||||
|
Point* tmp = index->list;
|
||||||
|
index->list = 0; // current pointer is useless on disk, normalize it as 0.
|
||||||
|
outfile.write((char*)index, sizeof(Access));
|
||||||
|
index->list = tmp;
|
||||||
|
|
||||||
|
outfile.write((char*)index->list, sizeof(Point) * index->have);
|
||||||
|
outfile.close();
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if (fsize(filename) != (s64)GZIP_ID_LEN + sizeof(Access) + sizeof(Point) * index->have) {
|
||||||
|
Console.Warning(L"Warning: Can't write index file to disk: '%s'", WX_STR(filename));
|
||||||
|
} else {
|
||||||
|
Console.WriteLn(Color_Green, L"OK: Gzip quick access index file saved to disk: '%s'", WX_STR(filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static wxString INDEX_TEMPLATE_KEY(L"$(f)");
|
||||||
|
// template:
|
||||||
|
// must contain one and only one instance of '$(f)' (without the quotes)
|
||||||
|
// if if !canEndWithKey -> must not end with $(f)
|
||||||
|
// if starts with $(f) then it expands to the full path + file name.
|
||||||
|
// if doesn't start with $(f) then it's expanded to file name only (with extension)
|
||||||
|
// if doesn't start with $(f) and ends up relative,
|
||||||
|
// then it's relative to base (not to cwd)
|
||||||
|
// No checks are performed if the result file name can be created.
|
||||||
|
// If this proves useful, we can move it into Path:: . Right now there's no need.
|
||||||
|
static wxString ApplyTemplate(const wxString &name, const wxDirName &base,
|
||||||
|
const wxString &fileTemplate, const wxString &filename,
|
||||||
|
bool canEndWithKey)
|
||||||
|
{
|
||||||
|
wxString tem(fileTemplate);
|
||||||
|
wxString key = INDEX_TEMPLATE_KEY;
|
||||||
|
tem = tem.Trim(true).Trim(false); // both sides
|
||||||
|
|
||||||
|
int first = tem.find(key);
|
||||||
|
if (first < 0 // not found
|
||||||
|
|| first != tem.rfind(key) // more than one instance
|
||||||
|
|| !canEndWithKey && first == tem.length() - key.length())
|
||||||
|
{
|
||||||
|
Console.Error(L"Invalid %s template '%s'.\n"
|
||||||
|
L"Template must contain exactly one '%s' and must not end with it. Abotring.",
|
||||||
|
WX_STR(name), WX_STR(tem), WX_STR(key));
|
||||||
|
return L"";
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString fname(filename);
|
||||||
|
if (first > 0)
|
||||||
|
fname = Path::GetFilename(fname); // without path
|
||||||
|
|
||||||
|
tem.Replace(key, fname);
|
||||||
|
if (first > 0)
|
||||||
|
tem = Path::Combine(base, tem); // ignores appRoot if tem is absolute
|
||||||
|
|
||||||
|
return tem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
static void TestTemplate(const wxDirName &base, const wxString &fname, bool canEndWithKey)
|
||||||
|
{
|
||||||
|
const char *ins[] = {
|
||||||
|
"$(f).pindex.tmp", // same folder as the original file
|
||||||
|
" $(f).pindex.tmp ", // same folder as the original file (trimmed silently)
|
||||||
|
"cache/$(f).pindex", // relative to base
|
||||||
|
"../$(f).pindex", // relative to base
|
||||||
|
"%appdata%/pcsx2/cache/$(f).pindex", // c:/Users/<user>/AppData/Roaming/pcsx2/cache/ ...
|
||||||
|
"c:\\pcsx2-cache/$(f).pindex", // absolute
|
||||||
|
"~/.cache/$(f).pindex", // TODO: check if this works on *nix. It should...
|
||||||
|
// (on windows ~ isn't recognized as special)
|
||||||
|
"cache/$(f)/$(f).index", // invalid: appears twice
|
||||||
|
"hello", // invalid: doesn't contain $(f)
|
||||||
|
"hello$(f)", // invalid, can't end with $(f)
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; ins[i]; i++) {
|
||||||
|
wxString tem(wxString::From8BitData(ins[i]));
|
||||||
|
Console.WriteLn(Color_Green, L"test: '%s' -> '%s'",
|
||||||
|
WX_STR(tem),
|
||||||
|
WX_STR(ApplyTemplate(L"test", base, tem, fname, canEndWithKey)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
static wxString iso2indexname(const wxString& isoname) {
|
||||||
|
//testTemplate(isoname);
|
||||||
|
wxDirName appRoot = // TODO: have only one of this in PCSX2. Right now have few...
|
||||||
|
(wxDirName)(wxFileName(wxStandardPaths::Get().GetExecutablePath()).GetPath());
|
||||||
|
//TestTemplate(appRoot, isoname, false);
|
||||||
|
return ApplyTemplate(L"gzip index", appRoot, g_Conf->GzipIsoIndexTemplate, isoname, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
GzippedFileReader::GzippedFileReader(void) :
|
||||||
|
m_pIndex(0),
|
||||||
|
m_zstates(0),
|
||||||
|
m_src(0),
|
||||||
|
m_cache(CACHE_SIZE_MB) {
|
||||||
|
m_blocksize = 2048;
|
||||||
|
AsyncPrefetchReset();
|
||||||
|
};
|
||||||
|
|
||||||
|
void GzippedFileReader::InitZstates() {
|
||||||
|
if (m_zstates) {
|
||||||
|
delete[] m_zstates;
|
||||||
|
m_zstates = 0;
|
||||||
|
}
|
||||||
|
if (!m_pIndex)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// having another extra element helps avoiding logic for last (so 2+ instead of 1+)
|
||||||
|
int size = 2 + m_pIndex->uncompressed_size / m_pIndex->span;
|
||||||
|
m_zstates = new Czstate[size]();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WIN32
|
||||||
|
void GzippedFileReader::AsyncPrefetchReset() {};
|
||||||
|
void GzippedFileReader::AsyncPrefetchOpen() {};
|
||||||
|
void GzippedFileReader::AsyncPrefetchClose() {};
|
||||||
|
void GzippedFileReader::AsyncPrefetchChunk(PX_off_t dummy) {};
|
||||||
|
void GzippedFileReader::AsyncPrefetchCancel() {};
|
||||||
|
#else
|
||||||
|
// AsyncPrefetch works as follows:
|
||||||
|
// ater extracting a chunk from the compressed file, ask the OS to asynchronously
|
||||||
|
// read the next chunk from the file, and then completely ignore the result and
|
||||||
|
// cancel the async read before the next extract. the next extract then reads the
|
||||||
|
// data from the disk buf if it's overlapping/contained within the chunk we've
|
||||||
|
// asked the OS to prefetch, then the OS is likely to already have it cached.
|
||||||
|
// This procedure is frequently able to overcome seek time due to fragmentation of the
|
||||||
|
// compressed file on disk without any meaningful penalty.
|
||||||
|
// This system is only enabled for win32 where we have this async read request.
|
||||||
|
void GzippedFileReader::AsyncPrefetchReset() {
|
||||||
|
hOverlappedFile = INVALID_HANDLE_VALUE;
|
||||||
|
asyncInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GzippedFileReader::AsyncPrefetchOpen() {
|
||||||
|
hOverlappedFile = CreateFile(
|
||||||
|
m_filename,
|
||||||
|
GENERIC_READ,
|
||||||
|
FILE_SHARE_READ,
|
||||||
|
NULL,
|
||||||
|
OPEN_EXISTING,
|
||||||
|
FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_OVERLAPPED,
|
||||||
|
NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
void GzippedFileReader::AsyncPrefetchClose()
|
||||||
|
{
|
||||||
|
AsyncPrefetchCancel();
|
||||||
|
|
||||||
|
if (hOverlappedFile != INVALID_HANDLE_VALUE)
|
||||||
|
CloseHandle(hOverlappedFile);
|
||||||
|
|
||||||
|
AsyncPrefetchReset();
|
||||||
|
};
|
||||||
|
|
||||||
|
void GzippedFileReader::AsyncPrefetchChunk(PX_off_t start)
|
||||||
|
{
|
||||||
|
if (hOverlappedFile == INVALID_HANDLE_VALUE || asyncInProgress) {
|
||||||
|
Console.Warning(L"Unexpected file handle or progress state. Aborting prefetch.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LARGE_INTEGER offset;
|
||||||
|
offset.QuadPart = start;
|
||||||
|
|
||||||
|
DWORD bytesToRead = READ_CHUNK_SIZE;
|
||||||
|
|
||||||
|
ZeroMemory(&asyncOperationContext, sizeof(asyncOperationContext));
|
||||||
|
asyncOperationContext.hEvent = 0;
|
||||||
|
asyncOperationContext.Offset = offset.LowPart;
|
||||||
|
asyncOperationContext.OffsetHigh = offset.HighPart;
|
||||||
|
|
||||||
|
ReadFile(hOverlappedFile, mDummyAsyncPrefetchTarget, bytesToRead, NULL, &asyncOperationContext);
|
||||||
|
asyncInProgress = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GzippedFileReader::AsyncPrefetchCancel()
|
||||||
|
{
|
||||||
|
if (!asyncInProgress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!CancelIo(hOverlappedFile)) {
|
||||||
|
Console.Warning("canceling gz prefetch failed. following prefetching will not work.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncInProgress = false;
|
||||||
|
};
|
||||||
|
#endif /* WIN32 */
|
||||||
|
|
||||||
|
// TODO: do better than just checking existance and extension
|
||||||
|
bool GzippedFileReader::CanHandle(const wxString& fileName) {
|
||||||
|
return wxFileName::FileExists(fileName) && fileName.Lower().EndsWith(L".gz");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GzippedFileReader::OkIndex() {
|
||||||
|
if (m_pIndex)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Try to read index from disk
|
||||||
|
wxString indexfile = iso2indexname(m_filename);
|
||||||
|
if (indexfile.length() == 0)
|
||||||
|
return false; // iso2indexname(...) will print errors if it can't apply the template
|
||||||
|
|
||||||
|
if (wxFileName::FileExists(indexfile) && (m_pIndex = ReadIndexFromFile(indexfile))) {
|
||||||
|
Console.WriteLn(Color_Green, L"OK: Gzip quick access index read from disk: '%s'", WX_STR(indexfile));
|
||||||
|
if (m_pIndex->span != SPAN_DEFAULT) {
|
||||||
|
Console.Warning(L"Note: This index has %1.1f MB intervals, while the current default for new indexes is %1.1f MB.",
|
||||||
|
(float)m_pIndex->span / 1024 / 1024, (float)SPAN_DEFAULT / 1024 / 1024);
|
||||||
|
Console.Warning(L"It will work fine, but if you want to generate a new index with default intervals, delete this index file.");
|
||||||
|
Console.Warning(L"(smaller intervals mean bigger index file and quicker but more frequent decompressions)");
|
||||||
|
}
|
||||||
|
InitZstates();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No valid index file. Generate an index
|
||||||
|
Console.Warning(L"This may take a while (but only once). Scanning compressed file to generate a quick access index...");
|
||||||
|
|
||||||
|
Access *index;
|
||||||
|
FILE* infile = PX_fopen_rb(m_filename);
|
||||||
|
int len = build_index(infile, SPAN_DEFAULT, &index);
|
||||||
|
printf("\n"); // build_index prints progress without \n's
|
||||||
|
fclose(infile);
|
||||||
|
|
||||||
|
if (len >= 0) {
|
||||||
|
m_pIndex = index;
|
||||||
|
WriteIndexToFile((Access*)m_pIndex, indexfile);
|
||||||
|
} else {
|
||||||
|
Console.Error(L"ERROR (%d): index could not be generated for file '%s'", len, WX_STR(m_filename));
|
||||||
|
InitZstates();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitZstates();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GzippedFileReader::Open(const wxString& fileName) {
|
||||||
|
Close();
|
||||||
|
m_filename = fileName;
|
||||||
|
if (!(m_src = PX_fopen_rb(m_filename)) || !CanHandle(fileName) || !OkIndex()) {
|
||||||
|
Close();
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
AsyncPrefetchOpen();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GzippedFileReader::BeginRead(void* pBuffer, uint sector, uint count) {
|
||||||
|
// No a-sync support yet, implement as sync
|
||||||
|
mBytesRead = ReadSync(pBuffer, sector, count);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
int GzippedFileReader::FinishRead(void) {
|
||||||
|
int res = mBytesRead;
|
||||||
|
mBytesRead = -1;
|
||||||
|
return res;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define PTT clock_t
|
||||||
|
#define NOW() (clock() / (CLOCKS_PER_SEC / 1000))
|
||||||
|
|
||||||
|
int GzippedFileReader::ReadSync(void* pBuffer, uint sector, uint count) {
|
||||||
|
PX_off_t offset = (s64)sector * m_blocksize + m_dataoffset;
|
||||||
|
int bytesToRead = count * m_blocksize;
|
||||||
|
int res = _ReadSync(pBuffer, offset, bytesToRead);
|
||||||
|
if (res < 0)
|
||||||
|
Console.Error(L"Error: iso-gzip read unsuccessful.");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a valid and adequate zstate for this span, use it, else, use the index
|
||||||
|
PX_off_t GzippedFileReader::GetOptimalExtractionStart(PX_off_t offset) {
|
||||||
|
int span = m_pIndex->span;
|
||||||
|
Czstate& cstate = m_zstates[offset / span];
|
||||||
|
PX_off_t stateOffset = cstate.state.isValid ? cstate.state.out_offset : 0;
|
||||||
|
if (stateOffset && stateOffset <= offset)
|
||||||
|
return stateOffset; // state is faster than indexed
|
||||||
|
|
||||||
|
// If span is not exact multiples of READ_CHUNK_SIZE (because it was configured badly),
|
||||||
|
// we fallback to always READ_CHUNK_SIZE boundaries
|
||||||
|
if (span % READ_CHUNK_SIZE)
|
||||||
|
return offset / READ_CHUNK_SIZE * READ_CHUNK_SIZE;
|
||||||
|
|
||||||
|
return span * (offset / span); // index direct access boundaries
|
||||||
|
}
|
||||||
|
|
||||||
|
int GzippedFileReader::_ReadSync(void* pBuffer, PX_off_t offset, uint bytesToRead) {
|
||||||
|
if (!OkIndex())
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Without all the caching, chunking and states, this would be enough:
|
||||||
|
// return extract(m_src, m_pIndex, offset, (unsigned char*)pBuffer, bytesToRead);
|
||||||
|
|
||||||
|
// Split request to READ_CHUNK_SIZE chunks at READ_CHUNK_SIZE boundaries
|
||||||
|
uint maxInChunk = READ_CHUNK_SIZE - offset % READ_CHUNK_SIZE;
|
||||||
|
if (bytesToRead > maxInChunk) {
|
||||||
|
int first = _ReadSync(pBuffer, offset, maxInChunk);
|
||||||
|
if (first != maxInChunk)
|
||||||
|
return first; // EOF or failure
|
||||||
|
|
||||||
|
int rest = _ReadSync((char*)pBuffer + maxInChunk, offset + maxInChunk, bytesToRead - maxInChunk);
|
||||||
|
if (rest < 0)
|
||||||
|
return rest;
|
||||||
|
|
||||||
|
return first + rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From here onwards it's guarenteed that the request is inside a single READ_CHUNK_SIZE boundaries
|
||||||
|
|
||||||
|
int res = m_cache.Read(pBuffer, offset, bytesToRead);
|
||||||
|
if (res >= 0)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
// Not available from cache. Decompress from optimal starting
|
||||||
|
// point in READ_CHUNK_SIZE chunks and cache each chunk.
|
||||||
|
PTT s = NOW();
|
||||||
|
PX_off_t extractOffset = GetOptimalExtractionStart(offset); // guaranteed in READ_CHUNK_SIZE boundaries
|
||||||
|
int size = offset + maxInChunk - extractOffset;
|
||||||
|
unsigned char* extracted = (unsigned char*)malloc(size);
|
||||||
|
|
||||||
|
int span = m_pIndex->span;
|
||||||
|
int spanix = extractOffset / span;
|
||||||
|
AsyncPrefetchCancel();
|
||||||
|
res = extract(m_src, m_pIndex, extractOffset, extracted, size, &(m_zstates[spanix].state));
|
||||||
|
if (res < 0) {
|
||||||
|
free(extracted);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
AsyncPrefetchChunk(getInOffset(&(m_zstates[spanix].state)));
|
||||||
|
|
||||||
|
int copied = ChunksCache::CopyAvailable(extracted, extractOffset, res, pBuffer, offset, bytesToRead);
|
||||||
|
|
||||||
|
if (m_zstates[spanix].state.isValid && (extractOffset + res) / span != offset / span) {
|
||||||
|
// The state no longer matches this span.
|
||||||
|
// move the state to the appropriate span because it will be faster than using the index
|
||||||
|
int targetix = (extractOffset + res) / span;
|
||||||
|
m_zstates[targetix].Kill();
|
||||||
|
m_zstates[targetix] = m_zstates[spanix]; // We have elements for the entire file, and another one.
|
||||||
|
m_zstates[spanix].state.isValid = 0; // Not killing because we need the state.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size <= READ_CHUNK_SIZE)
|
||||||
|
m_cache.Take(extracted, extractOffset, res, size);
|
||||||
|
else { // split into cacheable chunks
|
||||||
|
for (int i = 0; i < size; i += READ_CHUNK_SIZE) {
|
||||||
|
int available = CLAMP(res - i, 0, READ_CHUNK_SIZE);
|
||||||
|
void* chunk = available ? malloc(available) : 0;
|
||||||
|
if (available)
|
||||||
|
memcpy(chunk, extracted + i, available);
|
||||||
|
m_cache.Take(chunk, extractOffset + i, available, std::min(size - i, READ_CHUNK_SIZE));
|
||||||
|
}
|
||||||
|
free(extracted);
|
||||||
|
}
|
||||||
|
|
||||||
|
int duration = NOW() - s;
|
||||||
|
if (duration > 10)
|
||||||
|
Console.WriteLn(Color_Gray, L"gunzip: chunk #%5d-%2d : %1.2f MB - %d ms",
|
||||||
|
(int)(offset / 4 / 1024 / 1024),
|
||||||
|
(int)(offset % (4 * 1024 * 1024) / READ_CHUNK_SIZE),
|
||||||
|
(float)size / 1024 / 1024,
|
||||||
|
duration);
|
||||||
|
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GzippedFileReader::Close() {
|
||||||
|
m_filename.Empty();
|
||||||
|
if (m_pIndex) {
|
||||||
|
free_index((Access*)m_pIndex);
|
||||||
|
m_pIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitZstates(); // results in delete because no index
|
||||||
|
m_cache.Clear();
|
||||||
|
|
||||||
|
if (m_src) {
|
||||||
|
fclose(m_src);
|
||||||
|
m_src = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncPrefetchClose();
|
||||||
|
}
|
|
@ -0,0 +1,93 @@
|
||||||
|
/* PCSX2 - PS2 Emulator for PCs
|
||||||
|
* Copyright (C) 2002-2014 PCSX2 Dev Team
|
||||||
|
*
|
||||||
|
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||||
|
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||||
|
* ation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||||
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||||
|
* PURPOSE. See the GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||||
|
* If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
typedef struct zstate Zstate;
|
||||||
|
|
||||||
|
#include "AsyncFileReader.h"
|
||||||
|
#include "ChunksCache.h"
|
||||||
|
#include "zlib_indexed.h"
|
||||||
|
|
||||||
|
#define SPAN_DEFAULT (1048576L * 4) /* distance between direct access points when creating a new index */
|
||||||
|
#define READ_CHUNK_SIZE (256 * 1024) /* zlib extraction chunks size (at 0-based boundaries) */
|
||||||
|
#define CACHE_SIZE_MB 200 /* cache size for extracted data. must be at least READ_CHUNK_SIZE (in MB)*/
|
||||||
|
|
||||||
|
class GzippedFileReader : public AsyncFileReader
|
||||||
|
{
|
||||||
|
DeclareNoncopyableObject(GzippedFileReader);
|
||||||
|
public:
|
||||||
|
GzippedFileReader(void);
|
||||||
|
|
||||||
|
virtual ~GzippedFileReader(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 {
|
||||||
|
// type and formula copied from FlatFileReader
|
||||||
|
// FIXME? : Shouldn't it be uint and (size - m_dataoffset) / m_blocksize ?
|
||||||
|
return (int)((m_pIndex ? m_pIndex->uncompressed_size : 0) / m_blocksize);
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual void SetBlockSize(uint bytes) { m_blocksize = bytes; }
|
||||||
|
virtual void SetDataOffset(int bytes) { m_dataoffset = bytes; }
|
||||||
|
private:
|
||||||
|
class Czstate {
|
||||||
|
public:
|
||||||
|
Czstate() { state.isValid = 0; };
|
||||||
|
~Czstate() { Kill(); };
|
||||||
|
void Kill() {
|
||||||
|
if (state.isValid)
|
||||||
|
inflateEnd(&state.strm);
|
||||||
|
state.isValid = 0;
|
||||||
|
}
|
||||||
|
Zstate state;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool OkIndex(); // Verifies that we have an index, or try to create one
|
||||||
|
PX_off_t GetOptimalExtractionStart(PX_off_t offset);
|
||||||
|
int _ReadSync(void* pBuffer, PX_off_t offset, uint bytesToRead);
|
||||||
|
void InitZstates();
|
||||||
|
|
||||||
|
int mBytesRead; // Temp sync read result when simulating async read
|
||||||
|
Access* m_pIndex; // Quick access index
|
||||||
|
Czstate* m_zstates;
|
||||||
|
FILE* m_src;
|
||||||
|
|
||||||
|
ChunksCache m_cache;
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
// Used by async prefetch
|
||||||
|
HANDLE hOverlappedFile;
|
||||||
|
OVERLAPPED asyncOperationContext;
|
||||||
|
bool asyncInProgress;
|
||||||
|
byte mDummyAsyncPrefetchTarget[READ_CHUNK_SIZE];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void AsyncPrefetchReset();
|
||||||
|
void AsyncPrefetchOpen();
|
||||||
|
void AsyncPrefetchClose();
|
||||||
|
void AsyncPrefetchChunk(PX_off_t dummy);
|
||||||
|
void AsyncPrefetchCancel();
|
||||||
|
};
|
|
@ -18,6 +18,7 @@
|
||||||
#include "CDVD.h"
|
#include "CDVD.h"
|
||||||
#include "wx/wfstream.h"
|
#include "wx/wfstream.h"
|
||||||
#include "AsyncFileReader.h"
|
#include "AsyncFileReader.h"
|
||||||
|
#include "CompressedFileReader.h"
|
||||||
|
|
||||||
enum isoType
|
enum isoType
|
||||||
{
|
{
|
||||||
|
|
|
@ -336,7 +336,7 @@ typedef struct zstate {
|
||||||
int isValid;
|
int isValid;
|
||||||
} Zstate;
|
} Zstate;
|
||||||
|
|
||||||
PX_off_t getInOffset(zstate *state) {
|
static inline PX_off_t getInOffset(zstate *state) {
|
||||||
return state->in_offset;
|
return state->in_offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -236,7 +236,10 @@ set(pcsx2CDVDSources
|
||||||
CDVD/CDVDisoReader.cpp
|
CDVD/CDVDisoReader.cpp
|
||||||
CDVD/InputIsoFile.cpp
|
CDVD/InputIsoFile.cpp
|
||||||
CDVD/OutputIsoFile.cpp
|
CDVD/OutputIsoFile.cpp
|
||||||
|
CDVD/ChunksCache.cpp
|
||||||
CDVD/CompressedFileReader.cpp
|
CDVD/CompressedFileReader.cpp
|
||||||
|
CDVD/CsoFileReader.cpp
|
||||||
|
CDVD/GzippedFileReader.cpp
|
||||||
CDVD/IsoFS/IsoFile.cpp
|
CDVD/IsoFS/IsoFile.cpp
|
||||||
CDVD/IsoFS/IsoFSCDVD.cpp
|
CDVD/IsoFS/IsoFSCDVD.cpp
|
||||||
CDVD/IsoFS/IsoFS.cpp
|
CDVD/IsoFS/IsoFS.cpp
|
||||||
|
|
|
@ -423,6 +423,9 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\..\CDVD\BlockdumpFileReader.cpp" />
|
<ClCompile Include="..\..\CDVD\BlockdumpFileReader.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\ChunksCache.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\CsoFileReader.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\GzippedFileReader.cpp" />
|
||||||
<ClCompile Include="..\..\CDVD\OutputIsoFile.cpp" />
|
<ClCompile Include="..\..\CDVD\OutputIsoFile.cpp" />
|
||||||
<ClCompile Include="..\..\DebugTools\Breakpoints.cpp" />
|
<ClCompile Include="..\..\DebugTools\Breakpoints.cpp" />
|
||||||
<ClCompile Include="..\..\DebugTools\DebugInterface.cpp" />
|
<ClCompile Include="..\..\DebugTools\DebugInterface.cpp" />
|
||||||
|
@ -710,6 +713,11 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\AsyncFileReader.h" />
|
<ClInclude Include="..\..\AsyncFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\ChunksCache.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReaderUtils.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CsoFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\GzippedFileReader.h" />
|
||||||
<ClInclude Include="..\..\CDVD\zlib_indexed.h" />
|
<ClInclude Include="..\..\CDVD\zlib_indexed.h" />
|
||||||
<ClInclude Include="..\..\DebugTools\Breakpoints.h" />
|
<ClInclude Include="..\..\DebugTools\Breakpoints.h" />
|
||||||
<ClInclude Include="..\..\DebugTools\DebugInterface.h" />
|
<ClInclude Include="..\..\DebugTools\DebugInterface.h" />
|
||||||
|
|
|
@ -859,6 +859,15 @@
|
||||||
<ClCompile Include="..\..\DebugTools\MipsStackWalk.cpp">
|
<ClCompile Include="..\..\DebugTools\MipsStackWalk.cpp">
|
||||||
<Filter>System\Ps2\Debug</Filter>
|
<Filter>System\Ps2\Debug</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\CsoFileReader.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\GzippedFileReader.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\ChunksCache.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\Patch.h">
|
<ClInclude Include="..\..\Patch.h">
|
||||||
|
@ -1275,6 +1284,21 @@
|
||||||
<ClInclude Include="..\..\DebugTools\MipsStackWalk.h">
|
<ClInclude Include="..\..\DebugTools\MipsStackWalk.h">
|
||||||
<Filter>System\Ps2\Debug</Filter>
|
<Filter>System\Ps2\Debug</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CsoFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\GzippedFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\ChunksCache.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReaderUtils.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="..\..\..\3rdparty\wxWidgets\include\wx\msw\wx.rc">
|
<ResourceCompile Include="..\..\..\3rdparty\wxWidgets\include\wx\msw\wx.rc">
|
||||||
|
|
|
@ -407,6 +407,9 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\..\CDVD\BlockdumpFileReader.cpp" />
|
<ClCompile Include="..\..\CDVD\BlockdumpFileReader.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\ChunksCache.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\CsoFileReader.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\GzippedFileReader.cpp" />
|
||||||
<ClCompile Include="..\..\CDVD\OutputIsoFile.cpp" />
|
<ClCompile Include="..\..\CDVD\OutputIsoFile.cpp" />
|
||||||
<ClCompile Include="..\..\DebugTools\Breakpoints.cpp" />
|
<ClCompile Include="..\..\DebugTools\Breakpoints.cpp" />
|
||||||
<ClCompile Include="..\..\DebugTools\DebugInterface.cpp" />
|
<ClCompile Include="..\..\DebugTools\DebugInterface.cpp" />
|
||||||
|
@ -696,6 +699,11 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\AsyncFileReader.h" />
|
<ClInclude Include="..\..\AsyncFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\ChunksCache.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReaderUtils.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CsoFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\GzippedFileReader.h" />
|
||||||
<ClInclude Include="..\..\CDVD\zlib_indexed.h" />
|
<ClInclude Include="..\..\CDVD\zlib_indexed.h" />
|
||||||
<ClInclude Include="..\..\DebugTools\Breakpoints.h" />
|
<ClInclude Include="..\..\DebugTools\Breakpoints.h" />
|
||||||
<ClInclude Include="..\..\DebugTools\DebugInterface.h" />
|
<ClInclude Include="..\..\DebugTools\DebugInterface.h" />
|
||||||
|
|
|
@ -856,6 +856,15 @@
|
||||||
<ClCompile Include="..\..\DebugTools\MipsStackWalk.cpp">
|
<ClCompile Include="..\..\DebugTools\MipsStackWalk.cpp">
|
||||||
<Filter>System\Ps2\Debug</Filter>
|
<Filter>System\Ps2\Debug</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\CsoFileReader.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\GzippedFileReader.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\ChunksCache.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\Patch.h">
|
<ClInclude Include="..\..\Patch.h">
|
||||||
|
@ -1275,6 +1284,21 @@
|
||||||
<ClInclude Include="..\..\DebugTools\MipsStackWalk.h">
|
<ClInclude Include="..\..\DebugTools\MipsStackWalk.h">
|
||||||
<Filter>System\Ps2\Debug</Filter>
|
<Filter>System\Ps2\Debug</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CsoFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\GzippedFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\ChunksCache.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReaderUtils.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="..\..\..\3rdparty\wxWidgets\include\wx\msw\wx.rc">
|
<ResourceCompile Include="..\..\..\3rdparty\wxWidgets\include\wx\msw\wx.rc">
|
||||||
|
|
|
@ -406,7 +406,10 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="..\..\CDVD\BlockdumpFileReader.cpp" />
|
<ClCompile Include="..\..\CDVD\BlockdumpFileReader.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\ChunksCache.cpp" />
|
||||||
<ClCompile Include="..\..\CDVD\CompressedFileReader.cpp" />
|
<ClCompile Include="..\..\CDVD\CompressedFileReader.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\CsoFileReader.cpp" />
|
||||||
|
<ClCompile Include="..\..\CDVD\GzippedFileReader.cpp" />
|
||||||
<ClCompile Include="..\..\CDVD\OutputIsoFile.cpp" />
|
<ClCompile Include="..\..\CDVD\OutputIsoFile.cpp" />
|
||||||
<ClCompile Include="..\..\DebugTools\Breakpoints.cpp" />
|
<ClCompile Include="..\..\DebugTools\Breakpoints.cpp" />
|
||||||
<ClCompile Include="..\..\DebugTools\DebugInterface.cpp" />
|
<ClCompile Include="..\..\DebugTools\DebugInterface.cpp" />
|
||||||
|
@ -696,6 +699,11 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\AsyncFileReader.h" />
|
<ClInclude Include="..\..\AsyncFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\ChunksCache.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReaderUtils.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\CsoFileReader.h" />
|
||||||
|
<ClInclude Include="..\..\CDVD\GzippedFileReader.h" />
|
||||||
<ClInclude Include="..\..\CDVD\zlib_indexed.h" />
|
<ClInclude Include="..\..\CDVD\zlib_indexed.h" />
|
||||||
<ClInclude Include="..\..\DebugTools\Breakpoints.h" />
|
<ClInclude Include="..\..\DebugTools\Breakpoints.h" />
|
||||||
<ClInclude Include="..\..\DebugTools\DebugInterface.h" />
|
<ClInclude Include="..\..\DebugTools\DebugInterface.h" />
|
||||||
|
|
|
@ -856,6 +856,15 @@
|
||||||
<ClCompile Include="..\..\DebugTools\MipsStackWalk.cpp">
|
<ClCompile Include="..\..\DebugTools\MipsStackWalk.cpp">
|
||||||
<Filter>System\Ps2\Debug</Filter>
|
<Filter>System\Ps2\Debug</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\CsoFileReader.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\GzippedFileReader.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\CDVD\ChunksCache.cpp">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\Patch.h">
|
<ClInclude Include="..\..\Patch.h">
|
||||||
|
@ -1275,6 +1284,21 @@
|
||||||
<ClInclude Include="..\..\DebugTools\MipsStackWalk.h">
|
<ClInclude Include="..\..\DebugTools\MipsStackWalk.h">
|
||||||
<Filter>System\Ps2\Debug</Filter>
|
<Filter>System\Ps2\Debug</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CsoFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\GzippedFileReader.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\ChunksCache.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\CDVD\CompressedFileReaderUtils.h">
|
||||||
|
<Filter>System\ISO</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ResourceCompile Include="..\..\..\3rdparty\wxWidgets\include\wx\msw\wx.rc">
|
<ResourceCompile Include="..\..\..\3rdparty\wxWidgets\include\wx\msw\wx.rc">
|
||||||
|
|
Loading…
Reference in New Issue