Fix crash when calling JNI methods while inside a fiber & add support reading games from folder

This commit is contained in:
SSimco
2023-06-18 17:21:57 +03:00
parent f60abeddcd
commit 5a4e27e702
10 changed files with 509 additions and 140 deletions
+7
View File
@@ -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$<$<CONFIG:Debug>:Debug>")
target_include_directories(CemuCafe PUBLIC "../")
+3 -3
View File
@@ -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;
+15
View File
@@ -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);
}
+235
View File
@@ -0,0 +1,235 @@
#include "Cafe/Filesystem/fscDeviceAndroidSAF.h"
#include <memory>
#include <stdexcept>
#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<std::vector<fs::path>>(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;
}
+36
View File
@@ -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<fs::path> m_path{};
std::unique_ptr<std::vector<fs::path>> m_files{};
std::vector<fs::path>::iterator m_filesIterator;
};
+4 -5
View File
@@ -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)
{
+56
View File
@@ -83,6 +83,62 @@
#include <glm/gtc/quaternion.hpp>
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"
+134 -94
View File
@@ -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<void(JNIEnv *)> 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<std::filesystem::path> listFiles(const std::filesystem::path &uri) override {
std::unique_lock functionLock(m_functionMutex);
m_functionFinished = false;
std::vector<std::filesystem::path> paths;
{
std::lock_guard threadLock(m_threadMutex);
m_function = [&, this](JNIEnv *env) {
jstring uriString = env->NewStringUTF(uri.c_str());
jobjectArray pathsObjArray = static_cast<jobjectArray>(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<jstring>(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<AndroidFilesystemCallbacks> g_androidFilesystemCallbacks = nullptr;
extern "C"
JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_initializeEmulation(JNIEnv *env, jclass clazz) {
g_androidFilesystemCallbacks = std::make_shared<AndroidFilesystemCallbacks>();
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<std::filesystem::path> listFiles(const std::filesystem::path &uri) override {
JNIUtils::ScopedJNIENV env;
jstring uriString = env->NewStringUTF(uri.c_str());
jobjectArray pathsObjArray = static_cast<jobjectArray>(env->CallObjectMethod(
*m_filesystemCallbacksObj, m_listFilesMid, uriString));
env->DeleteLocalRef(uriString);
jsize arrayLength = env->GetArrayLength(pathsObjArray);
std::vector<std::filesystem::path> paths;
paths.reserve(arrayLength);
for (jsize i = 0; i < arrayLength; i++) {
jstring pathStrObj = static_cast<jstring>(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<AndroidFilesystemCallbacks> 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<AndroidFilesystemCallbacks>(
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) {
@@ -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();
}
}
@@ -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<String> 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());