mirror of
https://github.com/SSimco/Cemu.git
synced 2024-11-23 05:19:40 +00:00
Merge branch 'android' into android-2
This commit is contained in:
commit
fda05a5a00
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -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
|
||||
|
@ -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
99
cmake/vcpkg_android.cmake
Normal 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)
|
2
dependencies/ZArchive
vendored
2
dependencies/ZArchive
vendored
@ -1 +1 @@
|
||||
Subproject commit d2c717730092c7bf8cbb033b12fd4001b7c4d932
|
||||
Subproject commit c50b5d222bb599be9287664c835c2a820aa5342a
|
7
dependencies/ih264d/decoder/ih264d_dpb_mgr.c
vendored
7
dependencies/ih264d/decoder/ih264d_dpb_mgr.c
vendored
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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 )
|
||||
|
@ -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);
|
||||
}
|
||||
|
235
src/Cafe/Filesystem/fscDeviceAndroidSAF.cpp
Normal file
235
src/Cafe/Filesystem/fscDeviceAndroidSAF.cpp
Normal file
@ -0,0 +1,235 @@
|
||||
#include "Cafe/Filesystem/fscDeviceAndroidSAF.h"
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "Cafe/Filesystem/fsc.h"
|
||||
#include "Common/FileStream.h"
|
||||
#include "Common/unix/FilesystemAndroid.h"
|
||||
|
||||
FSCVirtualFile_AndroidSAF::~FSCVirtualFile_AndroidSAF()
|
||||
{
|
||||
if (m_type == FSC_TYPE_FILE)
|
||||
delete m_fs;
|
||||
}
|
||||
|
||||
sint32 FSCVirtualFile_AndroidSAF::fscGetType()
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
uint32 FSCVirtualFile_AndroidSAF::fscDeviceAndroidSAFFSFile_getFileSize()
|
||||
{
|
||||
if (m_type == FSC_TYPE_FILE)
|
||||
{
|
||||
if (m_fileSize > 0xFFFFFFFFULL)
|
||||
cemu_assert_suspicious(); // files larger than 4GB are not supported by Wii U filesystem
|
||||
return (uint32)m_fileSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64 FSCVirtualFile_AndroidSAF::fscQueryValueU64(uint32 id)
|
||||
{
|
||||
if (m_type == FSC_TYPE_FILE)
|
||||
{
|
||||
if (id == FSC_QUERY_SIZE)
|
||||
return fscDeviceAndroidSAFFSFile_getFileSize();
|
||||
else if (id == FSC_QUERY_WRITEABLE)
|
||||
return m_isWritable;
|
||||
else
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
else if (m_type == FSC_TYPE_DIRECTORY)
|
||||
{
|
||||
if (id == FSC_QUERY_SIZE)
|
||||
return fscDeviceAndroidSAFFSFile_getFileSize();
|
||||
else
|
||||
cemu_assert_unimplemented();
|
||||
}
|
||||
cemu_assert_unimplemented();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 FSCVirtualFile_AndroidSAF::fscWriteData(void* buffer, uint32 size)
|
||||
{
|
||||
throw std::logic_error("write not supported with SAF");
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 FSCVirtualFile_AndroidSAF::fscReadData(void* buffer, uint32 size)
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
if (size >= (2UL * 1024UL * 1024UL * 1024UL))
|
||||
{
|
||||
cemu_assert_suspicious();
|
||||
return 0;
|
||||
}
|
||||
uint32 bytesLeft = (uint32)(m_fileSize - m_seek);
|
||||
bytesLeft = std::min(bytesLeft, 0x7FFFFFFFu);
|
||||
sint32 bytesToRead = std::min(bytesLeft, size);
|
||||
uint32 bytesRead = m_fs->readData(buffer, bytesToRead);
|
||||
m_seek += bytesRead;
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
void FSCVirtualFile_AndroidSAF::fscSetSeek(uint64 seek)
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return;
|
||||
this->m_seek = seek;
|
||||
cemu_assert_debug(seek <= m_fileSize);
|
||||
m_fs->SetPosition(seek);
|
||||
}
|
||||
|
||||
uint64 FSCVirtualFile_AndroidSAF::fscGetSeek()
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return 0;
|
||||
return m_seek;
|
||||
}
|
||||
|
||||
void FSCVirtualFile_AndroidSAF::fscSetFileLength(uint64 endOffset)
|
||||
{
|
||||
if (m_type != FSC_TYPE_FILE)
|
||||
return;
|
||||
m_fs->SetPosition(endOffset);
|
||||
bool r = m_fs->SetEndOfFile();
|
||||
m_seek = std::min(m_seek, endOffset);
|
||||
m_fileSize = m_seek;
|
||||
m_fs->SetPosition(m_seek);
|
||||
if (!r)
|
||||
cemuLog_log(LogType::Force, "fscSetFileLength: Failed to set size to 0x{:x}", endOffset);
|
||||
}
|
||||
|
||||
bool FSCVirtualFile_AndroidSAF::fscDirNext(FSCDirEntry* dirEntry)
|
||||
{
|
||||
if (m_type != FSC_TYPE_DIRECTORY)
|
||||
return false;
|
||||
|
||||
if (!m_files)
|
||||
{
|
||||
// init iterator on first iteration attempt
|
||||
m_files = std::make_unique<std::vector<fs::path>>(FilesystemAndroid::listFiles(*m_path));
|
||||
m_filesIterator = m_files->begin();
|
||||
if (!m_files)
|
||||
{
|
||||
cemuLog_log(LogType::Force, "Failed to iterate directory: {}", _pathToUtf8(*m_path));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (m_filesIterator == m_files->end())
|
||||
return false;
|
||||
|
||||
const fs::path& file = *m_filesIterator;
|
||||
|
||||
std::string fileName = file.filename().generic_string();
|
||||
if (fileName.size() >= sizeof(dirEntry->path) - 1)
|
||||
fileName.resize(sizeof(dirEntry->path) - 1);
|
||||
strncpy(dirEntry->path, fileName.data(), sizeof(dirEntry->path));
|
||||
if (FilesystemAndroid::isDirectory(file))
|
||||
{
|
||||
dirEntry->isDirectory = true;
|
||||
dirEntry->isFile = false;
|
||||
dirEntry->fileSize = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
dirEntry->isDirectory = false;
|
||||
dirEntry->isFile = true;
|
||||
dirEntry->fileSize = 0;
|
||||
auto fs = FileStream::openFile2(file);
|
||||
if (fs)
|
||||
{
|
||||
dirEntry->fileSize = fs->GetSize();
|
||||
delete fs;
|
||||
}
|
||||
}
|
||||
m_filesIterator++;
|
||||
return true;
|
||||
}
|
||||
|
||||
FSCVirtualFile* FSCVirtualFile_AndroidSAF::OpenFile(const fs::path& path, FSC_ACCESS_FLAG accessFlags, sint32& fscStatus)
|
||||
{
|
||||
if (!HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE) && !HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||
cemu_assert_debug(false); // not allowed. At least one of both flags must be set
|
||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::WRITE_PERMISSION) ||
|
||||
HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALLOW_CREATE) ||
|
||||
HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::FILE_ALWAYS_CREATE))
|
||||
throw std::logic_error("writing and creating a file is not supported with SAF");
|
||||
// attempt to open as file
|
||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_FILE))
|
||||
{
|
||||
FileStream* fs = FileStream::openFile2(path);
|
||||
if (fs)
|
||||
{
|
||||
FSCVirtualFile_AndroidSAF* vf = new FSCVirtualFile_AndroidSAF(FSC_TYPE_FILE);
|
||||
vf->m_fs = fs;
|
||||
vf->m_isWritable = false;
|
||||
vf->m_fileSize = fs->GetSize();
|
||||
fscStatus = FSC_STATUS_OK;
|
||||
return vf;
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to open as directory
|
||||
if (HAS_FLAG(accessFlags, FSC_ACCESS_FLAG::OPEN_DIR))
|
||||
{
|
||||
bool isExistingDir = FilesystemAndroid::exists(path);
|
||||
if (isExistingDir)
|
||||
{
|
||||
FSCVirtualFile_AndroidSAF* vf = new FSCVirtualFile_AndroidSAF(FSC_TYPE_DIRECTORY);
|
||||
vf->m_path.reset(new std::filesystem::path(path));
|
||||
fscStatus = FSC_STATUS_OK;
|
||||
return vf;
|
||||
}
|
||||
}
|
||||
fscStatus = FSC_STATUS_FILE_NOT_FOUND;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Device implementation */
|
||||
|
||||
class fscDeviceAndroidSAFFSC : public fscDeviceC
|
||||
{
|
||||
public:
|
||||
FSCVirtualFile* fscDeviceOpenByPath(std::string_view path, FSC_ACCESS_FLAG accessFlags, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
*fscStatus = FSC_STATUS_OK;
|
||||
FSCVirtualFile* vf = FSCVirtualFile_AndroidSAF::OpenFile(_utf8ToPath(path), accessFlags, *fscStatus);
|
||||
cemu_assert_debug((bool)vf == (*fscStatus == FSC_STATUS_OK));
|
||||
return vf;
|
||||
}
|
||||
|
||||
bool fscDeviceCreateDir(std::string_view path, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
throw std::logic_error("creating a directory is not supported with SAF");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fscDeviceRemoveFileOrDir(std::string_view path, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
throw std::logic_error("removing a file or dir is not supported with SAF");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool fscDeviceRename(std::string_view srcPath, std::string_view dstPath, void* ctx, sint32* fscStatus) override
|
||||
{
|
||||
throw std::logic_error("renaming not supported with SAF");
|
||||
return false;
|
||||
}
|
||||
|
||||
// singleton
|
||||
public:
|
||||
static fscDeviceAndroidSAFFSC& instance()
|
||||
{
|
||||
static fscDeviceAndroidSAFFSC _instance;
|
||||
return _instance;
|
||||
}
|
||||
};
|
||||
|
||||
bool FSCDeviceAndroidSAF_Mount(std::string_view mountPath, std::string_view hostTargetPath, sint32 priority)
|
||||
{
|
||||
return fsc_mount(mountPath, hostTargetPath, &fscDeviceAndroidSAFFSC::instance(), nullptr, priority) == FSC_STATUS_OK;
|
||||
}
|
36
src/Cafe/Filesystem/fscDeviceAndroidSAF.h
Normal file
36
src/Cafe/Filesystem/fscDeviceAndroidSAF.h
Normal 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;
|
||||
};
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
5
src/Cafe/HW/Latte/Renderer/OpenGL/GLCanvas.h
Normal file
5
src/Cafe/HW/Latte/Renderer/OpenGL/GLCanvas.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
bool GLCanvas_MakeCurrent(bool padView);
|
||||
void GLCanvas_SwapBuffers(bool swapTV, bool swapDRC);
|
||||
bool GLCanvas_HasPadViewOpen();
|
@ -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*/)
|
||||
|
@ -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{};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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{};
|
||||
|
@ -1,5 +1,3 @@
|
||||
#include "gui/MainWindow.h"
|
||||
|
||||
#if BOOST_OS_WINDOWS
|
||||
|
||||
#include <Windows.h>
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include "iosu_ioctl.h"
|
||||
|
||||
#include "Cafe/OS/libs/nn_common.h"
|
||||
#include "gui/CemuApp.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
97
src/Cemu/GuiSystem/GuiSystem.cpp
Normal file
97
src/Cemu/GuiSystem/GuiSystem.cpp
Normal 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
|
115
src/Cemu/GuiSystem/GuiSystem.h
Normal file
115
src/Cemu/GuiSystem/GuiSystem.h
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
155
src/Common/FileStream.cpp
Normal 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
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
26
src/Common/unix/ContentUriIStream.h
Normal file
26
src/Common/unix/ContentUriIStream.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <boost/iostreams/device/file_descriptor.hpp>
|
||||
#include <boost/iostreams/stream_buffer.hpp>
|
||||
|
||||
#include "Common/unix/FilesystemAndroid.h"
|
||||
|
||||
class ContentUriIStream : public std::istream
|
||||
{
|
||||
public:
|
||||
ContentUriIStream(const std::filesystem::path& path)
|
||||
: m_fd(FilesystemAndroid::openContentUri(path)),
|
||||
m_fileDescriptorStreamBuffer(m_fd, boost::iostreams::close_handle),
|
||||
std::istream(&m_fileDescriptorStreamBuffer) {}
|
||||
|
||||
virtual ~ContentUriIStream() = default;
|
||||
|
||||
inline bool isOpen() const
|
||||
{
|
||||
return m_fd != -1;
|
||||
}
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_source> m_fileDescriptorStreamBuffer;
|
||||
};
|
122
src/Common/unix/FileStream_contentUri.cpp
Normal file
122
src/Common/unix/FileStream_contentUri.cpp
Normal file
@ -0,0 +1,122 @@
|
||||
#include "FileStream_contentUri.h"
|
||||
|
||||
#include "unix/ContentUriIStream.h"
|
||||
|
||||
void ThrowWriteNotSupportedError()
|
||||
{
|
||||
throw std::logic_error("write operation not supported");
|
||||
}
|
||||
|
||||
void FileStreamContentUri::SetPosition(uint64 pos)
|
||||
{
|
||||
if (!m_isValid)
|
||||
return;
|
||||
m_contentUriIStream.seekg((std::streampos)pos);
|
||||
}
|
||||
|
||||
uint64 FileStreamContentUri::GetSize()
|
||||
{
|
||||
cemu_assert(m_isValid);
|
||||
auto currentPos = m_contentUriIStream.tellg();
|
||||
m_contentUriIStream.seekg(0, std::ios::end);
|
||||
auto fileSize = m_contentUriIStream.tellg();
|
||||
m_contentUriIStream.seekg(currentPos, std::ios::beg);
|
||||
uint64 fs = (uint64)fileSize;
|
||||
return fs;
|
||||
}
|
||||
|
||||
bool FileStreamContentUri::SetEndOfFile()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FileStreamContentUri::extract(std::vector<uint8>& data)
|
||||
{
|
||||
if (!m_isValid)
|
||||
return;
|
||||
uint64 fileSize = GetSize();
|
||||
SetPosition(0);
|
||||
data.resize(fileSize);
|
||||
readData(data.data(), fileSize);
|
||||
}
|
||||
|
||||
uint32 FileStreamContentUri::readData(void* data, uint32 length)
|
||||
{
|
||||
m_contentUriIStream.read((char*)data, length);
|
||||
size_t bytesRead = m_contentUriIStream.gcount();
|
||||
return (uint32)bytesRead;
|
||||
}
|
||||
|
||||
bool FileStreamContentUri::readU64(uint64& v)
|
||||
{
|
||||
return readData(&v, sizeof(uint64)) == sizeof(uint64);
|
||||
}
|
||||
|
||||
bool FileStreamContentUri::readU32(uint32& v)
|
||||
{
|
||||
return readData(&v, sizeof(uint32)) == sizeof(uint32);
|
||||
}
|
||||
|
||||
bool FileStreamContentUri::readU8(uint8& v)
|
||||
{
|
||||
return readData(&v, sizeof(uint8)) == sizeof(uint8);
|
||||
}
|
||||
|
||||
bool FileStreamContentUri::readLine(std::string& line)
|
||||
{
|
||||
line.clear();
|
||||
uint8 c;
|
||||
bool isEOF = true;
|
||||
while (readU8(c))
|
||||
{
|
||||
isEOF = false;
|
||||
if (c == '\r')
|
||||
continue;
|
||||
if (c == '\n')
|
||||
break;
|
||||
line.push_back((char)c);
|
||||
}
|
||||
return !isEOF;
|
||||
}
|
||||
|
||||
sint32 FileStreamContentUri::writeData(const void* data, sint32 length)
|
||||
{
|
||||
ThrowWriteNotSupportedError();
|
||||
return -1;
|
||||
}
|
||||
|
||||
void FileStreamContentUri::writeU64(uint64 v)
|
||||
{
|
||||
ThrowWriteNotSupportedError();
|
||||
}
|
||||
|
||||
void FileStreamContentUri::writeU32(uint32 v)
|
||||
{
|
||||
ThrowWriteNotSupportedError();
|
||||
}
|
||||
|
||||
void FileStreamContentUri::writeU8(uint8 v)
|
||||
{
|
||||
ThrowWriteNotSupportedError();
|
||||
}
|
||||
|
||||
void FileStreamContentUri::writeStringFmt(const char* format, ...)
|
||||
{
|
||||
ThrowWriteNotSupportedError();
|
||||
}
|
||||
|
||||
void FileStreamContentUri::writeString(const char* str)
|
||||
{
|
||||
ThrowWriteNotSupportedError();
|
||||
}
|
||||
|
||||
void FileStreamContentUri::writeLine(const char* str)
|
||||
{
|
||||
ThrowWriteNotSupportedError();
|
||||
}
|
||||
|
||||
FileStreamContentUri::FileStreamContentUri(const std::string& uri)
|
||||
: m_contentUriIStream(uri)
|
||||
{
|
||||
m_isValid = m_contentUriIStream.isOpen();
|
||||
}
|
37
src/Common/unix/FileStream_contentUri.h
Normal file
37
src/Common/unix/FileStream_contentUri.h
Normal 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;
|
||||
};
|
@ -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)
|
||||
|
@ -1,56 +1,44 @@
|
||||
#pragma once
|
||||
#include "Common/precompiled.h"
|
||||
#include "Common/FileStream.h"
|
||||
|
||||
class FileStream
|
||||
class FileStreamUnix : public FileStream
|
||||
{
|
||||
public:
|
||||
static FileStream* openFile(std::string_view path);
|
||||
static FileStream* openFile(const wchar_t* path, bool allowWrite = false);
|
||||
static FileStream* openFile2(const fs::path& path, bool allowWrite = false);
|
||||
public:
|
||||
// size and seek
|
||||
void SetPosition(uint64 pos) override;
|
||||
|
||||
static FileStream* createFile(const wchar_t* path);
|
||||
static FileStream* createFile(std::string_view path);
|
||||
static FileStream* createFile2(const fs::path& path);
|
||||
uint64 GetSize() override;
|
||||
bool SetEndOfFile() override;
|
||||
void extract(std::vector<uint8>& data) override;
|
||||
|
||||
// helper function to load a file into memory
|
||||
static std::optional<std::vector<uint8>> LoadIntoMemory(const fs::path& path);
|
||||
// reading
|
||||
uint32 readData(void* data, uint32 length) override;
|
||||
bool readU64(uint64& v) override;
|
||||
bool readU32(uint32& v) override;
|
||||
bool readU8(uint8& v) override;
|
||||
bool readLine(std::string& line) override;
|
||||
|
||||
// size and seek
|
||||
void SetPosition(uint64 pos);
|
||||
// writing (binary)
|
||||
sint32 writeData(const void* data, sint32 length) override;
|
||||
void writeU64(uint64 v) override;
|
||||
void writeU32(uint32 v) override;
|
||||
void writeU8(uint8 v) override;
|
||||
|
||||
uint64 GetSize();
|
||||
bool SetEndOfFile();
|
||||
void extract(std::vector<uint8>& data);
|
||||
// writing (strings)
|
||||
void writeStringFmt(const char* format, ...) override;
|
||||
void writeString(const char* str) override;
|
||||
void writeLine(const char* str) override;
|
||||
|
||||
// reading
|
||||
uint32 readData(void* data, uint32 length);
|
||||
bool readU64(uint64& v);
|
||||
bool readU32(uint32& v);
|
||||
bool readU16(uint16& v);
|
||||
bool readU8(uint8& v);
|
||||
bool readLine(std::string& line);
|
||||
virtual ~FileStreamUnix();
|
||||
FileStreamUnix() = default;
|
||||
|
||||
// writing (binary)
|
||||
sint32 writeData(const void* data, sint32 length);
|
||||
void writeU64(uint64 v);
|
||||
void writeU32(uint32 v);
|
||||
void writeU16(uint16 v);
|
||||
void writeU8(uint8 v);
|
||||
|
||||
// writing (strings)
|
||||
void writeStringFmt(const char* format, ...);
|
||||
void writeString(const char* str);
|
||||
void writeLine(const char* str);
|
||||
|
||||
~FileStream();
|
||||
FileStream() {};
|
||||
|
||||
private:
|
||||
void SyncReadWriteSeek(bool nextOpIsWrite);
|
||||
FileStream(const fs::path& path, bool isOpen, bool isWriteable);
|
||||
|
||||
bool m_isValid{};
|
||||
std::fstream m_fileStream;
|
||||
bool m_prevOperationWasWrite{false};
|
||||
private:
|
||||
friend class FileStream;
|
||||
void SyncReadWriteSeek(bool nextOpIsWrite);
|
||||
FileStreamUnix(const fs::path& path, bool isOpen, bool isWriteable);
|
||||
|
||||
bool m_isValid{};
|
||||
std::fstream m_fileStream;
|
||||
bool m_prevOperationWasWrite{false};
|
||||
};
|
||||
|
48
src/Common/unix/FilesystemAndroid.cpp
Normal file
48
src/Common/unix/FilesystemAndroid.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "FilesystemAndroid.h"
|
||||
|
||||
namespace FilesystemAndroid
|
||||
{
|
||||
std::shared_ptr<FilesystemCallbacks> g_filesystemCallbacks = nullptr;
|
||||
|
||||
void setFilesystemCallbacks(const std::shared_ptr<FilesystemCallbacks> &filesystemCallbacks)
|
||||
{
|
||||
g_filesystemCallbacks = filesystemCallbacks;
|
||||
}
|
||||
|
||||
int openContentUri(const fs::path &uri)
|
||||
{
|
||||
if (g_filesystemCallbacks)
|
||||
return g_filesystemCallbacks->openContentUri(uri);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<fs::path> listFiles(const fs::path &uri)
|
||||
{
|
||||
if (g_filesystemCallbacks)
|
||||
return g_filesystemCallbacks->listFiles(uri);
|
||||
return {};
|
||||
}
|
||||
bool isDirectory(const fs::path &uri)
|
||||
{
|
||||
if (g_filesystemCallbacks)
|
||||
return g_filesystemCallbacks->isDirectory(uri);
|
||||
return false;
|
||||
}
|
||||
bool isFile(const fs::path &uri)
|
||||
{
|
||||
if (g_filesystemCallbacks)
|
||||
return g_filesystemCallbacks->isFile(uri);
|
||||
return false;
|
||||
}
|
||||
bool exists(const fs::path &uri)
|
||||
{
|
||||
if (g_filesystemCallbacks)
|
||||
return g_filesystemCallbacks->exists(uri);
|
||||
return false;
|
||||
}
|
||||
bool isContentUri(const std::string &uri)
|
||||
{
|
||||
static constexpr auto content = "content://";
|
||||
return uri.starts_with(content);
|
||||
}
|
||||
} // namespace FilesystemAndroid
|
29
src/Common/unix/FilesystemAndroid.h
Normal file
29
src/Common/unix/FilesystemAndroid.h
Normal 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
|
@ -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;
|
||||
|
@ -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
15
src/android/.gitignore
vendored
Normal 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
1
src/android/app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
71
src/android/app/build.gradle
Normal file
71
src/android/app/build.gradle
Normal 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
21
src/android/app/proguard-rules.pro
vendored
Normal 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
|
@ -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());
|
||||
}
|
||||
}
|
37
src/android/app/src/main/AndroidManifest.xml
Normal file
37
src/android/app/src/main/AndroidManifest.xml
Normal 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>
|
56
src/android/app/src/main/cpp/AndroidAudio.cpp
Normal file
56
src/android/app/src/main/cpp/AndroidAudio.cpp
Normal 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
|
9
src/android/app/src/main/cpp/AndroidAudio.h
Normal file
9
src/android/app/src/main/cpp/AndroidAudio.h
Normal 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
|
@ -0,0 +1,3 @@
|
||||
#include "AndroidEmulatedController.h"
|
||||
|
||||
std::array<std::unique_ptr<AndroidEmulatedController>, InputManager::kMaxController> AndroidEmulatedController::s_emulatedControllers;
|
112
src/android/app/src/main/cpp/AndroidEmulatedController.h
Normal file
112
src/android/app/src/main/cpp/AndroidEmulatedController.h
Normal 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);
|
||||
}
|
||||
};
|
144
src/android/app/src/main/cpp/AndroidFilesystemCallbacks.h
Normal file
144
src/android/app/src/main/cpp/AndroidFilesystemCallbacks.h
Normal 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);
|
||||
}
|
||||
};
|
25
src/android/app/src/main/cpp/AndroidGameIconLoadedCallback.h
Normal file
25
src/android/app/src/main/cpp/AndroidGameIconLoadedCallback.h
Normal 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);
|
||||
}
|
||||
};
|
@ -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);
|
||||
}
|
||||
};
|
34
src/android/app/src/main/cpp/CMakeLists.txt
Normal file
34
src/android/app/src/main/cpp/CMakeLists.txt
Normal 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
|
||||
)
|
52
src/android/app/src/main/cpp/CafeSystemUtils.cpp
Normal file
52
src/android/app/src/main/cpp/CafeSystemUtils.cpp
Normal 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
|
8
src/android/app/src/main/cpp/CafeSystemUtils.h
Normal file
8
src/android/app/src/main/cpp/CafeSystemUtils.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cafe/TitleList/TitleId.h"
|
||||
|
||||
namespace CafeSystemUtils
|
||||
{
|
||||
void startGame(TitleId titleId);
|
||||
};
|
234
src/android/app/src/main/cpp/EmulationState.h
Normal file
234
src/android/app/src/main/cpp/EmulationState.h
Normal 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);
|
||||
}
|
||||
};
|
87
src/android/app/src/main/cpp/GameIconLoader.cpp
Normal file
87
src/android/app/src/main/cpp/GameIconLoader.cpp
Normal 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();
|
||||
};
|
35
src/android/app/src/main/cpp/GameIconLoader.h
Normal file
35
src/android/app/src/main/cpp/GameIconLoader.h
Normal 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();
|
||||
};
|
140
src/android/app/src/main/cpp/GameTitleLoader.cpp
Normal file
140
src/android/app/src/main/cpp/GameTitleLoader.cpp
Normal 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();
|
||||
}
|
58
src/android/app/src/main/cpp/GameTitleLoader.h
Normal file
58
src/android/app/src/main/cpp/GameTitleLoader.h
Normal 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);
|
||||
};
|
44
src/android/app/src/main/cpp/Image.cpp
Normal file
44
src/android/app/src/main/cpp/Image.cpp
Normal 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);
|
||||
}
|
19
src/android/app/src/main/cpp/Image.h
Normal file
19
src/android/app/src/main/cpp/Image.h
Normal 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();
|
||||
};
|
69
src/android/app/src/main/cpp/JNIUtils.cpp
Normal file
69
src/android/app/src/main/cpp/JNIUtils.cpp
Normal 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;
|
||||
}
|
143
src/android/app/src/main/cpp/JNIUtils.h
Normal file
143
src/android/app/src/main/cpp/JNIUtils.h
Normal 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
|
82
src/android/app/src/main/cpp/Utils.cpp
Normal file
82
src/android/app/src/main/cpp/Utils.cpp
Normal 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);
|
||||
}
|
||||
}
|
6
src/android/app/src/main/cpp/Utils.h
Normal file
6
src/android/app/src/main/cpp/Utils.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "config/ActiveSettings.h"
|
||||
#include "Cemu/ncrypto/ncrypto.h"
|
||||
|
||||
void createCemuDirectories();
|
294
src/android/app/src/main/cpp/native-lib.cpp
Normal file
294
src/android/app/src/main/cpp/native-lib.cpp
Normal 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();
|
||||
}
|
7897
src/android/app/src/main/cpp/stb_image.h
Normal file
7897
src/android/app/src/main/cpp/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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
Loading…
Reference in New Issue
Block a user