android: Fully use raw FS access on vanilla builds

This commit is contained in:
PabloMK7 2026-02-27 19:58:33 +01:00 committed by OpenSauce
parent 0c624f16a7
commit e87635095a

View file

@ -145,6 +145,23 @@ static void StripTailDirSlashes(std::string& fname) {
fname.resize(i); fname.resize(i);
} }
#if defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
namespace {
std::string AndroidTranslateFilename(const std::string& file) {
std::optional<std::string> userDirLocation = AndroidStorage::GetUserDirectory();
if (userDirLocation) {
return *userDirLocation + file;
}
return "";
}
bool AndroidCanUseRawFS() {
return AndroidStorage::GetBuildFlavor() != AndroidStorage::AndroidBuildFlavors::GOOGLEPLAY;
}
} // anonymous namespace
#endif
bool Exists(const std::string& filename) { bool Exists(const std::string& filename) {
std::string copy(filename); std::string copy(filename);
StripTailDirSlashes(copy); StripTailDirSlashes(copy);
@ -157,7 +174,13 @@ bool Exists(const std::string& filename) {
int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
int result = AndroidStorage::FileExists(filename) ? 0 : -1; int result;
if (AndroidCanUseRawFS()) {
struct stat file_info;
result = stat(AndroidTranslateFilename(copy).c_str(), &file_info);
} else {
result = AndroidStorage::FileExists(filename) ? 0 : -1;
}
#else #else
struct stat file_info; struct stat file_info;
int result = stat(copy.c_str(), &file_info); int result = stat(copy.c_str(), &file_info);
@ -167,9 +190,6 @@ bool Exists(const std::string& filename) {
} }
bool IsDirectory(const std::string& filename) { bool IsDirectory(const std::string& filename) {
#if defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
return AndroidStorage::IsDirectory(filename);
#endif
std::string copy(filename); std::string copy(filename);
StripTailDirSlashes(copy); StripTailDirSlashes(copy);
@ -181,6 +201,14 @@ bool IsDirectory(const std::string& filename) {
copy += DIR_SEP_CHR; copy += DIR_SEP_CHR;
int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
struct stat file_info;
int result;
if (AndroidCanUseRawFS()) {
result = stat(AndroidTranslateFilename(copy).c_str(), &file_info);
} else {
return AndroidStorage::IsDirectory(filename);
}
#else #else
struct stat file_info; struct stat file_info;
int result = stat(copy.c_str(), &file_info); int result = stat(copy.c_str(), &file_info);
@ -239,9 +267,16 @@ bool Delete(const std::string& filename) {
return false; return false;
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
if (!AndroidStorage::DeleteDocument(filename)) { if (AndroidCanUseRawFS()) {
LOG_ERROR(Common_Filesystem, "unlink failed on {}", filename); if (unlink(AndroidTranslateFilename(filename).c_str()) == -1) {
return false; LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
return false;
}
} else {
if (!AndroidStorage::DeleteDocument(filename)) {
LOG_ERROR(Common_Filesystem, "unlink failed on {}", filename);
return false;
}
} }
#else #else
if (unlink(filename.c_str()) == -1) { if (unlink(filename.c_str()) == -1) {
@ -266,23 +301,39 @@ bool CreateDir(const std::string& path) {
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error); LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
return false; return false;
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
std::string directory = path; if (AndroidCanUseRawFS()) {
std::string filename = path; if (mkdir(AndroidTranslateFilename(path).c_str(), 0755) == 0)
if (Common::EndsWith(path, "/")) { return true;
directory = GetParentPath(path);
filename = GetParentPath(path); int err = errno;
}
directory = GetParentPath(directory); if (err == EEXIST) {
filename = GetFilename(filename); LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path);
// If directory path is empty, set it to root. return true;
if (directory.empty()) { }
directory = "/";
} LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err));
if (!AndroidStorage::CreateDir(directory, filename)) {
LOG_ERROR(Common_Filesystem, "mkdir failed on {}", path);
return false; return false;
}; } else {
return true; std::string directory = path;
std::string filename = path;
if (Common::EndsWith(path, "/")) {
directory = GetParentPath(path);
filename = GetParentPath(path);
}
directory = GetParentPath(directory);
filename = GetFilename(filename);
// If directory path is empty, set it to root.
if (directory.empty()) {
directory = "/";
}
if (!AndroidStorage::CreateDir(directory, filename)) {
LOG_ERROR(Common_Filesystem, "mkdir failed on {}", path);
return false;
};
return true;
}
#else #else
if (mkdir(path.c_str(), 0755) == 0) if (mkdir(path.c_str(), 0755) == 0)
return true; return true;
@ -353,8 +404,13 @@ bool DeleteDir(const std::string& filename) {
if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str())) if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str()))
return true; return true;
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
if (AndroidStorage::DeleteDocument(filename)) if (AndroidCanUseRawFS()) {
return true; if (rmdir(AndroidTranslateFilename(filename).c_str()) == 0)
return true;
} else {
if (AndroidStorage::DeleteDocument(filename))
return true;
}
#else #else
if (rmdir(filename.c_str()) == 0) if (rmdir(filename.c_str()) == 0)
return true; return true;
@ -372,16 +428,14 @@ bool Rename(const std::string& srcFullPath, const std::string& destFullPath) {
return true; return true;
} }
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
// srcFullPath and destFullPath are relative to the user directory if (AndroidCanUseRawFS()) {
if (AndroidStorage::GetBuildFlavor() == AndroidStorage::AndroidBuildFlavors::GOOGLEPLAY) { if (rename(AndroidTranslateFilename(srcFullPath).c_str(),
if (AndroidStorage::MoveAndRenameFile(srcFullPath, destFullPath)) AndroidTranslateFilename(destFullPath).c_str()) == 0) {
return true; return true;
}
} else { } else {
std::optional<std::string> userDirLocation = AndroidStorage::GetUserDirectory(); if (AndroidStorage::MoveAndRenameFile(srcFullPath, destFullPath)) {
if (userDirLocation && rename((*userDirLocation + srcFullPath).c_str(),
(*userDirLocation + destFullPath).c_str()) == 0) {
AndroidStorage::UpdateDocumentLocation(srcFullPath, destFullPath);
// ^ TODO: This shouldn't fail, but what should we do if it somehow does?
return true; return true;
} }
} }
@ -404,52 +458,62 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
GetLastErrorMsg()); GetLastErrorMsg());
return false; return false;
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
return AndroidStorage::CopyFile(srcFilename, std::string(GetParentPath(destFilename)),
std::string(GetFilename(destFilename)));
#else #else
auto copy_files = [](const std::string& src, const std::string& dst) -> bool {
// Open input file
FILE* input = FOPEN(src.c_str(), "rb");
if (!input) {
LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", src, dst,
GetLastErrorMsg());
return false;
}
SCOPE_EXIT({ FCLOSE(input); });
// Open input file // open output file
FILE* input = FOPEN(srcFilename.c_str(), "rb"); FILE* output = FOPEN(dst.c_str(), "wb");
if (!input) { if (!output) {
LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename, LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", src, dst,
destFilename, GetLastErrorMsg()); GetLastErrorMsg());
return false; return false;
} }
SCOPE_EXIT({ FCLOSE(input); }); SCOPE_EXIT({ FCLOSE(output); });
// open output file // copy loop
FILE* output = FOPEN(destFilename.c_str(), "wb"); std::array<char, 1024> buffer;
if (!output) { while (!FEOF(input)) {
LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename, // read input
destFilename, GetLastErrorMsg()); std::size_t rnum = FREAD(buffer.data(), sizeof(char), buffer.size(), input);
return false; if (rnum != buffer.size()) {
} if (FERROR(input) != 0) {
SCOPE_EXIT({ FCLOSE(output); }); LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}", src,
dst, GetLastErrorMsg());
return false;
}
}
// copy loop // write output
std::array<char, 1024> buffer; std::size_t wnum = FWRITE(buffer.data(), sizeof(char), rnum, output);
while (!FEOF(input)) { if (wnum != rnum) {
// read input LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", src, dst,
std::size_t rnum = FREAD(buffer.data(), sizeof(char), buffer.size(), input); GetLastErrorMsg());
if (rnum != buffer.size()) {
if (FERROR(input) != 0) {
LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}",
srcFilename, destFilename, GetLastErrorMsg());
return false; return false;
} }
} }
// write output return true;
std::size_t wnum = FWRITE(buffer.data(), sizeof(char), rnum, output); };
if (wnum != rnum) {
LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename,
destFilename, GetLastErrorMsg());
return false;
}
}
return true; #if defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
if (AndroidCanUseRawFS()) {
return copy_files(AndroidTranslateFilename(srcFilename),
AndroidTranslateFilename(destFilename));
} else {
return AndroidStorage::CopyFile(srcFilename, std::string(GetParentPath(destFilename)),
std::string(GetFilename(destFilename)));
}
#else
return copy_files(srcFilename, destFilename);
#endif
#endif #endif
} }
@ -470,9 +534,15 @@ u64 GetSize(const std::string& filename) {
struct _stat64 buf; struct _stat64 buf;
if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0) if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0)
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
u64 result = AndroidStorage::GetSize(filename); if (AndroidCanUseRawFS()) {
LOG_TRACE(Common_Filesystem, "{}: {}", filename, result); if (stat(AndroidTranslateFilename(filename).c_str(), &buf) == 0) {
return result; return buf.st_size;
}
} else {
u64 result = AndroidStorage::GetSize(filename);
LOG_TRACE(Common_Filesystem, "{}: {}", filename, result);
return result;
}
#else #else
if (stat(filename.c_str(), &buf) == 0) if (stat(filename.c_str(), &buf) == 0)
#endif #endif
@ -520,68 +590,97 @@ bool CreateEmptyFile(const std::string& filename) {
return true; return true;
} }
namespace {
#ifdef _WIN32
std::optional<std::vector<std::string>> ListDirectoryEntries(const std::string& directory) {
std::vector<std::string> entries;
WIN32_FIND_DATAW ffd;
const std::wstring search_path = Common::UTF8ToUTF16W(directory + "\\*");
HANDLE handle = FindFirstFileW(search_path.c_str(), &ffd);
if (handle == INVALID_HANDLE_VALUE) {
return std::nullopt;
}
do {
entries.emplace_back(Common::UTF16ToUTF8(ffd.cFileName));
} while (FindNextFileW(handle, &ffd) != 0);
FindClose(handle);
return entries;
}
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
std::optional<std::vector<std::string>> ListDirectoryEntries(const std::string& directory) {
if (AndroidCanUseRawFS()) {
std::vector<std::string> entries;
DIR* dirp = opendir(AndroidTranslateFilename(directory).c_str());
if (!dirp)
return std::nullopt;
while (dirent* entry = readdir(dirp)) {
entries.emplace_back(entry->d_name);
}
closedir(dirp);
return entries;
} else {
return AndroidStorage::GetFilesName(directory);
}
}
#else
std::optional<std::vector<std::string>> ListDirectoryEntries(const std::string& directory) {
std::vector<std::string> entries;
DIR* dirp = opendir(directory.c_str());
if (!dirp)
return std::nullopt;
while (dirent* entry = readdir(dirp)) {
entries.emplace_back(entry->d_name);
}
closedir(dirp);
return entries;
}
#endif
} // anonymous namespace
bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory, bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
DirectoryEntryCallable callback) { DirectoryEntryCallable callback) {
LOG_TRACE(Common_Filesystem, "directory {}", directory); LOG_TRACE(Common_Filesystem, "directory {}", directory);
// How many files + directories we found const auto entries = ListDirectoryEntries(directory);
u64 found_entries = 0; if (!entries.has_value()) {
// Save the status of callback function
bool callback_error = false;
#ifdef _WIN32
// Find the first file in the directory.
WIN32_FIND_DATAW ffd;
HANDLE handle_find = FindFirstFileW(Common::UTF8ToUTF16W(directory + "\\*").c_str(), &ffd);
if (handle_find == INVALID_HANDLE_VALUE) {
FindClose(handle_find);
return false; return false;
} }
// windows loop
do {
const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
// android loop
auto result = AndroidStorage::GetFilesName(directory);
for (auto virtual_name : result) {
#else
DIR* dirp = opendir(directory.c_str());
if (!dirp)
return false;
// non windows loop u64 found_entries = 0;
while (struct dirent* result = readdir(dirp)) {
const std::string virtual_name(result->d_name);
#endif
for (const std::string& virtual_name : *entries) {
if (virtual_name == "." || virtual_name == "..") if (virtual_name == "." || virtual_name == "..")
continue; continue;
u64 ret_entries = 0; u64 ret_entries = 0;
if (!callback(&ret_entries, directory, virtual_name)) { if (!callback(&ret_entries, directory, virtual_name))
callback_error = true; return false;
break;
}
found_entries += ret_entries; found_entries += ret_entries;
#ifdef _WIN32
} while (FindNextFileW(handle_find, &ffd) != 0);
FindClose(handle_find);
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
} }
#else
}
closedir(dirp);
#endif
if (callback_error)
return false;
// num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it
if (num_entries_out != nullptr) if (num_entries_out)
*num_entries_out = found_entries; *num_entries_out = found_entries;
return true; return true;
} }
@ -658,48 +757,39 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion)
void CopyDir([[maybe_unused]] const std::string& source_path, void CopyDir([[maybe_unused]] const std::string& source_path,
[[maybe_unused]] const std::string& dest_path) { [[maybe_unused]] const std::string& dest_path) {
#ifndef _WIN32
if (source_path == dest_path) if (source_path == dest_path)
return; return;
if (!FileUtil::Exists(source_path)) if (!FileUtil::Exists(source_path))
return; return;
if (!FileUtil::Exists(dest_path)) if (!FileUtil::Exists(dest_path))
FileUtil::CreateFullPath(dest_path); FileUtil::CreateFullPath(dest_path);
#if defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) const auto entries = ListDirectoryEntries(source_path);
auto result = AndroidStorage::GetFilesName(source_path); if (!entries.has_value() || (*entries).empty())
for (auto virtualName : result) {
#else
DIR* dirp = opendir(source_path.c_str());
if (!dirp)
return; return;
while (struct dirent* result = readdir(dirp)) { for (const std::string& virtual_name : *entries) {
const std::string virtualName(result->d_name); if (virtual_name == "." || virtual_name == "..")
#endif // ANDROID
// check for "." and ".."
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0')))
continue; continue;
std::string source, dest; std::string source = source_path + virtual_name;
source = source_path + virtualName; std::string dest = dest_path + virtual_name;
dest = dest_path + virtualName;
if (IsDirectory(source)) { if (IsDirectory(source)) {
source += '/'; source += '/';
dest += '/'; dest += '/';
if (!FileUtil::Exists(dest)) if (!FileUtil::Exists(dest))
FileUtil::CreateFullPath(dest); FileUtil::CreateFullPath(dest);
CopyDir(source, dest);
} else if (!FileUtil::Exists(dest))
FileUtil::Copy(source, dest);
}
#if !(defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)) CopyDir(source, dest);
closedir(dirp); } else {
#endif // ANDROID if (!FileUtil::Exists(dest))
#endif // _WIN32 FileUtil::Copy(source, dest);
}
}
} }
std::optional<std::string> GetCurrentDir() { std::optional<std::string> GetCurrentDir() {
@ -1234,34 +1324,37 @@ bool IOFile::Open() {
m_good = m_file != nullptr; m_good = m_file != nullptr;
#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)
// Check whether filepath is startsWith content if (AndroidCanUseRawFS()) {
AndroidStorage::AndroidOpenMode android_open_mode = AndroidStorage::ParseOpenmode(openmode); m_file = FOPEN(AndroidTranslateFilename(filename).c_str(), openmode.c_str());
if (android_open_mode == AndroidStorage::AndroidOpenMode::WRITE || } else {
android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE || // Check whether filepath is startsWith content
android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_APPEND || AndroidStorage::AndroidOpenMode android_open_mode = AndroidStorage::ParseOpenmode(openmode);
android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_TRUNCATE || if (android_open_mode == AndroidStorage::AndroidOpenMode::WRITE ||
android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_TRUNCATE || android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE ||
android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_APPEND) { android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_APPEND ||
if (!FileUtil::Exists(filename)) { android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_TRUNCATE ||
std::string directory(GetParentPath(filename)); android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_TRUNCATE ||
std::string display_name(GetFilename(filename)); android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_APPEND) {
if (!AndroidStorage::CreateFile(directory, display_name)) { if (!FileUtil::Exists(filename)) {
m_good = m_file != nullptr; std::string directory(GetParentPath(filename));
return m_good; std::string display_name(GetFilename(filename));
if (!AndroidStorage::CreateFile(directory, display_name)) {
m_good = m_file != nullptr;
return m_good;
}
}
}
m_fd = AndroidStorage::OpenContentUri(filename, android_open_mode);
if (m_fd != -1) {
int error_num = 0;
m_file = fdopen(m_fd, openmode.c_str());
error_num = errno;
if (error_num != 0 && m_file == nullptr) {
LOG_ERROR(Common_Filesystem, "Error on file: {}, error: {}", filename,
strerror(error_num));
} }
} }
} }
m_fd = AndroidStorage::OpenContentUri(filename, android_open_mode);
if (m_fd != -1) {
int error_num = 0;
m_file = fdopen(m_fd, openmode.c_str());
error_num = errno;
if (error_num != 0 && m_file == nullptr) {
LOG_ERROR(Common_Filesystem, "Error on file: {}, error: {}", filename,
strerror(error_num));
}
}
m_good = m_file != nullptr; m_good = m_file != nullptr;
#else #else
m_file = FOPEN(filename.c_str(), openmode.c_str()); m_file = FOPEN(filename.c_str(), openmode.c_str());