From 334f648eaa9768c4f053101d357cddb2da83f40e Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Tue, 14 Apr 2015 19:07:08 -0700 Subject: [PATCH] Separate compressed file types into separate files. Cleaner this way. --- pcsx2/AsyncFileReader.h | 16 - pcsx2/CDVD/ChunksCache.cpp | 52 + pcsx2/CDVD/ChunksCache.h | 63 ++ pcsx2/CDVD/CompressedFileReader.cpp | 927 +----------------- pcsx2/CDVD/CompressedFileReader.h | 32 + pcsx2/CDVD/CompressedFileReaderUtils.h | 32 + pcsx2/CDVD/CsoFileReader.cpp | 277 ++++++ pcsx2/CDVD/CsoFileReader.h | 74 ++ pcsx2/CDVD/GzippedFileReader.cpp | 474 +++++++++ pcsx2/CDVD/GzippedFileReader.h | 93 ++ pcsx2/CDVD/IsoFileFormats.h | 1 + pcsx2/CDVD/zlib_indexed.h | 2 +- pcsx2/CMakeLists.txt | 3 + pcsx2/windows/VCprojects/pcsx2.vcxproj | 8 + .../windows/VCprojects/pcsx2.vcxproj.filters | 24 + pcsx2/windows/VCprojects/pcsx2_vs2012.vcxproj | 8 + .../VCprojects/pcsx2_vs2012.vcxproj.filters | 24 + pcsx2/windows/VCprojects/pcsx2_vs2013.vcxproj | 8 + .../VCprojects/pcsx2_vs2013.vcxproj.filters | 24 + 19 files changed, 1201 insertions(+), 941 deletions(-) create mode 100644 pcsx2/CDVD/ChunksCache.cpp create mode 100644 pcsx2/CDVD/ChunksCache.h create mode 100644 pcsx2/CDVD/CompressedFileReader.h create mode 100644 pcsx2/CDVD/CompressedFileReaderUtils.h create mode 100644 pcsx2/CDVD/CsoFileReader.cpp create mode 100644 pcsx2/CDVD/CsoFileReader.h create mode 100644 pcsx2/CDVD/GzippedFileReader.cpp create mode 100644 pcsx2/CDVD/GzippedFileReader.h 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 cf604cde38..5e6588f6b9 100644 --- a/pcsx2/CDVD/CompressedFileReader.cpp +++ b/pcsx2/CDVD/CompressedFileReader.cpp @@ -14,931 +14,10 @@ */ #include "PrecompiledHeader.h" -#include -#include "AppConfig.h" #include "AsyncFileReader.h" -#include "Pcsx2Types.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(); -} - - -// 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(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. -} - +#include "CompressedFileReader.h" +#include "CsoFileReader.h" +#include "GzippedFileReader.h" // CompressedFileReader factory. diff --git a/pcsx2/CDVD/CompressedFileReader.h b/pcsx2/CDVD/CompressedFileReader.h new file mode 100644 index 0000000000..fbb11edc20 --- /dev/null +++ b/pcsx2/CDVD/CompressedFileReader.h @@ -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 . + */ + +#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; +}; diff --git a/pcsx2/CDVD/CompressedFileReaderUtils.h b/pcsx2/CDVD/CompressedFileReaderUtils.h new file mode 100644 index 0000000000..07fe871c16 --- /dev/null +++ b/pcsx2/CDVD/CompressedFileReaderUtils.h @@ -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 . +*/ + +#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 ////////// diff --git a/pcsx2/CDVD/CsoFileReader.cpp b/pcsx2/CDVD/CsoFileReader.cpp new file mode 100644 index 0000000000..1362c858cb --- /dev/null +++ b/pcsx2/CDVD/CsoFileReader.cpp @@ -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 . +*/ + +#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(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..0159566cf6 --- /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(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(); +} diff --git a/pcsx2/CDVD/GzippedFileReader.h b/pcsx2/CDVD/GzippedFileReader.h new file mode 100644 index 0000000000..b4de473a3e --- /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 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(); +}; 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..aba431e6a3 100644 --- a/pcsx2/CDVD/zlib_indexed.h +++ b/pcsx2/CDVD/zlib_indexed.h @@ -336,7 +336,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/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 +