Improve save directory creation algorithm in libretro builds

Before, if the game tried to create a save file, missing parent
directories would always be created because they could possibly exist
only in the game directory and not in the save directory, and we
wouldn't know due to the union mounting of the save and game
directories. But this is inconsistent with the behaviour of file
creation, where it should fail if parent directories don't exist.

The behaviour has been changed to only create parent directories if the
parent directories already exist. I know that sounds strange, but if the
parent directories exist, it could be that they only exist in the game
directory but not the save directory due to the union mounting, so we
need to create the parent directories, which will be created in the save
directory due to it being set as the write directory in PhysFS.
This commit is contained in:
刘皓 2025-05-07 22:13:45 -04:00
parent 66256e9156
commit d71cd242c7
No known key found for this signature in database
GPG key ID: 7901753DB465B711
3 changed files with 25 additions and 11 deletions

View file

@ -813,7 +813,7 @@ extern "C" u32 w2c_wasi__snapshot__preview1_path_open(wasi_t *wasi, u32 fd, u32
write_path_prefix = nullptr;
}
struct FileSystem::File *handle = new FileSystem::File(*mkxp_retro::fs, new_path.c_str(), write_path_prefix, truncate, !exists);
struct FileSystem::File *handle = new FileSystem::File(*mkxp_retro::fs, new_path.c_str(), write_path_prefix, truncate, exists);
// Check for errors opening the read handle and/or write handle
if (!handle->is_open() || (needs_write && writable && !handle->is_write_open())) {

View file

@ -718,7 +718,17 @@ void FileSystem::openReadRaw(SDL_RWops &ops, const char *filename,
#endif // MKXPZ_RETRO
#ifdef MKXPZ_RETRO
FileSystem::File::File(FileSystem &fs, const char *read_path, const char *write_path_prefix, bool truncate, bool mkdir) {
static std::string pop_last_path_element(const char *path) {
std::string parent(path);
size_t last_slash_index = parent.find_last_of('/');
if (last_slash_index == std::string::npos) {
last_slash_index = 0;
}
parent = parent.substr(0, last_slash_index);
return parent;
}
FileSystem::File::File(FileSystem &fs, const char *read_path, const char *write_path_prefix, bool truncate, unsigned char exists) {
_path = fs.normalize(read_path, false, true);
if (write_path_prefix != nullptr) {
@ -727,11 +737,15 @@ FileSystem::File::File(FileSystem &fs, const char *read_path, const char *write_
if (_path.length() >= prefix_length && !strncmp(_path.c_str(), write_path_prefix, prefix_length)) {
const char *suffix = _path.c_str() + prefix_length;
if (mkdir) {
std::string suffix_parent(suffix);
size_t last_slash_index = suffix_parent.find_last_of('/');
if (last_slash_index != std::string::npos) {
suffix_parent = suffix_parent.substr(0, last_slash_index);
// If the path doesn't exist but its parent does,
// create the parent directory in the PhysFS write directory
// since it might only exist in PhysFS's read-only search path
if (exists == 0 || (exists != 1 && !PHYSFS_exists(read_path))) {
std::string suffix_parent = pop_last_path_element(suffix);
if (suffix_parent.empty() || suffix_parent.front() != '/') {
suffix_parent = '/' + suffix_parent;
}
if (suffix_parent != "/" && PHYSFS_exists((write_path_prefix + suffix_parent).c_str())) {
PHYSFS_mkdir(suffix_parent.c_str());
}
}

View file

@ -54,12 +54,12 @@ public:
PHYSFS_ErrorCode write_error;
public:
// Opens a file using PhysFS. If the file doesn't already exist, it will be created.
// Opens a file using PhysFS. If the file doesn't already exist and write_path_prefix is non-null, it will be created.
// read_path: Path to open the read handle for the file from.
// write_path_prefix: Null to skip opening a write handle for the file, otherwise the write handle will be opened from the path corresponding to read_path with write_path_prefix removed from the beginning.
// truncate: Whether or not to delete the contents of the file. Does nothing unless write_path_prefix is non-null.
// mkdir: Whether or not to create the parent directories of the file if they don't exist. Does nothing unless write_path_prefix is non-null.
File(FileSystem &fs, const char *read_path, const char *write_path_prefix = nullptr, bool truncate = false, bool mkdir = false);
// truncate: Whether or not to delete the contents of the file after opening it. Does nothing unless write_path_prefix is non-null.
// exists: If you already know whether or not the file you're opening exists, you can set this to 1 if it exists or 0 if it doesn't exist to reduce the amount of file system calls needed. Any values other than 1 or 0 mean you don't know.
File(FileSystem &fs, const char *read_path, const char *write_path_prefix = nullptr, bool truncate = false, unsigned char exists = -1);
File(const struct File &) = delete;
File(struct File &&) noexcept = delete;
struct File operator=(const struct File &) = delete;