Merge branch 'android' into android-2

This commit is contained in:
SSimco 2023-12-23 18:00:09 +02:00
commit fda05a5a00
300 changed files with 16097 additions and 1732 deletions

2
.gitmodules vendored
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

View File

@ -17,13 +17,20 @@ if (EXPERIMENTAL_VERSION)
endif()
if (ENABLE_VCPKG)
if(UNIX AND NOT APPLE)
if(UNIX AND NOT APPLE AND NOT VCPKG_TARGET_ANDROID)
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports_linux")
else()
set(VCPKG_OVERLAY_PORTS "${CMAKE_CURRENT_LIST_DIR}/dependencies/vcpkg_overlay_ports")
endif()
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/vcpkg/scripts/buildsystems/vcpkg.cmake"
CACHE STRING "Vcpkg toolchain file")
if(VCPKG_TARGET_ANDROID)
set(ENV{ANDROID_NDK_HOME} ${ANDROID_NDK})
set(ENV{VCPKG_ROOT} "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/vcpkg")
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/vcpkg_android.cmake")
else()
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/dependencies/vcpkg/scripts/buildsystems/vcpkg.cmake"
CACHE STRING "Vcpkg toolchain file")
endif()
# Set this so that all the various find_package() calls don't need an explicit
# CONFIG option
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
@ -50,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)
@ -122,11 +131,17 @@ option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON)
set(THREADS_PREFER_PTHREAD_FLAG true)
find_package(Threads REQUIRED)
find_package(SDL2 REQUIRED)
if(ENABLE_SDL)
find_package(SDL2 REQUIRED)
add_compile_definitions("HAS_SDL=1")
endif()
find_package(CURL REQUIRED)
find_package(pugixml REQUIRED)
find_package(RapidJSON 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)
@ -141,7 +156,7 @@ if (NOT TARGET glslang::SPIRV AND TARGET SPIRV)
add_library(glslang::SPIRV ALIAS SPIRV)
endif()
if (UNIX AND NOT APPLE)
if (UNIX AND NOT APPLE AND NOT ANDROID)
find_package(X11 REQUIRED)
if (ENABLE_WAYLAND)
find_package(Wayland REQUIRED Client)
@ -180,7 +195,7 @@ if (ENABLE_HIDAPI)
add_compile_definitions(HAS_HIDAPI)
endif ()
if(UNIX AND NOT APPLE)
if(UNIX AND NOT APPLE AND NOT ANDROID)
if(ENABLE_FERAL_GAMEMODE)
add_compile_definitions(ENABLE_FERAL_GAMEMODE)
add_subdirectory(dependencies/gamemode EXCLUDE_FROM_ALL)

99
cmake/vcpkg_android.cmake Normal file
View File

@ -0,0 +1,99 @@
#
# vcpkg_android.cmake
#
# Helper script when using vcpkg with cmake. It should be triggered via the variable VCPKG_TARGET_ANDROID
#
# For example:
# if (VCPKG_TARGET_ANDROID)
# include("cmake/vcpkg_android.cmake")
# endif()
#
# This script will:
# 1 & 2. check the presence of needed env variables: ANDROID_NDK_HOME and VCPKG_ROOT
# 3. set VCPKG_TARGET_TRIPLET according to ANDROID_ABI
# 4. Combine vcpkg and Android toolchains by setting CMAKE_TOOLCHAIN_FILE
# and VCPKG_CHAINLOAD_TOOLCHAIN_FILE
# Note: VCPKG_TARGET_ANDROID is not an official Vcpkg variable.
# it is introduced for the need of this script
if (VCPKG_TARGET_ANDROID)
#
# 1. Check the presence of environment variable ANDROID_NDK_HOME
#
if (NOT DEFINED ENV{ANDROID_NDK_HOME})
message(FATAL_ERROR "
Please set an environment variable ANDROID_NDK_HOME
For example:
export ANDROID_NDK_HOME=/home/your-account/Android/Sdk/ndk-bundle
Or:
export ANDROID_NDK_HOME=/home/your-account/Android/android-ndk-r21b
")
endif()
#
# 2. Check the presence of environment variable VCPKG_ROOT
#
if (NOT DEFINED ENV{VCPKG_ROOT})
message(FATAL_ERROR "
Please set an environment variable VCPKG_ROOT
For example:
export VCPKG_ROOT=/path/to/vcpkg
")
endif()
#
# 3. Set VCPKG_TARGET_TRIPLET according to ANDROID_ABI
#
# There are four different Android ABI, each of which maps to
# a vcpkg triplet. The following table outlines the mapping from vcpkg architectures to android architectures
#
# |VCPKG_TARGET_TRIPLET | ANDROID_ABI |
# |---------------------------|----------------------|
# |arm64-android | arm64-v8a |
# |arm-android | armeabi-v7a |
# |x64-android | x86_64 |
# |x86-android | x86 |
#
# The variable must be stored in the cache in order to successfully the two toolchains.
#
if (ANDROID_ABI MATCHES "arm64-v8a")
set(VCPKG_TARGET_TRIPLET "arm64-android" CACHE STRING "" FORCE)
elseif(ANDROID_ABI MATCHES "armeabi-v7a")
set(VCPKG_TARGET_TRIPLET "arm-android" CACHE STRING "" FORCE)
elseif(ANDROID_ABI MATCHES "x86_64")
set(VCPKG_TARGET_TRIPLET "x64-android" CACHE STRING "" FORCE)
elseif(ANDROID_ABI MATCHES "x86")
set(VCPKG_TARGET_TRIPLET "x86-android" CACHE STRING "" FORCE)
else()
message(FATAL_ERROR "
Please specify ANDROID_ABI
For example
cmake ... -DANDROID_ABI=armeabi-v7a
Possible ABIs are: arm64-v8a, armeabi-v7a, x64-android, x86-android
")
endif()
message("vcpkg_android.cmake: VCPKG_TARGET_TRIPLET was set to ${VCPKG_TARGET_TRIPLET}")
#
# 4. Combine vcpkg and Android toolchains
#
# vcpkg and android both provide dedicated toolchains:
#
# vcpkg_toolchain_file=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
# android_toolchain_file=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake
#
# When using vcpkg, the vcpkg toolchain shall be specified first.
# However, vcpkg provides a way to preload and additional toolchain,
# with the VCPKG_CHAINLOAD_TOOLCHAIN_FILE option.
set(VCPKG_CHAINLOAD_TOOLCHAIN_FILE $ENV{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake)
set(CMAKE_TOOLCHAIN_FILE $ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
message("vcpkg_android.cmake: CMAKE_TOOLCHAIN_FILE was set to ${CMAKE_TOOLCHAIN_FILE}")
message("vcpkg_android.cmake: VCPKG_CHAINLOAD_TOOLCHAIN_FILE was set to ${VCPKG_CHAINLOAD_TOOLCHAIN_FILE}")
endif(VCPKG_TARGET_ANDROID)

@ -1 +1 @@
Subproject commit d2c717730092c7bf8cbb033b12fd4001b7c4d932
Subproject commit c50b5d222bb599be9287664c835c2a820aa5342a

View File

@ -17,9 +17,6 @@
*****************************************************************************
* Originally developed and contributed by Ittiam Systems Pvt. Ltd, Bangalore
*/
#ifdef __ANDROID__
#include <log/log.h>
#endif
#include "ih264_typedefs.h"
#include "ih264_macros.h"
#include "ih264_platform_macros.h"
@ -902,10 +899,6 @@ WORD32 ih264d_read_mmco_commands(struct _DecStruct * ps_dec)
{
if (j >= MAX_REF_BUFS)
{
#ifdef __ANDROID__
ALOGE("b/25818142");
android_errorWriteLog(0x534e4554, "25818142");
#endif
ps_dpb_cmds->u1_num_of_commands = 0;
return -1;
}

View File

@ -18,6 +18,8 @@ elseif(UNIX)
VK_USE_PLATFORM_MACOS_MVK
VK_USE_PLATFORM_METAL_EXT
)
elseif(ANDROID)
add_compile_definitions(VK_USE_PLATFORM_ANDROID_KHR)
else()
add_compile_definitions(
VK_USE_PLATFORM_XLIB_KHR # legacy. Do we need to support XLIB surfaces?
@ -40,7 +42,11 @@ add_compile_definitions(VK_NO_PROTOTYPES)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
add_subdirectory(Common)
add_subdirectory(gui)
if(ANDROID)
add_subdirectory(android/app/src/main/cpp)
else()
add_subdirectory(gui)
endif()
add_subdirectory(Cafe)
add_subdirectory(Cemu)
add_subdirectory(config)
@ -51,22 +57,28 @@ add_subdirectory(imgui)
add_subdirectory(resource)
add_subdirectory(asm)
add_executable(CemuBin
main.cpp
mainLLE.cpp
)
if(ANDROID)
add_library(CemuBin STATIC
main.cpp
mainLLE.cpp
)
else()
add_executable(CemuBin
main.cpp
mainLLE.cpp
)
if(WIN32)
target_sources(CemuBin PRIVATE
resource/cemu.rc
if(WIN32)
target_sources(CemuBin PRIVATE
resource/cemu.rc
../dist/windows/cemu.manifest
)
endif()
set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_property(TARGET CemuBin PROPERTY WIN32_EXECUTABLE $<NOT:$<CONFIG:Debug>>)
set(OUTPUT_NAME "Cemu_$<LOWER_CASE:$<CONFIG>>")
endif()
set_property(TARGET CemuBin PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
set_property(TARGET CemuBin PROPERTY WIN32_EXECUTABLE $<NOT:$<CONFIG:Debug>>)
set(OUTPUT_NAME "Cemu_$<LOWER_CASE:$<CONFIG>>")
if (MACOS_BUNDLE)
set_property(TARGET CemuBin PROPERTY MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/resource/MacOSXBundleInfo.plist.in")
@ -115,13 +127,22 @@ target_link_libraries(CemuBin PRIVATE
CemuCommon
CemuComponents
CemuConfig
CemuGui
CemuInput
CemuUtil
OpenGL::GL
SDL2::SDL2
)
if(NOT ANDROID)
target_link_libraries(CemuBin PRIVATE CemuGui)
endif()
if(ENABLE_OPENGL)
target_link_libraries(CemuBin PRIVATE OpenGL::GL)
endif()
if(ENABLE_SDL)
target_link_libraries(CemuBin PRIVATE SDL2::SDL2)
endif()
if(UNIX AND NOT APPLE)
# due to nasm output some linkers will make stack executable
# cemu does not require this so we explicity disable it

View File

@ -497,6 +497,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 "../")
@ -507,7 +514,6 @@ target_link_libraries(CemuCafe PRIVATE
CemuCommon
CemuComponents
CemuConfig
CemuGui
CemuInput
CemuResource
CemuUtil
@ -543,10 +549,6 @@ if (ENABLE_NSYSHID_LIBUSB)
endif ()
endif ()
if (ENABLE_WXWIDGETS)
target_link_libraries(CemuCafe PRIVATE wx::base wx::core)
endif()
if(WIN32)
target_link_libraries(CemuCafe PRIVATE iphlpapi)
endif()

View File

@ -1,5 +1,4 @@
#include "Cafe/OS/common/OSCommon.h"
#include "gui/wxgui.h"
#include "Cafe/OS/libs/gx2/GX2.h"
#include "Cafe/GameProfile/GameProfile.h"
#include "Cafe/HW/Espresso/Interpreter/PPCInterpreterInternal.h"
@ -61,9 +60,6 @@
// HW interfaces
#include "Cafe/HW/SI/si.h"
// dependency to be removed
#include "gui/guiWrapper.h"
#include <time.h>
#if BOOST_OS_LINUX
@ -168,7 +164,7 @@ void LoadMainExecutable()
applicationRPX = RPLLoader_LoadFromMemory(rpxData, rpxSize, (char*)_pathToExecutable.c_str());
if (!applicationRPX)
{
wxMessageBox(_("Failed to run this title because the executable is damaged"));
cemuLog_log(LogType::Force, "Failed to run this title because the executable is damaged");
cemuLog_createLogFile(false);
cemuLog_waitForFlush();
exit(0);
@ -353,7 +349,9 @@ uint32 LoadSharedData()
void cemu_initForGame()
{
gui_updateWindowTitles(false, true, 0.0);
auto cafeSystemCallbacks = CafeSystem::getCafeSystemCallbacks();
if (cafeSystemCallbacks)
cafeSystemCallbacks->updateWindowTitles(false, true, 0.0);
// input manager apply game profile
InputManager::instance().apply_game_profile();
// log info for launched title
@ -423,6 +421,20 @@ void cemu_initForGame()
namespace CafeSystem
{
CafeSystemCallbacks* sCafeSystemCallbacks = nullptr;
void registerCafeSystemCallbacks(CafeSystemCallbacks* cafeSystemCallbacks)
{
sCafeSystemCallbacks = cafeSystemCallbacks;
}
void unregisterCafeSystemCallbacks()
{
sCafeSystemCallbacks = nullptr;
}
CafeSystemCallbacks* getCafeSystemCallbacks()
{
return sCafeSystemCallbacks;
}
void InitVirtualMlcStorage();
void MlcStorageMountTitle(TitleInfo& titleInfo);
void MlcStorageUnmountAllTitles();
@ -798,10 +810,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));
@ -810,7 +822,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;
@ -847,7 +859,9 @@ namespace CafeSystem
PPCTimer_waitForInit();
// start system
sSystemRunning = true;
gui_notifyGameLoaded();
auto cafeSystemCallbacks = CafeSystem::getCafeSystemCallbacks();
if (cafeSystemCallbacks)
cafeSystemCallbacks->notifyGameLoaded();
std::thread t(_LaunchTitleThread);
t.detach();
}

View File

@ -20,6 +20,17 @@ namespace CafeSystem
//BAD_META_DATA, - the title list only stores titles with valid meta, so this error code is impossible
};
class CafeSystemCallbacks
{
public:
virtual void updateWindowTitles(bool isIdle, bool isLoading, double fps) = 0;
virtual void notifyGameLoaded() = 0;
};
void registerCafeSystemCallbacks(CafeSystemCallbacks* cafeSystemCallbacks);
void unregisterCafeSystemCallbacks();
CafeSystemCallbacks* getCafeSystemCallbacks();
void Initialize();
void SetImplementation(SystemImplementation* impl);
void Shutdown();

View File

@ -1,4 +1,3 @@
#include <wx/msgdlg.h>
#include <mutex>
#include <gui/helpers/wxHelpers.h>
@ -75,7 +74,7 @@ void KeyCache_Prepare()
}
else
{
wxMessageBox(_("Unable to create file keys.txt\nThis can happen if Cemu does not have write permission to its own directory, the disk is full or if anti-virus software is blocking Cemu."), _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
cemuLog_log(LogType::Force, "Unable to create file keys.txt\nThis can happen if Cemu does not have write permission to it's own directory, the disk is full or if anti-virus software is blocking Cemu.");
}
mtxKeyCache.unlock();
return;
@ -108,8 +107,7 @@ void KeyCache_Prepare()
continue;
if( strishex(line) == false )
{
auto errorMsg = formatWxString(_("Error in keys.txt at line {}"), lineNumber);
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
cemuLog_log(LogType::Force, "rror in keys.txt in line {}", lineNumber);
continue;
}
if(line.size() == 32 )

View File

@ -210,3 +210,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);
}

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;
}

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;
};

View File

@ -5,9 +5,7 @@
#include "Cafe/OS/RPL/rpl_structs.h"
#include "boost/algorithm/string.hpp"
#include "gui/wxgui.h" // for wxMessageBox
#include "gui/helpers/wxHelpers.h"
// error handler
void PatchErrorHandler::printError(class PatchGroup* patchGroup, sint32 lineNumber, std::string_view errorMsg)
{
@ -63,8 +61,7 @@ void PatchErrorHandler::showStageErrorMessageBox()
errorMsg.append("\n");
}
}
wxMessageBox(errorMsg, _("Graphic pack error"));
cemuLog_log(LogType::Force, "Graphic pack error: {}", errorMsg);
}
// loads Cemu-style patches (patch_<anything>.asm)

View File

@ -1,12 +1,9 @@
#include "gui/guiWrapper.h"
#include "Debugger.h"
#include "Cafe/OS/RPL/rpl_structs.h"
#include "Cemu/PPCAssembler/ppcAssembler.h"
#include "Cafe/HW/Espresso/Recompiler/PPCRecompiler.h"
#include "Cemu/ExpressionParser/ExpressionParser.h"
#include "gui/debugger/DebuggerWindow2.h"
#include "Cafe/OS/libs/coreinit/coreinit.h"
#if BOOST_OS_WINDOWS
@ -15,6 +12,21 @@
debuggerState_t debuggerState{ };
DebuggerCallbacks* sDebuggerCallbacks = nullptr;
void debugger_registerDebuggerCallbacks(DebuggerCallbacks* debuggerCallbacks)
{
sDebuggerCallbacks = debuggerCallbacks;
}
void debugger_unregisterDebuggerCallbacks()
{
sDebuggerCallbacks = nullptr;
}
DebuggerCallbacks* debugger_getDebuggerCallbacks()
{
return sDebuggerCallbacks;
}
DebuggerBreakpoint* debugger_getFirstBP(uint32 address)
{
for (auto& it : debuggerState.breakpoints)
@ -326,7 +338,8 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b
{
bp->enabled = state;
debugger_updateExecutionBreakpoint(address);
debuggerWindow_updateViewThreadsafe2();
if (sDebuggerCallbacks)
sDebuggerCallbacks->updateViewThreadsafe();
}
else if (bpItr->isMemBP())
{
@ -348,7 +361,8 @@ void debugger_toggleBreakpoint(uint32 address, bool state, DebuggerBreakpoint* b
debugger_updateMemoryBreakpoint(bpItr);
else
debugger_updateMemoryBreakpoint(nullptr);
debuggerWindow_updateViewThreadsafe2();
if (sDebuggerCallbacks)
sDebuggerCallbacks->updateViewThreadsafe();
}
return;
}
@ -456,8 +470,8 @@ void debugger_stepInto(PPCInterpreter_t* hCPU, bool updateDebuggerWindow = true)
PPCInterpreterSlim_executeInstruction(hCPU);
debugger_updateExecutionBreakpoint(initialIP);
debuggerState.debugSession.instructionPointer = hCPU->instructionPointer;
if(updateDebuggerWindow)
debuggerWindow_moveIP();
if(updateDebuggerWindow && sDebuggerCallbacks)
sDebuggerCallbacks->moveIP();
ppcRecompilerEnabled = isRecEnabled;
}
@ -476,7 +490,8 @@ bool debugger_stepOver(PPCInterpreter_t* hCPU)
// nothing to skip, use step-into
debugger_stepInto(hCPU);
debugger_updateExecutionBreakpoint(initialIP);
debuggerWindow_moveIP();
if (sDebuggerCallbacks)
sDebuggerCallbacks->moveIP();
ppcRecompilerEnabled = isRecEnabled;
return false;
}
@ -484,7 +499,8 @@ bool debugger_stepOver(PPCInterpreter_t* hCPU)
debugger_createCodeBreakpoint(initialIP + 4, DEBUGGER_BP_T_ONE_SHOT);
// step over current instruction (to avoid breakpoint)
debugger_stepInto(hCPU);
debuggerWindow_moveIP();
if (sDebuggerCallbacks)
sDebuggerCallbacks->moveIP();
// restore breakpoints
debugger_updateExecutionBreakpoint(initialIP);
// run
@ -543,8 +559,11 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
DebuggerBreakpoint* singleshotBP = debugger_getFirstBP(debuggerState.debugSession.instructionPointer, DEBUGGER_BP_T_ONE_SHOT);
if (singleshotBP)
debugger_deleteBreakpoint(singleshotBP);
debuggerWindow_notifyDebugBreakpointHit2();
debuggerWindow_updateViewThreadsafe2();
if (sDebuggerCallbacks)
{
sDebuggerCallbacks->notifyDebugBreakpointHit();
sDebuggerCallbacks->updateViewThreadsafe();
}
// reset step control
debuggerState.debugSession.stepInto = false;
debuggerState.debugSession.stepOver = false;
@ -561,14 +580,16 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
break; // if true is returned, continue with execution
}
debugger_createPPCStateSnapshot(hCPU);
debuggerWindow_updateViewThreadsafe2();
if (sDebuggerCallbacks)
sDebuggerCallbacks->updateViewThreadsafe();
debuggerState.debugSession.stepOver = false;
}
if (debuggerState.debugSession.stepInto)
{
debugger_stepInto(hCPU);
debugger_createPPCStateSnapshot(hCPU);
debuggerWindow_updateViewThreadsafe2();
if (sDebuggerCallbacks)
sDebuggerCallbacks->updateViewThreadsafe();
debuggerState.debugSession.stepInto = false;
continue;
}
@ -585,8 +606,11 @@ void debugger_enterTW(PPCInterpreter_t* hCPU)
debuggerState.debugSession.isTrapped = false;
debuggerState.debugSession.hCPU = nullptr;
debuggerWindow_updateViewThreadsafe2();
debuggerWindow_notifyRun();
if (sDebuggerCallbacks)
{
sDebuggerCallbacks->updateViewThreadsafe();
sDebuggerCallbacks->notifyRun();
}
}
void debugger_shouldBreak(PPCInterpreter_t* hCPU)

View File

@ -98,6 +98,21 @@ typedef struct
extern debuggerState_t debuggerState;
// new API
class DebuggerCallbacks
{
public:
virtual void updateViewThreadsafe() = 0;
virtual void notifyDebugBreakpointHit() = 0;
virtual void notifyRun() = 0;
virtual void moveIP() = 0;
virtual void notifyModuleLoaded(void* module) = 0;
virtual void notifyModuleUnloaded(void* module) = 0;
};
void debugger_registerDebuggerCallbacks(DebuggerCallbacks* debuggerCallbacks);
void debugger_unregisterDebuggerCallbacks();
DebuggerCallbacks* debugger_getDebuggerCallbacks();
DebuggerBreakpoint* debugger_getFirstBP(uint32 address);
void debugger_createCodeBreakpoint(uint32 address, uint8 bpType);
void debugger_createExecuteBreakpoint(uint32 address);

View File

@ -1,6 +1,5 @@
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "gui/guiWrapper.h"
#include "config/CemuConfig.h"
@ -14,6 +13,8 @@
#include "input/InputManager.h"
#include "util/SystemInfo/SystemInfo.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include <cinttypes>
struct OverlayStats
@ -513,17 +514,17 @@ void LatteOverlay_render(bool pad_view)
return;
sint32 w = 0, h = 0;
if (pad_view && gui_isPadWindowOpen())
gui_getPadWindowPhysSize(w, h);
if (pad_view && GuiSystem::isPadWindowOpen())
GuiSystem::getPadWindowPhysSize(w, h);
else
gui_getWindowPhysSize(w, h);
GuiSystem::getWindowPhysSize(w, h);
if (w == 0 || h == 0)
return;
const Vector2f window_size{ (float)w,(float)h };
float fontDPIScale = !pad_view ? gui_getWindowDPIScale() : gui_getPadDPIScale();
float fontDPIScale = !pad_view ? GuiSystem::getWindowDPIScale() : GuiSystem::getPadDPIScale();
float overlayFontSize = 14.0f * (float)config.overlay.text_scale / 100.0f * fontDPIScale;

View File

@ -1,6 +1,6 @@
#include "Cafe/HW/Latte/Core/LattePerformanceMonitor.h"
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
#include "gui/guiWrapper.h"
#include "Cafe/CafeSystem.h"
performanceMonitor_t performanceMonitor{};
@ -104,15 +104,18 @@ void LattePerformanceMonitor_frameEnd()
// next update in 1 second
performanceMonitor.cycle[performanceMonitor.cycleIndex].lastUpdate = GetTickCount();
auto cafeSystemCallbacks = CafeSystem::getCafeSystemCallbacks();
if (isFirstUpdate)
{
LatteOverlay_updateStats(0.0, 0, 0);
gui_updateWindowTitles(false, false, 0.0);
if (cafeSystemCallbacks)
cafeSystemCallbacks->updateWindowTitles(false, false, 0.0);
}
else
{
LatteOverlay_updateStats(fps, drawCallCounter / elapsedFrames, fastDrawCallCounter / elapsedFrames);
gui_updateWindowTitles(false, false, fps);
if (cafeSystemCallbacks)
cafeSystemCallbacks->updateWindowTitles(false, false, fps);
}
}
}

View File

@ -12,7 +12,7 @@
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "config/ActiveSettings.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#include "gui/guiWrapper.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include "Cafe/OS/libs/erreula/erreula.h"
#include "input/InputManager.h"
#include "Cafe/OS/libs/swkbd/swkbd.h"
@ -871,10 +871,10 @@ sint32 _currentOutputImageHeight = 0;
void LatteRenderTarget_getScreenImageArea(sint32* x, sint32* y, sint32* width, sint32* height, sint32* fullWidth, sint32* fullHeight, bool padView)
{
int w, h;
if(padView && gui_isPadWindowOpen())
gui_getPadWindowPhysSize(w, h);
if(padView && GuiSystem::isPadWindowOpen())
GuiSystem::getPadWindowPhysSize(w, h);
else
gui_getWindowPhysSize(w, h);
GuiSystem::getWindowPhysSize(w, h);
sint32 scaledOutputX;
sint32 scaledOutputY;
@ -1039,8 +1039,8 @@ void LatteRenderTarget_itHLECopyColorBufferToScanBuffer(MPTR colorBufferPtr, uin
return;
}
const bool tabPressed = gui_isKeyDown(PlatformKeyCodes::TAB);
const bool ctrlPressed = gui_isKeyDown(PlatformKeyCodes::LCONTROL);
const bool tabPressed = GuiSystem::isKeyDown(GuiSystem::PlatformKeyCodes::TAB);
const bool ctrlPressed = GuiSystem::isKeyDown(GuiSystem::PlatformKeyCodes::LCONTROL);
bool showDRC = swkbd_hasKeyboardInputHook() == false && tabPressed;
bool& alwaysDisplayDRC = LatteGPUState.alwaysDisplayDRC;

View File

@ -4,9 +4,9 @@
#include "Cafe/HW/Latte/Core/LatteShader.h"
#include "Cafe/HW/Latte/LegacyShaderDecompiler/LatteDecompiler.h"
#include "Cafe/HW/Latte/Core/FetchShader.h"
#include "Cemu/FileCache/FileCache.h"
#include "Cafe/GameProfile/GameProfile.h"
#include "gui/guiWrapper.h"
#include "Cemu/FileCache/FileCache.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "Cafe/HW/Latte/Renderer/OpenGL/RendererShaderGL.h"
@ -24,8 +24,6 @@
#include "Cafe/HW/Latte/Common/ShaderSerializer.h"
#include "util/helpers/Serializer.h"
#include <wx/msgdlg.h>
#if BOOST_OS_WINDOWS
#include <psapi.h>
#endif
@ -66,8 +64,6 @@ void LatteShaderCache_LoadVulkanPipelineCache(uint64 cacheTitleId);
bool LatteShaderCache_updatePipelineLoadingProgress();
void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateFunc, bool isPipelines);
void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path pathGenericPre1_25_0, fs::path pathGenericPre1_16_0);
struct
{
struct
@ -245,10 +241,7 @@ void LatteShaderCache_Load()
RendererShaderGL::ShaderCacheLoading_begin(cacheTitleId);
// get cache file name
const auto pathGeneric = ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}_shaders.bin", cacheTitleId);
const auto pathGenericPre1_25_0 = ActiveSettings::GetCachePath("shaderCache/transferable/{:016x}.bin", cacheTitleId); // before 1.25.0
const auto pathGenericPre1_16_0 = ActiveSettings::GetCachePath("shaderCache/transferable/{:08x}.bin", CafeSystem::GetRPXHashBase()); // before 1.16.0
LatteShaderCache_handleDeprecatedCacheFiles(pathGeneric, pathGenericPre1_25_0, pathGenericPre1_16_0);
// calculate extraVersion for transferable and precompiled shader cache
uint32 transferableExtraVersion = SHADER_CACHE_GENERIC_EXTRA_VERSION;
s_shaderCacheGeneric = FileCache::Open(pathGeneric, false, transferableExtraVersion); // legacy extra version (1.25.0 - 1.25.1b)
@ -345,7 +338,7 @@ void LatteShaderCache_Load()
if (g_renderer->GetType() == RendererAPI::Vulkan)
LatteShaderCache_LoadVulkanPipelineCache(cacheTitleId);
#if !__ANDROID__
g_renderer->BeginFrame(true);
if (g_renderer->ImguiBegin(true))
{
@ -358,7 +351,7 @@ void LatteShaderCache_Load()
LatteShaderCache_drawBackgroundImage(g_shaderCacheLoaderState.textureDRCId, 854, 480);
g_renderer->ImguiEnd();
}
#endif // __ANDROID__
g_renderer->SwapBuffers(true, true);
if (g_shaderCacheLoaderState.textureTVId)
@ -388,7 +381,7 @@ void LatteShaderCache_ShowProgress(const std::function <bool(void)>& loadUpdateF
continue;
int w, h;
gui_getWindowPhysSize(w, h);
GuiSystem::getWindowPhysSize(w, h);
const Vector2f window_size{ (float)w,(float)h };
ImGui_GetFont(window_size.y / 32.0f); // = 24 by default
@ -779,30 +772,3 @@ void LatteShaderCache_Close()
if (g_renderer->GetType() == RendererAPI::Vulkan)
VulkanPipelineStableCache::GetInstance().Close();
}
#include <wx/msgdlg.h>
void LatteShaderCache_handleDeprecatedCacheFiles(fs::path pathGeneric, fs::path pathGenericPre1_25_0, fs::path pathGenericPre1_16_0)
{
std::error_code ec;
bool hasOldCacheFiles = fs::exists(pathGenericPre1_25_0, ec) || fs::exists(pathGenericPre1_16_0, ec);
bool hasNewCacheFiles = fs::exists(pathGeneric, ec);
if (hasOldCacheFiles && !hasNewCacheFiles)
{
// ask user if they want to delete or keep the old cache file
auto infoMsg = _("Cemu detected that the shader cache for this game is outdated.\nOnly shader caches generated with Cemu 1.25.0 or above are supported.\n\nWe recommend deleting the outdated cache file as it will no longer be used by Cemu.");
wxMessageDialog dialog(nullptr, infoMsg, _("Outdated shader cache"),
wxYES_NO | wxCENTRE | wxICON_EXCLAMATION);
dialog.SetYesNoLabels(_("Delete outdated cache file [recommended]"), _("Keep outdated cache file"));
const auto result = dialog.ShowModal();
if (result == wxID_YES)
{
fs::remove(pathGenericPre1_16_0, ec);
fs::remove(pathGenericPre1_25_0, ec);
}
}
}

View File

@ -6,7 +6,7 @@
#include "Cafe/HW/Latte/Core/LatteAsyncCommands.h"
#include "Cafe/GameProfile/GameProfile.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "gui/guiWrapper.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include "Cafe/HW/Latte/Core/LatteBufferCache.h"
@ -115,7 +115,7 @@ int Latte_ThreadEntry()
{
SetThreadName("LatteThread");
sint32 w,h;
gui_getWindowPhysSize(w,h);
GuiSystem::getWindowPhysSize(w,h);
// renderer
g_renderer->Initialize();
@ -172,8 +172,7 @@ int Latte_ThreadEntry()
g_renderer->DrawEmptyFrame(true);
g_renderer->DrawEmptyFrame(false);
gui_hasScreenshotRequest(); // keep the screenshot request queue empty
g_renderer->CancelScreenshotRequest();
std::this_thread::sleep_for(std::chrono::milliseconds(1000/60));
}

View File

@ -2,7 +2,9 @@
#include "Cafe/HW/Latte/LatteAddrLib/LatteAddrLib.h"
#include "Cafe/OS/libs/gx2/GX2_Surface.h"
#include <bit>
#if __ANDROID__
#include <boost/core/bit.hpp>
#endif
/*
Info:
@ -73,7 +75,11 @@ namespace LatteAddrLib
uint32 NextPow2(uint32 dim)
{
#if __ANDROID__
return boost::core::bit_ceil(dim);
#else
return std::bit_ceil<uint32>(dim);
#endif
}
uint32 GetBitsPerPixel(E_HWSURFFMT format, uint32* pElemMode, uint32* pExpandX, uint32* pExpandY)

View File

@ -0,0 +1,5 @@
#pragma once
bool GLCanvas_MakeCurrent(bool padView);
void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC);
bool GLCanvas_HasPadViewOpen();

View File

@ -1,5 +1,4 @@
#include "Cafe/HW/Latte/Renderer/OpenGL/OpenGLRenderer.h"
#include "gui/guiWrapper.h"
#include "Cafe/HW/Latte/Core/LatteRingBuffer.h"
#include "Cafe/HW/Latte/Core/LatteDraw.h"
@ -19,7 +18,9 @@
#include "Cafe/HW/Latte/ISA/RegDefines.h"
#include "Cafe/OS/libs/gx2/GX2.h"
#include "gui/canvas/OpenGLCanvas.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include "GLCanvas.h"
#define STRINGIFY2(X) #X
#define STRINGIFY(X) STRINGIFY2(X)
@ -123,7 +124,7 @@ bool OpenGLRenderer::ImguiBegin(bool mainWindow)
{
if (!mainWindow)
{
GLCanvas_MakeCurrent(true);
m_openGLCallbacks->GLCanvas_MakeCurrent(true);
m_isPadViewContext = true;
}
@ -150,7 +151,7 @@ void OpenGLRenderer::ImguiEnd()
if (m_isPadViewContext)
{
GLCanvas_MakeCurrent(false);
m_openGLCallbacks->GLCanvas_MakeCurrent(false);
m_isPadViewContext = false;
}
@ -207,6 +208,11 @@ void LoadOpenGLImports()
#include "Common/GLInclude/glFunctions.h"
#undef GLFUNC
}
#elif __ANDROID__
void LoadOpenGLImports()
{
cemu_assert_unimplemented();
}
#elif BOOST_OS_LINUX
GL_IMPORT _GetOpenGLFunction(void* hLib, PFNGLXGETPROCADDRESSPROC func, const char* name)
{
@ -254,7 +260,7 @@ void OpenGLRenderer::Initialize()
auto lock = cemuLog_acquire();
cemuLog_log(LogType::Force, "------- Init OpenGL graphics backend -------");
GLCanvas_MakeCurrent(false);
m_openGLCallbacks->GLCanvas_MakeCurrent(false);
LoadOpenGLImports();
GetVendorInformation();
@ -342,7 +348,7 @@ void OpenGLRenderer::Initialize()
bool OpenGLRenderer::IsPadWindowActive()
{
return GLCanvas_HasPadViewOpen();
return m_openGLCallbacks->GLCanvas_HasPadViewOpen();
}
void OpenGLRenderer::Flush(bool waitIdle)
@ -357,6 +363,15 @@ void OpenGLRenderer::NotifyLatteCommandProcessorIdle()
glFlush();
}
void OpenGLRenderer::RegisterOpenGLCallbacks(OpenGLCallbacks* openGLCallbacks)
{
m_openGLCallbacks = openGLCallbacks;
}
void OpenGLRenderer::UnregisterOpenGLCallbacks()
{
m_openGLCallbacks = nullptr;
}
void OpenGLRenderer::GetVendorInformation()
{
// example vendor strings:
@ -430,7 +445,7 @@ void OpenGLRenderer::EnableDebugMode()
void OpenGLRenderer::SwapBuffers(bool swapTV, bool swapDRC)
{
GLCanvas_SwapBuffers(swapTV, swapDRC);
m_openGLCallbacks->GLCanvas_SwapBuffers(swapTV, swapDRC);
if (swapTV)
cleanupAfterFrame();
@ -441,7 +456,7 @@ bool OpenGLRenderer::BeginFrame(bool mainWindow)
if (!mainWindow && !IsPadWindowActive())
return false;
GLCanvas_MakeCurrent(!mainWindow);
m_openGLCallbacks->GLCanvas_MakeCurrent(!mainWindow);
ClearColorbuffer(!mainWindow);
return true;
@ -464,7 +479,7 @@ void OpenGLRenderer::ClearColorbuffer(bool padView)
void OpenGLRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool padView)
{
const bool hasScreenshotRequest = gui_hasScreenshotRequest();
const bool hasScreenshotRequest = std::exchange(m_screenshot_requested, false);
if(!hasScreenshotRequest && m_screenshot_state == ScreenshotState::None)
return;
@ -535,7 +550,7 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
return;
catchOpenGLError();
GLCanvas_MakeCurrent(padView);
m_openGLCallbacks->GLCanvas_MakeCurrent(padView);
renderstate_resetColorControl();
renderstate_resetDepthControl();
@ -548,9 +563,9 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
{
int windowWidth, windowHeight;
if (padView)
gui_getPadWindowPhysSize(windowWidth, windowHeight);
GuiSystem::getPadWindowPhysSize(windowWidth, windowHeight);
else
gui_getWindowPhysSize(windowWidth, windowHeight);
GuiSystem::getWindowPhysSize(windowWidth, windowHeight);
g_renderer->renderTarget_setViewport(0, 0, windowWidth, windowHeight, 0.0f, 1.0f);
g_renderer->ClearColorbuffer(padView);
}
@ -593,7 +608,7 @@ void OpenGLRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
// switch back to TV context
if (padView)
GLCanvas_MakeCurrent(false);
m_openGLCallbacks->GLCanvas_MakeCurrent(false);
}
void OpenGLRenderer::renderTarget_setViewport(float x, float y, float width, float height, float nearZ, float farZ, bool halfZ /*= false*/)

View File

@ -154,6 +154,18 @@ public:
void occlusionQuery_updateState() override {};
private:
class OpenGLCallbacks
{
public:
virtual bool GLCanvas_HasPadViewOpen() = 0;
virtual bool GLCanvas_MakeCurrent(bool padView) = 0;
virtual void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC) = 0;
};
void RegisterOpenGLCallbacks(OpenGLCallbacks* openGLCallbacks);
void UnregisterOpenGLCallbacks();
void GetVendorInformation() override;
void texture_setActiveTextureUnit(sint32 index);
@ -161,6 +173,8 @@ private:
void texture_syncSliceSpecialBC4(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex);
void texture_syncSliceSpecialIntegerToBC3(LatteTexture* srcTexture, sint32 srcSliceIndex, sint32 srcMipIndex, LatteTexture* dstTexture, sint32 dstSliceIndex, sint32 dstMipIndex);
OpenGLCallbacks* m_openGLCallbacks = nullptr;
GLuint m_pipeline = 0;
bool m_isPadViewContext{};

View File

@ -1,5 +1,5 @@
#include "Cafe/HW/Latte/Renderer/Renderer.h"
#include "gui/guiWrapper.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include "config/CemuConfig.h"
#include "Cafe/HW/Latte/Core/LatteOverlay.h"
@ -10,10 +10,6 @@
#include "config/ActiveSettings.h"
#include <wx/image.h>
#include <wx/dataobj.h>
#include <wx/clipbrd.h>
#include <wx/log.h>
std::unique_ptr<Renderer> g_renderer;
@ -70,9 +66,9 @@ bool Renderer::ImguiBegin(bool mainWindow)
{
sint32 w = 0, h = 0;
if(mainWindow)
gui_getWindowPhysSize(w, h);
else if(gui_isPadWindowOpen())
gui_getPadWindowPhysSize(w, h);
GuiSystem::getWindowPhysSize(w, h);
else if(GuiSystem::isPadWindowOpen())
GuiSystem::getPadWindowPhysSize(w, h);
else
return false;
@ -114,107 +110,29 @@ uint8 Renderer::RGBComponentToSRGB(uint8 cli)
return (uint8)(cs * 255.0f);
}
static std::optional<fs::path> GenerateScreenshotFilename(bool isDRC)
void Renderer::RequestScreenshot(const std::function<std::optional<std::string>(const std::vector<uint8>&, int, int, bool)>& onSaveScreenshot)
{
fs::path screendir = ActiveSettings::GetUserDataPath("screenshots");
// build screenshot name with format Screenshot_YYYY-MM-DD_HH-MM-SS[_GamePad].png
// if the file already exists add a suffix counter (_2.png, _3.png etc)
std::time_t time_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm* tm = std::localtime(&time_t);
std::string screenshotFileName = fmt::format("Screenshot_{:04}-{:02}-{:02}_{:02}-{:02}-{:02}", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
if (isDRC)
screenshotFileName.append("_GamePad");
fs::path screenshotPath;
for(sint32 i=0; i<999; i++)
{
screenshotPath = screendir;
if (i == 0)
screenshotPath.append(fmt::format("{}.png", screenshotFileName));
else
screenshotPath.append(fmt::format("{}_{}.png", screenshotFileName, i + 1));
std::error_code ec;
bool exists = fs::exists(screenshotPath, ec);
if (!ec && !exists)
return screenshotPath;
}
return std::nullopt;
m_screenshot_requested = true;
m_on_save_screenshot = onSaveScreenshot;
}
std::mutex s_clipboardMutex;
static bool SaveScreenshotToClipboard(const wxImage &image)
void Renderer::CancelScreenshotRequest()
{
bool success = false;
s_clipboardMutex.lock();
if (wxTheClipboard->Open())
{
wxTheClipboard->SetData(new wxImageDataObject(image));
wxTheClipboard->Close();
success = true;
}
s_clipboardMutex.unlock();
return success;
}
static bool SaveScreenshotToFile(const wxImage &image, bool mainWindow)
{
auto path = GenerateScreenshotFilename(!mainWindow);
if (!path) return false;
std::error_code ec;
fs::create_directories(path->parent_path(), ec);
if (ec) return false;
// suspend wxWidgets logging for the lifetime this object, to prevent a message box if wxImage::SaveFile fails
wxLogNull _logNo;
return image.SaveFile(path->wstring());
}
static void ScreenshotThread(std::vector<uint8> data, bool save_screenshot, int width, int height, bool mainWindow)
{
#if BOOST_OS_WINDOWS
// on Windows wxWidgets uses OLE API for the clipboard
// to make this work we need to call OleInitialize() on the same thread
OleInitialize(nullptr);
#endif
wxImage image(width, height, data.data(), true);
if (mainWindow)
{
if(SaveScreenshotToClipboard(image))
{
if (!save_screenshot)
LatteOverlay_pushNotification("Screenshot saved to clipboard", 2500);
}
else
{
LatteOverlay_pushNotification("Failed to open clipboard", 2500);
}
}
if (save_screenshot)
{
if (SaveScreenshotToFile(image, mainWindow))
{
if (mainWindow)
LatteOverlay_pushNotification("Screenshot saved", 2500);
}
else
{
LatteOverlay_pushNotification("Failed to save screenshot to file", 2500);
}
}
m_screenshot_requested = false;
m_on_save_screenshot = nullptr;
}
void Renderer::SaveScreenshot(const std::vector<uint8>& rgb_data, int width, int height, bool mainWindow) const
{
const bool save_screenshot = GetConfig().save_screenshot;
std::thread(ScreenshotThread, rgb_data, save_screenshot, width, height, mainWindow).detach();
std::thread(
[=, this]()
{
if (m_on_save_screenshot)
{
auto notificationMessage = m_on_save_screenshot(rgb_data, width, height, mainWindow);
if (notificationMessage.has_value())
LatteOverlay_pushNotification(notificationMessage.value(), 2500);
}
})
.detach();
}

View File

@ -67,6 +67,9 @@ public:
virtual void DrawEmptyFrame(bool mainWindow) = 0;
virtual void SwapBuffers(bool swapTV, bool swapDRC) = 0;
void RequestScreenshot(const std::function<std::optional<std::string>(const std::vector<uint8>&, int, int, bool)>& onSaveScreenshot);
void CancelScreenshotRequest();
virtual void HandleScreenshotRequest(LatteTextureView* texView, bool padView){}
virtual void DrawBackbufferQuad(LatteTextureView* texView, RendererOutputShader* shader, bool useLinearTexFilter,
@ -166,6 +169,8 @@ protected:
Pad,
};
ScreenshotState m_screenshot_state = ScreenshotState::None;
bool m_screenshot_requested = false;
std::function<std::optional<std::string>(const std::vector<uint8>&, int, int, bool)> m_on_save_screenshot;
void SaveScreenshot(const std::vector<uint8>& rgb_data, int width, int height, bool mainWindow) const;

View File

@ -296,16 +296,16 @@ std::string RendererOutputShader::GetVulkanVertexSource(bool render_upside_down)
{
// vertex shader
std::ostringstream vertex_source;
vertex_source <<
R"(#version 450
vertex_source << R"(#version 450
layout(location = 0) out vec2 passUV;
out gl_PerVertex
{
vec4 gl_Position;
};
void main(){
};)";
#if __ANDROID__
vertex_source << "layout (push_constant) uniform PushConstants { mat2 preRotate; } pushConstants;\n";
#endif // __ANDROID__
vertex_source << R"(void main(){
vec2 vPos;
vec2 vUV;
int vID = gl_VertexIndex;
@ -334,12 +334,13 @@ void main(){
)";
}
vertex_source <<
R"( passUV = vUV;
gl_Position = vec4(vPos, 0.0, 1.0);
}
)";
return vertex_source.str();
vertex_source << "passUV = vUV;\n";
#if __ANDROID__
vertex_source << "gl_Position = vec4(pushConstants.preRotate * vPos, 0.0, 1.0);}";
#else
vertex_source << "gl_Position = vec4(vPos, 0.0, 1.0);}";
#endif // __ANDROID__
return vertex_source.str();
}
void RendererOutputShader::InitializeStatic()
{

View File

@ -12,6 +12,9 @@ void SwapchainInfoVk::Create(VkPhysicalDevice physicalDevice, VkDevice logicalDe
const auto details = QuerySwapchainSupport(surface, physicalDevice);
m_surfaceFormat = ChooseSurfaceFormat(details.formats);
m_actualExtent = ChooseSwapExtent(details.capabilities);
#if __ANDROID__
m_preTransform = details.capabilities.currentTransform;
#endif // __ANDROID__
// use at least two swapchain images. fewer than that causes problems on some drivers
uint32_t image_count = std::max(2u, details.capabilities.minImageCount);

View File

@ -76,7 +76,9 @@ struct SwapchainInfoVk
bool m_usesSRGB = false;
VSync m_vsyncState = VSync::Immediate;
bool hasDefinedSwapchainImage{}; // indicates if the swapchain image is in a defined state
#if __ANDROID__
VkSurfaceTransformFlagBitsKHR m_preTransform{};
#endif // __ANDROID__
VkPhysicalDevice m_physicalDevice{};
VkDevice m_logicalDevice{};
VkSurfaceKHR surface{};

View File

@ -1,5 +1,3 @@
#include "gui/MainWindow.h"
#if BOOST_OS_WINDOWS
#include <Windows.h>

View File

@ -131,12 +131,16 @@ VKFUNC_DEVICE(vkCmdBindPipeline);
// swapchain
#if BOOST_OS_LINUX
#if __ANDROID__
VKFUNC_INSTANCE(vkCreateAndroidSurfaceKHR);
#else
VKFUNC_INSTANCE(vkCreateXlibSurfaceKHR);
VKFUNC_INSTANCE(vkCreateXcbSurfaceKHR);
#ifdef HAS_WAYLAND
VKFUNC_INSTANCE(vkCreateWaylandSurfaceKHR);
#endif
#endif
#endif // HAS_WAYLAND
#endif // __ANDROID__
#endif // BOOST_OS_LINUX
#if BOOST_OS_WINDOWS
VKFUNC_INSTANCE(vkCreateWin32SurfaceKHR);

View File

@ -12,12 +12,12 @@
#include "Cafe/CafeSystem.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include "util/helpers/helpers.h"
#include "util/helpers/StringHelpers.h"
#include "config/ActiveSettings.h"
#include "config/CemuConfig.h"
#include "gui/guiWrapper.h"
#include "imgui/imgui_extension.h"
#include "imgui/imgui_impl_vulkan.h"
@ -28,8 +28,6 @@
#include <glslang/Include/Types.h>
#include <wx/msgdlg.h>
#ifndef VK_API_VERSION_MAJOR
#define VK_API_VERSION_MAJOR(version) (((uint32_t)(version) >> 22) & 0x7FU)
#define VK_API_VERSION_MINOR(version) (((uint32_t)(version) >> 12) & 0x3FFU)
@ -51,7 +49,9 @@ const std::vector<const char*> kOptionalDeviceExtensions =
const std::vector<const char*> kRequiredDeviceExtensions =
{
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
#if !__ANDROID__
VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME
#endif
}; // Intel doesnt support VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME
VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData)
@ -108,15 +108,19 @@ std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices()
requiredExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME);
#if BOOST_OS_WINDOWS
requiredExtensions.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#elif BOOST_OS_LINUX
auto backend = gui_getWindowInfo().window_main.backend;
if(backend == WindowHandleInfo::Backend::X11)
#elif BOOST_OS_LINUX
#if __ANDROID__
requiredExtensions.emplace_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
#else
auto backend = GuiSystem::getWindowInfo().window_main.backend;
if(backend == GuiSystem::WindowHandleInfo::Backend::X11)
requiredExtensions.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
#ifdef HAS_WAYLAND
else if (backend == WindowHandleInfo::Backend::WAYLAND)
else if (backend == GuiSystem::WindowHandleInfo::Backend::Wayland)
requiredExtensions.emplace_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#endif
#elif BOOST_OS_MACOS
#endif // HAS_WAYLAND
#endif // __ANDROID__
#elif BOOST_OS_MACOS
requiredExtensions.emplace_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
#endif
@ -152,7 +156,7 @@ std::vector<VulkanRenderer::DeviceInfo> VulkanRenderer::GetDevices()
throw std::runtime_error("Failed to find a GPU with Vulkan support.");
// create tmp surface to create a logical device
auto surface = CreateFramebufferSurface(instance, gui_getWindowInfo().window_main);
auto surface = CreateFramebufferSurface(instance, GuiSystem::getWindowInfo().window_main);
std::vector<VkPhysicalDevice> devices(device_count);
vkEnumeratePhysicalDevices(instance, &device_count, devices.data());
for (const auto& device : devices)
@ -280,12 +284,8 @@ void VulkanRenderer::GetDeviceFeatures()
cemuLog_log(LogType::Force, "Shader round mode control not available on this device or driver. Some rendering issues might occur.");
if (!m_featureControl.deviceExtensions.pipeline_creation_cache_control)
{
cemuLog_log(LogType::Force, "VK_EXT_pipeline_creation_cache_control not supported. Cannot use asynchronous shader and pipeline compilation");
// if async shader compilation is enabled show warning message
if (GetConfig().async_compile)
wxMessageBox(_("The currently installed graphics driver does not support the Vulkan extension necessary for asynchronous shader compilation. Asynchronous compilation cannot be used.\n \nRequired extension: VK_EXT_pipeline_creation_cache_control\n\nInstalling the latest graphics driver may solve this error."), _("Information"), wxOK | wxCENTRE);
}
if (!m_featureControl.deviceExtensions.custom_border_color_without_format)
{
if (m_featureControl.deviceExtensions.custom_border_color)
@ -369,7 +369,7 @@ VulkanRenderer::VulkanRenderer()
throw std::runtime_error("Failed to find a GPU with Vulkan support.");
// create tmp surface to create a logical device
auto surface = CreateFramebufferSurface(m_instance, gui_getWindowInfo().window_main);
auto surface = CreateFramebufferSurface(m_instance, GuiSystem::getWindowInfo().window_main);
auto& config = GetConfig();
decltype(config.graphic_device_uuid) zero{};
@ -451,7 +451,7 @@ VulkanRenderer::VulkanRenderer()
deviceFeatures.imageCubeArray = VK_TRUE;
#if !BOOST_OS_MACOS
deviceFeatures.geometryShader = VK_TRUE;
deviceFeatures.logicOp = VK_TRUE;
// deviceFeatures.logicOp = VK_TRUE;
#endif
deviceFeatures.occlusionQueryPrecise = VK_TRUE;
deviceFeatures.depthClamp = VK_TRUE;
@ -689,7 +689,7 @@ VulkanRenderer* VulkanRenderer::GetInstance()
void VulkanRenderer::InitializeSurface(const Vector2i& size, bool mainWindow)
{
auto& windowHandleInfo = mainWindow ? gui_getWindowInfo().canvas_main : gui_getWindowInfo().canvas_pad;
auto& windowHandleInfo = mainWindow ? GuiSystem::getWindowInfo().canvas_main : GuiSystem::getWindowInfo().canvas_pad;
const auto surface = CreateFramebufferSurface(m_instance, windowHandleInfo);
if (mainWindow)
@ -733,7 +733,7 @@ bool VulkanRenderer::IsPadWindowActive()
void VulkanRenderer::HandleScreenshotRequest(LatteTextureView* texView, bool padView)
{
const bool hasScreenshotRequest = gui_hasScreenshotRequest();
const bool hasScreenshotRequest = std::exchange(m_screenshot_requested, false);
if (!hasScreenshotRequest && m_screenshot_state == ScreenshotState::None)
return;
@ -1184,14 +1184,18 @@ std::vector<const char*> VulkanRenderer::CheckInstanceExtensionSupport(FeatureCo
requiredInstanceExtensions.emplace_back(VK_KHR_SURFACE_EXTENSION_NAME);
#if BOOST_OS_WINDOWS
requiredInstanceExtensions.emplace_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
#elif BOOST_OS_LINUX
auto backend = gui_getWindowInfo().window_main.backend;
if(backend == WindowHandleInfo::Backend::X11)
#elif BOOST_OS_LINUX
#if __ANDROID__
requiredInstanceExtensions.emplace_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME);
#else
auto backend = GuiSystem::getWindowInfo().window_main.backend;
if(backend == GuiSystem::WindowHandleInfo::Backend::X11)
requiredInstanceExtensions.emplace_back(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
#if HAS_WAYLAND
else if (backend == WindowHandleInfo::Backend::WAYLAND)
else if (backend == GuiSystem::WindowHandleInfo::Backend::Wayland)
requiredInstanceExtensions.emplace_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#endif
#endif // HAS_WAYLAND
#endif // __ANDROID__
#elif BOOST_OS_MACOS
requiredInstanceExtensions.emplace_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
#endif
@ -1272,6 +1276,25 @@ VkSurfaceKHR VulkanRenderer::CreateWinSurface(VkInstance instance, HWND hwindow)
#endif
#if BOOST_OS_LINUX
#if __ANDROID__
VkSurfaceKHR VulkanRenderer::CreateAndroidSurface(VkInstance instance, ANativeWindow* window)
{
VkAndroidSurfaceCreateInfoKHR sci{};
sci.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
sci.flags = 0;
sci.window = window;
VkSurfaceKHR result;
VkResult err;
if ((err = vkCreateAndroidSurfaceKHR(instance, &sci, nullptr, &result)) != VK_SUCCESS)
{
cemuLog_log(LogType::Force, "Cannot create an Android Vulkan surface: {}", (sint32)err);
throw std::runtime_error(fmt::format("Cannot create an Android Vulkan surface: {}", err));
}
return result;
}
#else
VkSurfaceKHR VulkanRenderer::CreateXlibSurface(VkInstance instance, Display* dpy, Window window)
{
VkXlibSurfaceCreateInfoKHR sci{};
@ -1329,22 +1352,27 @@ VkSurfaceKHR VulkanRenderer::CreateWaylandSurface(VkInstance instance, wl_displa
return result;
}
#endif // HAS_WAYLAND
#endif // __ANDROID__
#endif // BOOST_OS_LINUX
VkSurfaceKHR VulkanRenderer::CreateFramebufferSurface(VkInstance instance, struct WindowHandleInfo& windowInfo)
VkSurfaceKHR VulkanRenderer::CreateFramebufferSurface(VkInstance instance, struct GuiSystem::WindowHandleInfo& windowInfo)
{
#if BOOST_OS_WINDOWS
return CreateWinSurface(instance, windowInfo.hwnd);
return CreateWinSurface(instance, reinterpret_cast<HWND>(windowInfo.hwnd));
#elif BOOST_OS_LINUX
if(windowInfo.backend == WindowHandleInfo::Backend::X11)
return CreateXlibSurface(instance, windowInfo.xlib_display, windowInfo.xlib_window);
#if __ANDROID__
return CreateAndroidSurface(instance, static_cast<ANativeWindow*>(windowInfo.surface));
#else
if(windowInfo.backend == GuiSystem::WindowHandleInfo::Backend::X11)
return CreateXlibSurface(instance, static_cast<Display*>(windowInfo.display), reinterpret_cast<Window>(windowInfo.surface));
#ifdef HAS_WAYLAND
if(windowInfo.backend == WindowHandleInfo::Backend::WAYLAND)
return CreateWaylandSurface(instance, windowInfo.display, windowInfo.surface);
if(windowInfo.backend == GuiSystem::WindowHandleInfo::Backend::Wayland)
return CreateWaylandSurface(instance, static_cast<wl_display*>(windowInfo.display), static_cast<wl_surface*>(windowInfo.surface));
#endif
return {};
#endif // __ANDROID__
#elif BOOST_OS_MACOS
return CreateCocoaSurface(instance, windowInfo.handle);
return CreateCocoaSurface(instance, windowInfo.surface);
#endif
}
@ -2618,7 +2646,13 @@ bool VulkanRenderer::AcquireNextSwapchainImage(bool mainWindow)
m_padCloseReadySemaphore.notify();
return false;
}
#if __ANDROID__
std::unique_lock lock(m_surfaceMutex);
m_surfaceCondVar.wait(lock,[&](){
auto& chainInfo = GetChainInfoPtr(mainWindow);
return chainInfo && chainInfo->surface;
});
#endif // __ANDROID__
auto& chainInfo = GetChainInfo(mainWindow);
if (chainInfo.swapchainImageIndex != -1)
@ -2647,11 +2681,11 @@ void VulkanRenderer::RecreateSwapchain(bool mainWindow, bool skipCreate)
if (mainWindow)
{
ImGui_ImplVulkan_Shutdown();
gui_getWindowPhysSize(size.x, size.y);
GuiSystem::getWindowPhysSize(size.x, size.y);
}
else
{
gui_getPadWindowPhysSize(size.x, size.y);
GuiSystem::getPadWindowPhysSize(size.x, size.y);
}
chainInfo.swapchainImageIndex = -1;
@ -2681,9 +2715,9 @@ bool VulkanRenderer::UpdateSwapchainProperties(bool mainWindow)
int width, height;
if (mainWindow)
gui_getWindowPhysSize(width, height);
GuiSystem::getWindowPhysSize(width, height);
else
gui_getPadWindowPhysSize(width, height);
GuiSystem::getPadWindowPhysSize(width, height);
auto extent = chainInfo.getExtent();
if (width != extent.width || height != extent.height)
stateChanged = true;
@ -2886,7 +2920,25 @@ void VulkanRenderer::DrawBackbufferQuad(LatteTextureView* texView, RendererOutpu
auto descriptSet = backbufferBlit_createDescriptorSet(m_swapchainDescriptorSetLayout, texViewVk, useLinearTexFilter);
vkCmdBeginRenderPass(m_state.currentCommandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
#if __ANDROID__
float radians = 0.0f;
switch (chainInfo.m_preTransform)
{
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
radians = glm::radians(90.0f);
break;
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
radians = glm::radians(180.0f);
break;
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
radians = glm::radians(270.0f);
break;
default:
break;
}
const glm::mat2 preRotate = glm::rotate(glm::mat4(1.0), radians, glm::vec3(0.0F, 0.0F, 1.0F));
vkCmdPushConstants(m_state.currentCommandBuffer, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(preRotate), &preRotate);
#endif // __ANDROID__
vkCmdBindPipeline(m_state.currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
m_state.currentPipeline = pipeline;
@ -3710,7 +3762,29 @@ void VulkanRenderer::AppendOverlayDebugInfo()
ImGui::Text("--- Tex heaps ---");
memoryManager->appendOverlayHeapDebugInfo();
}
#if __ANDROID__
void VulkanRenderer::ClearSurface(bool mainWindow)
{
std::lock_guard lock(m_surfaceMutex);
auto& chainInfo = GetChainInfoPtr(mainWindow);
if(!chainInfo || !chainInfo->surface)
return;
vkDestroySurfaceKHR(m_instance, chainInfo->surface, nullptr);
chainInfo->surface = nullptr;
}
void VulkanRenderer::NotifySurfaceChanged(bool mainWindow)
{
std::lock_guard lock(m_surfaceMutex);
auto& chainInfo = GetChainInfoPtr(mainWindow);
if(!chainInfo)
return;
if(mainWindow)
chainInfo->surface = CreateFramebufferSurface(m_instance, GuiSystem::getWindowInfo().canvas_main);
else
chainInfo->surface = CreateFramebufferSurface(m_instance, GuiSystem::getWindowInfo().canvas_pad);
m_surfaceCondVar.notify_one();
}
#endif // __ANDROID__
void VKRDestructibleObject::flagForCurrentCommandBuffer()
{
m_lastCmdBufferId = VulkanRenderer::GetInstance()->GetCurrentCommandBufferId();

View File

@ -12,6 +12,7 @@
#include "util/helpers/Semaphore.h"
#include "util/containers/flat_hash_map.hpp"
#include "util/containers/robin_hood.h"
#include "Cemu/GuiSystem/GuiSystem.h"
struct VkSupportedFormatInfo_t
{
@ -183,7 +184,10 @@ public:
void GetDeviceFeatures();
void DetermineVendor();
void InitializeSurface(const Vector2i& size, bool mainWindow);
#if __ANDROID__
void ClearSurface(bool mainWindow);
void NotifySurfaceChanged(bool mainWindow);
#endif // __ANDROID
const std::unique_ptr<SwapchainInfoVk>& GetChainInfoPtr(bool mainWindow) const;
SwapchainInfoVk& GetChainInfo(bool mainWindow) const;
@ -199,14 +203,18 @@ public:
static VkSurfaceKHR CreateWinSurface(VkInstance instance, HWND hwindow);
#endif
#if BOOST_OS_LINUX
#if __ANDROID__
static VkSurfaceKHR CreateAndroidSurface(VkInstance instance, ANativeWindow* window);
#else
static VkSurfaceKHR CreateXlibSurface(VkInstance instance, Display* dpy, Window window);
static VkSurfaceKHR CreateXcbSurface(VkInstance instance, xcb_connection_t* connection, xcb_window_t window);
#ifdef HAS_WAYLAND
#ifdef HAS_WAYLAND
static VkSurfaceKHR CreateWaylandSurface(VkInstance instance, wl_display* display, wl_surface* surface);
#endif
#endif
#endif // HAS_WAYLAND
#endif // __ANDROID__
#endif // BOOST_OS_LINUX
static VkSurfaceKHR CreateFramebufferSurface(VkInstance instance, struct WindowHandleInfo& windowInfo);
static VkSurfaceKHR CreateFramebufferSurface(VkInstance instance, GuiSystem::WindowHandleInfo& windowInfo);
void AppendOverlayDebugInfo() override;
@ -599,6 +607,11 @@ private:
VkPipelineLayout m_pipelineLayout{nullptr};
VkCommandPool m_commandPool{ nullptr };
#if __ANDROID__
std::mutex m_surfaceMutex;
std::condition_variable m_surfaceCondVar;
#endif // __ANDROID__
// buffer to cache uniform vars
VkBuffer m_uniformVarBuffer = VK_NULL_HANDLE;
VkDeviceMemory m_uniformVarBufferMemory = VK_NULL_HANDLE;

View File

@ -1,7 +1,6 @@
#include "Cafe/HW/MMU/MMU.h"
#include "Cafe/GraphicPack/GraphicPack2.h"
#include "util/MemMapper/MemMapper.h"
#include <wx/msgdlg.h>
#include "config/ActiveSettings.h"
uint8* memory_base = NULL; // base address of the reserved 4GB space
@ -90,8 +89,7 @@ void MMURange::mapMem()
cemu_assert_debug(!m_isMapped);
if (MemMapper::AllocateMemory(memory_base + baseAddress, size, MemMapper::PAGE_PERMISSION::P_RW, true) == nullptr)
{
std::string errorMsg = fmt::format("Unable to allocate {} memory", name);
wxMessageBox(errorMsg.c_str(), "Error", wxOK | wxCENTRE | wxICON_ERROR);
cemuLog_log(LogType::Force, "Unable to allocate {} memory", name);
#if BOOST_OS_WINDOWS
ExitProcess(-1);
#else
@ -131,9 +129,8 @@ void memory_init()
memory_base = (uint8*)MemMapper::ReserveMemory(nullptr, (size_t)0x100000000, MemMapper::PAGE_PERMISSION::P_RW);
if( !memory_base )
{
debug_printf("memory_init(): Unable to reserve 4GB of memory\n");
debugBreakpoint();
wxMessageBox("Unable to reserve 4GB of memory\n", "Error", wxOK | wxCENTRE | wxICON_ERROR);
cemuLog_log(LogType::Force, "Unable to reserve 4GB of memory");
exit(-1);
}
for (auto& itr : g_mmuRanges)

View File

@ -2,7 +2,6 @@
#include "iosu_ioctl.h"
#include "Cafe/OS/libs/nn_common.h"
#include "gui/CemuApp.h"
#include <algorithm>
#include <mutex>

View File

@ -14,7 +14,6 @@
#include "util/crypto/crc32.h"
#include "config/ActiveSettings.h"
#include "Cafe/OS/libs/coreinit/coreinit_DynLoad.h"
#include "gui/guiWrapper.h"
class PPCCodeHeap : public VHeap
{
@ -1795,7 +1794,8 @@ void RPLLoader_UnloadModule(RPLModule* rpl)
RPLLoader_decrementModuleDependencyRefs(rpl);
// save module config for this module in the debugger
debuggerWindow_notifyModuleUnloaded(rpl);
auto debuggerInterface = debugger_getDebuggerCallbacks();
if (debuggerInterface) debuggerInterface->notifyModuleLoaded(rpl);
// release memory
rplLoaderHeap_codeArea2.free(rpl->regionMappingBase_text.GetPtr());
@ -1878,7 +1878,8 @@ void RPLLoader_Link()
RPLLoader_LoadDebugSymbols(rplModuleList[i]);
rplModuleList[i]->isLinked = true; // mark as linked
GraphicPack2::NotifyModuleLoaded(rplModuleList[i]);
debuggerWindow_notifyModuleLoaded(rplModuleList[i]);
auto debuggerCallbacks = debugger_getDebuggerCallbacks();
if (debuggerCallbacks) debuggerCallbacks->notifyModuleLoaded(rplModuleList[i]);
}
}

View File

@ -6,8 +6,6 @@
#include <imgui.h>
#include "imgui/imgui_extension.h"
#include <wx/msgdlg.h>
#include "Cafe/OS/libs/coreinit/coreinit_FS.h"
#include "Cafe/OS/libs/vpad/vpad.h"

View File

@ -1,5 +1,4 @@
#include "Cafe/OS/common/OSCommon.h"
#include "gui/wxgui.h"
#include "nn_save.h"
#include "Cafe/OS/libs/nn_acp/nn_acp.h"
@ -208,79 +207,6 @@ namespace save
return ConvertACPToSaveStatus(acp::ACPUnmountSaveDir());
}
void _CheckAndMoveLegacySaves()
{
const uint64 titleId = CafeSystem::GetForegroundTitleId();
fs::path targetPath, sourcePath;
try
{
bool copiedUser = false, copiedCommon = false;
const auto sourceSavePath = ActiveSettings::GetMlcPath("emulatorSave/{:08x}", CafeSystem::GetRPXHashBase());
sourcePath = sourceSavePath;
if (fs::exists(sourceSavePath) && is_directory(sourceSavePath))
{
targetPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/{:08x}", GetTitleIdHigh(titleId), GetTitleIdLow(titleId), 0x80000001);
fs::create_directories(targetPath);
copy(sourceSavePath, targetPath, fs::copy_options::overwrite_existing | fs::copy_options::recursive);
copiedUser = true;
}
const auto sourceCommonPath = ActiveSettings::GetMlcPath("emulatorSave/{:08x}_255", CafeSystem::GetRPXHashBase());
sourcePath = sourceCommonPath;
if (fs::exists(sourceCommonPath) && is_directory(sourceCommonPath))
{
targetPath = ActiveSettings::GetMlcPath("usr/save/{:08x}/{:08x}/user/common", GetTitleIdHigh(titleId), GetTitleIdLow(titleId));
fs::create_directories(targetPath);
copy(sourceCommonPath, targetPath, fs::copy_options::overwrite_existing | fs::copy_options::recursive);
copiedCommon = true;
}
if (copiedUser)
fs::remove_all(sourceSavePath);
if (copiedCommon)
fs::remove_all(sourceCommonPath);
}
catch (const std::exception& ex)
{
#if BOOST_OS_WINDOWS
std::wstringstream errorMsg;
errorMsg << L"Couldn't move your save files!" << std::endl << std::endl;
errorMsg << L"Error: " << ex.what() << std::endl << std::endl;
errorMsg << L"From:" << std::endl << sourcePath << std::endl << std::endl << "To:" << std::endl << targetPath;
const DWORD lastError = GetLastError();
if (lastError != ERROR_SUCCESS)
{
LPTSTR lpMsgBuf = nullptr;
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, lastError, 0, (LPTSTR)&lpMsgBuf, 0, nullptr);
if (lpMsgBuf)
{
errorMsg << std::endl << std::endl << L"Details: " << lpMsgBuf;
LocalFree(lpMsgBuf);
}
else
{
errorMsg << std::endl << std::endl << L"Error Code: 0x" << std::hex << lastError;
}
}
errorMsg << std::endl << std::endl << "Continuing will create a new save at the target location." << std::endl << "Do you want to continue?";
int result = wxMessageBox(errorMsg.str(), "Save Migration - Error", wxCENTRE | wxYES_NO | wxICON_ERROR);
if (result != wxYES)
{
exit(0);
return;
}
#endif
}
}
SAVEStatus SAVEInit()
{
const uint64 titleId = CafeSystem::GetForegroundTitleId();
@ -300,8 +226,6 @@ namespace save
SAVEMountSaveDir();
g_nn_save->initialized = true;
_CheckAndMoveLegacySaves();
uint32 high = GetTitleIdHigh(titleId) & (~0xC);
uint32 low = GetTitleIdLow(titleId);

View File

@ -1,6 +1,5 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "gui/wxgui.h"
#include "Cafe/OS/libs/padscore/padscore.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
#include "Cafe/OS/libs/coreinit/coreinit_Alarm.h"
@ -450,7 +449,7 @@ sint32 _KPADRead(uint32 channel, KPADStatus_t* samplingBufs, uint32 length, bety
samplingBufs->wpadErr = WPAD_ERR_NONE;
samplingBufs->data_format = controller->get_data_format();
samplingBufs->devType = controller->get_device_type();
if(!g_inputConfigWindowHasFocus)
if (!InputManager::input_config_window_has_focus())
{
const auto btn_repeat = padscore::g_padscore.controller_data[channel].btn_repeat;
controller->KPADRead(*samplingBufs, btn_repeat);

View File

@ -1,6 +1,5 @@
#include "Cafe/OS/common/OSCommon.h"
#include "Cafe/HW/Espresso/PPCCallback.h"
#include "gui/wxgui.h"
#include "Cafe/OS/libs/vpad/vpad.h"
#include "audio/IAudioAPI.h"
#include "Cafe/OS/libs/coreinit/coreinit_Time.h"
@ -263,7 +262,7 @@ namespace vpad
PPCCore_switchToScheduler();
}
if (!g_inputConfigWindowHasFocus)
if (!InputManager::input_config_window_has_focus())
{
if (channel <= 1 && vpadDelayEnabled)
{

View File

@ -6,6 +6,10 @@
#include "pugixml.hpp"
#include "Common/FileStream.h"
#if __ANDROID__
#include "Common/unix/ContentUriIStream.h"
#endif // __ANDROID__
#include <zarchive/zarchivereader.h>
#include "config/ActiveSettings.h"
@ -178,7 +182,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"))
@ -190,7 +194,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;
@ -218,7 +222,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);
@ -263,7 +273,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;
@ -343,7 +353,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();
@ -385,7 +401,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)
{

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 };
@ -216,7 +221,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));
@ -351,25 +362,50 @@ 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());
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;
if(FilesystemAndroid::isFile(file))
{
filesInDirectory.emplace_back(file);
}
else if(FilesystemAndroid::isDirectory(file))
{
dirsInDirectory.emplace_back(file);
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)
{

View File

@ -5,6 +5,8 @@ add_library(CemuComponents
ExpressionParser/ExpressionParser.h
FileCache/FileCache.cpp
FileCache/FileCache.h
GuiSystem/GuiSystem.cpp
GuiSystem/GuiSystem.h
Logging/CemuDebugLogging.h
Logging/CemuLogging.cpp
Logging/CemuLogging.h
@ -40,7 +42,6 @@ target_link_libraries(CemuComponents PRIVATE
CemuCafe
CemuCommon
CemuConfig
CemuGui
CemuUtil
Boost::headers
CURL::libcurl

View File

@ -0,0 +1,97 @@
#include "GuiSystem.h"
namespace GuiSystem
{
std::function<std::string(uint32)> s_key_code_to_string;
void registerKeyCodeToStringCallback(const std::function<std::string(uint32)>& keyCodeToString)
{
s_key_code_to_string = keyCodeToString;
}
void unregisterKeyCodeToStringCallback()
{
s_key_code_to_string = {};
}
std::string keyCodeToString(uint32 key)
{
if (!s_key_code_to_string)
return "";
return s_key_code_to_string(key);
}
WindowInfo s_window_info;
WindowInfo& getWindowInfo()
{
return s_window_info;
}
void getWindowSize(int& w, int& h)
{
w = s_window_info.width;
h = s_window_info.height;
}
void getPadWindowSize(int& w, int& h)
{
if (s_window_info.pad_open)
{
w = s_window_info.pad_width;
h = s_window_info.pad_height;
}
else
{
w = 0;
h = 0;
}
}
void getWindowPhysSize(int& w, int& h)
{
w = s_window_info.phys_width;
h = s_window_info.phys_height;
}
void getPadWindowPhysSize(int& w, int& h)
{
if (s_window_info.pad_open)
{
w = s_window_info.phys_pad_width;
h = s_window_info.phys_pad_height;
}
else
{
w = 0;
h = 0;
}
}
double getWindowDPIScale()
{
return s_window_info.dpi_scale;
}
double getPadDPIScale()
{
return s_window_info.pad_open ? s_window_info.pad_dpi_scale.load() : 1.0;
}
bool isPadWindowOpen()
{
return s_window_info.pad_open;
}
bool isKeyDown(uint32 key)
{
return s_window_info.get_keystate(key);
}
bool isKeyDown(PlatformKeyCodes key)
{
return s_window_info.get_keystate(key);
}
bool isFullScreen()
{
return s_window_info.is_fullscreen;
}
} // namespace GuiSystem

View File

@ -0,0 +1,115 @@
#pragma once
namespace GuiSystem
{
struct WindowHandleInfo
{
enum class Backend
{
X11,
Wayland,
Android,
Cocoa,
Windows
} backend;
void* display = nullptr;
void* surface = nullptr;
};
enum struct PlatformKeyCodes : uint32
{
LCONTROL,
RCONTROL,
TAB,
MAX
};
struct WindowInfo
{
std::atomic_bool app_active; // our app is active/has focus
std::atomic_int32_t width, height; // client size of main window
std::atomic_int32_t phys_width, phys_height; // client size of main window in physical pixels
std::atomic<double> dpi_scale;
std::atomic_bool pad_open; // if separate pad view is open
std::atomic_int32_t pad_width, pad_height; // client size of pad window
std::atomic_int32_t phys_pad_width, phys_pad_height; // client size of pad window in physical pixels
std::atomic<double> pad_dpi_scale;
std::atomic_bool pad_maximized = false;
std::atomic_int32_t restored_pad_x = -1, restored_pad_y = -1;
std::atomic_int32_t restored_pad_width = -1, restored_pad_height = -1;
std::atomic_bool has_screenshot_request;
std::atomic_bool is_fullscreen;
inline void set_keystate(uint32 keycode, bool state)
{
const std::lock_guard<std::mutex> lock(keycode_mutex);
m_keydown[keycode] = state;
}
inline void set_keystate(PlatformKeyCodes keycode, bool state)
{
const std::lock_guard<std::mutex> lock(keycode_mutex);
m_platformkeydown.at(static_cast<std::size_t>(keycode)) = state;
}
inline bool get_keystate(uint32 keycode)
{
const std::lock_guard<std::mutex> lock(keycode_mutex);
auto result = m_keydown.find(keycode);
if (result == m_keydown.end())
return false;
return result->second;
}
inline bool get_keystate(PlatformKeyCodes keycode)
{
const std::lock_guard<std::mutex> lock(keycode_mutex);
return m_platformkeydown.at(static_cast<size_t>(keycode));
}
inline void set_keystates_up()
{
const std::lock_guard<std::mutex> lock(keycode_mutex);
std::for_each(m_keydown.begin(), m_keydown.end(), [](std::pair<const uint32, bool>& el) { el.second = false; });
m_platformkeydown.fill(false);
}
template <typename fn>
void iter_keystates(fn f)
{
const std::lock_guard<std::mutex> lock(keycode_mutex);
std::for_each(m_keydown.cbegin(), m_keydown.cend(), f);
}
WindowHandleInfo window_main;
WindowHandleInfo window_pad;
WindowHandleInfo canvas_main;
WindowHandleInfo canvas_pad;
private:
std::array<bool, static_cast<uint32>(PlatformKeyCodes::MAX)> m_platformkeydown;
std::mutex keycode_mutex;
std::unordered_map<uint32, bool> m_keydown;
};
void registerKeyCodeToStringCallback(const std::function<std::string(uint32)>& keyCodeToString);
void unregisterKeyCodeToStringCallback();
std::string keyCodeToString(uint32 key);
void getWindowSize(int& w, int& h);
void getPadWindowSize(int& w, int& h);
void getWindowPhysSize(int& w, int& h);
void getPadWindowPhysSize(int& w, int& h);
double getWindowDPIScale();
double getPadDPIScale();
bool isPadWindowOpen();
bool isKeyDown(uint32 key);
bool isKeyDown(PlatformKeyCodes key);
bool isFullScreen();
WindowInfo& getWindowInfo();
} // namespace GuiSystem

View File

@ -1,5 +1,4 @@
#include "CemuLogging.h"
#include "gui/LoggingWindow.h"
#include "util/helpers/helpers.h"
#include "config/CemuConfig.h"
#include "config/ActiveSettings.h"
@ -57,6 +56,19 @@ const std::map<LogType, std::string> g_logging_window_mapping
{LogType::VulkanValidation, "Vulkan validation layer"},
};
LogCallbacks* g_logCallbacks = nullptr;
void cemuLog_registerLogCallbacks(LogCallbacks* logCallbacks)
{
g_logCallbacks = logCallbacks;
}
void cemuLog_unregisterLogCallbacks()
{
g_logCallbacks = nullptr;
}
bool cemuLog_advancedPPCLoggingEnabled()
{
return GetConfig().advanced_ppc_logging;
@ -144,10 +156,13 @@ bool cemuLog_log(LogType type, std::string_view text)
const auto it = std::find_if(g_logging_window_mapping.cbegin(), g_logging_window_mapping.cend(),
[type](const auto& entry) { return entry.first == type; });
if (it == g_logging_window_mapping.cend())
LoggingWindow::Log(text);
else
LoggingWindow::Log(it->second, text);
if (g_logCallbacks)
{
if (it == g_logging_window_mapping.cend())
g_logCallbacks->Log("", text);
else
g_logCallbacks->Log(it->second, text);
}
return true;
}

View File

@ -110,6 +110,16 @@ bool cemuLog_logDebug(LogType type, TFmt format, TArgs&&... args)
#endif
}
class LogCallbacks
{
public:
virtual void Log(std::string_view filter, std::string_view message) = 0;
virtual void Log(std::string_view filter, std::wstring_view message) = 0;
};
void cemuLog_registerLogCallbacks(LogCallbacks* logCallbacks);
void cemuLog_unregisterLogCallbacks();
// cafe lib calls
bool cemuLog_advancedPPCLoggingEnabled();

View File

@ -1,7 +1,6 @@
#include "Cemu/Tools/DownloadManager/DownloadManager.h"
#include "Cafe/Account/Account.h"
#include "gui/CemuApp.h"
#include "util/crypto/md5.h"
#include "Cafe/TitleList/TitleId.h"
#include "Common/FileStream.h"
@ -10,7 +9,6 @@
#include "config/ActiveSettings.h"
#include "util/ThreadPool/ThreadPool.h"
#include "util/helpers/enum_array.hpp"
#include "gui/MainWindow.h"
#include "Cafe/Filesystem/FST/FST.h"
#include "Cafe/TitleList/TitleList.h"
@ -20,15 +18,21 @@
#include <curl/curl.h>
#include <pugixml.hpp>
#include "gui/helpers/wxHelpers.h"
#include "Cemu/napi/napi.h"
#include "util/helpers/Serializer.h"
FileCache* s_nupFileCache = nullptr;
/* version list */
// Todo: replace this
std::string _(const std::string& str) { return str; }
std::string from_wxString(const std::string& str){ return str; }
void DownloadManager::setOnGameListRefreshRequested(const std::function<void()>& onGameListRefreshRequested)
{
m_onGameListRefreshRequested = onGameListRefreshRequested;
}
/* version list */
void DownloadManager::downloadTitleVersionList()
{
if (m_hasTitleVersionList)
@ -371,7 +375,7 @@ bool DownloadManager::syncAccountTickets()
for (auto& tiv : resultTicketIds.tivs)
{
index++;
std::string msg = _("Downloading account ticket").utf8_string();
std::string msg = _("Downloading account ticket");
msg.append(fmt::format(" {0}/{1}", index, count));
setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING);
// skip if already cached
@ -508,7 +512,7 @@ bool DownloadManager::syncUpdateTickets()
if (titleIdParser.GetType() != TitleIdParser::TITLE_TYPE::BASE_TITLE_UPDATE)
continue;
std::string msg = _("Downloading ticket").utf8_string();
std::string msg = _("Downloading ticket");
msg.append(fmt::format(" {0}/{1}", updateIndex, numUpdates));
updateIndex++;
setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING);
@ -561,7 +565,7 @@ bool DownloadManager::syncTicketCache()
for (auto& ticketInfo : m_ticketCache)
{
index++;
std::string msg = _("Downloading meta data").utf8_string();
std::string msg = _("Downloading meta data");
msg.append(fmt::format(" {0}/{1}", index, count));
setStatusMessage(msg, DLMGR_STATUS_CODE::CONNECTING);
prepareIDBE(ticketInfo.titleId);
@ -1453,7 +1457,7 @@ void DownloadManager::asyncPackageInstall(Package* package)
reportPackageStatus(package);
checkPackagesState();
// lastly request game list to be refreshed
MainWindow::RequestGameListRefresh();
if (m_onGameListRefreshRequested) m_onGameListRefreshRequested();
return;
}

View File

@ -393,6 +393,8 @@ public:
return m_userData;
}
void setOnGameListRefreshRequested(const std::function<void()>& onGameListRefreshRequested);
// register/unregister callbacks
// setting valid callbacks will also trigger transfer of the entire title/package state and the current status message
void registerCallbacks(
@ -450,6 +452,7 @@ public:
}
private:
std::function<void()> m_onGameListRefreshRequested;
void(*m_cbUpdateConnectStatus)(std::string statusText, DLMGR_STATUS_CODE statusCode) { nullptr };
void(*m_cbAddDownloadableTitle)(const DlMgrTitleReport& titleInfo);
void(*m_cbRemoveDownloadableTitle)(uint64 titleId, uint16 version);

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
@ -53,6 +54,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)
@ -67,7 +78,11 @@ target_link_libraries(CemuCommon PRIVATE
glm::glm
)
if (UNIX AND NOT APPLE)
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()

View File

@ -10,6 +10,10 @@
#include "ELFSymbolTable.h"
#endif
#if __ANDROID__
#include <boost/stacktrace.hpp>
#endif // __ANDROID__
#if BOOST_OS_LINUX
void DemangleAndPrintBacktrace(char** backtrace, size_t size)
{
@ -74,7 +78,10 @@ void handlerDumpingSignal(int sig, siginfo_t *info, void *context)
// should never be the case
printf("Unknown core dumping signal!\n");
}
CrashLog_WriteLine(fmt::format("Error: signal {}:", sig));
#if __ANDROID__
CrashLog_WriteLine(to_string(boost::stacktrace::stacktrace()));
#else
void* backtraceArray[128];
size_t size;
@ -86,8 +93,6 @@ void handlerDumpingSignal(int sig, siginfo_t *info, void *context)
backtraceArray[0] = (void *)uc->uc_mcontext.gregs[REG_RIP];
#endif
CrashLog_WriteLine(fmt::format("Error: signal {}:", sig));
#if BOOST_OS_LINUX
char** symbol_trace = backtrace_symbols(backtraceArray, size);
@ -103,6 +108,7 @@ void handlerDumpingSignal(int sig, siginfo_t *info, void *context)
#else
backtrace_symbols_fd(backtraceArray, size, STDERR_FILENO);
#endif
#endif // __ANDROID__
std::cerr << fmt::format("\nStacktrace and additional info written to:") << std::endl;
std::cerr << cemuLog_GetLogFilePath().generic_string() << std::endl;

155
src/Common/FileStream.cpp Normal file
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

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;
};

View File

@ -6,8 +6,12 @@
#include "wglext.h"
#endif
#if BOOST_OS_LINUX > 0
#if BOOST_OS_LINUX
#if __ANDROID__
#define EGL_EGL_PROTOTYPES 0
#include "egl.h"
#undef EGL_EGL_PROTOTYPES
#else
// from Xlib
#define Bool int
#define Status int
@ -33,8 +37,8 @@ typedef struct __GLXFBConfigRec *GLXFBConfig;
#undef Status
#undef True
#undef False
#endif
#endif // __ANDROID__
#endif // BOOST_OS_LINUX
#define GLFUNC(__type, __name) extern __type __name;
#define EGLFUNC(__type, __name) extern __type __name;

View File

@ -165,36 +165,36 @@ public:
return tmp;
}
betype<T>& operator^=(const betype<T>& v) requires std::integral<T>
betype<T>& operator^=(const betype<T>& v) requires std::is_integral_v<T>
{
m_value ^= v.m_value;
return *this;
}
betype<T>& operator>>=(std::size_t idx) requires std::integral<T>
betype<T>& operator>>=(std::size_t idx) requires std::is_integral_v<T>
{
m_value = SwapEndian(T(value() >> idx));
return *this;
}
betype<T>& operator<<=(std::size_t idx) requires std::integral<T>
betype<T>& operator<<=(std::size_t idx) requires std::is_integral_v<T>
{
m_value = SwapEndian(T(value() << idx));
return *this;
}
betype<T> operator~() const requires std::integral<T>
betype<T> operator~() const requires std::is_integral_v<T>
{
return from_bevalue(T(~m_value));
}
betype<T>& operator++() requires std::integral<T>
betype<T>& operator++() requires std::is_integral_v<T>
{
m_value = SwapEndian(T(value() + 1));
return *this;
}
betype<T>& operator--() requires std::integral<T>
betype<T>& operator--() requires std::is_integral_v<T>
{
m_value = SwapEndian(T(value() - 1));
return *this;

View File

@ -5,6 +5,9 @@
#if BOOST_OS_WINDOWS
#include "Common/windows/platform.h"
#elif __ANDROID__
#include <byteswap.h>
#include "Common/unix/platform.h"
#elif BOOST_OS_LINUX
#include <byteswap.h>
#include <X11/Xlib.h>

View File

@ -74,7 +74,6 @@
#include <type_traits>
#include <optional>
#include <span>
#include <ranges>
#include <boost/predef.h>
#include <boost/nowide/convert.hpp>
@ -84,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"
@ -461,16 +516,24 @@ inline std::string_view _utf8Wrapper(std::u8string_view input)
// convert fs::path to utf8 encoded string
inline std::string _pathToUtf8(const fs::path& path)
{
#if __ANDROID__
return path.generic_string();
#else
std::u8string strU8 = path.generic_u8string();
std::string v((const char*)strU8.data(), strU8.size());
return v;
#endif // __ANDROID__
}
// convert utf8 encoded string to fs::path
inline fs::path _utf8ToPath(std::string_view input)
{
#if __ANDROID__
return fs::path(input);
#else
std::basic_string_view<char8_t> v((char8_t*)input.data(), input.size());
return fs::path(v);
#endif // __ANDROID__
}
// locale-independent variant of tolower() which also matches Wii U behavior

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;
};

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();
}

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;
};

View File

@ -21,66 +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)
{
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)
@ -89,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();
@ -100,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);
@ -115,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);
@ -123,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;
@ -155,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;
@ -186,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)
{
@ -206,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)
@ -226,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)

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};
};

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

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

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;

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;

15
src/android/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

1
src/android/app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,71 @@
plugins {
id 'com.android.application'
}
android {
namespace 'info.cemu.Cemu'
compileSdk 34
ndkVersion '25.2.9519653'
defaultConfig {
applicationId "info.cemu.Cemu"
minSdk 30
targetSdk 34
versionCode 1
versionName "1.0"
ndk {
// abiFilters("x86_64", "arm64-v8a")
abiFilters("arm64-v8a")
}
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
externalNativeBuild {
cmake {
version '3.22.1'
path file('../../../CMakeLists.txt')
}
}
defaultConfig {
externalNativeBuild {
cmake {
arguments(
'-DENABLE_VCPKG=ON',
'-DVCPKG_TARGET_ANDROID=ON',
'-DENABLE_SDL=OFF',
'-DENABLE_WXWIDGETS=OFF',
'-DENABLE_OPENGL=OFF',
'-DBUNDLE_SPEEX=ON',
'-DENABLE_DISCORD_RPC=OFF',
'-DENABLE_WAYLAND=OFF'
)
// abiFilters("x86_64", "arm64-v8a")
abiFilters("arm64-v8a")
}
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

21
src/android/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
package info.cemu.Cemu;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("info.cemu.Cemu", appContext.getPackageName());
}
}

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name="info.cemu.Cemu.CemuApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:isGame="true"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Cemu"
tools:targetApi="31">
<activity
android:name=".EmulationActivity"
android:launchMode="singleTop"
android:screenOrientation="userLandscape" />
<activity
android:name=".SettingsActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Cemu">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,56 @@
#include "AndroidAudio.h"
#include "Cafe/OS/libs/snd_core/ax.h"
#include "audio/IAudioAPI.h"
#if HAS_CUBEB
#include "audio/CubebAPI.h"
#endif // HAS_CUBEB
namespace AndroidAudio
{
void createAudioDevice(IAudioAPI::AudioAPI audioApi, sint32 channels, sint32 volume, bool isTV)
{
static constexpr int AX_FRAMES_PER_GROUP = 4;
std::unique_lock lock(g_audioMutex);
auto& audioDevice = isTV ? g_tvAudio : g_padAudio;
switch (channels)
{
case 0:
channels = 1;
break;
case 2:
channels = 6;
break;
default: // stereo
channels = 2;
break;
}
switch (audioApi)
{
#if HAS_CUBEB
case IAudioAPI::AudioAPI::Cubeb:
{
audioDevice.reset();
std::shared_ptr<CubebAPI::CubebDeviceDescription> deviceDescriptionPtr = std::make_shared<CubebAPI::CubebDeviceDescription>(nullptr, std::string(), std::wstring());
audioDevice = IAudioAPI::CreateDevice(IAudioAPI::AudioAPI::Cubeb, deviceDescriptionPtr, 48000, channels, snd_core::AX_SAMPLES_PER_3MS_48KHZ * AX_FRAMES_PER_GROUP, 16);
audioDevice->SetVolume(volume);
break;
}
#endif // HAS_CUBEB
default:
cemuLog_log(LogType::Force, fmt::format("Invalid audio api: {}", audioApi));
break;
}
}
void setAudioVolume(sint32 volume, bool isTV)
{
std::shared_lock lock(g_audioMutex);
auto& audioDevice = isTV ? g_tvAudio : g_padAudio;
if (audioDevice)
audioDevice->SetVolume(volume);
}
}; // namespace AndroidAudio

View File

@ -0,0 +1,9 @@
#pragma once
#include "audio/IAudioAPI.h"
namespace AndroidAudio
{
void createAudioDevice(IAudioAPI::AudioAPI audioApi, sint32 channels, sint32 volume, bool isTV = true);
void setAudioVolume(sint32 volume, bool isTV = true);
}; // namespace AndroidAudio

View File

@ -0,0 +1,3 @@
#include "AndroidEmulatedController.h"
std::array<std::unique_ptr<AndroidEmulatedController>, InputManager::kMaxController> AndroidEmulatedController::s_emulatedControllers;

View File

@ -0,0 +1,112 @@
#pragma once
#include "input/InputManager.h"
#include "input/api/Controller.h"
#include "input/emulated/ClassicController.h"
#include "input/emulated/ProController.h"
#include "input/emulated/WiimoteController.h"
class AndroidEmulatedController
{
private:
size_t m_index;
static std::array<std::unique_ptr<AndroidEmulatedController>, InputManager::kMaxController> s_emulatedControllers;
EmulatedControllerPtr m_emulatedController;
AndroidEmulatedController(size_t index) : m_index(index)
{
m_emulatedController = InputManager::instance().get_controller(m_index);
}
public:
static AndroidEmulatedController &getAndroidEmulatedController(size_t index)
{
auto &controller = s_emulatedControllers.at(index);
if (!controller)
controller = std::unique_ptr<AndroidEmulatedController>(new AndroidEmulatedController(index));
return *controller;
}
void setType(EmulatedController::Type type)
{
if (m_emulatedController && m_emulatedController->type() == type)
return;
m_emulatedController = InputManager::instance().set_controller(m_index, type);
InputManager::instance().save(m_index);
}
void setMapping(uint64 mappingId, ControllerPtr controller, uint64 buttonId)
{
if (m_emulatedController && controller)
{
const auto &controllers = m_emulatedController->get_controllers();
auto controllerIt = std::find_if(controllers.begin(), controllers.end(), [&](const ControllerPtr &c)
{ return c->api() == controller->api() && c->uuid() == controller->uuid(); });
if (controllerIt == controllers.end())
m_emulatedController->add_controller(controller);
else
controller = *controllerIt;
m_emulatedController->set_mapping(mappingId, controller, buttonId);
InputManager::instance().save(m_index);
}
}
std::optional<std::string> getMapping(uint64 mapping) const
{
if (!m_emulatedController)
return {};
auto controller = m_emulatedController->get_mapping_controller(mapping);
if (!controller)
return {};
auto mappingName = m_emulatedController->get_mapping_name(mapping);
return fmt::format("{}: {}", controller->display_name(), mappingName);
}
std::map<uint64, std::string> getMappings() const
{
if (!m_emulatedController)
return {};
std::map<uint64, std::string> mappings;
auto type = m_emulatedController->type();
uint64 mapping = 0;
uint64 maxMapping = 0;
if (type == EmulatedController::Type::VPAD)
{
mapping = VPADController::ButtonId::kButtonId_A;
maxMapping = VPADController::ButtonId::kButtonId_Max;
}
if (type == EmulatedController::Type::Pro)
{
mapping = ProController::ButtonId::kButtonId_A;
maxMapping = ProController::ButtonId::kButtonId_Max;
}
if (type == EmulatedController::Type::Classic)
{
mapping = ClassicController::ButtonId::kButtonId_A;
maxMapping = ClassicController::ButtonId::kButtonId_Max;
}
if (type == EmulatedController::Type::Wiimote)
{
mapping = WiimoteController::ButtonId::kButtonId_A;
maxMapping = WiimoteController::ButtonId::kButtonId_Max;
}
for (; mapping < maxMapping; mapping++)
{
auto mappingName = getMapping(mapping);
if (mappingName.has_value())
mappings[mapping] = mappingName.value();
}
return mappings;
}
void setDisabled()
{
InputManager::instance().delete_controller(m_index, true);
m_emulatedController.reset();
}
EmulatedControllerPtr getEmulatedController()
{
return m_emulatedController;
}
void clearMapping(uint64 mapping)
{
if (!m_emulatedController)
return;
m_emulatedController->delete_mapping(mapping);
}
};

View File

@ -0,0 +1,144 @@
#pragma once
#include "JNIUtils.h"
#include "Common/unix/FilesystemAndroid.h"
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::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::lock_guard lock(m_functionMutex);
bool functionFinished = false;
bool result = false;
m_function = [&, this](JNIEnv *env)
{
jstring uriString = env->NewStringUTF(uri.c_str());
result = env->CallStaticBooleanMethod(*m_fileUtilClass, methodId, uriString);
env->DeleteLocalRef(uriString);
functionFinished = true;
};
m_threadCV.notify_one();
{
std::unique_lock threadLock(m_threadMutex);
m_functionCV.wait(threadLock, [&]() -> bool
{ return functionFinished; });
}
return result;
}
void runFilesystemCallbacks()
{
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_functionCV.notify_one();
}
}
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(&AndroidFilesystemCallbacks::runFilesystemCallbacks, this);
}
~AndroidFilesystemCallbacks()
{
m_continue = false;
m_threadCV.notify_one();
m_thread.join();
}
int openContentUri(const std::filesystem::path &uri) override
{
std::lock_guard lock(m_functionMutex);
bool functionFinished = false;
int fd = -1;
m_function = [&, this](JNIEnv *env)
{
jstring uriString = env->NewStringUTF(uri.c_str());
fd = env->CallStaticIntMethod(*m_fileUtilClass, m_openContentUriMid, uriString);
env->DeleteLocalRef(uriString);
functionFinished = true;
};
m_threadCV.notify_one();
{
std::unique_lock threadLock(m_threadMutex);
m_functionCV.wait(threadLock, [&]()
{ return functionFinished; });
}
return fd;
}
std::vector<std::filesystem::path> listFiles(const std::filesystem::path &uri) override
{
std::lock_guard lock(m_functionMutex);
bool functionFinished = false;
std::vector<std::filesystem::path> paths;
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);
functionFinished = true;
};
m_threadCV.notify_one();
{
std::unique_lock threadLock(m_threadMutex);
m_functionCV.wait(threadLock, [&]()
{ return 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);
}
};

View File

@ -0,0 +1,25 @@
#pragma once
#include "GameIconLoader.h"
#include "JNIUtils.h"
class AndroidGameIconLoadedCallback : public GameIconLoadedCallback
{
jmethodID m_onGameIconLoadedMID;
JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj;
public:
AndroidGameIconLoadedCallback(jmethodID onGameIconLoadedMID,
jobject gameTitleLoadedCallbackObj)
: m_onGameIconLoadedMID(onGameIconLoadedMID),
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj) {}
void onIconLoaded(TitleId titleId, int* colors, int width, int height) override
{
JNIUtils::ScopedJNIENV env;
jintArray jIconData = env->NewIntArray(width * height);
env->SetIntArrayRegion(jIconData, 0, width * height, reinterpret_cast<const jint*>(colors));
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameIconLoadedMID, *reinterpret_cast<const jlong*>(&titleId), jIconData, width, height);
env->DeleteLocalRef(jIconData);
}
};

View File

@ -0,0 +1,23 @@
#pragma once
#include "GameTitleLoader.h"
class AndroidGameTitleLoadedCallback : public GameTitleLoadedCallback
{
jmethodID m_onGameTitleLoadedMID;
JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj;
public:
AndroidGameTitleLoadedCallback(jmethodID onGameTitleLoadedMID, jobject gameTitleLoadedCallbackObj)
: m_onGameTitleLoadedMID(onGameTitleLoadedMID),
m_gameTitleLoadedCallbackObj(gameTitleLoadedCallbackObj) {}
void onTitleLoaded(const Game &game) override
{
JNIUtils::ScopedJNIENV env;
jstring name = env->NewStringUTF(game.name.c_str());
jlong titleId = *reinterpret_cast<const jlong *>(&game.titleId);
env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameTitleLoadedMID, titleId, name);
env->DeleteLocalRef(name);
}
};

View File

@ -0,0 +1,34 @@
add_library(CemuAndroid SHARED
AndroidAudio.cpp
AndroidAudio.h
AndroidEmulatedController.cpp
AndroidEmulatedController.h
AndroidFilesystemCallbacks.h
AndroidGameIconLoadedCallback.h
AndroidGameTitleLoadedCallback.h
CMakeLists.txt
CafeSystemUtils.cpp
CafeSystemUtils.h
EmulationState.h
GameIconLoader.cpp
GameIconLoader.h
GameTitleLoader.cpp
GameTitleLoader.h
Image.cpp
Image.h
JNIUtils.cpp
JNIUtils.h
Utils.cpp
Utils.h
native-lib.cpp
stb_image.h
)
target_link_libraries(CemuAndroid PRIVATE
-landroid
CemuCommon
CemuAudio
CemuComponents
CemuCafe
CemuBin
)

View File

@ -0,0 +1,52 @@
#include "CafeSystemUtils.h"
#include "Cafe/CafeSystem.h"
#include "Cafe/TitleList/TitleList.h"
namespace CafeSystemUtils
{
void startGame(TitleId titleId)
{
TitleInfo launchTitle;
if (!CafeTitleList::GetFirstByTitleId(titleId, launchTitle))
return;
if (launchTitle.IsValid())
{
// the title might not be in the TitleList, so we add it as a temporary entry
CafeTitleList::AddTitleFromPath(launchTitle.GetPath());
// title is valid, launch from TitleId
TitleId baseTitleId;
if (!CafeTitleList::FindBaseTitleId(launchTitle.GetAppTitleId(), baseTitleId))
{
}
CafeSystem::STATUS_CODE statusCode = CafeSystem::PrepareForegroundTitle(baseTitleId);
if (statusCode == CafeSystem::STATUS_CODE::INVALID_RPX)
{
}
else if (statusCode == CafeSystem::STATUS_CODE::UNABLE_TO_MOUNT)
{
}
else if (statusCode != CafeSystem::STATUS_CODE::SUCCESS)
{
}
}
else // if (launchTitle.GetFormat() == TitleInfo::TitleDataFormat::INVALID_STRUCTURE )
{
// title is invalid, if its an RPX/ELF we can launch it directly
// otherwise its an error
CafeTitleFileType fileType = DetermineCafeSystemFileType(launchTitle.GetPath());
if (fileType == CafeTitleFileType::RPX || fileType == CafeTitleFileType::ELF)
{
CafeSystem::STATUS_CODE statusCode = CafeSystem::PrepareForegroundTitleFromStandaloneRPX(
launchTitle.GetPath());
if (statusCode != CafeSystem::STATUS_CODE::SUCCESS)
{
return;
}
}
}
CafeSystem::LaunchForegroundTitle();
}
}; // namespace CafeSystemUtils

View File

@ -0,0 +1,8 @@
#pragma once
#include "Cafe/TitleList/TitleId.h"
namespace CafeSystemUtils
{
void startGame(TitleId titleId);
};

View File

@ -0,0 +1,234 @@
#pragma once
#include <jni.h>
#include "AndroidAudio.h"
#include "AndroidEmulatedController.h"
#include "AndroidFilesystemCallbacks.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
#include "CafeSystemUtils.h"
#include "Cemu/GuiSystem/GuiSystem.h"
#include "GameIconLoader.h"
#include "GameTitleLoader.h"
#include "Utils.h"
#include "input/ControllerFactory.h"
#include "input/InputManager.h"
#include "input/api/Android/AndroidController.h"
#include "input/api/Android/AndroidControllerProvider.h"
int mainEmulatorHLE();
class EmulationState
{
GameIconLoader m_gameIconLoader;
GameTitleLoader m_gameTitleLoader;
public:
void initializeEmulation()
{
FilesystemAndroid::setFilesystemCallbacks(std::make_shared<AndroidFilesystemCallbacks>());
NetworkConfig::LoadOnce();
mainEmulatorHLE();
InputManager::instance().load();
InitializeGlobalVulkan();
createCemuDirectories();
g_config.Load();
}
void initializeActiveSettings(const fs::path &dataPath, const fs::path &cachePath)
{
ActiveSettings::LoadOnce({}, dataPath, dataPath, cachePath, dataPath);
}
void clearSurface(bool isMainCanvas)
{
VulkanRenderer::GetInstance()->ClearSurface(isMainCanvas);
}
void notifySurfaceChanged(bool isMainCanvas)
{
VulkanRenderer::GetInstance()->NotifySurfaceChanged(isMainCanvas);
}
void setSurface(JNIEnv *env, jobject surface, bool isMainCanvas)
{
auto &windowHandleInfo = isMainCanvas ? GuiSystem::getWindowInfo().canvas_main : GuiSystem::getWindowInfo().canvas_pad;
if (windowHandleInfo.surface)
{
ANativeWindow_release(static_cast<ANativeWindow *>(windowHandleInfo.surface));
windowHandleInfo.surface = nullptr;
}
if (surface)
windowHandleInfo.surface = ANativeWindow_fromSurface(env, surface);
if (isMainCanvas)
GuiSystem::getWindowInfo().window_main.surface = windowHandleInfo.surface;
}
void setSurfaceSize(int width, int height, bool isMainCanvas)
{
auto &windowInfo = GuiSystem::getWindowInfo();
if (isMainCanvas)
{
windowInfo.width = windowInfo.phys_width = width;
windowInfo.height = windowInfo.phys_height = height;
}
else
{
windowInfo.pad_width = windowInfo.phys_pad_width = width;
windowInfo.pad_height = windowInfo.phys_pad_height = height;
}
}
void onKeyEvent(const std::string &deviceDescriptor, const std::string &deviceName, int keyCode, bool isPressed)
{
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
androidControllerProvider->on_key_event(deviceDescriptor, deviceName, keyCode, isPressed);
}
void onAxisEvent(const std::string &deviceDescriptor, const std::string &deviceName, int axisCode, float value)
{
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
androidControllerProvider->on_axis_event(deviceDescriptor, deviceName, axisCode, value);
}
std::optional<std::string> getEmulatedControllerMapping(size_t index, uint64 mappingId)
{
return AndroidEmulatedController::getAndroidEmulatedController(index).getMapping(mappingId);
}
int getVPADControllersCount()
{
int vpadCount = 0;
for (int i = 0; i < InputManager::kMaxController; i++)
{
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(i).getEmulatedController();
if (!emulatedController)
continue;
if (emulatedController->type() == EmulatedController::Type::VPAD)
++vpadCount;
}
return vpadCount;
}
int getWPADControllersCount()
{
int wpadCount = 0;
for (int i = 0; i < InputManager::kMaxController; i++)
{
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(
i)
.getEmulatedController();
if (!emulatedController)
continue;
if (emulatedController->type() != EmulatedController::Type::VPAD)
++wpadCount;
}
return wpadCount;
}
EmulatedController::Type getEmulatedControllerType(size_t index)
{
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(index).getEmulatedController();
if (emulatedController)
return emulatedController->type();
throw std::runtime_error(fmt::format("can't get type for emulated controller {}", index));
}
void clearEmulatedControllerMapping(size_t index, uint64 mapping)
{
AndroidEmulatedController::getAndroidEmulatedController(index).clearMapping(mapping);
}
void setEmulatedControllerType(size_t index, EmulatedController::Type type)
{
auto &androidEmulatedController = AndroidEmulatedController::getAndroidEmulatedController(index);
if (EmulatedController::Type::VPAD <= type && type < EmulatedController::Type::MAX)
androidEmulatedController.setType(type);
else
androidEmulatedController.setDisabled();
}
void initializeRenderer()
{
g_renderer = std::make_unique<VulkanRenderer>();
}
void initializeRenderSurface(bool isMainCanvas)
{
int width, height;
if (isMainCanvas)
GuiSystem::getWindowPhysSize(width, height);
else
GuiSystem::getPadWindowPhysSize(width, height);
VulkanRenderer::GetInstance()->InitializeSurface({width, height}, isMainCanvas);
}
void setDPI(float dpi)
{
auto &windowInfo = GuiSystem::getWindowInfo();
windowInfo.dpi_scale = windowInfo.pad_dpi_scale = dpi;
}
bool isEmulatedControllerDisabled(size_t index)
{
return AndroidEmulatedController::getAndroidEmulatedController(index).getEmulatedController() == nullptr;
}
std::map<uint64, std::string> getEmulatedControllerMappings(size_t index)
{
return AndroidEmulatedController::getAndroidEmulatedController(index).getMappings();
}
void setControllerMapping(const std::string &deviceDescriptor, const std::string &deviceName, size_t index, uint64 mappingId, uint64 buttonId)
{
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
auto controller = ControllerFactory::CreateController(InputAPI::Android, deviceDescriptor, deviceName);
AndroidEmulatedController::getAndroidEmulatedController(index).setMapping(mappingId, controller, buttonId);
}
void initializeAudioDevices()
{
auto &config = g_config.data();
if (!config.tv_device.empty())
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb, config.tv_channels, config.tv_volume, true);
if (!config.pad_device.empty())
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb, config.pad_channels, config.pad_volume, false);
}
void setOnGameTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback> &onGameTitleLoaded)
{
m_gameTitleLoader.setOnTitleLoaded(onGameTitleLoaded);
}
const Image &getGameIcon(TitleId titleId)
{
return m_gameIconLoader.getGameIcon(titleId);
}
void setOnGameIconLoaded(const std::shared_ptr<class GameIconLoadedCallback> &onGameIconLoaded)
{
m_gameIconLoader.setOnIconLoaded(onGameIconLoaded);
}
void addGamePath(const fs::path &gamePath)
{
m_gameTitleLoader.addGamePath(gamePath);
}
void requestGameIcon(TitleId titleId)
{
m_gameIconLoader.requestIcon(titleId);
}
void reloadGameTitles()
{
m_gameTitleLoader.reloadGameTitles();
}
void startGame(TitleId titleId)
{
initializeAudioDevices();
CafeSystemUtils::startGame(titleId);
}
};

View File

@ -0,0 +1,87 @@
#include "GameIconLoader.h"
#include "Cafe/TitleList/TitleInfo.h"
#include "Cafe/TitleList/TitleList.h"
GameIconLoader::GameIconLoader()
{
m_loaderThread = std::thread(&GameIconLoader::loadGameIcons, this);
}
GameIconLoader::~GameIconLoader()
{
m_continueLoading = false;
m_condVar.notify_one();
m_loaderThread.join();
}
const Image &GameIconLoader::getGameIcon(TitleId titleId)
{
return m_iconCache.at(titleId);
}
void GameIconLoader::requestIcon(TitleId titleId)
{
{
std::lock_guard lock(m_threadMutex);
m_iconsToLoad.emplace_front(titleId);
}
m_condVar.notify_one();
}
void GameIconLoader::loadGameIcons()
{
while (m_continueLoading)
{
TitleId titleId = 0;
{
std::unique_lock lock(m_threadMutex);
m_condVar.wait(lock, [this]
{ return !m_iconsToLoad.empty() || !m_continueLoading; });
if (!m_continueLoading)
return;
titleId = m_iconsToLoad.front();
m_iconsToLoad.pop_front();
}
TitleInfo titleInfo;
if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo))
continue;
if (auto iconIt = m_iconCache.find(titleId); iconIt != m_iconCache.end())
{
auto &icon = iconIt->second;
if (m_onIconLoaded)
m_onIconLoaded->onIconLoaded(titleId, icon.intColors(), icon.m_width, icon.m_height);
continue;
}
std::string tempMountPath = TitleInfo::GetUniqueTempMountingPath();
if (!titleInfo.Mount(tempMountPath, "", FSC_PRIORITY_BASE))
continue;
auto tgaData = fsc_extractFile((tempMountPath + "/meta/iconTex.tga").c_str());
if (tgaData && tgaData->size() > 16)
{
Image image(tgaData.value());
if (image.isOk())
{
if (m_onIconLoaded)
m_onIconLoaded->onIconLoaded(titleId, image.intColors(), image.m_width, image.m_height);
m_iconCache.emplace(titleId, std::move(image));
}
}
else
{
cemuLog_log(LogType::Force, "Failed to load icon for title {:016x}", titleId);
}
titleInfo.Unmount(tempMountPath);
}
}
void GameIconLoader::setOnIconLoaded(const std::shared_ptr<GameIconLoadedCallback> &onIconLoaded)
{
{
std::lock_guard lock(m_threadMutex);
m_onIconLoaded = onIconLoaded;
}
m_condVar.notify_one();
};

View File

@ -0,0 +1,35 @@
#pragma once
#include "Cafe/TitleList/TitleId.h"
#include "Image.h"
class GameIconLoadedCallback
{
public:
virtual void onIconLoaded(TitleId titleId, int *colors, int width, int height) = 0;
};
class GameIconLoader
{
std::condition_variable m_condVar;
std::atomic_bool m_continueLoading = true;
std::thread m_loaderThread;
std::mutex m_threadMutex;
std::deque<TitleId> m_iconsToLoad;
std::map<TitleId, Image> m_iconCache;
std::shared_ptr<GameIconLoadedCallback> m_onIconLoaded = nullptr;
public:
GameIconLoader();
~GameIconLoader();
const Image &getGameIcon(TitleId titleId);
void setOnIconLoaded(const std::shared_ptr<GameIconLoadedCallback> &onIconLoaded);
void requestIcon(TitleId titleId);
private:
void loadGameIcons();
};

View File

@ -0,0 +1,140 @@
#include "GameTitleLoader.h"
GameTitleLoader::GameTitleLoader()
{
m_loaderThread = std::thread(&GameTitleLoader::loadGameTitles, this);
}
void GameTitleLoader::queueTitle(TitleId titleId)
{
{
std::lock_guard lock(m_threadMutex);
m_titlesToLoad.emplace_front(titleId);
}
m_condVar.notify_one();
}
void GameTitleLoader::setOnTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback> &gameTitleLoadedCallback)
{
{
std::lock_guard lock(m_threadMutex);
m_gameTitleLoadedCallback = gameTitleLoadedCallback;
}
m_condVar.notify_one();
}
void GameTitleLoader::reloadGameTitles()
{
if (m_callbackIdTitleList.has_value())
{
CafeTitleList::Refresh();
CafeTitleList::UnregisterCallback(m_callbackIdTitleList.value());
}
m_gameInfos.clear();
registerCallback();
}
GameTitleLoader::~GameTitleLoader()
{
m_continueLoading = false;
m_condVar.notify_one();
m_loaderThread.join();
if (m_callbackIdTitleList.has_value())
CafeTitleList::UnregisterCallback(m_callbackIdTitleList.value());
}
void GameTitleLoader::titleRefresh(TitleId titleId)
{
GameInfo2 gameInfo = CafeTitleList::GetGameInfo(titleId);
if (!gameInfo.IsValid())
{
return;
}
TitleId baseTitleId = gameInfo.GetBaseTitleId();
bool isNewEntry = false;
if (auto gameInfoIt = m_gameInfos.find(baseTitleId); gameInfoIt == m_gameInfos.end())
{
isNewEntry = true;
m_gameInfos[baseTitleId] = Game();
}
Game &game = m_gameInfos[baseTitleId];
game.titleId = baseTitleId;
game.name = GetNameByTitleId(baseTitleId);
game.version = gameInfo.GetVersion();
game.region = gameInfo.GetRegion();
if (gameInfo.HasAOC())
{
game.dlc = gameInfo.GetAOCVersion();
}
if (isNewEntry)
{
iosu::pdm::GameListStat playTimeStat{};
if (iosu::pdm::GetStatForGamelist(baseTitleId, playTimeStat))
game.secondsPlayed = playTimeStat.numMinutesPlayed * 60;
if (m_gameTitleLoadedCallback)
m_gameTitleLoadedCallback->onTitleLoaded(game);
}
else
{
// TODO: Update
}
}
void GameTitleLoader::loadGameTitles()
{
while (m_continueLoading)
{
TitleId titleId = 0;
{
std::unique_lock lock(m_threadMutex);
m_condVar.wait(lock, [this]
{ return (!m_titlesToLoad.empty()) ||
!m_continueLoading; });
if (!m_continueLoading)
return;
titleId = m_titlesToLoad.front();
m_titlesToLoad.pop_front();
}
titleRefresh(titleId);
}
}
std::string GameTitleLoader::GetNameByTitleId(uint64 titleId)
{
auto it = m_name_cache.find(titleId);
if (it != m_name_cache.end())
return it->second;
TitleInfo titleInfo;
if (!CafeTitleList::GetFirstByTitleId(titleId, titleInfo))
return "Unknown title";
std::string name;
if (!GetConfig().GetGameListCustomName(titleId, name))
name = titleInfo.GetMetaTitleName();
m_name_cache.emplace(titleId, name);
return name;
}
void GameTitleLoader::registerCallback()
{
m_callbackIdTitleList = CafeTitleList::RegisterCallback(
[](CafeTitleListCallbackEvent *evt, void *ctx)
{
static_cast<GameTitleLoader *>(ctx)->HandleTitleListCallback(evt);
},
this);
}
void GameTitleLoader::HandleTitleListCallback(CafeTitleListCallbackEvent *evt)
{
if (evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_DISCOVERED || evt->eventType == CafeTitleListCallbackEvent::TYPE::TITLE_REMOVED)
{
queueTitle(evt->titleInfo->GetAppTitleId());
}
}
void GameTitleLoader::addGamePath(const fs::path &path)
{
CafeTitleList::ClearScanPaths();
CafeTitleList::AddScanPath(path);
CafeTitleList::Refresh();
}

View File

@ -0,0 +1,58 @@
#pragma once
#include "Cafe/IOSU/PDM/iosu_pdm.h"
#include "Cafe/TitleList/TitleId.h"
#include "Cafe/TitleList/TitleList.h"
struct Game
{
std::string name;
uint32 secondsPlayed;
uint16 dlc;
uint16 version;
TitleId titleId;
CafeConsoleRegion region;
};
class GameTitleLoadedCallback
{
public:
virtual void onTitleLoaded(const Game &game) = 0;
};
class GameTitleLoader
{
std::mutex m_threadMutex;
std::condition_variable m_condVar;
std::thread m_loaderThread;
std::atomic_bool m_continueLoading = true;
std::deque<TitleId> m_titlesToLoad;
std::optional<uint64> m_callbackIdTitleList;
std::map<TitleId, Game> m_gameInfos;
std::map<TitleId, std::string> m_name_cache;
std::shared_ptr<GameTitleLoadedCallback> m_gameTitleLoadedCallback = nullptr;
public:
GameTitleLoader();
void queueTitle(TitleId titleId);
void setOnTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback> &gameTitleLoadedCallback);
void reloadGameTitles();
~GameTitleLoader();
void titleRefresh(TitleId titleId);
void addGamePath(const fs::path &path);
private:
void loadGameTitles();
std::string GetNameByTitleId(uint64 titleId);
void registerCallback();
void HandleTitleListCallback(CafeTitleListCallbackEvent *evt);
};

View File

@ -0,0 +1,44 @@
#include "Image.h"
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_TGA
#include "stb_image.h"
Image::Image(Image &&image)
{
this->m_image = image.m_image;
this->m_width = image.m_width;
this->m_height = image.m_height;
this->m_channels = image.m_channels;
image.m_image = nullptr;
}
Image::Image(const std::vector<uint8> &imageBytes)
{
m_image = stbi_load_from_memory(imageBytes.data(), imageBytes.size(), &m_width, &m_height, &m_channels, STBI_rgb_alpha);
if (m_image)
{
for (int i = 0; i < m_width * m_height * 4; i += 4)
{
uint8 r = m_image[i];
uint8 g = m_image[i + 1];
uint8 b = m_image[i + 2];
uint8 a = m_image[i + 3];
m_image[i] = b;
m_image[i + 1] = g;
m_image[i + 2] = r;
m_image[i + 3] = a;
}
}
}
bool Image::isOk() const { return m_image != nullptr; }
int *Image::intColors() const { return reinterpret_cast<int *>(m_image); }
Image::~Image()
{
if (m_image)
stbi_image_free(m_image);
}

View File

@ -0,0 +1,19 @@
#pragma once
struct Image
{
uint8_t *m_image = nullptr;
int m_width = 0;
int m_height = 0;
int m_channels = 0;
Image(Image &&image);
Image(const std::vector<uint8> &imageBytes);
bool isOk() const;
int *intColors() const;
~Image();
};

View File

@ -0,0 +1,69 @@
#include "JNIUtils.h"
namespace JNIUtils
{
JavaVM *g_jvm = nullptr;
};
std::string JNIUtils::JStringToString(JNIEnv *env, jstring jstr)
{
const char *c_str = env->GetStringUTFChars(jstr, nullptr);
std::string str(c_str);
env->ReleaseStringUTFChars(jstr, c_str);
return str;
}
jobject JNIUtils::createJavaStringArrayList(JNIEnv *env, const std::vector<std::string> &strings)
{
jclass clsArrayList = env->FindClass("java/util/ArrayList");
jmethodID arrayListConstructor = env->GetMethodID(clsArrayList, "<init>", "()V");
jobject arrayListObject = env->NewObject(clsArrayList, arrayListConstructor);
jmethodID addMethod = env->GetMethodID(clsArrayList, "add", "(Ljava/lang/Object;)Z");
env->DeleteLocalRef(clsArrayList);
for (const auto &string : strings)
{
jstring element = env->NewStringUTF(string.c_str());
env->CallBooleanMethod(arrayListObject, addMethod, element);
env->DeleteLocalRef(element);
}
return arrayListObject;
}
JNIUtils::Scopedjobject JNIUtils::getEnumValue(JNIEnv *env, const std::string &enumClassName, const std::string &enumName)
{
jclass enumClass = env->FindClass(enumClassName.c_str());
jfieldID fieldID = env->GetStaticFieldID(enumClass, enumName.c_str(), ("L" + enumClassName + ";").c_str());
jobject enumValue = env->GetStaticObjectField(enumClass, fieldID);
env->DeleteLocalRef(enumClass);
Scopedjobject enumObj = Scopedjobject(enumValue);
env->DeleteLocalRef(enumValue);
return enumObj;
}
jobject JNIUtils::createArrayList(JNIEnv *env, const std::vector<jobject> &objects)
{
static Scopedjclass listClass = Scopedjclass("java/util/ArrayList");
static jmethodID listConstructor = env->GetMethodID(*listClass, "<init>", "()V");
static jmethodID listAdd = env->GetMethodID(*listClass, "add", "(Ljava/lang/Object;)Z");
jobject arrayList = env->NewObject(*listClass, listConstructor);
for (auto &&obj : objects)
env->CallBooleanMethod(arrayList, listAdd, obj);
return arrayList;
}
jobject JNIUtils::createJavaStringArrayList(JNIEnv *env, const std::vector<std::wstring> &strings)
{
jclass arrayListClass = env->FindClass("java/util/ArrayList");
jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", "()V");
jobject arrayListObject = env->NewObject(arrayListClass, arrayListConstructor);
jmethodID addMethod = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z");
for (const auto &string : strings)
{
jstring element = env->NewString((jchar *)string.c_str(), string.length());
env->CallBooleanMethod(arrayListObject, addMethod, element);
env->DeleteLocalRef(element);
}
return arrayListObject;
}

View File

@ -0,0 +1,143 @@
#pragma once
#include <android/native_window_jni.h>
#include <jni.h>
namespace JNIUtils
{
extern JavaVM *g_jvm;
std::string JStringToString(JNIEnv *env, jstring jstr);
jobject createJavaStringArrayList(JNIEnv *env, const std::vector<std::string> &stringList);
jobject createJavaStringArrayList(JNIEnv *env, const std::vector<std::wstring> &stringList);
class ScopedJNIENV
{
public:
ScopedJNIENV()
{
jint result = g_jvm->GetEnv((void **)&m_env, JNI_VERSION_1_6);
if (result == JNI_EDETACHED)
{
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6;
args.name = nullptr;
args.group = nullptr;
result = g_jvm->AttachCurrentThread(&m_env, &args);
if (result == JNI_OK)
m_threadWasAttached = true;
}
}
JNIEnv *&operator*() { return m_env; }
JNIEnv *operator->() { return m_env; }
~ScopedJNIENV()
{
if (m_threadWasAttached)
g_jvm->DetachCurrentThread();
}
private:
JNIEnv *m_env = nullptr;
bool m_threadWasAttached = false;
};
class Scopedjobject
{
public:
Scopedjobject() = default;
Scopedjobject(Scopedjobject &&other)
{
this->m_jobject = other.m_jobject;
other.m_jobject = nullptr;
}
void deleteRef()
{
if (m_jobject)
{
ScopedJNIENV()->DeleteGlobalRef(m_jobject);
m_jobject = nullptr;
}
}
Scopedjobject &operator=(Scopedjobject &&other)
{
if (this != &other)
{
deleteRef();
m_jobject = other.m_jobject;
other.m_jobject = nullptr;
}
return *this;
}
jobject &operator*() { return m_jobject; }
Scopedjobject(jobject obj)
{
if (obj)
m_jobject = ScopedJNIENV()->NewGlobalRef(obj);
}
~Scopedjobject()
{
deleteRef();
}
bool isValid() const { return m_jobject; }
private:
jobject m_jobject = nullptr;
};
class Scopedjclass
{
public:
Scopedjclass() = default;
Scopedjclass(Scopedjclass &&other)
{
this->m_jclass = other.m_jclass;
other.m_jclass = nullptr;
}
Scopedjclass &operator=(Scopedjclass &&other)
{
if (this != &other)
{
if (m_jclass)
ScopedJNIENV()->DeleteGlobalRef(m_jclass);
m_jclass = other.m_jclass;
other.m_jclass = nullptr;
}
return *this;
}
Scopedjclass(const std::string &className)
{
ScopedJNIENV scopedEnv;
jobject tempObj = scopedEnv->FindClass(className.c_str());
m_jclass = static_cast<jclass>(scopedEnv->NewGlobalRef(tempObj));
scopedEnv->DeleteLocalRef(tempObj);
}
~Scopedjclass()
{
if (m_jclass)
ScopedJNIENV()->DeleteGlobalRef(m_jclass);
}
bool isValid() const { return m_jclass; }
jclass &operator*() { return m_jclass; }
private:
jclass m_jclass = nullptr;
};
Scopedjobject getEnumValue(JNIEnv *env, const std::string &enumClassName, const std::string &enumName);
jobject createArrayList(JNIEnv *env, const std::vector<jobject> &objects);
} // namespace JNIUtils

View File

@ -0,0 +1,82 @@
#include "Utils.h"
#include "Cemu/ncrypto/ncrypto.h"
#include "config/ActiveSettings.h"
void createCemuDirectories()
{
std::wstring mlc = ActiveSettings::GetMlcPath().generic_wstring();
// create sys/usr folder in mlc01
try
{
const auto sysFolder = fs::path(mlc).append(L"sys");
fs::create_directories(sysFolder);
const auto usrFolder = fs::path(mlc).append(L"usr");
fs::create_directories(usrFolder);
fs::create_directories(fs::path(usrFolder).append("title/00050000")); // base
fs::create_directories(fs::path(usrFolder).append("title/0005000c")); // dlc
fs::create_directories(fs::path(usrFolder).append("title/0005000e")); // update
// Mii Maker save folders {0x500101004A000, 0x500101004A100, 0x500101004A200},
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a000/user/common/db"));
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a100/user/common/db"));
fs::create_directories(fs::path(mlc).append(L"usr/save/00050010/1004a200/user/common/db"));
// lang files
auto langDir = fs::path(mlc).append(L"sys/title/0005001b/1005c000/content");
fs::create_directories(langDir);
auto langFile = fs::path(langDir).append("language.txt");
if (!fs::exists(langFile))
{
std::ofstream file(langFile);
if (file.is_open())
{
const char *langStrings[] = {"ja", "en", "fr", "de", "it", "es", "zh", "ko", "nl", "pt", "ru", "zh"};
for (const char *lang : langStrings)
file << fmt::format(R"("{}",)", lang) << std::endl;
file.flush();
file.close();
}
}
auto countryFile = fs::path(langDir).append("country.txt");
if (!fs::exists(countryFile))
{
std::ofstream file(countryFile);
for (sint32 i = 0; i < 201; i++)
{
const char *countryCode = NCrypto::GetCountryAsString(i);
if (boost::iequals(countryCode, "NN"))
file << "NULL," << std::endl;
else
file << fmt::format(R"("{}",)", countryCode) << std::endl;
}
file.flush();
file.close();
}
}
catch (const std::exception &ex)
{
exit(0);
}
// cemu directories
try
{
const auto controllerProfileFolder = ActiveSettings::GetConfigPath(L"controllerProfiles").generic_wstring();
if (!fs::exists(controllerProfileFolder))
fs::create_directories(controllerProfileFolder);
const auto memorySearcherFolder = ActiveSettings::GetUserDataPath(L"memorySearcher").generic_wstring();
if (!fs::exists(memorySearcherFolder))
fs::create_directories(memorySearcherFolder);
}
catch (const std::exception &ex)
{
exit(0);
}
}

View File

@ -0,0 +1,6 @@
#pragma once
#include "config/ActiveSettings.h"
#include "Cemu/ncrypto/ncrypto.h"
void createCemuDirectories();

View File

@ -0,0 +1,294 @@
#include "AndroidGameIconLoadedCallback.h"
#include "AndroidGameTitleLoadedCallback.h"
#include "EmulationState.h"
#include "JNIUtils.h"
EmulationState s_emulationState;
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, [[maybe_unused]] void *reserved)
{
JNIUtils::g_jvm = vm;
return JNI_VERSION_1_6;
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setSurface(JNIEnv *env, [[maybe_unused]] jclass clazz, jobject surface, jboolean is_main_canvas)
{
s_emulationState.setSurface(env, surface, is_main_canvas);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setSurfaceSize([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jint width, jint height, jboolean is_main_canvas)
{
s_emulationState.setSurfaceSize(width, height, is_main_canvas);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_startGame([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jlong title_id)
{
s_emulationState.startGame(*reinterpret_cast<TitleId *>(&title_id));
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setGameTitleLoadedCallback(JNIEnv *env, [[maybe_unused]] jclass clazz, jobject game_title_loaded_callback)
{
jclass gameTitleLoadedCallbackClass = env->GetObjectClass(game_title_loaded_callback);
jmethodID onGameTitleLoadedMID = env->GetMethodID(gameTitleLoadedCallbackClass, "onGameTitleLoaded", "(JLjava/lang/String;)V");
env->DeleteLocalRef(gameTitleLoadedCallbackClass);
s_emulationState.setOnGameTitleLoaded(std::make_shared<AndroidGameTitleLoadedCallback>(onGameTitleLoadedMID, game_title_loaded_callback));
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setGameIconLoadedCallback(JNIEnv *env, [[maybe_unused]] jclass clazz, jobject game_icon_loaded_callback)
{
jclass gameIconLoadedCallbackClass = env->GetObjectClass(game_icon_loaded_callback);
jmethodID gameIconLoadedMID = env->GetMethodID(gameIconLoadedCallbackClass, "onGameIconLoaded", "(J[III)V");
s_emulationState.setOnGameIconLoaded(std::make_shared<AndroidGameIconLoadedCallback>(gameIconLoadedMID, game_icon_loaded_callback));
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_requestGameIcon([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jlong title_id)
{
s_emulationState.requestGameIcon(*reinterpret_cast<TitleId *>(&title_id));
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_reloadGameTitles([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz)
{
s_emulationState.reloadGameTitles();
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_initializeActiveSettings(JNIEnv *env, [[maybe_unused]] jclass clazz, jstring data_path, jstring cache_path)
{
std::string dataPath = JNIUtils::JStringToString(env, data_path);
std::string cachePath = JNIUtils::JStringToString(env, cache_path);
s_emulationState.initializeActiveSettings(dataPath, cachePath);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_initializeEmulation([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz)
{
s_emulationState.initializeEmulation();
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_initializerRenderer([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz)
{
s_emulationState.initializeRenderer();
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_initializeRendererSurface([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean is_main_canvas)
{
s_emulationState.initializeRenderSurface(is_main_canvas);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setDPI([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jfloat dpi)
{
s_emulationState.setDPI(dpi);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_clearSurface([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean is_main_canvas)
{
s_emulationState.clearSurface(is_main_canvas);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_recreateRenderSurface([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean is_main_canvas)
{
s_emulationState.notifySurfaceChanged(is_main_canvas);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_addGamePath(JNIEnv *env, [[maybe_unused]] jclass clazz, jstring uri)
{
s_emulationState.addGamePath(JNIUtils::JStringToString(env, uri));
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onNativeKey(JNIEnv *env, [[maybe_unused]] jclass clazz, jstring device_descriptor, jstring device_name, jint key, jboolean is_pressed)
{
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::JStringToString(env, device_name);
s_emulationState.onKeyEvent(deviceDescriptor, deviceName, key, is_pressed);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onNativeAxis(JNIEnv *env, [[maybe_unused]] jclass clazz, jstring device_descriptor, jstring device_name, jint axis, jfloat value)
{
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::JStringToString(env, device_name);
s_emulationState.onAxisEvent(deviceDescriptor, deviceName, axis, value);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setControllerType([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jint index, jint emulated_controller_type)
{
s_emulationState.setEmulatedControllerType(index, static_cast<EmulatedController::Type>(emulated_controller_type));
}
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getControllerType([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jint index)
{
return s_emulationState.getEmulatedControllerType(index);
}
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getWPADControllersCount([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz)
{
return s_emulationState.getWPADControllersCount();
}
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getVPADControllersCount([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz)
{
return s_emulationState.getVPADControllersCount();
}
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
Java_info_cemu_Cemu_NativeLibrary_isControllerDisabled([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jint index)
{
return s_emulationState.isEmulatedControllerDisabled(index);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setControllerMapping(JNIEnv *env, [[maybe_unused]] jclass clazz, jstring device_descriptor, jstring device_name, jint index, jint mapping_id, jint button_id)
{
auto deviceName = JNIUtils::JStringToString(env, device_name);
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
s_emulationState.setControllerMapping(deviceDescriptor, deviceName, index, mapping_id, button_id);
}
extern "C" [[maybe_unused]] JNIEXPORT jstring JNICALL
Java_info_cemu_Cemu_NativeLibrary_getControllerMapping(JNIEnv *env, [[maybe_unused]] jclass clazz, jint index, jint mapping_id)
{
auto mapping = s_emulationState.getEmulatedControllerMapping(index, mapping_id);
return env->NewStringUTF(mapping.value_or("").c_str());
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_clearControllerMapping([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jint index, jint mapping_id)
{
s_emulationState.clearEmulatedControllerMapping(index, mapping_id);
}
extern "C" [[maybe_unused]] JNIEXPORT jobject JNICALL
Java_info_cemu_Cemu_NativeLibrary_getControllerMappings(JNIEnv *env, [[maybe_unused]] jclass clazz, jint index)
{
jclass hashMapClass = env->FindClass("java/util/HashMap");
jmethodID hashMapConstructor = env->GetMethodID(hashMapClass, "<init>", "()V");
jmethodID hashMapPut = env->GetMethodID(hashMapClass, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
jclass integerClass = env->FindClass("java/lang/Integer");
jmethodID integerConstructor = env->GetMethodID(integerClass, "<init>", "(I)V");
jobject hashMapObj = env->NewObject(hashMapClass, hashMapConstructor);
auto mappings = s_emulationState.getEmulatedControllerMappings(index);
for (const auto &pair : mappings)
{
jint key = pair.first;
jstring buttonName = env->NewStringUTF(pair.second.c_str());
jobject mappingId = env->NewObject(integerClass, integerConstructor, key);
env->CallObjectMethod(hashMapObj, hashMapPut, mappingId, buttonName);
}
return hashMapObj;
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onKeyEvent(JNIEnv *env, [[maybe_unused]] jclass clazz, jstring device_descriptor, jstring device_name, jint key_code, jboolean is_pressed)
{
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::JStringToString(env, device_name);
s_emulationState.onKeyEvent(deviceDescriptor, deviceName, key_code, is_pressed);
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_onAxisEvent([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jstring device_descriptor, jstring device_name, jint axis_code, jfloat value)
{
auto deviceDescriptor = JNIUtils::JStringToString(env, device_descriptor);
auto deviceName = JNIUtils::JStringToString(env, device_name);
s_emulationState.onAxisEvent(deviceDescriptor, deviceName, axis_code, value);
}
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAsyncShaderCompile([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz)
{
return g_config.data().async_compile;
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAsyncShaderCompile([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean enabled)
{
g_config.data().async_compile = enabled;
g_config.Save();
}
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getVSyncMode([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz)
{
return g_config.data().vsync;
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setVSyncMode([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jint vsync_mode)
{
g_config.data().vsync = vsync_mode;
g_config.Save();
}
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAccurateBarriers([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz)
{
return g_config.data().vk_accurate_barriers;
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAccurateBarriers([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean enabled)
{
g_config.data().vk_accurate_barriers = enabled;
g_config.Save();
}
extern "C" [[maybe_unused]] JNIEXPORT jboolean JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAudioDeviceEnabled([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean tv)
{
const auto &device = tv ? g_config.data().tv_device : g_config.data().pad_device;
return !device.empty();
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAudioDeviceEnabled([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean enabled, jboolean tv)
{
auto &device = tv ? g_config.data().tv_device : g_config.data().pad_device;
if (enabled)
device = L"Default";
else
device.clear();
g_config.Save();
}
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAudioDeviceChannels([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean tv)
{
const auto &deviceChannels = tv ? g_config.data().tv_channels : g_config.data().pad_channels;
return deviceChannels;
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAudioDeviceChannels([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jint channels, jboolean tv)
{
auto &deviceChannels = tv ? g_config.data().tv_channels : g_config.data().pad_channels;
deviceChannels = static_cast<AudioChannels>(channels);
g_config.Save();
}
extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL
Java_info_cemu_Cemu_NativeLibrary_getAudioDeviceVolume([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jboolean tv)
{
const auto &deviceVolume = tv ? g_config.data().tv_volume : g_config.data().pad_volume;
return deviceVolume;
}
extern "C" [[maybe_unused]] JNIEXPORT void JNICALL
Java_info_cemu_Cemu_NativeLibrary_setAudioDeviceVolume([[maybe_unused]] JNIEnv *env, [[maybe_unused]] jclass clazz, jint volume, jboolean tv)
{
auto &deviceVolume = tv ? g_config.data().tv_volume : g_config.data().pad_volume;
deviceVolume = volume;
g_config.Save();
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
package info.cemu.Cemu;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import info.cemu.Cemu.databinding.FragmentAudioSettingsBinding;
public class AudioSettingsFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentAudioSettingsBinding binding = FragmentAudioSettingsBinding.inflate(inflater, container, false);
GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
CheckboxRecyclerViewItem tvDeviceCheckbox = new CheckboxRecyclerViewItem(getString(R.string.tv),
getString(R.string.tv_audio_description), NativeLibrary.getAudioDeviceEnabled(true),
checked -> NativeLibrary.setAudioDeviceEnabled(checked, true));
genericRecyclerViewAdapter.addRecyclerViewItem(tvDeviceCheckbox);
var tvChannelsChoices = Stream.of(NativeLibrary.AUDIO_CHANNELS_MONO, NativeLibrary.AUDIO_CHANNELS_STEREO, NativeLibrary.AUDIO_CHANNELS_SURROUND)
.map(channels -> new SelectionAdapter.ChoiceItem<>(NativeLibrary.channelsToResourceNameId(channels), channels))
.collect(Collectors.toList());
int tvChannels = NativeLibrary.getAudioDeviceChannels(true);
SelectionAdapter<Integer> tvChannelsSelectionAdapter = new SelectionAdapter<>(tvChannelsChoices, tvChannels);
SingleSelectionRecyclerViewItem<Integer> tvChannelsModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.tv_channels),
getString(NativeLibrary.channelsToResourceNameId(tvChannels)), tvChannelsSelectionAdapter,
(channels, selectionRecyclerViewItem) -> {
NativeLibrary.setAudioDeviceChannels(channels, true);
selectionRecyclerViewItem.setDescription(getString(NativeLibrary.channelsToResourceNameId(channels)));
});
genericRecyclerViewAdapter.addRecyclerViewItem(tvChannelsModeSelection);
SliderRecyclerViewItem tvVolumeSlider = new SliderRecyclerViewItem(getString(R.string.tv_volume),
NativeLibrary.AUDIO_MIN_VOLUME,
NativeLibrary.AUDIO_MAX_VOLUME,
NativeLibrary.getAudioDeviceVolume(true),
value -> NativeLibrary.setAudioDeviceVolume((int) value, true),
value -> (int) value + "%");
genericRecyclerViewAdapter.addRecyclerViewItem(tvVolumeSlider);
CheckboxRecyclerViewItem padDeviceCheckbox = new CheckboxRecyclerViewItem(getString(R.string.gamepad),
getString(R.string.gamepad_audio_description), NativeLibrary.getAudioDeviceEnabled(true),
checked -> NativeLibrary.setAudioDeviceEnabled(checked, true));
genericRecyclerViewAdapter.addRecyclerViewItem(padDeviceCheckbox);
var gamepadChannelsChoices = List.of(new SelectionAdapter.ChoiceItem<>(NativeLibrary.channelsToResourceNameId(NativeLibrary.AUDIO_CHANNELS_STEREO), NativeLibrary.AUDIO_CHANNELS_STEREO));
int gamepadChannels = NativeLibrary.getAudioDeviceChannels(false);
SelectionAdapter<Integer> gamepadChannelsSelectionAdapter = new SelectionAdapter<>(gamepadChannelsChoices, gamepadChannels);
SingleSelectionRecyclerViewItem<Integer> gamepadChannelsModeSelection = new SingleSelectionRecyclerViewItem<>(getString(R.string.gamepad_channels),
getString(NativeLibrary.channelsToResourceNameId(gamepadChannels)), gamepadChannelsSelectionAdapter,
(channels, selectionRecyclerViewItem) -> {
NativeLibrary.setAudioDeviceChannels(channels, false);
selectionRecyclerViewItem.setDescription(getString(NativeLibrary.channelsToResourceNameId(channels)));
});
genericRecyclerViewAdapter.addRecyclerViewItem(gamepadChannelsModeSelection);
SliderRecyclerViewItem padVolumeSlider = new SliderRecyclerViewItem(getString(R.string.pad_volume),
NativeLibrary.AUDIO_MIN_VOLUME,
NativeLibrary.AUDIO_MAX_VOLUME,
NativeLibrary.getAudioDeviceVolume(false),
value -> NativeLibrary.setAudioDeviceVolume((int) value, false),
value -> (int) value + "%");
genericRecyclerViewAdapter.addRecyclerViewItem(padVolumeSlider);
binding.audioSettingsRecyclerView.setAdapter(genericRecyclerViewAdapter);
return binding.getRoot();
}
}

View File

@ -0,0 +1,51 @@
package info.cemu.Cemu;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
public class ButtonRecyclerViewItem implements RecyclerViewItem {
public interface OnButtonClickListener {
void onButtonClick();
}
private static class ButtonViewHolder extends RecyclerView.ViewHolder {
TextView text;
TextView description;
public ButtonViewHolder(View itemView) {
super(itemView);
text = itemView.findViewById(R.id.button_text);
description = itemView.findViewById(R.id.button_description);
}
}
private final OnButtonClickListener onButtonClickListener;
private final String text;
private final String description;
public ButtonRecyclerViewItem(String text, String description, OnButtonClickListener onButtonClickListener) {
this.text = text;
this.description = description;
this.onButtonClickListener = onButtonClickListener;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new ButtonViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_button, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
ButtonViewHolder buttonViewHolder = (ButtonViewHolder) viewHolder;
buttonViewHolder.text.setText(text);
buttonViewHolder.description.setText(description);
buttonViewHolder.itemView.setOnClickListener(view -> {
if (onButtonClickListener != null)
onButtonClickListener.onButtonClick();
});
}
}

View File

@ -0,0 +1,16 @@
package info.cemu.Cemu;
import android.app.Application;
import android.util.DisplayMetrics;
public class CemuApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
NativeLibrary.setDPI(displayMetrics.density);
FileUtil.setCemuApplication(this);
NativeLibrary.initializeActiveSettings(getExternalFilesDir(null).getAbsoluteFile().toString(), getExternalFilesDir(null).getAbsoluteFile().toString());
NativeLibrary.initializeEmulation();
}
}

View File

@ -0,0 +1,6 @@
package info.cemu.Cemu;
public class CemuPreferences {
public static final String PREFERENCES_NAME = "CEMU_PREFERENCES";
public static final String GAMES_PATH_KEY = "GAME_PATHS_KEY";
}

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