citra_android: Storage Access Framework implementation (#6313)
This commit is contained in:
parent
8c12eb4905
commit
8d563d37b4
68 changed files with 1972 additions and 545 deletions
|
@ -57,6 +57,8 @@ add_library(common STATIC
|
|||
aarch64/cpu_detect.cpp
|
||||
aarch64/cpu_detect.h
|
||||
alignment.h
|
||||
android_storage.h
|
||||
android_storage.cpp
|
||||
announce_multiplayer_room.h
|
||||
arch.h
|
||||
archives.h
|
||||
|
@ -137,7 +139,7 @@ add_library(common STATIC
|
|||
|
||||
create_target_directory_groups(common)
|
||||
|
||||
target_link_libraries(common PUBLIC fmt::fmt microprofile Boost::boost Boost::serialization)
|
||||
target_link_libraries(common PUBLIC fmt::fmt microprofile Boost::boost Boost::serialization Boost::iostreams)
|
||||
target_link_libraries(common PRIVATE libzstd_static)
|
||||
set_target_properties(common PROPERTIES INTERPROCEDURAL_OPTIMIZATION ${ENABLE_LTO})
|
||||
|
||||
|
|
190
src/common/android_storage.cpp
Normal file
190
src/common/android_storage.cpp
Normal file
|
@ -0,0 +1,190 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifdef ANDROID
|
||||
#include "common/android_storage.h"
|
||||
|
||||
namespace AndroidStorage {
|
||||
JNIEnv* GetEnvForThread() {
|
||||
thread_local static struct OwnedEnv {
|
||||
OwnedEnv() {
|
||||
status = g_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
|
||||
if (status == JNI_EDETACHED)
|
||||
g_jvm->AttachCurrentThread(&env, nullptr);
|
||||
}
|
||||
|
||||
~OwnedEnv() {
|
||||
if (status == JNI_EDETACHED)
|
||||
g_jvm->DetachCurrentThread();
|
||||
}
|
||||
|
||||
int status;
|
||||
JNIEnv* env = nullptr;
|
||||
} owned;
|
||||
return owned.env;
|
||||
}
|
||||
|
||||
AndroidOpenMode ParseOpenmode(const std::string_view openmode) {
|
||||
AndroidOpenMode android_open_mode = AndroidOpenMode::NEVER;
|
||||
const char* mode = openmode.data();
|
||||
int o = 0;
|
||||
switch (*mode++) {
|
||||
case 'r':
|
||||
android_open_mode = AndroidStorage::AndroidOpenMode::READ;
|
||||
break;
|
||||
case 'w':
|
||||
android_open_mode = AndroidStorage::AndroidOpenMode::WRITE;
|
||||
o = O_TRUNC;
|
||||
break;
|
||||
case 'a':
|
||||
android_open_mode = AndroidStorage::AndroidOpenMode::WRITE;
|
||||
o = O_APPEND;
|
||||
break;
|
||||
}
|
||||
|
||||
// [rwa]\+ or [rwa]b\+ means read and write
|
||||
if (*mode == '+' || (*mode == 'b' && mode[1] == '+')) {
|
||||
android_open_mode = AndroidStorage::AndroidOpenMode::READ_WRITE;
|
||||
}
|
||||
|
||||
return android_open_mode | o;
|
||||
}
|
||||
|
||||
void InitJNI(JNIEnv* env, jclass clazz) {
|
||||
env->GetJavaVM(&g_jvm);
|
||||
native_library = clazz;
|
||||
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
|
||||
F(JMethodID, JMethodName, Signature)
|
||||
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
|
||||
F(JMethodID, JMethodName, Signature)
|
||||
#define F(JMethodID, JMethodName, Signature) \
|
||||
JMethodID = env->GetStaticMethodID(native_library, JMethodName, Signature);
|
||||
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
ANDROID_STORAGE_FUNCTIONS(FS)
|
||||
#undef F
|
||||
#undef FS
|
||||
#undef FR
|
||||
}
|
||||
|
||||
void CleanupJNI() {
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
|
||||
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
|
||||
#define F(JMethodID) JMethodID = nullptr;
|
||||
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
ANDROID_STORAGE_FUNCTIONS(FS)
|
||||
#undef F
|
||||
#undef FS
|
||||
#undef FR
|
||||
}
|
||||
|
||||
bool CreateFile(const std::string& directory, const std::string& filename) {
|
||||
if (create_file == nullptr)
|
||||
return false;
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_directory = env->NewStringUTF(directory.c_str());
|
||||
jstring j_filename = env->NewStringUTF(filename.c_str());
|
||||
return env->CallStaticBooleanMethod(native_library, create_file, j_directory, j_filename);
|
||||
}
|
||||
|
||||
bool CreateDir(const std::string& directory, const std::string& filename) {
|
||||
if (create_dir == nullptr)
|
||||
return false;
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_directory = env->NewStringUTF(directory.c_str());
|
||||
jstring j_directory_name = env->NewStringUTF(filename.c_str());
|
||||
return env->CallStaticBooleanMethod(native_library, create_dir, j_directory, j_directory_name);
|
||||
}
|
||||
|
||||
int OpenContentUri(const std::string& filepath, AndroidOpenMode openmode) {
|
||||
if (open_content_uri == nullptr)
|
||||
return -1;
|
||||
|
||||
const char* mode = "";
|
||||
switch (openmode) {
|
||||
case AndroidOpenMode::READ:
|
||||
mode = "r";
|
||||
break;
|
||||
case AndroidOpenMode::WRITE:
|
||||
mode = "w";
|
||||
break;
|
||||
case AndroidOpenMode::READ_WRITE:
|
||||
mode = "rw";
|
||||
break;
|
||||
case AndroidOpenMode::WRITE_TRUNCATE:
|
||||
mode = "wt";
|
||||
break;
|
||||
case AndroidOpenMode::WRITE_APPEND:
|
||||
mode = "wa";
|
||||
break;
|
||||
case AndroidOpenMode::READ_WRITE_APPEND:
|
||||
mode = "rwa";
|
||||
break;
|
||||
case AndroidOpenMode::READ_WRITE_TRUNCATE:
|
||||
mode = "rwt";
|
||||
break;
|
||||
case AndroidOpenMode::NEVER:
|
||||
return -1;
|
||||
}
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_filepath = env->NewStringUTF(filepath.c_str());
|
||||
jstring j_mode = env->NewStringUTF(mode);
|
||||
return env->CallStaticIntMethod(native_library, open_content_uri, j_filepath, j_mode);
|
||||
}
|
||||
|
||||
std::vector<std::string> GetFilesName(const std::string& filepath) {
|
||||
auto vector = std::vector<std::string>();
|
||||
if (get_files_name == nullptr)
|
||||
return vector;
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_filepath = env->NewStringUTF(filepath.c_str());
|
||||
auto j_object =
|
||||
(jobjectArray)env->CallStaticObjectMethod(native_library, get_files_name, j_filepath);
|
||||
jsize j_size = env->GetArrayLength(j_object);
|
||||
for (int i = 0; i < j_size; i++) {
|
||||
auto string = (jstring)(env->GetObjectArrayElement(j_object, i));
|
||||
vector.emplace_back(env->GetStringUTFChars(string, nullptr));
|
||||
}
|
||||
return vector;
|
||||
}
|
||||
|
||||
bool CopyFile(const std::string& source, const std::string& destination_path,
|
||||
const std::string& destination_filename) {
|
||||
if (copy_file == nullptr)
|
||||
return false;
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_source_path = env->NewStringUTF(source.c_str());
|
||||
jstring j_destination_path = env->NewStringUTF(destination_path.c_str());
|
||||
jstring j_destination_filename = env->NewStringUTF(destination_filename.c_str());
|
||||
return env->CallStaticBooleanMethod(native_library, copy_file, j_source_path,
|
||||
j_destination_path, j_destination_filename);
|
||||
}
|
||||
|
||||
bool RenameFile(const std::string& source, const std::string& filename) {
|
||||
if (rename_file == nullptr)
|
||||
return false;
|
||||
auto env = GetEnvForThread();
|
||||
jstring j_source_path = env->NewStringUTF(source.c_str());
|
||||
jstring j_destination_path = env->NewStringUTF(filename.c_str());
|
||||
return env->CallStaticBooleanMethod(native_library, rename_file, j_source_path,
|
||||
j_destination_path);
|
||||
}
|
||||
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
|
||||
F(FunctionName, ReturnValue, JMethodID, Caller)
|
||||
#define F(FunctionName, ReturnValue, JMethodID, Caller) \
|
||||
ReturnValue FunctionName(const std::string& filepath) { \
|
||||
if (JMethodID == nullptr) { \
|
||||
return 0; \
|
||||
} \
|
||||
auto env = GetEnvForThread(); \
|
||||
jstring j_filepath = env->NewStringUTF(filepath.c_str()); \
|
||||
return env->Caller(native_library, JMethodID, j_filepath); \
|
||||
}
|
||||
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
#undef F
|
||||
#undef FR
|
||||
|
||||
} // namespace AndroidStorage
|
||||
#endif
|
84
src/common/android_storage.h
Normal file
84
src/common/android_storage.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef ANDROID
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <fcntl.h>
|
||||
#include <jni.h>
|
||||
|
||||
#define ANDROID_STORAGE_FUNCTIONS(V) \
|
||||
V(CreateFile, bool, (const std::string& directory, const std::string& filename), create_file, \
|
||||
"createFile", "(Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||
V(CreateDir, bool, (const std::string& directory, const std::string& filename), create_dir, \
|
||||
"createDir", "(Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||
V(OpenContentUri, int, (const std::string& filepath, AndroidOpenMode openmode), \
|
||||
open_content_uri, "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") \
|
||||
V(GetFilesName, std::vector<std::string>, (const std::string& filepath), get_files_name, \
|
||||
"getFilesName", "(Ljava/lang/String;)[Ljava/lang/String;") \
|
||||
V(CopyFile, bool, \
|
||||
(const std::string& source, const std::string& destination_path, \
|
||||
const std::string& destination_filename), \
|
||||
copy_file, "copyFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z") \
|
||||
V(RenameFile, bool, (const std::string& source, const std::string& filename), rename_file, \
|
||||
"renameFile", "(Ljava/lang/String;Ljava/lang/String;)Z")
|
||||
#define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \
|
||||
V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \
|
||||
"(Ljava/lang/String;)Z") \
|
||||
V(FileExists, bool, file_exists, CallStaticBooleanMethod, "fileExists", \
|
||||
"(Ljava/lang/String;)Z") \
|
||||
V(GetSize, std::uint64_t, get_size, CallStaticLongMethod, "getSize", "(Ljava/lang/String;)J") \
|
||||
V(DeleteDocument, bool, delete_document, CallStaticBooleanMethod, "deleteDocument", \
|
||||
"(Ljava/lang/String;)Z")
|
||||
namespace AndroidStorage {
|
||||
static JavaVM* g_jvm = nullptr;
|
||||
static jclass native_library = nullptr;
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) F(JMethodID)
|
||||
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) F(JMethodID)
|
||||
#define F(JMethodID) static jmethodID JMethodID = nullptr;
|
||||
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
ANDROID_STORAGE_FUNCTIONS(FS)
|
||||
#undef F
|
||||
#undef FS
|
||||
#undef FR
|
||||
// Reference:
|
||||
// https://developer.android.com/reference/android/os/ParcelFileDescriptor#parseMode(java.lang.String)
|
||||
enum class AndroidOpenMode {
|
||||
READ = O_RDONLY, // "r"
|
||||
WRITE = O_WRONLY, // "w"
|
||||
READ_WRITE = O_RDWR, // "rw"
|
||||
WRITE_APPEND = O_WRONLY | O_APPEND, // "wa"
|
||||
WRITE_TRUNCATE = O_WRONLY | O_TRUNC, // "wt
|
||||
READ_WRITE_APPEND = O_RDWR | O_APPEND, // "rwa"
|
||||
READ_WRITE_TRUNCATE = O_RDWR | O_TRUNC, // "rwt"
|
||||
NEVER = EINVAL,
|
||||
};
|
||||
|
||||
inline AndroidOpenMode operator|(AndroidOpenMode a, int b) {
|
||||
return static_cast<AndroidOpenMode>(static_cast<int>(a) | b);
|
||||
}
|
||||
|
||||
AndroidOpenMode ParseOpenmode(const std::string_view openmode);
|
||||
|
||||
void InitJNI(JNIEnv* env, jclass clazz);
|
||||
|
||||
void CleanupJNI();
|
||||
|
||||
#define FS(FunctionName, ReturnValue, Parameters, JMethodID, JMethodName, Signature) \
|
||||
F(FunctionName, Parameters, ReturnValue)
|
||||
#define F(FunctionName, Parameters, ReturnValue) ReturnValue FunctionName Parameters;
|
||||
ANDROID_STORAGE_FUNCTIONS(FS)
|
||||
#undef F
|
||||
#undef FS
|
||||
|
||||
#define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \
|
||||
F(FunctionName, ReturnValue)
|
||||
#define F(FunctionName, ReturnValue) ReturnValue FunctionName(const std::string& filepath);
|
||||
ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(FR)
|
||||
#undef F
|
||||
#undef FR
|
||||
} // namespace AndroidStorage
|
||||
#endif
|
|
@ -24,10 +24,6 @@
|
|||
#define MACOS_EMU_DATA_DIR "Library" DIR_SEP "Application Support" DIR_SEP "Citra"
|
||||
// For compatibility with XDG paths.
|
||||
#define EMU_DATA_DIR "citra-emu"
|
||||
#elif ANDROID
|
||||
// On Android internal storage is mounted as "/sdcard"
|
||||
#define SDCARD_DIR "sdcard"
|
||||
#define EMU_DATA_DIR "citra-emu"
|
||||
#else
|
||||
#define EMU_DATA_DIR "citra-emu"
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <boost/iostreams/device/file_descriptor.hpp>
|
||||
#include <boost/iostreams/stream.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_paths.h"
|
||||
|
@ -66,6 +68,11 @@
|
|||
|
||||
#endif
|
||||
|
||||
#ifdef ANDROID
|
||||
#include "common/android_storage.h"
|
||||
#include "common/string_util.h"
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <sys/stat.h>
|
||||
|
||||
|
@ -104,6 +111,8 @@ bool Exists(const std::string& filename) {
|
|||
copy += DIR_SEP_CHR;
|
||||
|
||||
int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info);
|
||||
#elif ANDROID
|
||||
int result = AndroidStorage::FileExists(filename) ? 0 : -1;
|
||||
#else
|
||||
int result = stat(copy.c_str(), &file_info);
|
||||
#endif
|
||||
|
@ -112,6 +121,10 @@ bool Exists(const std::string& filename) {
|
|||
}
|
||||
|
||||
bool IsDirectory(const std::string& filename) {
|
||||
#ifdef ANDROID
|
||||
return AndroidStorage::IsDirectory(filename);
|
||||
#endif
|
||||
|
||||
struct stat file_info;
|
||||
|
||||
std::string copy(filename);
|
||||
|
@ -156,6 +169,11 @@ bool Delete(const std::string& filename) {
|
|||
LOG_ERROR(Common_Filesystem, "DeleteFile failed on {}: {}", filename, GetLastErrorMsg());
|
||||
return false;
|
||||
}
|
||||
#elif ANDROID
|
||||
if (!AndroidStorage::DeleteDocument(filename)) {
|
||||
LOG_ERROR(Common_Filesystem, "unlink failed on {}", filename);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
if (unlink(filename.c_str()) == -1) {
|
||||
LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg());
|
||||
|
@ -178,6 +196,24 @@ bool CreateDir(const std::string& path) {
|
|||
}
|
||||
LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error);
|
||||
return false;
|
||||
#elif ANDROID
|
||||
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
|
||||
if (mkdir(path.c_str(), 0755) == 0)
|
||||
return true;
|
||||
|
@ -241,6 +277,9 @@ bool DeleteDir(const std::string& filename) {
|
|||
#ifdef _WIN32
|
||||
if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str()))
|
||||
return true;
|
||||
#elif ANDROID
|
||||
if (AndroidStorage::DeleteDocument(filename))
|
||||
return true;
|
||||
#else
|
||||
if (rmdir(filename.c_str()) == 0)
|
||||
return true;
|
||||
|
@ -256,6 +295,9 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) {
|
|||
if (_wrename(Common::UTF8ToUTF16W(srcFilename).c_str(),
|
||||
Common::UTF8ToUTF16W(destFilename).c_str()) == 0)
|
||||
return true;
|
||||
#elif ANDROID
|
||||
if (AndroidStorage::RenameFile(srcFilename, std::string(GetFilename(destFilename))))
|
||||
return true;
|
||||
#else
|
||||
if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)
|
||||
return true;
|
||||
|
@ -275,6 +317,9 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
|
|||
LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename,
|
||||
GetLastErrorMsg());
|
||||
return false;
|
||||
#elif ANDROID
|
||||
return AndroidStorage::CopyFile(srcFilename, std::string(GetParentPath(destFilename)),
|
||||
std::string(GetFilename(destFilename)));
|
||||
#else
|
||||
using CFilePointer = std::unique_ptr<FILE, decltype(&std::fclose)>;
|
||||
|
||||
|
@ -334,6 +379,10 @@ u64 GetSize(const std::string& filename) {
|
|||
struct stat buf;
|
||||
#ifdef _WIN32
|
||||
if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0)
|
||||
#elif ANDROID
|
||||
u64 result = AndroidStorage::GetSize(filename);
|
||||
LOG_TRACE(Common_Filesystem, "{}: {}", filename, result);
|
||||
return result;
|
||||
#else
|
||||
if (stat(filename.c_str(), &buf) == 0)
|
||||
#endif
|
||||
|
@ -403,6 +452,10 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
|
|||
// windows loop
|
||||
do {
|
||||
const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName));
|
||||
#elif ANDROID
|
||||
// android loop
|
||||
auto result = AndroidStorage::GetFilesName(directory);
|
||||
for (auto virtual_name : result) {
|
||||
#else
|
||||
DIR* dirp = opendir(directory.c_str());
|
||||
if (!dirp)
|
||||
|
@ -426,6 +479,8 @@ bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory,
|
|||
#ifdef _WIN32
|
||||
} while (FindNextFileW(handle_find, &ffd) != 0);
|
||||
FindClose(handle_find);
|
||||
#elif ANDROID
|
||||
}
|
||||
#else
|
||||
}
|
||||
closedir(dirp);
|
||||
|
@ -514,12 +569,18 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
|
|||
if (!FileUtil::Exists(dest_path))
|
||||
FileUtil::CreateFullPath(dest_path);
|
||||
|
||||
#ifdef ANDROID
|
||||
auto result = AndroidStorage::GetFilesName(source_path);
|
||||
for (auto virtualName : result) {
|
||||
#else
|
||||
DIR* dirp = opendir(source_path.c_str());
|
||||
if (!dirp)
|
||||
return;
|
||||
|
||||
while (struct dirent* result = readdir(dirp)) {
|
||||
const std::string virtualName(result->d_name);
|
||||
#endif // ANDROID
|
||||
|
||||
// check for "." and ".."
|
||||
if (((virtualName[0] == '.') && (virtualName[1] == '\0')) ||
|
||||
((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0')))
|
||||
|
@ -537,8 +598,11 @@ void CopyDir(const std::string& source_path, const std::string& dest_path) {
|
|||
} else if (!FileUtil::Exists(dest))
|
||||
FileUtil::Copy(source, dest);
|
||||
}
|
||||
|
||||
#ifndef ANDROID
|
||||
closedir(dirp);
|
||||
#endif
|
||||
#endif // ANDROID
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
std::optional<std::string> GetCurrentDir() {
|
||||
|
@ -698,11 +762,9 @@ void SetUserPath(const std::string& path) {
|
|||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
#elif ANDROID
|
||||
if (FileUtil::Exists(DIR_SEP SDCARD_DIR)) {
|
||||
user_path = DIR_SEP SDCARD_DIR DIR_SEP EMU_DATA_DIR DIR_SEP;
|
||||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
}
|
||||
user_path = "/";
|
||||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
#else
|
||||
if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
|
||||
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
|
||||
|
@ -929,6 +991,9 @@ std::string_view RemoveTrailingSlash(std::string_view path) {
|
|||
|
||||
std::string SanitizePath(std::string_view path_, DirectorySeparator directory_separator) {
|
||||
std::string path(path_);
|
||||
#ifdef ANDROID
|
||||
return std::string(RemoveTrailingSlash(path));
|
||||
#endif
|
||||
char type1 = directory_separator == DirectorySeparator::BackwardSlash ? '/' : '\\';
|
||||
char type2 = directory_separator == DirectorySeparator::BackwardSlash ? '\\' : '/';
|
||||
|
||||
|
@ -975,6 +1040,7 @@ IOFile& IOFile::operator=(IOFile&& other) noexcept {
|
|||
|
||||
void IOFile::Swap(IOFile& other) noexcept {
|
||||
std::swap(m_file, other.m_file);
|
||||
std::swap(m_fd, other.m_fd);
|
||||
std::swap(m_good, other.m_good);
|
||||
std::swap(filename, other.filename);
|
||||
std::swap(openmode, other.openmode);
|
||||
|
@ -993,6 +1059,36 @@ bool IOFile::Open() {
|
|||
m_good = _wfopen_s(&m_file, Common::UTF8ToUTF16W(filename).c_str(),
|
||||
Common::UTF8ToUTF16W(openmode).c_str()) == 0;
|
||||
}
|
||||
#elif ANDROID
|
||||
// Check whether filepath is startsWith content
|
||||
AndroidStorage::AndroidOpenMode android_open_mode = AndroidStorage::ParseOpenmode(openmode);
|
||||
if (android_open_mode == AndroidStorage::AndroidOpenMode::WRITE ||
|
||||
android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE ||
|
||||
android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_APPEND ||
|
||||
android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_TRUNCATE ||
|
||||
android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_TRUNCATE ||
|
||||
android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_APPEND) {
|
||||
if (!FileUtil::Exists(filename)) {
|
||||
std::string directory(GetParentPath(filename));
|
||||
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_good = m_file != nullptr;
|
||||
#else
|
||||
m_file = std::fopen(filename.c_str(), openmode.c_str());
|
||||
m_good = m_file != nullptr;
|
||||
|
@ -1083,4 +1179,30 @@ bool IOFile::Resize(u64 size) {
|
|||
return m_good;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using boost_iostreams = boost::iostreams::stream<T>;
|
||||
|
||||
template <>
|
||||
void OpenFStream<std::ios_base::in>(
|
||||
boost_iostreams<boost::iostreams::file_descriptor_source>& fstream,
|
||||
const std::string& filename) {
|
||||
IOFile file(filename, "r");
|
||||
int fd = dup(file.GetFd());
|
||||
if (fd == -1)
|
||||
return;
|
||||
boost::iostreams::file_descriptor_source file_descriptor_source(fd,
|
||||
boost::iostreams::close_handle);
|
||||
fstream.open(file_descriptor_source);
|
||||
}
|
||||
|
||||
template <>
|
||||
void OpenFStream<std::ios_base::out>(
|
||||
boost_iostreams<boost::iostreams::file_descriptor_sink>& fstream, const std::string& filename) {
|
||||
IOFile file(filename, "w");
|
||||
int fd = dup(file.GetFd());
|
||||
if (fd == -1)
|
||||
return;
|
||||
boost::iostreams::file_descriptor_sink file_descriptor_sink(fd, boost::iostreams::close_handle);
|
||||
fstream.open(file_descriptor_sink);
|
||||
}
|
||||
} // namespace FileUtil
|
||||
|
|
|
@ -336,6 +336,15 @@ public:
|
|||
[[nodiscard]] bool IsGood() const {
|
||||
return m_good;
|
||||
}
|
||||
[[nodiscard]] int GetFd() const {
|
||||
#ifdef ANDROID
|
||||
return m_fd;
|
||||
#else
|
||||
if (m_file == nullptr)
|
||||
return -1;
|
||||
return fileno(m_file);
|
||||
#endif
|
||||
}
|
||||
[[nodiscard]] explicit operator bool() const {
|
||||
return IsGood();
|
||||
}
|
||||
|
@ -359,6 +368,7 @@ private:
|
|||
bool Open();
|
||||
|
||||
std::FILE* m_file = nullptr;
|
||||
int m_fd = -1;
|
||||
bool m_good = true;
|
||||
|
||||
std::string filename;
|
||||
|
@ -383,6 +393,8 @@ private:
|
|||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
template <std::ios_base::openmode o, typename T>
|
||||
void OpenFStream(T& fstream, const std::string& filename);
|
||||
} // namespace FileUtil
|
||||
|
||||
// To deal with Windows being dumb at unicode:
|
||||
|
|
|
@ -123,6 +123,12 @@ std::string TabsToSpaces(int tab_size, std::string in) {
|
|||
return in;
|
||||
}
|
||||
|
||||
bool EndsWith(const std::string& value, const std::string& ending) {
|
||||
if (ending.size() > value.size())
|
||||
return false;
|
||||
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
||||
}
|
||||
|
||||
std::string ReplaceAll(std::string result, const std::string& src, const std::string& dest) {
|
||||
std::size_t pos = 0;
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ namespace Common {
|
|||
|
||||
[[nodiscard]] std::string TabsToSpaces(int tab_size, std::string in);
|
||||
|
||||
[[nodiscard]] bool EndsWith(const std::string& value, const std::string& ending);
|
||||
|
||||
void SplitString(const std::string& str, char delim, std::vector<std::string>& output);
|
||||
|
||||
// "C:/Windows/winhelp.exe" to "C:/Windows/", "winhelp", ".exe"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue