diff --git a/Core/CoreParameter.h b/Core/CoreParameter.h index 3cb404f46e..b4c3a78991 100644 --- a/Core/CoreParameter.h +++ b/Core/CoreParameter.h @@ -31,9 +31,11 @@ enum GPUCore { GPU_DIRECTX9, }; +class FileLoader; + // PSP_CoreParameter() struct CoreParameter { - CoreParameter() : collectEmuLog(0), unthrottle(false), fpsLimit(0), updateRecent(true), freezeNext(false), frozen(false) {} + CoreParameter() : collectEmuLog(0), unthrottle(false), fpsLimit(0), updateRecent(true), freezeNext(false), frozen(false), mountIsoLoader(nullptr) {} CPUCore cpuCore; GPUCore gpuCore; bool enableSound; // there aren't multiple sound cores. @@ -65,4 +67,6 @@ struct CoreParameter { // Freeze-frame. For nvidia perfhud profiling. Developers only. bool freezeNext; bool frozen; + + FileLoader *mountIsoLoader; }; diff --git a/Core/FileSystems/BlockDevices.cpp b/Core/FileSystems/BlockDevices.cpp index 1ddf3fad38..41863baa06 100644 --- a/Core/FileSystems/BlockDevices.cpp +++ b/Core/FileSystems/BlockDevices.cpp @@ -17,6 +17,7 @@ #include "Common/FileUtil.h" +#include "Core/Loaders.h" #include "Core/FileSystems/BlockDevices.h" #include #include @@ -29,20 +30,19 @@ extern "C" #include "ext/libkirk/kirk_engine.h" }; -BlockDevice *constructBlockDevice(const char *filename) { +BlockDevice *constructBlockDevice(FileLoader *fileLoader) { // Check for CISO - FILE *f = File::OpenCFile(filename, "rb"); - if (!f) - return 0; + if (!fileLoader->Exists()) + return nullptr; char buffer[4]; - auto size = fread(buffer, 1, 4, f); //size_t - fseek(f, 0, SEEK_SET); + size_t size = fileLoader->ReadAt(0, 1, 4, buffer); + fileLoader->Seek(0); if (!memcmp(buffer, "CISO", 4) && size == 4) - return new CISOFileBlockDevice(f); + return new CISOFileBlockDevice(fileLoader); else if (!memcmp(buffer, "\x00PBP", 4) && size == 4) - return new NPDRMDemoBlockDevice(f); + return new NPDRMDemoBlockDevice(fileLoader); else - return new FileBlockDevice(f); + return new FileBlockDevice(fileLoader); } RAMBlockDevice::RAMBlockDevice(BlockDevice *device) { @@ -74,67 +74,16 @@ u32 RAMBlockDevice::GetNumBlocks() { - -// Android NDK does not support 64-bit file I/O using C streams -// so we fall back onto syscalls - -#ifdef ANDROID - -FileBlockDevice::FileBlockDevice(FILE *file) -: f(file) -{ - fd = fileno(file); - - off64_t off = lseek64(fd, 0, SEEK_END); - filesize = off; - lseek64(fd, 0, SEEK_SET); +FileBlockDevice::FileBlockDevice(FileLoader *fileLoader) + : fileLoader_(fileLoader) { + filesize_ = fileLoader->FileSize(); } -FileBlockDevice::~FileBlockDevice() -{ - fclose(f); +FileBlockDevice::~FileBlockDevice() { } -bool FileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) -{ - lseek64(fd, (u64)blockNumber * (u64)GetBlockSize(), SEEK_SET); - if (read(fd, outPtr, 2048) != 2048) { - ERROR_LOG(FILESYS, "Could not read() 2048 bytes from block"); - return false; - } - return true; -} - -bool FileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) -{ - lseek64(fd, (u64)minBlock * (u64)GetBlockSize(), SEEK_SET); - const s32 bytes = GetBlockSize() * count; - if (read(fd, outPtr, bytes) != bytes) { - ERROR_LOG(FILESYS, "Could not read() %d bytes from block", bytes); - return false; - } - return true; -} - -#else - -FileBlockDevice::FileBlockDevice(FILE *file) - : f(file) -{ - fseek(f, 0, SEEK_END); - filesize = ftello(f); - fseek(f, 0, SEEK_SET); -} - -FileBlockDevice::~FileBlockDevice() -{ - fclose(f); -} - -bool FileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) -{ - fseeko(f, (u64)blockNumber * (u64)GetBlockSize(), SEEK_SET); - if (fread(outPtr, 1, 2048, f) != 2048) { +bool FileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) { + if (fileLoader_->ReadAt((u64)blockNumber * (u64)GetBlockSize(), 1, 2048, outPtr) != 2048) { DEBUG_LOG(FILESYS, "Could not read 2048 bytes from block"); return false; } @@ -142,18 +91,14 @@ bool FileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) return true; } -bool FileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) -{ - fseeko(f, (u64)minBlock * (u64)GetBlockSize(), SEEK_SET); - if (fread(outPtr, 2048, count, f) != count) { +bool FileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) { + if (fileLoader_->ReadAt((u64)minBlock * (u64)GetBlockSize(), 2048, count, outPtr) != count) { ERROR_LOG(FILESYS, "Could not read %d bytes from block", 2048 * count); return false; } return true; } -#endif - // .CSO format // compressed ISO(9660) header format @@ -184,14 +129,13 @@ typedef struct ciso_header static const u32 CSO_READ_BUFFER_SIZE = 256 * 1024; -CISOFileBlockDevice::CISOFileBlockDevice(FILE *file) - : f(file) +CISOFileBlockDevice::CISOFileBlockDevice(FileLoader *fileLoader) + : fileLoader_(fileLoader) { // CISO format is fairly simple, but most tools do not write the header_size. - f = file; CISO_H hdr; - size_t readSize = fread(&hdr, sizeof(CISO_H), 1, f); + size_t readSize = fileLoader->ReadAt(0, sizeof(CISO_H), 1, &hdr); if (readSize != 1 || memcmp(hdr.magic, "CISO", 4) != 0) { WARN_LOG(LOADER, "Invalid CSO!"); @@ -235,13 +179,13 @@ CISOFileBlockDevice::CISOFileBlockDevice(FILE *file) #if COMMON_LITTLE_ENDIAN index = new u32[indexSize]; - if (fread(index, sizeof(u32), indexSize, f) != indexSize) + if (fileLoader->ReadAt(sizeof(hdr), sizeof(u32), indexSize, index) != indexSize) memset(index, 0, indexSize * sizeof(u32)); #else index = new u32[indexSize]; u32_le *indexTemp = new u32_le[indexSize]; - if (fread(indexTemp, sizeof(u32), indexSize, f) != indexSize) + if (fileLoader->ReadAt(sizeof(hdr), sizeof(u32), indexSize, indexTemp) != indexSize) { memset(indexTemp, 0, indexSize * sizeof(u32_le)); } @@ -257,7 +201,6 @@ CISOFileBlockDevice::CISOFileBlockDevice(FILE *file) CISOFileBlockDevice::~CISOFileBlockDevice() { - fclose(f); delete [] index; delete [] readBuffer; delete [] zlibBuffer; @@ -285,8 +228,7 @@ bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) const int plain = idx & 0x80000000; if (plain) { - fseeko(f, compressedReadPos + compressedOffset, SEEK_SET); - int readSize = (u32)fread(outPtr, 1, GetBlockSize(), f); + int readSize = (u32)fileLoader_->ReadAt(compressedReadPos + compressedOffset, 1, GetBlockSize(), outPtr); if (readSize < GetBlockSize()) memset(outPtr + readSize, 0, GetBlockSize() - readSize); } @@ -297,8 +239,7 @@ bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) } else { - fseeko(f, compressedReadPos, SEEK_SET); - const u32 readSize = (u32)fread(readBuffer, 1, compressedReadSize, f); + const u32 readSize = (u32)fileLoader_->ReadAt(compressedReadPos, 1, compressedReadSize, readBuffer); z.zalloc = Z_NULL; z.zfree = Z_NULL; @@ -387,8 +328,7 @@ bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) { const s64 maxNeeded = totalReadEnd - frameReadPos; const size_t chunkSize = (size_t)std::min(maxNeeded, (s64)std::max(frameReadSize, CSO_READ_BUFFER_SIZE)); - fseeko(f, frameReadPos, SEEK_SET); - const u32 readSize = (u32)fread(readBuffer, 1, chunkSize, f); + const u32 readSize = (u32)fileLoader_->ReadAt(frameReadPos, 1, chunkSize, readBuffer); if (readSize < chunkSize) { memset(readBuffer + readSize, 0, chunkSize - readSize); } @@ -432,8 +372,8 @@ bool CISOFileBlockDevice::ReadBlocks(u32 minBlock, int count, u8 *outPtr) { } -NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FILE *file) - : f(file) +NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FileLoader *fileLoader) + : fileLoader_(fileLoader) { MAC_KEY mkey; CIPHER_KEY ckey; @@ -441,10 +381,8 @@ NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FILE *file) u32 tableOffset, tableSize; u32 lbaStart, lbaEnd; - fseek(f, 0x24, SEEK_SET); - fread(&psarOffset, 1, 4, f); - fseek(f, psarOffset, SEEK_SET); - size_t readSize = fread(&np_header, 1, 256, f); + fileLoader_->ReadAt(0x24, 1, 4, &psarOffset); + size_t readSize = fileLoader_->ReadAt(psarOffset, 1, 256, &np_header); if(readSize!=256){ ERROR_LOG(LOADER, "Invalid NPUMDIMG header!"); } @@ -473,12 +411,11 @@ NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FILE *file) tempBuf = new u8[blockSize]; tableOffset = *(u32*)(np_header+0x6c); // table offset - fseek(f, psarOffset+tableOffset, SEEK_SET); tableSize = numBlocks*32; table = new table_info[numBlocks]; - readSize = fread(table, 1, tableSize, f); + readSize = fileLoader_->ReadAt(psarOffset + tableOffset, 1, tableSize, table); if(readSize!=tableSize){ ERROR_LOG(LOADER, "Invalid NPUMDIMG table!"); } @@ -503,7 +440,6 @@ NPDRMDemoBlockDevice::NPDRMDemoBlockDevice(FILE *file) NPDRMDemoBlockDevice::~NPDRMDemoBlockDevice() { - fclose(f); delete [] table; delete [] tempBuf; delete [] blockBuf; @@ -535,14 +471,12 @@ bool NPDRMDemoBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) return false; } - fseek(f, psarOffset+table[block].offset, SEEK_SET); - if(table[block].sizeReadAt(psarOffset+table[block].offset, 1, table[block].size, readBuf); if(readSize != (size_t)table[block].size){ if((u32)block==(numBlocks-1)) return true; diff --git a/Core/FileSystems/BlockDevices.h b/Core/FileSystems/BlockDevices.h index 3223c2bad6..a715514a9f 100644 --- a/Core/FileSystems/BlockDevices.h +++ b/Core/FileSystems/BlockDevices.h @@ -26,6 +26,8 @@ #include "Common/CommonTypes.h" #include "Core/ELF/PBPReader.h" +class FileLoader; + class BlockDevice { public: @@ -48,14 +50,14 @@ public: class CISOFileBlockDevice : public BlockDevice { public: - CISOFileBlockDevice(FILE *file); + CISOFileBlockDevice(FileLoader *fileLoader); ~CISOFileBlockDevice(); bool ReadBlock(int blockNumber, u8 *outPtr) override; bool ReadBlocks(u32 minBlock, int count, u8 *outPtr) override; u32 GetNumBlocks() { return numBlocks;} private: - FILE *f; + FileLoader *fileLoader_; u32 *index; u8 *readBuffer; u8 *zlibBuffer; @@ -71,18 +73,15 @@ private: class FileBlockDevice : public BlockDevice { public: - FileBlockDevice(FILE *file); + FileBlockDevice(FileLoader *fileLoader); ~FileBlockDevice(); bool ReadBlock(int blockNumber, u8 *outPtr) override; bool ReadBlocks(u32 minBlock, int count, u8 *outPtr) override; - u32 GetNumBlocks() override {return (u32)(filesize / GetBlockSize());} + u32 GetNumBlocks() override {return (u32)(filesize_ / GetBlockSize());} private: -#ifdef ANDROID - int fd; -#endif - FILE *f; - u64 filesize; + FileLoader *fileLoader_; + u64 filesize_; }; @@ -99,14 +98,14 @@ struct table_info { class NPDRMDemoBlockDevice : public BlockDevice { public: - NPDRMDemoBlockDevice(FILE *file); + NPDRMDemoBlockDevice(FileLoader *fileLoader); ~NPDRMDemoBlockDevice(); bool ReadBlock(int blockNumber, u8 *outPtr) override; u32 GetNumBlocks() override {return (u32)lbaSize;} private: - FILE *f; + FileLoader *fileLoader_; u32 lbaSize; u32 psarOffset; @@ -140,4 +139,4 @@ private: }; -BlockDevice *constructBlockDevice(const char *filename); +BlockDevice *constructBlockDevice(FileLoader *fileLoader); diff --git a/Core/HLE/sceUmd.cpp b/Core/HLE/sceUmd.cpp index 1e54d38d85..b5688260ce 100644 --- a/Core/HLE/sceUmd.cpp +++ b/Core/HLE/sceUmd.cpp @@ -20,6 +20,7 @@ #include "file/file_util.h" #include "Common/ChunkFile.h" +#include "Core/Loaders.h" #include "Core/MemMap.h" #include "Core/System.h" #include "Core/CoreTiming.h" @@ -460,20 +461,28 @@ u32 sceUmdGetErrorStat() } void __UmdReplace(std::string filepath) { + // TODO: This should really go through Loaders, no? What if it's an invalid file? + // Only get system from disc0 seems have been enough. IFileSystem* currentUMD = pspFileSystem.GetSystem("disc0:"); IFileSystem* currentISOBlock = pspFileSystem.GetSystem("umd0:"); if (!currentUMD) return; + FileLoader *loadedFile = ConstructFileLoader(filepath); + IFileSystem* umd2; FileInfo info; - if (!getFileInfo(filepath.c_str(), &info)) // This shouldn't happen, but for safety. + if (!loadedFile->Exists()) { + delete loadedFile; return; + } + UpdateLoadedFile(loadedFile); + if (info.isDirectory) { umd2 = new VirtualDiscFileSystem(&pspFileSystem, filepath); } else { - auto bd = constructBlockDevice(filepath.c_str()); + auto bd = constructBlockDevice(loadedFile); if (!bd) return; umd2 = new ISOFileSystem(&pspFileSystem, bd); diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index afd7418160..adb3cad1c8 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -15,7 +15,15 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include "thread/thread.h" +#include "base/mutex.h" +#include "base/stringutil.h" +#include "base/timeutil.h" #include "file/file_util.h" +#include "net/http_client.h" +#include "net/resolve.h" +#include "net/url.h" +#include #include "Common/FileUtil.h" #include "Core/MIPS/MIPS.h" @@ -30,34 +38,698 @@ #include "Core/ELF/PBPReader.h" #include "Core/ELF/ParamSFO.h" -// TODO : improve, look in the file more -IdentifiedFileType Identify_File(std::string &filename) -{ - if (filename.size() == 0) { - ERROR_LOG(LOADER, "invalid filename %s", filename.c_str()); - return FILETYPE_ERROR; +class LocalFileLoader : public FileLoader { +public: + LocalFileLoader(const std::string &filename); + virtual ~LocalFileLoader(); + + virtual bool Exists() override; + virtual bool IsDirectory() override; + virtual s64 FileSize() override; + virtual std::string Path() const override; + + virtual void Seek(s64 absolutePos) override; + virtual size_t Read(size_t bytes, size_t count, void *data) override; + virtual size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data) override; + +private: + // First only used by Android, but we can keep it here for everyone. + int fd_; + FILE *f_; + u64 filesize_; + std::string filename_; +}; + +class HTTPFileLoader : public FileLoader { +public: + HTTPFileLoader(const std::string &filename); + virtual ~HTTPFileLoader() override; + + virtual bool Exists() override; + virtual bool IsDirectory() override; + virtual s64 FileSize() override; + virtual std::string Path() const override; + + virtual void Seek(s64 absolutePos) override; + virtual size_t Read(size_t bytes, size_t count, void *data) override { + return ReadAt(filepos_, bytes, count, data); + } + virtual size_t Read(size_t bytes, void *data) override { + return ReadAt(filepos_, bytes, data); + } + virtual size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data) override { + return ReadAt(absolutePos, bytes * count, data) / bytes; + } + virtual size_t ReadAt(s64 absolutePos, size_t bytes, void *data) override; + +private: + s64 filesize_; + s64 filepos_; + Url url_; + net::AutoInit netInit_; + http::Client client_; + std::string filename_; +}; + +class RetryingFileLoader : public FileLoader { +public: + RetryingFileLoader(FileLoader *backend); + virtual ~RetryingFileLoader() override; + + virtual bool Exists() override; + virtual bool IsDirectory() override; + virtual s64 FileSize() override; + virtual std::string Path() const override; + + virtual void Seek(s64 absolutePos) override; + virtual size_t Read(size_t bytes, size_t count, void *data) override { + return ReadAt(filepos_, bytes, count, data); + } + virtual size_t Read(size_t bytes, void *data) override { + return ReadAt(filepos_, bytes, data); + } + virtual size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data) override { + return ReadAt(absolutePos, bytes * count, data) / bytes; + } + virtual size_t ReadAt(s64 absolutePos, size_t bytes, void *data) override; + +private: + enum { + MAX_RETRIES = 3, + }; + + s64 filepos_; + FileLoader *backend_; +}; + +class CachingFileLoader : public FileLoader { +public: + CachingFileLoader(FileLoader *backend); + virtual ~CachingFileLoader() override; + + virtual bool Exists() override; + virtual bool IsDirectory() override; + virtual s64 FileSize() override; + virtual std::string Path() const override; + + virtual void Seek(s64 absolutePos) override; + virtual size_t Read(size_t bytes, size_t count, void *data) override { + return ReadAt(filepos_, bytes, count, data); + } + virtual size_t Read(size_t bytes, void *data) override { + return ReadAt(filepos_, bytes, data); + } + virtual size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data) override { + return ReadAt(absolutePos, bytes * count, data) / bytes; + } + virtual size_t ReadAt(s64 absolutePos, size_t bytes, void *data) override; + +private: + void InitCache(); + void ShutdownCache(); + size_t ReadFromCache(s64 pos, size_t bytes, void *data); + // Guaranteed to read at least one block into the cache. + void SaveIntoCache(s64 pos, size_t bytes, bool readingAhead = false); + bool MakeCacheSpaceFor(size_t blocks, bool readingAhead); + void StartReadAhead(s64 pos); + + enum { + BLOCK_SIZE = 65536, + BLOCK_SHIFT = 16, + MAX_BLOCKS_PER_READ = 16, + MAX_BLOCKS_CACHED = 4096, // 256 MB + BLOCK_READAHEAD = 4, + }; + + s64 filesize_; + s64 filepos_; + FileLoader *backend_; + int exists_; + int isDirectory_; + u64 generation_; + u64 oldestGeneration_; + size_t cacheSize_; + + struct BlockInfo { + u8 *ptr; + u64 generation; + + BlockInfo() : ptr(nullptr), generation(0) { + } + BlockInfo(u8 *p) : ptr(p), generation(0) { + } + }; + + std::map blocks_; + recursive_mutex blocksMutex_; + mutable recursive_mutex backendMutex_; + bool aheadThread_; +}; + +FileLoader *ConstructFileLoader(const std::string &filename) { + if (filename.find("http://") == 0 || filename.find("https://") == 0) + return new CachingFileLoader(new RetryingFileLoader(new HTTPFileLoader(filename))); + return new LocalFileLoader(filename); +} + +LocalFileLoader::LocalFileLoader(const std::string &filename) + : fd_(0), f_(nullptr), filesize_(0), filename_(filename) { + f_ = File::OpenCFile(filename, "rb"); + if (!f_) { + return; } +#ifdef ANDROID + // Android NDK does not support 64-bit file I/O using C streams + // so we fall back onto syscalls + fd_ = fileno(f_); + + off64_t off = lseek64(fd_, 0, SEEK_END); + filesize_ = off; + lseek64(fd_, 0, SEEK_SET); +#else + fseek(f_, 0, SEEK_END); + filesize_ = ftello(f_); + fseek(f_, 0, SEEK_SET); +#endif +} + +LocalFileLoader::~LocalFileLoader() { + fclose(f_); +} + +bool LocalFileLoader::Exists() { FileInfo info; - if (!getFileInfo(filename.c_str(), &info)) { + return getFileInfo(filename_.c_str(), &info); +} + +bool LocalFileLoader::IsDirectory() { + FileInfo info; + if (getFileInfo(filename_.c_str(), &info)) { + return info.isDirectory; + } + return false; +} + +s64 LocalFileLoader::FileSize() { + return filesize_; +} + +std::string LocalFileLoader::Path() const { + return filename_; +} + +void LocalFileLoader::Seek(s64 absolutePos) { +#ifdef ANDROID + lseek64(fd_, absolutePos, SEEK_SET); +#else + fseeko(f_, absolutePos, SEEK_SET); +#endif +} + +size_t LocalFileLoader::Read(size_t bytes, size_t count, void *data) { +#ifdef ANDROID + return read(fd_, data, bytes * count) / bytes; +#else + return fread(data, bytes, count, f_); +#endif +} + +size_t LocalFileLoader::ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data) { + Seek(absolutePos); + return Read(bytes, count, data); +} + +HTTPFileLoader::HTTPFileLoader(const std::string &filename) + : filesize_(0), filepos_(0), url_(filename), filename_(filename) { + if (!client_.Resolve(url_.Host().c_str(), 80)) { + return; + } + + // TODO: Keepalive, etc. + client_.Connect(); + int err = client_.SendRequest("HEAD", url_.Resource().c_str()); + if (err < 0) { + return; + } + + Buffer readbuf; + std::vector responseHeaders; + int code = client_.ReadResponseHeaders(&readbuf, responseHeaders); + if (code != 200) { + // Leave size at 0, invalid. + ERROR_LOG(LOADER, "HTTP request failed, got %03d for %s", code, filename.c_str()); + return; + } + + // TODO: Expire cache via ETag, etc. + bool acceptsRange = false; + for (std::string header : responseHeaders) { + if (startsWithNoCase(header, "Content-Length:")) { + size_t size_pos = header.find_first_of(' '); + if (size_pos != header.npos) { + size_pos = header.find_first_not_of(' ', size_pos); + } + if (size_pos != header.npos) { + filesize_ = atoll(&header[size_pos]); + } + } + if (startsWithNoCase(header, "Accept-Ranges:")) { + std::string lowerHeader = header; + std::transform(lowerHeader.begin(), lowerHeader.end(), lowerHeader.begin(), tolower); + // TODO: Delimited. + if (lowerHeader.find("bytes") != lowerHeader.npos) { + acceptsRange = true; + } + } + } + + client_.Disconnect(); + + if (!acceptsRange) { + WARN_LOG(LOADER, "HTTP server did not advertise support for range requests."); + } + if (filesize_ == 0) { + ERROR_LOG(LOADER, "Could not determine file size for %s", filename.c_str()); + } + + // If we didn't end up with a filesize_ (e.g. chunked response), give up. File invalid. +} + +HTTPFileLoader::~HTTPFileLoader() { +} + +bool HTTPFileLoader::Exists() { + // TODO + return url_.Valid() && filesize_ > 0; +} + +bool HTTPFileLoader::IsDirectory() { + // Only files. + return false; +} + +s64 HTTPFileLoader::FileSize() { + return filesize_; +} + +std::string HTTPFileLoader::Path() const { + return filename_; +} + +void HTTPFileLoader::Seek(s64 absolutePos) { + filepos_ = absolutePos; +} + +size_t HTTPFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data) { + s64 absoluteEnd = std::min(absolutePos + (s64)bytes, filesize_); + if (absolutePos >= filesize_ || bytes == 0) { + // Read outside of the file or no read at all, just fail immediately. + return 0; + } + + // TODO: Keepalive, etc. + client_.Connect(); + + char requestHeaders[4096]; + // Note that the Range header is *inclusive*. + snprintf(requestHeaders, sizeof(requestHeaders), + "Range: bytes=%lld-%lld\r\n", absolutePos, absoluteEnd - 1); + + int err = client_.SendRequest("GET", url_.Resource().c_str(), requestHeaders); + if (err < 0) { + return 0; + } + + Buffer readbuf; + std::vector responseHeaders; + int code = client_.ReadResponseHeaders(&readbuf, responseHeaders); + if (code != 206) { + ERROR_LOG(LOADER, "HTTP server did not respond with range, received code=%03d", code); + return 0; + } + + // TODO: Expire cache via ETag, etc. + // We don't support multipart/byteranges responses. + bool supportedResponse = false; + for (std::string header : responseHeaders) { + if (startsWithNoCase(header, "Content-Range:")) { + // TODO: More correctness. Whitespace can be missing or different. + s64 first = -1, last = -1, total = -1; + std::string lowerHeader = header; + std::transform(lowerHeader.begin(), lowerHeader.end(), lowerHeader.begin(), tolower); + if (sscanf(lowerHeader.c_str(), "content-range: bytes %lld-%lld/%lld", &first, &last, &total) >= 2) { + if (first == absolutePos && last == absoluteEnd - 1) { + supportedResponse = true; + } else { + ERROR_LOG(LOADER, "Unexpected HTTP range: got %lld-%lld, wanted %lld-%lld.", first, last, absolutePos, absoluteEnd - 1); + } + } else { + ERROR_LOG(LOADER, "Unexpected HTTP range response: %s", header.c_str()); + } + } + } + + // TODO: Would be nice to read directly. + Buffer output; + client_.ReadResponseEntity(&readbuf, responseHeaders, &output); + + client_.Disconnect(); + + if (!supportedResponse) { + ERROR_LOG(LOADER, "HTTP server did not respond with the range we wanted."); + return 0; + } + + size_t readBytes = output.size(); + output.Take(readBytes, (char *)data); + filepos_ = absolutePos + readBytes; + return readBytes; +} + +// Takes ownership of backend. +CachingFileLoader::CachingFileLoader(FileLoader *backend) + : filesize_(0), filepos_(0), backend_(backend), exists_(-1), isDirectory_(-1), aheadThread_(false) { + filesize_ = backend->FileSize(); + if (filesize_ > 0) { + InitCache(); + } +} + +CachingFileLoader::~CachingFileLoader() { + if (filesize_ > 0) { + ShutdownCache(); + } + // Takes ownership. + delete backend_; +} + +bool CachingFileLoader::Exists() { + if (exists_ == -1) { + lock_guard guard(backendMutex_); + exists_ = backend_->Exists() ? 1 : 0; + } + return exists_ == 1; +} + +bool CachingFileLoader::IsDirectory() { + if (isDirectory_ == -1) { + lock_guard guard(backendMutex_); + isDirectory_ = backend_->IsDirectory() ? 1 : 0; + } + return isDirectory_ == 1; +} + +s64 CachingFileLoader::FileSize() { + return filesize_; +} + +std::string CachingFileLoader::Path() const { + lock_guard guard(backendMutex_); + return backend_->Path(); +} + +void CachingFileLoader::Seek(s64 absolutePos) { + filepos_ = absolutePos; +} + +size_t CachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data) { + size_t readSize = ReadFromCache(absolutePos, bytes, data); + // While in case the cache size is too small for the entire read. + while (readSize < bytes) { + SaveIntoCache(absolutePos + readSize, bytes - readSize); + readSize += ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize); + } + + StartReadAhead(absolutePos + readSize); + + filepos_ = absolutePos + readSize; + return readSize; +} + +void CachingFileLoader::InitCache() { + cacheSize_ = 0; + oldestGeneration_ = 0; + generation_ = 0; +} + +void CachingFileLoader::ShutdownCache() { + // TODO: Maybe add some hint that deletion is coming soon? + // We can't delete while the thread is running, so have to wait. + // This should only happen from the menu. + while (aheadThread_) { + sleep_ms(1); + } + + lock_guard guard(blocksMutex_); + for (auto block : blocks_) { + delete [] block.second.ptr; + } + blocks_.clear(); + cacheSize_ = 0; +} + +size_t CachingFileLoader::ReadFromCache(s64 pos, size_t bytes, void *data) { + s64 cacheStartPos = pos >> BLOCK_SHIFT; + s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT; + // TODO: Smarter. + size_t readSize = 0; + size_t offset = (size_t)(pos - (cacheStartPos << BLOCK_SHIFT)); + u8 *p = (u8 *)data; + + lock_guard guard(blocksMutex_); + for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) { + auto block = blocks_.find(i); + if (block == blocks_.end()) { + return readSize; + } + block->second.generation = generation_; + + size_t toRead = std::min(bytes - readSize, (size_t)BLOCK_SIZE - offset); + memcpy(p + readSize, block->second.ptr + offset, toRead); + readSize += toRead; + + // Don't need an offset after the first read. + offset = 0; + } + return readSize; +} + +void CachingFileLoader::SaveIntoCache(s64 pos, size_t bytes, bool readingAhead) { + s64 cacheStartPos = pos >> BLOCK_SHIFT; + s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT; + + lock_guard guard(blocksMutex_); + size_t blocksToRead = 0; + for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) { + auto block = blocks_.find(i); + if (block != blocks_.end()) { + break; + } + ++blocksToRead; + if (blocksToRead >= MAX_BLOCKS_PER_READ) { + break; + } + } + + if (!MakeCacheSpaceFor(blocksToRead, readingAhead) || blocksToRead == 0) { + return; + } + + if (blocksToRead == 1) { + blocksMutex_.unlock(); + + u8 *buf = new u8[BLOCK_SIZE]; + backendMutex_.lock(); + backend_->ReadAt(cacheStartPos << BLOCK_SHIFT, BLOCK_SIZE, buf); + backendMutex_.unlock(); + + blocksMutex_.lock(); + // While blocksMutex_ was unlocked, another thread may have read. + // If so, free the one we just read. + if (blocks_.find(cacheStartPos) == blocks_.end()) { + blocks_[cacheStartPos] = BlockInfo(buf); + } else { + delete [] buf; + } + } else { + blocksMutex_.unlock(); + + u8 *wholeRead = new u8[blocksToRead << BLOCK_SHIFT]; + backendMutex_.lock(); + backend_->ReadAt(cacheStartPos << BLOCK_SHIFT, blocksToRead << BLOCK_SHIFT, wholeRead); + backendMutex_.unlock(); + + blocksMutex_.lock(); + for (size_t i = 0; i < blocksToRead; ++i) { + if (blocks_.find(cacheStartPos + i) != blocks_.end()) { + // Written while we were busy, just skip it. Keep the existing block. + continue; + } + u8 *buf = new u8[BLOCK_SIZE]; + memcpy(buf, wholeRead + (i << BLOCK_SHIFT), BLOCK_SIZE); + blocks_[cacheStartPos + i] = BlockInfo(buf); + } + delete wholeRead; + } + + cacheSize_ += blocksToRead; + ++generation_; +} + +bool CachingFileLoader::MakeCacheSpaceFor(size_t blocks, bool readingAhead) { + size_t goal = MAX_BLOCKS_CACHED - blocks; + + if (readingAhead && cacheSize_ > goal) { + return false; + } + + lock_guard guard(blocksMutex_); + while (cacheSize_ > goal) { + u64 minGeneration = generation_; + + // We increment the iterator inside because we delete things inside. + for (auto it = blocks_.begin(); it != blocks_.end(); ) { + // Check for the minimum seen generation. + // TODO: Do this smarter? + if (it->second.generation != 0 && it->second.generation < minGeneration) { + minGeneration = it->second.generation; + } + + // 0 means it was never used yet or was the first read (e.g. block descriptor.) + if (it->second.generation == oldestGeneration_ || it->second.generation == 0) { + s64 pos = it->first; + delete it->second.ptr; + blocks_.erase(it); + --cacheSize_; + + // Our iterator is invalid now. Keep going? + if (cacheSize_ > goal) { + // This finds the one at that position. + it = blocks_.lower_bound(pos); + } else { + break; + } + } else { + ++it; + } + } + + // If we didn't find any, update to the lowest we did find. + oldestGeneration_ = minGeneration; + } + + return true; +} + +void CachingFileLoader::StartReadAhead(s64 pos) { + lock_guard guard(blocksMutex_); + if (aheadThread_) { + // Already going. + return; + } + if (cacheSize_ + BLOCK_READAHEAD > MAX_BLOCKS_CACHED) { + // Not enough space to readahead. + return; + } + + aheadThread_ = true; + std::thread th([this, pos] { + lock_guard guard(blocksMutex_); + s64 cacheStartPos = pos >> BLOCK_SHIFT; + s64 cacheEndPos = cacheStartPos + BLOCK_READAHEAD - 1; + + for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) { + auto block = blocks_.find(i); + if (block == blocks_.end()) { + blocksMutex_.unlock(); + SaveIntoCache(i << BLOCK_SHIFT, BLOCK_SIZE * BLOCK_READAHEAD, true); + break; + } + } + + aheadThread_ = false; + }); + th.detach(); +} + +// Takes ownership of backend. +RetryingFileLoader::RetryingFileLoader(FileLoader *backend) + : filepos_(0), backend_(backend) { +} + +RetryingFileLoader::~RetryingFileLoader() { + // Takes ownership. + delete backend_; +} + +bool RetryingFileLoader::Exists() { + if (!backend_->Exists()) { + // Retry once, immediately. + return backend_->Exists(); + } + return true; +} + +bool RetryingFileLoader::IsDirectory() { + // Can't tell if it's an error either way. + return backend_->IsDirectory(); +} + +s64 RetryingFileLoader::FileSize() { + s64 filesize = backend_->FileSize(); + if (filesize == 0) { + return backend_->FileSize(); + } + return filesize; +} + +std::string RetryingFileLoader::Path() const { + return backend_->Path(); +} + +void RetryingFileLoader::Seek(s64 absolutePos) { + filepos_ = absolutePos; +} + +size_t RetryingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data) { + size_t readSize = backend_->ReadAt(absolutePos, bytes, data); + + int retries = 0; + while (readSize < bytes && retries < MAX_RETRIES) { + u8 *p = (u8 *)data; + readSize += backend_->ReadAt(absolutePos + readSize, bytes - readSize, p + readSize); + ++retries; + } + + filepos_ = absolutePos + readSize; + return readSize; +} + +// TODO : improve, look in the file more +IdentifiedFileType Identify_File(FileLoader *fileLoader) +{ + if (fileLoader == nullptr) { + ERROR_LOG(LOADER, "Invalid fileLoader"); + return FILETYPE_ERROR; + } + if (fileLoader->Path().size() == 0) { + ERROR_LOG(LOADER, "Invalid filename %s", fileLoader->Path().c_str()); return FILETYPE_ERROR; } - std::string extension = filename.size() >= 5 ? filename.substr(filename.size() - 4) : ""; + if (!fileLoader->Exists()) { + return FILETYPE_ERROR; + } + + std::string extension = fileLoader->Extension(); if (!strcasecmp(extension.c_str(),".iso")) { // may be a psx iso, they have 2352 byte sectors. You never know what some people try to open - if ((info.size % 2352) == 0) + if ((fileLoader->FileSize() % 2352) == 0) { - FILE *f = File::OpenCFile(filename.c_str(), "rb"); - if (!f) { - // File does not exists - return FILETYPE_ERROR; - } - unsigned char sync[12]; - fread(sync,1,12,f); - fclose(f); + fileLoader->ReadAt(0, 12, sync); // each sector in a mode2 image starts with these 12 bytes if (memcmp(sync,"\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00",12) == 0) @@ -76,7 +748,8 @@ IdentifiedFileType Identify_File(std::string &filename) // First, check if it's a directory with an EBOOT.PBP in it. - if (info.isDirectory) { + if (fileLoader->IsDirectory()) { + std::string filename = fileLoader->Path(); if (filename.size() > 4) { FileInfo ebootInfo; // Check for existence of EBOOT.PBP, as required for "Directory games". @@ -97,17 +770,10 @@ IdentifiedFileType Identify_File(std::string &filename) return FILETYPE_NORMAL_DIRECTORY; } - FILE *f = File::OpenCFile(filename.c_str(), "rb"); - if (!f) { - // File does not exists - return FILETYPE_ERROR; - } - u32_le id; - size_t readSize = fread(&id, 4, 1, f); + size_t readSize = fileLoader->ReadAt(0, 4, 1, &id); if (readSize != 1) { - fclose(f); return FILETYPE_ERROR; } @@ -115,10 +781,8 @@ IdentifiedFileType Identify_File(std::string &filename) u32 _id = id; switch (_id) { case 'PBP\x00': - fseek(f, 0x24, SEEK_SET); - fread(&psar_offset, 4, 1, f); - fseek(f, psar_offset, SEEK_SET); - fread(&psar_id, 4, 1, f); + fileLoader->ReadAt(0x24, 4, 1, &psar_offset); + fileLoader->ReadAt(psar_offset, 4, 1, &psar_id); break; case '!raR': return FILETYPE_ARCHIVE_RAR; @@ -128,9 +792,8 @@ IdentifiedFileType Identify_File(std::string &filename) return FILETYPE_ARCHIVE_ZIP; } - fclose(f); - if (id == 'FLE\x7F') { + std::string filename = fileLoader->Path(); // There are a few elfs misnamed as pbp (like Trig Wars), accept that. if (!strcasecmp(extension.c_str(), ".plf") || strstr(filename.c_str(),"BOOT.BIN") || !strcasecmp(extension.c_str(), ".elf") || !strcasecmp(extension.c_str(), ".prx") || @@ -142,6 +805,8 @@ IdentifiedFileType Identify_File(std::string &filename) else if (id == 'PBP\x00') { // Do this PS1 eboot check FIRST before checking other eboot types. // It seems like some are malformed and slip through the PSAR check below. + // TODO: Change PBPReader to read FileLoader objects? + std::string filename = fileLoader->Path(); PBPReader pbp(filename.c_str()); if (pbp.IsValid()) { if (!pbp.IsELF()) { @@ -199,23 +864,26 @@ IdentifiedFileType Identify_File(std::string &filename) return FILETYPE_UNKNOWN; } -bool LoadFile(std::string &filename, std::string *error_string) { +bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) { + FileLoader *&fileLoader = *fileLoaderPtr; // Note that this can modify filename! - switch (Identify_File(filename)) { + switch (Identify_File(fileLoader)) { case FILETYPE_PSP_PBP_DIRECTORY: { + std::string filename = fileLoader->Path(); std::string ebootFilename = filename + "/EBOOT.PBP"; - FileInfo fileInfo; - getFileInfo((filename + "/EBOOT.PBP").c_str(), &fileInfo); - if (fileInfo.exists) { + // Switch fileLoader to the EBOOT. + delete fileLoader; + fileLoader = ConstructFileLoader(ebootFilename); + + if (fileLoader->Exists()) { INFO_LOG(LOADER, "File is a PBP in a directory!"); - std::string ebootPath = filename + "/EBOOT.PBP"; - IdentifiedFileType ebootType = Identify_File(ebootPath); + IdentifiedFileType ebootType = Identify_File(fileLoader); if (ebootType == FILETYPE_PSP_ISO_NP) { - InitMemoryForGameISO(ebootPath); + InitMemoryForGameISO(fileLoader); pspFileSystem.SetStartingDirectory("disc0:/PSP_GAME/USRDIR"); - return Load_PSP_ISO(filename.c_str(), error_string); + return Load_PSP_ISO(fileLoader, error_string); } else if (ebootType == FILETYPE_PSP_PS1_PBP) { *error_string = "PS1 EBOOTs are not supported by PPSSPP."; @@ -225,7 +893,7 @@ bool LoadFile(std::string &filename, std::string *error_string) { size_t pos = path.find("/PSP/GAME/"); if (pos != std::string::npos) pspFileSystem.SetStartingDirectory("ms0:" + path.substr(pos)); - return Load_PSP_ELF_PBP(ebootFilename.c_str(), error_string); + return Load_PSP_ELF_PBP(fileLoader, error_string); } else { *error_string = "No EBOOT.PBP, misidentified game"; return false; @@ -236,14 +904,14 @@ bool LoadFile(std::string &filename, std::string *error_string) { case FILETYPE_PSP_ELF: { INFO_LOG(LOADER,"File is an ELF or loose PBP!"); - return Load_PSP_ELF_PBP(filename.c_str(), error_string); + return Load_PSP_ELF_PBP(fileLoader, error_string); } case FILETYPE_PSP_ISO: case FILETYPE_PSP_ISO_NP: case FILETYPE_PSP_DISC_DIRECTORY: // behaves the same as the mounting is already done by now pspFileSystem.SetStartingDirectory("disc0:/PSP_GAME/USRDIR"); - return Load_PSP_ISO(filename.c_str(), error_string); + return Load_PSP_ISO(fileLoader, error_string); case FILETYPE_PSP_PS1_PBP: *error_string = "PS1 EBOOTs are not supported by PPSSPP."; diff --git a/Core/Loaders.h b/Core/Loaders.h index ddf1ee5b43..f3b56668f3 100644 --- a/Core/Loaders.h +++ b/Core/Loaders.h @@ -46,9 +46,40 @@ enum IdentifiedFileType { FILETYPE_UNKNOWN }; +class FileLoader { +public: + virtual ~FileLoader() {} + + virtual bool Exists() = 0; + virtual bool IsDirectory() = 0; + virtual s64 FileSize() = 0; + virtual std::string Path() const = 0; + virtual std::string Extension() { + const std::string filename = Path(); + size_t pos = filename.find_last_of('.'); + if (pos == filename.npos) { + return ""; + } else { + return filename.substr(pos); + } + } + + virtual void Seek(s64 absolutePos) = 0; + virtual size_t Read(size_t bytes, size_t count, void *data) = 0; + virtual size_t Read(size_t bytes, void *data) { + return Read(1, bytes, data); + } + virtual size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data) = 0; + virtual size_t ReadAt(s64 absolutePos, size_t bytes, void *data) { + return ReadAt(absolutePos, 1, bytes, data); + } +}; + +FileLoader *ConstructFileLoader(const std::string &filename); + // This can modify the string, for example for stripping off the "/EBOOT.PBP" // for a FILETYPE_PSP_PBP_DIRECTORY. -IdentifiedFileType Identify_File(std::string &str); +IdentifiedFileType Identify_File(FileLoader *fileLoader); // Can modify the string filename, as it calls IdentifyFile above. -bool LoadFile(std::string &filename, std::string *error_string); +bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string); diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 5a14cd7173..2831d4daaf 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -32,6 +32,7 @@ #include "FileSystems/MetaFileSystem.h" #include "FileSystems/VirtualDiscFileSystem.h" +#include "Core/Loaders.h" #include "Core/MemMap.h" #include "Core/HDRemaster.h" @@ -53,21 +54,21 @@ // We gather the game info before actually loading/booting the ISO // to determine if the emulator should enable extra memory and // double-sized texture coordinates. -void InitMemoryForGameISO(std::string fileToStart) { +void InitMemoryForGameISO(FileLoader *fileLoader) { IFileSystem* umd2; - // check if it's a disc directory - FileInfo info; - if (!getFileInfo(fileToStart.c_str(), &info)) return; + if (!fileLoader->Exists()) { + return; + } bool actualIso = false; - if (info.isDirectory) + if (fileLoader->IsDirectory()) { - umd2 = new VirtualDiscFileSystem(&pspFileSystem, fileToStart); + umd2 = new VirtualDiscFileSystem(&pspFileSystem, fileLoader->Path()); } else { - auto bd = constructBlockDevice(fileToStart.c_str()); + auto bd = constructBlockDevice(fileLoader); // Can't init anything without a block device... if (!bd) return; @@ -150,7 +151,7 @@ static const char *altBootNames[] = { "disc0:/PSP_GAME/SYSDIR/ss.RAW", }; -bool Load_PSP_ISO(const char *filename, std::string *error_string) +bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { // Mounting stuff relocated to InitMemoryForGameISO due to HD Remaster restructuring of code. @@ -223,12 +224,12 @@ static std::string NormalizePath(const std::string &path) return buf; } -bool Load_PSP_ELF_PBP(const char *filename, std::string *error_string) +bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { // This is really just for headless, might need tweaking later. - if (!PSP_CoreParameter().mountIso.empty()) + if (PSP_CoreParameter().mountIsoLoader != nullptr) { - auto bd = constructBlockDevice(PSP_CoreParameter().mountIso.c_str()); + auto bd = constructBlockDevice(PSP_CoreParameter().mountIsoLoader); if (bd != NULL) { ISOFileSystem *umd2 = new ISOFileSystem(&pspFileSystem, bd); @@ -238,7 +239,7 @@ bool Load_PSP_ELF_PBP(const char *filename, std::string *error_string) } } - std::string full_path = filename; + std::string full_path = fileLoader->Path(); std::string path, file, extension; SplitPath(ReplaceAll(full_path, "\\", "/"), &path, &file, &extension); #ifdef _WIN32 diff --git a/Core/PSPLoaders.h b/Core/PSPLoaders.h index 4faa7287d9..3dce913c08 100644 --- a/Core/PSPLoaders.h +++ b/Core/PSPLoaders.h @@ -19,6 +19,8 @@ #include -bool Load_PSP_ISO(const char *filename, std::string *error_string); -bool Load_PSP_ELF_PBP(const char *filename, std::string *error_string); -void InitMemoryForGameISO(std::string fileToStart); +class FileLoader; + +bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string); +bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string); +void InitMemoryForGameISO(FileLoader *fileLoader); diff --git a/Core/System.cpp b/Core/System.cpp index 7a87f7b61d..ece502bad4 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -72,6 +72,7 @@ MetaFileSystem pspFileSystem; ParamSFOData g_paramSFO; static GlobalUIState globalUIState; static CoreParameter coreParameter; +static FileLoader *loadedFile; static PSPMixer *mixer; static std::thread *cpuThread = NULL; static std::thread::id cpuThreadID; @@ -181,7 +182,13 @@ void CPU_Init() { Memory::g_PSPModel = g_Config.iPSPModel; std::string filename = coreParameter.fileToStart; - IdentifiedFileType type = Identify_File(filename); + loadedFile = ConstructFileLoader(filename); + IdentifiedFileType type = Identify_File(loadedFile); + + // TODO: Put this somewhere better? + if (coreParameter.mountIso != "") { + coreParameter.mountIsoLoader = ConstructFileLoader(coreParameter.mountIso); + } MIPSAnalyst::Reset(); Replacement_Init(); @@ -190,7 +197,7 @@ void CPU_Init() { case FILETYPE_PSP_ISO: case FILETYPE_PSP_ISO_NP: case FILETYPE_PSP_DISC_DIRECTORY: - InitMemoryForGameISO(filename); + InitMemoryForGameISO(loadedFile); break; default: break; @@ -213,7 +220,7 @@ void CPU_Init() { // TODO: Check Game INI here for settings, patches and cheats, and modify coreParameter accordingly // Why did we check for CORE_POWERDOWN here? - if (!LoadFile(filename, &coreParameter.errorString)) { + if (!LoadFile(&loadedFile, &coreParameter.errorString)) { CPU_Shutdown(); coreParameter.fileToStart = ""; CPU_SetState(CPU_THREAD_NOT_RUNNING); @@ -245,6 +252,18 @@ void CPU_Shutdown() { pspFileSystem.Shutdown(); mipsr4k.Shutdown(); Memory::Shutdown(); + + delete loadedFile; + loadedFile = nullptr; + + delete coreParameter.mountIsoLoader; + coreParameter.mountIsoLoader = nullptr; +} + +// TODO: Maybe loadedFile doesn't even belong here... +void UpdateLoadedFile(FileLoader *fileLoader) { + delete loadedFile; + loadedFile = fileLoader; } void CPU_RunLoop() { diff --git a/Core/System.h b/Core/System.h index 336ac59ff6..651aac0733 100644 --- a/Core/System.h +++ b/Core/System.h @@ -63,6 +63,8 @@ void Audio_Init(); bool IsOnSeparateCPUThread(); bool IsAudioInitialised(); +void UpdateLoadedFile(FileLoader *fileLoader); + std::string GetSysDirectory(PSPDirectories directoryType); #ifdef _WIN32 void InitSysDirectories(); diff --git a/UI/GameInfoCache.cpp b/UI/GameInfoCache.cpp index 985950b5ff..a8d18e5cc4 100644 --- a/UI/GameInfoCache.cpp +++ b/UI/GameInfoCache.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include "base/logging.h" @@ -246,8 +247,9 @@ public: return; std::string filename = gamePath_; + std::unique_ptr fileLoader(ConstructFileLoader(filename)); info_->path = gamePath_; - info_->fileType = Identify_File(filename); + info_->fileType = Identify_File(fileLoader.get()); // Fallback title info_->title = getFilename(info_->path); @@ -376,7 +378,7 @@ handleELF: // Let's assume it's an ISO. // TODO: This will currently read in the whole directory tree. Not really necessary for just a // few files. - BlockDevice *bd = constructBlockDevice(gamePath_.c_str()); + BlockDevice *bd = constructBlockDevice(fileLoader.get()); if (!bd) return; // nothing to do here.. ISOFileSystem umd(&handles, bd, "/PSP_GAME"); diff --git a/native b/native index 92be7b0b1b..1150598ce4 160000 --- a/native +++ b/native @@ -1 +1 @@ -Subproject commit 92be7b0b1bb73b01b4322d787b9c54d03ed7c270 +Subproject commit 1150598ce463c99fbb08ba4177a7937692fe9bc0