From 6514a8ba5fc993ee7d70e15f20b9ba0bcadeecd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Fri, 7 Nov 2025 22:44:23 -0500 Subject: [PATCH 1/9] Use the libretro VFS interface in libretro builds --- CMakeLists.txt | 2 + src/filesystem_root.cpp | 15 ++ src/platform/libretro/filesystem_libretro.cpp | 195 ++++++++++++++++++ src/platform/libretro/filesystem_libretro.h | 56 +++++ src/platform/libretro/ui.cpp | 9 + 5 files changed, 277 insertions(+) create mode 100644 src/platform/libretro/filesystem_libretro.cpp create mode 100644 src/platform/libretro/filesystem_libretro.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f4214968d..24a709a2be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1463,6 +1463,8 @@ else() # library src/platform/libretro/audio.h src/platform/libretro/clock.cpp src/platform/libretro/clock.h + src/platform/libretro/filesystem_libretro.cpp + src/platform/libretro/filesystem_libretro.h src/platform/libretro/input_buttons.cpp src/platform/libretro/ui.cpp src/platform/libretro/ui.h diff --git a/src/filesystem_root.cpp b/src/filesystem_root.cpp index 4cb334590c..8f569a3506 100644 --- a/src/filesystem_root.cpp +++ b/src/filesystem_root.cpp @@ -23,6 +23,10 @@ # include "platform/android/filesystem_saf.h" #endif +#ifdef USE_LIBRETRO +# include "platform/libretro/filesystem_libretro.h" +#endif + constexpr const std::string_view root_ns = "root://"; RootFilesystem::RootFilesystem() : Filesystem("", FilesystemView()) { @@ -32,6 +36,10 @@ RootFilesystem::RootFilesystem() : Filesystem("", FilesystemView()) { fs_list.push_back(std::make_pair("content", std::make_unique("", FilesystemView()))); #endif +#ifdef USE_LIBRETRO + fs_list.push_back(std::make_pair("libretro", std::make_unique("", FilesystemView()))); +#endif + // IMPORTANT: This must be the last filesystem in the list, do not push anything to fs_list afterwards! fs_list.push_back(std::make_pair("file", std::make_unique("", FilesystemView()))); @@ -106,12 +114,19 @@ const Filesystem& RootFilesystem::FilesystemForPath(std::string_view path) const assert(!fs_list.empty()); std::string_view ns; + +#ifdef USE_LIBRETRO + if (LibretroFilesystem::vfs.required_interface_version >= EP_FILESYSTEM_LIBRETRO_REQUIRED_INTERFACE_VERSION) { + ns = "libretro"; + } +#else // Check if the path contains a namespace auto ns_pos = path.find("://"); if (ns_pos != std::string::npos) { ns = path.substr(0, ns_pos); path = path.substr(ns_pos + 3); } +#endif if (ns.empty()) { // No namespace returns the last fs which is the NativeFilesystem diff --git a/src/platform/libretro/filesystem_libretro.cpp b/src/platform/libretro/filesystem_libretro.cpp new file mode 100644 index 0000000000..0bcd463ea2 --- /dev/null +++ b/src/platform/libretro/filesystem_libretro.cpp @@ -0,0 +1,195 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "filesystem_libretro.h" +#include "filesystem_stream.h" +#include "output.h" + +struct retro_vfs_interface_info LibretroFilesystem::vfs; + +LibretroFilesystem::LibretroFilesystem(std::string base_path, FilesystemView parent_fs) : Filesystem(std::move(base_path), parent_fs) { +} + +bool LibretroFilesystem::IsFile(std::string_view path) const { + int flags = vfs.iface->stat(ToString(path).c_str(), nullptr); + return flags & RETRO_VFS_STAT_IS_VALID && !(flags & RETRO_VFS_STAT_IS_DIRECTORY); +} + +bool LibretroFilesystem::IsDirectory(std::string_view dir, bool) const { + int flags = vfs.iface->stat(ToString(dir).c_str(), nullptr); + return flags & RETRO_VFS_STAT_IS_VALID && flags & RETRO_VFS_STAT_IS_DIRECTORY; +} + +bool LibretroFilesystem::Exists(std::string_view filename) const { + int flags = vfs.iface->stat(ToString(filename).c_str(), nullptr); + return flags & RETRO_VFS_STAT_IS_VALID; +} + +int64_t LibretroFilesystem::GetFilesize(std::string_view path) const { + int32_t size; + int flags = vfs.iface->stat(ToString(path).c_str(), &flags); + return flags & RETRO_VFS_STAT_IS_VALID ? size : -1; +} + +class LibretroStreamBufIn : public std::streambuf { +public: + LibretroStreamBufIn(struct retro_vfs_file_handle* handle) : std::streambuf(), handle(handle) { + setg(buffer_start, buffer_start, buffer_start); + } + + ~LibretroStreamBufIn() override { + LibretroFilesystem::vfs.iface->close(handle); + } + + int underflow() override { + ssize_t res = LibretroFilesystem::vfs.iface->read(handle, buffer.data(), buffer.size()); + if (res == 0) { + return traits_type::eof(); + } else if (res < 0) { + Output::Debug("underflow failed: {}", strerror(errno)); + return traits_type::eof(); + } + setg(buffer_start, buffer_start, buffer_start + res); + return traits_type::to_int_type(*gptr()); + } + + std::streambuf::pos_type seekoff(std::streambuf::off_type offset, std::ios_base::seekdir dir, std::ios_base::openmode mode) override { + if (dir == std::ios_base::cur) { + offset += static_cast(gptr() - egptr()); + } + int cdir = Filesystem_Stream::CppSeekdirToCSeekdir(dir); + auto res = LibretroFilesystem::vfs.iface->seek(handle, offset, cdir == SEEK_CUR ? RETRO_VFS_SEEK_POSITION_CURRENT : cdir == SEEK_END ? RETRO_VFS_SEEK_POSITION_END : RETRO_VFS_SEEK_POSITION_START); + setg(buffer_start, buffer_end, buffer_end); + return res; + } + + std::streambuf::pos_type seekpos(std::streambuf::pos_type pos, std::ios_base::openmode mode) override { + return LibretroFilesystem::vfs.iface->tell(handle); + } + +private: + struct retro_vfs_file_handle* handle; + std::array buffer; + char* buffer_start = &buffer.front(); + char* buffer_end = &buffer.back(); +}; + +std::streambuf* LibretroFilesystem::CreateInputStreambuffer(std::string_view path, std::ios_base::openmode) const { + struct retro_vfs_file_handle* handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); + return handle == nullptr ? nullptr : new LibretroStreamBufIn(handle); +} + +class LibretroStreamBufOut : public std::streambuf { +public: + LibretroStreamBufOut(struct retro_vfs_file_handle* handle) : std::streambuf(), handle(handle) { + setp(buffer_start, buffer_end); + } + + ~LibretroStreamBufOut() override { + sync(); + LibretroFilesystem::vfs.iface->close(handle); + } + + int overflow(int c = EOF) override { + if (sync() < 0) { + return traits_type::eof(); + } + if (c != EOF) { + char a = static_cast(c); + ssize_t res = LibretroFilesystem::vfs.iface->write(handle, &a, 1); + if (res < 1) { + return traits_type::eof(); + } + } + + return c; + } + + int sync() override { + auto len = pptr() - pbase(); + if (len == 0) { + return 0; + } + ssize_t res = LibretroFilesystem::vfs.iface->write(handle, pbase(), len); + setp(buffer_start, buffer_end); + if (res < len) { + return -1; + } + return 0; + } + +private: + struct retro_vfs_file_handle* handle; + std::array buffer; + char* buffer_start = &buffer.front(); + char* buffer_end = &buffer.back(); +}; + +std::streambuf* LibretroFilesystem::CreateOutputStreambuffer(std::string_view path, std::ios_base::openmode) const { + struct retro_vfs_file_handle* handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); + return handle == nullptr ? nullptr : new LibretroStreamBufIn(handle); +} + +// To prevent leaking of the directory handle if an exception is thrown within GetDirectoryContent +class LibretroDirGuard { +public: + LibretroDirGuard(struct retro_vfs_dir_handle* handle) : handle(handle) { + } + + ~LibretroDirGuard() { + LibretroFilesystem::vfs.iface->closedir(handle); + } + +private: + struct retro_vfs_dir_handle* handle; +}; + +bool LibretroFilesystem::GetDirectoryContent(std::string_view path, std::vector& entries) const { + std::string p = ToString(path); + + struct retro_vfs_dir_handle* dir = vfs.iface->opendir(p.c_str(), true); + if (dir == nullptr) { + Output::Debug("Error opening dir {}", p); + return false; + } + LibretroDirGuard guard(dir); + + while (vfs.iface->readdir(dir)) { + const char* name = vfs.iface->dirent_get_name(dir); + if (name == nullptr) { + continue; + } + bool is_directory = vfs.iface->dirent_is_dir(dir); + entries.emplace_back( + name, + is_directory ? DirectoryTree::FileType::Directory : DirectoryTree::FileType::Regular); + } + + return true; +} + +bool LibretroFilesystem::MakeDirectory(std::string_view path, bool) const { + return vfs.iface->mkdir(ToString(path).c_str()) >= 0; +} + +bool LibretroFilesystem::IsFeatureSupported(Feature f) const { + return f == Filesystem::Feature::Write; +} + +std::string LibretroFilesystem::Describe() const { + return fmt::format("[libretro] {}", GetPath()); +} diff --git a/src/platform/libretro/filesystem_libretro.h b/src/platform/libretro/filesystem_libretro.h new file mode 100644 index 0000000000..60ee7da0c7 --- /dev/null +++ b/src/platform/libretro/filesystem_libretro.h @@ -0,0 +1,56 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_FILESYSTEM_LIBRETRO_H +#define EP_FILESYSTEM_LIBRETRO_H + +#include "filesystem.h" +#include "libretro.h" + +#define EP_FILESYSTEM_LIBRETRO_REQUIRED_INTERFACE_VERSION 3U + +/** + * A wrapper around the libretro virtual filesystem interface + */ +class LibretroFilesystem : public Filesystem { +public: + /** + * Initializes a libretro filesystem + */ + explicit LibretroFilesystem(std::string base_path, FilesystemView parent_fs); + + static struct retro_vfs_interface_info vfs; + +protected: + /** + * Implementation of abstract methods + */ + /** @{ */ + bool IsFile(std::string_view path) const override; + bool IsDirectory(std::string_view path, bool follow_symlinks) const override; + bool Exists(std::string_view path) const override; + int64_t GetFilesize(std::string_view path) const override; + std::streambuf* CreateInputStreambuffer(std::string_view path, std::ios_base::openmode mode) const override; + std::streambuf* CreateOutputStreambuffer(std::string_view path, std::ios_base::openmode mode) const override; + bool GetDirectoryContent(std::string_view path, std::vector& entries) const override; + bool MakeDirectory(std::string_view path, bool follow_symlinks) const override; + bool IsFeatureSupported(Feature f) const override; + std::string Describe() const override; + /** @} */ +}; + +#endif diff --git a/src/platform/libretro/ui.cpp b/src/platform/libretro/ui.cpp index e05087ed97..dcde4430f4 100644 --- a/src/platform/libretro/ui.cpp +++ b/src/platform/libretro/ui.cpp @@ -18,6 +18,7 @@ // Headers #include "ui.h" #include "clock.h" +#include "filesystem_libretro.h" #include "bitmap.h" #include "color.h" #include "filefinder.h" @@ -316,6 +317,14 @@ RETRO_API void retro_set_environment(retro_environment_t cb) { { nullptr, nullptr } }; cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables); + + if (LibretroFilesystem::vfs.required_interface_version < EP_FILESYSTEM_LIBRETRO_REQUIRED_INTERFACE_VERSION) { + LibretroFilesystem::vfs.required_interface_version = EP_FILESYSTEM_LIBRETRO_REQUIRED_INTERFACE_VERSION; + if (!cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &LibretroFilesystem::vfs)) { + LibretroFilesystem::vfs.required_interface_version = 0; + LibretroFilesystem::vfs.iface = nullptr; + } + } } RETRO_API void retro_set_video_refresh(retro_video_refresh_t cb) { From ee412e012433c7942e753cab248979c385b59961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 8 Nov 2025 09:51:25 -0500 Subject: [PATCH 2/9] Fix seekpos implementation in filesystem_libretro.cpp --- src/platform/libretro/filesystem_libretro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/libretro/filesystem_libretro.cpp b/src/platform/libretro/filesystem_libretro.cpp index 0bcd463ea2..eaf7cf7d0c 100644 --- a/src/platform/libretro/filesystem_libretro.cpp +++ b/src/platform/libretro/filesystem_libretro.cpp @@ -78,7 +78,7 @@ class LibretroStreamBufIn : public std::streambuf { } std::streambuf::pos_type seekpos(std::streambuf::pos_type pos, std::ios_base::openmode mode) override { - return LibretroFilesystem::vfs.iface->tell(handle); + return seekoff(pos, std::ios_base::beg, mode); } private: From a9359df1aa3e98bfe4a66db29eda61688f8bea9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 8 Nov 2025 09:53:19 -0500 Subject: [PATCH 3/9] Improve code for getting the libretro VFS --- src/platform/libretro/ui.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/platform/libretro/ui.cpp b/src/platform/libretro/ui.cpp index dcde4430f4..d06ca1f311 100644 --- a/src/platform/libretro/ui.cpp +++ b/src/platform/libretro/ui.cpp @@ -318,12 +318,10 @@ RETRO_API void retro_set_environment(retro_environment_t cb) { }; cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables); - if (LibretroFilesystem::vfs.required_interface_version < EP_FILESYSTEM_LIBRETRO_REQUIRED_INTERFACE_VERSION) { - LibretroFilesystem::vfs.required_interface_version = EP_FILESYSTEM_LIBRETRO_REQUIRED_INTERFACE_VERSION; - if (!cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &LibretroFilesystem::vfs)) { - LibretroFilesystem::vfs.required_interface_version = 0; - LibretroFilesystem::vfs.iface = nullptr; - } + struct retro_vfs_interface_info vfs; + vfs.required_interface_version = EP_FILESYSTEM_LIBRETRO_REQUIRED_INTERFACE_VERSION; + if (cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE, &vfs)) { + LibretroFilesystem::vfs = vfs; } } From eb8a6d966b4bd363791aa05f344dcd91629f0159 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 8 Nov 2025 12:59:56 -0500 Subject: [PATCH 4/9] Handle append mode in `LibretroFilesystem::CreateOutputStreambuffer()` --- src/platform/libretro/filesystem_libretro.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/platform/libretro/filesystem_libretro.cpp b/src/platform/libretro/filesystem_libretro.cpp index eaf7cf7d0c..e0e88e4a10 100644 --- a/src/platform/libretro/filesystem_libretro.cpp +++ b/src/platform/libretro/filesystem_libretro.cpp @@ -139,9 +139,21 @@ class LibretroStreamBufOut : public std::streambuf { char* buffer_end = &buffer.back(); }; -std::streambuf* LibretroFilesystem::CreateOutputStreambuffer(std::string_view path, std::ios_base::openmode) const { - struct retro_vfs_file_handle* handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); - return handle == nullptr ? nullptr : new LibretroStreamBufIn(handle); +std::streambuf* LibretroFilesystem::CreateOutputStreambuffer(std::string_view path, std::ios_base::openmode mode) const { + if ((mode & std::ios_base::app) == std::ios_base::app && Exists(path)) { + struct retro_vfs_file_handle* handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING, RETRO_VFS_FILE_ACCESS_HINT_NONE); + if (handle == nullptr) { + return nullptr; + } + if (vfs.iface->seek(handle, 0, RETRO_VFS_SEEK_POSITION_END) == -1) { + vfs.iface->close(handle); + return nullptr; + } + return new LibretroStreamBufIn(handle); + } else { + struct retro_vfs_file_handle* handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); + return handle == nullptr ? nullptr : new LibretroStreamBufIn(handle); + } } // To prevent leaking of the directory handle if an exception is thrown within GetDirectoryContent From ed7a93a5881166fff0029e29cd821ad981f52f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 8 Nov 2025 13:04:42 -0500 Subject: [PATCH 5/9] Don't return false in `LibretroFilesystem::MakeDirectory()` if the directory already exists --- src/platform/libretro/filesystem_libretro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/libretro/filesystem_libretro.cpp b/src/platform/libretro/filesystem_libretro.cpp index e0e88e4a10..347461927a 100644 --- a/src/platform/libretro/filesystem_libretro.cpp +++ b/src/platform/libretro/filesystem_libretro.cpp @@ -195,7 +195,7 @@ bool LibretroFilesystem::GetDirectoryContent(std::string_view path, std::vector< } bool LibretroFilesystem::MakeDirectory(std::string_view path, bool) const { - return vfs.iface->mkdir(ToString(path).c_str()) >= 0; + return vfs.iface->mkdir(ToString(path).c_str()) != -1; } bool LibretroFilesystem::IsFeatureSupported(Feature f) const { From c537ffbb15dc70f244161bd42a7dad74ac2bc1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 8 Nov 2025 15:02:03 -0500 Subject: [PATCH 6/9] Replace `ssize_t` with `int64_t` in filesystem_libretro.cpp to fix x64 Windows builds --- src/platform/libretro/filesystem_libretro.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/platform/libretro/filesystem_libretro.cpp b/src/platform/libretro/filesystem_libretro.cpp index 347461927a..bf92f3eacc 100644 --- a/src/platform/libretro/filesystem_libretro.cpp +++ b/src/platform/libretro/filesystem_libretro.cpp @@ -56,7 +56,7 @@ class LibretroStreamBufIn : public std::streambuf { } int underflow() override { - ssize_t res = LibretroFilesystem::vfs.iface->read(handle, buffer.data(), buffer.size()); + int64_t res = LibretroFilesystem::vfs.iface->read(handle, buffer.data(), buffer.size()); if (res == 0) { return traits_type::eof(); } else if (res < 0) { @@ -110,7 +110,7 @@ class LibretroStreamBufOut : public std::streambuf { } if (c != EOF) { char a = static_cast(c); - ssize_t res = LibretroFilesystem::vfs.iface->write(handle, &a, 1); + int64_t res = LibretroFilesystem::vfs.iface->write(handle, &a, 1); if (res < 1) { return traits_type::eof(); } @@ -124,7 +124,7 @@ class LibretroStreamBufOut : public std::streambuf { if (len == 0) { return 0; } - ssize_t res = LibretroFilesystem::vfs.iface->write(handle, pbase(), len); + int64_t res = LibretroFilesystem::vfs.iface->write(handle, pbase(), len); setp(buffer_start, buffer_end); if (res < len) { return -1; From 4e344bf053d52421d597777ae713aae2f61f0569 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Sat, 8 Nov 2025 15:32:10 -0500 Subject: [PATCH 7/9] Fix a typo in `LibretroFilesystem::GetFilesize()` --- src/platform/libretro/filesystem_libretro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/libretro/filesystem_libretro.cpp b/src/platform/libretro/filesystem_libretro.cpp index bf92f3eacc..c60bebc0fd 100644 --- a/src/platform/libretro/filesystem_libretro.cpp +++ b/src/platform/libretro/filesystem_libretro.cpp @@ -41,7 +41,7 @@ bool LibretroFilesystem::Exists(std::string_view filename) const { int64_t LibretroFilesystem::GetFilesize(std::string_view path) const { int32_t size; - int flags = vfs.iface->stat(ToString(path).c_str(), &flags); + int flags = vfs.iface->stat(ToString(path).c_str(), &size); return flags & RETRO_VFS_STAT_IS_VALID ? size : -1; } From 96764a0a0c277183b1dd84a5ade49850024a1131 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Wed, 12 Nov 2025 12:33:11 -0500 Subject: [PATCH 8/9] Don't leak the file handle if an exception is thrown while creating a `LibretroFilesystem` file stream --- src/platform/libretro/filesystem_libretro.cpp | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/platform/libretro/filesystem_libretro.cpp b/src/platform/libretro/filesystem_libretro.cpp index c60bebc0fd..ad22f578f1 100644 --- a/src/platform/libretro/filesystem_libretro.cpp +++ b/src/platform/libretro/filesystem_libretro.cpp @@ -45,6 +45,26 @@ int64_t LibretroFilesystem::GetFilesize(std::string_view path) const { return flags & RETRO_VFS_STAT_IS_VALID ? size : -1; } +// To prevent leaking of the file handle if an exception is thrown within CreateInputStreambuffer/CreateOutputStreambuffer +class LibretroFileGuard { +public: + LibretroFileGuard(struct retro_vfs_file_handle* handle) noexcept : handle(handle) { + } + + ~LibretroFileGuard() { + if (handle != nullptr) { + LibretroFilesystem::vfs.iface->close(handle); + } + } + + void forget() noexcept { + handle = nullptr; + } + +private: + struct retro_vfs_file_handle* handle; +}; + class LibretroStreamBufIn : public std::streambuf { public: LibretroStreamBufIn(struct retro_vfs_file_handle* handle) : std::streambuf(), handle(handle) { @@ -90,7 +110,13 @@ class LibretroStreamBufIn : public std::streambuf { std::streambuf* LibretroFilesystem::CreateInputStreambuffer(std::string_view path, std::ios_base::openmode) const { struct retro_vfs_file_handle* handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE); - return handle == nullptr ? nullptr : new LibretroStreamBufIn(handle); + if (handle == nullptr) { + return nullptr; + } + LibretroFileGuard guard(handle); + LibretroStreamBufIn* stream = new LibretroStreamBufIn(handle); + guard.forget(); + return stream; } class LibretroStreamBufOut : public std::streambuf { @@ -140,8 +166,9 @@ class LibretroStreamBufOut : public std::streambuf { }; std::streambuf* LibretroFilesystem::CreateOutputStreambuffer(std::string_view path, std::ios_base::openmode mode) const { + struct retro_vfs_file_handle* handle; if ((mode & std::ios_base::app) == std::ios_base::app && Exists(path)) { - struct retro_vfs_file_handle* handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING, RETRO_VFS_FILE_ACCESS_HINT_NONE); + handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING, RETRO_VFS_FILE_ACCESS_HINT_NONE); if (handle == nullptr) { return nullptr; } @@ -149,17 +176,22 @@ std::streambuf* LibretroFilesystem::CreateOutputStreambuffer(std::string_view pa vfs.iface->close(handle); return nullptr; } - return new LibretroStreamBufIn(handle); } else { - struct retro_vfs_file_handle* handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); - return handle == nullptr ? nullptr : new LibretroStreamBufIn(handle); + handle = vfs.iface->open(ToString(path).c_str(), RETRO_VFS_FILE_ACCESS_WRITE, RETRO_VFS_FILE_ACCESS_HINT_NONE); + if (handle == nullptr) { + return nullptr; + } } + LibretroFileGuard guard(handle); + LibretroStreamBufIn* stream = new LibretroStreamBufIn(handle); + guard.forget(); + return stream; } // To prevent leaking of the directory handle if an exception is thrown within GetDirectoryContent class LibretroDirGuard { public: - LibretroDirGuard(struct retro_vfs_dir_handle* handle) : handle(handle) { + LibretroDirGuard(struct retro_vfs_dir_handle* handle) noexcept : handle(handle) { } ~LibretroDirGuard() { From a206aac404c5ad4ef1e9b3583abf8052249bf4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=9A=93?= Date: Wed, 12 Nov 2025 12:44:18 -0500 Subject: [PATCH 9/9] Fix a typo in the previous commit --- src/platform/libretro/filesystem_libretro.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/libretro/filesystem_libretro.cpp b/src/platform/libretro/filesystem_libretro.cpp index ad22f578f1..5532fd066b 100644 --- a/src/platform/libretro/filesystem_libretro.cpp +++ b/src/platform/libretro/filesystem_libretro.cpp @@ -183,7 +183,7 @@ std::streambuf* LibretroFilesystem::CreateOutputStreambuffer(std::string_view pa } } LibretroFileGuard guard(handle); - LibretroStreamBufIn* stream = new LibretroStreamBufIn(handle); + LibretroStreamBufOut* stream = new LibretroStreamBufOut(handle); guard.forget(); return stream; }