Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
179c247
Use the libretro VFS interface in libretro builds
white-axe Nov 8, 2025
7a48203
Fix seekpos implementation in filesystem_libretro.cpp
white-axe Nov 8, 2025
a553f65
Improve code for getting the libretro VFS
white-axe Nov 8, 2025
52b6393
Handle append mode in `LibretroFilesystem::CreateOutputStreambuffer()`
white-axe Nov 8, 2025
ed6f97e
Don't return false in `LibretroFilesystem::MakeDirectory()` if the di…
white-axe Nov 8, 2025
04e6b34
Replace `ssize_t` with `int64_t` in filesystem_libretro.cpp to fix x6…
white-axe Nov 8, 2025
2853c2f
Fix a typo in `LibretroFilesystem::GetFilesize()`
white-axe Nov 8, 2025
63129c3
Don't leak the file handle if an exception is thrown while creating a…
white-axe Nov 12, 2025
0250dd1
Fix a typo in the previous commit
white-axe Nov 12, 2025
04c6070
Update libretro common to dfccc5
Ghabry Jul 13, 2026
315372a
Fix warnings
Ghabry Jul 13, 2026
85c19d6
Android: Add folder creation to SAF
Ghabry Jul 13, 2026
3de2575
Filesystem: Don't strip the namespace when libretro is used
Ghabry Jul 13, 2026
62c5aea
Move recursive directory creation code into the Filesystem class
Ghabry Jul 13, 2026
419906f
Recursive Direction creating is now much smarter
Ghabry Jul 13, 2026
2ac30c1
Use the 64-bit stat function from libretro VFS v4 if available
white-axe Jul 13, 2026
8a0f591
Fix the seek implementation in LibretroStreamBufIn
white-axe Jul 13, 2026
c4a4b2f
Refactor Startup when an Archive is loaded
Ghabry Jul 29, 2026
9a11b4b
Reset Save Directory when a new game is loaded
Ghabry Jul 29, 2026
4f4b6b7
Android: Fixes to make the new Create function also work with our App
Ghabry Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1449,6 +1449,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
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ Java_org_easyrpg_player_settings_SettingsFontActivity_DrawText(JNIEnv *env, jcla
jbyte* buffer_raw = env->GetByteArrayElements(buffer_array, 0);

Bitmap::SetFormat(Bitmap::ChooseFormat(format_R8G8B8A8_a().format()));
auto sys = Cache::System(CACHE_DEFAULT_BITMAP);
auto sys = Cache::System(CACHE_DEFAULT_BITMAP, false);
BitmapRef draw_area = Bitmap::Create(reinterpret_cast<void*>(buffer_raw), width, height, 0, format_R8G8B8A8_a().format());
draw_area->Fill(Color(0, 0, 0, 255));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,13 @@ public static Game fromCacheEntry(Context context, String cache) {
}

String savePath = entries[0];
DocumentFile gameFolder = DocumentFile.fromTreeUri(context, Uri.parse(entries[1]));
DocumentFile gameFolder = null;
try {
gameFolder = DocumentFile.fromTreeUri(context, Uri.parse(entries[1]));
} catch (IllegalArgumentException e) {
return null;
}

if (gameFolder == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,13 @@ private void scanRootFolder(Activity activity, Uri folderURI) {
myTextView.setText(String.format("%s (%d/%d)", name, j + 1, names.size()));
});

Game[] candidates = findGames(fileURIs.get(i).toString(), names.get(i));
String fileURI = fileURIs.get(i).toString();
int encoded_slash_pos = fileURI.lastIndexOf("%2F");
// Encode everything from the last %2F so our native code works properly
String toDecode = fileURI.substring(encoded_slash_pos);
toDecode = Uri.decode(toDecode);

Game[] candidates = findGames(fileURI.substring(0, encoded_slash_pos) + toDecode, names.get(i));

if (candidates == null) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ public long getFilesize() {
return metaFileSize;
}

public boolean makeDirectory() {
if (exists()) {
return isDirectory();
}

// To create it the parent directory must be obtained
String full_path = rootUri.toString();
int last_slash = full_path.lastIndexOf("%2F");
if (last_slash == -1) {
return false;
}
String directory = full_path.substring(0, last_slash);
String filename = full_path.substring(last_slash + 3);
filename = Uri.decode(filename);

DocumentFile df = DocumentFile.fromTreeUri(context, Uri.parse(directory));
if (df == null || !df.exists()) {
return false;
}
df = df.createDirectory(filename);
return df != null && df.exists();
}

public int createInputFileDescriptor() {
// No difference between read mode and binary read mode
try (ParcelFileDescriptor fd = context.getContentResolver().openFileDescriptor(rootUri, "r")) {
Expand Down
2 changes: 1 addition & 1 deletion builds/libretro/libretro-common
Submodule libretro-common updated 360 files
47 changes: 46 additions & 1 deletion src/filefinder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ FilesystemView FileFinder::Save() {
std::reverse(comps.begin(), comps.end());
std::string save_path = MakePath(lcf::MakeSpan(comps));
if (!parent.IsDirectory(save_path, true)) {
parent.MakeDirectory(save_path, true);
if (!parent.MakeDirectory(save_path, true)) {
Output::Debug("Save FS: MakeDirectory {} Failed", save_path);
}
}
redir = parent.Subtree(save_path);

Expand Down Expand Up @@ -225,6 +227,49 @@ std::vector<std::string> FileFinder::SplitPath(std::string_view path) {
return Utils::Tokenize(path, f);
}

std::vector<std::string> FileFinder::SplitPathPrefixes(std::string_view path) {
// Check if the path contains a namespace and save it
std::string ns;
auto ns_pos = path.find("://");
if (ns_pos != std::string::npos) {
ns = path.substr(0, ns_pos + 3);
path = path.substr(ns_pos + 3);
}

auto components = FileFinder::SplitPath(path);
std::string cur_path;
if (StartsWith(path, "/")) {
cur_path += "/";
}

// Create all the subpaths
// This will create e.g. for /a/b/c/d:
// ["/a", "/a/b", "/a/b/c", "/a/b/c/d"]
std::vector<std::string> full_paths;
for (const auto& comp : components) {
if (comp.empty() || comp == ".") {
continue;
}

cur_path = FileFinder::MakePath(cur_path, comp);
full_paths.push_back(cur_path);
}

if (EndsWith(path, "/")) {
full_paths.back() += "/";
}

// Prepend namespace to all paths
if (!ns.empty()) {
for (auto& s : full_paths) {
s = ns + s;
}
full_paths.insert(full_paths.begin(), ns);
}

return full_paths;
}

std::pair<std::string, std::string> FileFinder::GetPathAndFilename(std::string_view path) {
if (path.empty()) {
return {"", ""};
Expand Down
10 changes: 10 additions & 0 deletions src/filefinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,16 @@ namespace FileFinder {
*/
std::vector<std::string> SplitPath(std::string_view path);

/**
* Constructs a list of all the paths leading to the file.
* e.g. for /a/b/c/d it creates
* ["/a", "/a/b", "/a/b/c", "/a/b/c/d"]
*
* @param path Path to build prefixes from
* @return all path prefixes
*/
std::vector<std::string> SplitPathPrefixes(std::string_view path);

/**
* Splits a path into path and filename.
*
Expand Down
107 changes: 70 additions & 37 deletions src/filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,62 +91,48 @@ FilesystemView Filesystem::Create(std::string_view path) const {
// When the path doesn't exist check if the path contains a file that can
// be handled by another filesystem
if (!IsDirectory(path, true)) {
std::string dir_of_file;
std::string path_prefix;
std::vector<std::string> components = FileFinder::SplitPath(path);

// TODO this should probably move to a static function in the FS classes
// Search for the deepest directory
int i = 0;
for (const auto& comp : components) {
std::vector<std::string> components = FileFinder::SplitPathPrefixes(path);
// Prepend empty path for the root directory if not present
if (components.empty() || components.front() != "") {
components.insert(components.begin(), "");
}

size_t archive_idx = components.size();
for (auto it = components.rbegin(); it != components.rend(); ++it) {
const std::string& p = *it;
// Do not check stuff that looks like drives, such as C:, ux0: or sd:
// Some systems do not consider them directories
if (i > 0 || (!comp.empty() && comp.back() != ':')) {
if (!IsDirectory(FileFinder::MakePath(dir_of_file, comp), true)) {
if (archive_idx > 1 || (!p.empty() && p.back() != ':')) {
if (IsDirectory(p, true)) {
break;
}
} else {
break;
}
dir_of_file += comp + "/";
++i;
}

if (!dir_of_file.empty()) {
dir_of_file.pop_back();
--archive_idx;
}

// The next component must be a file
// search for known file extensions and "do magic"
std::string internal_path;
bool handle_internal = false;
for (const auto& comp : lcf::MakeSpan(components).subspan(i)) {
if (handle_internal) {
internal_path += comp + "/";
} else {
path_prefix += comp + "/";
if (FileFinder::IsSupportedArchiveExtension(comp)) {
path_prefix.pop_back();
handle_internal = true;
}
}
}
std::string archive_full_path = components[archive_idx];
std::string archive_parent_path = components[archive_idx - 1];
std::string archive_name = FileFinder::GetPathAndFilename(archive_full_path).second;
std::string internal_path = FileFinder::GetPathInsidePath(archive_full_path, components.back());

if (!handle_internal) {
if (!IsFile(archive_full_path) || !FileFinder::IsSupportedArchiveExtension(archive_name)) {
// No supported archive type found
return {};
}

if (!internal_path.empty()) {
internal_path.pop_back();
}

std::shared_ptr<Filesystem> filesystem = std::make_shared<ZipFilesystem>(path_prefix, Subtree(dir_of_file));
std::shared_ptr<Filesystem> filesystem = std::make_shared<ZipFilesystem>(archive_name, Subtree(archive_parent_path));
#if HAVE_LHASA
if (!filesystem->IsValid()) {
filesystem = std::make_shared<LzhFilesystem>(path_prefix, Subtree(dir_of_file));
filesystem = std::make_shared<LzhFilesystem>(archive_name, Subtree(archive_parent_path));
}
#endif
if (!filesystem->IsValid()) {
filesystem = std::make_shared<TarFilesystem>(path_prefix, Subtree(dir_of_file));
filesystem = std::make_shared<TarFilesystem>(archive_name, Subtree(archive_parent_path));
}
if (!filesystem->IsValid()) {
return {};
Expand Down Expand Up @@ -184,7 +170,54 @@ FilesystemView Filesystem::Subtree(std::string sub_path) const {
return FilesystemView(shared_from_this(), sub_path);
}

bool Filesystem::MakeDirectory(std::string_view, bool) const {
bool Filesystem::MakeDirectory(std::string_view path, bool follow_symlinks) const {
if (IsDirectory(path, follow_symlinks)) {
return true;
}

auto full_paths = FileFinder::SplitPathPrefixes(path);

if (full_paths.empty()) {
return true;
}

// Do not check stuff that looks like drives, such as C:, ux0: or sd:
// Some systems do not consider them directories
if (full_paths[0].back() == ':') {
full_paths.erase(full_paths.begin());
}

// Traverse from the longest subpath and find the first one that exists
// e.g. assuming "/a/b" exists:
// First it checks "/a/b/c/d" (*), then "/a/b/c" (*), then ends at "/a/b"
std::vector<std::string_view> to_create;
for (auto it = full_paths.rbegin(); it != full_paths.rend(); ++it) {
const std::string& p = *it;

if (IsDirectory(p, follow_symlinks)) {
break;
}

if (Exists(p)) {
// Exists but not a directory
return false;
}

to_create.push_back(p);
}

// (*) These paths will be created here
for (auto it = to_create.rbegin(); it != to_create.rend(); ++it) {
if (!vMakeDirectory(*it, follow_symlinks)) {
Output::Debug("MakeDirectory {} failed", *it);
return false;
}
}

return true;
}

bool Filesystem::vMakeDirectory(std::string_view, bool) const {
return false;
}

Expand Down
17 changes: 12 additions & 5 deletions src/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@
#include <cassert>
#include <cstdint>
#include <memory>
#include <istream>
#include <ostream>
#include <unordered_map>
#include <vector>
#include "directory_tree.h"

Expand Down Expand Up @@ -211,6 +208,16 @@ class Filesystem : public std::enable_shared_from_this<Filesystem> {
/** Implicit conversion to FilesystemView */
operator FilesystemView();

/**
* Recursively creates a new directory.
* Not all filesystems support directory creation.
*
* @param dir Directory to create.
* @param follow_symlinks Whether to follow symlinks (if supported by this filesystem)
* @return true when the directory was created or already exists.
*/
bool MakeDirectory(std::string_view path, bool follow_symlinks) const;

/**
* Abstract methods to be implemented by filesystems.
*/
Expand All @@ -219,7 +226,7 @@ class Filesystem : public std::enable_shared_from_this<Filesystem> {
virtual bool IsDirectory(std::string_view path, bool follow_symlinks) const = 0;
virtual bool Exists(std::string_view path) const = 0;
virtual int64_t GetFilesize(std::string_view path) const = 0;
virtual bool MakeDirectory(std::string_view dir, bool follow_symlinks) const;
virtual bool vMakeDirectory(std::string_view path, bool follow_symlinks) const;
virtual bool IsFeatureSupported(Feature f) const;
virtual std::string Describe() const = 0;
/** @} */
Expand Down Expand Up @@ -457,7 +464,7 @@ class FilesystemView {
*
* @param dir Directory to create.
* @param follow_symlinks Whether to follow symlinks (if supported by this filesystem)
* @return true when the path was created
* @return true when the directory was created or already exists.
*/
bool MakeDirectory(std::string_view dir, bool follow_symlinks) const;

Expand Down
2 changes: 1 addition & 1 deletion src/filesystem_hook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ int64_t HookFilesystem::GetFilesize(std::string_view path) const {
return GetParent().GetFilesize(path);
}

bool HookFilesystem::MakeDirectory(std::string_view dir, bool follow_symlinks) const {
bool HookFilesystem::vMakeDirectory(std::string_view dir, bool follow_symlinks) const {
return GetParent().MakeDirectory(dir, follow_symlinks);
}

Expand Down
2 changes: 1 addition & 1 deletion src/filesystem_hook.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class HookFilesystem : public Filesystem {
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;
bool MakeDirectory(std::string_view dir, bool follow_symlinks) const override;
bool vMakeDirectory(std::string_view dir, bool follow_symlinks) const override;
bool IsFeatureSupported(Feature f) const override;
std::string Describe() const override;
/** @} */
Expand Down
2 changes: 1 addition & 1 deletion src/filesystem_native.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ bool NativeFilesystem::GetDirectoryContent(std::string_view path, std::vector<Di
return true;
}

bool NativeFilesystem::MakeDirectory(std::string_view path, bool follow_symlinks) const {
bool NativeFilesystem::vMakeDirectory(std::string_view path, bool follow_symlinks) const {
return Platform::File(ToString(path)).MakeDirectory(follow_symlinks);
}

Expand Down
2 changes: 1 addition & 1 deletion src/filesystem_native.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class NativeFilesystem : public Filesystem {
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 vMakeDirectory(std::string_view path, bool follow_symlinks) const override;
bool IsFeatureSupported(Feature f) const override;
std::string Describe() const override;
/** @} */
Expand Down
Loading
Loading