From 5a4e27e70280732ad5ed22a50857a2e8d0c48209 Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Sun, 18 Jun 2023 17:21:57 +0300 Subject: [PATCH] Fix crash when calling JNI methods while inside a fiber & add support reading games from folder --- src/Cafe/CMakeLists.txt | 7 + src/Cafe/CafeSystem.cpp | 6 +- src/Cafe/Filesystem/fsc.h | 15 ++ src/Cafe/Filesystem/fscDeviceAndroidSAF.cpp | 235 ++++++++++++++++++ src/Cafe/Filesystem/fscDeviceAndroidSAF.h | 36 +++ src/Cafe/TitleList/TitleInfo.cpp | 9 +- src/Common/precompiled.h | 56 +++++ src/android/app/src/main/cpp/native-lib.cpp | 228 ++++++++++------- .../java/info/cemu/Cemu/CemuApplication.java | 29 +-- .../main/java/info/cemu/Cemu/FileUtil.java | 28 ++- 10 files changed, 509 insertions(+), 140 deletions(-) create mode 100644 src/Cafe/Filesystem/fscDeviceAndroidSAF.cpp create mode 100644 src/Cafe/Filesystem/fscDeviceAndroidSAF.h diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index 4b9ec166..7e94db4e 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -473,6 +473,13 @@ if(APPLE) target_sources(CemuCafe PRIVATE "HW/Latte/Renderer/Vulkan/CocoaSurface.mm") endif() +if(ANDROID) + target_sources(CemuCafe PRIVATE + Filesystem/fscDeviceAndroidSAF.cpp + Filesystem/fscDeviceAndroidSAF.h + ) +endif() + set_property(TARGET CemuCafe PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") target_include_directories(CemuCafe PUBLIC "../") diff --git a/src/Cafe/CafeSystem.cpp b/src/Cafe/CafeSystem.cpp index 25bb71d6..f92911cd 100644 --- a/src/Cafe/CafeSystem.cpp +++ b/src/Cafe/CafeSystem.cpp @@ -631,10 +631,10 @@ namespace CafeSystem // check for content folder fs::path contentPath = executablePath.parent_path().parent_path().append("content"); std::error_code ec; - if (fs::is_directory(contentPath, ec)) + if (cemu::fs::is_directory(contentPath, ec)) { // mounting content folder - bool r = FSCDeviceHostFS_Mount(std::string("/vol/content").c_str(), _pathToUtf8(contentPath), FSC_PRIORITY_BASE); + bool r = FSCDeviceHost_Mount(std::string("/vol/content").c_str(), _pathToUtf8(contentPath), FSC_PRIORITY_BASE); if (!r) { cemuLog_log(LogType::Force, "Failed to mount {}", _pathToUtf8(contentPath)); @@ -643,7 +643,7 @@ namespace CafeSystem } } // mount code folder to a virtual temporary path - FSCDeviceHostFS_Mount(std::string("/internal/code/").c_str(), _pathToUtf8(executablePath.parent_path()), FSC_PRIORITY_BASE); + FSCDeviceHost_Mount(std::string("/internal/code/").c_str(), _pathToUtf8(executablePath.parent_path()), FSC_PRIORITY_BASE); std::string internalExecutablePath = "/internal/code/"; internalExecutablePath.append(_pathToUtf8(executablePath.filename())); _pathToExecutable = internalExecutablePath; diff --git a/src/Cafe/Filesystem/fsc.h b/src/Cafe/Filesystem/fsc.h index e34c74dd..235d6bea 100644 --- a/src/Cafe/Filesystem/fsc.h +++ b/src/Cafe/Filesystem/fsc.h @@ -200,3 +200,18 @@ bool FSCDeviceHostFS_Mount(std::string_view mountPath, std::string_view hostTarg // redirect device void fscDeviceRedirect_map(); void fscDeviceRedirect_add(std::string_view virtualSourcePath, const fs::path& targetFilePath, sint32 priority); + +#if __ANDROID__ +#include "Common/unix/FilesystemAndroid.h" +bool FSCDeviceAndroidSAF_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority); +#endif // __ANDROID__ + +inline bool FSCDeviceHost_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority) +{ +#if __ANDROID__ + if (FilesystemAndroid::isContentUri(std::string(hostTargetPath))) + return FSCDeviceAndroidSAF_Mount(mountPath, hostTargetPath, priority); + else +#endif // __ANDROID__ + return FSCDeviceHostFS_Mount(mountPath, hostTargetPath, priority); +} diff --git a/src/Cafe/Filesystem/fscDeviceAndroidSAF.cpp b/src/Cafe/Filesystem/fscDeviceAndroidSAF.cpp new file mode 100644 index 00000000..b063dd3b --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceAndroidSAF.cpp @@ -0,0 +1,235 @@ +#include "Cafe/Filesystem/fscDeviceAndroidSAF.h" + +#include +#include + +#include "Cafe/Filesystem/fsc.h" +#include "Common/FileStream.h" +#include "Common/unix/FilesystemAndroid.h" + +FSCVirtualFile_AndroidSAF::~FSCVirtualFile_AndroidSAF() +{ + if (m_type == FSC_TYPE_FILE) + delete m_fs; +} + +sint32 FSCVirtualFile_AndroidSAF::fscGetType() +{ + return m_type; +} + +uint32 FSCVirtualFile_AndroidSAF::fscDeviceAndroidSAFFSFile_getFileSize() +{ + if (m_type == FSC_TYPE_FILE) + { + if (m_fileSize > 0xFFFFFFFFULL) + cemu_assert_suspicious(); // files larger than 4GB are not supported by Wii U filesystem + return (uint32)m_fileSize; + } + return 0; +} + +uint64 FSCVirtualFile_AndroidSAF::fscQueryValueU64(uint32 id) +{ + if (m_type == FSC_TYPE_FILE) + { + if (id == FSC_QUERY_SIZE) + return fscDeviceAndroidSAFFSFile_getFileSize(); + else if (id == FSC_QUERY_WRITEABLE) + return m_isWritable; + else + cemu_assert_unimplemented(); + } + else if (m_type == FSC_TYPE_DIRECTORY) + { + if (id == FSC_QUERY_SIZE) + return fscDeviceAndroidSAFFSFile_getFileSize(); + else + cemu_assert_unimplemented(); + } + cemu_assert_unimplemented(); + return 0; +} + +uint32 FSCVirtualFile_AndroidSAF::fscWriteData(void* buffer, uint32 size) +{ + throw std::logic_error("write not supported with SAF"); + return 0; +} + +uint32 FSCVirtualFile_AndroidSAF::fscReadData(void* buffer, uint32 size) +{ + if (m_type != FSC_TYPE_FILE) + return 0; + if (size >= (2UL * 1024UL * 1024UL * 1024UL)) + { + cemu_assert_suspicious(); + return 0; + } + uint32 bytesLeft = (uint32)(m_fileSize - m_seek); + bytesLeft = std::min(bytesLeft, 0x7FFFFFFFu); + sint32 bytesToRead = std::min(bytesLeft, size); + uint32 bytesRead = m_fs->readData(buffer, bytesToRead); + m_seek += bytesRead; + return bytesRead; +} + +void FSCVirtualFile_AndroidSAF::fscSetSeek(uint64 seek) +{ + if (m_type != FSC_TYPE_FILE) + return; + this->m_seek = seek; + cemu_assert_debug(seek <= m_fileSize); + m_fs->SetPosition(seek); +} + +uint64 FSCVirtualFile_AndroidSAF::fscGetSeek() +{ + if (m_type != FSC_TYPE_FILE) + return 0; + return m_seek; +} + +void FSCVirtualFile_AndroidSAF::fscSetFileLength(uint64 endOffset) +{ + if (m_type != FSC_TYPE_FILE) + return; + m_fs->SetPosition(endOffset); + bool r = m_fs->SetEndOfFile(); + m_seek = std::min(m_seek, endOffset); + m_fileSize = m_seek; + m_fs->SetPosition(m_seek); + if (!r) + cemuLog_log(LogType::Force, "fscSetFileLength: Failed to set size to 0x{:x}", endOffset); +} + +bool FSCVirtualFile_AndroidSAF::fscDirNext(FSCDirEntry* dirEntry) +{ + if (m_type != FSC_TYPE_DIRECTORY) + return false; + + if (!m_files) + { + // init iterator on first iteration attempt + m_files = std::make_unique>(FilesystemAndroid::listFiles(*m_path)); + m_filesIterator = m_files->begin(); + if (!m_files) + { + cemuLog_log(LogType::Force, "Failed to iterate directory: {}", _pathToUtf8(*m_path)); + return false; + } + } + if (m_filesIterator == m_files->end()) + return false; + + const fs::path& file = *m_filesIterator; + + std::string fileName = file.filename().generic_string(); + if (fileName.size() >= sizeof(dirEntry->path) - 1) + fileName.resize(sizeof(dirEntry->path) - 1); + strncpy(dirEntry->path, fileName.data(), sizeof(dirEntry->path)); + if (FilesystemAndroid::isDirectory(file)) + { + dirEntry->isDirectory = true; + dirEntry->isFile = false; + dirEntry->fileSize = 0; + } + else + { + dirEntry->isDirectory = false; + dirEntry->isFile = true; + dirEntry->fileSize = 0; + auto fs = FileStream::openFile2(file); + if (fs) + { + dirEntry->fileSize = fs->GetSize(); + delete fs; + } + } + m_filesIterator++; + return true; +} + +FSCVirtualFile* FSCVirtualFile_AndroidSAF::OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus) +{ + if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && !HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + cemu_assert_debug(false); // not allowed. At least one of both flags must be set + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION) || + HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALLOW_CREATE) || + HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE)) + throw std::logic_error("writing and creating a file is not supported with SAF"); + // attempt to open as file + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE)) + { + FileStream* fs = FileStream::openFile2(path); + if (fs) + { + FSCVirtualFile_AndroidSAF* vf = new FSCVirtualFile_AndroidSAF(FSC_TYPE_FILE); + vf->m_fs = fs; + vf->m_isWritable = false; + vf->m_fileSize = fs->GetSize(); + fscStatus = FSC_STATUS_OK; + return vf; + } + } + + // attempt to open as directory + if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR)) + { + bool isExistingDir = FilesystemAndroid::exists(path); + if (isExistingDir) + { + FSCVirtualFile_AndroidSAF* vf = new FSCVirtualFile_AndroidSAF(FSC_TYPE_DIRECTORY); + vf->m_path.reset(new std::filesystem::path(path)); + fscStatus = FSC_STATUS_OK; + return vf; + } + } + fscStatus = FSC_STATUS_FILE_NOT_FOUND; + return nullptr; +} + +/* Device implementation */ + +class fscDeviceAndroidSAFFSC : public fscDeviceC +{ + public: + FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override + { + *fscStatus = FSC_STATUS_OK; + FSCVirtualFile* vf = FSCVirtualFile_AndroidSAF::OpenFile(_utf8ToPath(path), accessFlags, *fscStatus); + cemu_assert_debug((bool)vf == (*fscStatus == FSC_STATUS_OK)); + return vf; + } + + bool fscDeviceCreateDir(std::string_view path, void* ctx, sint32* fscStatus) override + { + throw std::logic_error("creating a directory is not supported with SAF"); + return false; + } + + bool fscDeviceRemoveFileOrDir(std::string_view path, void* ctx, sint32* fscStatus) override + { + throw std::logic_error("removing a file or dir is not supported with SAF"); + return false; + } + + bool fscDeviceRename(std::string_view srcPath, std::string_view dstPath, void* ctx, sint32* fscStatus) override + { + throw std::logic_error("renaming not supported with SAF"); + return false; + } + + // singleton + public: + static fscDeviceAndroidSAFFSC& instance() + { + static fscDeviceAndroidSAFFSC _instance; + return _instance; + } +}; + +bool FSCDeviceAndroidSAF_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority) +{ + return fsc_mount(mountPath, hostTargetPath, &fscDeviceAndroidSAFFSC::instance(), nullptr, priority) == FSC_STATUS_OK; +} diff --git a/src/Cafe/Filesystem/fscDeviceAndroidSAF.h b/src/Cafe/Filesystem/fscDeviceAndroidSAF.h new file mode 100644 index 00000000..86f7eba8 --- /dev/null +++ b/src/Cafe/Filesystem/fscDeviceAndroidSAF.h @@ -0,0 +1,36 @@ +#pragma once + +#include "Cafe/Filesystem/fsc.h" + +class FSCVirtualFile_AndroidSAF : public FSCVirtualFile +{ + public: + static FSCVirtualFile* OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus); + ~FSCVirtualFile_AndroidSAF() override; + + sint32 fscGetType() override; + + uint32 fscDeviceAndroidSAFFSFile_getFileSize(); + + uint64 fscQueryValueU64(uint32 id) override; + uint32 fscWriteData(void* buffer, uint32 size) override; + uint32 fscReadData(void* buffer, uint32 size) override; + void fscSetSeek(uint64 seek) override; + uint64 fscGetSeek() override; + void fscSetFileLength(uint64 endOffset) override; + bool fscDirNext(FSCDirEntry* dirEntry) override; + + private: + FSCVirtualFile_AndroidSAF(uint32 type) : m_type(type){}; + + uint32 m_type; // FSC_TYPE_* + class FileStream* m_fs{}; + // file + uint64 m_seek{0}; + uint64 m_fileSize{0}; + bool m_isWritable{false}; + // directory + std::unique_ptr m_path{}; + std::unique_ptr> m_files{}; + std::vector::iterator m_filesIterator; +}; diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 7dcb0beb..93498a3c 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -7,7 +7,6 @@ #include "Common/FileStream.h" #if __ANDROID__ -#include "Common/unix/FilesystemAndroid.h" #include "Common/unix/ContentUriIStream.h" #endif // __ANDROID__ @@ -180,7 +179,7 @@ bool TitleInfo::ParseWuaTitleFolderName(std::string_view name, TitleId& titleIdO bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataFormat& formatOut) { std::error_code ec; - if (path.has_extension() && fs::is_regular_file(path, ec)) + if (path.has_extension() && cemu::fs::is_file(path, ec)) { std::string filenameStr = _pathToUtf8(path.filename()); if (boost::iends_with(filenameStr, ".rpx")) @@ -192,7 +191,7 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF parentPath = parentPath.parent_path(); // next to content and meta? std::error_code ec; - if (fs::exists(parentPath / "content", ec) && fs::exists(parentPath / "meta", ec)) + if (cemu::fs::exists(parentPath / "content", ec) && cemu::fs::exists(parentPath / "meta", ec)) { formatOut = TitleDataFormat::HOST_FS; pathOut = parentPath; @@ -265,7 +264,7 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF { // does it point to the root folder of a title? std::error_code ec; - if (fs::exists(path / "content", ec) && fs::exists(path / "meta", ec) && fs::exists(path / "code", ec)) + if (cemu::fs::exists(path / "content", ec) && cemu::fs::exists(path / "meta", ec) && cemu::fs::exists(path / "code", ec)) { formatOut = TitleDataFormat::HOST_FS; pathOut = path; @@ -386,7 +385,7 @@ bool TitleInfo::Mount(std::string_view virtualPath, std::string_view subfolder, { fs::path hostFSPath = m_fullPath; hostFSPath.append(subfolder); - bool r = FSCDeviceHostFS_Mount(std::string(virtualPath).c_str(), _pathToUtf8(hostFSPath), mountPriority); + bool r = FSCDeviceHost_Mount(std::string(virtualPath).c_str(), _pathToUtf8(hostFSPath), mountPriority); cemu_assert_debug(r); if (!r) { diff --git a/src/Common/precompiled.h b/src/Common/precompiled.h index efa29487..ed8b63b4 100644 --- a/src/Common/precompiled.h +++ b/src/Common/precompiled.h @@ -83,6 +83,62 @@ #include namespace fs = std::filesystem; +#if __ANDROID__ +#endif // __ANDROID +#include "Common/unix/FilesystemAndroid.h" + +namespace cemu +{ +namespace fs +{ +inline bool is_directory(const std::filesystem::path& p) +{ + if (FilesystemAndroid::isContentUri(p)) + return FilesystemAndroid::isDirectory(p); + return std::filesystem::is_directory(p); +} +inline bool is_directory(const std::filesystem::path& p, std::error_code& ec) +{ +#if __ANDROID__ + if (FilesystemAndroid::isContentUri(p)) + return FilesystemAndroid::isDirectory(p); +#endif // __ANDROID__ + return std::filesystem::is_directory(p, ec); +} +inline bool is_file(const std::filesystem::path& p) +{ +#if __ANDROID__ + if (FilesystemAndroid::isContentUri(p)) + return FilesystemAndroid::isDirectory(p); +#endif // __ANDROID__ + return std::filesystem::is_regular_file(p); +} +inline bool is_file(const std::filesystem::path& p, std::error_code& ec) +{ +#if __ANDROID__ + if (FilesystemAndroid::isContentUri(p)) + return FilesystemAndroid::isDirectory(p); +#endif // __ANDROID__ + return std::filesystem::is_regular_file(p, ec); +} +inline bool exists(const std::filesystem::path& p) +{ +#if __ANDROID__ + if (FilesystemAndroid::isContentUri(p)) + return FilesystemAndroid::isDirectory(p); +#endif // __ANDROID__ + return std::filesystem::exists(p); +} +inline bool exists(const std::filesystem::path& p, std::error_code& ec) +{ +#if __ANDROID__ + if (FilesystemAndroid::isContentUri(p)) + return FilesystemAndroid::isDirectory(p); +#endif // __ANDROID__ + return std::filesystem::exists(p, ec); +} +} // namespace fs +} // namespace cemu); #include "enumFlags.h" diff --git a/src/android/app/src/main/cpp/native-lib.cpp b/src/android/app/src/main/cpp/native-lib.cpp index c003b6cd..4eeea4ce 100644 --- a/src/android/app/src/main/cpp/native-lib.cpp +++ b/src/android/app/src/main/cpp/native-lib.cpp @@ -155,9 +155,143 @@ Java_info_cemu_Cemu_NativeLibrary_initializeActiveSettings(JNIEnv *env, jclass c int mainEmulatorHLE(); + +class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks { + jmethodID m_openContentUriMid; + jmethodID m_listFilesMid; + jmethodID m_isDirectoryMid; + jmethodID m_isFileMid; + jmethodID m_existsMid; + JNIUtils::Scopedjclass m_fileUtilClass; + std::function m_function = nullptr; + std::atomic_bool m_functionFinished; + std::mutex m_functionMutex; + std::condition_variable m_functionCV; + std::thread m_thread; + std::mutex m_threadMutex; + std::condition_variable m_threadCV; + std::atomic_bool m_continue = true; + + bool callBooleanFunction(const std::filesystem::path &uri, jmethodID methodId) { + std::unique_lock functionLock(m_functionMutex); + m_functionFinished = false; + bool condition; + { + std::lock_guard threadLock(m_threadMutex); + m_function = [&, this](JNIEnv *env) { + jstring uriString = env->NewStringUTF(uri.c_str()); + condition = env->CallStaticBooleanMethod(*m_fileUtilClass, methodId, uriString); + env->DeleteLocalRef(uriString); + }; + } + m_threadCV.notify_one(); + m_functionCV.wait(functionLock, [this]() -> bool { return m_functionFinished; }); + return condition; + } + +public: + AndroidFilesystemCallbacks() { + JNIUtils::ScopedJNIENV env; + m_fileUtilClass = JNIUtils::Scopedjclass("info/cemu/Cemu/FileUtil"); + m_openContentUriMid = env->GetStaticMethodID(*m_fileUtilClass, "openContentUri", + "(Ljava/lang/String;)I"); + m_listFilesMid = env->GetStaticMethodID(*m_fileUtilClass, "listFiles", + "(Ljava/lang/String;)[Ljava/lang/String;"); + m_isDirectoryMid = env->GetStaticMethodID(*m_fileUtilClass, "isDirectory", + "(Ljava/lang/String;)Z"); + m_isFileMid = env->GetStaticMethodID(*m_fileUtilClass, "isFile", + "(Ljava/lang/String;)Z"); + m_existsMid = env->GetStaticMethodID(*m_fileUtilClass, "exists", + "(Ljava/lang/String;)Z"); + m_thread = std::thread([this]() { + JNIUtils::ScopedJNIENV env; + while (m_continue) { + std::unique_lock threadLock(m_threadMutex); + m_threadCV.wait(threadLock, [&] { + return m_function || !m_continue; + }); + if (!m_continue) + return; + m_function(*env); + m_function = nullptr; + m_functionFinished = true; + m_functionCV.notify_one(); + } + }); + } + + ~AndroidFilesystemCallbacks() { + m_continue = false; + m_threadCV.notify_one(); + m_thread.join(); + } + + int openContentUri(const std::filesystem::path &uri) override { + std::unique_lock functionLock(m_functionMutex); + m_functionFinished = false; + int fd; + { + std::lock_guard threadLock(m_threadMutex); + m_function = [&, this](JNIEnv *env) { + jstring uriString = env->NewStringUTF(uri.c_str()); + fd = env->CallStaticIntMethod(*m_fileUtilClass, m_openContentUriMid, uriString); + env->DeleteLocalRef(uriString); + }; + } + m_threadCV.notify_one(); + m_functionCV.wait(functionLock, [this]() -> bool { return m_functionFinished; }); + return fd; + } + + std::vector listFiles(const std::filesystem::path &uri) override { + std::unique_lock functionLock(m_functionMutex); + m_functionFinished = false; + std::vector paths; + { + std::lock_guard threadLock(m_threadMutex); + m_function = [&, this](JNIEnv *env) { + jstring uriString = env->NewStringUTF(uri.c_str()); + jobjectArray pathsObjArray = static_cast(env->CallStaticObjectMethod( + *m_fileUtilClass, + m_listFilesMid, uriString)); + env->DeleteLocalRef(uriString); + jsize arrayLength = env->GetArrayLength(pathsObjArray); + paths.reserve(arrayLength); + for (jsize i = 0; i < arrayLength; i++) { + jstring pathStr = static_cast(env->GetObjectArrayElement( + pathsObjArray, + i)); + paths.push_back(JNIUtils::JStringToString(env, pathStr)); + env->DeleteLocalRef(pathStr); + } + env->DeleteLocalRef(pathsObjArray); + }; + } + m_threadCV.notify_one(); + m_functionCV.wait(functionLock, [this]() -> bool { return m_functionFinished; }); + return paths; + } + + bool isDirectory(const std::filesystem::path &uri) override { + return callBooleanFunction(uri, m_isDirectoryMid); + } + + bool isFile(const std::filesystem::path &uri) override { + return callBooleanFunction(uri, m_isFileMid); + } + + bool exists(const std::filesystem::path &uri) override { + return callBooleanFunction(uri, m_existsMid); + } +}; + +std::shared_ptr g_androidFilesystemCallbacks = nullptr; + extern "C" JNIEXPORT void JNICALL Java_info_cemu_Cemu_NativeLibrary_initializeEmulation(JNIEnv *env, jclass clazz) { + g_androidFilesystemCallbacks = std::make_shared(); + FilesystemAndroid::setFilesystemCallbacks(g_androidFilesystemCallbacks); NetworkConfig::LoadOnce(); mainEmulatorHLE(); InputManager::instance().load(); @@ -202,100 +336,6 @@ Java_info_cemu_Cemu_NativeLibrary_recreateRenderSurface(JNIEnv *env, jclass claz VulkanRenderer::GetInstance()->NotifySurfaceChanged(is_main_canvas); } - -class AndroidFilesystemCallbacks : public FilesystemAndroid::FilesystemCallbacks { - JNIUtils::Scopedjobject m_filesystemCallbacksObj; - jmethodID m_openContentUriMid; - jmethodID m_listFilesMid; - jmethodID m_isDirectoryMid; - jmethodID m_isFileMid; - jmethodID m_existsMid; -public: - AndroidFilesystemCallbacks(jobject filesystemCallbacksObj, jmethodID openContentUriMid, - jmethodID listFilesMid, - jmethodID isDirectoryMid, - jmethodID isFileMid, - jmethodID existsMid) - : m_filesystemCallbacksObj(filesystemCallbacksObj), - m_openContentUriMid(openContentUriMid), - m_listFilesMid(listFilesMid), - m_isDirectoryMid(isDirectoryMid), - m_isFileMid(isFileMid), - m_existsMid(existsMid) {} - - int openContentUri(const std::filesystem::path &uri) override { - JNIUtils::ScopedJNIENV env; - jstring uriString = env->NewStringUTF(uri.c_str()); - int fd = env->CallIntMethod(*m_filesystemCallbacksObj, m_openContentUriMid, uriString); - env->DeleteLocalRef(uriString); - return fd; - } - - std::vector listFiles(const std::filesystem::path &uri) override { - JNIUtils::ScopedJNIENV env; - jstring uriString = env->NewStringUTF(uri.c_str()); - jobjectArray pathsObjArray = static_cast(env->CallObjectMethod( - *m_filesystemCallbacksObj, m_listFilesMid, uriString)); - env->DeleteLocalRef(uriString); - jsize arrayLength = env->GetArrayLength(pathsObjArray); - std::vector paths; - paths.reserve(arrayLength); - for (jsize i = 0; i < arrayLength; i++) { - jstring pathStrObj = static_cast(env->GetObjectArrayElement(pathsObjArray, i)); - paths.push_back(JNIUtils::JStringToString(*env, pathStrObj)); - env->DeleteLocalRef(pathStrObj); - } - env->DeleteLocalRef(pathsObjArray); - return paths; - } - - bool isDirectory(const std::filesystem::path &uri) override { - JNIUtils::ScopedJNIENV env; - jstring uriString = env->NewStringUTF(uri.c_str()); - bool isDir = env->CallBooleanMethod(*m_filesystemCallbacksObj, m_isDirectoryMid, uriString); - env->DeleteLocalRef(uriString); - return isDir; - } - - bool isFile(const std::filesystem::path &uri) override { - JNIUtils::ScopedJNIENV env; - jstring uriString = env->NewStringUTF(uri.c_str()); - bool isFile = env->CallBooleanMethod(*m_filesystemCallbacksObj, m_isFileMid, uriString); - env->DeleteLocalRef(uriString); - return isFile; - } - - bool exists(const std::filesystem::path &uri) override { - JNIUtils::ScopedJNIENV env; - jstring uriString = env->NewStringUTF(uri.c_str()); - bool exists = env->CallBooleanMethod(*m_filesystemCallbacksObj, m_existsMid, uriString); - env->DeleteLocalRef(uriString); - return exists; - } - -}; - -std::shared_ptr g_androidFilesystemCallbacks = nullptr; -extern "C" -JNIEXPORT void JNICALL -Java_info_cemu_Cemu_NativeLibrary_setFileSystemCallbacks(JNIEnv *env, jclass clazz, - jobject file_system_callbacks) { - jclass fileSystemCallbackClass = env->GetObjectClass(file_system_callbacks); - jmethodID openContentUriMid = env->GetMethodID(fileSystemCallbackClass, "openContentUri", - "(Ljava/lang/String;)I"); - jmethodID listFilesMid = env->GetMethodID(fileSystemCallbackClass, "listFiles", - "(Ljava/lang/String;)[Ljava/lang/String;"); - jmethodID isDirectoryMid = env->GetMethodID(fileSystemCallbackClass, "isDirectory", - "(Ljava/lang/String;)Z"); - jmethodID isFileMid = env->GetMethodID(fileSystemCallbackClass, "isFile", - "(Ljava/lang/String;)Z"); - jmethodID existsMid = env->GetMethodID(fileSystemCallbackClass, "exists", - "(Ljava/lang/String;)Z"); - g_androidFilesystemCallbacks = std::make_shared( - file_system_callbacks, openContentUriMid, listFilesMid, isDirectoryMid, isFileMid, - existsMid); - FilesystemAndroid::setFilesystemCallbacks(g_androidFilesystemCallbacks); -} extern "C" JNIEXPORT void JNICALL Java_info_cemu_Cemu_NativeLibrary_addGamePath(JNIEnv *env, jclass clazz, jstring uri) { diff --git a/src/android/app/src/main/java/info/cemu/Cemu/CemuApplication.java b/src/android/app/src/main/java/info/cemu/Cemu/CemuApplication.java index 2a5a091f..b94d7070 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/CemuApplication.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/CemuApplication.java @@ -12,33 +12,8 @@ public class CemuApplication extends Application { super.onCreate(); DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); NativeLibrary.setDPI(displayMetrics.density); - NativeLibrary.setFileSystemCallbacks(new NativeLibrary.FileSystemCallbacks() { - @Override - public int openContentUri(String uri) { - return FileUtil.openContentUri(getApplicationContext(), uri); - } - - @Override - public String[] listFiles(String uri) { - return FileUtil.listFiles(getApplicationContext(), uri); - } - - @Override - public boolean isDirectory(String uri) { - return FileUtil.isDirectory(getApplicationContext(), uri); - } - - @Override - public boolean isFile(String uri) { - return FileUtil.isFile(getApplicationContext(), uri); - } - - @Override - public boolean exists(String uri) { - return FileUtil.exists(getApplicationContext(), uri); - } - }); - NativeLibrary.initializeActiveSettings(getExternalFilesDir(null).getAbsoluteFile().toString(), getCacheDir().toString()); + FileUtil.setCemuApplication(this); + NativeLibrary.initializeActiveSettings(getExternalFilesDir(null).getAbsoluteFile().toString(), getExternalFilesDir(null).getAbsoluteFile().toString()); NativeLibrary.initializeEmulation(); } } diff --git a/src/android/app/src/main/java/info/cemu/Cemu/FileUtil.java b/src/android/app/src/main/java/info/cemu/Cemu/FileUtil.java index 57453721..575a138c 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/FileUtil.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/FileUtil.java @@ -19,6 +19,12 @@ public class FileUtil { private static final String COLON_ENCODED = "%3A"; private static final String MODE = "r"; + private static CemuApplication cemuApplication; + + public static void setCemuApplication(CemuApplication cemuApplication) { + FileUtil.cemuApplication = cemuApplication; + } + private static String toCppPath(Uri uri) { String uriPath = uri.toString(); int delimiterPos = uriPath.lastIndexOf(COLON_ENCODED); @@ -30,12 +36,12 @@ public class FileUtil { return Uri.parse(cppPath.substring(0, delimiterPos) + cppPath.substring(delimiterPos).replace(PATH_SEPARATOR_DECODED, PATH_SEPARATOR_ENCODED)); } - public static int openContentUri(Context context, String uri) { + public static int openContentUri(String uri) { try { - if (!exists(context, uri)) { + if (!exists(uri)) { return -1; } - ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(fromCppPath(uri), MODE); + ParcelFileDescriptor parcelFileDescriptor = cemuApplication.getApplicationContext().getContentResolver().openFileDescriptor(fromCppPath(uri), MODE); if (parcelFileDescriptor != null) { int fd = parcelFileDescriptor.detachFd(); parcelFileDescriptor.close(); @@ -47,11 +53,11 @@ public class FileUtil { return -1; } - public static String[] listFiles(Context context, String uri) { + public static String[] listFiles(String uri) { ArrayList files = new ArrayList<>(); Uri directoryUri = fromCppPath(uri); Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(directoryUri, DocumentsContract.getDocumentId(directoryUri)); - try (Cursor cursor = context.getContentResolver().query(childrenUri, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID}, null, null, null)) { + try (Cursor cursor = cemuApplication.getApplicationContext().getContentResolver().query(childrenUri, new String[]{DocumentsContract.Document.COLUMN_DOCUMENT_ID}, null, null, null)) { while (cursor != null && cursor.moveToNext()) { String documentId = cursor.getString(0); Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(directoryUri, documentId); @@ -65,17 +71,17 @@ public class FileUtil { return filesArray; } - public static boolean isDirectory(Context context, String uri) { - String mimeType = context.getContentResolver().getType(fromCppPath(uri)); + public static boolean isDirectory(String uri) { + String mimeType = cemuApplication.getApplicationContext().getContentResolver().getType(fromCppPath(uri)); return DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType); } - public static boolean isFile(Context context, String uri) { - return !isDirectory(context, uri); + public static boolean isFile(String uri) { + return !isDirectory(uri); } - public static boolean exists(Context context, String uri) { - try (Cursor cursor = context.getContentResolver().query(fromCppPath(uri), null, null, null, null)) { + public static boolean exists(String uri) { + try (Cursor cursor = cemuApplication.getApplicationContext().getContentResolver().query(fromCppPath(uri), null, null, null, null)) { return cursor != null && cursor.moveToFirst(); } catch (Exception e) { Log.e("FileUtil", "Failed checking if file exists: " + e.getMessage());