Add support for reading game files using SAF

This commit is contained in:
SSimco
2023-06-17 23:19:17 +03:00
parent 3c8fe805b8
commit f60abeddcd
26 changed files with 898 additions and 266 deletions
+1 -1
View File
@@ -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
+8 -3
View File
@@ -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)
+19 -2
View File
@@ -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 <zarchive/zarchivereader.h>
#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<ContentUriIStream>(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<ContentUriIStream>(path));
else
#endif // __ANDROID__
zar = ZArchiveReader::OpenFromFile(path);
if (!zar)
return nullptr;
_lock.lock();
+53 -17
View File
@@ -3,6 +3,11 @@
#include "util/helpers/helpers.h"
#if __ANDROID__
#include "Common/unix/FilesystemAndroid.h"
#include "Common/unix/ContentUriIStream.h"
#endif // __ANDROID__
#include <zarchive/zarchivereader.h>
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<ContentUriIStream>(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<fs::path> filesInDirectory;
std::vector<fs::path> 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)
{
+15
View File
@@ -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()
+155
View File
@@ -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<std::vector<uint8>> 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<std::vector<uint8>> 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<std::vector<uint8>> 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<std::vector<uint8>> v(fileSize);
if (fs->readData(v->data(), (uint32)fileSize) != (uint32)fileSize)
{
delete fs;
return std::nullopt;
}
delete fs;
return v;
}
#endif // _WIN32
+41 -5
View File
@@ -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<std::vector<uint8>> 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<uint8>& 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;
};
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream_buffer.hpp>
#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<boost::iostreams::file_descriptor_source> m_fileDescriptorStreamBuffer;
};
+122
View File
@@ -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<uint8>& 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();
}
+37
View File
@@ -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<uint8>& 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;
};
+19 -79
View File
@@ -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<std::vector<uint8>> 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<std::vector<uint8>> 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<uint8>& data)
void FileStreamUnix::extract(std::vector<uint8>& data)
{
uint64 fileSize = GetSize();
SetPosition(0);
@@ -116,7 +56,7 @@ void FileStream::extract(std::vector<uint8>& 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)
+32 -44
View File
@@ -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<uint8>& data) override;
// helper function to load a file into memory
static std::optional<std::vector<uint8>> 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<uint8>& 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};
};
+48
View File
@@ -0,0 +1,48 @@
#include "FilesystemAndroid.h"
namespace FilesystemAndroid
{
std::shared_ptr<FilesystemCallbacks> g_filesystemCallbacks = nullptr;
void setFilesystemCallbacks(const std::shared_ptr<FilesystemCallbacks> &filesystemCallbacks)
{
g_filesystemCallbacks = filesystemCallbacks;
}
int openContentUri(const fs::path &uri)
{
if (g_filesystemCallbacks)
return g_filesystemCallbacks->openContentUri(uri);
return -1;
}
std::vector<fs::path> 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
+29
View File
@@ -0,0 +1,29 @@
#pragma once
namespace FilesystemAndroid
{
class FilesystemCallbacks
{
public:
virtual int openContentUri(const std::filesystem::path &uri) = 0;
virtual std::vector<std::filesystem::path> 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> &filesystemCallbacks);
int openContentUri(const std::filesystem::path &uri);
std::vector<std::filesystem::path> 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
+18 -82
View File
@@ -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<std::vector<uint8>> 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<std::vector<uint8>> 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<uint8>& data)
void FileStreamWin32::extract(std::vector<uint8>& data)
{
DWORD fileSize = GetFileSize(m_hFile, nullptr);
data.resize(fileSize);
@@ -93,29 +29,29 @@ void FileStream::extract(std::vector<uint8>& 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;
+22 -30
View File
@@ -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<std::vector<uint8>> 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<uint8>& data);
uint64 GetSize() override;
bool SetEndOfFile() override;
void extract(std::vector<uint8>& 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;
+101
View File
@@ -7,6 +7,7 @@
#include <AndroidGui/AndroidGuiWrapper.h>
#include <Cafe/HW/Latte/Renderer/Renderer.h>
#include <Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h>
#include <Common/unix/FilesystemAndroid.h>
#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<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) {
gui_addGamePath(JNIUtils::JStringToString(env, uri));
}
@@ -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();
}
@@ -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<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)) {
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;
}
}
}

Some files were not shown because too many files have changed in this diff Show More