mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 21:30:04 +00:00 
			
		
		
		
	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