diff --git a/pcsx2/AsyncFileReader.h b/pcsx2/AsyncFileReader.h
index af167682cc..d4cc7e1a1c 100644
--- a/pcsx2/AsyncFileReader.h
+++ b/pcsx2/AsyncFileReader.h
@@ -97,22 +97,6 @@ public:
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
{
DeclareNoncopyableObject( MultipartFileReader );
diff --git a/pcsx2/CDVD/ChunksCache.cpp b/pcsx2/CDVD/ChunksCache.cpp
new file mode 100644
index 0000000000..5f127d1c16
--- /dev/null
+++ b/pcsx2/CDVD/ChunksCache.cpp
@@ -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 .
+*/
+
+#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::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;
+}
+
diff --git a/pcsx2/CDVD/ChunksCache.h b/pcsx2/CDVD/ChunksCache.h
new file mode 100644
index 0000000000..58dbae46eb
--- /dev/null
+++ b/pcsx2/CDVD/ChunksCache.h
@@ -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 .
+*/
+
+#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 m_entries;
+ void MatchLimit(bool removeAll = false);
+ PX_off_t m_size;
+ PX_off_t m_limit;
+};
+
+#undef CLAMP
diff --git a/pcsx2/CDVD/CompressedFileReader.cpp b/pcsx2/CDVD/CompressedFileReader.cpp
index bda3b2cfb0..4f41c997d0 100644
--- a/pcsx2/CDVD/CompressedFileReader.cpp
+++ b/pcsx2/CDVD/CompressedFileReader.cpp
@@ -14,635 +14,19 @@
*/
#include "PrecompiledHeader.h"
-#include
-#include "AppConfig.h"
#include "AsyncFileReader.h"
+#include "CompressedFileReader.h"
+#include "CsoFileReader.h"
+#include "GzippedFileReader.h"
-#include "zlib_indexed.h"
-
-/////////// Some complementary utilities for zlib_indexed.c //////////
-
-#include
-
-// 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 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::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//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();
-}
-
-
-// CompressedFileReader factory - currently there's only GzippedFileReader
-
-// Go through available compressed readers
-bool CompressedFileReader::DetectCompressed(AsyncFileReader* pReader) {
- return GzippedFileReader::CanHandle(pReader->GetFilename());
-}
-
-// Return a new reader which can handle, or any reader otherwise (which will fail on open)
+// CompressedFileReader factory.
AsyncFileReader* CompressedFileReader::GetNewReader(const wxString& fileName) {
- //if (GzippedFileReader::CanHandle(pReader))
- return new GzippedFileReader();
+ if (GzippedFileReader::CanHandle(fileName)) {
+ return new GzippedFileReader();
+ }
+ if (CsoFileReader::CanHandle(fileName)) {
+ return new CsoFileReader();
+ }
+ // This is the one which will fail on open.
+ return NULL;
}
diff --git a/pcsx2/CDVD/CompressedFileReader.h b/pcsx2/CDVD/CompressedFileReader.h
new file mode 100644
index 0000000000..2267bf2c96
--- /dev/null
+++ b/pcsx2/CDVD/CompressedFileReader.h
@@ -0,0 +1,29 @@
+/* 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 .
+ */
+
+#pragma once
+
+// Factory - creates an AsyncFileReader derived instance which can read a compressed file
+class CompressedFileReader {
+public:
+ // fileName and its contents may be used to choose the compressed reader.
+ // If no matching handler is found, NULL is 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;
+};
diff --git a/pcsx2/CDVD/CompressedFileReaderUtils.h b/pcsx2/CDVD/CompressedFileReaderUtils.h
new file mode 100644
index 0000000000..6a45100549
--- /dev/null
+++ b/pcsx2/CDVD/CompressedFileReaderUtils.h
@@ -0,0 +1,44 @@
+/* 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 .
+*/
+
+#pragma once
+
+#include
+
+/////////// 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
+
+#ifdef WIN32
+# define PX_fseeko _fseeki64
+# define PX_ftello _ftelli64
+# define PX_off_t s64 /* __int64 */
+#else
+# define PX_fseeko fseeko
+# define PX_ftello ftello
+# define PX_off_t off_t
+#endif
+
+/////////// End of complementary utilities for zlib_indexed.c //////////
diff --git a/pcsx2/CDVD/CsoFileReader.cpp b/pcsx2/CDVD/CsoFileReader.cpp
new file mode 100644
index 0000000000..44f8ca5b1e
--- /dev/null
+++ b/pcsx2/CDVD/CsoFileReader.cpp
@@ -0,0 +1,281 @@
+/* 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 .
+*/
+
+#include "PrecompiledHeader.h"
+#include "AsyncFileReader.h"
+#include "CompressedFileReaderUtils.h"
+#include "CsoFileReader.h"
+#include "Pcsx2Types.h"
+#ifdef __linux__
+#include
+#else
+#include
+#endif
+
+// 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(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.
+}
diff --git a/pcsx2/CDVD/CsoFileReader.h b/pcsx2/CDVD/CsoFileReader.h
new file mode 100644
index 0000000000..2363053741
--- /dev/null
+++ b/pcsx2/CDVD/CsoFileReader.h
@@ -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 .
+*/
+
+#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;
+};
diff --git a/pcsx2/CDVD/GzippedFileReader.cpp b/pcsx2/CDVD/GzippedFileReader.cpp
new file mode 100644
index 0000000000..4f0ccc1354
--- /dev/null
+++ b/pcsx2/CDVD/GzippedFileReader.cpp
@@ -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 .
+*/
+
+#include "PrecompiledHeader.h"
+#include
+#include
+#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//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(GZFILE_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 = GZFILE_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 != GZFILE_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)GZFILE_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, GZFILE_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 GZFILE_READ_CHUNK_SIZE (because it was configured badly),
+ // we fallback to always GZFILE_READ_CHUNK_SIZE boundaries
+ if (span % GZFILE_READ_CHUNK_SIZE)
+ return offset / GZFILE_READ_CHUNK_SIZE * GZFILE_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 GZFILE_READ_CHUNK_SIZE chunks at GZFILE_READ_CHUNK_SIZE boundaries
+ uint maxInChunk = GZFILE_READ_CHUNK_SIZE - offset % GZFILE_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 GZFILE_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 GZFILE_READ_CHUNK_SIZE chunks and cache each chunk.
+ PTT s = NOW();
+ PX_off_t extractOffset = GetOptimalExtractionStart(offset); // guaranteed in GZFILE_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 <= GZFILE_READ_CHUNK_SIZE)
+ m_cache.Take(extracted, extractOffset, res, size);
+ else { // split into cacheable chunks
+ for (int i = 0; i < size; i += GZFILE_READ_CHUNK_SIZE) {
+ int available = CLAMP(res - i, 0, GZFILE_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, GZFILE_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) / GZFILE_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();
+}
diff --git a/pcsx2/CDVD/GzippedFileReader.h b/pcsx2/CDVD/GzippedFileReader.h
new file mode 100644
index 0000000000..abb781296e
--- /dev/null
+++ b/pcsx2/CDVD/GzippedFileReader.h
@@ -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 .
+*/
+
+#pragma once
+
+typedef struct zstate Zstate;
+
+#include "AsyncFileReader.h"
+#include "ChunksCache.h"
+#include "zlib_indexed.h"
+
+#define GZFILE_SPAN_DEFAULT (1048576L * 4) /* distance between direct access points when creating a new index */
+#define GZFILE_READ_CHUNK_SIZE (256 * 1024) /* zlib extraction chunks size (at 0-based boundaries) */
+#define GZFILE_CACHE_SIZE_MB 200 /* cache size for extracted data. must be at least GZFILE_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[GZFILE_READ_CHUNK_SIZE];
+#endif
+
+ void AsyncPrefetchReset();
+ void AsyncPrefetchOpen();
+ void AsyncPrefetchClose();
+ void AsyncPrefetchChunk(PX_off_t dummy);
+ void AsyncPrefetchCancel();
+};
diff --git a/pcsx2/CDVD/InputIsoFile.cpp b/pcsx2/CDVD/InputIsoFile.cpp
index ab1602f853..2bf6bef0bc 100644
--- a/pcsx2/CDVD/InputIsoFile.cpp
+++ b/pcsx2/CDVD/InputIsoFile.cpp
@@ -200,20 +200,34 @@ bool InputIsoFile::Open( const wxString& srcfile, bool testOnly )
{
Close();
m_filename = srcfile;
-
- // Allow write sharing of the iso based on the ini settings.
- // Mostly useful for romhacking, where the disc is frequently
- // changed and the emulator would block modifications
- m_reader = new FlatFileReader(EmuConfig.CdvdShareWrite);
+ m_reader = NULL;
+
+ bool isBlockdump = false;
+ bool isCompressed = false;
+
+ // First try using a compressed reader. If it works, go with it.
+ m_reader = CompressedFileReader::GetNewReader(m_filename);
+ isCompressed = m_reader != NULL;
+
+ // If it wasn't compressed, let's open it has a FlatFileReader.
+ if (!isCompressed)
+ {
+ // Allow write sharing of the iso based on the ini settings.
+ // Mostly useful for romhacking, where the disc is frequently
+ // changed and the emulator would block modifications
+ m_reader = new FlatFileReader(EmuConfig.CdvdShareWrite);
+ }
+
m_reader->Open(m_filename);
- bool isBlockdump, isCompressed = false;
- if(isBlockdump = BlockdumpFileReader::DetectBlockdump(m_reader))
+ // It might actually be a blockdump file.
+ // Check that before continuing with the FlatFileReader.
+ isBlockdump = BlockdumpFileReader::DetectBlockdump(m_reader);
+ if (isBlockdump)
{
delete m_reader;
- BlockdumpFileReader *bdr = new BlockdumpFileReader();;
-
+ BlockdumpFileReader *bdr = new BlockdumpFileReader();
bdr->Open(m_filename);
m_blockofs = bdr->GetBlockOffset();
@@ -221,11 +235,7 @@ bool InputIsoFile::Open( const wxString& srcfile, bool testOnly )
m_reader = bdr;
- ReadUnit = 1;
- } else if (isCompressed = CompressedFileReader::DetectCompressed(m_reader)) {
- delete m_reader;
- m_reader = CompressedFileReader::GetNewReader(m_filename);
- m_reader->Open(m_filename);
+ ReadUnit = 1;
}
bool detected = Detect();
diff --git a/pcsx2/CDVD/IsoFileFormats.h b/pcsx2/CDVD/IsoFileFormats.h
index 9feea6b47c..c390fb510a 100644
--- a/pcsx2/CDVD/IsoFileFormats.h
+++ b/pcsx2/CDVD/IsoFileFormats.h
@@ -18,6 +18,7 @@
#include "CDVD.h"
#include "wx/wfstream.h"
#include "AsyncFileReader.h"
+#include "CompressedFileReader.h"
enum isoType
{
diff --git a/pcsx2/CDVD/zlib_indexed.h b/pcsx2/CDVD/zlib_indexed.h
index ae61787fc5..1ae7238944 100644
--- a/pcsx2/CDVD/zlib_indexed.h
+++ b/pcsx2/CDVD/zlib_indexed.h
@@ -107,16 +107,7 @@ Comments) 1950 to 1952 in the files http://tools.ietf.org/html/rfc1950
#include
#endif
-#include
-#ifdef WIN32
-# define PX_fseeko _fseeki64
-# define PX_ftello _ftelli64
-# define PX_off_t s64 /* __int64 */
-#else
-# define PX_fseeko fseeko
-# define PX_ftello ftello
-# define PX_off_t off_t
-#endif
+#include "CompressedFileReaderUtils.h"
#define local static
@@ -336,7 +327,7 @@ typedef struct zstate {
int isValid;
} Zstate;
-PX_off_t getInOffset(zstate *state) {
+static inline PX_off_t getInOffset(zstate *state) {
return state->in_offset;
}
diff --git a/pcsx2/CMakeLists.txt b/pcsx2/CMakeLists.txt
index 871713e09c..1a1705b32f 100644
--- a/pcsx2/CMakeLists.txt
+++ b/pcsx2/CMakeLists.txt
@@ -236,7 +236,10 @@ set(pcsx2CDVDSources
CDVD/CDVDisoReader.cpp
CDVD/InputIsoFile.cpp
CDVD/OutputIsoFile.cpp
+ CDVD/ChunksCache.cpp
CDVD/CompressedFileReader.cpp
+ CDVD/CsoFileReader.cpp
+ CDVD/GzippedFileReader.cpp
CDVD/IsoFS/IsoFile.cpp
CDVD/IsoFS/IsoFSCDVD.cpp
CDVD/IsoFS/IsoFS.cpp
diff --git a/pcsx2/gui/MainMenuClicks.cpp b/pcsx2/gui/MainMenuClicks.cpp
index d967f4328e..57a8aa45bf 100644
--- a/pcsx2/gui/MainMenuClicks.cpp
+++ b/pcsx2/gui/MainMenuClicks.cpp
@@ -260,8 +260,8 @@ bool MainEmuFrame::_DoSelectIsoBrowser( wxString& result )
wxArrayString isoFilterTypes;
- isoFilterTypes.Add(pxsFmt(_("All Supported (%s)"), WX_STR((isoSupportedLabel + L" .dump" + L" .gz"))));
- isoFilterTypes.Add(isoSupportedList + L";*.dump" + L";*.gz");
+ isoFilterTypes.Add(pxsFmt(_("All Supported (%s)"), WX_STR((isoSupportedLabel + L" .dump" + L" .gz" + L" .cso"))));
+ isoFilterTypes.Add(isoSupportedList + L";*.dump" + L";*.gz" + L";*.cso");
isoFilterTypes.Add(pxsFmt(_("Disc Images (%s)"), WX_STR(isoSupportedLabel) ));
isoFilterTypes.Add(isoSupportedList);
@@ -269,13 +269,13 @@ bool MainEmuFrame::_DoSelectIsoBrowser( wxString& result )
isoFilterTypes.Add(pxsFmt(_("Blockdumps (%s)"), L".dump" ));
isoFilterTypes.Add(L"*.dump");
- isoFilterTypes.Add(pxsFmt(_("Compressed (%s)"), L".gz"));
- isoFilterTypes.Add(L"*.gz");
+ isoFilterTypes.Add(pxsFmt(_("Compressed (%s)"), L".gz .cso"));
+ isoFilterTypes.Add(L"*.gz;*.cso");
isoFilterTypes.Add(_("All Files (*.*)"));
isoFilterTypes.Add(L"*.*");
- wxFileDialog ctrl( this, _("Select disc image, gzip compressed disc image, or block-dump..."), g_Conf->Folders.RunIso.ToString(), wxEmptyString,
+ wxFileDialog ctrl( this, _("Select disc image, compressed disc image, or block-dump..."), g_Conf->Folders.RunIso.ToString(), wxEmptyString,
JoinString(isoFilterTypes, L"|"), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
if( ctrl.ShowModal() != wxID_CANCEL )
diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj b/pcsx2/windows/VCprojects/pcsx2.vcxproj
index deb70be028..53bfb9ddec 100644
--- a/pcsx2/windows/VCprojects/pcsx2.vcxproj
+++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj
@@ -423,6 +423,9 @@
+
+
+
@@ -710,6 +713,11 @@
+
+
+
+
+
diff --git a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters
index 58eb17b43b..8a45ffef37 100644
--- a/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters
+++ b/pcsx2/windows/VCprojects/pcsx2.vcxproj.filters
@@ -859,6 +859,15 @@
System\Ps2\Debug
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
@@ -1275,6 +1284,21 @@
System\Ps2\Debug
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj
index 648b4ceb7f..d79af944a8 100644
--- a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj
+++ b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj
@@ -407,6 +407,9 @@
+
+
+
@@ -696,6 +699,11 @@
+
+
+
+
+
diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters
index 3bb1e35861..636a7938eb 100644
--- a/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters
+++ b/pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj.filters
@@ -856,6 +856,15 @@
System\Ps2\Debug
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
@@ -1275,6 +1284,21 @@
System\Ps2\Debug
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj
index 98dea47e9a..8ebbd35066 100644
--- a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj
+++ b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj
@@ -406,7 +406,10 @@
+
+
+
@@ -696,6 +699,11 @@
+
+
+
+
+
diff --git a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters
index 62717f06c6..ea48ac61dc 100644
--- a/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters
+++ b/pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj.filters
@@ -856,6 +856,15 @@
System\Ps2\Debug
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
@@ -1275,6 +1284,21 @@
System\Ps2\Debug
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+
+
+ System\ISO
+