From 5716cbd41d7fcde16fb2052a00fb61f4efc8ee73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Thu, 1 Jan 2026 00:24:01 -0500 Subject: [PATCH] Use the libretro VFS interface in libretro builds --- CMakeLists.txt | 13 ++ Common/Data/Format/PNGLoad.cpp | 54 ++++-- Common/File/FileUtil.cpp | 216 ++++++++++++++++----- Common/File/FileUtil.h | 34 +++- Common/File/Path.h | 4 + Common/File/VFS/DirectoryReader.cpp | 8 +- Common/File/VFS/ZipFileReader.cpp | 168 ++++++++++++---- Common/File/VFS/ZipFileReader.h | 27 ++- Common/Log/LogManager.cpp | 4 + Common/Render/AtlasGen.cpp | 3 +- Common/UI/IconCache.cpp | 3 +- Common/UI/IconCache.h | 2 + Core/Debugger/SymbolMap.cpp | 89 +++++++-- Core/FileLoaders/DiskCachingFileLoader.cpp | 44 +---- Core/FileLoaders/LocalFileLoader.cpp | 28 ++- Core/FileLoaders/LocalFileLoader.h | 11 +- Core/FileSystems/DirectoryFileSystem.cpp | 70 ++++++- Core/FileSystems/DirectoryFileSystem.h | 6 +- Core/Loaders.cpp | 30 +-- Core/Loaders.h | 12 +- Core/MIPS/MIPSTracer.cpp | 4 +- Core/WebServer.cpp | 2 +- GPU/Vulkan/PipelineManagerVulkan.cpp | 4 +- GPU/Vulkan/PipelineManagerVulkan.h | 2 + GPU/Vulkan/ShaderManagerVulkan.cpp | 4 +- GPU/Vulkan/ShaderManagerVulkan.h | 2 + libretro/CMakeLists.txt | 13 ++ libretro/LibretroFileStreamTransforms.h | 6 + libretro/LibretroVulkanContext.cpp | 2 + libretro/libretro.cpp | 11 +- 30 files changed, 624 insertions(+), 252 deletions(-) create mode 100644 libretro/LibretroFileStreamTransforms.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 007b6c846f..e0f2722e7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,6 +256,7 @@ if(LIBRETRO) if(NOT MSVC) add_compile_options(-fPIC) endif() + add_compile_definitions(HAVE_LIBRETRO_VFS) endif() if(ANDROID) @@ -944,6 +945,10 @@ add_library(Common STATIC Common/TimeUtil.h ) +if(LIBRETRO) + target_include_directories(Common PRIVATE libretro/libretro-common/include) +endif() + include_directories(Common) setup_target_project(Common Common) @@ -1685,6 +1690,10 @@ add_library(native STATIC ext/jpge/jpge.h ) +if(LIBRETRO) + target_include_directories(native PRIVATE libretro/libretro-common/include) +endif() + if(LINUX AND NOT ANDROID) set(RT_LIB rt) endif() @@ -2509,6 +2518,10 @@ add_library(${CoreLibName} ${CoreLinkType} ${CMAKE_CURRENT_BINARY_DIR}/git-version.cpp ) +if(LIBRETRO) + target_include_directories(${CoreLibName} PRIVATE libretro/libretro-common/include) +endif() + if(ANDROID) set(CoreExtraLibs ${CoreExtraLibs} android) if(X86_64) diff --git a/Common/Data/Format/PNGLoad.cpp b/Common/Data/Format/PNGLoad.cpp index 51ebd5d9ac..fb9136d514 100644 --- a/Common/Data/Format/PNGLoad.cpp +++ b/Common/Data/Format/PNGLoad.cpp @@ -134,12 +134,7 @@ bool PNGHeaderPeek::IsValidPNGHeader() const { } bool pngSave(const Path &filename, const void *buffer, int w, int h, int bytesPerPixel) { - png_image png{}; - png.version = PNG_IMAGE_VERSION; - png.format = bytesPerPixel == 3 ? PNG_FORMAT_RGB : PNG_FORMAT_RGBA; - png.width = w; - png.height = h; - const int row_stride = w * bytesPerPixel; + png_bytepp row_ptrs = nullptr; FILE *fp = File::OpenCFile(filename, "wb"); if (!fp) { @@ -147,16 +142,28 @@ bool pngSave(const Path &filename, const void *buffer, int w, int h, int bytesPe return false; } - int result = png_image_write_to_stdio(&png, fp, 0, buffer, row_stride, nullptr); - - if (png.warning_or_error >= 2) { - ERROR_LOG(Log::IO, "Saving image to PNG produced errors."); + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, pngErrorHandler, pngWarningHandler); + if (png_ptr == nullptr) { + fclose(fp); + ERROR_LOG(Log::IO, "PNG encode failed."); + return false; } - png_image_free(&png); - fclose(fp); + png_infop info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == nullptr) { + png_destroy_write_struct(&png_ptr, nullptr); + fclose(fp); + ERROR_LOG(Log::IO, "PNG encode failed."); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + if (row_ptrs != nullptr) { + png_free(png_ptr, row_ptrs); + } + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); - if (!result) { // Should we even do this? File::Delete(filename); @@ -164,5 +171,26 @@ bool pngSave(const Path &filename, const void *buffer, int w, int h, int bytesPe return false; } + png_set_write_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data, png_size_t size) { + if (fwrite(data, 1, size, (FILE *)png_get_io_ptr(png_ptr)) < size) { + png_error(png_ptr, "Failed to write to file."); + } + }, [](png_structp png_ptr) { + fflush((FILE *)png_get_io_ptr(png_ptr)); + }); + + png_set_IHDR(png_ptr, info_ptr, w, h, 8, bytesPerPixel == 3 ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + row_ptrs = (png_bytepp)png_malloc(png_ptr, (png_alloc_size_t)h * (png_alloc_size_t)sizeof(png_bytep)); + for (png_alloc_size_t i = 0; i < h; ++i) { + row_ptrs[i] = (png_bytep)buffer + (png_alloc_size_t)w * (png_alloc_size_t)bytesPerPixel * i; + } + png_set_rows(png_ptr, info_ptr, row_ptrs); + + png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); + + png_free(png_ptr, row_ptrs); + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); return true; } diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 7a7c748008..e9c71f289c 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -77,6 +77,10 @@ #endif // !PPSSPP_PLATFORM(IOS) #endif // __APPLE__ +#ifdef HAVE_LIBRETRO_VFS +#include +#endif + #include "Common/Data/Encoding/Utf8.h" #include @@ -105,11 +109,27 @@ constexpr bool LOG_IO = false; #define DIR_SEP_CHRS "/" #endif +#ifdef HAVE_LIBRETRO_VFS +retro_vfs_mkdir_t LibretroMkdirCallback = nullptr; + +static int LibretroMkdir(const char *path) noexcept { + return LibretroMkdirCallback != nullptr ? LibretroMkdirCallback(path) : retro_vfs_mkdir_impl(path); +} +#endif + // This namespace has various generic functions related to files and paths. // The code still needs a ton of cleanup. // REMEMBER: strdup considered harmful! namespace File { +#ifdef HAVE_LIBRETRO_VFS +void InitLibretroVFS(const struct retro_vfs_interface_info *vfs) noexcept { + filestream_vfs_init(vfs); + path_vfs_init(vfs); + LibretroMkdirCallback = vfs->required_interface_version >= 3 ? vfs->iface->mkdir : nullptr; +} +#endif + FILE *OpenCFile(const Path &path, const char *mode) { if (LOG_IO) { INFO_LOG(Log::IO, "OpenCFile %s, %s", path.c_str(), mode); @@ -120,6 +140,7 @@ FILE *OpenCFile(const Path &path, const char *mode) { switch (path.Type()) { case PathType::NATIVE: break; +#ifndef HAVE_LIBRETRO_VFS case PathType::CONTENT_URI: // We're gonna need some error codes.. if (!strcmp(mode, "r") || !strcmp(mode, "rb") || !strcmp(mode, "rt")) { @@ -167,7 +188,7 @@ FILE *OpenCFile(const Path &path, const char *mode) { FILE *f = fdopen(descriptor, fmode); if (f && (!strcmp(mode, "at") || !strcmp(mode, "a"))) { // Append mode - not sure we got a "true" append mode, so seek to the end. - fseek(f, 0, SEEK_END); + Fseek(f, 0, SEEK_END); } return f; } else { @@ -175,12 +196,29 @@ FILE *OpenCFile(const Path &path, const char *mode) { return nullptr; } break; +#endif default: ERROR_LOG(Log::IO, "OpenCFile(%s): PathType not yet supported", path.c_str()); return nullptr; } -#if defined(_WIN32) && defined(UNICODE) +#ifdef HAVE_LIBRETRO_VFS + if (!strcmp(mode, "r") || !strcmp(mode, "rb") || !strcmp(mode, "rt")) { + INFO_LOG(Log::IO, "OpenCFile(%s): Opening content file for read.", path.c_str()); + return filestream_open(path.c_str(), RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + } else if (!strcmp(mode, "w") || !strcmp(mode, "wb") || !strcmp(mode, "wt") || !strcmp(mode, "at") || !strcmp(mode, "a")) { + INFO_LOG(Log::IO, "OpenCFile(%s): Opening content file for write.", path.c_str()); + bool append = !strcmp(mode, "at") || !strcmp(mode, "a"); + FILE *f = filestream_open(path.c_str(), append && Exists(path) ? RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); + if (f != nullptr && append) { + Fseek(f, 0, SEEK_END); + } + return f; + } else { + ERROR_LOG(Log::IO, "OpenCFile(%s): Mode not yet supported: %s", path.c_str(), mode); + return nullptr; + } +#elif defined(_WIN32) && defined(UNICODE) #if PPSSPP_PLATFORM(UWP) && !defined(__LIBRETRO__) // We shouldn't use _wfopen here, // this function is not allowed to read outside Local and Installation folders @@ -438,7 +476,9 @@ bool Exists(const Path &path) { return Android_FileExists(path.c_str()); } -#if defined(_WIN32) +#ifdef HAVE_LIBRETRO_VFS + return path_is_valid(path.c_str()); +#elif defined(_WIN32) // Make sure Windows will no longer handle critical errors, which means no annoying "No disk" dialog #if !PPSSPP_PLATFORM(UWP) @@ -488,7 +528,9 @@ bool IsDirectory(const Path &path) { return false; } -#if defined(_WIN32) +#ifdef HAVE_LIBRETRO_VFS + return path_is_directory(path.c_str()); +#elif defined(_WIN32) WIN32_FILE_ATTRIBUTE_DATA data{}; #if PPSSPP_PLATFORM(UWP) if (!GetFileAttributesExFromAppW(path.ToWString().c_str(), GetFileExInfoStandard, &data) || data.dwFileAttributes == INVALID_FILE_ATTRIBUTES) { @@ -543,7 +585,12 @@ bool Delete(const Path &filename) { return false; } -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + if (filestream_delete(filename.c_str()) != 0) { + WARN_LOG(Log::IO, "Delete: DeleteFile failed on %s", filename.c_str()); + return false; + } +#elif defined(_WIN32) #if PPSSPP_PLATFORM(UWP) if (!DeleteFileFromAppW(filename.ToWString().c_str())) { WARN_LOG(Log::IO, "Delete: DeleteFile failed on %s: %s", filename.c_str(), GetLastErrorMsg().c_str()); @@ -603,7 +650,17 @@ bool CreateDir(const Path &path) { } DEBUG_LOG(Log::IO, "CreateDir('%s')", path.c_str()); -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + switch (LibretroMkdir(path.ToString().c_str())) { + case -2: + DEBUG_LOG(Log::IO, "CreateDir: mkdir failed on %s: already exists", path.c_str()); + case 0: + return true; + default: + ERROR_LOG(Log::IO, "CreateDir: mkdir failed on %s", path.c_str()); + return false; + } +#elif defined(_WIN32) #if PPSSPP_PLATFORM(UWP) if (CreateDirectoryFromAppW(path.ToWString().c_str(), NULL)) return true; @@ -715,6 +772,13 @@ bool Rename(const Path &srcFilename, const Path &destFilename) { INFO_LOG(Log::IO, "Rename: %s --> %s", srcFilename.c_str(), destFilename.c_str()); +#ifdef HAVE_LIBRETRO_VFS + if (filestream_rename(srcFilename.c_str(), destFilename.c_str()) == 0) + return true; + ERROR_LOG(Log::IO, "Rename: failed %s --> %s", + srcFilename.c_str(), destFilename.c_str()); + return false; +#else #if defined(_WIN32) && defined(UNICODE) #if PPSSPP_PLATFORM(UWP) if (MoveFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str())) @@ -733,6 +797,7 @@ bool Rename(const Path &srcFilename, const Path &destFilename) { ERROR_LOG(Log::IO, "Rename: failed %s --> %s: %s", srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg().c_str()); return false; +#endif } // copies file srcFilename to destFilename, returns true on success @@ -762,7 +827,7 @@ bool Copy(const Path &srcFilename, const Path &destFilename) { } INFO_LOG(Log::IO, "Copy by OpenCFile: %s --> %s", srcFilename.c_str(), destFilename.c_str()); -#ifdef _WIN32 +#if defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS) #if PPSSPP_PLATFORM(UWP) if (CopyFileFromAppW(srcFilename.ToWString().c_str(), destFilename.ToWString().c_str(), FALSE)) return true; @@ -906,7 +971,9 @@ uint64_t GetFileSize(const Path &filename) { return false; } -#if defined(_WIN32) && defined(UNICODE) +#ifdef HAVE_LIBRETRO_VFS + return path_get_size(filename.c_str()); +#elif defined(_WIN32) && defined(UNICODE) WIN32_FILE_ATTRIBUTE_DATA attr; #if PPSSPP_PLATFORM(UWP) if (!GetFileAttributesExFromAppW(filename.ToWString().c_str(), GetFileExInfoStandard, &attr)) @@ -939,45 +1006,19 @@ uint64_t GetFileSize(const Path &filename) { } uint64_t GetFileSize(FILE *f) { - // This will only support 64-bit when large file support is available. - // That won't be the case on some versions of Android, at least. -#if defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64) - int fd = fileno(f); - - off64_t pos = lseek64(fd, 0, SEEK_CUR); - off64_t size = lseek64(fd, 0, SEEK_END); - if (size != pos && lseek64(fd, pos, SEEK_SET) != pos) { + uint64_t pos = Ftell(f); + if (Fseek(f, 0, SEEK_END) != 0) { + return 0; + } + uint64_t size = Ftell(f); + // Reset the seek position to where it was when we started. + if (size != pos && Fseek(f, pos, SEEK_SET) != 0) { // Should error here. return 0; } if (size == -1) return 0; return size; -#else -#ifdef _WIN32 - uint64_t pos = _ftelli64(f); -#else - uint64_t pos = ftello(f); -#endif - if (fseek(f, 0, SEEK_END) != 0) { - return 0; - } -#ifdef _WIN32 - uint64_t size = _ftelli64(f); - // Reset the seek position to where it was when we started. - if (size != pos && _fseeki64(f, pos, SEEK_SET) != 0) { -#else - uint64_t size = ftello(f); - // Reset the seek position to where it was when we started. - if (size != pos && fseeko(f, pos, SEEK_SET) != 0) { -#endif - // Should error here. - return 0; - } - if (size == -1) - return 0; - return size; -#endif } // creates an empty file filename, returns true on success @@ -1017,6 +1058,12 @@ bool DeleteDir(const Path &path) { return false; } +#ifdef HAVE_LIBRETRO_VFS + if (filestream_delete(path.c_str()) == 0) + return true; + ERROR_LOG(Log::IO, "DeleteDir: %s", path.c_str()); + return false; +#else #ifdef _WIN32 #if PPSSPP_PLATFORM(UWP) if (RemoveDirectoryFromAppW(path.ToWString().c_str())) @@ -1032,6 +1079,7 @@ bool DeleteDir(const Path &path) { ERROR_LOG(Log::IO, "DeleteDir: %s: %s", path.c_str(), GetLastErrorMsg().c_str()); return false; +#endif } // Deletes the given directory and anything under it. Returns true on success. @@ -1164,6 +1212,64 @@ const Path &GetExeDirectory() { return ExePath; } +int Fseek(FILE *file, int64_t offset, int whence) { +#ifdef HAVE_LIBRETRO_VFS + switch (whence) { + default: + whence = RETRO_VFS_SEEK_POSITION_START; + break; + case SEEK_CUR: + whence = RETRO_VFS_SEEK_POSITION_CURRENT; + break; + case SEEK_END: + whence = RETRO_VFS_SEEK_POSITION_END; + break; + } + return filestream_seek(file, offset, whence) < 0 ? -1 : 0; +#elif defined(_WIN32) + return _fseeki64(file, offset, whence); +#elif defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64) + return lseek64(fileno(file), offset, whence) == -1 ? -1 : 0; +#else + return fseeko(file, offset, whence); +#endif +} + +int64_t Fseektell(FILE *file, int64_t offset, int whence) { +#ifdef HAVE_LIBRETRO_VFS + switch (whence) { + default: + whence = RETRO_VFS_SEEK_POSITION_START; + break; + case SEEK_CUR: + whence = RETRO_VFS_SEEK_POSITION_CURRENT; + break; + case SEEK_END: + whence = RETRO_VFS_SEEK_POSITION_END; + break; + } + return filestream_seek(file, offset, whence) < 0 ? -1 : filestream_tell(file); +#elif defined(_WIN32) + return _fseeki64(file, offset, whence) != 0 ? -1 : _ftelli64(file); +#elif defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64) + return lseek64(fileno(file), offset, whence); +#else + return fseeko(file, offset, whence) != 0 ? -1 : ftello(file); +#endif +} + +int64_t Ftell(FILE *file) { +#ifdef HAVE_LIBRETRO_VFS + return filestream_tell(file); +#elif defined(_WIN32) + return _ftelli64(file); +#elif defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64) + return lseek64(fileno(file), 0, SEEK_CUR); +#else + return ftello(file); +#endif +} + IOFile::IOFile(const Path &filename, const char openmode[]) { Open(filename, openmode); @@ -1183,21 +1289,21 @@ bool IOFile::Open(const Path& filename, const char openmode[]) bool IOFile::Close() { - if (!IsOpen() || 0 != std::fclose(m_file)) + if (!IsOpen() || 0 != fclose(m_file)) m_good = false; m_file = NULL; return m_good; } -std::FILE* IOFile::ReleaseHandle() +FILE* IOFile::ReleaseHandle() { - std::FILE* const ret = m_file; + FILE* const ret = m_file; m_file = NULL; return ret; } -void IOFile::SetHandle(std::FILE* file) +void IOFile::SetHandle(FILE* file) { Close(); Clear(); @@ -1214,7 +1320,7 @@ uint64_t IOFile::GetSize() bool IOFile::Seek(int64_t off, int origin) { - if (!IsOpen() || 0 != fseeko(m_file, off, origin)) + if (!IsOpen() || 0 != Fseek(m_file, off, origin)) m_good = false; return m_good; @@ -1223,14 +1329,14 @@ bool IOFile::Seek(int64_t off, int origin) uint64_t IOFile::Tell() { if (IsOpen()) - return ftello(m_file); + return Ftell(m_file); else return -1; } bool IOFile::Flush() { - if (!IsOpen() || 0 != std::fflush(m_file)) + if (!IsOpen() || 0 != fflush(m_file)) m_good = false; return m_good; @@ -1239,7 +1345,9 @@ bool IOFile::Flush() bool IOFile::Resize(uint64_t size) { if (!IsOpen() || 0 != -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + filestream_truncate(m_file, size) +#elif defined(_WIN32) // ector: _chsize sucks, not 64-bit safe // F|RES: changed to _chsize_s. i think it is 64-bit safe _chsize_s(_fileno(m_file), size) @@ -1280,7 +1388,7 @@ bool ReadFileToStringOptions(bool textFile, bool allowShort, const Path &filenam if (textFile) { // totalRead doesn't take \r into account since they might be skipped in this mode. // So let's just ask how far the cursor got. - totalRead = ftell(f); + totalRead = Ftell(f); } success = allowShort ? (totalRead <= len) : (totalRead == len); } @@ -1294,14 +1402,14 @@ uint8_t *ReadLocalFile(const Path &filename, size_t *size) { *size = 0; return nullptr; } - fseek(file, 0, SEEK_END); - size_t f_size = ftell(file); + Fseek(file, 0, SEEK_END); + size_t f_size = Ftell(file); if ((long)f_size < 0) { *size = 0; fclose(file); return nullptr; } - fseek(file, 0, SEEK_SET); + Fseek(file, 0, SEEK_SET); // NOTE: If you find ~10 memory leaks from here, with very varying sizes, it might be the VFPU LUTs. uint8_t *contents = new uint8_t[f_size + 1]; if (fread(contents, 1, f_size, file) != f_size) { @@ -1349,6 +1457,7 @@ void ChangeMTime(const Path &path, time_t mtime) { return; } +#ifndef HAVE_LIBRETRO_VFS #ifdef _WIN32 _utimbuf buf{}; buf.actime = mtime; @@ -1360,6 +1469,7 @@ void ChangeMTime(const Path &path, time_t mtime) { buf.modtime = mtime; utime(path.c_str(), &buf); #endif +#endif } bool IsProbablyInDownloadsFolder(const Path &filename) { diff --git a/Common/File/FileUtil.h b/Common/File/FileUtil.h index 56e07d76ca..3b060bc2db 100644 --- a/Common/File/FileUtil.h +++ b/Common/File/FileUtil.h @@ -17,7 +17,6 @@ #pragma once -#include #include #include #include @@ -137,6 +136,19 @@ const Path &GetExeDirectory(); const Path GetCurDirectory(); +// Portable version of fseek() that works with 64-bit offsets if supported by +// the operating system. +int Fseek(FILE *file, int64_t offset, int whence); + +// Portable version of fseek() that works with 64-bit offsets if supported by +// the operating system, and returns the new offset from the start of the file +// or -1 if it failed. +int64_t Fseektell(FILE *file, int64_t offset, int whence); + +// Portable version of ftell() that works with 64-bit offsets if supported by +// the operating system. +int64_t Ftell(FILE *file); + // simple wrapper for cstdlib file functions to // hopefully will make error checking easier // and make forgetting an fclose() harder @@ -156,7 +168,7 @@ public: template bool ReadArray(T* data, size_t length) { - if (!IsOpen() || length != std::fread(data, sizeof(T), length, m_file)) + if (!IsOpen() || length != fread(data, sizeof(T), length, m_file)) m_good = false; return m_good; @@ -165,7 +177,7 @@ public: template bool WriteArray(const T* data, size_t length) { - if (!IsOpen() || length != std::fwrite(data, sizeof(T), length, m_file)) + if (!IsOpen() || length != fwrite(data, sizeof(T), length, m_file)) m_good = false; return m_good; @@ -187,11 +199,11 @@ public: bool IsGood() const { return m_good; } operator bool() const { return IsGood() && IsOpen(); } - std::FILE* ReleaseHandle(); + FILE* ReleaseHandle(); - std::FILE* GetHandle() { return m_file; } + FILE* GetHandle() { return m_file; } - void SetHandle(std::FILE* file); + void SetHandle(FILE* file); bool Seek(int64_t off, int origin); uint64_t Tell(); @@ -202,15 +214,23 @@ public: // clear error state void Clear() { m_good = true; +#ifndef HAVE_LIBRETRO_VFS #undef clearerr std::clearerr(m_file); +#endif } private: - std::FILE *m_file = nullptr; + FILE *m_file = nullptr; bool m_good = true; }; +#ifdef HAVE_LIBRETRO_VFS +// Call this on libretro core initialization with the libretro virtual file +// system interface. +void InitLibretroVFS(const struct retro_vfs_interface_info *vfs) noexcept; +#endif + // TODO: Refactor, this was moved from the old file_util.cpp. // Whole-file reading/writing diff --git a/Common/File/Path.h b/Common/File/Path.h index 5d437a0bcb..2b3cf3c535 100644 --- a/Common/File/Path.h +++ b/Common/File/Path.h @@ -5,6 +5,10 @@ #include #include +#ifdef HAVE_LIBRETRO_VFS +#include "libretro/LibretroFileStreamTransforms.h" +#endif + #if defined(__APPLE__) #if TARGET_OS_IPHONE diff --git a/Common/File/VFS/DirectoryReader.cpp b/Common/File/VFS/DirectoryReader.cpp index 6e13eb6ad9..60e2940390 100644 --- a/Common/File/VFS/DirectoryReader.cpp +++ b/Common/File/VFS/DirectoryReader.cpp @@ -77,9 +77,9 @@ VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference, si if (!file) { return nullptr; } - fseek(file, 0, SEEK_END); - *size = ftell(file); - fseek(file, 0, SEEK_SET); + File::Fseek(file, 0, SEEK_END); + *size = File::Ftell(file); + File::Fseek(file, 0, SEEK_SET); DirectoryReaderOpenFile *openFile = new DirectoryReaderOpenFile(); openFile->file = file; return openFile; @@ -87,7 +87,7 @@ VFSOpenFile *DirectoryReader::OpenFileForRead(VFSFileReference *vfsReference, si void DirectoryReader::Rewind(VFSOpenFile *vfsOpenFile) { DirectoryReaderOpenFile *openFile = (DirectoryReaderOpenFile *)vfsOpenFile; - fseek(openFile->file, 0, SEEK_SET); + File::Fseek(openFile->file, 0, SEEK_SET); } size_t DirectoryReader::Read(VFSOpenFile *vfsOpenFile, void *buffer, size_t length) { diff --git a/Common/File/VFS/ZipFileReader.cpp b/Common/File/VFS/ZipFileReader.cpp index 7ff871ecbc..87546bdb7a 100644 --- a/Common/File/VFS/ZipFileReader.cpp +++ b/Common/File/VFS/ZipFileReader.cpp @@ -15,41 +15,146 @@ #include "Common/File/VFS/ZipFileReader.h" #include "Common/StringUtils.h" -ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath, bool logErrors) { - int error = 0; - zip *zip_file; - if (zipFile.Type() == PathType::CONTENT_URI) { - int fd = File::OpenFD(zipFile, File::OPEN_READ); - if (!fd) { - if (logErrors) { - ERROR_LOG(Log::IO, "Failed to open FD for '%s' as zip file", zipFile.c_str()); +ZipContainer::ZipContainer() noexcept : sourceData_ {{}, nullptr}, zip_(nullptr) {} + +ZipContainer::ZipContainer(const Path &path) : sourceData_ {path, nullptr}, zip_(nullptr) { + zip_source_t *source = zip_source_function_create(SourceCallback, &sourceData_, nullptr); + if ((zip_ = zip_open_from_source(source, ZIP_RDONLY, nullptr)) == nullptr) { + zip_source_free(source); + } +} + +ZipContainer::ZipContainer(ZipContainer &&other) noexcept : sourceData_(other.sourceData_), zip_(other.zip_) { + other.sourceData_.path.clear(); + other.sourceData_.file = nullptr; + other.zip_ = nullptr; +} + +ZipContainer &ZipContainer::operator=(ZipContainer &&other) noexcept { + sourceData_ = other.sourceData_; + zip_ = other.zip_; + other.sourceData_.path.clear(); + other.sourceData_.file = nullptr; + other.zip_ = nullptr; + return *this; +} + +ZipContainer::~ZipContainer() { + close(); +} + +void ZipContainer::close() noexcept { + if (zip_ != nullptr) { + zip_close(zip_); + zip_ = nullptr; + } +} + +ZipContainer::operator zip_t *() const noexcept { + return zip_; +} + +zip_int64_t ZipContainer::SourceCallback(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd) { + SourceData &sourceData = *(SourceData *)userdata; + + switch (cmd) { + case ZIP_SOURCE_SUPPORTS: + return zip_source_make_command_bitmap( + ZIP_SOURCE_SUPPORTS, + ZIP_SOURCE_OPEN, + ZIP_SOURCE_READ, + ZIP_SOURCE_CLOSE, + ZIP_SOURCE_STAT, + ZIP_SOURCE_ERROR, + ZIP_SOURCE_SEEK, + ZIP_SOURCE_TELL, + -1); + + case ZIP_SOURCE_OPEN: + { + FILE *newFile = File::OpenCFile(sourceData.path, "rb"); + if (newFile == nullptr) { + return -1; + } + sourceData.file = newFile; + return 0; + } + case ZIP_SOURCE_READ: + { + size_t n = fread(data, 1, len, sourceData.file); + return ferror(sourceData.file) ? -1 : n; } - return nullptr; - } - zip_file = zip_fdopen(fd, 0, &error); - } else { - zip_file = zip_open(zipFile.c_str(), 0, &error); - } - if (!zip_file) { - if (logErrors) { - ERROR_LOG(Log::IO, "Failed to open %s as a zip file", zipFile.c_str()); - } - return nullptr; - } + case ZIP_SOURCE_CLOSE: + fclose(sourceData.file); + sourceData.file = nullptr; + return 0; + case ZIP_SOURCE_STAT: + { + if (sourceData.file == nullptr) { + return -1; + } + int64_t pos = File::Ftell(sourceData.file); + if (pos == -1) { + return -1; + } + if (File::Fseek(sourceData.file, 0, SEEK_END) != 0) { + return -1; + } + int64_t size = File::Ftell(sourceData.file); + if (size != pos && File::Fseek(sourceData.file, pos, SEEK_SET) != 0) { + return -1; + } + zip_stat_t *stat = (zip_stat_t *)data; + zip_stat_init(stat); + stat->valid = ZIP_STAT_SIZE; + stat->size = size; + return 0; + } + + case ZIP_SOURCE_ERROR: + return ZIP_ER_INTERNAL; + + case ZIP_SOURCE_SEEK: + { + zip_source_args_seek_t *args = (zip_source_args_seek_t *)data; + if (args == nullptr) { + return -1; + } + return File::Fseek(sourceData.file, args->offset, args->whence) ? -1 : 0; + } + + case ZIP_SOURCE_TELL: + return File::Ftell(sourceData.file); + + default: + return -1; + } +} + +ZipFileReader *ZipFileReader::Create(const Path &zipFile, const char *inZipPath, bool logErrors) { // The inZipPath is supposed to be a folder, and internally in this class, we suffix // folder paths with '/', matching how the zip library works. std::string path = inZipPath; if (!path.empty() && path.back() != '/') { path.push_back('/'); } - return new ZipFileReader(zip_file, zipFile, path); + + ZipContainer zip(zipFile); + if (zip == nullptr) { + if (logErrors) { + ERROR_LOG(Log::IO, "Failed to open %s as a zip file", zipFile.c_str()); + } + return nullptr; + } + + return new ZipFileReader(std::move(zip), zipFile, path); } ZipFileReader::~ZipFileReader() { std::lock_guard guard(lock_); - zip_close(zip_file_); + zip_file_.close(); } uint8_t *ZipFileReader::ReadFile(const char *path, size_t *size) { @@ -320,30 +425,17 @@ void ZipFileReader::CloseFile(VFSOpenFile *vfsOpenFile) { } bool ReadSingleFileFromZip(Path zipFile, const char *path, std::string *data, std::mutex *mutex) { - zip *zip = nullptr; - int error = 0; - if (zipFile.Type() == PathType::CONTENT_URI) { - int fd = File::OpenFD(zipFile, File::OPEN_READ); - if (!fd) { - return false; - } - zip = zip_fdopen(fd, 0, &error); - } else { - zip = zip_open(zipFile.c_str(), 0, &error); - } - - if (!zip) { + ZipContainer zip(zipFile); + if (zip == nullptr) { return false; } struct zip_stat zstat; if (zip_stat(zip, path, ZIP_FL_NOCASE | ZIP_FL_UNCHANGED, &zstat) != 0) { - zip_close(zip); return false; } zip_file *file = zip_fopen_index(zip, zstat.index, ZIP_FL_UNCHANGED); if (!file) { - zip_close(zip); return false; } if (mutex) { @@ -356,13 +448,11 @@ bool ReadSingleFileFromZip(Path zipFile, const char *path, std::string *data, st } data->resize(0); zip_fclose(file); - zip_close(zip); return false; } if (mutex) { mutex->unlock(); } zip_fclose(file); - zip_close(zip); return true; } diff --git a/Common/File/VFS/ZipFileReader.h b/Common/File/VFS/ZipFileReader.h index 4c40877f17..9c699993b3 100644 --- a/Common/File/VFS/ZipFileReader.h +++ b/Common/File/VFS/ZipFileReader.h @@ -9,11 +9,34 @@ #include #include #include +#include #include "Common/File/VFS/VFS.h" #include "Common/File/FileUtil.h" #include "Common/File/Path.h" +class ZipContainer { +public: + ZipContainer() noexcept; + ZipContainer(const Path &path); + ~ZipContainer(); + ZipContainer(const ZipContainer &) = delete; + ZipContainer(ZipContainer &&) noexcept; + ZipContainer &operator=(const ZipContainer &) = delete; + ZipContainer &operator=(ZipContainer &&) noexcept; + void close() noexcept; + operator zip_t *() const noexcept; + +private: + struct SourceData { + Path path; + FILE *file; + } sourceData_; + zip_t *zip_; + + static zip_int64_t SourceCallback(void *userdata, void *data, zip_uint64_t len, zip_source_cmd_t cmd); +}; + class ZipFileReader : public VFSBackend { public: static ZipFileReader *Create(const Path &zipFile, const char *inZipPath, bool logErrors = true); @@ -45,11 +68,11 @@ public: } private: - ZipFileReader(zip *zip_file, const Path &zipPath, const std::string &inZipPath) : zip_file_(zip_file), zipPath_(zipPath), inZipPath_(inZipPath) {} + ZipFileReader(ZipContainer &&zip, const Path &zipPath, const std::string &inZipPath) : zip_file_(std::move(zip)), zipPath_(zipPath), inZipPath_(inZipPath) {} // Path has to be either an empty string, or a string ending with a /. bool GetZipListings(const std::string &path, std::set &files, std::set &directories); - zip *zip_file_ = nullptr; + ZipContainer zip_file_; std::mutex lock_; std::string inZipPath_; Path zipPath_; diff --git a/Common/Log/LogManager.cpp b/Common/Log/LogManager.cpp index 103f4e7d78..dd9836100f 100644 --- a/Common/Log/LogManager.cpp +++ b/Common/Log/LogManager.cpp @@ -382,6 +382,10 @@ void OutputDebugStringUTF8(const char *p) { #endif +#ifdef HAVE_LIBRETRO_VFS +#undef fprintf +#endif + void LogManager::StdioLog(const LogMessage &message) { #if PPSSPP_PLATFORM(ANDROID) #ifndef LOG_APP_NAME diff --git a/Common/Render/AtlasGen.cpp b/Common/Render/AtlasGen.cpp index 7b968e29a3..241a4abd89 100644 --- a/Common/Render/AtlasGen.cpp +++ b/Common/Render/AtlasGen.cpp @@ -20,6 +20,7 @@ #include "Common/Data/Convert/ColorConv.h" #include "Common/Data/Encoding/Utf8.h" +#include "Common/File/FileUtil.h" #include "Common/File/VFS/VFS.h" #include "Common/Render/AtlasGen.h" @@ -74,7 +75,7 @@ void Image::SaveZIM(const char *zim_name, int zim_format) { for (int y = 0; y < height(); y++) { memcpy(image_data + y * width() * 4, (dat.data() + y * w), width() * 4); } - FILE *f = fopen(zim_name, "wb"); + FILE *f = File::OpenCFile(Path(zim_name), "wb"); // SaveZIM takes ownership over image_data, there's no leak. ::SaveZIM(f, width(), height(), width() * 4, zim_format | ZIM_DITHER, image_data); fclose(f); diff --git a/Common/UI/IconCache.cpp b/Common/UI/IconCache.cpp index c1b6277f36..3c8747d4e6 100644 --- a/Common/UI/IconCache.cpp +++ b/Common/UI/IconCache.cpp @@ -6,6 +6,7 @@ #include "Common/Data/Format/PNGLoad.h" #include "Common/Log.h" #include "Common/GPU/thin3d.h" +#include "Common/File/FileUtil.h" #define ICON_CACHE_VERSION 1 #define MK_FOURCC(str) (str[0] | ((uint8_t)str[1] << 8) | ((uint8_t)str[2] << 16) | ((uint8_t)str[3] << 24)) @@ -88,7 +89,7 @@ bool IconCache::LoadFromFile(FILE *file) { // Check if we already have the entry somehow. if (cache_.find(key) != cache_.end()) { // Seek past the data and go to the next entry. - fseek(file, entryHeader.dataLen, SEEK_CUR); + File::Fseek(file, entryHeader.dataLen, SEEK_CUR); continue; } diff --git a/Common/UI/IconCache.h b/Common/UI/IconCache.h index 20f69c13c6..c93483a95b 100644 --- a/Common/UI/IconCache.h +++ b/Common/UI/IconCache.h @@ -6,6 +6,8 @@ #include #include +#include "Common/File/Path.h" + class UIContext; diff --git a/Core/Debugger/SymbolMap.cpp b/Core/Debugger/SymbolMap.cpp index 6d71345fd4..fd68959ddb 100644 --- a/Core/Debugger/SymbolMap.cpp +++ b/Core/Debugger/SymbolMap.cpp @@ -36,6 +36,8 @@ #include "zlib.h" +#include "ext/armips/Core/Assembler.h" + #include "Common/CommonTypes.h" #include "Common/Log.h" #include "Common/File/FileUtil.h" @@ -44,7 +46,6 @@ #include "Core/MemMap.h" #include "Core/Config.h" #include "Core/Debugger/SymbolMap.h" -#include "ext/armips/Core/Assembler.h" SymbolMap *g_symbolMap; @@ -228,30 +229,80 @@ bool SymbolMap::SaveSymbolMap(const Path &filename) const { std::string data; buf.TakeAll(&data); + FILE *file = File::OpenCFile(filename, "wb"); + if (file == nullptr) { + return false; + } if (g_Config.bCompressSymbols) { - // TODO: Wrap this in some nicer way. - gzFile f; - if (filename.Type() == PathType::CONTENT_URI) { - int fd = File::OpenFD(filename, File::OPEN_WRITE); - f = gzdopen(fd, "w9"); - if (f == Z_NULL) { - File::CloseFD(fd); - return false; - } - } else { - f = gzopen(filename.c_str(), "w9"); - if (f == Z_NULL) { - return false; - } + uInt out_size = 4096; + Bytef *out_data = (Bytef *)std::malloc(out_size); + if (out_data == nullptr) { + fclose(file); + return false; } - gzwrite(f, data.data(), (unsigned int)data.size()); - gzclose(f); + z_stream strm; + strm.zalloc = nullptr; + strm.zfree = nullptr; + strm.opaque = nullptr; + if (deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, MAX_WBITS + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) { + std::free(out_data); + fclose(file); + return false; + } + strm.next_in = (Bytef *)data.data(); + strm.avail_in = data.size(); + strm.next_out = out_data; + strm.avail_out = out_size; + int flush = Z_NO_FLUSH; + for (;;) { + int status = deflate(&strm, flush); + switch (status) { + case Z_OK: + case Z_STREAM_END: + if (strm.avail_out != out_size) { + fwrite(out_data, 1, out_size - strm.avail_out, file); + } + break; + case Z_BUF_ERROR: + { + std::free(out_data); + uInt new_out_size = 2 * out_size; + if (new_out_size < out_size) { + deflateEnd(&strm); + fclose(file); + return false; + } + out_size = new_out_size; + out_data = (Bytef *)std::malloc(out_size); + if (out_data == nullptr) { + deflateEnd(&strm); + fclose(file); + return false; + } + } + break; + default: + deflateEnd(&strm); + std::free(out_data); + fclose(file); + return false; + } + if (status == Z_STREAM_END) { + break; + } + if (strm.avail_in == 0) { + flush = Z_FINISH; + } + strm.next_out = out_data; + strm.avail_out = out_size; + } + deflateEnd(&strm); + std::free(out_data); } else { // Just plain write it. - FILE *file = File::OpenCFile(filename, "wb"); fwrite(data.data(), 1, data.size(), file); - fclose(file); } + fclose(file); return true; } diff --git a/Core/FileLoaders/DiskCachingFileLoader.cpp b/Core/FileLoaders/DiskCachingFileLoader.cpp index 6a36add50d..ed40ef6ad5 100644 --- a/Core/FileLoaders/DiskCachingFileLoader.cpp +++ b/Core/FileLoaders/DiskCachingFileLoader.cpp @@ -37,11 +37,6 @@ #include #endif -#if PPSSPP_PLATFORM(SWITCH) -// Far from optimal, but I guess it works... -#define fseeko fseek -#endif - static const char * const CACHEFILE_MAGIC = "ppssppDC"; static const s64 SAFETY_FREE_DISK_SPACE = 768 * 1024 * 1024; // 768 MB // Aim to allow this many files cached at once. @@ -203,7 +198,7 @@ void DiskCachingFileLoaderCache::InitCache(const Path &filename) { void DiskCachingFileLoaderCache::ShutdownCache() { if (f_) { bool failed = false; - if (fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) { + if (File::Fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) { failed = true; } else if (fwrite(&index_[0], sizeof(BlockInfo), indexCount_, f_) != indexCount_) { failed = true; @@ -460,19 +455,11 @@ bool DiskCachingFileLoaderCache::ReadBlockData(u8 *dest, BlockInfo &info, size_t fflush(f_); bool failed = false; -#ifdef __ANDROID__ - if (lseek64(fd_, blockOffset, SEEK_SET) != blockOffset) { - failed = true; - } else if (read(fd_, dest + offset, size) != (ssize_t)size) { - failed = true; - } -#else - if (fseeko(f_, blockOffset, SEEK_SET) != 0) { + if (File::Fseek(f_, blockOffset, SEEK_SET) != 0) { failed = true; } else if (fread(dest + offset, size, 1, f_) != 1) { failed = true; } -#endif if (failed) { ERROR_LOG(Log::Loader, "Unable to read disk cache data entry."); @@ -488,19 +475,11 @@ void DiskCachingFileLoaderCache::WriteBlockData(BlockInfo &info, const u8 *src) s64 blockOffset = GetBlockOffset(info.block); bool failed = false; -#ifdef __ANDROID__ - if (lseek64(fd_, blockOffset, SEEK_SET) != blockOffset) { - failed = true; - } else if (write(fd_, src, blockSize_) != (ssize_t)blockSize_) { - failed = true; - } -#else - if (fseeko(f_, blockOffset, SEEK_SET) != 0) { + if (File::Fseek(f_, blockOffset, SEEK_SET) != 0) { failed = true; } else if (fwrite(src, blockSize_, 1, f_) != 1) { failed = true; } -#endif if (failed) { ERROR_LOG(Log::Loader, "Unable to write disk cache data entry."); @@ -516,7 +495,7 @@ void DiskCachingFileLoaderCache::WriteIndexData(u32 indexPos, BlockInfo &info) { u32 offset = (u32)sizeof(FileHeader) + indexPos * (u32)sizeof(BlockInfo); bool failed = false; - if (fseek(f_, offset, SEEK_SET) != 0) { + if (File::Fseek(f_, offset, SEEK_SET) != 0) { failed = true; } else if (fwrite(&info, sizeof(BlockInfo), 1, f_) != 1) { failed = true; @@ -553,11 +532,6 @@ bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) { if (valid) { f_ = fp; -#ifdef __ANDROID__ - // Android NDK does not support 64-bit file I/O using C streams - fd_ = fileno(f_); -#endif - // Now let's load the index. blockSize_ = header.blockSize; maxBlocks_ = header.maxBlocks; @@ -572,7 +546,7 @@ bool DiskCachingFileLoaderCache::LoadCacheFile(const Path &path) { } void DiskCachingFileLoaderCache::LoadCacheIndex() { - if (fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) { + if (File::Fseek(f_, sizeof(FileHeader), SEEK_SET) != 0) { CloseFileHandle(); return; } @@ -631,10 +605,6 @@ void DiskCachingFileLoaderCache::CreateCacheFile(const Path &path) { ERROR_LOG(Log::Loader, "Could not create disk cache file"); return; } -#ifdef __ANDROID__ - // Android NDK does not support 64-bit file I/O using C streams - fd_ = fileno(f_); -#endif blockSize_ = DEFAULT_BLOCK_SIZE; @@ -677,7 +647,7 @@ bool DiskCachingFileLoaderCache::LockCacheFile(bool lockStatus) { u32 offset = (u32)offsetof(FileHeader, flags); bool failed = false; - if (fseek(f_, offset, SEEK_SET) != 0) { + if (File::Fseek(f_, offset, SEEK_SET) != 0) { failed = true; } else if (fread(&flags_, sizeof(u32), 1, f_) != 1) { failed = true; @@ -704,7 +674,7 @@ bool DiskCachingFileLoaderCache::LockCacheFile(bool lockStatus) { flags_ &= ~FLAG_LOCKED; } - if (fseek(f_, offset, SEEK_SET) != 0) { + if (File::Fseek(f_, offset, SEEK_SET) != 0) { failed = true; } else if (fwrite(&flags_, sizeof(u32), 1, f_) != 1) { failed = true; diff --git a/Core/FileLoaders/LocalFileLoader.cpp b/Core/FileLoaders/LocalFileLoader.cpp index 89b0fe4ce9..280e3ec25f 100644 --- a/Core/FileLoaders/LocalFileLoader.cpp +++ b/Core/FileLoaders/LocalFileLoader.cpp @@ -62,15 +62,6 @@ LocalFileLoader::LocalFileLoader(const Path &filename) return; } -#if HAVE_LIBRETRO_VFS - isOpenedByFd_ = false; - handle_ = filestream_open(filename.c_str(), RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); - filestream_seek(handle_, 0, RETRO_VFS_SEEK_POSITION_END); - filesize_ = filestream_tell(handle_); - filestream_seek(handle_, 0, RETRO_VFS_SEEK_POSITION_START); - return; -#endif - #if PPSSPP_PLATFORM(ANDROID) && !defined(HAVE_LIBRETRO_VFS) if (filename.Type() == PathType::CONTENT_URI) { int fd = Android_OpenContentUriFd(filename.ToString(), Android_OpenContentUriMode::READ); @@ -87,7 +78,11 @@ LocalFileLoader::LocalFileLoader(const Path &filename) #endif #if defined(HAVE_LIBRETRO_VFS) - // Nothing to do here... + file_ = File::OpenCFile(filename, "rb"); + if (file_ == nullptr) { + return; + } + filesize_ = File::GetFileSize(file_); #elif !defined(_WIN32) fd_ = open(filename.c_str(), O_RDONLY | O_CLOEXEC); @@ -123,7 +118,9 @@ LocalFileLoader::LocalFileLoader(const Path &filename) LocalFileLoader::~LocalFileLoader() { #if defined(HAVE_LIBRETRO_VFS) - filestream_close(handle_); + if (file_ != nullptr) { + fclose(file_); + } #elif !defined(_WIN32) if (fd_ != -1) { close(fd_); @@ -138,8 +135,7 @@ LocalFileLoader::~LocalFileLoader() { bool LocalFileLoader::Exists() { // If we opened it for reading, it must exist. Done. #if defined(HAVE_LIBRETRO_VFS) - return handle_ != 0; - + return file_ != nullptr; #elif !defined(_WIN32) if (isOpenedByFd_) { // As an optimization, if we already tried and failed, quickly return. @@ -178,9 +174,9 @@ size_t LocalFileLoader::ReadAt(s64 absolutePos, size_t bytes, size_t count, void } #if defined(HAVE_LIBRETRO_VFS) - std::lock_guard guard(readLock_); - filestream_seek(handle_, absolutePos, RETRO_VFS_SEEK_POSITION_START); - return filestream_read(handle_, data, bytes * count) / bytes; + std::lock_guard guard(readLock_); + File::Fseek(file_, absolutePos, SEEK_SET); + return fread(data, bytes, count, file_); #elif PPSSPP_PLATFORM(SWITCH) // Toolchain has no fancy IO API. We must lock. std::lock_guard guard(readLock_); diff --git a/Core/FileLoaders/LocalFileLoader.h b/Core/FileLoaders/LocalFileLoader.h index 7e1546a995..0d734f9206 100644 --- a/Core/FileLoaders/LocalFileLoader.h +++ b/Core/FileLoaders/LocalFileLoader.h @@ -24,15 +24,10 @@ #include "Common/StringUtils.h" #include "Core/Loaders.h" -#ifdef _WIN32 +#if defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS) typedef void *HANDLE; #endif -#ifdef HAVE_LIBRETRO_VFS -#include -typedef RFILE* HANDLE; -#endif - class LocalFileLoader : public FileLoader { public: LocalFileLoader(const Path &filename); @@ -47,7 +42,9 @@ public: size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data, Flags flags = Flags::NONE) override; private: -#if !defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS) +#ifdef HAVE_LIBRETRO_VFS + FILE *file_; +#elif !defined(_WIN32) void DetectSizeFd(); int fd_ = -1; #else diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index c5bd74a6c7..d8832457a4 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -155,7 +155,28 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File } //TODO: tests, should append seek to end of file? seeking in a file opened for append? -#if PPSSPP_PLATFORM(WINDOWS) +#ifdef HAVE_LIBRETRO_VFS + int flags = 0; + if (access & FILEACCESS_READ && access & FILEACCESS_WRITE) + flags = RETRO_VFS_FILE_ACCESS_READ_WRITE; + else if (access & FILEACCESS_WRITE) + flags = RETRO_VFS_FILE_ACCESS_WRITE; + else if (access & FILEACCESS_READ && access & FILEACCESS_CREATE) + flags = RETRO_VFS_FILE_ACCESS_READ_WRITE; + else + flags = RETRO_VFS_FILE_ACCESS_READ; + bool success = false; + if (!(access & FILEACCESS_CREATE)) { + hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE); + success = hFile != nullptr; + } else if (!(access & FILEACCESS_EXCL)) { + hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE && File::Exists(fullName) ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE); + success = hFile != nullptr; + } else if (!File::Exists(fullName)) { + hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE); + success = hFile != nullptr; + } +#elif PPSSPP_PLATFORM(WINDOWS) // Convert parameters to Windows permissions and access DWORD desired = 0; DWORD sharemode = 0; @@ -278,7 +299,19 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File DEBUG_LOG(Log::FileSystem, "Case may have been incorrect, second try opening %s (%s)", fullName.c_str(), fileName.c_str()); // And try again with the correct case this time -#if PPSSPP_PLATFORM(UWP) +#ifdef HAVE_LIBRETRO_VFS + success = false; + if (!(access & FILEACCESS_CREATE)) { + hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE); + success = hFile != nullptr; + } else if (!(access & FILEACCESS_EXCL)) { + hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE && File::Exists(fullName) ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE); + success = hFile != nullptr; + } else if (!File::Exists(fullName)) { + hFile = filestream_open(fullName.c_str(), flags & RETRO_VFS_FILE_ACCESS_WRITE ? flags | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING : flags, RETRO_VFS_FILE_ACCESS_HINT_NONE); + success = hFile != nullptr; + } +#elif PPSSPP_PLATFORM(UWP) // Should never get here. #elif PPSSPP_PLATFORM(WINDOWS) // Unlikely to get here, heh. @@ -291,7 +324,7 @@ bool DirectoryFileHandle::Open(const Path &basePath, std::string &fileName, File } } -#if !PPSSPP_PLATFORM(WINDOWS) +#if !PPSSPP_PLATFORM(WINDOWS) && !defined(HAVE_LIBRETRO_VFS) if (success) { // Reject directories, even if we succeed in opening them. // TODO: Might want to do this stat first... @@ -337,7 +370,9 @@ size_t DirectoryFileHandle::Read(u8* pointer, s64 size) } } if (size > 0) { -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + bytesRead = fread(pointer, 1, size, hFile); +#elif defined(_WIN32) ::ReadFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesRead, 0); #else bytesRead = read(hFile, pointer, size); @@ -351,7 +386,9 @@ size_t DirectoryFileHandle::Write(const u8* pointer, s64 size) size_t bytesWritten = 0; bool diskFull = false; -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + bytesWritten = fwrite(pointer, 1, size, hFile); +#elif defined(_WIN32) BOOL success = ::WriteFile(hFile, (LPVOID)pointer, (DWORD)size, (LPDWORD)&bytesWritten, 0); if (success == FALSE) { DWORD err = GetLastError(); @@ -406,7 +443,15 @@ size_t DirectoryFileHandle::Seek(s32 position, FileMove type) } size_t result; -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + int moveMethod = 0; + switch (type) { + case FILEMOVE_BEGIN: moveMethod = SEEK_SET; break; + case FILEMOVE_CURRENT: moveMethod = SEEK_CUR; break; + case FILEMOVE_END: moveMethod = SEEK_END; break; + } + result = File::Fseektell(hFile, position, moveMethod); +#elif defined(_WIN32) DWORD moveMethod = 0; switch (type) { case FILEMOVE_BEGIN: moveMethod = FILE_BEGIN; break; @@ -434,7 +479,11 @@ size_t DirectoryFileHandle::Seek(s32 position, FileMove type) void DirectoryFileHandle::Close() { if (needsTrunc_ != -1) { -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + if (filestream_truncate(hFile, needsTrunc_) != 0) { + ERROR_LOG(Log::FileSystem, "Failed to truncate file to %d bytes", (int)needsTrunc_); + } +#elif defined(_WIN32) Seek((s32)needsTrunc_, FILEMOVE_BEGIN); if (SetEndOfFile(hFile) == 0) { ERROR_LOG(Log::FileSystem, "Failed to truncate file to %d bytes", (int)needsTrunc_); @@ -447,7 +496,10 @@ void DirectoryFileHandle::Close() { #endif } -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + if (hFile != nullptr) + fclose(hFile); +#elif defined(_WIN32) if (hFile != (HANDLE)-1) CloseHandle(hFile); #else @@ -598,7 +650,7 @@ int DirectoryFileSystem::OpenFile(std::string filename, FileAccess access, const } return err; } else { -#ifdef _WIN32 +#if defined(_WIN32) || defined(HAVE_LIBRETRO_VFS) if (access & FILEACCESS_APPEND) { entry.hFile.Seek(0, FILEMOVE_END); } diff --git a/Core/FileSystems/DirectoryFileSystem.h b/Core/FileSystems/DirectoryFileSystem.h index d3fb373781..083b15635c 100644 --- a/Core/FileSystems/DirectoryFileSystem.h +++ b/Core/FileSystems/DirectoryFileSystem.h @@ -24,7 +24,7 @@ #include "Common/File/Path.h" #include "Core/FileSystems/FileSystem.h" -#ifdef _WIN32 +#if defined(_WIN32) && !defined(HAVE_LIBRETRO_VFS) typedef void * HANDLE; #endif @@ -34,7 +34,9 @@ struct DirectoryFileHandle { SKIP_REPLAY, }; -#ifdef _WIN32 +#ifdef HAVE_LIBRETRO_VFS + FILE *hFile = nullptr; +#elif defined(_WIN32) HANDLE hFile = (HANDLE)-1; #else int hFile = -1; diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 5a9ed1d8fc..231af74f87 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -319,39 +319,25 @@ bool UmdReplace(const Path &filepath, FileLoader **fileLoader, std::string &erro } // Close the return value with ZipClose (if non-null, of course). -struct zip *ZipOpenPath(const Path &fileName) { - int error = 0; - // Need to special case for content URI here, similar to OpenCFile. - struct zip *z; -#if PPSSPP_PLATFORM(ANDROID) - if (fileName.Type() == PathType::CONTENT_URI) { - int fd = File::OpenFD(fileName, File::OPEN_READ); - z = zip_fdopen(fd, 0, &error); - } else -#endif - { // continuation of above else in the ifdef - z = zip_open(fileName.c_str(), 0, &error); - } - - if (!z) { - ERROR_LOG(Log::HLE, "Failed to open ZIP file '%s', error code=%i", fileName.c_str(), error); +ZipContainer ZipOpenPath(const Path &fileName) { + ZipContainer z(fileName); + if (z == nullptr) { + ERROR_LOG(Log::HLE, "Failed to open ZIP file '%s'", fileName.c_str()); } return z; } -void ZipClose(struct zip *z) { - if (z) - zip_close(z); +void ZipClose(ZipContainer &z) { + z.close(); } bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info) { - struct zip *z = ZipOpenPath(fileName); + ZipContainer z = ZipOpenPath(fileName); if (!z) { info->contents = ZipFileContents::UNKNOWN; return false; } DetectZipFileContents(z, info); - zip_close(z); return true; } @@ -408,7 +394,7 @@ static bool ZipExtractFileToMemory(struct zip *z, int fileIndex, std::string *da } } -void DetectZipFileContents(struct zip *z, ZipFileInfo *info) { +void DetectZipFileContents(zip_t *z, ZipFileInfo *info) { int numFiles = zip_get_num_files(z); _dbg_assert_(numFiles >= 0); diff --git a/Core/Loaders.h b/Core/Loaders.h index c60b19ed88..680da1431f 100644 --- a/Core/Loaders.h +++ b/Core/Loaders.h @@ -20,13 +20,9 @@ #include #include -#ifdef SHARED_LIBZIP -#include -#else -#include "ext/libzip/zip.h" -#endif #include "Common/CommonTypes.h" #include "Common/File/Path.h" +#include "Common/File/VFS/ZipFileReader.h" enum class IdentifiedFileType { ERROR_IDENTIFYING, @@ -191,8 +187,8 @@ struct ZipFileInfo { std::string contentName; }; -struct zip *ZipOpenPath(const Path &fileName); -void ZipClose(zip *z); +ZipContainer ZipOpenPath(const Path &fileName); +void ZipClose(ZipContainer &z); bool DetectZipFileContents(const Path &fileName, ZipFileInfo *info); -void DetectZipFileContents(struct zip *z, ZipFileInfo *info); +void DetectZipFileContents(zip_t *z, ZipFileInfo *info); diff --git a/Core/MIPS/MIPSTracer.cpp b/Core/MIPS/MIPSTracer.cpp index bd51f26061..27e62892a0 100644 --- a/Core/MIPS/MIPSTracer.cpp +++ b/Core/MIPS/MIPSTracer.cpp @@ -119,7 +119,7 @@ bool MIPSTracer::flush_to_file() { } INFO_LOG(Log::JIT, "Trace flushed, closing the file..."); - std::fclose(output); + fclose(output); clear(); return true; @@ -144,7 +144,7 @@ void MIPSTracer::flush_block_to_file(const TraceBlockInfo& block_info) { snprintf(buffer, sizeof(buffer), "0x%08x: ", addr); MIPSDisAsm(storage.read_asm(index), addr, buffer + prefix_size, sizeof(buffer) - prefix_size, true); - std::fprintf(output, "%s\n", buffer); + fprintf(output, "%s\n", buffer); } } diff --git a/Core/WebServer.cpp b/Core/WebServer.cpp index face815634..b3d4e4f02b 100644 --- a/Core/WebServer.cpp +++ b/Core/WebServer.cpp @@ -249,7 +249,7 @@ static void DiscHandler(const http::ServerRequest &request, const Path &filename } FILE *fp = File::OpenCFile(filename, "rb"); - if (!fp || fseek(fp, begin, SEEK_SET) != 0) { + if (!fp || File::Fseek(fp, begin, SEEK_SET) != 0) { request.WriteHttpResponseHeader("1.0", 500, -1, "text/plain"); request.Out()->Push("File access failed."); if (fp) { diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index b80a3d2351..91fccce988 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -659,7 +659,7 @@ void PipelineManagerVulkan::SavePipelineCache(FILE *file, bool saveRawPipelineCa NOTICE_LOG(Log::G3D, "Saved Vulkan pipeline cache (%d bytes).", (int)size); } - size_t seekPosOnFailure = ftell(file); + int64_t seekPosOnFailure = File::Ftell(file); bool failed = false; bool writeFailed = false; @@ -711,7 +711,7 @@ void PipelineManagerVulkan::SavePipelineCache(FILE *file, bool saveRawPipelineCa ERROR_LOG(Log::G3D, "Failed to write pipeline cache, some shader was missing"); // Write a zero in the right place so it doesn't try to load the pipelines next time. size = 0; - fseek(file, (long)seekPosOnFailure, SEEK_SET); + File::Fseek(file, seekPosOnFailure, SEEK_SET); writeFailed = fwrite(&size, sizeof(size), 1, file) != 1; if (writeFailed) { ERROR_LOG(Log::G3D, "Failed to write pipeline cache, disk full?"); diff --git a/GPU/Vulkan/PipelineManagerVulkan.h b/GPU/Vulkan/PipelineManagerVulkan.h index b1b83664e0..f96b3d50b4 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.h +++ b/GPU/Vulkan/PipelineManagerVulkan.h @@ -30,6 +30,8 @@ #include "GPU/Vulkan/VulkanQueueRunner.h" #include "GPU/Vulkan/VulkanRenderManager.h" +#include "Common/File/FileUtil.h" + struct VKRGraphicsPipeline; class VulkanRenderManager; class VulkanContext; diff --git a/GPU/Vulkan/ShaderManagerVulkan.cpp b/GPU/Vulkan/ShaderManagerVulkan.cpp index c1424dc985..6f28e58495 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.cpp +++ b/GPU/Vulkan/ShaderManagerVulkan.cpp @@ -506,10 +506,10 @@ struct VulkanCacheHeader { bool ShaderManagerVulkan::LoadCacheFlags(FILE *f, DrawEngineVulkan *drawEngine) { VulkanCacheHeader header{}; - long pos = ftell(f); + int64_t pos = File::Ftell(f); bool success = fread(&header, sizeof(header), 1, f) == 1; // We'll read it again later, this is just to check the flags. - success = success && fseek(f, pos, SEEK_SET) == 0; + success = success && File::Fseek(f, pos, SEEK_SET) == 0; if (!success || header.magic != CACHE_HEADER_MAGIC) { WARN_LOG(Log::G3D, "Shader cache magic mismatch"); return false; diff --git a/GPU/Vulkan/ShaderManagerVulkan.h b/GPU/Vulkan/ShaderManagerVulkan.h index fd14c235c0..f49f2de125 100644 --- a/GPU/Vulkan/ShaderManagerVulkan.h +++ b/GPU/Vulkan/ShaderManagerVulkan.h @@ -32,6 +32,8 @@ #include "Common/Math/lin/matrix4x4.h" #include "GPU/Common/ShaderUniforms.h" +#include "Common/File/FileUtil.h" + class VulkanContext; class DrawEngineVulkan; class VulkanPushPool; diff --git a/libretro/CMakeLists.txt b/libretro/CMakeLists.txt index 7cd34d07a8..d304b4dba4 100644 --- a/libretro/CMakeLists.txt +++ b/libretro/CMakeLists.txt @@ -12,6 +12,19 @@ if(WIN32) LibretroD3D11Context.cpp) endif() +set(LIBRETRO_SRCS ${LIBRETRO_SRCS} + libretro-common/compat/compat_posix_string.c + libretro-common/compat/fopen_utf8.c + libretro-common/compat/compat_strl.c + libretro-common/encodings/encoding_utf.c + libretro-common/file/file_path.c + libretro-common/file/file_path_io.c + libretro-common/streams/file_stream.c + libretro-common/streams/file_stream_transforms.c + libretro-common/string/stdstring.c + libretro-common/time/rtime.c + libretro-common/vfs/vfs_implementation.c) + include_directories(libretro) add_library(ppsspp_libretro SHARED ${LIBRETRO_SRCS}) diff --git a/libretro/LibretroFileStreamTransforms.h b/libretro/LibretroFileStreamTransforms.h new file mode 100644 index 0000000000..fee177f327 --- /dev/null +++ b/libretro/LibretroFileStreamTransforms.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#undef fopen +#undef fseek +#undef ftell diff --git a/libretro/LibretroVulkanContext.cpp b/libretro/LibretroVulkanContext.cpp index e39cc1f1c9..6c1b423ad4 100644 --- a/libretro/LibretroVulkanContext.cpp +++ b/libretro/LibretroVulkanContext.cpp @@ -13,6 +13,8 @@ #include #include +#undef fflush + static VulkanContext *vk; void vk_libretro_init(VkInstance instance, VkPhysicalDevice gpu, VkSurfaceKHR surface, PFN_vkGetInstanceProcAddr get_instance_proc_addr, const char **required_device_extensions, unsigned num_required_device_extensions, const char **required_device_layers, unsigned num_required_device_layers, const VkPhysicalDeviceFeatures *required_features); diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index d525eb3319..11db3ce3ba 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "Common/CPUDetect.h" #include "Common/Log.h" @@ -401,11 +402,11 @@ void retro_set_environment(retro_environment_t cb) update_display_cb.callback = set_variable_visibility; environ_cb(RETRO_ENVIRONMENT_SET_CORE_OPTIONS_UPDATE_DISPLAY_CALLBACK, &update_display_cb); - #ifdef HAVE_LIBRETRO_VFS - struct retro_vfs_interface_info vfs_iface_info { 1, nullptr }; - if (cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info)) - filestream_vfs_init(&vfs_iface_info); - #endif +#ifdef HAVE_LIBRETRO_VFS + struct retro_vfs_interface_info vfs_iface_info { 2, nullptr }; + if (cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs_iface_info)) + File::InitLibretroVFS(&vfs_iface_info); +#endif } static int get_language_auto(void)