Skip to content
Open
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions src/filesystem_root.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -32,6 +36,10 @@ RootFilesystem::RootFilesystem() : Filesystem("", FilesystemView()) {
fs_list.push_back(std::make_pair("content", std::make_unique<SafFilesystem>("", FilesystemView())));
#endif

#ifdef USE_LIBRETRO
fs_list.push_back(std::make_pair("libretro", std::make_unique<LibretroFilesystem>("", 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<NativeFilesystem>("", FilesystemView())));

Expand Down Expand Up @@ -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
Expand Down
239 changes: 239 additions & 0 deletions src/platform/libretro/filesystem_libretro.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

#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(), &size);
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) {
setg(buffer_start, buffer_start, buffer_start);
}

~LibretroStreamBufIn() override {
LibretroFilesystem::vfs.iface->close(handle);
}

int underflow() override {
int64_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<std::streambuf::off_type>(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 seekoff(pos, std::ios_base::beg, mode);
}

private:
struct retro_vfs_file_handle* handle;
std::array<char, 4096> 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);
if (handle == nullptr) {
return nullptr;
}
LibretroFileGuard guard(handle);
LibretroStreamBufIn* stream = new LibretroStreamBufIn(handle);
guard.forget();
return stream;
}

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<char>(c);
int64_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;
}
int64_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<char, 4096> buffer;
char* buffer_start = &buffer.front();
char* buffer_end = &buffer.back();
};

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)) {
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;
}
} else {
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);
LibretroStreamBufOut* stream = new LibretroStreamBufOut(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) noexcept : handle(handle) {
}

~LibretroDirGuard() {
LibretroFilesystem::vfs.iface->closedir(handle);
}

private:
struct retro_vfs_dir_handle* handle;
};

bool LibretroFilesystem::GetDirectoryContent(std::string_view path, std::vector<DirectoryTree::Entry>& 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()) != -1;
}

bool LibretroFilesystem::IsFeatureSupported(Feature f) const {
return f == Filesystem::Feature::Write;
}

std::string LibretroFilesystem::Describe() const {
return fmt::format("[libretro] {}", GetPath());
}
56 changes: 56 additions & 0 deletions src/platform/libretro/filesystem_libretro.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#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<DirectoryTree::Entry>& 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
7 changes: 7 additions & 0 deletions src/platform/libretro/ui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
// Headers
#include "ui.h"
#include "clock.h"
#include "filesystem_libretro.h"
#include "bitmap.h"
#include "color.h"
#include "filefinder.h"
Expand Down Expand Up @@ -316,6 +317,12 @@ RETRO_API void retro_set_environment(retro_environment_t cb) {
{ nullptr, nullptr }
};
cb(RETRO_ENVIRONMENT_SET_VARIABLES, variables);

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;
}
}

RETRO_API void retro_set_video_refresh(retro_video_refresh_t cb) {
Expand Down
Loading