From f60abeddcd5f434377a6832c1d2b1b7dd5a1275f Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Sat, 17 Jun 2023 22:52:58 +0300 Subject: [PATCH] Add support for reading game files using SAF --- .gitmodules | 2 +- CMakeLists.txt | 11 +- dependencies/ZArchive | 2 +- src/Cafe/TitleList/TitleInfo.cpp | 21 ++- src/Cafe/TitleList/TitleList.cpp | 70 ++++++-- src/Common/CMakeLists.txt | 15 ++ src/Common/FileStream.cpp | 155 ++++++++++++++++++ src/Common/FileStream.h | 46 +++++- src/Common/unix/ContentUriIStream.h | 26 +++ src/Common/unix/FileStream_contentUri.cpp | 122 ++++++++++++++ src/Common/unix/FileStream_contentUri.h | 37 +++++ src/Common/unix/FileStream_unix.cpp | 98 +++-------- src/Common/unix/FileStream_unix.h | 76 ++++----- src/Common/unix/FilesystemAndroid.cpp | 48 ++++++ src/Common/unix/FilesystemAndroid.h | 29 ++++ src/Common/windows/FileStream_win32.cpp | 100 ++--------- src/Common/windows/FileStream_win32.h | 52 +++--- src/android/app/src/main/cpp/native-lib.cpp | 101 ++++++++++++ .../java/info/cemu/Cemu/CemuApplication.java | 29 ++++ .../main/java/info/cemu/Cemu/FileUtil.java | 85 ++++++++++ .../java/info/cemu/Cemu/NativeLibrary.java | 18 ++ src/gui/AndroidGui/AndroidGuiWrapper.cpp | 5 +- src/gui/AndroidGui/AndroidGuiWrapper.h | 4 +- src/gui/AndroidGui/GameTitleLoader.cpp | 6 + src/gui/AndroidGui/GameTitleLoader.h | 2 + vcpkg.json | 4 + 26 files changed, 898 insertions(+), 266 deletions(-) create mode 100644 src/Common/FileStream.cpp create mode 100644 src/Common/unix/ContentUriIStream.h create mode 100644 src/Common/unix/FileStream_contentUri.cpp create mode 100644 src/Common/unix/FileStream_contentUri.h create mode 100644 src/Common/unix/FilesystemAndroid.cpp create mode 100644 src/Common/unix/FilesystemAndroid.h create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/FileUtil.java diff --git a/.gitmodules b/.gitmodules index f352d478..0d95d984 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "dependencies/ZArchive"] path = dependencies/ZArchive - url = https://github.com/Exzap/ZArchive + url = https://github.com/SSimco/ZArchive shallow = true [submodule "dependencies/cubeb"] path = dependencies/cubeb diff --git a/CMakeLists.txt b/CMakeLists.txt index bb40f2ca..e54a4534 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,8 +57,10 @@ endif() set_property(GLOBAL PROPERTY USE_FOLDERS ON) # enable link time optimization for release builds -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON) +if(NOT ANDROID) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON) + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON) +endif() if (MSVC) set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT CemuBin) @@ -117,7 +119,10 @@ endif() find_package(CURL REQUIRED) find_package(pugixml REQUIRED) find_package(RapidJSON REQUIRED) -find_package(Boost COMPONENTS program_options filesystem nowide context REQUIRED) +find_package(Boost COMPONENTS program_options filesystem nowide REQUIRED) +if(ANDROID) + find_package(Boost COMPONENTS context iostreams REQUIRED) +endif() find_package(libzip REQUIRED) find_package(glslang REQUIRED) find_package(ZLIB REQUIRED) diff --git a/dependencies/ZArchive b/dependencies/ZArchive index d2c71773..c50b5d22 160000 --- a/dependencies/ZArchive +++ b/dependencies/ZArchive @@ -1 +1 @@ -Subproject commit d2c717730092c7bf8cbb033b12fd4001b7c4d932 +Subproject commit c50b5d222bb599be9287664c835c2a820aa5342a diff --git a/src/Cafe/TitleList/TitleInfo.cpp b/src/Cafe/TitleList/TitleInfo.cpp index 36814ab8..7dcb0beb 100644 --- a/src/Cafe/TitleList/TitleInfo.cpp +++ b/src/Cafe/TitleList/TitleInfo.cpp @@ -6,6 +6,11 @@ #include "pugixml.hpp" #include "Common/FileStream.h" +#if __ANDROID__ +#include "Common/unix/FilesystemAndroid.h" +#include "Common/unix/ContentUriIStream.h" +#endif // __ANDROID__ + #include #include "config/ActiveSettings.h" @@ -209,7 +214,13 @@ bool TitleInfo::DetectFormat(const fs::path& path, fs::path& pathOut, TitleDataF pathOut = path; // a Wii U archive file can contain multiple titles but TitleInfo only maps to one // we use the first base title that we find. This is the most intuitive behavior when someone launches "game.wua" - ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); + ZArchiveReader* zar = nullptr; +#if __ANDROID__ + if(FilesystemAndroid::isContentUri(path)) + zar = ZArchiveReader::OpenFromStream(std::make_unique(path)); + else +#endif // __ANDROID__ + zar = ZArchiveReader::OpenFromFile(path); if (!zar) return false; ZArchiveNodeHandle rootDir = zar->LookUp("", false, true); @@ -327,7 +338,13 @@ ZArchiveReader* _ZArchivePool_AcquireInstance(const fs::path& path) } _lock.unlock(); // opening wua files can be expensive, so we do it outside of the lock - ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); + ZArchiveReader* zar = nullptr; +#if __ANDROID__ + if(FilesystemAndroid::isContentUri(path)) + zar = ZArchiveReader::OpenFromStream(std::make_unique(path)); + else +#endif // __ANDROID__ + zar = ZArchiveReader::OpenFromFile(path); if (!zar) return nullptr; _lock.lock(); diff --git a/src/Cafe/TitleList/TitleList.cpp b/src/Cafe/TitleList/TitleList.cpp index deb4564f..3d975204 100644 --- a/src/Cafe/TitleList/TitleList.cpp +++ b/src/Cafe/TitleList/TitleList.cpp @@ -3,6 +3,11 @@ #include "util/helpers/helpers.h" +#if __ANDROID__ +#include "Common/unix/FilesystemAndroid.h" +#include "Common/unix/ContentUriIStream.h" +#endif // __ANDROID__ + #include bool sTLInitialized{ false }; @@ -213,7 +218,13 @@ void CafeTitleList::AddTitleFromPath(fs::path path) { if (path.has_extension() && boost::iequals(_pathToUtf8(path.extension()), ".wua")) { - ZArchiveReader* zar = ZArchiveReader::OpenFromFile(path); + ZArchiveReader* zar = nullptr; +#if __ANDROID__ + if(FilesystemAndroid::isContentUri(path)) + zar = ZArchiveReader::OpenFromStream(std::make_unique(path)); + else +#endif // __ANDROID__ + zar = ZArchiveReader::OpenFromFile(path); if (!zar) { cemuLog_log(LogType::Force, "Found {} but it is not a valid Wii U archive file", _pathToUtf8(path)); @@ -340,26 +351,51 @@ void CafeTitleList::ScanGamePath(const fs::path& path) std::vector filesInDirectory; std::vector dirsInDirectory; bool hasContentFolder = false, hasCodeFolder = false, hasMetaFolder = false; - std::error_code ec; - for (auto& it : fs::directory_iterator(path, ec)) - { - if (it.is_regular_file(ec)) + auto checkForTitleFolders = [&](const std::string& dirName) + { + if (boost::iequals(dirName, "content")) + hasContentFolder = true; + else if (boost::iequals(dirName, "code")) + hasCodeFolder = true; + else if (boost::iequals(dirName, "meta")) + hasMetaFolder = true; + }; +#if __ANDROID__ + if(FilesystemAndroid::isContentUri(path)) + { + for(auto&& file:FilesystemAndroid::listFiles(path)) { - filesInDirectory.emplace_back(it.path()); - } - else if (it.is_directory(ec)) - { - dirsInDirectory.emplace_back(it.path()); + if(FilesystemAndroid::isFile(file)) + { + filesInDirectory.emplace_back(file); + } + else if(FilesystemAndroid::isDirectory(file)) + { + dirsInDirectory.emplace_back(file); - std::string dirName = _pathToUtf8(it.path().filename()); - if (boost::iequals(dirName, "content")) - hasContentFolder = true; - else if (boost::iequals(dirName, "code")) - hasCodeFolder = true; - else if (boost::iequals(dirName, "meta")) - hasMetaFolder = true; + checkForTitleFolders(_pathToUtf8(file.filename())); + } } } + else +#endif // __ANDROID__ + { + std::error_code ec; + for (auto& it : fs::directory_iterator(path, ec)) + { + if (it.is_regular_file(ec)) + { + filesInDirectory.emplace_back(it.path()); + } + else if (it.is_directory(ec)) + { + dirsInDirectory.emplace_back(it.path()); + + checkForTitleFolders(_pathToUtf8(it.path().filename())); + } + } + } + // always check individual files for (auto& it : filesInDirectory) { diff --git a/src/Common/CMakeLists.txt b/src/Common/CMakeLists.txt index 4442501b..086e40dd 100644 --- a/src/Common/CMakeLists.txt +++ b/src/Common/CMakeLists.txt @@ -6,6 +6,7 @@ add_library(CemuCommon ExceptionHandler/ExceptionHandler.cpp ExceptionHandler/ExceptionHandler.h FileStream.h + FileStream.cpp GLInclude/glext.h GLInclude/glFunctions.h GLInclude/GLInclude.h @@ -52,6 +53,16 @@ if(UNIX AND NOT APPLE) ) endif() +if(ANDROID) + target_sources(CemuCommon PRIVATE + unix/ContentUriIStream.h + unix/FilesystemAndroid.cpp + unix/FilesystemAndroid.h + unix/FileStream_contentUri.cpp + unix/FileStream_contentUri.h + ) +endif() + # All the targets wanting to use the precompiled.h header # have to link to CemuCommon target_precompile_headers(CemuCommon PUBLIC precompiled.h) @@ -66,6 +77,10 @@ target_link_libraries(CemuCommon PRIVATE glm::glm ) +if(ANDROID) + target_link_libraries(CemuCommon PRIVATE Boost::iostreams) +endif() + if (UNIX AND NOT APPLE AND NOT ANDROID) target_link_libraries(CemuCommon PRIVATE X11::X11 X11::Xrender X11::Xutil) endif() diff --git a/src/Common/FileStream.cpp b/src/Common/FileStream.cpp new file mode 100644 index 00000000..b227b10b --- /dev/null +++ b/src/Common/FileStream.cpp @@ -0,0 +1,155 @@ +#include "Common/FileStream.h" + +#if BOOST_OS_UNIX + +#if __ANDROID__ +#include "Common/unix/FileStream_contentUri.h" +#include "Common/unix/FilesystemAndroid.h" +#endif // __ANDROID__ + +#include "Common/unix/FileStream_unix.h" + +FileStream* FileStream::openFile(std::string_view path) +{ + return openFile2(path, false); +} + +FileStream* FileStream::openFile(const wchar_t* path, bool allowWrite) +{ + return openFile2(path, allowWrite); +} + +FileStream* FileStream::openFile2(const fs::path& path, bool allowWrite) +{ +#if __ANDROID__ + if (FilesystemAndroid::isContentUri(path)) + { + if (allowWrite || FilesystemAndroid::isDirectory(path)) + { + return nullptr; + } + FileStreamContentUri* fs = new FileStreamContentUri(path); + if (fs->m_isValid) + return fs; + delete fs; + return nullptr; + } +#endif // __ANDROID__ + FileStreamUnix* fs = new FileStreamUnix(path, true, allowWrite); + if (fs->m_isValid) + return fs; + delete fs; + return nullptr; +} + +FileStream* FileStream::createFile(const wchar_t* path) +{ + return createFile2(path); +} + +FileStream* FileStream::createFile(std::string_view path) +{ + return createFile2(path); +} + +FileStream* FileStream::createFile2(const fs::path& path) +{ + FileStreamUnix* fs = new FileStreamUnix(path, false, false); + if (fs->m_isValid) + return fs; + delete fs; + return nullptr; +} + +std::optional> FileStream::LoadIntoMemory(const fs::path& path) +{ + FileStream* fs = openFile2(path); + if (!fs) + return std::nullopt; + uint64 fileSize = fs->GetSize(); + if (fileSize > 0xFFFFFFFFull) + { + delete fs; + return std::nullopt; + } + std::optional> v(fileSize); + if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize) + { + delete fs; + return std::nullopt; + } + delete fs; + return v; +} + +#endif // BOOST_OS_UNIX + +#ifdef _WIN32 + +#include "Common/windows/FileStream_win32.h" + +FileStream* FileStream::openFile(std::string_view path) +{ + HANDLE hFile = CreateFileW(boost::nowide::widen(path.data(), path.size()).c_str(), FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + return new FileStreamWin32(hFile); +} + +FileStream* FileStream::openFile(const wchar_t* path, bool allowWrite) +{ + HANDLE hFile = CreateFileW(path, allowWrite ? (FILE_GENERIC_READ | FILE_GENERIC_WRITE) : FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + return new FileStreamWin32(hFile); +} + +FileStream* FileStream::openFile2(const fs::path& path, bool allowWrite) +{ + return openFile(path.generic_wstring().c_str(), allowWrite); +} + +FileStream* FileStream::createFile(const wchar_t* path) +{ + HANDLE hFile = CreateFileW(path, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + return new FileStreamWin32(hFile); +} + +FileStream* FileStream::createFile(std::string_view path) +{ + auto w = boost::nowide::widen(path.data(), path.size()); + HANDLE hFile = CreateFileW(w.c_str(), FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0); + if (hFile == INVALID_HANDLE_VALUE) + return nullptr; + return new FileStreamWin32(hFile); +} + +FileStream* FileStream::createFile2(const fs::path& path) +{ + return createFile(path.generic_wstring().c_str()); +} + +std::optional> FileStream::LoadIntoMemory(const fs::path& path) +{ + FileStream* fs = openFile2(path); + if (!fs) + return std::nullopt; + uint64 fileSize = fs->GetSize(); + if (fileSize > 0xFFFFFFFFull) + { + delete fs; + return std::nullopt; + } + std::optional> v(fileSize); + if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize) + { + delete fs; + return std::nullopt; + } + delete fs; + return v; +} + +#endif // _WIN32 \ No newline at end of file diff --git a/src/Common/FileStream.h b/src/Common/FileStream.h index f5d8ff0e..e91b0a17 100644 --- a/src/Common/FileStream.h +++ b/src/Common/FileStream.h @@ -1,8 +1,44 @@ #pragma once #include "Common/precompiled.h" -#ifdef _WIN32 -#include "Common/windows/FileStream_win32.h" -#else -#include "Common/unix/FileStream_unix.h" -#endif +class FileStream +{ + public: + static FileStream* openFile(std::string_view path); + static FileStream* openFile(const wchar_t* path, bool allowWrite = false); + static FileStream* openFile2(const fs::path& path, bool allowWrite = false); + + static FileStream* createFile(const wchar_t* path); + static FileStream* createFile(std::string_view path); + static FileStream* createFile2(const fs::path& path); + + // helper function to load a file into memory + static std::optional> LoadIntoMemory(const fs::path& path); + + // size and seek + virtual void SetPosition(uint64 pos) = 0; + + virtual uint64 GetSize() = 0; + virtual bool SetEndOfFile() = 0; + virtual void extract(std::vector& data) = 0; + + // reading + virtual uint32 readData(void* data, uint32 length) = 0; + virtual bool readU64(uint64& v) = 0; + virtual bool readU32(uint32& v) = 0; + virtual bool readU8(uint8& v) = 0; + virtual bool readLine(std::string& line) = 0; + + // writing (binary) + virtual sint32 writeData(const void* data, sint32 length) = 0; + virtual void writeU64(uint64 v) = 0; + virtual void writeU32(uint32 v) = 0; + virtual void writeU8(uint8 v) = 0; + + // writing (strings) + virtual void writeStringFmt(const char* format, ...) = 0; + virtual void writeString(const char* str) = 0; + virtual void writeLine(const char* str) = 0; + + virtual ~FileStream() = default; +}; diff --git a/src/Common/unix/ContentUriIStream.h b/src/Common/unix/ContentUriIStream.h new file mode 100644 index 00000000..91b94d80 --- /dev/null +++ b/src/Common/unix/ContentUriIStream.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "Common/unix/FilesystemAndroid.h" + +class ContentUriIStream : public std::istream +{ + public: + ContentUriIStream(const std::filesystem::path& path) + : m_fd(FilesystemAndroid::openContentUri(path)), + m_fileDescriptorStreamBuffer(m_fd, boost::iostreams::close_handle), + std::istream(&m_fileDescriptorStreamBuffer) {} + + virtual ~ContentUriIStream() = default; + + inline bool isOpen() const + { + return m_fd != -1; + } + + private: + int m_fd; + boost::iostreams::stream_buffer m_fileDescriptorStreamBuffer; +}; diff --git a/src/Common/unix/FileStream_contentUri.cpp b/src/Common/unix/FileStream_contentUri.cpp new file mode 100644 index 00000000..2a7bf934 --- /dev/null +++ b/src/Common/unix/FileStream_contentUri.cpp @@ -0,0 +1,122 @@ +#include "FileStream_contentUri.h" + +#include "unix/ContentUriIStream.h" + +void ThrowWriteNotSupportedError() +{ + throw std::logic_error("write operation not supported"); +} + +void FileStreamContentUri::SetPosition(uint64 pos) +{ + if (!m_isValid) + return; + m_contentUriIStream.seekg((std::streampos)pos); +} + +uint64 FileStreamContentUri::GetSize() +{ + cemu_assert(m_isValid); + auto currentPos = m_contentUriIStream.tellg(); + m_contentUriIStream.seekg(0, std::ios::end); + auto fileSize = m_contentUriIStream.tellg(); + m_contentUriIStream.seekg(currentPos, std::ios::beg); + uint64 fs = (uint64)fileSize; + return fs; +} + +bool FileStreamContentUri::SetEndOfFile() +{ + return true; +} + +void FileStreamContentUri::extract(std::vector& data) +{ + if (!m_isValid) + return; + uint64 fileSize = GetSize(); + SetPosition(0); + data.resize(fileSize); + readData(data.data(), fileSize); +} + +uint32 FileStreamContentUri::readData(void* data, uint32 length) +{ + m_contentUriIStream.read((char*)data, length); + size_t bytesRead = m_contentUriIStream.gcount(); + return (uint32)bytesRead; +} + +bool FileStreamContentUri::readU64(uint64& v) +{ + return readData(&v, sizeof(uint64)) == sizeof(uint64); +} + +bool FileStreamContentUri::readU32(uint32& v) +{ + return readData(&v, sizeof(uint32)) == sizeof(uint32); +} + +bool FileStreamContentUri::readU8(uint8& v) +{ + return readData(&v, sizeof(uint8)) == sizeof(uint8); +} + +bool FileStreamContentUri::readLine(std::string& line) +{ + line.clear(); + uint8 c; + bool isEOF = true; + while (readU8(c)) + { + isEOF = false; + if (c == '\r') + continue; + if (c == '\n') + break; + line.push_back((char)c); + } + return !isEOF; +} + +sint32 FileStreamContentUri::writeData(const void* data, sint32 length) +{ + ThrowWriteNotSupportedError(); + return -1; +} + +void FileStreamContentUri::writeU64(uint64 v) +{ + ThrowWriteNotSupportedError(); +} + +void FileStreamContentUri::writeU32(uint32 v) +{ + ThrowWriteNotSupportedError(); +} + +void FileStreamContentUri::writeU8(uint8 v) +{ + ThrowWriteNotSupportedError(); +} + +void FileStreamContentUri::writeStringFmt(const char* format, ...) +{ + ThrowWriteNotSupportedError(); +} + +void FileStreamContentUri::writeString(const char* str) +{ + ThrowWriteNotSupportedError(); +} + +void FileStreamContentUri::writeLine(const char* str) +{ + ThrowWriteNotSupportedError(); +} + +FileStreamContentUri::FileStreamContentUri(const std::string& uri) + : m_contentUriIStream(uri) +{ + m_isValid = m_contentUriIStream.isOpen(); +} diff --git a/src/Common/unix/FileStream_contentUri.h b/src/Common/unix/FileStream_contentUri.h new file mode 100644 index 00000000..907dda2e --- /dev/null +++ b/src/Common/unix/FileStream_contentUri.h @@ -0,0 +1,37 @@ +#pragma once + +#include "Common/FileStream.h" +#include "Common/unix/ContentUriIStream.h" + +class FileStreamContentUri : public FileStream +{ + public: + // size and seek + void SetPosition(uint64 pos) override; + + uint64 GetSize() override; + bool SetEndOfFile() override; + void extract(std::vector& data) override; + + // reading + uint32 readData(void* data, uint32 length) override; + bool readU64(uint64& v) override; + bool readU32(uint32& v) override; + bool readU8(uint8& v) override; + bool readLine(std::string& line) override; + + // writing (not supported) + sint32 writeData(const void* data, sint32 length) override; + void writeU64(uint64 v) override; + void writeU32(uint32 v) override; + void writeU8(uint8 v) override; + void writeStringFmt(const char* format, ...) override; + void writeString(const char* str) override; + void writeLine(const char* str) override; + + private: + friend class FileStream; + FileStreamContentUri(const std::string& uri); + ContentUriIStream m_contentUriIStream; + bool m_isValid = false; +}; diff --git a/src/Common/unix/FileStream_unix.cpp b/src/Common/unix/FileStream_unix.cpp index c65a3219..cfda5af0 100644 --- a/src/Common/unix/FileStream_unix.cpp +++ b/src/Common/unix/FileStream_unix.cpp @@ -21,67 +21,7 @@ fs::path findPathCI(const fs::path& path) return parentPath / fName; } -FileStream* FileStream::openFile(std::string_view path) -{ - return openFile2(path, false); -} - -FileStream* FileStream::openFile(const wchar_t* path, bool allowWrite) -{ - return openFile2(path, allowWrite); -} - -FileStream* FileStream::openFile2(const fs::path& path, bool allowWrite) -{ - //return openFile(path.generic_wstring().c_str(), allowWrite); - FileStream* fs = new FileStream(path, true, allowWrite); - if (fs->m_isValid) - return fs; - delete fs; - return nullptr; -} - -FileStream* FileStream::createFile(const wchar_t* path) -{ - return createFile2(path); -} - -FileStream* FileStream::createFile(std::string_view path) -{ - return createFile2(path); -} - -FileStream* FileStream::createFile2(const fs::path& path) -{ - FileStream* fs = new FileStream(path, false, false); - if (fs->m_isValid) - return fs; - delete fs; - return nullptr; -} - -std::optional> FileStream::LoadIntoMemory(const fs::path& path) -{ - FileStream* fs = openFile2(path); - if (!fs) - return std::nullopt; - uint64 fileSize = fs->GetSize(); - if (fileSize > 0xFFFFFFFFull) - { - delete fs; - return std::nullopt; - } - std::optional> v(fileSize); - if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize) - { - delete fs; - return std::nullopt; - } - delete fs; - return v; -} - -void FileStream::SetPosition(uint64 pos) +void FileStreamUnix::SetPosition(uint64 pos) { cemu_assert(m_isValid); if (m_prevOperationWasWrite) @@ -90,7 +30,7 @@ void FileStream::SetPosition(uint64 pos) m_fileStream.seekg((std::streampos)pos); } -uint64 FileStream::GetSize() +uint64 FileStreamUnix::GetSize() { cemu_assert(m_isValid); auto currentPos = m_fileStream.tellg(); @@ -101,14 +41,14 @@ uint64 FileStream::GetSize() return fs; } -bool FileStream::SetEndOfFile() +bool FileStreamUnix::SetEndOfFile() { assert_dbg(); return true; //return ::SetEndOfFile(m_hFile) != 0; } -void FileStream::extract(std::vector& data) +void FileStreamUnix::extract(std::vector& data) { uint64 fileSize = GetSize(); SetPosition(0); @@ -116,7 +56,7 @@ void FileStream::extract(std::vector& data) readData(data.data(), fileSize); } -uint32 FileStream::readData(void* data, uint32 length) +uint32 FileStreamUnix::readData(void* data, uint32 length) { SyncReadWriteSeek(false); m_fileStream.read((char*)data, length); @@ -124,22 +64,22 @@ uint32 FileStream::readData(void* data, uint32 length) return (uint32)bytesRead; } -bool FileStream::readU64(uint64& v) +bool FileStreamUnix::readU64(uint64& v) { return readData(&v, sizeof(uint64)) == sizeof(uint64); } -bool FileStream::readU32(uint32& v) +bool FileStreamUnix::readU32(uint32& v) { return readData(&v, sizeof(uint32)) == sizeof(uint32); } -bool FileStream::readU8(uint8& v) +bool FileStreamUnix::readU8(uint8& v) { return readData(&v, sizeof(uint8)) == sizeof(uint8); } -bool FileStream::readLine(std::string& line) +bool FileStreamUnix::readLine(std::string& line) { line.clear(); uint8 c; @@ -156,29 +96,29 @@ bool FileStream::readLine(std::string& line) return !isEOF; } -sint32 FileStream::writeData(const void* data, sint32 length) +sint32 FileStreamUnix::writeData(const void* data, sint32 length) { SyncReadWriteSeek(true); m_fileStream.write((const char*)data, length); return length; } -void FileStream::writeU64(uint64 v) +void FileStreamUnix::writeU64(uint64 v) { writeData(&v, sizeof(uint64)); } -void FileStream::writeU32(uint32 v) +void FileStreamUnix::writeU32(uint32 v) { writeData(&v, sizeof(uint32)); } -void FileStream::writeU8(uint8 v) +void FileStreamUnix::writeU8(uint8 v) { writeData(&v, sizeof(uint8)); } -void FileStream::writeStringFmt(const char* format, ...) +void FileStreamUnix::writeStringFmt(const char* format, ...) { char buffer[2048]; va_list args; @@ -187,18 +127,18 @@ void FileStream::writeStringFmt(const char* format, ...) writeData(buffer, (sint32)strlen(buffer)); } -void FileStream::writeString(const char* str) +void FileStreamUnix::writeString(const char* str) { writeData(str, (sint32)strlen(str)); } -void FileStream::writeLine(const char* str) +void FileStreamUnix::writeLine(const char* str) { writeData(str, (sint32)strlen(str)); writeData("\r\n", 2); } -FileStream::~FileStream() +FileStreamUnix::~FileStreamUnix() { if (m_isValid) { @@ -207,7 +147,7 @@ FileStream::~FileStream() // CloseHandle(m_hFile); } -FileStream::FileStream(const fs::path& path, bool isOpen, bool isWriteable) +FileStreamUnix::FileStreamUnix(const fs::path& path, bool isOpen, bool isWriteable) { fs::path CIPath = findPathCI(path); if (isOpen) @@ -227,7 +167,7 @@ FileStream::FileStream(const fs::path& path, bool isOpen, bool isWriteable) } } -void FileStream::SyncReadWriteSeek(bool nextOpIsWrite) +void FileStreamUnix::SyncReadWriteSeek(bool nextOpIsWrite) { // nextOpIsWrite == false -> read. Otherwise write if (nextOpIsWrite == m_prevOperationWasWrite) diff --git a/src/Common/unix/FileStream_unix.h b/src/Common/unix/FileStream_unix.h index 12c971d1..425a5731 100644 --- a/src/Common/unix/FileStream_unix.h +++ b/src/Common/unix/FileStream_unix.h @@ -1,56 +1,44 @@ #pragma once #include "Common/precompiled.h" +#include "Common/FileStream.h" -class FileStream +class FileStreamUnix : public FileStream { - public: - static FileStream* openFile(std::string_view path); - static FileStream* openFile(const wchar_t* path, bool allowWrite = false); - static FileStream* openFile2(const fs::path& path, bool allowWrite = false); + public: + // size and seek + void SetPosition(uint64 pos) override; - static FileStream* createFile(const wchar_t* path); - static FileStream* createFile(std::string_view path); - static FileStream* createFile2(const fs::path& path); + uint64 GetSize() override; + bool SetEndOfFile() override; + void extract(std::vector& data) override; - // helper function to load a file into memory - static std::optional> LoadIntoMemory(const fs::path& path); + // reading + uint32 readData(void* data, uint32 length) override; + bool readU64(uint64& v) override; + bool readU32(uint32& v) override; + bool readU8(uint8& v) override; + bool readLine(std::string& line) override; - // size and seek - void SetPosition(uint64 pos); + // writing (binary) + sint32 writeData(const void* data, sint32 length) override; + void writeU64(uint64 v) override; + void writeU32(uint32 v) override; + void writeU8(uint8 v) override; - uint64 GetSize(); - bool SetEndOfFile(); - void extract(std::vector& data); + // writing (strings) + void writeStringFmt(const char* format, ...) override; + void writeString(const char* str) override; + void writeLine(const char* str) override; - // reading - uint32 readData(void* data, uint32 length); - bool readU64(uint64& v); - bool readU32(uint32& v); - bool readU16(uint16& v); - bool readU8(uint8& v); - bool readLine(std::string& line); + virtual ~FileStreamUnix(); + FileStreamUnix() = default; - // writing (binary) - sint32 writeData(const void* data, sint32 length); - void writeU64(uint64 v); - void writeU32(uint32 v); - void writeU16(uint16 v); - void writeU8(uint8 v); - - // writing (strings) - void writeStringFmt(const char* format, ...); - void writeString(const char* str); - void writeLine(const char* str); - - ~FileStream(); - FileStream() {}; - - private: - void SyncReadWriteSeek(bool nextOpIsWrite); - FileStream(const fs::path& path, bool isOpen, bool isWriteable); - - bool m_isValid{}; - std::fstream m_fileStream; - bool m_prevOperationWasWrite{false}; + private: + friend class FileStream; + void SyncReadWriteSeek(bool nextOpIsWrite); + FileStreamUnix(const fs::path& path, bool isOpen, bool isWriteable); + bool m_isValid{}; + std::fstream m_fileStream; + bool m_prevOperationWasWrite{false}; }; diff --git a/src/Common/unix/FilesystemAndroid.cpp b/src/Common/unix/FilesystemAndroid.cpp new file mode 100644 index 00000000..f072d4c0 --- /dev/null +++ b/src/Common/unix/FilesystemAndroid.cpp @@ -0,0 +1,48 @@ +#include "FilesystemAndroid.h" + +namespace FilesystemAndroid +{ +std::shared_ptr g_filesystemCallbacks = nullptr; + +void setFilesystemCallbacks(const std::shared_ptr &filesystemCallbacks) +{ + g_filesystemCallbacks = filesystemCallbacks; +} + +int openContentUri(const fs::path &uri) +{ + if (g_filesystemCallbacks) + return g_filesystemCallbacks->openContentUri(uri); + return -1; +} + +std::vector listFiles(const fs::path &uri) +{ + if (g_filesystemCallbacks) + return g_filesystemCallbacks->listFiles(uri); + return {}; +} +bool isDirectory(const fs::path &uri) +{ + if (g_filesystemCallbacks) + return g_filesystemCallbacks->isDirectory(uri); + return false; +} +bool isFile(const fs::path &uri) +{ + if (g_filesystemCallbacks) + return g_filesystemCallbacks->isFile(uri); + return false; +} +bool exists(const fs::path &uri) +{ + if (g_filesystemCallbacks) + return g_filesystemCallbacks->exists(uri); + return false; +} +bool isContentUri(const std::string &uri) +{ + static constexpr auto content = "content://"; + return uri.starts_with(content); +} +} // namespace FilesystemAndroid \ No newline at end of file diff --git a/src/Common/unix/FilesystemAndroid.h b/src/Common/unix/FilesystemAndroid.h new file mode 100644 index 00000000..b67c5be0 --- /dev/null +++ b/src/Common/unix/FilesystemAndroid.h @@ -0,0 +1,29 @@ +#pragma once + +namespace FilesystemAndroid +{ +class FilesystemCallbacks +{ + public: + virtual int openContentUri(const std::filesystem::path &uri) = 0; + virtual std::vector listFiles(const std::filesystem::path &uri) = 0; + virtual bool isDirectory(const std::filesystem::path &uri) = 0; + virtual bool isFile(const std::filesystem::path &uri) = 0; + virtual bool exists(const std::filesystem::path &uri) = 0; +}; + +void setFilesystemCallbacks(const std::shared_ptr &filesystemCallbacks); + +int openContentUri(const std::filesystem::path &uri); + +std::vector listFiles(const std::filesystem::path &uri); + +bool isDirectory(const std::filesystem::path &uri); + +bool isFile(const std::filesystem::path &uri); + +bool exists(const std::filesystem::path& uri); + +bool isContentUri(const std::string &uri); + +} // namespace FilesystemAndroid diff --git a/src/Common/windows/FileStream_win32.cpp b/src/Common/windows/FileStream_win32.cpp index 2ae1247f..3cdc80e4 100644 --- a/src/Common/windows/FileStream_win32.cpp +++ b/src/Common/windows/FileStream_win32.cpp @@ -1,77 +1,13 @@ #include "Common/windows/FileStream_win32.h" -FileStream* FileStream::openFile(std::string_view path) -{ - HANDLE hFile = CreateFileW(boost::nowide::widen(path.data(), path.size()).c_str(), FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); - if (hFile == INVALID_HANDLE_VALUE) - return nullptr; - return new FileStream(hFile); -} - -FileStream* FileStream::openFile(const wchar_t* path, bool allowWrite) -{ - HANDLE hFile = CreateFileW(path, allowWrite ? (FILE_GENERIC_READ | FILE_GENERIC_WRITE) : FILE_GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0); - if (hFile == INVALID_HANDLE_VALUE) - return nullptr; - return new FileStream(hFile); -} - -FileStream* FileStream::openFile2(const fs::path& path, bool allowWrite) -{ - return openFile(path.generic_wstring().c_str(), allowWrite); -} - -FileStream* FileStream::createFile(const wchar_t* path) -{ - HANDLE hFile = CreateFileW(path, FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0); - if (hFile == INVALID_HANDLE_VALUE) - return nullptr; - return new FileStream(hFile); -} - -FileStream* FileStream::createFile(std::string_view path) -{ - auto w = boost::nowide::widen(path.data(), path.size()); - HANDLE hFile = CreateFileW(w.c_str(), FILE_GENERIC_READ | FILE_GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, 0); - if (hFile == INVALID_HANDLE_VALUE) - return nullptr; - return new FileStream(hFile); -} - -FileStream* FileStream::createFile2(const fs::path& path) -{ - return createFile(path.generic_wstring().c_str()); -} - -std::optional> FileStream::LoadIntoMemory(const fs::path& path) -{ - FileStream* fs = openFile2(path); - if (!fs) - return std::nullopt; - uint64 fileSize = fs->GetSize(); - if(fileSize > 0xFFFFFFFFull) - { - delete fs; - return std::nullopt; - } - std::optional> v(fileSize); - if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize) - { - delete fs; - return std::nullopt; - } - delete fs; - return v; -} - -void FileStream::SetPosition(uint64 pos) +void FileStreamWin32::SetPosition(uint64 pos) { LONG posHigh = (LONG)(pos >> 32); LONG posLow = (LONG)(pos); SetFilePointer(m_hFile, posLow, &posHigh, FILE_BEGIN); } -uint64 FileStream::GetSize() +uint64 FileStreamWin32::GetSize() { DWORD fileSizeHigh = 0; DWORD fileSizeLow = 0; @@ -79,12 +15,12 @@ uint64 FileStream::GetSize() return ((uint64)fileSizeHigh << 32) | (uint64)fileSizeLow; } -bool FileStream::SetEndOfFile() +bool FileStreamWin32::SetEndOfFile() { return ::SetEndOfFile(m_hFile) != 0; } -void FileStream::extract(std::vector& data) +void FileStreamWin32::extract(std::vector& data) { DWORD fileSize = GetFileSize(m_hFile, nullptr); data.resize(fileSize); @@ -93,29 +29,29 @@ void FileStream::extract(std::vector& data) ReadFile(m_hFile, data.data(), fileSize, &bt, nullptr); } -uint32 FileStream::readData(void* data, uint32 length) +uint32 FileStreamWin32::readData(void* data, uint32 length) { DWORD bytesRead = 0; ReadFile(m_hFile, data, length, &bytesRead, NULL); return bytesRead; } -bool FileStream::readU64(uint64& v) +bool FileStreamWin32::readU64(uint64& v) { return readData(&v, sizeof(uint64)) == sizeof(uint64); } -bool FileStream::readU32(uint32& v) +bool FileStreamWin32::readU32(uint32& v) { return readData(&v, sizeof(uint32)) == sizeof(uint32); } -bool FileStream::readU8(uint8& v) +bool FileStreamWin32::readU8(uint8& v) { return readData(&v, sizeof(uint8)) == sizeof(uint8); } -bool FileStream::readLine(std::string& line) +bool FileStreamWin32::readLine(std::string& line) { line.clear(); uint8 c; @@ -132,29 +68,29 @@ bool FileStream::readLine(std::string& line) return !isEOF; } -sint32 FileStream::writeData(const void* data, sint32 length) +sint32 FileStreamWin32::writeData(const void* data, sint32 length) { DWORD bytesWritten = 0; WriteFile(m_hFile, data, length, &bytesWritten, NULL); return bytesWritten; } -void FileStream::writeU64(uint64 v) +void FileStreamWin32::writeU64(uint64 v) { writeData(&v, sizeof(uint64)); } -void FileStream::writeU32(uint32 v) +void FileStreamWin32::writeU32(uint32 v) { writeData(&v, sizeof(uint32)); } -void FileStream::writeU8(uint8 v) +void FileStreamWin32::writeU8(uint8 v) { writeData(&v, sizeof(uint8)); } -void FileStream::writeStringFmt(const char* format, ...) +void FileStreamWin32::writeStringFmt(const char* format, ...) { char buffer[2048]; va_list args; @@ -163,24 +99,24 @@ void FileStream::writeStringFmt(const char* format, ...) writeData(buffer, (sint32)strlen(buffer)); } -void FileStream::writeString(const char* str) +void FileStreamWin32::writeString(const char* str) { writeData(str, (sint32)strlen(str)); } -void FileStream::writeLine(const char* str) +void FileStreamWin32::writeLine(const char* str) { writeData(str, (sint32)strlen(str)); writeData("\r\n", 2); } -FileStream::~FileStream() +FileStreamWin32::~FileStreamWin32() { if(m_isValid) CloseHandle(m_hFile); } -FileStream::FileStream(HANDLE hFile) +FileStreamWin32::FileStreamWin32(HANDLE hFile) { m_hFile = hFile; m_isValid = true; diff --git a/src/Common/windows/FileStream_win32.h b/src/Common/windows/FileStream_win32.h index a0b1b30a..b9a0c861 100644 --- a/src/Common/windows/FileStream_win32.h +++ b/src/Common/windows/FileStream_win32.h @@ -1,52 +1,44 @@ #pragma once #include "Common/precompiled.h" +#include "Common/FileStream.h" -class FileStream +class FileStreamWin32 : public FileStream { public: - static FileStream* openFile(std::string_view path); - static FileStream* openFile(const wchar_t* path, bool allowWrite = false); - static FileStream* openFile2(const fs::path& path, bool allowWrite = false); - - static FileStream* createFile(const wchar_t* path); - static FileStream* createFile(std::string_view path); - static FileStream* createFile2(const fs::path& path); - // helper function to load a file into memory static std::optional> LoadIntoMemory(const fs::path& path); // size and seek - void SetPosition(uint64 pos); + void SetPosition(uint64 pos) override; - uint64 GetSize(); - bool SetEndOfFile(); - void extract(std::vector& data); + uint64 GetSize() override; + bool SetEndOfFile() override; + void extract(std::vector& data) override; // reading - uint32 readData(void* data, uint32 length); - bool readU64(uint64& v); - bool readU32(uint32& v); - bool readU16(uint16& v); - bool readU8(uint8& v); - bool readLine(std::string& line); + uint32 readData(void* data, uint32 length) override; + bool readU64(uint64& v) override; + bool readU32(uint32& v) override; + bool readU8(uint8& v) override; + bool readLine(std::string& line) override; // writing (binary) - sint32 writeData(const void* data, sint32 length); - void writeU64(uint64 v); - void writeU32(uint32 v); - void writeU16(uint16 v); - void writeU8(uint8 v); + sint32 writeData(const void* data, sint32 length) override; + void writeU64(uint64 v) override; + void writeU32(uint32 v) override; + void writeU8(uint8 v) override; // writing (strings) - void writeStringFmt(const char* format, ...); - void writeString(const char* str); - void writeLine(const char* str); + void writeStringFmt(const char* format, ...) override; + void writeString(const char* str) override; + void writeLine(const char* str) override; - ~FileStream(); - FileStream() = default; + virtual ~FileStreamWin32(); + FileStreamWin32() = default; private: - FileStream(HANDLE hFile); + friend class FileStream; + FileStreamWin32(HANDLE hFile); bool m_isValid{}; HANDLE m_hFile; diff --git a/src/android/app/src/main/cpp/native-lib.cpp b/src/android/app/src/main/cpp/native-lib.cpp index 7a60c9e4..c003b6cd 100644 --- a/src/android/app/src/main/cpp/native-lib.cpp +++ b/src/android/app/src/main/cpp/native-lib.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "AndroidGui/Utils.h" #include "config/ActiveSettings.h" #include "config/NetworkSettings.h" @@ -199,4 +200,104 @@ JNIEXPORT void JNICALL Java_info_cemu_Cemu_NativeLibrary_recreateRenderSurface(JNIEnv *env, jclass clazz, jboolean is_main_canvas) { 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) { + gui_addGamePath(JNIUtils::JStringToString(env, uri)); } \ No newline at end of file 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 6b9f132e..2a5a091f 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 @@ -1,14 +1,43 @@ package info.cemu.Cemu; import android.app.Application; +import android.os.FileUtils; import android.util.DisplayMetrics; +import java.util.ArrayList; + public class CemuApplication extends Application { @Override public void onCreate() { 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()); 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 new file mode 100644 index 00000000..57453721 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/FileUtil.java @@ -0,0 +1,85 @@ +package info.cemu.Cemu; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract; +import android.util.Log; + +import androidx.documentfile.provider.DocumentFile; + +import java.io.File; +import java.util.ArrayList; +import java.util.Objects; + +public class FileUtil { + private static final String PATH_SEPARATOR_ENCODED = "%2F"; + private static final String PATH_SEPARATOR_DECODED = "/"; + private static final String COLON_ENCODED = "%3A"; + private static final String MODE = "r"; + + private static String toCppPath(Uri uri) { + String uriPath = uri.toString(); + int delimiterPos = uriPath.lastIndexOf(COLON_ENCODED); + return uriPath.substring(0, delimiterPos) + uriPath.substring(delimiterPos).replace(PATH_SEPARATOR_ENCODED, PATH_SEPARATOR_DECODED); + } + + private static Uri fromCppPath(String cppPath) { + int delimiterPos = cppPath.lastIndexOf(COLON_ENCODED); + 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) { + try { + if (!exists(context, uri)) { + return -1; + } + ParcelFileDescriptor parcelFileDescriptor = context.getContentResolver().openFileDescriptor(fromCppPath(uri), MODE); + if (parcelFileDescriptor != null) { + int fd = parcelFileDescriptor.detachFd(); + parcelFileDescriptor.close(); + return fd; + } + } catch (Exception e) { + Log.e("FileUtil", "Cannot open content uri, error: " + e.getMessage()); + } + return -1; + } + + public static String[] listFiles(Context context, 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)) { + while (cursor != null && cursor.moveToNext()) { + String documentId = cursor.getString(0); + Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(directoryUri, documentId); + files.add(toCppPath(documentUri)); + } + } catch (Exception e) { + Log.e("FileUtil", "Cannot list files: " + e.getMessage()); + } + String[] filesArray = new String[files.size()]; + filesArray = files.toArray(filesArray); + return filesArray; + } + + public static boolean isDirectory(Context context, String uri) { + String mimeType = context.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 exists(Context context, String uri) { + try (Cursor cursor = context.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()); + return false; + } + } +} diff --git a/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java b/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java index 17a8b1d9..2654ffb3 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java @@ -2,11 +2,27 @@ package info.cemu.Cemu; import android.view.Surface; +import java.util.ArrayList; + public class NativeLibrary { static { System.loadLibrary("CemuAndroid"); } + public interface FileSystemCallbacks { + int openContentUri(String uri); + + String[] listFiles(String uri); + + boolean isDirectory(String uri); + + boolean isFile(String uri); + + boolean exists(String uri); + } + + public static native void setFileSystemCallbacks(FileSystemCallbacks fileSystemCallbacks); + public static native void setDPI(float dpi); public static native void setSurface(Surface surface, boolean isMainCanvas); @@ -43,4 +59,6 @@ public class NativeLibrary { public static native void initializeActiveSettings(String dataPath, String cachePath); public static native void initializeEmulation(); + + public static native void addGamePath(String uri); } diff --git a/src/gui/AndroidGui/AndroidGuiWrapper.cpp b/src/gui/AndroidGui/AndroidGuiWrapper.cpp index 8664525d..d0c87f3f 100644 --- a/src/gui/AndroidGui/AndroidGuiWrapper.cpp +++ b/src/gui/AndroidGui/AndroidGuiWrapper.cpp @@ -20,7 +20,10 @@ void gui_setOnGameIconLoaded(const std::shared_ptr { g_gameIconLoader.setOnIconLoaded(onGameIconLoaded); } - +void gui_addGamePath(const fs::path& gamePath) +{ + g_gameTitleLoader.addGamePath(gamePath); +} void gui_requestGameIcon(TitleId titleId) { g_gameIconLoader.requestIcon(titleId); diff --git a/src/gui/AndroidGui/AndroidGuiWrapper.h b/src/gui/AndroidGui/AndroidGuiWrapper.h index 289cc1ab..66a044e0 100644 --- a/src/gui/AndroidGui/AndroidGuiWrapper.h +++ b/src/gui/AndroidGui/AndroidGuiWrapper.h @@ -13,4 +13,6 @@ void gui_requestGameIcon(TitleId titleId); void gui_reloadGameTitles(); -void gui_createCemuDirs(); \ No newline at end of file +void gui_createCemuDirs(); + +void gui_addGamePath(const fs::path& gamePath); diff --git a/src/gui/AndroidGui/GameTitleLoader.cpp b/src/gui/AndroidGui/GameTitleLoader.cpp index 8c9c15f2..ab3b0986 100644 --- a/src/gui/AndroidGui/GameTitleLoader.cpp +++ b/src/gui/AndroidGui/GameTitleLoader.cpp @@ -114,3 +114,9 @@ void GameTitleLoader::HandleTitleListCallback(CafeTitleListCallbackEvent *evt) { queueTitle(evt->titleInfo->GetAppTitleId()); } } + +void GameTitleLoader::addGamePath(const fs::path &path) { + CafeTitleList::ClearScanPaths(); + CafeTitleList::AddScanPath(path); + CafeTitleList::Refresh(); +} diff --git a/src/gui/AndroidGui/GameTitleLoader.h b/src/gui/AndroidGui/GameTitleLoader.h index a5bc727d..f4df5b2e 100644 --- a/src/gui/AndroidGui/GameTitleLoader.h +++ b/src/gui/AndroidGui/GameTitleLoader.h @@ -41,6 +41,8 @@ public: void titleRefresh(TitleId titleId); + void addGamePath(const fs::path &path); + private: void loadGameTitles(); diff --git a/vcpkg.json b/vcpkg.json index e83d3bcc..8ddc07d3 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -31,6 +31,10 @@ "name": "boost-context", "platform" : "android" }, + { + "name": "boost-iostreams", + "platform" : "android" + }, "boost-random", "fmt", "libpng",