mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-20 01:16:38 +00:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4188417d6f | |||
| ccbcc9958c | |||
| 8b514d39fe | |||
| a3d1871002 | |||
| 2387863c4a | |||
| 9fc078ac49 | |||
| 5000815f9d | |||
| 1da937c5f1 | |||
| 5f894ea6e5 | |||
| f48e2f9647 | |||
| 1a8c30b332 | |||
| 9ac88a6576 | |||
| 190275fd97 | |||
| f43902a845 | |||
| dc87555982 | |||
| bd4130055d | |||
| faca1b377a | |||
| 56536549cc | |||
| a2b542693b | |||
| e3a6a99cae | |||
| e553e14d53 | |||
| f4cd650f95 | |||
| 38d7021a54 | |||
| b2223ec9db | |||
| 2b1caac3e8 | |||
| 0fc949d29d | |||
| 88d1fb47d4 | |||
| 4f4bf58153 | |||
| 61c0c8bb7c | |||
| a86291a812 | |||
| ea337df5dd | |||
| f0fe092be8 | |||
| 6c1bb8d5c5 | |||
| f2cf3028c5 | |||
| 463f255299 | |||
| aea66ab8e1 | |||
| 084014d942 | |||
| 349b19fc1a | |||
| 896ee3080f | |||
| 63b5e01627 | |||
| 62b0169f05 | |||
| 8d799eaf1c | |||
| 15a1d783cd | |||
| af9f924658 | |||
| cc8ea0f574 | |||
| a020ce62f8 | |||
| bd532f51b4 | |||
| 8694f708eb | |||
| 339694df1b | |||
| 0043edb66f | |||
| 29888a6f35 | |||
| 78aa04ed51 | |||
| fdc8ea262d | |||
| c09c3edd11 | |||
| 910c59ceab | |||
| ee378e1322 | |||
| 5b525e5f35 | |||
| 6b42e8346d | |||
| bba106569a | |||
| 4dea82c984 |
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/bin/sh -ex
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
WORK_DIR="$PWD"
|
||||
xcrun --sdk iphoneos --show-sdk-path
|
||||
|
||||
# TODO: support iphonesimulator sdk
|
||||
|
||||
cmake -G Xcode -B build/ios \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=16.0 \
|
||||
-DCMAKE_OSX_SYSROOT=iphoneos \
|
||||
-DCMAKE_SYSTEM_NAME=iOS \
|
||||
-DCMAKE_OSX_ARCHITECTURES="arm64" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DYUZU_USE_EXTERNAL_SDL2=ON \
|
||||
"$@"
|
||||
|
||||
cmake --build build/ios -t eden-ios --config Release
|
||||
@@ -115,7 +115,7 @@ for file in $FILES; do
|
||||
*.cmake|*.sh|*.ps1|*.py|*.rb|*.perl|*.pl|*.nix|*CMakeLists.txt)
|
||||
begin="#"
|
||||
;;
|
||||
*.kt|*.kts|*.cpp|*.h|*.qml|*.c|*.hpp|*.hxx|*.cxx|*.h.in|*.inc)
|
||||
*.kt|*.kts|*.cpp|*.h|*.qml|*.c|*.hpp|*.hxx|*.cxx|*.h.in|*.inc|*.mm)
|
||||
begin="//"
|
||||
;;
|
||||
*)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
diff --git a/libs/process/src/shell.cpp b/libs/process/src/shell.cpp
|
||||
index bf4bbfd8..bc4aae89 100644
|
||||
--- a/libs/process/src/shell.cpp
|
||||
+++ b/libs/process/src/shell.cpp
|
||||
@@ -19,7 +19,7 @@
|
||||
#if defined(BOOST_PROCESS_V2_WINDOWS)
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
-#elif !defined(__OpenBSD__) && !defined(__ANDROID__)
|
||||
+#elif !defined(__OpenBSD__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE)
|
||||
#include <wordexp.h>
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,7 @@ BOOST_PROCESS_V2_DECL const error_category& get_shell_category()
|
||||
{
|
||||
return system_category();
|
||||
}
|
||||
-#elif !defined(__OpenBSD__) && !defined(__ANDROID__)
|
||||
+#elif !defined(__OpenBSD__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE)
|
||||
|
||||
struct shell_category_t final : public error_category
|
||||
{
|
||||
@@ -99,7 +99,7 @@ auto shell::args() const-> args_type
|
||||
return input_.c_str();
|
||||
}
|
||||
|
||||
-#elif !defined(__OpenBSD__) && !defined(__ANDROID__)
|
||||
+#elif !defined(__OpenBSD__) && !defined(__ANDROID__) && !(defined(__APPLE__) && TARGET_OS_IPHONE)
|
||||
|
||||
void shell::parse_()
|
||||
{
|
||||
+48
-12
@@ -24,6 +24,29 @@ include(CMakeDependentOption)
|
||||
include(CTest)
|
||||
include(CPMUtil)
|
||||
|
||||
# TODO(crueter): Make this more automatic.
|
||||
if (IOS)
|
||||
list(APPEND CMAKE_FIND_ROOT_PATH "${CMAKE_OSX_SYSROOT_INT}" CACHE INTERNAL "")
|
||||
list(APPEND CMAKE_PROGRAM_PATH "/opt/homebrew/bin" CACHE INTERNAL "")
|
||||
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH CACHE INTERNAL "")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH CACHE INTERNAL "")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE BOTH CACHE INTERNAL "")
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE BOTH CACHE INTERNAL "")
|
||||
|
||||
list(LENGTH CMAKE_OSX_ARCHITECTURES _arch_len)
|
||||
if (NOT _arch_len EQUAL 1)
|
||||
message(FATAL_ERROR "CMAKE_OSX_ARCHITECTURES must contain exactly one architecture.")
|
||||
endif()
|
||||
|
||||
# TODO(crueter): Proper handling for this.
|
||||
if (CMAKE_OSX_ARCHITECTURES STREQUAL arm64)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
else()
|
||||
set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_OSX_ARCHITECTURES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED ARCHITECTURE)
|
||||
message(FATAL_ERROR "Architecture didn't make it out of scope, did you delete DetectArchitecture.cmake?")
|
||||
endif()
|
||||
@@ -45,7 +68,7 @@ if (NETBSD)
|
||||
set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${CMAKE_SYSROOT}/usr/pkg/lib/ffmpeg7/pkgconfig")
|
||||
endif()
|
||||
|
||||
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "LINUX" OFF)
|
||||
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "LINUX OR (APPLE AND NOT IOS)" OFF)
|
||||
if (YUZU_STATIC_ROOM)
|
||||
set(YUZU_ROOM ON)
|
||||
set(YUZU_ROOM_STANDALONE ON)
|
||||
@@ -71,9 +94,15 @@ if (YUZU_STATIC_ROOM)
|
||||
endif()
|
||||
|
||||
# qt stuff
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
if (IOS OR ANDROID)
|
||||
set(_default_qt OFF)
|
||||
else()
|
||||
set(_default_qt ON)
|
||||
endif()
|
||||
|
||||
option(ENABLE_QT "Enable the Qt frontend" ${_default_qt})
|
||||
option(ENABLE_QT_TRANSLATION "Enable translations for the Qt frontend" OFF)
|
||||
option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF)
|
||||
cmake_dependent_option(ENABLE_UPDATE_CHECKER "Enable update checker (for Qt and Android)" OFF "ENABLE_QT OR ANDROID" OFF)
|
||||
cmake_dependent_option(YUZU_USE_QT_MULTIMEDIA "Use QtMultimedia for Camera" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
|
||||
cmake_dependent_option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF "NOT YUZU_USE_BUNDLED_QT" OFF)
|
||||
set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundled Qt libraries")
|
||||
@@ -220,7 +249,7 @@ endif()
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
set(EXT_DEFAULT OFF)
|
||||
if (MSVC OR ANDROID)
|
||||
if (MSVC OR ANDROID OR IOS)
|
||||
set(EXT_DEFAULT ON)
|
||||
endif()
|
||||
|
||||
@@ -232,14 +261,14 @@ cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external sour
|
||||
|
||||
# sirit
|
||||
set(BUNDLED_SIRIT_DEFAULT OFF)
|
||||
if (MSVC AND NOT (CMAKE_BUILD_TYPE MATCHES "Deb") OR ANDROID)
|
||||
if ((MSVC AND NOT (CMAKE_BUILD_TYPE MATCHES "Deb")) OR ANDROID OR IOS)
|
||||
set(BUNDLED_SIRIT_DEFAULT ON)
|
||||
endif()
|
||||
|
||||
option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
|
||||
|
||||
# FreeBSD 15+ has libusb, versions below should disable it
|
||||
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR APPLE" OFF)
|
||||
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR (APPLE AND NOT IOS)" OFF)
|
||||
|
||||
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE AND NOT ANDROID" OFF)
|
||||
mark_as_advanced(FORCE ENABLE_OPENGL)
|
||||
@@ -261,10 +290,10 @@ option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.
|
||||
|
||||
option(NIGHTLY_BUILD "Use Nightly qualifiers in the update checker and build metadata" OFF)
|
||||
|
||||
cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
|
||||
cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID AND NOT IOS" OFF)
|
||||
cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
|
||||
|
||||
cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID" OFF)
|
||||
cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID AND NOT IOS" OFF)
|
||||
|
||||
cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
|
||||
|
||||
@@ -308,7 +337,7 @@ if (YUZU_ROOM)
|
||||
add_compile_definitions(YUZU_ROOM)
|
||||
endif()
|
||||
|
||||
if (UNIX AND NOT (LINUX OR WIN32))
|
||||
if ((UNIX OR IOS) AND NOT (LINUX OR WIN32))
|
||||
if(CXX_APPLE OR CXX_CLANG)
|
||||
# libc++ has stop_token and jthread as experimental
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
|
||||
@@ -381,7 +410,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
find_package(RenderDoc MODULE)
|
||||
find_package(RenderDoc MODULE QUIET)
|
||||
if (NOT RenderDoc_FOUND)
|
||||
message(WARNING "RenderDoc not found. Some debugging features may be disabled.")
|
||||
endif()
|
||||
|
||||
# openssl funniness
|
||||
if (YUZU_USE_BUNDLED_OPENSSL)
|
||||
@@ -509,9 +541,13 @@ endfunction()
|
||||
# Platform-specific library requirements
|
||||
# Put these BEFORE EXTERNALS or Boost WILL die
|
||||
# =============================================
|
||||
|
||||
if (APPLE)
|
||||
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security UniformTypeIdentifiers Foundation)
|
||||
if (IOS)
|
||||
set(_libs Carbon Metal IOKit CoreVideo CoreMedia Security UniformTypeIdentifiers Foundation)
|
||||
else()
|
||||
set(_libs Carbon Metal Cocoa IOKit CoreVideo CoreMedia Security UniformTypeIdentifiers Foundation)
|
||||
endif()
|
||||
foreach(fw ${_libs})
|
||||
find_library(${fw}_LIBRARY ${fw} REQUIRED)
|
||||
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
|
||||
endforeach()
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
- [macOS](#macos)
|
||||
- [OpenIndiana](#openindiana)
|
||||
- [OmniOS](#omnios)
|
||||
- [iOS](#ios)
|
||||
- [HaikuOS](#haikuos)
|
||||
- [OpenBSD](#openbsd)
|
||||
- [FreeBSD](#freebsd)
|
||||
@@ -90,6 +91,16 @@ If it wasn't obvious already, you require a X11 server to properly run the emula
|
||||
|
||||
For Solaris based OSes, `${CMAKE_SYSTEM_NAME}` isn't properly set on CMake (it's set to i686 on AMD64), you may find issues when building OpenSSL from `openssl-cmake`.
|
||||
|
||||
## iOS
|
||||
|
||||
iOS has a dedicated build script, we **highly** recommend using that instead of doing anything else, we don't support any other configuration than the one present in said build script.
|
||||
|
||||
To build, it's simply as easy as doing
|
||||
```sh
|
||||
chmod +x .ci/ios/build.sh
|
||||
.ci/ios/build.sh
|
||||
```
|
||||
|
||||
## HaikuOS
|
||||
|
||||
It's recommended to do a `pkgman full-sync` before installing. See [HaikuOS: Installing applications](https://www.haiku-os.org/guides/daily-tasks/install-applications/). Sometimes the process may be interrupted by an error like "Interrupted syscall". Simply firing the command again fixes the issue. By default `g++` is included on the default installation.
|
||||
|
||||
Vendored
+6
@@ -272,6 +272,12 @@ if (VulkanMemoryAllocator_ADDED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# httplib
|
||||
if (IOS)
|
||||
set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF)
|
||||
endif()
|
||||
AddJsonPackage(httplib)
|
||||
|
||||
# cpp-jwt
|
||||
if (ENABLE_WEB_SERVICE OR ENABLE_UPDATE_CHECKER)
|
||||
AddJsonPackage(cpp-jwt)
|
||||
|
||||
+14
-9
@@ -35,16 +35,21 @@ This file is based off of Yuzu and Dynarmic.
|
||||
# Do note that situations where multiple architectures are defined
|
||||
# should NOT be too dependent on the architecture
|
||||
# otherwise, you may end up with duplicate code
|
||||
if (CMAKE_OSX_ARCHITECTURES)
|
||||
if (DEFINED CMAKE_OSX_ARCHITECTURES)
|
||||
set(MULTIARCH_BUILD 1)
|
||||
set(ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}")
|
||||
|
||||
# hope and pray the architecture names match
|
||||
foreach(ARCH IN ${CMAKE_OSX_ARCHITECTURES})
|
||||
set(ARCHITECTURE_${ARCH} 1 PARENT_SCOPE)
|
||||
add_definitions(-DARCHITECTURE_${ARCH}=1)
|
||||
endforeach()
|
||||
|
||||
if (IOS)
|
||||
# TODO: Right... the toolchain file won't properly accomodate OSX_ARCHITECTURE
|
||||
# they aren't defining it as a list properly I assume?
|
||||
set(ARCHITECTURE_arm64 1)
|
||||
add_definitions(-DARCHITECTURE_arm64=1)
|
||||
else ()
|
||||
# hope and pray the architecture names match
|
||||
foreach(ARCH ${CMAKE_OSX_ARCHITECTURES})
|
||||
set(ARCHITECTURE_${ARCH} 1)
|
||||
add_definitions(-DARCHITECTURE_${ARCH}=1)
|
||||
endforeach()
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
@@ -218,4 +223,4 @@ if (NOT DEFINED ARCHITECTURE)
|
||||
add_definitions(-DARCHITECTURE_GENERIC=1)
|
||||
endif()
|
||||
|
||||
message(STATUS "[DetectArchitecture] Target architecture: ${ARCHITECTURE}")
|
||||
message(STATUS "[DetectArchitecture] Target architecture: ${ARCHITECTURE}")
|
||||
|
||||
@@ -54,6 +54,12 @@ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
|
||||
set(CXX_APPLE ON)
|
||||
endif()
|
||||
|
||||
# This fixes some quirks with xcrun or weird iOS toolchain cmake files
|
||||
if (IOS)
|
||||
unset(CXX_CLANG)
|
||||
set(CXX_APPLE ON)
|
||||
endif()
|
||||
|
||||
# https://gitlab.kitware.com/cmake/cmake/-/merge_requests/11112
|
||||
# This works totally fine on MinGW64, but not CLANG{,ARM}64
|
||||
if(MINGW AND CXX_CLANG)
|
||||
|
||||
Vendored
+17
-1
@@ -29,6 +29,22 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
|
||||
--extra-ldflags="-nostdlib"
|
||||
)
|
||||
set(FFmpeg_IS_CROSS_COMPILING TRUE)
|
||||
elseif(IOS)
|
||||
execute_process(COMMAND xcrun --sdk iphoneos --show-sdk-path OUTPUT_VARIABLE SYSROOT)
|
||||
# Lovely extra newline apple adds that **we** must remove... thank you apple!
|
||||
string(STRIP "${SYSROOT}" SYSROOT)
|
||||
set(FFmpeg_CC xcrun --sdk iphoneos clang -arch arm64)
|
||||
set(FFmpeg_CXX xcrun --sdk iphoneos clang++ -arch arm64)
|
||||
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
|
||||
--arch=arm64
|
||||
--enable-cross-compile
|
||||
--sysroot="${SYSROOT}"
|
||||
--extra-ldflags="-miphoneos-version-min=16.0"
|
||||
--install-name-dir='@rpath'
|
||||
--disable-audiotoolbox
|
||||
--disable-videotoolbox
|
||||
)
|
||||
set(FFmpeg_IS_CROSS_COMPILING TRUE)
|
||||
# User attempts to do a FFmpeg cross compilation because...
|
||||
# Here we just quickly test against host/system processors not matching
|
||||
# TODO: Test for versions not matching as well?
|
||||
@@ -62,7 +78,7 @@ elseif (ANDROID)
|
||||
--enable-mediacodec
|
||||
--enable-jni
|
||||
)
|
||||
elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID)
|
||||
elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID AND NOT IOS)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBVA libva)
|
||||
pkg_check_modules(CUDA cuda)
|
||||
|
||||
Vendored
+2
-1
@@ -24,7 +24,8 @@ if (MINGW OR LINUX OR APPLE)
|
||||
message(FATAL_ERROR "Required program `autoconf` not found.")
|
||||
endif()
|
||||
|
||||
find_program(LIBTOOLIZE libtoolize)
|
||||
find_program(LIBTOOLIZE
|
||||
NAMES libtoolize glibtoolize)
|
||||
if ("${LIBTOOLIZE}" STREQUAL "LIBTOOLIZE-NOTFOUND")
|
||||
message(FATAL_ERROR "Required program `libtoolize` not found.")
|
||||
endif()
|
||||
|
||||
@@ -177,6 +177,9 @@ else()
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-invalid-offsetof>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
|
||||
if (NOT IOS)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-Werror=missing-declarations>)
|
||||
endif()
|
||||
|
||||
if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
|
||||
if (NOT MSVC)
|
||||
@@ -295,4 +298,9 @@ if (ANDROID)
|
||||
target_include_directories(yuzu-android PRIVATE android/app/src/main)
|
||||
endif()
|
||||
|
||||
if (IOS)
|
||||
add_subdirectory(ios)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-Wno-error>)
|
||||
endif()
|
||||
|
||||
include(GenerateDepHashes)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "device_power_state.h"
|
||||
@@ -14,11 +14,14 @@ extern std::atomic<bool> g_has_battery;
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#if TARGET_OS_MAC
|
||||
#if defined(TARGET_OS_MAC) && TARGET_OS_MAC
|
||||
#if TARGET_OS_IPHONE
|
||||
// ios doesnt have this
|
||||
#else
|
||||
#include <IOKit/ps/IOPSKeys.h>
|
||||
#include <IOKit/ps/IOPowerSources.h>
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#elif defined(__linux__)
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
@@ -48,7 +51,9 @@ namespace Common {
|
||||
info.percentage = g_battery_percentage.load(std::memory_order_relaxed);
|
||||
info.charging = g_is_charging.load(std::memory_order_relaxed);
|
||||
info.has_battery = g_has_battery.load(std::memory_order_relaxed);
|
||||
|
||||
#elif defined(__APPLE__) && TARGET_OS_IPHONE
|
||||
// Not implemented
|
||||
info.has_battery = false;
|
||||
#elif defined(__APPLE__) && TARGET_OS_MAC
|
||||
CFTypeRef info_ref = IOPSCopyPowerSourcesInfo();
|
||||
CFArrayRef sources = IOPSCopyPowerSourcesList(info_ref);
|
||||
@@ -96,7 +101,6 @@ namespace Common {
|
||||
#else
|
||||
info.has_battery = false;
|
||||
#endif
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
+20
-18
@@ -17,33 +17,35 @@
|
||||
|
||||
namespace Common {
|
||||
|
||||
// glibc, mlibc, musl, and newlib all define their own variants of strerror_r
|
||||
// We don't need to use the preprocessor, we can just select depending on return type
|
||||
template<typename T> std::string HandleStrerrorR(T r, char *err_str);
|
||||
template<> std::string HandleStrerrorR(char* r, char *) { return std::string{r}; }
|
||||
template<> std::string HandleStrerrorR(const char* r, char *) { return std::string{r}; }
|
||||
template<> std::string HandleStrerrorR(int r, char *err_str) {
|
||||
return std::string{r != 0
|
||||
? "(strerror_r failed to format error)"
|
||||
: err_str};
|
||||
}
|
||||
|
||||
std::string NativeErrorToString(int e) {
|
||||
#ifdef _WIN32
|
||||
LPSTR err_str;
|
||||
|
||||
DWORD res = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, e, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
LPSTR(&err_str), 1, nullptr);
|
||||
if (res) {
|
||||
std::string ret(err_str);
|
||||
LocalFree(err_str);
|
||||
return ret;
|
||||
reinterpret_cast<LPSTR>(&err_str), 1, nullptr);
|
||||
if (!res) {
|
||||
return "(FormatMessageA failed to format error)";
|
||||
}
|
||||
return "(FormatMessageA failed to format error)";
|
||||
std::string ret(err_str);
|
||||
LocalFree(err_str);
|
||||
return ret;
|
||||
#else
|
||||
char err_str[255];
|
||||
return HandleStrerrorR(strerror_r(e, err_str, sizeof(err_str)), err_str);
|
||||
#if defined(__ANDROID__) || \
|
||||
(defined(__GLIBC__) && (_GNU_SOURCE || (_POSIX_C_SOURCE < 200112L && _XOPEN_SOURCE < 600)))
|
||||
// Thread safe (GNU-specific)
|
||||
const char* str = strerror_r(e, err_str, sizeof(err_str));
|
||||
return std::string(str);
|
||||
#else
|
||||
// Thread safe (XSI-compliant)
|
||||
int second_err = strerror_r(e, err_str, sizeof(err_str));
|
||||
if (second_err != 0) {
|
||||
return "(strerror_r failed to format error)";
|
||||
}
|
||||
return std::string(err_str);
|
||||
#endif // GLIBC etc.
|
||||
#endif // _WIN32
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,11 @@
|
||||
#include <sys/random.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <sys/types.h>
|
||||
#if defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
||||
// Not available on iOS for some fucking stupid reason...
|
||||
#else
|
||||
#include <sys/random.h>
|
||||
#endif
|
||||
#include <mach/vm_map.h>
|
||||
#include <mach/mach.h>
|
||||
#elif defined(__FreeBSD__)
|
||||
|
||||
+107
-6
@@ -116,18 +116,119 @@ std::string ReplaceAll(std::string result, const std::string& src, const std::st
|
||||
}
|
||||
|
||||
std::string UTF16ToUTF8(std::u16string_view input) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
|
||||
return convert.to_bytes(input.data(), input.data() + input.size());
|
||||
std::string result;
|
||||
result.reserve(input.size());
|
||||
for (size_t i = 0; i < input.size(); ++i) {
|
||||
uint32_t codepoint = input[i];
|
||||
// Handle surrogate pairs
|
||||
if (codepoint >= 0xD800 && codepoint <= 0xDBFF) {
|
||||
if (i + 1 < input.size()) {
|
||||
uint32_t low = input[i + 1];
|
||||
if (low >= 0xDC00 && low <= 0xDFFF) {
|
||||
codepoint = ((codepoint - 0xD800) << 10) + (low - 0xDC00) + 0x10000;
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (codepoint <= 0x7F) {
|
||||
result.push_back(static_cast<char>(codepoint));
|
||||
} else if (codepoint <= 0x7FF) {
|
||||
result.push_back(static_cast<char>(0xC0 | (codepoint >> 6)));
|
||||
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||
} else if (codepoint <= 0xFFFF) {
|
||||
result.push_back(static_cast<char>(0xE0 | (codepoint >> 12)));
|
||||
result.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||
} else {
|
||||
result.push_back(static_cast<char>(0xF0 | (codepoint >> 18)));
|
||||
result.push_back(static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)));
|
||||
result.push_back(static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)));
|
||||
result.push_back(static_cast<char>(0x80 | (codepoint & 0x3F)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::u16string UTF8ToUTF16(std::string_view input) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
|
||||
return convert.from_bytes(input.data(), input.data() + input.size());
|
||||
std::u16string result;
|
||||
size_t i = 0;
|
||||
while (i < input.size()) {
|
||||
uint32_t codepoint = 0;
|
||||
unsigned char c = input[i];
|
||||
size_t extra = 0;
|
||||
if ((c & 0x80) == 0) {
|
||||
codepoint = c;
|
||||
extra = 0;
|
||||
} else if ((c & 0xE0) == 0xC0) {
|
||||
codepoint = c & 0x1F;
|
||||
extra = 1;
|
||||
} else if ((c & 0xF0) == 0xE0) {
|
||||
codepoint = c & 0x0F;
|
||||
extra = 2;
|
||||
} else if ((c & 0xF8) == 0xF0) {
|
||||
codepoint = c & 0x07;
|
||||
extra = 3;
|
||||
} else {
|
||||
// Invalid UTF-8
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (i + extra >= input.size()) break;
|
||||
for (size_t j = 1; j <= extra; ++j) {
|
||||
if ((input[i + j] & 0xC0) != 0x80) {
|
||||
codepoint = 0xFFFD;
|
||||
break;
|
||||
}
|
||||
codepoint = (codepoint << 6) | (input[i + j] & 0x3F);
|
||||
}
|
||||
if (codepoint <= 0xFFFF) {
|
||||
result.push_back(static_cast<char16_t>(codepoint));
|
||||
} else {
|
||||
codepoint -= 0x10000;
|
||||
result.push_back(static_cast<char16_t>(0xD800 + (codepoint >> 10)));
|
||||
result.push_back(static_cast<char16_t>(0xDC00 + (codepoint & 0x3FF)));
|
||||
}
|
||||
i += extra + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::u32string UTF8ToUTF32(std::string_view input) {
|
||||
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
|
||||
return convert.from_bytes(input.data(), input.data() + input.size());
|
||||
std::u32string result;
|
||||
size_t i = 0;
|
||||
while (i < input.size()) {
|
||||
uint32_t codepoint = 0;
|
||||
unsigned char c = input[i];
|
||||
size_t extra = 0;
|
||||
if ((c & 0x80) == 0) {
|
||||
codepoint = c;
|
||||
extra = 0;
|
||||
} else if ((c & 0xE0) == 0xC0) {
|
||||
codepoint = c & 0x1F;
|
||||
extra = 1;
|
||||
} else if ((c & 0xF0) == 0xE0) {
|
||||
codepoint = c & 0x0F;
|
||||
extra = 2;
|
||||
} else if ((c & 0xF8) == 0xF0) {
|
||||
codepoint = c & 0x07;
|
||||
extra = 3;
|
||||
} else {
|
||||
// Invalid UTF-8
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
if (i + extra >= input.size()) break;
|
||||
for (size_t j = 1; j <= extra; ++j) {
|
||||
if ((input[i + j] & 0xC0) != 0x80) {
|
||||
codepoint = 0xFFFD;
|
||||
break;
|
||||
}
|
||||
codepoint = (codepoint << 6) | (input[i + j] & 0x3F);
|
||||
}
|
||||
result.push_back(codepoint);
|
||||
i += extra + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -1261,6 +1261,10 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITE
|
||||
hle/service/jit/jit.cpp
|
||||
hle/service/jit/jit.h)
|
||||
target_link_libraries(core PRIVATE dynarmic::dynarmic)
|
||||
# Quick hack for XCode generator...
|
||||
if (IOS)
|
||||
target_include_directories(core PRIVATE "${CMAKE_SOURCE_DIR}/dynarmic/src")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (TARGET OpenSSL::SSL)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dynarmic/interface/halt_reason.h>
|
||||
#include "dynarmic/src/dynarmic/interface/halt_reason.h"
|
||||
|
||||
#include "core/arm/arm_interface.h"
|
||||
|
||||
|
||||
@@ -22,15 +22,21 @@ DynarmicCallbacks32::DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess
|
||||
, m_check_memory_access{m_debugger_enabled || !Settings::values.cpuopt_ignore_memory_aborts.GetValue()}
|
||||
{}
|
||||
|
||||
u64 DynarmicCallbacks32::MemoryRead(u32 vaddr, size_t size) {
|
||||
CheckMemoryAccess(vaddr, size, Kernel::DebugWatchpointType::Read);
|
||||
switch (size) {
|
||||
case sizeof(u64): return m_memory.Read64(vaddr);
|
||||
case sizeof(u32): return m_memory.Read32(vaddr);
|
||||
case sizeof(u16): return m_memory.Read16(vaddr);
|
||||
case sizeof(u8): return m_memory.Read8(vaddr);
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
u8 DynarmicCallbacks32::MemoryRead8(u32 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
|
||||
return m_memory.Read8(vaddr);
|
||||
}
|
||||
u16 DynarmicCallbacks32::MemoryRead16(u32 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
|
||||
return m_memory.Read16(vaddr);
|
||||
}
|
||||
u32 DynarmicCallbacks32::MemoryRead32(u32 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
|
||||
return m_memory.Read32(vaddr);
|
||||
}
|
||||
u64 DynarmicCallbacks32::MemoryRead64(u32 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
|
||||
return m_memory.Read64(vaddr);
|
||||
}
|
||||
|
||||
std::optional<u32> DynarmicCallbacks32::MemoryReadCode(u32 vaddr) {
|
||||
@@ -44,17 +50,27 @@ std::optional<u32> DynarmicCallbacks32::MemoryReadCode(u32 vaddr) {
|
||||
return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)];
|
||||
}
|
||||
|
||||
void DynarmicCallbacks32::MemoryWrite(Dynarmic::A32::VAddr vaddr, u64 value, size_t size) {
|
||||
if (CheckMemoryAccess(vaddr, size, Kernel::DebugWatchpointType::Write)) {
|
||||
switch (size) {
|
||||
case sizeof(u64): return m_memory.Write64(vaddr, value);
|
||||
case sizeof(u32): return m_memory.Write32(vaddr, u32(value));
|
||||
case sizeof(u16): return m_memory.Write16(vaddr, u16(value));
|
||||
case sizeof(u8): return m_memory.Write8(vaddr, u8(value));
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
void DynarmicCallbacks32::MemoryWrite8(u32 vaddr, u8 value) {
|
||||
if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
|
||||
m_memory.Write8(vaddr, value);
|
||||
}
|
||||
}
|
||||
void DynarmicCallbacks32::MemoryWrite16(u32 vaddr, u16 value) {
|
||||
if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
|
||||
m_memory.Write16(vaddr, value);
|
||||
}
|
||||
}
|
||||
void DynarmicCallbacks32::MemoryWrite32(u32 vaddr, u32 value) {
|
||||
if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
|
||||
m_memory.Write32(vaddr, value);
|
||||
}
|
||||
}
|
||||
void DynarmicCallbacks32::MemoryWrite64(u32 vaddr, u64 value) {
|
||||
if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
|
||||
m_memory.Write64(vaddr, value);
|
||||
}
|
||||
}
|
||||
|
||||
bool DynarmicCallbacks32::MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) {
|
||||
return CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write) &&
|
||||
m_memory.WriteExclusive8(vaddr, value, expected);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dynarmic/interface/A32/a32.h>
|
||||
#include <dynarmic/interface/code_page.h>
|
||||
#include "dynarmic/src/dynarmic/interface/A32/a32.h"
|
||||
#include "dynarmic/src/dynarmic/interface/code_page.h"
|
||||
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
|
||||
@@ -30,12 +30,18 @@ class System;
|
||||
class DynarmicCallbacks32 : public Dynarmic::A32::UserCallbacks {
|
||||
public:
|
||||
explicit DynarmicCallbacks32(ArmDynarmic32& parent, Kernel::KProcess* process);
|
||||
u64 MemoryRead(u32 vaddr, size_t size) override;
|
||||
u8 MemoryRead8(u32 vaddr) override;
|
||||
u16 MemoryRead16(u32 vaddr) override;
|
||||
u32 MemoryRead32(u32 vaddr) override;
|
||||
u64 MemoryRead64(u32 vaddr) override;
|
||||
std::optional<u32> MemoryReadCode(u32 vaddr) override;
|
||||
void InstructionSynchronizationBarrierRaised() override {
|
||||
last_code_addr = u64(-1); //reset back, force refetch
|
||||
}
|
||||
void MemoryWrite(Dynarmic::A32::VAddr vaddr, u64 value, size_t size) override;
|
||||
void MemoryWrite8(u32 vaddr, u8 value) override;
|
||||
void MemoryWrite16(u32 vaddr, u16 value) override;
|
||||
void MemoryWrite32(u32 vaddr, u32 value) override;
|
||||
void MemoryWrite64(u32 vaddr, u64 value) override;
|
||||
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override;
|
||||
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override;
|
||||
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "dynarmic/interface/A64/config.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
@@ -22,15 +21,21 @@ DynarmicCallbacks64::DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess
|
||||
, m_check_memory_access{m_debugger_enabled || !Settings::values.cpuopt_ignore_memory_aborts.GetValue()}
|
||||
{}
|
||||
|
||||
u64 DynarmicCallbacks64::MemoryRead(u64 vaddr, size_t size) {
|
||||
CheckMemoryAccess(vaddr, size, Kernel::DebugWatchpointType::Read);
|
||||
switch (size) {
|
||||
case sizeof(u64): return m_memory.Read64(vaddr);
|
||||
case sizeof(u32): return m_memory.Read32(vaddr);
|
||||
case sizeof(u16): return m_memory.Read16(vaddr);
|
||||
case sizeof(u8): return m_memory.Read8(vaddr);
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
u8 DynarmicCallbacks64::MemoryRead8(u64 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Read);
|
||||
return m_memory.Read8(vaddr);
|
||||
}
|
||||
u16 DynarmicCallbacks64::MemoryRead16(u64 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Read);
|
||||
return m_memory.Read16(vaddr);
|
||||
}
|
||||
u32 DynarmicCallbacks64::MemoryRead32(u64 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Read);
|
||||
return m_memory.Read32(vaddr);
|
||||
}
|
||||
u64 DynarmicCallbacks64::MemoryRead64(u64 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Read);
|
||||
return m_memory.Read64(vaddr);
|
||||
}
|
||||
Dynarmic::A64::Vector DynarmicCallbacks64::MemoryRead128(u64 vaddr) {
|
||||
CheckMemoryAccess(vaddr, 16, Kernel::DebugWatchpointType::Read);
|
||||
@@ -48,15 +53,24 @@ std::optional<u32> DynarmicCallbacks64::MemoryReadCode(u64 vaddr) {
|
||||
return cached_code_page.inst[(vaddr & Core::Memory::YUZU_PAGEMASK) / sizeof(u32)];
|
||||
}
|
||||
|
||||
void DynarmicCallbacks64::MemoryWrite(Dynarmic::A64::VAddr vaddr, u64 value, std::size_t size) {
|
||||
if (CheckMemoryAccess(vaddr, size, Kernel::DebugWatchpointType::Write)) {
|
||||
switch (size) {
|
||||
case sizeof(u64): return m_memory.Write64(vaddr, u64(value));
|
||||
case sizeof(u32): return m_memory.Write32(vaddr, u32(value));
|
||||
case sizeof(u16): return m_memory.Write16(vaddr, u16(value));
|
||||
case sizeof(u8): return m_memory.Write8(vaddr, u8(value));
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
void DynarmicCallbacks64::MemoryWrite8(u64 vaddr, u8 value) {
|
||||
if (CheckMemoryAccess(vaddr, 1, Kernel::DebugWatchpointType::Write)) {
|
||||
m_memory.Write8(vaddr, value);
|
||||
}
|
||||
}
|
||||
void DynarmicCallbacks64::MemoryWrite16(u64 vaddr, u16 value) {
|
||||
if (CheckMemoryAccess(vaddr, 2, Kernel::DebugWatchpointType::Write)) {
|
||||
m_memory.Write16(vaddr, value);
|
||||
}
|
||||
}
|
||||
void DynarmicCallbacks64::MemoryWrite32(u64 vaddr, u32 value) {
|
||||
if (CheckMemoryAccess(vaddr, 4, Kernel::DebugWatchpointType::Write)) {
|
||||
m_memory.Write32(vaddr, value);
|
||||
}
|
||||
}
|
||||
void DynarmicCallbacks64::MemoryWrite64(u64 vaddr, u64 value) {
|
||||
if (CheckMemoryAccess(vaddr, 8, Kernel::DebugWatchpointType::Write)) {
|
||||
m_memory.Write64(vaddr, value);
|
||||
}
|
||||
}
|
||||
void DynarmicCallbacks64::MemoryWrite128(u64 vaddr, Dynarmic::A64::Vector value) {
|
||||
|
||||
@@ -10,13 +10,12 @@
|
||||
#include <memory>
|
||||
#include "common/container/unordered_map.h"
|
||||
|
||||
#include <dynarmic/interface/A64/a64.h>
|
||||
#include <dynarmic/interface/code_page.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
|
||||
#include "dynarmic/interface/A64/config.h"
|
||||
#include "../../../dynarmic/src/dynarmic/interface/A64/a64.h"
|
||||
#include "../../../dynarmic/src/dynarmic/interface/code_page.h"
|
||||
#include "../../../common/common_types.h"
|
||||
#include "../../../common/hash.h"
|
||||
#include "../arm_interface.h"
|
||||
#include "dynarmic_exclusive_monitor.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
@@ -37,13 +36,19 @@ class DynarmicCallbacks64 : public Dynarmic::A64::UserCallbacks {
|
||||
public:
|
||||
explicit DynarmicCallbacks64(ArmDynarmic64& parent, Kernel::KProcess* process);
|
||||
|
||||
u64 MemoryRead(u64 vaddr, size_t size) override;
|
||||
u8 MemoryRead8(u64 vaddr) override;
|
||||
u16 MemoryRead16(u64 vaddr) override;
|
||||
u32 MemoryRead32(u64 vaddr) override;
|
||||
u64 MemoryRead64(u64 vaddr) override;
|
||||
Dynarmic::A64::Vector MemoryRead128(u64 vaddr) override;
|
||||
std::optional<u32> MemoryReadCode(u64 vaddr) override;
|
||||
void InstructionSynchronizationBarrierRaised() override {
|
||||
last_code_addr = u64(-1); //reset back, force refetch
|
||||
}
|
||||
void MemoryWrite(Dynarmic::A64::VAddr vaddr, u64 value, std::size_t size) override;
|
||||
void MemoryWrite8(u64 vaddr, u8 value) override;
|
||||
void MemoryWrite16(u64 vaddr, u16 value) override;
|
||||
void MemoryWrite32(u64 vaddr, u32 value) override;
|
||||
void MemoryWrite64(u64 vaddr, u64 value) override;
|
||||
void MemoryWrite128(u64 vaddr, Dynarmic::A64::Vector value) override;
|
||||
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override;
|
||||
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -5,7 +8,7 @@
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <dynarmic/interface/A32/coprocessor.h>
|
||||
#include "dynarmic/interface/A32/coprocessor.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <dynarmic/interface/exclusive_monitor.h>
|
||||
#include "dynarmic/src/dynarmic/interface/exclusive_monitor.h"
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2023 merryhime <https://mary.rs>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -7,9 +10,9 @@
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wshadow"
|
||||
|
||||
#include <dynarmic/frontend/A64/a64_types.h>
|
||||
#include <dynarmic/frontend/A64/decoder/a64.h>
|
||||
#include <dynarmic/frontend/imm.h>
|
||||
#include "dynarmic/frontend/A64/a64_types.h"
|
||||
#include "dynarmic/frontend/A64/decoder/a64.h"
|
||||
#include "dynarmic/frontend/imm.h"
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
#include "core/hle/service/am/frontend/applet_web_browser.h"
|
||||
#include "core/hle/service/am/frontend/applets.h"
|
||||
#include "core/hle/service/am/service/storage.h"
|
||||
#include "core/hle/service/am/window_system.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
namespace Service::AM::Frontend {
|
||||
@@ -73,16 +72,10 @@ void FrontendApplet::PushInteractiveOutData(std::shared_ptr<IStorage> storage) {
|
||||
|
||||
void FrontendApplet::Exit() {
|
||||
auto applet_ = applet.lock();
|
||||
{
|
||||
std::scoped_lock lk{applet_->lock};
|
||||
applet_->is_completed = true;
|
||||
applet_->state_changed_event.Signal(system.Kernel());
|
||||
}
|
||||
if (auto caller_applet = applet_->caller_applet.lock()) {
|
||||
std::scoped_lock lk{caller_applet->lock};
|
||||
std::erase(caller_applet->child_applets, applet_);
|
||||
}
|
||||
if (auto* window_system = system.GetAppletManager().GetWindowSystem()) window_system->RequestUpdate();
|
||||
|
||||
std::scoped_lock lk{applet_->lock};
|
||||
applet_->is_completed = true;
|
||||
applet_->state_changed_event.Signal(system.Kernel());
|
||||
}
|
||||
|
||||
FrontendAppletSet::FrontendAppletSet() = default;
|
||||
|
||||
@@ -122,10 +122,7 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
|
||||
auto broker = std::make_shared<AppletDataBroker>(system);
|
||||
applet->caller_applet = caller_applet;
|
||||
applet->caller_applet_broker = broker;
|
||||
{
|
||||
std::scoped_lock lk{caller_applet->lock};
|
||||
caller_applet->child_applets.push_back(applet);
|
||||
}
|
||||
caller_applet->child_applets.push_back(applet);
|
||||
window_system.TrackApplet(applet, false);
|
||||
return std::make_shared<ILibraryAppletAccessor>(system, broker, applet);
|
||||
}
|
||||
@@ -151,10 +148,10 @@ std::shared_ptr<ILibraryAppletAccessor> CreateFrontendApplet(Core::System& syste
|
||||
applet->caller_applet = caller_applet;
|
||||
applet->caller_applet_broker = storage;
|
||||
applet->frontend = system.GetFrontendAppletHolder().GetApplet(applet, applet_id, mode);
|
||||
{
|
||||
std::scoped_lock lk{caller_applet->lock};
|
||||
caller_applet->child_applets.push_back(applet);
|
||||
}
|
||||
caller_applet->child_applets.push_back(applet);
|
||||
|
||||
window_system.TrackApplet(applet, false);
|
||||
|
||||
return std::make_shared<ILibraryAppletAccessor>(system, storage, applet);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <boost/icl/interval_set.hpp>
|
||||
#include <dynarmic/interface/A64/a64.h>
|
||||
#include <dynarmic/interface/A64/config.h>
|
||||
#include <dynarmic/interface/code_page.h>
|
||||
#include "dynarmic/interface/A64/a64.h"
|
||||
#include "dynarmic/interface/A64/config.h"
|
||||
#include "dynarmic/interface/code_page.h"
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_funcs.h"
|
||||
@@ -71,14 +71,17 @@ public:
|
||||
void InstructionSynchronizationBarrierRaised() override {
|
||||
last_code_addr = u64(-1); //reset back, force refetch
|
||||
}
|
||||
u64 MemoryRead(u64 vaddr, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64): return ReadMemory<u64>(vaddr);
|
||||
case sizeof(u32): return ReadMemory<u32>(vaddr);
|
||||
case sizeof(u16): return ReadMemory<u16>(vaddr);
|
||||
case sizeof(u8): return ReadMemory<u8>(vaddr);
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
u8 MemoryRead8(u64 vaddr) override {
|
||||
return ReadMemory<u8>(vaddr);
|
||||
}
|
||||
u16 MemoryRead16(u64 vaddr) override {
|
||||
return ReadMemory<u16>(vaddr);
|
||||
}
|
||||
u32 MemoryRead32(u64 vaddr) override {
|
||||
return ReadMemory<u32>(vaddr);
|
||||
}
|
||||
u64 MemoryRead64(u64 vaddr) override {
|
||||
return ReadMemory<u64>(vaddr);
|
||||
}
|
||||
u128 MemoryRead128(u64 vaddr) override {
|
||||
return ReadMemory<u128>(vaddr);
|
||||
@@ -86,19 +89,22 @@ public:
|
||||
std::string MemoryReadCString(u64 vaddr) {
|
||||
std::string result{};
|
||||
u8 next;
|
||||
while ((next = u8(MemoryRead(vaddr++, sizeof(u8)))) != 0)
|
||||
while ((next = MemoryRead8(vaddr++)) != 0)
|
||||
result += char(next);
|
||||
return result;
|
||||
}
|
||||
|
||||
void MemoryWrite(u64 vaddr, u64 value, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64): WriteMemory<u64>(vaddr, u64(value)); break;
|
||||
case sizeof(u32): WriteMemory<u32>(vaddr, u32(value)); break;
|
||||
case sizeof(u16): WriteMemory<u16>(vaddr, u16(value)); break;
|
||||
case sizeof(u8): WriteMemory<u8>(vaddr, u8(value)); break;
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
void MemoryWrite8(u64 vaddr, u8 value) override {
|
||||
WriteMemory<u8>(vaddr, value);
|
||||
}
|
||||
void MemoryWrite16(u64 vaddr, u16 value) override {
|
||||
WriteMemory<u16>(vaddr, value);
|
||||
}
|
||||
void MemoryWrite32(u64 vaddr, u32 value) override {
|
||||
WriteMemory<u32>(vaddr, value);
|
||||
}
|
||||
void MemoryWrite64(u64 vaddr, u64 value) override {
|
||||
WriteMemory<u64>(vaddr, value);
|
||||
}
|
||||
void MemoryWrite128(u64 vaddr, u128 value) override {
|
||||
WriteMemory<u128>(vaddr, value);
|
||||
@@ -187,14 +193,14 @@ public:
|
||||
// The loaded NRO file has ELF relocations that must be processed before it can run.
|
||||
// Normally this would be processed by RTLD, but in HLE context, we don't have
|
||||
// the linker available, so we have to do it ourselves.
|
||||
const VAddr mod_offset{callbacks->MemoryRead(4, sizeof(u32))};
|
||||
if (callbacks->MemoryRead(mod_offset, sizeof(u32)) != Common::MakeMagic('M', 'O', 'D', '0'))
|
||||
const VAddr mod_offset{callbacks->MemoryRead32(4)};
|
||||
if (callbacks->MemoryRead32(mod_offset) != Common::MakeMagic('M', 'O', 'D', '0'))
|
||||
return false;
|
||||
|
||||
// For more info about dynamic entries, see the ELF ABI specification:
|
||||
// https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
|
||||
// https://refspecs.linuxbase.org/elf/gabi4+/ch4.reloc.html
|
||||
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead(mod_offset + 4, sizeof(u32))};
|
||||
VAddr dynamic_offset{mod_offset + callbacks->MemoryRead32(mod_offset + 4)};
|
||||
VAddr rela_dyn = 0, relr_dyn = 0;
|
||||
size_t num_rela = 0, num_relr = 0;
|
||||
while (true) {
|
||||
@@ -216,8 +222,8 @@ public:
|
||||
for (size_t i = 0; i < num_rela; i++) {
|
||||
const auto rela{callbacks->ReadMemory<Elf64_Rela>(rela_dyn + i * sizeof(Elf64_Rela))};
|
||||
if (Elf64RelType(rela.r_info) == ElfAArch64Relative) {
|
||||
const VAddr contents{callbacks->MemoryRead(rela.r_offset, sizeof(u64))};
|
||||
callbacks->MemoryWrite(rela.r_offset, contents + rela.r_addend, sizeof(u64));
|
||||
const VAddr contents{callbacks->MemoryRead64(rela.r_offset)};
|
||||
callbacks->MemoryWrite64(rela.r_offset, contents + rela.r_addend);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +231,7 @@ public:
|
||||
for (size_t i = 0; i < num_relr; i++) {
|
||||
const auto relr = callbacks->ReadMemory<Elf64_Relr>(relr_dyn + i * sizeof(Elf64_Relr));
|
||||
const auto incr = [&](VAddr where) {
|
||||
callbacks->MemoryWrite(where, callbacks->MemoryRead(where, sizeof(u64)) + relocbase, sizeof(u64));
|
||||
callbacks->MemoryWrite64(where, callbacks->MemoryRead64(where) + relocbase);
|
||||
};
|
||||
if ((relr & 1) == 0) {
|
||||
// where pointer
|
||||
@@ -288,7 +294,7 @@ public:
|
||||
if (argument_stack.size() > 8) {
|
||||
const VAddr new_sp = Common::AlignDown(top_of_stack - (argument_stack.size() - 8) * sizeof(u64), STACK_ALIGN);
|
||||
for (size_t i = 8; i < argument_stack.size(); i++)
|
||||
callbacks->MemoryWrite(new_sp + (i - 8) * sizeof(u64), argument_stack[i], sizeof(u64));
|
||||
callbacks->MemoryWrite64(new_sp + (i - 8) * sizeof(u64), argument_stack[i]);
|
||||
jit->SetSP(new_sp);
|
||||
}
|
||||
// Reset the call state for the next invocation
|
||||
@@ -379,17 +385,17 @@ void DynarmicCallbacks64::CallSVC(u32 swi) {
|
||||
|
||||
if (dest < src) {
|
||||
for (size_t i = 0; i < n; i++)
|
||||
MemoryWrite(dest + i, u8(MemoryRead(src + i, sizeof(u8))), sizeof(u8));
|
||||
MemoryWrite8(dest + i, MemoryRead8(src + i));
|
||||
} else {
|
||||
for (size_t i = n; i > 0; i--)
|
||||
MemoryWrite(dest + i - 1, u8(MemoryRead(src + i - 1, sizeof(u8))), sizeof(u8));
|
||||
MemoryWrite8(dest + i - 1, MemoryRead8(src + i - 1));
|
||||
}
|
||||
} else if (pc == parent.helpers[size_t(HelperFn::Memset)]) {
|
||||
const VAddr dest{parent.jit->GetRegister(0)};
|
||||
const u64 c{parent.jit->GetRegister(1)};
|
||||
const size_t n{parent.jit->GetRegister(2)};
|
||||
for (size_t i = 0; i < n; i++)
|
||||
MemoryWrite(dest + i, u8(c), sizeof(u8));
|
||||
MemoryWrite8(dest + i, u8(c));
|
||||
} else if (pc == parent.helpers[size_t(HelperFn::Resolve)]) {
|
||||
// X0 contains a char* for a symbol to resolve
|
||||
const auto name{MemoryReadCString(parent.jit->GetRegister(0))};
|
||||
@@ -416,7 +422,7 @@ void DynarmicCallbacks64::CallSVC(u32 swi) {
|
||||
}
|
||||
|
||||
void DynarmicCallbacks64::ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) {
|
||||
auto const inst = MemoryRead(pc, sizeof(u32));
|
||||
auto const inst = MemoryRead32(pc);
|
||||
LOG_CRITICAL(Service_JIT, "{} PC @ {:08x}, data = {:08x}", exception, pc, inst);
|
||||
parent.jit->HaltExecution();
|
||||
}
|
||||
|
||||
@@ -33,10 +33,18 @@ u64 MemoryReadWidth(Core::Memory::Memory& memory, u32 width, VAddr addr) {
|
||||
|
||||
void MemoryWriteWidth(Core::Memory::Memory& memory, u32 width, VAddr addr, u64 value) {
|
||||
switch (width) {
|
||||
case sizeof(u64): return memory.Write64(addr, value);
|
||||
case sizeof(u32): return memory.Write32(addr, u32(value));
|
||||
case sizeof(u16): return memory.Write16(addr, u16(value));
|
||||
case sizeof(u8): return memory.Write8(addr, u8(value));
|
||||
case 1:
|
||||
memory.Write8(addr, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
memory.Write16(addr, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
memory.Write32(addr, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
memory.Write64(addr, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
function(target_architecture_specific_sources project arch)
|
||||
if (NOT MULTIARCH_BUILD)
|
||||
target_sources("${project}" PRIVATE ${ARGN})
|
||||
return()
|
||||
endif()
|
||||
|
||||
foreach(input_file IN LISTS ARGN)
|
||||
if(input_file MATCHES ".cpp$")
|
||||
if(NOT IS_ABSOLUTE ${input_file})
|
||||
set(input_file "${CMAKE_CURRENT_SOURCE_DIR}/${input_file}")
|
||||
endif()
|
||||
|
||||
set(output_file "${CMAKE_CURRENT_BINARY_DIR}/arch_gen/${input_file}")
|
||||
add_custom_command(
|
||||
OUTPUT "${output_file}"
|
||||
COMMAND ${CMAKE_COMMAND} "-Darch=${arch}"
|
||||
"-Dinput_file=${input_file}"
|
||||
"-Doutput_file=${output_file}"
|
||||
-P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/impl/TargetArchitectureSpecificSourcesWrapFile.cmake"
|
||||
DEPENDS "${input_file}"
|
||||
VERBATIM
|
||||
)
|
||||
target_sources(${project} PRIVATE "${output_file}")
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
@@ -1,6 +1,5 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
include(TargetArchitectureSpecificSources)
|
||||
|
||||
add_library(dynarmic STATIC
|
||||
mcl/bit.hpp
|
||||
@@ -143,7 +142,7 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
target_compile_definitions(dynarmic PRIVATE XBYAK_OLD_DISP_CHECK=1)
|
||||
target_link_libraries(dynarmic PRIVATE xbyak::xbyak)
|
||||
|
||||
target_architecture_specific_sources(dynarmic "x86_64"
|
||||
target_sources(dynarmic PRIVATE
|
||||
backend/x64/abi.cpp
|
||||
backend/x64/abi.h
|
||||
backend/x64/block_of_code.cpp
|
||||
@@ -204,7 +203,7 @@ endif()
|
||||
if ("arm64" IN_LIST ARCHITECTURE)
|
||||
target_link_libraries(dynarmic PRIVATE merry::oaknut)
|
||||
|
||||
target_architecture_specific_sources(dynarmic "arm64"
|
||||
target_sources(dynarmic PRIVATE
|
||||
backend/arm64/a32_jitstate.cpp
|
||||
backend/arm64/a32_jitstate.h
|
||||
backend/arm64/a64_jitstate.h
|
||||
|
||||
@@ -82,7 +82,7 @@ static void* EmitExclusiveReadCallTrampoline(oaknut::CodeGenerator& code, const
|
||||
|
||||
auto fn = [](const A32::UserConfig& conf, A32::VAddr vaddr) -> T {
|
||||
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
|
||||
return (conf.callbacks->*callback)(vaddr, sizeof(T));
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -136,9 +136,12 @@ static void* EmitExclusiveWriteCallTrampoline(oaknut::CodeGenerator& code, const
|
||||
oaknut::Label l_addr, l_this;
|
||||
|
||||
auto fn = [](const A32::UserConfig& conf, A32::VAddr vaddr, T value) -> u32 {
|
||||
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr, [&](T expected) -> bool {
|
||||
return (conf.callbacks->*callback)(vaddr, value, expected);
|
||||
}) ? 0 : 1;
|
||||
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr,
|
||||
[&](T expected) -> bool {
|
||||
return (conf.callbacks->*callback)(vaddr, value, expected);
|
||||
})
|
||||
? 0
|
||||
: 1;
|
||||
};
|
||||
|
||||
void* target = code.xptr<void*>();
|
||||
@@ -176,14 +179,26 @@ void A32AddressSpace::EmitPrelude() {
|
||||
|
||||
UnprotectCodeMemory();
|
||||
|
||||
prelude_info.read_memory = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead>(code, conf.callbacks);
|
||||
prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u8>(code, conf);
|
||||
prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u16>(code, conf);
|
||||
prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u32>(code, conf);
|
||||
prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead, u64>(code, conf);
|
||||
prelude_info.write_memory = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite>(code, conf.callbacks);
|
||||
prelude_info.read_memory_8 = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead8>(code, conf.callbacks);
|
||||
prelude_info.read_memory_16 = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead16>(code, conf.callbacks);
|
||||
prelude_info.read_memory_32 = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead32>(code, conf.callbacks);
|
||||
prelude_info.read_memory_64 = EmitCallTrampoline<&A32::UserCallbacks::MemoryRead64>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_8 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead8>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_16 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead16>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_32 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead32>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_64 = EmitWrappedReadCallTrampoline<&A32::UserCallbacks::MemoryRead64>(code, conf.callbacks);
|
||||
prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead8, u8>(code, conf);
|
||||
prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead16, u16>(code, conf);
|
||||
prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead32, u32>(code, conf);
|
||||
prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A32::UserCallbacks::MemoryRead64, u64>(code, conf);
|
||||
prelude_info.write_memory_8 = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite8>(code, conf.callbacks);
|
||||
prelude_info.write_memory_16 = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite16>(code, conf.callbacks);
|
||||
prelude_info.write_memory_32 = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite32>(code, conf.callbacks);
|
||||
prelude_info.write_memory_64 = EmitCallTrampoline<&A32::UserCallbacks::MemoryWrite64>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_8 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite8>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_16 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite16>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_32 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite32>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_64 = EmitWrappedWriteCallTrampoline<&A32::UserCallbacks::MemoryWrite64>(code, conf.callbacks);
|
||||
prelude_info.exclusive_write_memory_8 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive8, u8>(code, conf);
|
||||
prelude_info.exclusive_write_memory_16 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive16, u16>(code, conf);
|
||||
prelude_info.exclusive_write_memory_32 = EmitExclusiveWriteCallTrampoline<&A32::UserCallbacks::MemoryWriteExclusive32, u32>(code, conf);
|
||||
|
||||
@@ -81,7 +81,7 @@ static void* EmitExclusiveReadCallTrampoline(oaknut::CodeGenerator& code, const
|
||||
|
||||
auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr) -> T {
|
||||
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
|
||||
return (conf.callbacks->*callback)(vaddr, sizeof(T));
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -133,10 +133,14 @@ static void* EmitExclusiveWriteCallTrampoline(oaknut::CodeGenerator& code, const
|
||||
using namespace oaknut::util;
|
||||
|
||||
oaknut::Label l_addr, l_this;
|
||||
|
||||
auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, T value) -> u32 {
|
||||
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr, [&](T expected) -> bool {
|
||||
return (conf.callbacks->*callback)(vaddr, value, expected);
|
||||
}) ? 0 : 1;
|
||||
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr,
|
||||
[&](T expected) -> bool {
|
||||
return (conf.callbacks->*callback)(vaddr, value, expected);
|
||||
})
|
||||
? 0
|
||||
: 1;
|
||||
};
|
||||
|
||||
void* target = code.xptr<void*>();
|
||||
@@ -342,18 +346,30 @@ void A64AddressSpace::EmitPrelude() {
|
||||
|
||||
UnprotectCodeMemory();
|
||||
|
||||
prelude_info.read_memory = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks);
|
||||
prelude_info.read_memory_8 = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead8>(code, conf.callbacks);
|
||||
prelude_info.read_memory_16 = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead16>(code, conf.callbacks);
|
||||
prelude_info.read_memory_32 = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead32>(code, conf.callbacks);
|
||||
prelude_info.read_memory_64 = EmitCallTrampoline<&A64::UserCallbacks::MemoryRead64>(code, conf.callbacks);
|
||||
prelude_info.read_memory_128 = EmitRead128CallTrampoline(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_8 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead8>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_16 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead16>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_32 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead32>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_64 = EmitWrappedReadCallTrampoline<&A64::UserCallbacks::MemoryRead64>(code, conf.callbacks);
|
||||
prelude_info.wrapped_read_memory_128 = EmitWrappedRead128CallTrampoline(code, conf.callbacks);
|
||||
prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u8>(code, conf);
|
||||
prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u16>(code, conf);
|
||||
prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u32>(code, conf);
|
||||
prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead, u64>(code, conf);
|
||||
prelude_info.exclusive_read_memory_8 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead8, u8>(code, conf);
|
||||
prelude_info.exclusive_read_memory_16 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead16, u16>(code, conf);
|
||||
prelude_info.exclusive_read_memory_32 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead32, u32>(code, conf);
|
||||
prelude_info.exclusive_read_memory_64 = EmitExclusiveReadCallTrampoline<&A64::UserCallbacks::MemoryRead64, u64>(code, conf);
|
||||
prelude_info.exclusive_read_memory_128 = EmitExclusiveRead128CallTrampoline(code, conf);
|
||||
prelude_info.write_memory = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks);
|
||||
prelude_info.write_memory_8 = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite8>(code, conf.callbacks);
|
||||
prelude_info.write_memory_16 = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite16>(code, conf.callbacks);
|
||||
prelude_info.write_memory_32 = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite32>(code, conf.callbacks);
|
||||
prelude_info.write_memory_64 = EmitCallTrampoline<&A64::UserCallbacks::MemoryWrite64>(code, conf.callbacks);
|
||||
prelude_info.write_memory_128 = EmitWrite128CallTrampoline(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_8 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite8>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_16 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite16>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_32 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite32>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_64 = EmitWrappedWriteCallTrampoline<&A64::UserCallbacks::MemoryWrite64>(code, conf.callbacks);
|
||||
prelude_info.wrapped_write_memory_128 = EmitWrappedWrite128CallTrampoline(code, conf.callbacks);
|
||||
prelude_info.exclusive_write_memory_8 = EmitExclusiveWriteCallTrampoline<&A64::UserCallbacks::MemoryWriteExclusive8, u8>(code, conf);
|
||||
prelude_info.exclusive_write_memory_16 = EmitExclusiveWriteCallTrampoline<&A64::UserCallbacks::MemoryWriteExclusive16, u16>(code, conf);
|
||||
|
||||
@@ -145,42 +145,32 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) {
|
||||
case LinkTarget::ReturnFromRunCode:
|
||||
c.B(prelude_info.return_from_run_code);
|
||||
break;
|
||||
// { this, vaddr, size }
|
||||
case LinkTarget::ReadMemory8:
|
||||
c.LDR(X2, 1);
|
||||
c.BL(prelude_info.read_memory);
|
||||
c.BL(prelude_info.read_memory_8);
|
||||
break;
|
||||
case LinkTarget::ReadMemory16:
|
||||
c.LDR(X2, 2);
|
||||
c.BL(prelude_info.read_memory);
|
||||
c.BL(prelude_info.read_memory_16);
|
||||
break;
|
||||
case LinkTarget::ReadMemory32:
|
||||
c.LDR(X2, 4);
|
||||
c.BL(prelude_info.read_memory);
|
||||
c.BL(prelude_info.read_memory_32);
|
||||
break;
|
||||
case LinkTarget::ReadMemory64:
|
||||
c.LDR(X2, 8);
|
||||
c.BL(prelude_info.read_memory);
|
||||
c.BL(prelude_info.read_memory_64);
|
||||
break;
|
||||
case LinkTarget::ReadMemory128:
|
||||
c.BL(prelude_info.read_memory_128);
|
||||
break;
|
||||
// { this, vaddr, size }
|
||||
case LinkTarget::WrappedReadMemory8:
|
||||
c.LDR(X2, 1);
|
||||
c.BL(prelude_info.wrapped_read_memory);
|
||||
c.BL(prelude_info.wrapped_read_memory_8);
|
||||
break;
|
||||
case LinkTarget::WrappedReadMemory16:
|
||||
c.LDR(X2, 2);
|
||||
c.BL(prelude_info.wrapped_read_memory);
|
||||
c.BL(prelude_info.wrapped_read_memory_16);
|
||||
break;
|
||||
case LinkTarget::WrappedReadMemory32:
|
||||
c.LDR(X2, 4);
|
||||
c.BL(prelude_info.wrapped_read_memory);
|
||||
c.BL(prelude_info.wrapped_read_memory_32);
|
||||
break;
|
||||
case LinkTarget::WrappedReadMemory64:
|
||||
c.LDR(X2, 8);
|
||||
c.BL(prelude_info.wrapped_read_memory);
|
||||
c.BL(prelude_info.wrapped_read_memory_64);
|
||||
break;
|
||||
case LinkTarget::WrappedReadMemory128:
|
||||
c.BL(prelude_info.wrapped_read_memory_128);
|
||||
@@ -200,41 +190,32 @@ void AddressSpace::Link(EmittedBlockInfo& block_info) {
|
||||
case LinkTarget::ExclusiveReadMemory128:
|
||||
c.BL(prelude_info.exclusive_read_memory_128);
|
||||
break;
|
||||
// { this, vaddr, value, size }
|
||||
case LinkTarget::WriteMemory8:
|
||||
c.LDR(X3, 1);
|
||||
c.BL(prelude_info.write_memory);
|
||||
c.BL(prelude_info.write_memory_8);
|
||||
break;
|
||||
case LinkTarget::WriteMemory16:
|
||||
c.LDR(X3, 2);
|
||||
c.BL(prelude_info.write_memory);
|
||||
c.BL(prelude_info.write_memory_16);
|
||||
break;
|
||||
case LinkTarget::WriteMemory32:
|
||||
c.LDR(X3, 4);
|
||||
c.BL(prelude_info.write_memory);
|
||||
c.BL(prelude_info.write_memory_32);
|
||||
break;
|
||||
case LinkTarget::WriteMemory64:
|
||||
c.LDR(X3, 8);
|
||||
c.BL(prelude_info.write_memory);
|
||||
c.BL(prelude_info.write_memory_64);
|
||||
break;
|
||||
case LinkTarget::WriteMemory128:
|
||||
c.BL(prelude_info.write_memory_128);
|
||||
break;
|
||||
case LinkTarget::WrappedWriteMemory8:
|
||||
c.LDR(X3, 1);
|
||||
c.BL(prelude_info.wrapped_write_memory);
|
||||
c.BL(prelude_info.wrapped_write_memory_8);
|
||||
break;
|
||||
case LinkTarget::WrappedWriteMemory16:
|
||||
c.LDR(X3, 2);
|
||||
c.BL(prelude_info.wrapped_write_memory);
|
||||
c.BL(prelude_info.wrapped_write_memory_16);
|
||||
break;
|
||||
case LinkTarget::WrappedWriteMemory32:
|
||||
c.LDR(X3, 4);
|
||||
c.BL(prelude_info.wrapped_write_memory);
|
||||
c.BL(prelude_info.wrapped_write_memory_32);
|
||||
break;
|
||||
case LinkTarget::WrappedWriteMemory64:
|
||||
c.LDR(X3, 8);
|
||||
c.BL(prelude_info.wrapped_write_memory);
|
||||
c.BL(prelude_info.wrapped_write_memory_64);
|
||||
break;
|
||||
case LinkTarget::WrappedWriteMemory128:
|
||||
c.BL(prelude_info.wrapped_write_memory_128);
|
||||
|
||||
@@ -93,18 +93,30 @@ protected:
|
||||
void* return_to_dispatcher;
|
||||
void* return_from_run_code;
|
||||
|
||||
void* read_memory;
|
||||
void* read_memory_8;
|
||||
void* read_memory_16;
|
||||
void* read_memory_32;
|
||||
void* read_memory_64;
|
||||
void* read_memory_128;
|
||||
void* wrapped_read_memory;
|
||||
void* wrapped_read_memory_8;
|
||||
void* wrapped_read_memory_16;
|
||||
void* wrapped_read_memory_32;
|
||||
void* wrapped_read_memory_64;
|
||||
void* wrapped_read_memory_128;
|
||||
void* exclusive_read_memory_8;
|
||||
void* exclusive_read_memory_16;
|
||||
void* exclusive_read_memory_32;
|
||||
void* exclusive_read_memory_64;
|
||||
void* exclusive_read_memory_128;
|
||||
void* write_memory;
|
||||
void* write_memory_8;
|
||||
void* write_memory_16;
|
||||
void* write_memory_32;
|
||||
void* write_memory_64;
|
||||
void* write_memory_128;
|
||||
void* wrapped_write_memory;
|
||||
void* wrapped_write_memory_8;
|
||||
void* wrapped_write_memory_16;
|
||||
void* wrapped_write_memory_32;
|
||||
void* wrapped_write_memory_64;
|
||||
void* wrapped_write_memory_128;
|
||||
void* exclusive_write_memory_8;
|
||||
void* exclusive_write_memory_16;
|
||||
|
||||
@@ -31,16 +31,16 @@ using namespace Xbyak::util;
|
||||
void A32EmitX64::GenFastmemFallbacks() {
|
||||
const std::initializer_list<int> idxes{0, 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14};
|
||||
const std::array<std::pair<size_t, ArgCallback>, 4> read_callbacks{{
|
||||
{8, Devirtualize<&A32::UserCallbacks::MemoryRead>(conf.callbacks)},
|
||||
{16, Devirtualize<&A32::UserCallbacks::MemoryRead>(conf.callbacks)},
|
||||
{32, Devirtualize<&A32::UserCallbacks::MemoryRead>(conf.callbacks)},
|
||||
{64, Devirtualize<&A32::UserCallbacks::MemoryRead>(conf.callbacks)},
|
||||
{8, Devirtualize<&A32::UserCallbacks::MemoryRead8>(conf.callbacks)},
|
||||
{16, Devirtualize<&A32::UserCallbacks::MemoryRead16>(conf.callbacks)},
|
||||
{32, Devirtualize<&A32::UserCallbacks::MemoryRead32>(conf.callbacks)},
|
||||
{64, Devirtualize<&A32::UserCallbacks::MemoryRead64>(conf.callbacks)},
|
||||
}};
|
||||
const std::array<std::pair<size_t, ArgCallback>, 4> write_callbacks{{
|
||||
{8, Devirtualize<&A32::UserCallbacks::MemoryWrite>(conf.callbacks)},
|
||||
{16, Devirtualize<&A32::UserCallbacks::MemoryWrite>(conf.callbacks)},
|
||||
{32, Devirtualize<&A32::UserCallbacks::MemoryWrite>(conf.callbacks)},
|
||||
{64, Devirtualize<&A32::UserCallbacks::MemoryWrite>(conf.callbacks)},
|
||||
{8, Devirtualize<&A32::UserCallbacks::MemoryWrite8>(conf.callbacks)},
|
||||
{16, Devirtualize<&A32::UserCallbacks::MemoryWrite16>(conf.callbacks)},
|
||||
{32, Devirtualize<&A32::UserCallbacks::MemoryWrite32>(conf.callbacks)},
|
||||
{64, Devirtualize<&A32::UserCallbacks::MemoryWrite64>(conf.callbacks)},
|
||||
}};
|
||||
const std::array<std::pair<size_t, ArgCallback>, 4> exclusive_write_callbacks{{
|
||||
{8, Devirtualize<&A32::UserCallbacks::MemoryWriteExclusive8>(conf.callbacks)},
|
||||
@@ -56,12 +56,12 @@ void A32EmitX64::GenFastmemFallbacks() {
|
||||
code.align();
|
||||
read_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)] = code.getCurr<void (*)()>();
|
||||
ABI_PushCallerSaveRegistersAndAdjustStackExcept(code, HostLocRegIdx(value_idx));
|
||||
// params = { this, vaddr, size }
|
||||
if (vaddr_idx != code.ABI_PARAM2.getIdx()) {
|
||||
code.mov(code.ABI_PARAM2, Xbyak::Reg64{vaddr_idx});
|
||||
}
|
||||
code.mov(code.ABI_PARAM3, bitsize / CHAR_BIT);
|
||||
if (ordered) code.mfence();
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
callback.EmitCall(code);
|
||||
if (value_idx != code.ABI_RETURN.getIdx()) {
|
||||
code.mov(Xbyak::Reg64{value_idx}, code.ABI_RETURN);
|
||||
@@ -76,7 +76,6 @@ void A32EmitX64::GenFastmemFallbacks() {
|
||||
code.align();
|
||||
write_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)] = code.getCurr<void (*)()>();
|
||||
ABI_PushCallerSaveRegistersAndAdjustStack(code);
|
||||
// params = { this, vaddr, value, size }
|
||||
if (vaddr_idx == code.ABI_PARAM3.getIdx() && value_idx == code.ABI_PARAM2.getIdx()) {
|
||||
code.xchg(code.ABI_PARAM2, code.ABI_PARAM3);
|
||||
} else if (vaddr_idx == code.ABI_PARAM3.getIdx()) {
|
||||
@@ -93,9 +92,10 @@ void A32EmitX64::GenFastmemFallbacks() {
|
||||
}
|
||||
}
|
||||
code.ZeroExtendFrom(bitsize, code.ABI_PARAM3);
|
||||
code.mov(code.ABI_PARAM4, bitsize / CHAR_BIT);
|
||||
callback.EmitCall(code);
|
||||
if (ordered) code.mfence();
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
ABI_PopCallerSaveRegistersAndAdjustStack(code);
|
||||
code.ret();
|
||||
PerfMapRegister(write_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)], code.getCurr(), fmt::format("a32_write_fallback_{}", bitsize));
|
||||
@@ -138,35 +138,35 @@ void A32EmitX64::GenFastmemFallbacks() {
|
||||
#undef Axx
|
||||
|
||||
void A32EmitX64::EmitA32ReadMemory8(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryRead<8, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitMemoryRead<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ReadMemory16(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryRead<16, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitMemoryRead<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ReadMemory32(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryRead<32, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitMemoryRead<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ReadMemory64(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryRead<64, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitMemoryRead<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32WriteMemory8(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<8, &A32::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<8, &A32::UserCallbacks::MemoryWrite8>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32WriteMemory16(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<16, &A32::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<16, &A32::UserCallbacks::MemoryWrite16>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32WriteMemory32(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<32, &A32::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<32, &A32::UserCallbacks::MemoryWrite32>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32WriteMemory64(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<64, &A32::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<64, &A32::UserCallbacks::MemoryWrite64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ClearExclusive(A32EmitContext&, IR::Inst*) {
|
||||
@@ -175,33 +175,33 @@ void A32EmitX64::EmitA32ClearExclusive(A32EmitContext&, IR::Inst*) {
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory8(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<8, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemory<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory16(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<16, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemory<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory32(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<32, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemory<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory64(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<64, &A32::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemory<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,16 +115,16 @@ void A64EmitX64::GenMemory128Accessors() {
|
||||
void A64EmitX64::GenFastmemFallbacks() {
|
||||
const std::initializer_list<int> idxes{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
|
||||
const std::array<std::pair<size_t, ArgCallback>, 4> read_callbacks{{
|
||||
{8, Devirtualize<&A64::UserCallbacks::MemoryRead>(conf.callbacks)},
|
||||
{16, Devirtualize<&A64::UserCallbacks::MemoryRead>(conf.callbacks)},
|
||||
{32, Devirtualize<&A64::UserCallbacks::MemoryRead>(conf.callbacks)},
|
||||
{64, Devirtualize<&A64::UserCallbacks::MemoryRead>(conf.callbacks)},
|
||||
{8, Devirtualize<&A64::UserCallbacks::MemoryRead8>(conf.callbacks)},
|
||||
{16, Devirtualize<&A64::UserCallbacks::MemoryRead16>(conf.callbacks)},
|
||||
{32, Devirtualize<&A64::UserCallbacks::MemoryRead32>(conf.callbacks)},
|
||||
{64, Devirtualize<&A64::UserCallbacks::MemoryRead64>(conf.callbacks)},
|
||||
}};
|
||||
const std::array<std::pair<size_t, ArgCallback>, 4> write_callbacks{{
|
||||
{8, Devirtualize<&A64::UserCallbacks::MemoryWrite>(conf.callbacks)},
|
||||
{16, Devirtualize<&A64::UserCallbacks::MemoryWrite>(conf.callbacks)},
|
||||
{32, Devirtualize<&A64::UserCallbacks::MemoryWrite>(conf.callbacks)},
|
||||
{64, Devirtualize<&A64::UserCallbacks::MemoryWrite>(conf.callbacks)},
|
||||
{8, Devirtualize<&A64::UserCallbacks::MemoryWrite8>(conf.callbacks)},
|
||||
{16, Devirtualize<&A64::UserCallbacks::MemoryWrite16>(conf.callbacks)},
|
||||
{32, Devirtualize<&A64::UserCallbacks::MemoryWrite32>(conf.callbacks)},
|
||||
{64, Devirtualize<&A64::UserCallbacks::MemoryWrite64>(conf.callbacks)},
|
||||
}};
|
||||
const std::array<std::pair<size_t, ArgCallback>, 4> exclusive_write_callbacks{{
|
||||
{8, Devirtualize<&A64::UserCallbacks::MemoryWriteExclusive8>(conf.callbacks)},
|
||||
@@ -204,12 +204,12 @@ void A64EmitX64::GenFastmemFallbacks() {
|
||||
code.align();
|
||||
read_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)] = code.getCurr<void (*)()>();
|
||||
ABI_PushCallerSaveRegistersAndAdjustStackExcept(code, HostLocRegIdx(value_idx));
|
||||
// params = { this, vaddr, size }
|
||||
if (vaddr_idx != code.ABI_PARAM2.getIdx()) {
|
||||
code.mov(code.ABI_PARAM2, Xbyak::Reg64{vaddr_idx});
|
||||
}
|
||||
code.mov(code.ABI_PARAM3, bitsize / CHAR_BIT);
|
||||
if (ordered) code.mfence();
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
callback.EmitCall(code);
|
||||
if (value_idx != code.ABI_RETURN.getIdx()) {
|
||||
code.mov(Xbyak::Reg64{value_idx}, code.ABI_RETURN);
|
||||
@@ -224,7 +224,6 @@ void A64EmitX64::GenFastmemFallbacks() {
|
||||
code.align();
|
||||
write_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)] = code.getCurr<void (*)()>();
|
||||
ABI_PushCallerSaveRegistersAndAdjustStack(code);
|
||||
// params = { this, vaddr, value, size }
|
||||
if (vaddr_idx == code.ABI_PARAM3.getIdx() && value_idx == code.ABI_PARAM2.getIdx()) {
|
||||
code.xchg(code.ABI_PARAM2, code.ABI_PARAM3);
|
||||
} else if (vaddr_idx == code.ABI_PARAM3.getIdx()) {
|
||||
@@ -241,9 +240,10 @@ void A64EmitX64::GenFastmemFallbacks() {
|
||||
}
|
||||
}
|
||||
code.ZeroExtendFrom(bitsize, code.ABI_PARAM3);
|
||||
code.mov(code.ABI_PARAM4, bitsize / CHAR_BIT);
|
||||
callback.EmitCall(code);
|
||||
if (ordered) code.mfence();
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
ABI_PopCallerSaveRegistersAndAdjustStack(code);
|
||||
code.ret();
|
||||
PerfMapRegister(write_fallbacks[std::make_tuple(ordered, bitsize, vaddr_idx, value_idx)], code.getCurr(), fmt::format("a64_write_fallback_{}", bitsize));
|
||||
@@ -286,19 +286,19 @@ void A64EmitX64::GenFastmemFallbacks() {
|
||||
#undef Axx
|
||||
|
||||
void A64EmitX64::EmitA64ReadMemory8(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryRead<8, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitMemoryRead<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ReadMemory16(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryRead<16, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitMemoryRead<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ReadMemory32(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryRead<32, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitMemoryRead<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ReadMemory64(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryRead<64, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitMemoryRead<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ReadMemory128(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
@@ -306,23 +306,23 @@ void A64EmitX64::EmitA64ReadMemory128(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64WriteMemory8(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<8, &A64::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<8, &A64::UserCallbacks::MemoryWrite8>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64WriteMemory16(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<16, &A64::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<16, &A64::UserCallbacks::MemoryWrite16>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64WriteMemory32(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<32, &A64::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<32, &A64::UserCallbacks::MemoryWrite32>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64WriteMemory64(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<64, &A64::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<64, &A64::UserCallbacks::MemoryWrite64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64WriteMemory128(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitMemoryWrite<128, &A64::UserCallbacks::MemoryWrite>(ctx, inst);
|
||||
EmitMemoryWrite<128, &A64::UserCallbacks::MemoryWrite64>(ctx, inst);
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ClearExclusive(A64EmitContext&, IR::Inst*) {
|
||||
@@ -331,33 +331,33 @@ void A64EmitX64::EmitA64ClearExclusive(A64EmitContext&, IR::Inst*) {
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory8(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<8, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemory<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory16(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<16, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemory<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory32(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<32, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemory<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory64(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<64, &A64::UserCallbacks::MemoryRead>(ctx, inst);
|
||||
EmitExclusiveReadMemory<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2022 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -59,13 +56,16 @@ void AxxEmitX64::EmitMemoryRead(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
// Neither fastmem nor page table: Use callbacks
|
||||
if constexpr (bitsize == 128) {
|
||||
ctx.reg_alloc.HostCall(code, nullptr, {}, args[1]);
|
||||
if (ordered) code.mfence();
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
code.CallFunction(memory_read_128);
|
||||
ctx.reg_alloc.DefineValue(code, inst, xmm1);
|
||||
} else {
|
||||
ctx.reg_alloc.HostCall(code, inst, {}, args[1]);
|
||||
code.mov(code.ABI_PARAM3.cvt32(), bitsize / CHAR_BIT);
|
||||
if (ordered) code.mfence();
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
Devirtualize<callback>(conf.callbacks).EmitCall(code);
|
||||
code.ZeroExtendFrom(bitsize, code.ABI_RETURN);
|
||||
}
|
||||
@@ -148,12 +148,12 @@ void AxxEmitX64::EmitMemoryWrite(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
ctx.reg_alloc.HostCall(code, nullptr);
|
||||
code.CallFunction(memory_write_128);
|
||||
} else {
|
||||
// { this, vaddr, value, size }
|
||||
ctx.reg_alloc.HostCall(code, nullptr, {}, args[1], args[2]);
|
||||
code.mov(code.ABI_PARAM4.cvt32(), bitsize / CHAR_BIT);
|
||||
Devirtualize<callback>(conf.callbacks).EmitCall(code);
|
||||
}
|
||||
if (ordered) code.mfence();
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
EmitCheckMemoryAbort(ctx, inst);
|
||||
return;
|
||||
}
|
||||
@@ -230,11 +230,12 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr) -> T {
|
||||
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
|
||||
return (conf.callbacks->*callback)(vaddr, bitsize / CHAR_BIT);
|
||||
code.CallLambda(
|
||||
[](AxxUserConfig& conf, Axx::VAddr vaddr) -> T {
|
||||
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
});
|
||||
});
|
||||
code.ZeroExtendFrom(bitsize, code.ABI_RETURN);
|
||||
} else {
|
||||
const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code);
|
||||
@@ -249,11 +250,12 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) {
|
||||
ret = conf.global_monitor->ReadAndMark<Vector>(conf.processor_id, vaddr, [&]() -> Vector {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
code.CallLambda(
|
||||
[](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) {
|
||||
ret = conf.global_monitor->ReadAndMark<Vector>(conf.processor_id, vaddr, [&]() -> Vector {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
});
|
||||
});
|
||||
code.movups(result, xword[rsp + ABI_SHADOW_SPACE]);
|
||||
ctx.reg_alloc.ReleaseStackSpace(code, 16 + ABI_SHADOW_SPACE);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -14,8 +14,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "dynarmic/interface/A32/config.h"
|
||||
#include "dynarmic/interface/halt_reason.h"
|
||||
#include "config.h"
|
||||
#include "dynarmic/src/dynarmic/interface/halt_reason.h"
|
||||
|
||||
namespace Dynarmic {
|
||||
namespace A32 {
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "dynarmic/frontend/A32/translate/translate_callbacks.h"
|
||||
#include "dynarmic/interface/A32/arch_version.h"
|
||||
#include "dynarmic/interface/optimization_flags.h"
|
||||
#include "../../frontend/A32/translate/translate_callbacks.h"
|
||||
#include "arch_version.h"
|
||||
#include "../optimization_flags.h"
|
||||
|
||||
namespace Dynarmic {
|
||||
class ExclusiveMonitor;
|
||||
@@ -66,9 +66,7 @@ struct UserCallbacks : public TranslateCallbacks {
|
||||
|
||||
// All reads through this callback are 4-byte aligned.
|
||||
// Memory must be interpreted as little endian.
|
||||
std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) override {
|
||||
return std::uint32_t(MemoryRead(vaddr, sizeof(std::uint32_t)));
|
||||
}
|
||||
std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) override { return MemoryRead32(vaddr); }
|
||||
|
||||
// This function is called before the instruction at pc is read.
|
||||
// IR code can be emitted by the callee prior to instruction handling.
|
||||
@@ -82,10 +80,16 @@ struct UserCallbacks : public TranslateCallbacks {
|
||||
|
||||
// Reads through these callbacks may not be aligned.
|
||||
// Memory must be interpreted as if ENDIANSTATE == 0, endianness will be corrected by the JIT.
|
||||
virtual std::uint64_t MemoryRead(VAddr vaddr, std::size_t size) = 0;
|
||||
virtual std::uint8_t MemoryRead8(VAddr vaddr) = 0;
|
||||
virtual std::uint16_t MemoryRead16(VAddr vaddr) = 0;
|
||||
virtual std::uint32_t MemoryRead32(VAddr vaddr) = 0;
|
||||
virtual std::uint64_t MemoryRead64(VAddr vaddr) = 0;
|
||||
|
||||
// Writes through these callbacks may not be aligned.
|
||||
virtual void MemoryWrite(VAddr vaddr, std::uint64_t value, std::size_t size) = 0;
|
||||
virtual void MemoryWrite8(VAddr vaddr, std::uint8_t value) = 0;
|
||||
virtual void MemoryWrite16(VAddr vaddr, std::uint16_t value) = 0;
|
||||
virtual void MemoryWrite32(VAddr vaddr, std::uint32_t value) = 0;
|
||||
virtual void MemoryWrite64(VAddr vaddr, std::uint64_t value) = 0;
|
||||
|
||||
// Writes through these callbacks may not be aligned.
|
||||
virtual bool MemoryWriteExclusive8(VAddr /*vaddr*/, std::uint8_t /*value*/, std::uint8_t /*expected*/) { return false; }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
@@ -15,8 +15,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "dynarmic/interface/A64/config.h"
|
||||
#include "dynarmic/interface/halt_reason.h"
|
||||
#include "config.h"
|
||||
#include "../halt_reason.h"
|
||||
|
||||
namespace Dynarmic {
|
||||
namespace A64 {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "dynarmic/interface/optimization_flags.h"
|
||||
#include "../optimization_flags.h"
|
||||
|
||||
namespace Dynarmic {
|
||||
class ExclusiveMonitor;
|
||||
@@ -89,16 +89,20 @@ struct UserCallbacks {
|
||||
|
||||
// All reads through this callback are 4-byte aligned.
|
||||
// Memory must be interpreted as little endian.
|
||||
virtual std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) {
|
||||
return std::uint32_t(MemoryRead(vaddr, sizeof(std::uint32_t)));
|
||||
}
|
||||
virtual std::optional<std::uint32_t> MemoryReadCode(VAddr vaddr) { return MemoryRead32(vaddr); }
|
||||
|
||||
// Reads through these callbacks may not be aligned.
|
||||
virtual std::uint64_t MemoryRead(VAddr vaddr, std::size_t size) = 0;
|
||||
virtual std::uint8_t MemoryRead8(VAddr vaddr) = 0;
|
||||
virtual std::uint16_t MemoryRead16(VAddr vaddr) = 0;
|
||||
virtual std::uint32_t MemoryRead32(VAddr vaddr) = 0;
|
||||
virtual std::uint64_t MemoryRead64(VAddr vaddr) = 0;
|
||||
virtual Vector MemoryRead128(VAddr vaddr) = 0;
|
||||
|
||||
// Writes through these callbacks may not be aligned.
|
||||
virtual void MemoryWrite(VAddr vaddr, std::uint64_t value, std::size_t size) = 0;
|
||||
virtual void MemoryWrite8(VAddr vaddr, std::uint8_t value) = 0;
|
||||
virtual void MemoryWrite16(VAddr vaddr, std::uint16_t value) = 0;
|
||||
virtual void MemoryWrite32(VAddr vaddr, std::uint32_t value) = 0;
|
||||
virtual void MemoryWrite64(VAddr vaddr, std::uint64_t value) = 0;
|
||||
virtual void MemoryWrite128(VAddr vaddr, Vector value) = 0;
|
||||
|
||||
// Writes through these callbacks may not be aligned.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <cstring>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#include <dynarmic/common/spin_lock.h>
|
||||
#include "dynarmic/src/dynarmic/common/spin_lock.h"
|
||||
|
||||
namespace Dynarmic {
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
|
||||
if (inst.AreAllArgsImmediates()) {
|
||||
const u32 vaddr = inst.GetArg(1).GetU32();
|
||||
if (cb->IsReadOnlyMemory(vaddr)) {
|
||||
const u8 value_from_memory = u8(cb->MemoryRead(vaddr, sizeof(u8)));
|
||||
const u8 value_from_memory = cb->MemoryRead8(vaddr);
|
||||
inst.ReplaceUsesWith(IR::Value{value_from_memory});
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
|
||||
if (inst.AreAllArgsImmediates()) {
|
||||
const u32 vaddr = inst.GetArg(1).GetU32();
|
||||
if (cb->IsReadOnlyMemory(vaddr)) {
|
||||
const u16 value_from_memory = u16(cb->MemoryRead(vaddr, sizeof(u16)));
|
||||
const u16 value_from_memory = cb->MemoryRead16(vaddr);
|
||||
inst.ReplaceUsesWith(IR::Value{value_from_memory});
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
|
||||
if (inst.AreAllArgsImmediates()) {
|
||||
const u32 vaddr = inst.GetArg(1).GetU32();
|
||||
if (cb->IsReadOnlyMemory(vaddr)) {
|
||||
const u32 value_from_memory = u32(cb->MemoryRead(vaddr, sizeof(u32)));
|
||||
const u32 value_from_memory = cb->MemoryRead32(vaddr);
|
||||
inst.ReplaceUsesWith(IR::Value{value_from_memory});
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ static void ConstantMemoryReads(IR::Block& block, A32::UserCallbacks* cb) {
|
||||
if (inst.AreAllArgsImmediates()) {
|
||||
const u32 vaddr = inst.GetArg(1).GetU32();
|
||||
if (cb->IsReadOnlyMemory(vaddr)) {
|
||||
const u64 value_from_memory = u64(cb->MemoryRead(vaddr, sizeof(u64)));
|
||||
const u64 value_from_memory = cb->MemoryRead64(vaddr);
|
||||
inst.ReplaceUsesWith(IR::Value{value_from_memory});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,7 +503,7 @@ TEST_CASE("Fuzz Thumb32 instructions set", "[JitX64][Thumb][Thumb32]") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Verify fix for off by one error in MemoryRead<32> worked", "[Thumb][Thumb16]") {
|
||||
TEST_CASE("Verify fix for off by one error in MemoryRead32 worked", "[Thumb][Thumb16]") {
|
||||
ThumbTestEnv test_env;
|
||||
|
||||
// Prepare test subjects
|
||||
|
||||
@@ -553,9 +553,9 @@ TEST_CASE("arm: Memory access (fastmem)", "[arm][A32]") {
|
||||
memset(backing_memory, 0, memory_size);
|
||||
memcpy(backing_memory + 0x100, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 57);
|
||||
|
||||
env.MemoryWrite(0, 0xE5904000, sizeof(u32)); // LDR R4, [R0]
|
||||
env.MemoryWrite(4, 0xE5814000, sizeof(u32)); // STR R4, [R1]
|
||||
env.MemoryWrite(8, 0xEAFFFFFE, sizeof(u32)); // B .
|
||||
env.MemoryWrite32(0, 0xE5904000); // LDR R4, [R0]
|
||||
env.MemoryWrite32(4, 0xE5814000); // STR R4, [R1]
|
||||
env.MemoryWrite32(8, 0xEAFFFFFE); // B .
|
||||
jit.Regs()[0] = 0x100;
|
||||
jit.Regs()[1] = 0x1F0;
|
||||
jit.Regs()[15] = 0; // PC = 0
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "dynarmic/frontend/A32/translate/translate_callbacks.h"
|
||||
#include "dynarmic/interface/A32/a32.h"
|
||||
|
||||
template<typename InstructionType_, u32 infinite_loop_u32>
|
||||
@@ -60,51 +59,42 @@ public:
|
||||
return infinite_loop_u32; // B .
|
||||
}
|
||||
|
||||
u64 MemoryRead(u32 vaddr, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64):
|
||||
return MemoryRead(vaddr, sizeof(u32))
|
||||
| MemoryRead(vaddr + sizeof(u32), sizeof(u32)) << 32;
|
||||
case sizeof(u32):
|
||||
return MemoryRead(vaddr, sizeof(u16))
|
||||
| MemoryRead(vaddr + sizeof(u16), sizeof(u16)) << 16;
|
||||
case sizeof(u16):
|
||||
return MemoryRead(vaddr, sizeof(u8))
|
||||
| MemoryRead(vaddr + sizeof(u8), sizeof(u8)) << 8;
|
||||
case sizeof(u8): {
|
||||
if (IsInCodeMem(vaddr))
|
||||
return reinterpret_cast<u8*>(code_mem.data())[vaddr];
|
||||
if (auto iter = modified_memory.find(vaddr); iter != modified_memory.end())
|
||||
return iter->second;
|
||||
return u8(vaddr);
|
||||
std::uint8_t MemoryRead8(u32 vaddr) override {
|
||||
if (IsInCodeMem(vaddr)) {
|
||||
return reinterpret_cast<u8*>(code_mem.data())[vaddr];
|
||||
}
|
||||
default:
|
||||
std::abort();
|
||||
if (auto iter = modified_memory.find(vaddr); iter != modified_memory.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return static_cast<u8>(vaddr);
|
||||
}
|
||||
std::uint16_t MemoryRead16(u32 vaddr) override {
|
||||
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
|
||||
}
|
||||
std::uint32_t MemoryRead32(u32 vaddr) override {
|
||||
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
|
||||
}
|
||||
std::uint64_t MemoryRead64(u32 vaddr) override {
|
||||
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
|
||||
}
|
||||
|
||||
void MemoryWrite(Dynarmic::A32::VAddr vaddr, u64 value, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64):
|
||||
MemoryWrite(vaddr, u32(value), sizeof(u32));
|
||||
MemoryWrite(vaddr + 4, u32(value >> 32), sizeof(u32));
|
||||
break;
|
||||
case sizeof(u32):
|
||||
MemoryWrite(vaddr, u16(value), sizeof(u16));
|
||||
MemoryWrite(vaddr + 2, u16(value >> 16), sizeof(u16));
|
||||
break;
|
||||
case sizeof(u16):
|
||||
MemoryWrite(vaddr, u8(value), sizeof(u8));
|
||||
MemoryWrite(vaddr + 1, u8(value >> 8), sizeof(u8));
|
||||
break;
|
||||
case sizeof(u8):
|
||||
if (vaddr < code_mem.size() * sizeof(u32))
|
||||
code_mem_modified_by_guest = true;
|
||||
modified_memory[vaddr] = value;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
void MemoryWrite8(u32 vaddr, std::uint8_t value) override {
|
||||
if (vaddr < code_mem.size() * sizeof(u32)) {
|
||||
code_mem_modified_by_guest = true;
|
||||
}
|
||||
modified_memory[vaddr] = value;
|
||||
}
|
||||
void MemoryWrite16(u32 vaddr, std::uint16_t value) override {
|
||||
MemoryWrite8(vaddr, static_cast<u8>(value));
|
||||
MemoryWrite8(vaddr + 1, static_cast<u8>(value >> 8));
|
||||
}
|
||||
void MemoryWrite32(u32 vaddr, std::uint32_t value) override {
|
||||
MemoryWrite16(vaddr, static_cast<u16>(value));
|
||||
MemoryWrite16(vaddr + 2, static_cast<u16>(value >> 16));
|
||||
}
|
||||
void MemoryWrite64(u32 vaddr, std::uint64_t value) override {
|
||||
MemoryWrite32(vaddr, static_cast<u32>(value));
|
||||
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
@@ -153,41 +143,46 @@ public:
|
||||
return read<std::uint32_t>(vaddr);
|
||||
}
|
||||
|
||||
u64 MemoryRead(u32 vaddr, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64): return read<u64>(vaddr);
|
||||
case sizeof(u32): return read<u32>(vaddr);
|
||||
case sizeof(u16): return read<u16>(vaddr);
|
||||
case sizeof(u8): return read<u8>(vaddr);
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
std::uint8_t MemoryRead8(std::uint32_t vaddr) override {
|
||||
return read<std::uint8_t>(vaddr);
|
||||
}
|
||||
std::uint16_t MemoryRead16(std::uint32_t vaddr) override {
|
||||
return read<std::uint16_t>(vaddr);
|
||||
}
|
||||
std::uint32_t MemoryRead32(std::uint32_t vaddr) override {
|
||||
return read<std::uint32_t>(vaddr);
|
||||
}
|
||||
std::uint64_t MemoryRead64(std::uint32_t vaddr) override {
|
||||
return read<std::uint64_t>(vaddr);
|
||||
}
|
||||
|
||||
void MemoryWrite(Dynarmic::A32::VAddr vaddr, std::uint64_t value, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64): return write<u64>(vaddr, u64(value));
|
||||
case sizeof(u32): return write<u32>(vaddr, u32(value));
|
||||
case sizeof(u16): return write<u16>(vaddr, u16(value));
|
||||
case sizeof(u8): return write<u8>(vaddr, u8(value));
|
||||
default: std::abort();
|
||||
}
|
||||
void MemoryWrite8(std::uint32_t vaddr, std::uint8_t value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
void MemoryWrite16(std::uint32_t vaddr, std::uint16_t value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
void MemoryWrite32(std::uint32_t vaddr, std::uint32_t value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
void MemoryWrite64(std::uint32_t vaddr, std::uint64_t value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
|
||||
bool MemoryWriteExclusive8(Dynarmic::A32::VAddr vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u8));
|
||||
bool MemoryWriteExclusive8(std::uint32_t vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override {
|
||||
MemoryWrite8(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive16(Dynarmic::A32::VAddr vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u16));
|
||||
bool MemoryWriteExclusive16(std::uint32_t vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override {
|
||||
MemoryWrite16(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive32(Dynarmic::A32::VAddr vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u32));
|
||||
bool MemoryWriteExclusive32(std::uint32_t vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override {
|
||||
MemoryWrite32(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive64(Dynarmic::A32::VAddr vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u64));
|
||||
bool MemoryWriteExclusive64(std::uint32_t vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override {
|
||||
MemoryWrite64(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -25,55 +25,48 @@ public:
|
||||
u64 ticks_left = 0;
|
||||
::Common::unordered_map<u64, u8> memory{};
|
||||
|
||||
u64 MemoryRead(u64 vaddr, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64):
|
||||
return MemoryRead(vaddr, sizeof(u32))
|
||||
| MemoryRead(vaddr + sizeof(u32), sizeof(u32)) << 32;
|
||||
case sizeof(u32):
|
||||
return MemoryRead(vaddr, sizeof(u16))
|
||||
| MemoryRead(vaddr + sizeof(u16), sizeof(u16)) << 16;
|
||||
case sizeof(u16):
|
||||
return MemoryRead(vaddr, sizeof(u8))
|
||||
| MemoryRead(vaddr + sizeof(u8), sizeof(u8)) << 8;
|
||||
case sizeof(u8):
|
||||
return memory[vaddr];
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
u8 MemoryRead8(u64 vaddr) override {
|
||||
return memory[vaddr];
|
||||
}
|
||||
|
||||
u16 MemoryRead16(u64 vaddr) override {
|
||||
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
|
||||
}
|
||||
|
||||
u32 MemoryRead32(u64 vaddr) override {
|
||||
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
|
||||
}
|
||||
|
||||
u64 MemoryRead64(u64 vaddr) override {
|
||||
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
|
||||
}
|
||||
|
||||
std::array<u64, 2> MemoryRead128(u64 vaddr) override {
|
||||
return {
|
||||
MemoryRead(vaddr, sizeof(u64)),
|
||||
MemoryRead(vaddr + sizeof(u64), sizeof(u64))
|
||||
};
|
||||
return {MemoryRead64(vaddr), MemoryRead64(vaddr + 8)};
|
||||
}
|
||||
|
||||
void MemoryWrite(Dynarmic::A64::VAddr vaddr, u64 value, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64):
|
||||
MemoryWrite(vaddr, u32(value), sizeof(u32));
|
||||
MemoryWrite(vaddr + 4, u32(value >> 32), sizeof(u32));
|
||||
break;
|
||||
case sizeof(u32):
|
||||
MemoryWrite(vaddr, u16(value), sizeof(u16));
|
||||
MemoryWrite(vaddr + 2, u16(value >> 16), sizeof(u16));
|
||||
break;
|
||||
case sizeof(u16):
|
||||
MemoryWrite(vaddr, u8(value), sizeof(u8));
|
||||
MemoryWrite(vaddr + 1, u8(value >> 8), sizeof(u8));
|
||||
break;
|
||||
case sizeof(u8):
|
||||
memory[vaddr] = value;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
void MemoryWrite8(u64 vaddr, u8 value) override {
|
||||
memory[vaddr] = value;
|
||||
}
|
||||
|
||||
void MemoryWrite16(u64 vaddr, u16 value) override {
|
||||
MemoryWrite8(vaddr, u8(value));
|
||||
MemoryWrite8(vaddr + 1, u8(value >> 8));
|
||||
}
|
||||
|
||||
void MemoryWrite32(u64 vaddr, u32 value) override {
|
||||
MemoryWrite16(vaddr, u16(value));
|
||||
MemoryWrite16(vaddr + 2, u16(value >> 16));
|
||||
}
|
||||
|
||||
void MemoryWrite64(u64 vaddr, u64 value) override {
|
||||
MemoryWrite32(vaddr, u32(value));
|
||||
MemoryWrite32(vaddr + 4, u32(value >> 32));
|
||||
}
|
||||
|
||||
void MemoryWrite128(u64 vaddr, std::array<u64, 2> value) override {
|
||||
MemoryWrite(vaddr, value[0], sizeof(u64));
|
||||
MemoryWrite(vaddr + 8, value[1], sizeof(u64));
|
||||
MemoryWrite64(vaddr, value[0]);
|
||||
MemoryWrite64(vaddr + 8, value[1]);
|
||||
}
|
||||
|
||||
void CallSVC(u32) override {
|
||||
@@ -142,9 +135,9 @@ TEST_CASE("A64: fibonacci", "[a64]") {
|
||||
code.RET();
|
||||
|
||||
for (size_t i = 0; i < 1024; i++) {
|
||||
env.MemoryWrite(i * 4, instructions[i], sizeof(u32));
|
||||
env.MemoryWrite32(i * 4, instructions[i]);
|
||||
}
|
||||
env.MemoryWrite(8888, 0xd4200000, sizeof(u32));
|
||||
env.MemoryWrite32(8888, 0xd4200000);
|
||||
cpu.SetRegister(30, 8888);
|
||||
|
||||
cpu.SetRegister(0, 10);
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "dynarmic/interface/A64/a64.h"
|
||||
#include "dynarmic/interface/A64/config.h"
|
||||
|
||||
using Vector = Dynarmic::A64::Vector;
|
||||
|
||||
@@ -35,79 +34,64 @@ public:
|
||||
return code_mem[index];
|
||||
}
|
||||
|
||||
u64 MemoryRead(u64 vaddr, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64):
|
||||
return MemoryRead(vaddr, sizeof(u32))
|
||||
| MemoryRead(vaddr + sizeof(u32), sizeof(u32)) << 32;
|
||||
case sizeof(u32):
|
||||
return MemoryRead(vaddr, sizeof(u16))
|
||||
| MemoryRead(vaddr + sizeof(u16), sizeof(u16)) << 16;
|
||||
case sizeof(u16):
|
||||
return MemoryRead(vaddr, sizeof(u8))
|
||||
| MemoryRead(vaddr + sizeof(u8), sizeof(u8)) << 8;
|
||||
case sizeof(u8): {
|
||||
if (IsInCodeMem(vaddr))
|
||||
return reinterpret_cast<u8*>(code_mem.data())[vaddr - code_mem_start_address];
|
||||
if (auto const it = modified_memory.find(vaddr); it != modified_memory.end())
|
||||
return it->second;
|
||||
return u8(vaddr);
|
||||
}
|
||||
default:
|
||||
std::abort();
|
||||
std::uint8_t MemoryRead8(u64 vaddr) override {
|
||||
if (IsInCodeMem(vaddr)) {
|
||||
return reinterpret_cast<u8*>(code_mem.data())[vaddr - code_mem_start_address];
|
||||
}
|
||||
if (auto const it = modified_memory.find(vaddr); it != modified_memory.end())
|
||||
return it->second;
|
||||
return u8(vaddr);
|
||||
}
|
||||
std::uint16_t MemoryRead16(u64 vaddr) override {
|
||||
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
|
||||
}
|
||||
std::uint32_t MemoryRead32(u64 vaddr) override {
|
||||
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
|
||||
}
|
||||
std::uint64_t MemoryRead64(u64 vaddr) override {
|
||||
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
|
||||
}
|
||||
|
||||
Vector MemoryRead128(u64 vaddr) override {
|
||||
return {
|
||||
MemoryRead(vaddr, sizeof(u64)),
|
||||
MemoryRead(vaddr + 8, sizeof(u64))
|
||||
};
|
||||
return {MemoryRead64(vaddr), MemoryRead64(vaddr + 8)};
|
||||
}
|
||||
|
||||
void MemoryWrite(Dynarmic::A64::VAddr vaddr, u64 value, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64):
|
||||
MemoryWrite(vaddr, u32(value), sizeof(u32));
|
||||
MemoryWrite(vaddr + 4, u32(value >> 32), sizeof(u32));
|
||||
break;
|
||||
case sizeof(u32):
|
||||
MemoryWrite(vaddr, u16(value), sizeof(u16));
|
||||
MemoryWrite(vaddr + 2, u16(value >> 16), sizeof(u16));
|
||||
break;
|
||||
case sizeof(u16):
|
||||
MemoryWrite(vaddr, u8(value), sizeof(u8));
|
||||
MemoryWrite(vaddr + 1, u8(value >> 8), sizeof(u8));
|
||||
break;
|
||||
case sizeof(u8):
|
||||
if (IsInCodeMem(vaddr)) {
|
||||
code_mem_modified_by_guest = true;
|
||||
}
|
||||
modified_memory[vaddr] = value;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
void MemoryWrite8(u64 vaddr, std::uint8_t value) override {
|
||||
if (IsInCodeMem(vaddr)) {
|
||||
code_mem_modified_by_guest = true;
|
||||
}
|
||||
modified_memory[vaddr] = value;
|
||||
}
|
||||
void MemoryWrite16(u64 vaddr, std::uint16_t value) override {
|
||||
MemoryWrite8(vaddr, u8(value));
|
||||
MemoryWrite8(vaddr + 1, u8(value >> 8));
|
||||
}
|
||||
void MemoryWrite32(u64 vaddr, std::uint32_t value) override {
|
||||
MemoryWrite16(vaddr, u16(value));
|
||||
MemoryWrite16(vaddr + 2, u16(value >> 16));
|
||||
}
|
||||
void MemoryWrite64(u64 vaddr, std::uint64_t value) override {
|
||||
MemoryWrite32(vaddr, u32(value));
|
||||
MemoryWrite32(vaddr + 4, u32(value >> 32));
|
||||
}
|
||||
void MemoryWrite128(u64 vaddr, Vector value) override {
|
||||
MemoryWrite(vaddr, value[0], sizeof(u64));
|
||||
MemoryWrite(vaddr + 8, value[1], sizeof(u64));
|
||||
MemoryWrite64(vaddr, value[0]);
|
||||
MemoryWrite64(vaddr + 8, value[1]);
|
||||
}
|
||||
|
||||
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u8));
|
||||
MemoryWrite8(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u16));
|
||||
MemoryWrite16(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u32));
|
||||
MemoryWrite32(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u64));
|
||||
MemoryWrite64(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive128(u64 vaddr, Vector value, [[maybe_unused]] Vector expected) override {
|
||||
@@ -161,46 +145,52 @@ public:
|
||||
return read<std::uint32_t>(vaddr);
|
||||
}
|
||||
|
||||
u64 MemoryRead(u64 vaddr, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64): return read<u64>(vaddr);
|
||||
case sizeof(u32): return read<u32>(vaddr);
|
||||
case sizeof(u16): return read<u16>(vaddr);
|
||||
case sizeof(u8): return read<u8>(vaddr);
|
||||
default: std::abort();
|
||||
}
|
||||
std::uint8_t MemoryRead8(u64 vaddr) override {
|
||||
return read<std::uint8_t>(vaddr);
|
||||
}
|
||||
std::uint16_t MemoryRead16(u64 vaddr) override {
|
||||
return read<std::uint16_t>(vaddr);
|
||||
}
|
||||
std::uint32_t MemoryRead32(u64 vaddr) override {
|
||||
return read<std::uint32_t>(vaddr);
|
||||
}
|
||||
std::uint64_t MemoryRead64(u64 vaddr) override {
|
||||
return read<std::uint64_t>(vaddr);
|
||||
}
|
||||
Vector MemoryRead128(u64 vaddr) override {
|
||||
return read<Vector>(vaddr);
|
||||
}
|
||||
|
||||
void MemoryWrite(u64 vaddr, std::uint64_t value, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64): return write<u64>(vaddr, u64(value));
|
||||
case sizeof(u32): return write<u32>(vaddr, u32(value));
|
||||
case sizeof(u16): return write<u16>(vaddr, u16(value));
|
||||
case sizeof(u8): return write<u8>(vaddr, u8(value));
|
||||
default: std::abort();
|
||||
}
|
||||
void MemoryWrite8(u64 vaddr, std::uint8_t value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
void MemoryWrite16(u64 vaddr, std::uint16_t value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
void MemoryWrite32(u64 vaddr, std::uint32_t value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
void MemoryWrite64(u64 vaddr, std::uint64_t value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
void MemoryWrite128(u64 vaddr, Vector value) override {
|
||||
write(vaddr, value);
|
||||
}
|
||||
|
||||
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u8));
|
||||
MemoryWrite8(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u16));
|
||||
MemoryWrite16(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u32));
|
||||
MemoryWrite32(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override {
|
||||
MemoryWrite(vaddr, value, sizeof(u64));
|
||||
MemoryWrite64(vaddr, value);
|
||||
return true;
|
||||
}
|
||||
bool MemoryWriteExclusive128(u64 vaddr, Vector value, [[maybe_unused]] Vector expected) override {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
include(TargetArchitectureSpecificSources)
|
||||
|
||||
add_executable(dynarmic_tests
|
||||
fp/FPToFixed.cpp
|
||||
@@ -50,7 +49,7 @@ endif()
|
||||
|
||||
if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
target_link_libraries(dynarmic_tests PRIVATE xbyak::xbyak)
|
||||
target_architecture_specific_sources(dynarmic_tests "x86_64"
|
||||
target_sources(dynarmic PRIVATE
|
||||
x64_cpu_info.cpp
|
||||
native/preserve_xmm.cpp
|
||||
)
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
#include <fmt/ranges.h>
|
||||
#include "dynarmic/frontend/A32/translate/translate_callbacks.h"
|
||||
#include "dynarmic/mcl/bit.hpp"
|
||||
#include "common/common_types.h"
|
||||
|
||||
@@ -119,47 +118,36 @@ public:
|
||||
u64 ticks_left = 0;
|
||||
std::map<u32, u8> memory;
|
||||
|
||||
u64 MemoryRead(Dynarmic::A32::VAddr vaddr, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64):
|
||||
return MemoryRead(vaddr, sizeof(u32))
|
||||
| MemoryRead(vaddr + sizeof(u32), sizeof(u32)) << 32;
|
||||
case sizeof(u32):
|
||||
return MemoryRead(vaddr, sizeof(u16))
|
||||
| MemoryRead(vaddr + sizeof(u16), sizeof(u16)) << 16;
|
||||
case sizeof(u16):
|
||||
return MemoryRead(vaddr, sizeof(u8))
|
||||
| MemoryRead(vaddr + sizeof(u8), sizeof(u8)) << 8;
|
||||
case sizeof(u8): {
|
||||
if (auto const it = memory.find(vaddr); it != memory.end())
|
||||
return it->second;
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
std::abort();
|
||||
std::uint8_t MemoryRead8(u32 vaddr) override {
|
||||
if (auto iter = memory.find(vaddr); iter != memory.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
std::uint16_t MemoryRead16(u32 vaddr) override {
|
||||
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
|
||||
}
|
||||
std::uint32_t MemoryRead32(u32 vaddr) override {
|
||||
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
|
||||
}
|
||||
std::uint64_t MemoryRead64(u32 vaddr) override {
|
||||
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
|
||||
}
|
||||
|
||||
void MemoryWrite(Dynarmic::A32::VAddr vaddr, u64 value, size_t size) override {
|
||||
switch (size) {
|
||||
case sizeof(u64):
|
||||
MemoryWrite(vaddr, u32(value), sizeof(u32));
|
||||
MemoryWrite(vaddr + 4, u32(value >> 32), sizeof(u32));
|
||||
break;
|
||||
case sizeof(u32):
|
||||
MemoryWrite(vaddr, u16(value), sizeof(u16));
|
||||
MemoryWrite(vaddr + 2, u16(value >> 16), sizeof(u16));
|
||||
break;
|
||||
case sizeof(u16):
|
||||
MemoryWrite(vaddr, u8(value), sizeof(u8));
|
||||
MemoryWrite(vaddr + 1, u8(value >> 8), sizeof(u8));
|
||||
break;
|
||||
case sizeof(u8):
|
||||
memory[vaddr] = value;
|
||||
break;
|
||||
default:
|
||||
std::abort();
|
||||
}
|
||||
void MemoryWrite8(u32 vaddr, std::uint8_t value) override {
|
||||
memory[vaddr] = value;
|
||||
}
|
||||
void MemoryWrite16(u32 vaddr, std::uint16_t value) override {
|
||||
MemoryWrite8(vaddr, static_cast<u8>(value));
|
||||
MemoryWrite8(vaddr + 1, static_cast<u8>(value >> 8));
|
||||
}
|
||||
void MemoryWrite32(u32 vaddr, std::uint32_t value) override {
|
||||
MemoryWrite16(vaddr, static_cast<u16>(value));
|
||||
MemoryWrite16(vaddr + 2, static_cast<u16>(value >> 16));
|
||||
}
|
||||
void MemoryWrite64(u32 vaddr, std::uint64_t value) override {
|
||||
MemoryWrite32(vaddr, static_cast<u32>(value));
|
||||
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
@@ -243,7 +231,7 @@ void ExecuteA32Instruction(u32 instruction) {
|
||||
if (const auto address = get_value()) {
|
||||
fmt::print("value: ");
|
||||
if (const auto value = get_value()) {
|
||||
env.MemoryWrite(*address, *value, sizeof(u32));
|
||||
env.MemoryWrite32(*address, *value);
|
||||
fmt::print("> mem[{:#08x}] = {:#08x}\n", *address, *value);
|
||||
}
|
||||
}
|
||||
@@ -259,8 +247,8 @@ void ExecuteA32Instruction(u32 instruction) {
|
||||
cpu.SetFpscr(fpscr);
|
||||
|
||||
const u32 initial_pc = regs[15];
|
||||
env.MemoryWrite(initial_pc + 0, instruction, sizeof(u32));
|
||||
env.MemoryWrite(initial_pc + 4, 0xEAFFFFFE, sizeof(u32)); // B +0
|
||||
env.MemoryWrite32(initial_pc + 0, instruction);
|
||||
env.MemoryWrite32(initial_pc + 4, 0xEAFFFFFE); // B +0
|
||||
|
||||
cpu.Run();
|
||||
fmt::print("{}", fmt::join(cpu.Disassemble(), "\n"));
|
||||
|
||||
@@ -290,7 +290,7 @@ bool A32Unicorn<TestEnvironment>::UnmappedMemoryHook(uc_engine* uc, uc_mem_type
|
||||
auto page = std::make_unique<Page>();
|
||||
page->address = base_address;
|
||||
for (size_t i = 0; i < page->data.size(); ++i)
|
||||
page->data[i] = u8(this_->testenv.MemoryRead(u32(base_address + i), sizeof(u8)));
|
||||
page->data[i] = this_->testenv.MemoryRead8(static_cast<u32>(base_address + i));
|
||||
|
||||
uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data());
|
||||
if (err == UC_ERR_MAP)
|
||||
@@ -321,7 +321,24 @@ bool A32Unicorn<TestEnvironment>::UnmappedMemoryHook(uc_engine* uc, uc_mem_type
|
||||
template<class TestEnvironment>
|
||||
bool A32Unicorn<TestEnvironment>::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u32 start_address, int size, u64 value, void* user_data) {
|
||||
auto* this_ = static_cast<A32Unicorn*>(user_data);
|
||||
this_->testenv.MemoryWrite(start_address, value, size);
|
||||
|
||||
switch (size) {
|
||||
case 1:
|
||||
this_->testenv.MemoryWrite8(start_address, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
this_->testenv.MemoryWrite16(start_address, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
this_->testenv.MemoryWrite32(start_address, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
this_->testenv.MemoryWrite64(start_address, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ bool A64Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u64 sta
|
||||
auto page = std::make_unique<Page>();
|
||||
page->address = base_address;
|
||||
for (size_t i = 0; i < page->data.size(); ++i)
|
||||
page->data[i] = u8(this_->testenv.MemoryRead(base_address + i, sizeof(u8)));
|
||||
page->data[i] = this_->testenv.MemoryRead8(base_address + i);
|
||||
|
||||
uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data());
|
||||
if (err == UC_ERR_MAP)
|
||||
@@ -227,6 +227,23 @@ bool A64Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u64 sta
|
||||
|
||||
bool A64Unicorn::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u64 start_address, int size, u64 value, void* user_data) {
|
||||
auto* this_ = static_cast<A64Unicorn*>(user_data);
|
||||
this_->testenv.MemoryWrite(start_address, value, size);
|
||||
|
||||
switch (size) {
|
||||
case 1:
|
||||
this_->testenv.MemoryWrite8(start_address, static_cast<u8>(value));
|
||||
break;
|
||||
case 2:
|
||||
this_->testenv.MemoryWrite16(start_address, static_cast<u16>(value));
|
||||
break;
|
||||
case 4:
|
||||
this_->testenv.MemoryWrite32(start_address, static_cast<u32>(value));
|
||||
break;
|
||||
case 8:
|
||||
this_->testenv.MemoryWrite64(start_address, value);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -761,6 +761,9 @@ void EmulatedController::StartMotionCalibration() {
|
||||
}
|
||||
|
||||
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index, Common::UUID uuid) {
|
||||
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
|
||||
if (index >= controller.button_values.size()) {
|
||||
return;
|
||||
}
|
||||
@@ -913,10 +916,21 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
|
||||
break;
|
||||
}
|
||||
|
||||
const auto player_index = Service::HID::NpadIdTypeToIndex(npad_id_type);
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
if (player.connected) {
|
||||
Connect();
|
||||
if (!is_connected) {
|
||||
if (npad_type == NpadStyleIndex::Handheld) {
|
||||
if (npad_id_type == NpadIdType::Handheld) {
|
||||
Connect();
|
||||
controller_connected[player_index] = true;
|
||||
}
|
||||
} else if (npad_type != NpadStyleIndex::Handheld) {
|
||||
if (npad_id_type == NpadIdType::Player1) {
|
||||
Connect();
|
||||
controller_connected[player_index] = true;
|
||||
} else if (player.connected && !controller_connected[player_index]) {
|
||||
Connect();
|
||||
controller_connected[player_index] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TriggerOnChange(ControllerTriggerType::Button, true);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "common/settings.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "hid_core/frontend/motion_input.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "hid_core/irsensor/irs_types.h"
|
||||
|
||||
@@ -584,6 +585,7 @@ private:
|
||||
std::array<VibrationValue, 2> last_vibration_value{DEFAULT_VIBRATION_VALUE,
|
||||
DEFAULT_VIBRATION_VALUE};
|
||||
std::array<std::chrono::steady_clock::time_point, 2> last_vibration_timepoint{};
|
||||
std::array<bool, HIDCore::available_controllers> controller_connected{};
|
||||
|
||||
// Atomically synched values
|
||||
std::atomic<HID::NpadStyleIndex> npad_type{HID::NpadStyleIndex::None};
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifndef AppUI_Bridging_Header_h
|
||||
#define AppUI_Bridging_Header_h
|
||||
|
||||
#import "AppUIObjC.h"
|
||||
|
||||
#endif /* AppUI_Bridging_Header_h */
|
||||
@@ -0,0 +1,113 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import UIKit
|
||||
import Foundation
|
||||
import QuartzCore.CAMetalLayer
|
||||
import SwiftUI
|
||||
|
||||
public struct AppUI {
|
||||
public static let shared = AppUI()
|
||||
fileprivate let appUIObjC = AppUIObjC.shared()
|
||||
|
||||
public func configure(layer: CAMetalLayer, with size: CGSize) {
|
||||
appUIObjC.configure(layer: layer, with: size)
|
||||
}
|
||||
|
||||
public func information(for url: URL) -> AppUIInformation {
|
||||
appUIObjC.gameInformation.information(for: url)
|
||||
}
|
||||
|
||||
public func insert(game url: URL) {
|
||||
appUIObjC.insert(game: url)
|
||||
}
|
||||
|
||||
public func insert(games urls: [URL]) {
|
||||
appUIObjC.insert(games: urls)
|
||||
}
|
||||
|
||||
public func bootOS() {
|
||||
appUIObjC.bootOS()
|
||||
}
|
||||
|
||||
public func pause() {
|
||||
appUIObjC.pause()
|
||||
}
|
||||
|
||||
public func play() {
|
||||
appUIObjC.play()
|
||||
}
|
||||
|
||||
public func ispaused() -> Bool {
|
||||
return appUIObjC.ispaused()
|
||||
}
|
||||
|
||||
public func FirstFrameShowed() -> Bool {
|
||||
return appUIObjC.hasfirstfame()
|
||||
}
|
||||
|
||||
public func canGetFullPath() -> Bool {
|
||||
return appUIObjC.canGetFullPath()
|
||||
}
|
||||
|
||||
|
||||
public func exit() {
|
||||
appUIObjC.quit()
|
||||
}
|
||||
|
||||
public func step() {
|
||||
appUIObjC.step()
|
||||
}
|
||||
|
||||
public func orientationChanged(orientation: UIInterfaceOrientation, with layer: CAMetalLayer, size: CGSize) {
|
||||
appUIObjC.orientationChanged(orientation: orientation, with: layer, size: size)
|
||||
}
|
||||
|
||||
public func touchBegan(at point: CGPoint, for index: UInt) {
|
||||
appUIObjC.touchBegan(at: point, for: index)
|
||||
}
|
||||
|
||||
public func touchEnded(for index: UInt) {
|
||||
appUIObjC.touchEnded(for: index)
|
||||
}
|
||||
|
||||
public func touchMoved(at point: CGPoint, for index: UInt) {
|
||||
appUIObjC.touchMoved(at: point, for: index)
|
||||
}
|
||||
|
||||
public func gyroMoved(x: Float, y: Float, z: Float, accelX: Float, accelY: Float, accelZ: Float, controllerId: Int32, deltaTimestamp: Int32) {
|
||||
// Calling the Objective-C function with both gyroscope and accelerometer data
|
||||
appUIObjC.virtualControllerGyro(controllerId,
|
||||
deltaTimestamp: deltaTimestamp,
|
||||
gyroX: x, gyroY: y, gyroZ: z,
|
||||
accelX: accelX, accelY: accelY, accelZ: accelZ)
|
||||
}
|
||||
|
||||
|
||||
public func thumbstickMoved(analog: VirtualControllerAnalogType, x: Float, y: Float, controllerid: Int) {
|
||||
appUIObjC.thumbstickMoved(analog, x: CGFloat(x), y: CGFloat(y), controllerId: Int32(controllerid))
|
||||
}
|
||||
|
||||
public func virtualControllerButtonDown(button: VirtualControllerButtonType, controllerid: Int) {
|
||||
appUIObjC.virtualControllerButtonDown(button, controllerId: Int32(controllerid))
|
||||
}
|
||||
|
||||
public func virtualControllerButtonUp(button: VirtualControllerButtonType, controllerid: Int) {
|
||||
appUIObjC.virtualControllerButtonUp(button, controllerId: Int32(controllerid))
|
||||
}
|
||||
|
||||
public func settingsSaved() {
|
||||
appUIObjC.settingsChanged()
|
||||
}
|
||||
}
|
||||
|
||||
struct EdenApp: App {
|
||||
@StateObject private var core = EmulationViewModel()
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ContentView(core: core)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface AppUIInformation : NSObject
|
||||
@property (nonatomic, strong) NSString *developer;
|
||||
@property (nonatomic, strong) NSData *iconData;
|
||||
@property (nonatomic) BOOL isHomebrew;
|
||||
@property (nonatomic) uint64_t programID;
|
||||
@property (nonatomic, strong) NSString *title, *version;
|
||||
|
||||
-(AppUIInformation *) initWithDeveloper:(NSString *)developer iconData:(NSData *)iconData isHomebrew:(BOOL)isHomebrew programID:(uint64_t)programID title:(NSString *)title version:(NSString *)version;
|
||||
@end
|
||||
|
||||
@interface AppUIGameInformation : NSObject
|
||||
+(AppUIGameInformation *) sharedInstance NS_SWIFT_NAME(shared());
|
||||
|
||||
-(AppUIInformation *) informationForGame:(NSURL *)url NS_SWIFT_NAME(information(for:));
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,436 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AppUIGameInformation.h"
|
||||
#import "EmulationSession.h"
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "frontend_common/config.h"
|
||||
|
||||
struct GameMetadata {
|
||||
std::string title;
|
||||
u64 programId;
|
||||
std::string developer;
|
||||
std::string version;
|
||||
std::vector<u8> icon;
|
||||
bool isHomebrew;
|
||||
};
|
||||
|
||||
|
||||
class SdlConfig final : public Config {
|
||||
public:
|
||||
explicit SdlConfig(std::optional<std::string> config_path);
|
||||
~SdlConfig() override;
|
||||
|
||||
void ReloadAllValues() override;
|
||||
void SaveAllValues() override;
|
||||
|
||||
protected:
|
||||
void ReadSdlValues();
|
||||
void ReadSdlPlayerValues(std::size_t player_index);
|
||||
void ReadSdlControlValues();
|
||||
void ReadHidbusValues() override;
|
||||
void ReadDebugControlValues() override;
|
||||
void ReadPathValues() override {}
|
||||
void ReadShortcutValues() override {}
|
||||
void ReadUIValues() override {}
|
||||
void ReadUIGamelistValues() override {}
|
||||
void ReadUILayoutValues() override {}
|
||||
void ReadMultiplayerValues() override {}
|
||||
|
||||
void SaveSdlValues();
|
||||
void SaveSdlPlayerValues(std::size_t player_index);
|
||||
void SaveSdlControlValues();
|
||||
void SaveHidbusValues() override;
|
||||
void SaveDebugControlValues() override;
|
||||
void SavePathValues() override {}
|
||||
void SaveShortcutValues() override {}
|
||||
void SaveUIValues() override {}
|
||||
void SaveUIGamelistValues() override {}
|
||||
void SaveUILayoutValues() override {}
|
||||
void SaveMultiplayerValues() override {}
|
||||
|
||||
std::vector<Settings::BasicSetting*>& FindRelevantList(Settings::Category category) override;
|
||||
|
||||
public:
|
||||
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
||||
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
|
||||
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
||||
static const std::array<int, 2> default_stick_mod;
|
||||
static const std::array<int, 2> default_ringcon_analogs;
|
||||
};
|
||||
|
||||
|
||||
#define SDL_MAIN_HANDLED
|
||||
#include <SDL.h>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
const std::array<int, Settings::NativeButton::NumButtons> SdlConfig::default_buttons = {
|
||||
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X, SDL_SCANCODE_T,
|
||||
SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, SDL_SCANCODE_Q, SDL_SCANCODE_W,
|
||||
SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_B,
|
||||
};
|
||||
|
||||
const std::array<int, Settings::NativeMotion::NumMotions> SdlConfig::default_motions = {
|
||||
SDL_SCANCODE_7,
|
||||
SDL_SCANCODE_8,
|
||||
};
|
||||
|
||||
const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> SdlConfig::default_analogs{
|
||||
{
|
||||
{
|
||||
SDL_SCANCODE_UP,
|
||||
SDL_SCANCODE_DOWN,
|
||||
SDL_SCANCODE_LEFT,
|
||||
SDL_SCANCODE_RIGHT,
|
||||
},
|
||||
{
|
||||
SDL_SCANCODE_I,
|
||||
SDL_SCANCODE_K,
|
||||
SDL_SCANCODE_J,
|
||||
SDL_SCANCODE_L,
|
||||
},
|
||||
}};
|
||||
|
||||
const std::array<int, 2> SdlConfig::default_stick_mod = {
|
||||
SDL_SCANCODE_D,
|
||||
0,
|
||||
};
|
||||
|
||||
const std::array<int, 2> SdlConfig::default_ringcon_analogs{{
|
||||
0,
|
||||
0,
|
||||
}};
|
||||
|
||||
SdlConfig::SdlConfig(const std::optional<std::string> config_path) {
|
||||
Initialize(config_path);
|
||||
ReadSdlValues();
|
||||
SaveSdlValues();
|
||||
}
|
||||
|
||||
SdlConfig::~SdlConfig() {
|
||||
if (global) {
|
||||
SdlConfig::SaveAllValues();
|
||||
}
|
||||
}
|
||||
|
||||
void SdlConfig::ReloadAllValues() {
|
||||
Reload();
|
||||
ReadSdlValues();
|
||||
SaveSdlValues();
|
||||
}
|
||||
|
||||
void SdlConfig::SaveAllValues() {
|
||||
SaveValues();
|
||||
SaveSdlValues();
|
||||
}
|
||||
|
||||
void SdlConfig::ReadSdlValues() {
|
||||
ReadSdlControlValues();
|
||||
}
|
||||
|
||||
void SdlConfig::ReadSdlControlValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
||||
|
||||
Settings::values.players.SetGlobal(!IsCustomConfig());
|
||||
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
|
||||
ReadSdlPlayerValues(p);
|
||||
}
|
||||
if (IsCustomConfig()) {
|
||||
EndGroup();
|
||||
return;
|
||||
}
|
||||
ReadDebugControlValues();
|
||||
ReadHidbusValues();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void SdlConfig::ReadSdlPlayerValues(const std::size_t player_index) {
|
||||
std::string player_prefix;
|
||||
if (type != ConfigType::InputProfile) {
|
||||
player_prefix.append("player_").append(ToString(player_index)).append("_");
|
||||
}
|
||||
|
||||
auto& player = Settings::values.players.GetValue()[player_index];
|
||||
if (IsCustomConfig()) {
|
||||
const auto profile_name =
|
||||
ReadStringSetting(std::string(player_prefix).append("profile_name"));
|
||||
if (profile_name.empty()) {
|
||||
// Use the global input config
|
||||
player = Settings::values.players.GetValue(true)[player_index];
|
||||
player.profile_name = "";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
auto& player_buttons = player.buttons[i];
|
||||
|
||||
player_buttons = ReadStringSetting(
|
||||
std::string(player_prefix).append(Settings::NativeButton::mapping[i]), default_param);
|
||||
if (player_buttons.empty()) {
|
||||
player_buttons = default_param;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_stick_mod[i], 0.5f);
|
||||
auto& player_analogs = player.analogs[i];
|
||||
|
||||
player_analogs = ReadStringSetting(
|
||||
std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]), default_param);
|
||||
if (player_analogs.empty()) {
|
||||
player_analogs = default_param;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
|
||||
auto& player_motions = player.motions[i];
|
||||
|
||||
player_motions = ReadStringSetting(
|
||||
std::string(player_prefix).append(Settings::NativeMotion::mapping[i]), default_param);
|
||||
if (player_motions.empty()) {
|
||||
player_motions = default_param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SdlConfig::ReadDebugControlValues() {
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
auto& debug_pad_buttons = Settings::values.debug_pad_buttons[i];
|
||||
debug_pad_buttons = ReadStringSetting(
|
||||
std::string("debug_pad_").append(Settings::NativeButton::mapping[i]), default_param);
|
||||
if (debug_pad_buttons.empty()) {
|
||||
debug_pad_buttons = default_param;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_stick_mod[i], 0.5f);
|
||||
auto& debug_pad_analogs = Settings::values.debug_pad_analogs[i];
|
||||
debug_pad_analogs = ReadStringSetting(
|
||||
std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]), default_param);
|
||||
if (debug_pad_analogs.empty()) {
|
||||
debug_pad_analogs = default_param;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SdlConfig::ReadHidbusValues() {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
|
||||
auto& ringcon_analogs = Settings::values.ringcon_analogs;
|
||||
|
||||
ringcon_analogs = ReadStringSetting(std::string("ring_controller"), default_param);
|
||||
if (ringcon_analogs.empty()) {
|
||||
ringcon_analogs = default_param;
|
||||
}
|
||||
}
|
||||
|
||||
void SdlConfig::SaveSdlValues() {
|
||||
LOG_DEBUG(Config, "Saving SDL configuration values");
|
||||
SaveSdlControlValues();
|
||||
|
||||
WriteToIni();
|
||||
}
|
||||
|
||||
void SdlConfig::SaveSdlControlValues() {
|
||||
BeginGroup(Settings::TranslateCategory(Settings::Category::Controls));
|
||||
|
||||
Settings::values.players.SetGlobal(!IsCustomConfig());
|
||||
for (std::size_t p = 0; p < Settings::values.players.GetValue().size(); ++p) {
|
||||
SaveSdlPlayerValues(p);
|
||||
}
|
||||
if (IsCustomConfig()) {
|
||||
EndGroup();
|
||||
return;
|
||||
}
|
||||
SaveDebugControlValues();
|
||||
SaveHidbusValues();
|
||||
|
||||
EndGroup();
|
||||
}
|
||||
|
||||
void SdlConfig::SaveSdlPlayerValues(const std::size_t player_index) {
|
||||
std::string player_prefix;
|
||||
if (type != ConfigType::InputProfile) {
|
||||
player_prefix = std::string("player_").append(ToString(player_index)).append("_");
|
||||
}
|
||||
|
||||
const auto& player = Settings::values.players.GetValue()[player_index];
|
||||
if (IsCustomConfig() && player.profile_name.empty()) {
|
||||
// No custom profile selected
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeButton::mapping[i]),
|
||||
player.buttons[i], std::make_optional(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_stick_mod[i], 0.5f);
|
||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeAnalog::mapping[i]),
|
||||
player.analogs[i], std::make_optional(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeMotion::NumMotions; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_motions[i]);
|
||||
WriteStringSetting(std::string(player_prefix).append(Settings::NativeMotion::mapping[i]),
|
||||
player.motions[i], std::make_optional(default_param));
|
||||
}
|
||||
}
|
||||
|
||||
void SdlConfig::SaveDebugControlValues() {
|
||||
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
|
||||
WriteStringSetting(std::string("debug_pad_").append(Settings::NativeButton::mapping[i]),
|
||||
Settings::values.debug_pad_buttons[i],
|
||||
std::make_optional(default_param));
|
||||
}
|
||||
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
|
||||
default_analogs[i][3], default_stick_mod[i], 0.5f);
|
||||
WriteStringSetting(std::string("debug_pad_").append(Settings::NativeAnalog::mapping[i]),
|
||||
Settings::values.debug_pad_analogs[i],
|
||||
std::make_optional(default_param));
|
||||
}
|
||||
}
|
||||
|
||||
void SdlConfig::SaveHidbusValues() {
|
||||
const std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
|
||||
0, 0, default_ringcon_analogs[0], default_ringcon_analogs[1], 0, 0.05f);
|
||||
WriteStringSetting(std::string("ring_controller"), Settings::values.ringcon_analogs,
|
||||
std::make_optional(default_param));
|
||||
}
|
||||
|
||||
std::vector<Settings::BasicSetting*>& SdlConfig::FindRelevantList(Settings::Category category) {
|
||||
return Settings::values.linkage.by_category[category];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
std::unordered_map<std::string, GameMetadata> m_game_metadata_cache;
|
||||
|
||||
GameMetadata CacheGameMetadata(const std::string& path) {
|
||||
const auto file =
|
||||
Core::GetGameFileFromPath(EmulationSession::GetInstance().System().GetFilesystem(), path);
|
||||
auto loader = Loader::GetLoader(EmulationSession::GetInstance().System(), file, 0, 0);
|
||||
|
||||
GameMetadata entry;
|
||||
loader->ReadTitle(entry.title);
|
||||
loader->ReadProgramId(entry.programId);
|
||||
loader->ReadIcon(entry.icon);
|
||||
|
||||
const FileSys::PatchManager pm{
|
||||
entry.programId, EmulationSession::GetInstance().System().GetFileSystemController(),
|
||||
EmulationSession::GetInstance().System().GetContentProvider()};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
if (control.first != nullptr) {
|
||||
entry.developer = control.first->GetDeveloperName();
|
||||
entry.version = control.first->GetVersionString();
|
||||
} else {
|
||||
FileSys::NACP nacp;
|
||||
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success) {
|
||||
entry.developer = nacp.GetDeveloperName();
|
||||
} else {
|
||||
entry.developer = "";
|
||||
}
|
||||
|
||||
entry.version = "1.0.0";
|
||||
}
|
||||
|
||||
if (loader->GetFileType() == Loader::FileType::NRO) {
|
||||
auto loader_nro = reinterpret_cast<Loader::AppLoader_NRO*>(loader.get());
|
||||
entry.isHomebrew = loader_nro->IsHomebrew();
|
||||
} else {
|
||||
entry.isHomebrew = false;
|
||||
}
|
||||
|
||||
m_game_metadata_cache[path] = entry;
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
GameMetadata GameMetadata(const std::string& path, bool reload = false) {
|
||||
if (!EmulationSession::GetInstance().IsInitialized()) {
|
||||
NSURL *dir_url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
|
||||
const char *directory_cstr = [[dir_url path] UTF8String];
|
||||
Common::FS::SetAppDirectory(directory_cstr);
|
||||
|
||||
EmulationSession::GetInstance().System().Initialize();
|
||||
EmulationSession::GetInstance().InitializeSystem(false);
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
return CacheGameMetadata(path);
|
||||
}
|
||||
|
||||
if (auto search = m_game_metadata_cache.find(path); search != m_game_metadata_cache.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
return CacheGameMetadata(path);
|
||||
}
|
||||
|
||||
|
||||
@implementation AppUIInformation
|
||||
-(AppUIInformation *) initWithDeveloper:(NSString *)developer iconData:(NSData *)iconData isHomebrew:(BOOL)isHomebrew programID:(uint64_t)programID
|
||||
title:(NSString *)title version:(NSString *)version {
|
||||
if (self = [super init]) {
|
||||
self.developer = developer;
|
||||
self.iconData = iconData;
|
||||
self.isHomebrew = isHomebrew;
|
||||
self.programID = programID;
|
||||
self.title = title;
|
||||
self.version = version;
|
||||
} return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation AppUIGameInformation
|
||||
+(AppUIGameInformation *) sharedInstance {
|
||||
static AppUIGameInformation *sharedInstance = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
|
||||
-(AppUIInformation *) informationForGame:(NSURL *)url {
|
||||
auto gameMetadata = GameMetadata([url.path UTF8String]);
|
||||
|
||||
return [[AppUIInformation alloc] initWithDeveloper:[NSString stringWithCString:gameMetadata.developer.c_str() encoding:NSUTF8StringEncoding]
|
||||
iconData:[NSData dataWithBytes:gameMetadata.icon.data() length:gameMetadata.icon.size()]
|
||||
isHomebrew:gameMetadata.isHomebrew programID:gameMetadata.programId
|
||||
title:[NSString stringWithCString:gameMetadata.title.c_str() encoding:NSUTF8StringEncoding]
|
||||
version:[NSString stringWithCString:gameMetadata.version.c_str() encoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,93 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MetalKit/MetalKit.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "AppUIGameInformation.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSUInteger, VirtualControllerAnalogType) {
|
||||
VirtualControllerAnalogTypeLeft = 0,
|
||||
VirtualControllerAnalogTypeRight = 1
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSUInteger, VirtualControllerButtonType) {
|
||||
VirtualControllerButtonTypeA = 0,
|
||||
VirtualControllerButtonTypeB = 1,
|
||||
VirtualControllerButtonTypeX = 2,
|
||||
VirtualControllerButtonTypeY = 3,
|
||||
VirtualControllerButtonTypeL = 4,
|
||||
VirtualControllerButtonTypeR = 5,
|
||||
VirtualControllerButtonTypeTriggerL = 6,
|
||||
VirtualControllerButtonTypeTriggerR = 7,
|
||||
VirtualControllerButtonTypeTriggerZL = 8,
|
||||
VirtualControllerButtonTypeTriggerZR = 9,
|
||||
VirtualControllerButtonTypePlus = 10,
|
||||
VirtualControllerButtonTypeMinus = 11,
|
||||
VirtualControllerButtonTypeDirectionalPadLeft = 12,
|
||||
VirtualControllerButtonTypeDirectionalPadUp = 13,
|
||||
VirtualControllerButtonTypeDirectionalPadRight = 14,
|
||||
VirtualControllerButtonTypeDirectionalPadDown = 15,
|
||||
VirtualControllerButtonTypeSL = 16,
|
||||
VirtualControllerButtonTypeSR = 17,
|
||||
VirtualControllerButtonTypeHome = 18,
|
||||
VirtualControllerButtonTypeCapture = 19
|
||||
};
|
||||
|
||||
@interface AppUIObjC : NSObject {
|
||||
CAMetalLayer *_layer;
|
||||
CGSize _size;
|
||||
}
|
||||
|
||||
@property (nonatomic, strong) AppUIGameInformation *gameInformation;
|
||||
|
||||
+(AppUIObjC *) sharedInstance NS_SWIFT_NAME(shared());
|
||||
-(void) configureLayer:(CAMetalLayer *)layer withSize:(CGSize)size NS_SWIFT_NAME(configure(layer:with:));
|
||||
-(void) bootOS;
|
||||
-(void) pause;
|
||||
-(void) play;
|
||||
-(BOOL) ispaused;
|
||||
-(BOOL) canGetFullPath;
|
||||
-(void) quit;
|
||||
-(void) insertGame:(NSURL *)url NS_SWIFT_NAME(insert(game:));
|
||||
-(void) insertGames:(NSArray<NSURL *> *)games NS_SWIFT_NAME(insert(games:));
|
||||
-(void) step;
|
||||
-(BOOL) hasfirstfame;
|
||||
|
||||
-(void) touchBeganAtPoint:(CGPoint)point index:(NSUInteger)index NS_SWIFT_NAME(touchBegan(at:for:));
|
||||
-(void) touchEndedForIndex:(NSUInteger)index;
|
||||
-(void) touchMovedAtPoint:(CGPoint)point index:(NSUInteger)index NS_SWIFT_NAME(touchMoved(at:for:));
|
||||
|
||||
-(void) thumbstickMoved:(VirtualControllerAnalogType)analog
|
||||
x:(CGFloat)x
|
||||
y:(CGFloat)y
|
||||
controllerId:(int)controllerId;
|
||||
|
||||
-(void) virtualControllerGyro:(int)controllerId
|
||||
deltaTimestamp:(int)delta_timestamp
|
||||
gyroX:(float)gyro_x
|
||||
gyroY:(float)gyro_y
|
||||
gyroZ:(float)gyro_z
|
||||
accelX:(float)accel_x
|
||||
accelY:(float)accel_y
|
||||
accelZ:(float)accel_z;
|
||||
|
||||
-(void) virtualControllerButtonDown:(VirtualControllerButtonType)button
|
||||
controllerId:(int)controllerId;
|
||||
|
||||
-(void) virtualControllerButtonUp:(VirtualControllerButtonType)button
|
||||
controllerId:(int)controllerId;
|
||||
|
||||
|
||||
-(void) orientationChanged:(UIInterfaceOrientation)orientation with:(CAMetalLayer *)layer size:(CGSize)size NS_SWIFT_NAME(orientationChanged(orientation:with:size:));
|
||||
|
||||
-(void) settingsChanged;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,251 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#import "AppUIObjC.h"
|
||||
|
||||
#import "Config.h"
|
||||
#import "EmulationSession.h"
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
#include "common/settings_enums.h"
|
||||
#include "network/announce_multiplayer_session.h"
|
||||
#include "common/announce_multiplayer_room.h"
|
||||
#include "network/network.h"
|
||||
|
||||
#include "common/detached_tasks.h"
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/vfs/vfs_real.h"
|
||||
#include "core/frontend/applets/cabinet.h"
|
||||
#include "core/frontend/applets/controller.h"
|
||||
#include "core/frontend/applets/error.h"
|
||||
#include "core/frontend/applets/general.h"
|
||||
#include "core/frontend/applets/mii_edit.h"
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
#include "core/hle/service/am/applet_manager.h"
|
||||
#include "core/hle/service/am/frontend/applets.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/vulkan_common/vulkan_instance.h"
|
||||
#include "video_core/vulkan_common/vulkan_surface.h"
|
||||
|
||||
|
||||
#import <mach/mach.h>
|
||||
|
||||
@implementation AppUIObjC
|
||||
-(AppUIObjC *) init {
|
||||
if (self = [super init]) {
|
||||
_gameInformation = [AppUIGameInformation sharedInstance];
|
||||
|
||||
NSURL *dir_url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] firstObject];
|
||||
const char *directory_cstr = [[dir_url path] UTF8String];
|
||||
|
||||
Common::FS::SetAppDirectory(directory_cstr);
|
||||
// Config{"config", Config::ConfigType::GlobalConfig};
|
||||
|
||||
EmulationSession::GetInstance().System().Initialize();
|
||||
EmulationSession::GetInstance().InitializeSystem(false);
|
||||
EmulationSession::GetInstance().InitializeGpuDriver();
|
||||
|
||||
|
||||
Settings::values.dump_shaders.SetValue(true);
|
||||
Settings::values.use_asynchronous_shaders.SetValue(true);
|
||||
// Settings::values.astc_recompression.SetValue(Settings::AstcRecompression::Bc3);
|
||||
// Settings::values.resolution_setup.SetValue(Settings::ResolutionSetup::Res1X);
|
||||
// Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::Bilinear);
|
||||
} return self;
|
||||
}
|
||||
|
||||
|
||||
+(AppUIObjC *) sharedInstance {
|
||||
static AppUIObjC *sharedInstance = NULL;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedInstance = [[self alloc] init];
|
||||
});
|
||||
return sharedInstance;
|
||||
}
|
||||
|
||||
- (BOOL)ispaused {
|
||||
return EmulationSession::GetInstance().IsPaused();
|
||||
}
|
||||
|
||||
-(void) pause {
|
||||
EmulationSession::GetInstance().System().Pause();
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
EmulationSession::GetInstance().PauseEmulation();
|
||||
}
|
||||
|
||||
-(void) play {
|
||||
|
||||
EmulationSession::GetInstance().System().Run();
|
||||
EmulationSession::GetInstance().RunEmulation();
|
||||
EmulationSession::GetInstance().UnPauseEmulation();
|
||||
}
|
||||
|
||||
-(BOOL)hasfirstfame {
|
||||
@try {
|
||||
auto* window = &EmulationSession::GetInstance().Window();
|
||||
if (window && window->HasFirstFrame()) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
NSLog(@"Exception occurred: %@", exception);
|
||||
// Handle the exception, maybe return a default value
|
||||
return NO;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)canGetFullPath {
|
||||
@try {
|
||||
Core::System& system = EmulationSession::GetInstance().System();
|
||||
auto bis_system = system.GetFileSystemController().GetSystemNANDContents();
|
||||
|
||||
if (bis_system == nullptr) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
constexpr u64 QLaunchId = static_cast<u64>(Service::AM::AppletProgramId::QLaunch);
|
||||
auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
|
||||
|
||||
if (qlaunch_applet_nca == nullptr) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
const auto filename = qlaunch_applet_nca->GetFullPath();
|
||||
|
||||
// If GetFullPath() is successful
|
||||
return YES;
|
||||
} @catch (NSException *exception) {
|
||||
// Handle the exception if needed
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
|
||||
-(void) quit {
|
||||
EmulationSession::GetInstance().ShutdownEmulation();
|
||||
}
|
||||
|
||||
-(void) configureLayer:(CAMetalLayer *)layer withSize:(CGSize)size {
|
||||
_layer = layer;
|
||||
_size = size;
|
||||
EmulationSession::GetInstance().SetNativeWindow(layer, size);
|
||||
}
|
||||
|
||||
-(void) bootOS {
|
||||
EmulationSession::GetInstance().BootOS();
|
||||
}
|
||||
|
||||
-(void) insertGame:(NSURL *)url {
|
||||
EmulationSession::GetInstance().InitializeEmulation([url.path UTF8String], [_gameInformation informationForGame:url].programID, true);
|
||||
}
|
||||
|
||||
-(void) insertGames:(NSArray<NSURL *> *)games {
|
||||
for (NSURL *url in games) {
|
||||
EmulationSession::GetInstance().ConfigureFilesystemProvider([url.path UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
-(void) step {
|
||||
void(EmulationSession::GetInstance().System().Run());
|
||||
}
|
||||
|
||||
-(void) touchBeganAtPoint:(CGPoint)point index:(NSUInteger)index {
|
||||
float h_ratio, w_ratio;
|
||||
h_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().height / (_size.height * [[UIScreen mainScreen] nativeScale]);
|
||||
w_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().width / (_size.width * [[UIScreen mainScreen] nativeScale]);
|
||||
|
||||
EmulationSession::GetInstance().Window().OnTouchPressed([[NSNumber numberWithUnsignedInteger:index] intValue],
|
||||
(point.x) * [[UIScreen mainScreen] nativeScale] * w_ratio,
|
||||
((point.y) * [[UIScreen mainScreen] nativeScale] * h_ratio));
|
||||
}
|
||||
|
||||
-(void) touchEndedForIndex:(NSUInteger)index {
|
||||
EmulationSession::GetInstance().Window().OnTouchReleased([[NSNumber numberWithUnsignedInteger:index] intValue]);
|
||||
}
|
||||
|
||||
-(void) touchMovedAtPoint:(CGPoint)point index:(NSUInteger)index {
|
||||
float h_ratio, w_ratio;
|
||||
h_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().height / (_size.height * [[UIScreen mainScreen] nativeScale]);
|
||||
w_ratio = EmulationSession::GetInstance().Window().GetFramebufferLayout().width / (_size.width * [[UIScreen mainScreen] nativeScale]);
|
||||
|
||||
EmulationSession::GetInstance().Window().OnTouchMoved([[NSNumber numberWithUnsignedInteger:index] intValue],
|
||||
(point.x) * [[UIScreen mainScreen] nativeScale] * w_ratio,
|
||||
((point.y) * [[UIScreen mainScreen] nativeScale] * h_ratio));
|
||||
}
|
||||
|
||||
-(void) thumbstickMoved:(VirtualControllerAnalogType)analog
|
||||
x:(CGFloat)x
|
||||
y:(CGFloat)y
|
||||
controllerId:(int)controllerId {
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId);
|
||||
EmulationSession::GetInstance().Window().OnGamepadJoystickEvent(controllerId, [[NSNumber numberWithUnsignedInteger:analog] intValue], CGFloat(x), CGFloat(y));
|
||||
}
|
||||
|
||||
-(void) virtualControllerButtonDown:(VirtualControllerButtonType)button
|
||||
controllerId:(int)controllerId {
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId);
|
||||
EmulationSession::GetInstance().Window().OnGamepadButtonEvent(controllerId, [[NSNumber numberWithUnsignedInteger:button] intValue], true);
|
||||
}
|
||||
|
||||
-(void) virtualControllerGyro:(int)controllerId
|
||||
deltaTimestamp:(int)delta_timestamp
|
||||
gyroX:(float)gyro_x
|
||||
gyroY:(float)gyro_y
|
||||
gyroZ:(float)gyro_z
|
||||
accelX:(float)accel_x
|
||||
accelY:(float)accel_y
|
||||
accelZ:(float)accel_z
|
||||
{
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId);
|
||||
EmulationSession::GetInstance().Window().OnGamepadMotionEvent(controllerId, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
|
||||
}
|
||||
|
||||
|
||||
-(void) virtualControllerButtonUp:(VirtualControllerButtonType)button
|
||||
controllerId:(int)controllerId {
|
||||
EmulationSession::GetInstance().OnGamepadConnectEvent(controllerId);
|
||||
EmulationSession::GetInstance().Window().OnGamepadButtonEvent(controllerId, [[NSNumber numberWithUnsignedInteger:button] intValue], false);
|
||||
}
|
||||
|
||||
|
||||
-(void) orientationChanged:(UIInterfaceOrientation)orientation with:(CAMetalLayer *)layer size:(CGSize)size {
|
||||
_layer = layer;
|
||||
_size = size;
|
||||
EmulationSession::GetInstance().Window().OnSurfaceChanged(layer, size);
|
||||
}
|
||||
|
||||
-(void) settingsChanged {
|
||||
//
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,86 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
enable_language(Swift OBJCXX)
|
||||
add_executable(eden-ios
|
||||
AppUI-Bridging-Header.h
|
||||
AppUI.swift
|
||||
AppUIGameInformation.h
|
||||
AppUIGameInformation.mm
|
||||
AppUIObjC.h
|
||||
AppUIObjC.mm
|
||||
Config.h
|
||||
Config.mm
|
||||
EmulationSession.h
|
||||
EmulationSession.mm
|
||||
EmulationWindow.h
|
||||
EmulationWindow.mm
|
||||
VMA.cpp
|
||||
|
||||
EmulationGame.swift
|
||||
JoystickView.swift
|
||||
EmulationHandler.swift
|
||||
PomeloApp.swift
|
||||
FileManager.swift
|
||||
EmulationView.swift
|
||||
LibraryView.swift
|
||||
KeyboardHostingController.swift
|
||||
FolderMonitor.swift
|
||||
GameButtonView.swift
|
||||
EmulationScreenView.swift
|
||||
GameListView.swift
|
||||
|
||||
# Extensions
|
||||
SwiftUIJoystick.swift
|
||||
)
|
||||
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER "dev.eden-emu.eden")
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME "Eden")
|
||||
set(MACOSX_BUNDLE_INFO_STRING "Eden: A high-performance Nintendo Switch emulator")
|
||||
|
||||
# TODO(crueter): Copyright, and versioning
|
||||
|
||||
# Keep bundle identifier as-is, for compatibility sake
|
||||
set_target_properties(eden-ios PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/AppUI-Bridging-Header.h"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "eden-ios-Swift.h"
|
||||
XCODE_ATTRIBUTE_DERIVED_FILE_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
|
||||
target_link_libraries(eden-ios PRIVATE common core input_common frontend_common video_core sirit::sirit)
|
||||
target_link_libraries(eden-ios PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
target_link_libraries(eden-ios PRIVATE SDL3::SDL3 glad stb::headers)
|
||||
create_target_directory_groups(eden-ios)
|
||||
|
||||
# FIXME(crueter): This should /all/ be in a module of some kind!
|
||||
|
||||
# Xcode will automatically generate the Assets.car and icns file for us.
|
||||
set(_dist "${CMAKE_SOURCE_DIR}/dist")
|
||||
if (CMAKE_GENERATOR MATCHES "Xcode")
|
||||
set(_icons "${_dist}/eden.icon")
|
||||
|
||||
set_target_properties(eden-ios PROPERTIES
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME eden
|
||||
MACOSX_BUNDLE_ICON_FILE eden
|
||||
# Also force xcode to manage signing for us.
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED ON
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED ON
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic)
|
||||
# Otherwise, we'll use our own.
|
||||
else()
|
||||
set(_icons "${_dist}/eden.icns" "${_dist}/Assets.car")
|
||||
endif()
|
||||
|
||||
set_source_files_properties(${_icons} PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources)
|
||||
target_sources(eden-ios PRIVATE ${_icons})
|
||||
|
||||
set_target_properties(eden-ios PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib")
|
||||
find_library(MOLTENVK_LIBRARY MoltenVK REQUIRED)
|
||||
message(STATUS "Using MoltenVK at ${MOLTENVK_LIBRARY}.")
|
||||
|
||||
set_source_files_properties(${MOLTENVK_LIBRARY} PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Frameworks
|
||||
XCODE_FILE_ATTRIBUTES "CodeSignOnCopy")
|
||||
target_sources(eden-ios PRIVATE ${MOLTENVK_LIBRARY})
|
||||
@@ -0,0 +1,19 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <common/settings_common.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_setting.h"
|
||||
#include "common/settings_enums.h"
|
||||
|
||||
namespace IOSSettings {
|
||||
struct Values {
|
||||
Settings::Linkage linkage;
|
||||
Settings::Setting<bool> touchscreen{linkage, true, "touchscreen", Settings::Category::Overlay};
|
||||
};
|
||||
|
||||
extern Values values;
|
||||
|
||||
} // namespace IOSSettings
|
||||
@@ -0,0 +1,11 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
namespace IOSSettings {
|
||||
|
||||
Values values;
|
||||
|
||||
} // namespace IOSSettings
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import Foundation
|
||||
|
||||
struct EmulationGame : Comparable, Hashable, Identifiable {
|
||||
var id = UUID()
|
||||
|
||||
let developer: String
|
||||
let fileURL: URL
|
||||
let imageData: Data
|
||||
let title: String
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
hasher.combine(developer)
|
||||
hasher.combine(fileURL)
|
||||
hasher.combine(imageData)
|
||||
hasher.combine(title)
|
||||
}
|
||||
|
||||
static func < (lhs: EmulationGame, rhs: EmulationGame) -> Bool {
|
||||
lhs.title < rhs.title
|
||||
}
|
||||
|
||||
static func == (lhs: EmulationGame, rhs: EmulationGame) -> Bool {
|
||||
lhs.title == rhs.title
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
|
||||
import Metal
|
||||
import Foundation
|
||||
|
||||
class EmulationSessionViewModel: ObservableObject {
|
||||
@Published var isShowingCustomButton = true
|
||||
@State var should = false
|
||||
var device: MTLDevice?
|
||||
@State var mtkView: MTKView = MTKView()
|
||||
var CaLayer: CAMetalLayer?
|
||||
private var sudachiGame: EmulationGame?
|
||||
private let appui = AppUI.shared
|
||||
private var thread: Thread!
|
||||
private var isRunning = false
|
||||
var doesneedresources = false
|
||||
@State var iscustom: Bool = false
|
||||
|
||||
init(game: EmulationGame?) {
|
||||
self.device = MTLCreateSystemDefaultDevice()
|
||||
self.sudachiGame = game
|
||||
}
|
||||
|
||||
func configureAppUI(with mtkView: MTKView) {
|
||||
self.mtkView = mtkView
|
||||
device = self.mtkView.device
|
||||
guard !isRunning else { return }
|
||||
isRunning = true
|
||||
appui.configure(layer: mtkView.layer as! CAMetalLayer, with: mtkView.frame.size)
|
||||
|
||||
iscustom = ((sudachiGame?.fileURL.startAccessingSecurityScopedResource()) != nil)
|
||||
|
||||
DispatchQueue.global(qos: .userInitiated).async { [self] in
|
||||
if let sudachiGame = self.sudachiGame {
|
||||
self.appui.insert(game: sudachiGame.fileURL)
|
||||
} else {
|
||||
self.appui.bootOS()
|
||||
}
|
||||
}
|
||||
|
||||
thread = .init(block: self.step)
|
||||
thread.name = "Yuzu"
|
||||
thread.qualityOfService = .userInteractive
|
||||
thread.threadPriority = 0.9
|
||||
thread.start()
|
||||
}
|
||||
|
||||
private func step() {
|
||||
while true {
|
||||
appui.step()
|
||||
}
|
||||
}
|
||||
|
||||
func customButtonTapped() {
|
||||
stopEmulation()
|
||||
}
|
||||
|
||||
private func stopEmulation() {
|
||||
if isRunning {
|
||||
isRunning = false
|
||||
appui.exit()
|
||||
thread.cancel()
|
||||
if iscustom {
|
||||
sudachiGame?.fileURL.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleOrientationChange() {
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let interfaceOrientation = self.getInterfaceOrientation(from: UIDevice.current.orientation)
|
||||
self.appui.orientationChanged(orientation: interfaceOrientation, with: self.mtkView.layer as! CAMetalLayer, size: mtkView.frame.size)
|
||||
}
|
||||
}
|
||||
|
||||
private func getInterfaceOrientation(from deviceOrientation: UIDeviceOrientation) -> UIInterfaceOrientation {
|
||||
switch deviceOrientation {
|
||||
case .portrait:
|
||||
return .portrait
|
||||
case .portraitUpsideDown:
|
||||
return .portraitUpsideDown
|
||||
case .landscapeLeft:
|
||||
return .landscapeRight
|
||||
case .landscapeRight:
|
||||
return .landscapeLeft
|
||||
default:
|
||||
return .unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
|
||||
import MetalKit
|
||||
|
||||
class EmulationScreenView: UIView {
|
||||
var primaryScreen: UIView!
|
||||
var portraitconstraints = [NSLayoutConstraint]()
|
||||
var landscapeconstraints = [NSLayoutConstraint]()
|
||||
var fullscreenconstraints = [NSLayoutConstraint]()
|
||||
let appui = AppUI.shared
|
||||
let userDefaults = UserDefaults.standard
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
setupAppUIScreenforiPad()
|
||||
} else {
|
||||
setupAppUIScreen()
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
setupAppUIScreenforiPad()
|
||||
} else {
|
||||
setupAppUIScreen()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
guard let touch = touches.first else {
|
||||
return
|
||||
}
|
||||
|
||||
print("Location: \(touch.location(in: primaryScreen))")
|
||||
appui.touchBegan(at: touch.location(in: primaryScreen), for: 0)
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
print("Touch Ended")
|
||||
appui.touchEnded(for: 0)
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
guard let touch = touches.first else {
|
||||
return
|
||||
}
|
||||
let location = touch.location(in: primaryScreen)
|
||||
print("Location Moved: \(location)")
|
||||
appui.touchMoved(at: location, for: 0)
|
||||
}
|
||||
|
||||
func setupAppUIScreenforiPad() {
|
||||
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
|
||||
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
|
||||
primaryScreen.clipsToBounds = true
|
||||
primaryScreen.layer.borderColor = UIColor.secondarySystemBackground.cgColor
|
||||
primaryScreen.layer.borderWidth = 3
|
||||
primaryScreen.layer.cornerCurve = .continuous
|
||||
primaryScreen.layer.cornerRadius = 10
|
||||
addSubview(primaryScreen)
|
||||
|
||||
|
||||
portraitconstraints = [
|
||||
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
|
||||
primaryScreen.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 10),
|
||||
primaryScreen.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -10),
|
||||
primaryScreen.heightAnchor.constraint(equalTo: primaryScreen.widthAnchor, multiplier: 9 / 16),
|
||||
]
|
||||
|
||||
landscapeconstraints = [
|
||||
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 50),
|
||||
primaryScreen.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -100),
|
||||
primaryScreen.widthAnchor.constraint(equalTo: primaryScreen.heightAnchor, multiplier: 16 / 9),
|
||||
primaryScreen.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
|
||||
]
|
||||
|
||||
|
||||
updateConstraintsForOrientation()
|
||||
}
|
||||
|
||||
|
||||
|
||||
func setupAppUIScreen() {
|
||||
primaryScreen = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
|
||||
primaryScreen.translatesAutoresizingMaskIntoConstraints = false
|
||||
primaryScreen.clipsToBounds = true
|
||||
primaryScreen.layer.borderColor = UIColor.secondarySystemBackground.cgColor
|
||||
primaryScreen.layer.borderWidth = 3
|
||||
primaryScreen.layer.cornerCurve = .continuous
|
||||
primaryScreen.layer.cornerRadius = 10
|
||||
addSubview(primaryScreen)
|
||||
|
||||
|
||||
portraitconstraints = [
|
||||
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
|
||||
primaryScreen.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 10),
|
||||
primaryScreen.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor, constant: -10),
|
||||
primaryScreen.heightAnchor.constraint(equalTo: primaryScreen.widthAnchor, multiplier: 9 / 16),
|
||||
]
|
||||
|
||||
landscapeconstraints = [
|
||||
primaryScreen.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor, constant: 10),
|
||||
primaryScreen.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor, constant: -10),
|
||||
primaryScreen.widthAnchor.constraint(equalTo: primaryScreen.heightAnchor, multiplier: 16 / 9),
|
||||
primaryScreen.centerXAnchor.constraint(equalTo: safeAreaLayoutGuide.centerXAnchor),
|
||||
]
|
||||
|
||||
updateConstraintsForOrientation()
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
updateConstraintsForOrientation()
|
||||
}
|
||||
|
||||
private func updateConstraintsForOrientation() {
|
||||
removeConstraints(portraitconstraints)
|
||||
removeConstraints(landscapeconstraints)
|
||||
let isPortrait = UIApplication.shared.statusBarOrientation.isPortrait
|
||||
addConstraints(isPortrait ? portraitconstraints : landscapeconstraints)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
#if __has_include(<Metal/Metal.hpp>)
|
||||
#import <Metal/Metal.hpp>
|
||||
#else
|
||||
#import <Metal/Metal.h>
|
||||
#endif
|
||||
#import "EmulationWindow.h"
|
||||
|
||||
#include "common/detached_tasks.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
class EmulationSession final {
|
||||
public:
|
||||
explicit EmulationSession();
|
||||
~EmulationSession() = default;
|
||||
|
||||
static EmulationSession& GetInstance();
|
||||
const Core::System& System() const;
|
||||
Core::System& System();
|
||||
FileSys::ManualContentProvider* GetContentProvider();
|
||||
InputCommon::InputSubsystem& GetInputSubsystem();
|
||||
|
||||
const EmulationWindow& Window() const;
|
||||
EmulationWindow& Window();
|
||||
CAMetalLayer* NativeWindow() const;
|
||||
void SetNativeWindow(CAMetalLayer* native_window, CGSize size);
|
||||
void SurfaceChanged();
|
||||
|
||||
void InitializeGpuDriver();
|
||||
|
||||
bool IsRunning() const;
|
||||
bool IsPaused() const;
|
||||
void PauseEmulation();
|
||||
void UnPauseEmulation();
|
||||
void HaltEmulation();
|
||||
void RunEmulation();
|
||||
void ShutdownEmulation();
|
||||
|
||||
const Core::PerfStatsResults& PerfStats();
|
||||
void ConfigureFilesystemProvider(const std::string& filepath);
|
||||
void InitializeSystem(bool reload);
|
||||
void SetAppletId(int applet_id);
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath,
|
||||
const std::size_t program_index,
|
||||
const bool frontend_initiated);
|
||||
Core::SystemResultStatus BootOS();
|
||||
|
||||
static void OnEmulationStarted();
|
||||
static u64 GetProgramId(std::string programId);
|
||||
bool IsInitialized() { return is_initialized; };
|
||||
|
||||
bool IsHandheldOnly();
|
||||
void SetDeviceType([[maybe_unused]] int index, int type);
|
||||
void OnGamepadConnectEvent([[maybe_unused]] int index);
|
||||
void OnGamepadDisconnectEvent([[maybe_unused]] int index);
|
||||
private:
|
||||
static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max);
|
||||
static void OnEmulationStopped(Core::SystemResultStatus result);
|
||||
static void ChangeProgram(std::size_t program_index);
|
||||
|
||||
private:
|
||||
// Window management
|
||||
std::unique_ptr<EmulationWindow> m_window;
|
||||
CAMetalLayer* m_native_window{};
|
||||
|
||||
// Core emulation
|
||||
Core::System m_system;
|
||||
InputCommon::InputSubsystem m_input_subsystem;
|
||||
Common::DetachedTasks m_detached_tasks;
|
||||
Core::PerfStatsResults m_perf_stats{};
|
||||
std::shared_ptr<FileSys::VfsFilesystem> m_vfs;
|
||||
Core::SystemResultStatus m_load_result{Core::SystemResultStatus::ErrorNotInitialized};
|
||||
std::atomic<bool> m_is_running = false;
|
||||
std::atomic<bool> m_is_paused = false;
|
||||
std::unique_ptr<FileSys::ManualContentProvider> m_manual_provider;
|
||||
int m_applet_id{1};
|
||||
|
||||
// GPU driver parameters
|
||||
std::shared_ptr<Common::DynamicLibrary> m_vulkan_library;
|
||||
|
||||
// Synchronization
|
||||
std::condition_variable_any m_cv;
|
||||
mutable std::mutex m_mutex;
|
||||
bool is_initialized = false;
|
||||
CGSize m_size;
|
||||
|
||||
// Program index for next boot
|
||||
std::atomic<s32> m_next_program_index = -1;
|
||||
};
|
||||
@@ -0,0 +1,474 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#import "EmulationSession.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <dlfcn.h>
|
||||
|
||||
#include "common/fs/fs.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/loader/nro.h"
|
||||
#include "frontend_common/content_manager.h"
|
||||
|
||||
#include "common/detached_tasks.h"
|
||||
#include "common/dynamic_library.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/scm_rev.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/fs_filesystem.h"
|
||||
#include "core/file_sys/submission_package.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/vfs/vfs_real.h"
|
||||
#include "core/frontend/applets/cabinet.h"
|
||||
#include "core/frontend/applets/controller.h"
|
||||
#include "core/frontend/applets/error.h"
|
||||
#include "core/frontend/applets/general.h"
|
||||
#include "core/frontend/applets/mii_edit.h"
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
#include "core/hle/service/am/applet_manager.h"
|
||||
#include "core/hle/service/am/frontend/applets.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "frontend_common/config.h"
|
||||
#include "hid_core/frontend/emulated_controller.h"
|
||||
#include "hid_core/hid_core.h"
|
||||
#include "hid_core/hid_types.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||
#include "video_core/vulkan_common/vulkan_instance.h"
|
||||
#include "video_core/vulkan_common/vulkan_surface.h"
|
||||
|
||||
#define jconst [[maybe_unused]] const auto
|
||||
#define jauto [[maybe_unused]] auto
|
||||
|
||||
static EmulationSession s_instance;
|
||||
|
||||
EmulationSession::EmulationSession() {
|
||||
m_vfs = std::make_shared<FileSys::RealVfsFilesystem>();
|
||||
}
|
||||
|
||||
EmulationSession& EmulationSession::GetInstance() {
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
const Core::System& EmulationSession::System() const {
|
||||
return m_system;
|
||||
}
|
||||
|
||||
Core::System& EmulationSession::System() {
|
||||
return m_system;
|
||||
}
|
||||
|
||||
FileSys::ManualContentProvider* EmulationSession::GetContentProvider() {
|
||||
return m_manual_provider.get();
|
||||
}
|
||||
|
||||
InputCommon::InputSubsystem& EmulationSession::GetInputSubsystem() {
|
||||
return m_input_subsystem;
|
||||
}
|
||||
|
||||
const EmulationWindow& EmulationSession::Window() const {
|
||||
return *m_window;
|
||||
}
|
||||
|
||||
EmulationWindow& EmulationSession::Window() {
|
||||
return *m_window;
|
||||
}
|
||||
|
||||
CAMetalLayer* EmulationSession::NativeWindow() const {
|
||||
return m_native_window;
|
||||
}
|
||||
|
||||
void EmulationSession::SetNativeWindow(CAMetalLayer* native_window, CGSize size) {
|
||||
m_native_window = native_window;
|
||||
m_size = size;
|
||||
}
|
||||
|
||||
void EmulationSession::InitializeGpuDriver() {
|
||||
m_vulkan_library = std::make_shared<Common::DynamicLibrary>(dlopen("@executable_path/Frameworks/MoltenVK", RTLD_NOW));
|
||||
}
|
||||
|
||||
bool EmulationSession::IsRunning() const {
|
||||
return m_is_running;
|
||||
}
|
||||
|
||||
bool EmulationSession::IsPaused() const {
|
||||
return m_is_running && m_is_paused;
|
||||
}
|
||||
|
||||
const Core::PerfStatsResults& EmulationSession::PerfStats() {
|
||||
m_perf_stats = m_system.GetAndResetPerfStats();
|
||||
return m_perf_stats;
|
||||
}
|
||||
|
||||
void EmulationSession::SurfaceChanged() {
|
||||
if (!IsRunning()) {
|
||||
return;
|
||||
}
|
||||
m_window->OnSurfaceChanged(m_native_window, m_size);
|
||||
}
|
||||
|
||||
void EmulationSession::ConfigureFilesystemProvider(const std::string& filepath) {
|
||||
const auto file = m_system.GetFilesystem()->OpenFile(filepath, FileSys::OpenMode::Read);
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto loader = Loader::GetLoader(m_system, file);
|
||||
if (!loader) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto file_type = loader->GetFileType();
|
||||
if (file_type == Loader::FileType::Unknown || file_type == Loader::FileType::Error) {
|
||||
return;
|
||||
}
|
||||
|
||||
u64 program_id = 0;
|
||||
const auto res2 = loader->ReadProgramId(program_id);
|
||||
if (res2 == Loader::ResultStatus::Success && file_type == Loader::FileType::NCA) {
|
||||
m_manual_provider->AddEntry(FileSys::TitleType::Application,
|
||||
FileSys::GetCRTypeFromNCAType(FileSys::NCA{file}.GetType()),
|
||||
program_id, file);
|
||||
} else if (res2 == Loader::ResultStatus::Success &&
|
||||
(file_type == Loader::FileType::XCI || file_type == Loader::FileType::NSP)) {
|
||||
const auto nsp = file_type == Loader::FileType::NSP
|
||||
? std::make_shared<FileSys::NSP>(file)
|
||||
: FileSys::XCI{file}.GetSecurePartitionNSP();
|
||||
for (const auto& title : nsp->GetNCAs()) {
|
||||
for (const auto& entry : title.second) {
|
||||
m_manual_provider->AddEntry(entry.first.first, entry.first.second, title.first,
|
||||
entry.second->GetBaseFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSession::InitializeSystem(bool reload) {
|
||||
if (!reload) {
|
||||
SDL_SetMainReady();
|
||||
|
||||
// Initialize logging system
|
||||
Common::Log::Initialize();
|
||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||
Common::Log::Start();
|
||||
}
|
||||
|
||||
// Initialize filesystem.
|
||||
m_system.SetFilesystem(m_vfs);
|
||||
m_system.GetUserChannel().clear();
|
||||
m_manual_provider = std::make_unique<FileSys::ManualContentProvider>();
|
||||
m_system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
|
||||
m_system.RegisterContentProvider(FileSys::ContentProviderUnionSlot::FrontendManual,
|
||||
m_manual_provider.get());
|
||||
m_system.GetFileSystemController().CreateFactories(*m_vfs);
|
||||
|
||||
is_initialized = true;
|
||||
}
|
||||
|
||||
void EmulationSession::SetAppletId(int applet_id) {
|
||||
m_applet_id = applet_id;
|
||||
m_system.GetFrontendAppletHolder().SetCurrentAppletId(
|
||||
static_cast<Service::AM::AppletId>(m_applet_id));
|
||||
}
|
||||
|
||||
Core::SystemResultStatus EmulationSession::InitializeEmulation(const std::string& filepath,
|
||||
const std::size_t program_index,
|
||||
const bool frontend_initiated) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
// Create the render window.
|
||||
m_window = std::make_unique<EmulationWindow>(&m_input_subsystem, m_native_window, m_size, m_vulkan_library);
|
||||
|
||||
// Initialize system.
|
||||
m_system.SetShuttingDown(false);
|
||||
m_system.ApplySettings();
|
||||
Settings::LogSettings();
|
||||
m_system.HIDCore().ReloadInputDevices();
|
||||
m_system.SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet{});
|
||||
|
||||
// Initialize filesystem.
|
||||
ConfigureFilesystemProvider(filepath);
|
||||
|
||||
// Load the ROM.
|
||||
Service::AM::FrontendAppletParameters params{
|
||||
.applet_id = static_cast<Service::AM::AppletId>(m_applet_id),
|
||||
.launch_type = frontend_initiated ? Service::AM::LaunchType::FrontendInitiated
|
||||
: Service::AM::LaunchType::ApplicationInitiated,
|
||||
.program_index = static_cast<s32>(program_index),
|
||||
};
|
||||
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filepath, params);
|
||||
if (m_load_result != Core::SystemResultStatus::Success) {
|
||||
return m_load_result;
|
||||
}
|
||||
|
||||
// Complete initialization.
|
||||
m_system.GPU().Start();
|
||||
m_system.GetCpuManager().OnGpuReady();
|
||||
m_system.RegisterExitCallback([&] { HaltEmulation(); });
|
||||
|
||||
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
||||
m_system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
m_system.GetApplicationProcessProgramID(), std::stop_token{},
|
||||
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
|
||||
}
|
||||
|
||||
// Register an ExecuteProgram callback such that Core can execute a sub-program
|
||||
m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) {
|
||||
m_next_program_index = program_index_;
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
ChangeProgram(m_next_program_index);
|
||||
});
|
||||
|
||||
OnEmulationStarted();
|
||||
return Core::SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
Core::SystemResultStatus EmulationSession::BootOS() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
// Create the render window.
|
||||
m_window = std::make_unique<EmulationWindow>(&m_input_subsystem, m_native_window, m_size, m_vulkan_library);
|
||||
|
||||
// Initialize system.
|
||||
m_system.SetShuttingDown(false);
|
||||
m_system.ApplySettings();
|
||||
Settings::LogSettings();
|
||||
m_system.HIDCore().ReloadInputDevices();
|
||||
m_system.SetFrontendAppletSet(Service::AM::Frontend::FrontendAppletSet{});
|
||||
|
||||
constexpr u64 QLaunchId = static_cast<u64>(Service::AM::AppletProgramId::QLaunch);
|
||||
auto bis_system = m_system.GetFileSystemController().GetSystemNANDContents();
|
||||
|
||||
auto qlaunch_applet_nca = bis_system->GetEntry(QLaunchId, FileSys::ContentRecordType::Program);
|
||||
|
||||
m_system.GetFrontendAppletHolder().SetCurrentAppletId(Service::AM::AppletId::QLaunch);
|
||||
|
||||
const auto filename = qlaunch_applet_nca->GetFullPath();
|
||||
|
||||
auto params = Service::AM::FrontendAppletParameters {
|
||||
.program_id = QLaunchId,
|
||||
.applet_id = Service::AM::AppletId::QLaunch,
|
||||
.applet_type = Service::AM::AppletType::LibraryApplet
|
||||
};
|
||||
|
||||
m_load_result = m_system.Load(EmulationSession::GetInstance().Window(), filename, params);
|
||||
|
||||
if (m_load_result != Core::SystemResultStatus::Success) {
|
||||
return m_load_result;
|
||||
}
|
||||
|
||||
// Complete initialization.
|
||||
m_system.GPU().Start();
|
||||
m_system.GetCpuManager().OnGpuReady();
|
||||
m_system.RegisterExitCallback([&] { HaltEmulation(); });
|
||||
|
||||
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
||||
m_system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
m_system.GetApplicationProcessProgramID(), std::stop_token{},
|
||||
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
|
||||
}
|
||||
|
||||
// Register an ExecuteProgram callback such that Core can execute a sub-program
|
||||
m_system.RegisterExecuteProgramCallback([&](std::size_t program_index_) {
|
||||
m_next_program_index = program_index_;
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
});
|
||||
|
||||
OnEmulationStarted();
|
||||
return Core::SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
void EmulationSession::ShutdownEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
if (m_next_program_index != -1) {
|
||||
ChangeProgram(m_next_program_index);
|
||||
m_next_program_index = -1;
|
||||
}
|
||||
|
||||
m_is_running = false;
|
||||
|
||||
// Unload user input.
|
||||
m_system.HIDCore().UnloadInputDevices();
|
||||
|
||||
// Enable all controllers
|
||||
m_system.HIDCore().SetSupportedStyleTag({Core::HID::NpadStyleSet::All});
|
||||
|
||||
// Shutdown the main emulated process
|
||||
if (m_load_result == Core::SystemResultStatus::Success) {
|
||||
m_system.DetachDebugger();
|
||||
m_system.ShutdownMainProcess();
|
||||
m_detached_tasks.WaitForAllTasks();
|
||||
m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
|
||||
m_window.reset();
|
||||
OnEmulationStopped(Core::SystemResultStatus::Success);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tear down the render window.
|
||||
m_window.reset();
|
||||
}
|
||||
|
||||
void EmulationSession::PauseEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.Pause();
|
||||
m_is_paused = true;
|
||||
}
|
||||
|
||||
void EmulationSession::UnPauseEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_system.Run();
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
void EmulationSession::HaltEmulation() {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_is_running = false;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
|
||||
void EmulationSession::RunEmulation() {
|
||||
{
|
||||
std::scoped_lock lock(m_mutex);
|
||||
m_is_running = true;
|
||||
}
|
||||
|
||||
// Load the disk shader cache.
|
||||
if (Settings::values.use_disk_shader_cache.GetValue()) {
|
||||
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
m_system.Renderer().ReadRasterizer()->LoadDiskResources(
|
||||
m_system.GetApplicationProcessProgramID(), std::stop_token{}, LoadDiskCacheProgress);
|
||||
LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
|
||||
}
|
||||
|
||||
void(m_system.Run());
|
||||
|
||||
if (m_system.DebuggerEnabled()) {
|
||||
m_system.InitializeDebugger();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
{
|
||||
[[maybe_unused]] std::unique_lock lock(m_mutex);
|
||||
if (m_cv.wait_for(lock, std::chrono::milliseconds(800),
|
||||
[&]() { return !m_is_running; })) {
|
||||
// Emulation halted.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset current applet ID.
|
||||
m_applet_id = static_cast<int>(Service::AM::AppletId::Application);
|
||||
}
|
||||
|
||||
void EmulationSession::LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress,
|
||||
int max) {
|
||||
|
||||
}
|
||||
|
||||
void EmulationSession::OnEmulationStarted() {
|
||||
|
||||
}
|
||||
|
||||
void EmulationSession::OnEmulationStopped(Core::SystemResultStatus result) {
|
||||
|
||||
}
|
||||
|
||||
void EmulationSession::ChangeProgram(std::size_t program_index) {
|
||||
LOG_INFO(Frontend, "Trying To Switch Program");
|
||||
// Halt current emulation session
|
||||
EmulationSession::GetInstance().HaltEmulation();
|
||||
// Save the current state if necessary
|
||||
|
||||
// Shutdown the current emulation session cleanly
|
||||
// Update the program index
|
||||
EmulationSession::GetInstance().m_next_program_index = program_index;
|
||||
|
||||
// Initialize the new program
|
||||
// Start the new emulation session
|
||||
EmulationSession::GetInstance().RunEmulation();
|
||||
}
|
||||
|
||||
u64 EmulationSession::GetProgramId(std::string programId) {
|
||||
try {
|
||||
return std::stoull(programId);
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool EmulationSession::IsHandheldOnly() {
|
||||
jconst npad_style_set = m_system.HIDCore().GetSupportedStyleTag();
|
||||
|
||||
if (npad_style_set.fullkey == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (npad_style_set.handheld == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !Settings::IsDockedMode();
|
||||
}
|
||||
|
||||
void EmulationSession::SetDeviceType([[maybe_unused]] int index, int type) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->SetNpadStyleIndex(static_cast<Core::HID::NpadStyleIndex>(type));
|
||||
}
|
||||
|
||||
void EmulationSession::OnGamepadConnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
|
||||
// Ensure that player1 is configured correctly and handheld disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Player1) {
|
||||
jauto handheld = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Handheld);
|
||||
|
||||
if (controller->GetNpadStyleIndex() == Core::HID::NpadStyleIndex::Handheld) {
|
||||
handheld->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
|
||||
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Fullkey);
|
||||
handheld->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that handheld is configured correctly and player 1 disconnected
|
||||
if (controller->GetNpadIdType() == Core::HID::NpadIdType::Handheld) {
|
||||
jauto player1 = m_system.HIDCore().GetEmulatedController(Core::HID::NpadIdType::Player1);
|
||||
|
||||
if (controller->GetNpadStyleIndex() != Core::HID::NpadStyleIndex::Handheld) {
|
||||
player1->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
controller->SetNpadStyleIndex(Core::HID::NpadStyleIndex::Handheld);
|
||||
player1->Disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
if (!controller->IsConnected()) {
|
||||
controller->Connect();
|
||||
}
|
||||
}
|
||||
|
||||
void EmulationSession::OnGamepadDisconnectEvent([[maybe_unused]] int index) {
|
||||
jauto controller = m_system.HIDCore().GetEmulatedControllerByIndex(index);
|
||||
controller->Disconnect();
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
|
||||
import Foundation
|
||||
import GameController
|
||||
import UIKit
|
||||
import Metal
|
||||
|
||||
struct MetalView: UIViewRepresentable {
|
||||
let device: MTLDevice?
|
||||
let configure: (UIView) -> Void
|
||||
func makeUIView(context: Context) -> EmulationScreenView {
|
||||
let view = EmulationScreenView()
|
||||
configure(view.primaryScreen)
|
||||
return view
|
||||
}
|
||||
func updateUIView(_ uiView: EmulationScreenView, context: Context) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
struct ControllerView: View {
|
||||
let appui = AppUI.shared
|
||||
@State var isPressed = false
|
||||
@State var controllerconnected = false
|
||||
@State private var x: CGFloat = 0.0
|
||||
@State private var y: CGFloat = 0.0
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
if !controllerconnected {
|
||||
OnScreenController(geometry: geometry) // i did this to clean it up as it was quite long lmfao
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
print("checking for controller:")
|
||||
controllerconnected = false
|
||||
DispatchQueue.main.async {
|
||||
setupControllers() // i dont know what half of this shit does
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a dictionary to track controller IDs
|
||||
@State var controllerIDs: [GCController: Int] = [:]
|
||||
|
||||
private func setupControllers() {
|
||||
NotificationCenter.default.addObserver(forName: .GCControllerDidConnect, object: nil, queue: .main) { notification in
|
||||
if let controller = notification.object as? GCController {
|
||||
print("wow controller onstart") // yippeeee
|
||||
self.setupController(controller)
|
||||
self.controllerconnected = true
|
||||
} else {
|
||||
print("not GCController :((((((") // wahhhhhhh
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NotificationCenter.default.addObserver(forName: .GCControllerDidDisconnect, object: nil, queue: .main) { notification in
|
||||
if let controller = notification.object as? GCController {
|
||||
print("wow controller gone")
|
||||
if self.controllerIDs.isEmpty {
|
||||
controllerconnected = false
|
||||
}
|
||||
self.controllerIDs.removeValue(forKey: controller) // Remove the controller ID
|
||||
}
|
||||
}
|
||||
|
||||
GCController.controllers().forEach { controller in
|
||||
print("wow controller")
|
||||
self.controllerconnected = true
|
||||
self.setupController(controller)
|
||||
}
|
||||
}
|
||||
|
||||
private func setupController(_ controller: GCController) {
|
||||
// Assign a unique ID to the controller, max 5 controllers
|
||||
if controllerIDs.count < 6, controllerIDs[controller] == nil {
|
||||
controllerIDs[controller] = controllerIDs.count
|
||||
}
|
||||
guard let controllerId = controllerIDs[controller] else { return }
|
||||
if let extendedGamepad = controller.extendedGamepad {
|
||||
// Handle extended gamepad
|
||||
extendedGamepad.dpad.up.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.directionalPadUp, controllerId: controllerId) : self.touchUpInside(.directionalPadUp, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.dpad.down.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.directionalPadDown, controllerId: controllerId) : self.touchUpInside(.directionalPadDown, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.dpad.left.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.directionalPadLeft, controllerId: controllerId) : self.touchUpInside(.directionalPadLeft, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.dpad.right.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.directionalPadRight, controllerId: controllerId) : self.touchUpInside(.directionalPadRight, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.buttonOptions?.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.minus, controllerId: controllerId) : self.touchUpInside(.minus, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.buttonMenu.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.plus, controllerId: controllerId) : self.touchUpInside(.plus, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.buttonA.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.A, controllerId: controllerId) : self.touchUpInside(.A, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.buttonB.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.B, controllerId: controllerId) : self.touchUpInside(.B, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.buttonX.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.X, controllerId: controllerId) : self.touchUpInside(.X, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.buttonY.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.Y, controllerId: controllerId) : self.touchUpInside(.Y, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.leftShoulder.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.triggerL, controllerId: controllerId) : self.touchUpInside(.L, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.leftTrigger.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.triggerZL, controllerId: controllerId) : self.touchUpInside(.triggerZL, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.rightShoulder.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.triggerR, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.leftThumbstickButton?.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.L, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.rightThumbstickButton?.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.R, controllerId: controllerId) : self.touchUpInside(.triggerR, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.rightTrigger.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.triggerZR, controllerId: controllerId) : self.touchUpInside(.triggerZR, controllerId: controllerId)
|
||||
}
|
||||
extendedGamepad.buttonHome?.pressedChangedHandler = { button, value, pressed in
|
||||
if pressed {
|
||||
appui.exit()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
extendedGamepad.leftThumbstick.valueChangedHandler = { dpad, x, y in
|
||||
self.appui.thumbstickMoved(analog: .left, x: x, y: y, controllerid: controllerId)
|
||||
}
|
||||
extendedGamepad.rightThumbstick.valueChangedHandler = { dpad, x, y in
|
||||
self.appui.thumbstickMoved(analog: .right, x: x, y: y, controllerid: controllerId)
|
||||
}
|
||||
if let motion = controller.motion {
|
||||
var lastTimestamp = Date().timeIntervalSince1970 // Initialize timestamp when motion starts
|
||||
motion.valueChangedHandler = { motion in
|
||||
// Get current time
|
||||
let currentTimestamp = Date().timeIntervalSince1970
|
||||
|
||||
// Update last timestamp
|
||||
lastTimestamp = currentTimestamp
|
||||
|
||||
// Get gyroscope data
|
||||
let gyroX = motion.rotationRate.x
|
||||
let gyroY = motion.rotationRate.y
|
||||
let gyroZ = motion.rotationRate.z
|
||||
|
||||
// Get accelerometer data
|
||||
let accelX = motion.gravity.x + motion.userAcceleration.x
|
||||
let accelY = motion.gravity.y + motion.userAcceleration.y
|
||||
let accelZ = motion.gravity.z + motion.userAcceleration.z
|
||||
|
||||
print("\(gyroX), \(gyroY), \(gyroZ), \(accelX), \(accelY), \(accelZ)")
|
||||
|
||||
// Call your gyroMoved function with the motion data
|
||||
appui.gyroMoved(x: Float(gyroX), y: Float(gyroY), z: Float(gyroZ), accelX: Float(accelX), accelY: Float(accelY), accelZ: Float(accelZ), controllerId: Int32(controllerId), deltaTimestamp: Int32(lastTimestamp))
|
||||
}
|
||||
}
|
||||
} else if let microGamepad = controller.microGamepad {
|
||||
// Handle micro gamepad
|
||||
microGamepad.dpad.up.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.directionalPadUp, controllerId: controllerId) : self.touchUpInside(.directionalPadUp, controllerId: controllerId)
|
||||
}
|
||||
microGamepad.dpad.down.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.directionalPadDown, controllerId: controllerId) : self.touchUpInside(.directionalPadDown, controllerId: controllerId)
|
||||
}
|
||||
microGamepad.dpad.left.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.directionalPadLeft, controllerId: controllerId) : self.touchUpInside(.directionalPadLeft, controllerId: controllerId)
|
||||
}
|
||||
microGamepad.dpad.right.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.directionalPadRight, controllerId: controllerId) : self.touchUpInside(.directionalPadRight, controllerId: controllerId)
|
||||
}
|
||||
microGamepad.buttonA.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.A, controllerId: controllerId) : self.touchUpInside(.A, controllerId: controllerId)
|
||||
}
|
||||
microGamepad.buttonX.pressedChangedHandler = { button, value, pressed in
|
||||
pressed ? self.touchDown(.X, controllerId: controllerId) : self.touchUpInside(.X, controllerId: controllerId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func touchDown(_ button: VirtualControllerButtonType, controllerId: Int) {
|
||||
appui.virtualControllerButtonDown(button: button, controllerid: controllerId) }
|
||||
|
||||
private func touchUpInside(_ button: VirtualControllerButtonType, controllerId: Int) {
|
||||
appui.virtualControllerButtonUp(button: button, controllerid: controllerId)
|
||||
}
|
||||
}
|
||||
|
||||
struct OnScreenController: View {
|
||||
@State var geometry: GeometryProxy
|
||||
var body: some View {
|
||||
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad {
|
||||
// portrait
|
||||
VStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
HStack {
|
||||
VStack {
|
||||
ShoulderButtonsViewLeft()
|
||||
ZStack {
|
||||
Joystick()
|
||||
DPadView()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
VStack {
|
||||
ShoulderButtonsViewRight()
|
||||
ZStack {
|
||||
Joystick(iscool: true) // hope this works
|
||||
ABXYView()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
HStack {
|
||||
ButtonView(button: .plus).padding(.horizontal, 40)
|
||||
ButtonView(button: .minus).padding(.horizontal, 40)
|
||||
}
|
||||
}
|
||||
.padding(.bottom, geometry.size.height / 3.2) // very broken
|
||||
}
|
||||
} else {
|
||||
// could be landscape
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
ButtonView(button: .home)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
Spacer()
|
||||
VStack {
|
||||
HStack {
|
||||
|
||||
// gotta fuckin add + and - now
|
||||
VStack {
|
||||
ShoulderButtonsViewLeft()
|
||||
ZStack {
|
||||
Joystick()
|
||||
DPadView()
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Spacer()
|
||||
VStack {
|
||||
Spacer()
|
||||
ButtonView(button: .plus) // Adding the + button
|
||||
}
|
||||
VStack {
|
||||
Spacer()
|
||||
ButtonView(button: .minus) // Adding the - button
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
VStack {
|
||||
ShoulderButtonsViewRight()
|
||||
ZStack {
|
||||
Joystick(iscool: true) // hope this work s
|
||||
ABXYView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.bottom, geometry.size.height / 11) // also extremally broken (
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ShoulderButtonsViewLeft: View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
ButtonView(button: .triggerZL).padding(.horizontal)
|
||||
ButtonView(button: .triggerL).padding(.horizontal)
|
||||
}
|
||||
.frame(width: 160, height: 20)
|
||||
}
|
||||
}
|
||||
|
||||
struct ShoulderButtonsViewRight: View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
ButtonView(button: .triggerR).padding(.horizontal)
|
||||
ButtonView(button: .triggerZR).padding(.horizontal)
|
||||
}
|
||||
.frame(width: 160, height: 20)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
struct DPadView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
ButtonView(button: .directionalPadUp)
|
||||
HStack {
|
||||
ButtonView(button: .directionalPadLeft)
|
||||
Spacer(minLength: 20)
|
||||
ButtonView(button: .directionalPadRight)
|
||||
}
|
||||
ButtonView(button: .directionalPadDown).padding(.horizontal)
|
||||
}
|
||||
.frame(width: 145, height: 145)
|
||||
}
|
||||
}
|
||||
|
||||
struct ABXYView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
ButtonView(button: .X)
|
||||
HStack {
|
||||
ButtonView(button: .Y)
|
||||
Spacer(minLength: 20)
|
||||
ButtonView(button: .A)
|
||||
}
|
||||
ButtonView(button: .B).padding(.horizontal)
|
||||
}
|
||||
.frame(width: 145, height: 145)
|
||||
}
|
||||
}
|
||||
|
||||
class Haptics {
|
||||
static let shared = Haptics()
|
||||
private init() { }
|
||||
func play(_ feedbackStyle: UIImpactFeedbackGenerator.FeedbackStyle) {
|
||||
print("haptics")
|
||||
UIImpactFeedbackGenerator(style: feedbackStyle).impactOccurred()
|
||||
}
|
||||
func notify(_ feedbackType: UINotificationFeedbackGenerator.FeedbackType) {
|
||||
UINotificationFeedbackGenerator().notificationOccurred(feedbackType)
|
||||
}
|
||||
}
|
||||
|
||||
struct ButtonView: View {
|
||||
var button: VirtualControllerButtonType
|
||||
@StateObject private var viewModel: EmulationViewModel = EmulationViewModel()
|
||||
let appui = AppUI.shared
|
||||
@State var mtkView: MTKView?
|
||||
@State var width: CGFloat = 45
|
||||
@State var height: CGFloat = 45
|
||||
@State var isPressed = false
|
||||
var id: Int {
|
||||
if onscreenjoy {
|
||||
return 8
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var body: some View {
|
||||
Image(systemName: buttonText)
|
||||
.resizable()
|
||||
.frame(width: width, height: height)
|
||||
.foregroundColor(colorScheme == .dark ? Color.gray : Color.gray)
|
||||
.opacity(isPressed ? 0.5 : 1)
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { _ in
|
||||
if !self.isPressed {
|
||||
self.isPressed = true
|
||||
DispatchQueue.main.async {
|
||||
if button == .home {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
appui.exit()
|
||||
} else {
|
||||
appui.virtualControllerButtonDown(button: button, controllerid: id)
|
||||
Haptics.shared.play(.heavy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onEnded { _ in
|
||||
self.isPressed = false
|
||||
DispatchQueue.main.async {
|
||||
if button != .home {
|
||||
appui.virtualControllerButtonUp(button: button, controllerid: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.onAppear() {
|
||||
if button == .triggerL || button == .triggerZL || button == .triggerZR || button == .triggerR {
|
||||
width = 65
|
||||
}
|
||||
|
||||
|
||||
if button == .minus || button == .plus || button == .home {
|
||||
width = 35
|
||||
height = 35
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var buttonText: String {
|
||||
switch button {
|
||||
case .A: return "a.circle.fill"
|
||||
case .B: return "b.circle.fill"
|
||||
case .X: return "x.circle.fill"
|
||||
case .Y: return "y.circle.fill"
|
||||
case .directionalPadUp: return "arrowtriangle.up.circle.fill"
|
||||
case .directionalPadDown: return "arrowtriangle.down.circle.fill"
|
||||
case .directionalPadLeft: return "arrowtriangle.left.circle.fill"
|
||||
case .directionalPadRight: return "arrowtriangle.right.circle.fill"
|
||||
case .triggerZL: return"zl.rectangle.roundedtop.fill"
|
||||
case .triggerZR: return "zr.rectangle.roundedtop.fill"
|
||||
case .triggerL: return "l.rectangle.roundedbottom.fill"
|
||||
case .triggerR: return "r.rectangle.roundedbottom.fill"
|
||||
case .plus: return "plus.circle.fill"
|
||||
case .minus: return "minus.circle.fill"
|
||||
case .home: return "house.circle.fill"
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EmulationView: View {
|
||||
@StateObject private var viewModel: EmulationViewModel
|
||||
@State var controllerconnected = false
|
||||
@State var appui = AppUI.shared
|
||||
var device: MTLDevice? = MTLCreateSystemDefaultDevice()
|
||||
@State var CaLayer: CAMetalLayer?
|
||||
@State var ShowPopup: Bool = false
|
||||
@State var mtkview: MTKView?
|
||||
@State private var thread: Thread!
|
||||
@State var uiTabBarController: UITabBarController?
|
||||
@State private var isFirstFrameShown = false
|
||||
@State private var timer: Timer?
|
||||
@Environment(\.scenePhase) var scenePhase
|
||||
|
||||
init(game: EmulationGame?) {
|
||||
_viewModel = StateObject(wrappedValue: EmulationViewModel())
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
MetalView(device: device) { view in
|
||||
DispatchQueue.main.async {
|
||||
if let metalView = view as? MTKView {
|
||||
mtkview = metalView
|
||||
viewModel.configureAppUI(with: metalView)
|
||||
} else {
|
||||
print("Error: view is not of type MTKView")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onRotate { size in
|
||||
viewModel.handleOrientationChange()
|
||||
}
|
||||
ControllerView()
|
||||
}
|
||||
.overlay(
|
||||
// Loading screen overlay on top of MetalView
|
||||
Group {
|
||||
if !isFirstFrameShown {
|
||||
LoadingView()
|
||||
}
|
||||
}
|
||||
.transition(.opacity)
|
||||
)
|
||||
.onAppear {
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
startPollingFirstFrameShowed()
|
||||
}
|
||||
.onDisappear {
|
||||
stopPollingFirstFrameShowed()
|
||||
uiTabBarController?.tabBar.isHidden = false
|
||||
viewModel.customButtonTapped()
|
||||
}
|
||||
.navigationBarBackButtonHidden(true)
|
||||
// .introspect(.tabView, on: .iOS(.v13, .v14, .v15, .v16, .v17)) { (tabBarController) in
|
||||
// tabBarController.tabBar.isHidden = true
|
||||
// uiTabBarController = tabBarController
|
||||
// }
|
||||
}
|
||||
|
||||
private func startPollingFirstFrameShowed() {
|
||||
timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { _ in
|
||||
if appui.FirstFrameShowed() {
|
||||
withAnimation {
|
||||
isFirstFrameShown = true
|
||||
}
|
||||
stopPollingFirstFrameShowed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stopPollingFirstFrameShowed() {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
print("Timer Invalidated")
|
||||
}
|
||||
}
|
||||
|
||||
struct LoadingView: View {
|
||||
var body: some View {
|
||||
VStack {
|
||||
ProgressView("Loading...")
|
||||
// .font(.system(size: 90))
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
.padding()
|
||||
Text("Please wait while the game loads.")
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.black.opacity(0.8))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func onRotate(perform action: @escaping (CGSize) -> Void) -> some View {
|
||||
self.modifier(DeviceRotationModifier(action: action))
|
||||
}
|
||||
}
|
||||
|
||||
struct DeviceRotationModifier: ViewModifier {
|
||||
let action: (CGSize) -> Void
|
||||
@State var startedfirst: Bool = false
|
||||
|
||||
func body(content: Content) -> some View { content
|
||||
.background(GeometryReader { geometry in
|
||||
Color.clear
|
||||
.preference(key: SizePreferenceKey.self, value: geometry.size)
|
||||
})
|
||||
.onPreferenceChange(SizePreferenceKey.self) { newSize in
|
||||
if startedfirst {
|
||||
action(newSize)
|
||||
} else {
|
||||
startedfirst = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SizePreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGSize = .zero
|
||||
|
||||
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
|
||||
value = nextValue()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#if __has_include(<Metal/Metal.hpp>)
|
||||
#import <Metal/Metal.hpp>
|
||||
#else
|
||||
#import <Metal/Metal.h>
|
||||
#endif
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/graphics_context.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
class GraphicsContext_Apple final : public Core::Frontend::GraphicsContext {
|
||||
public:
|
||||
explicit GraphicsContext_Apple(std::shared_ptr<Common::DynamicLibrary> driver_library)
|
||||
: m_driver_library{driver_library} {}
|
||||
|
||||
~GraphicsContext_Apple() = default;
|
||||
|
||||
std::shared_ptr<Common::DynamicLibrary> GetDriverLibrary() override {
|
||||
return m_driver_library;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Common::DynamicLibrary> m_driver_library;
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
class EmulationWindow final : public Core::Frontend::EmuWindow {
|
||||
public:
|
||||
EmulationWindow(InputCommon::InputSubsystem* input_subsystem, CAMetalLayer* surface, CGSize size,
|
||||
std::shared_ptr<Common::DynamicLibrary> driver_library);
|
||||
|
||||
~EmulationWindow() = default;
|
||||
|
||||
void OnSurfaceChanged(CAMetalLayer* surface, CGSize size);
|
||||
void OrientationChanged(UIInterfaceOrientation orientation);
|
||||
void OnFrameDisplayed() override;
|
||||
|
||||
void OnTouchPressed(int id, float x, float y);
|
||||
void OnTouchMoved(int id, float x, float y);
|
||||
void OnTouchReleased(int id);
|
||||
|
||||
void OnGamepadButtonEvent(int player_index, int button_id, bool pressed);
|
||||
void OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y);
|
||||
void OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z);
|
||||
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override {
|
||||
return {std::make_unique<GraphicsContext_Apple>(m_driver_library)};
|
||||
}
|
||||
|
||||
|
||||
bool HasFirstFrame() const {
|
||||
return m_first_frame;
|
||||
}
|
||||
|
||||
bool IsShown() const override {
|
||||
return true;
|
||||
};
|
||||
|
||||
private:
|
||||
float m_window_width{};
|
||||
float m_window_height{};
|
||||
CGSize m_size;
|
||||
bool is_portrait = true;
|
||||
|
||||
InputCommon::InputSubsystem* m_input_subsystem{};
|
||||
std::shared_ptr<Common::DynamicLibrary> m_driver_library;
|
||||
|
||||
bool m_first_frame = false;
|
||||
};
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,82 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Jarrod Norwell
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#import "EmulationWindow.h"
|
||||
#import "EmulationSession.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "common/logging.h"
|
||||
#include "input_common/drivers/touch_screen.h"
|
||||
#include "input_common/drivers/virtual_amiibo.h"
|
||||
#include "input_common/drivers/virtual_gamepad.h"
|
||||
#include "input_common/main.h"
|
||||
|
||||
void EmulationWindow::OnSurfaceChanged(CAMetalLayer* surface, CGSize size) {
|
||||
m_size = size;
|
||||
|
||||
m_window_width = size.width;
|
||||
m_window_height = size.height;
|
||||
|
||||
// Ensures that we emulate with the correct aspect ratio.
|
||||
// UpdateCurrentFramebufferLayout(m_window_width, m_window_height);
|
||||
|
||||
window_info.render_surface = (__bridge void *)surface;
|
||||
window_info.render_surface_scale = [[UIScreen mainScreen] nativeScale];
|
||||
}
|
||||
|
||||
void EmulationWindow::OrientationChanged(UIInterfaceOrientation orientation) {
|
||||
is_portrait = orientation == UIInterfaceOrientationPortrait;
|
||||
}
|
||||
|
||||
void EmulationWindow::OnTouchPressed(int id, float x, float y) {
|
||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchPressed(touch_x,
|
||||
touch_y, id);
|
||||
}
|
||||
|
||||
void EmulationWindow::OnTouchMoved(int id, float x, float y) {
|
||||
const auto [touch_x, touch_y] = MapToTouchScreen(x, y);
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchMoved(touch_x,
|
||||
touch_y, id);
|
||||
}
|
||||
|
||||
void EmulationWindow::OnTouchReleased(int id) {
|
||||
EmulationSession::GetInstance().GetInputSubsystem().GetTouchScreen()->TouchReleased(id);
|
||||
}
|
||||
|
||||
void EmulationWindow::OnGamepadButtonEvent(int player_index, int button_id, bool pressed) {
|
||||
m_input_subsystem->GetVirtualGamepad()->SetButtonState(player_index, button_id, pressed);
|
||||
}
|
||||
|
||||
void EmulationWindow::OnGamepadJoystickEvent(int player_index, int stick_id, float x, float y) {
|
||||
m_input_subsystem->GetVirtualGamepad()->SetStickPosition(player_index, stick_id, x, y);
|
||||
}
|
||||
|
||||
void EmulationWindow::OnGamepadMotionEvent(int player_index, u64 delta_timestamp, float gyro_x, float gyro_y, float gyro_z, float accel_x, float accel_y, float accel_z) {
|
||||
m_input_subsystem->GetVirtualGamepad()->SetMotionState(player_index, delta_timestamp, gyro_x, gyro_y, gyro_z, accel_x, accel_y, accel_z);
|
||||
}
|
||||
|
||||
void EmulationWindow::OnFrameDisplayed() {
|
||||
if (!m_first_frame) {
|
||||
m_first_frame = true;
|
||||
}
|
||||
}
|
||||
|
||||
EmulationWindow::EmulationWindow(InputCommon::InputSubsystem* input_subsystem, CAMetalLayer* surface, CGSize size, std::shared_ptr<Common::DynamicLibrary> driver_library)
|
||||
: m_window_width{}, m_window_height{}, m_size{size}, is_portrait{true}, m_input_subsystem{input_subsystem}, m_driver_library{driver_library}, m_first_frame{false} {
|
||||
LOG_INFO(Frontend, "initializing");
|
||||
|
||||
if (!surface) {
|
||||
LOG_CRITICAL(Frontend, "surface is nullptr");
|
||||
return;
|
||||
}
|
||||
|
||||
OnSurfaceChanged(surface, m_size);
|
||||
window_info.render_surface_scale = [[UIScreen mainScreen] nativeScale];
|
||||
window_info.type = Core::Frontend::WindowSystemType::Cocoa;
|
||||
|
||||
m_input_subsystem->Initialize();
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
struct Core : Comparable, Hashable {
|
||||
|
||||
let name = "Yuzu"
|
||||
var games: [EmulationGame]
|
||||
let root: URL
|
||||
|
||||
static func < (lhs: Core, rhs: Core) -> Bool {
|
||||
lhs.name < rhs.name
|
||||
}
|
||||
|
||||
func AddFirmware(at fileURL: URL) {
|
||||
do {
|
||||
let fileManager = FileManager.default
|
||||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
let destinationURL = documentsDirectory.appendingPathComponent("nand/system/Contents/registered")
|
||||
if !fileManager.fileExists(atPath: destinationURL.path) {
|
||||
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil)
|
||||
}
|
||||
//try Zip.unzipFile(fileURL, destination: destinationURL, overwrite: true, password: nil)
|
||||
print("File unzipped successfully to \(destinationURL.path)")
|
||||
} catch {
|
||||
print("Failed to unzip file: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LibManError : Error {
|
||||
case ripenum, urlgobyebye
|
||||
}
|
||||
|
||||
class LibraryManager {
|
||||
static let shared = LibraryManager()
|
||||
let documentdir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("roms", conformingTo: .folder)
|
||||
|
||||
func removerom(_ game: EmulationGame) throws {
|
||||
do {
|
||||
try FileManager.default.removeItem(at: game.fileURL)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
func homebrewroms() -> [EmulationGame] {
|
||||
// TODO(lizzie): this is horrible
|
||||
var urls: [URL] = []
|
||||
let sdmc = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("sdmc", conformingTo: .folder)
|
||||
let sdfolder = sdmc.appendingPathComponent("switch", conformingTo: .folder)
|
||||
if FileManager.default.fileExists(atPath: sdfolder.path) {
|
||||
if let dirContents = FileManager.default.enumerator(at: sdmc, includingPropertiesForKeys: nil, options: []) {
|
||||
do {
|
||||
try dirContents.forEach() { files in
|
||||
if let file = files as? URL {
|
||||
let getaboutfile = try file.resourceValues(forKeys: [.isRegularFileKey])
|
||||
if let isfile = getaboutfile.isRegularFile, isfile {
|
||||
if ["nso", "nro"].contains(file.pathExtension.lowercased()) {
|
||||
urls.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if let dirContents = FileManager.default.enumerator(at: documentdir, includingPropertiesForKeys: nil, options: []) {
|
||||
do {
|
||||
try dirContents.forEach() { files in
|
||||
if let file = files as? URL {
|
||||
let getaboutfile = try file.resourceValues(forKeys: [.isRegularFileKey])
|
||||
if let isfile = getaboutfile.isRegularFile, isfile {
|
||||
if ["nso", "nro"].contains(file.pathExtension.lowercased()) {
|
||||
urls.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print("damn")
|
||||
if let dirContents = FileManager.default.enumerator(at: documentdir, includingPropertiesForKeys: nil, options: []) {
|
||||
do {
|
||||
try dirContents.forEach() { files in
|
||||
if let file = files as? URL {
|
||||
let getaboutfile = try file.resourceValues(forKeys: [.isRegularFileKey])
|
||||
if let isfile = getaboutfile.isRegularFile, isfile {
|
||||
if ["nso", "nro"].contains(file.pathExtension.lowercased()) {
|
||||
urls.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if let dirContents = FileManager.default.enumerator(at: documentdir, includingPropertiesForKeys: nil, options: []) {
|
||||
do {
|
||||
try dirContents.forEach() { files in
|
||||
if let file = files as? URL {
|
||||
let getaboutfile = try file.resourceValues(forKeys: [.isRegularFileKey])
|
||||
if let isfile = getaboutfile.isRegularFile, isfile {
|
||||
if ["nso", "nro"].contains(file.pathExtension.lowercased()) {
|
||||
urls.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
func games(from urls: [URL]) -> [EmulationGame] {
|
||||
var pomelogames: [EmulationGame] = []
|
||||
pomelogames = urls.reduce(into: [EmulationGame]()) { partialResult, element in
|
||||
let iscustom = element.startAccessingSecurityScopedResource()
|
||||
let information = AppUI.shared.information(for: element)
|
||||
let game = EmulationGame(developer: information.developer, fileURL: element, imageData: information.iconData, title: information.title)
|
||||
if iscustom {
|
||||
element.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
partialResult.append(game)
|
||||
}
|
||||
return pomelogames
|
||||
}
|
||||
return games(from: urls)
|
||||
}
|
||||
|
||||
func library() throws -> Core {
|
||||
func getromsfromdir() throws -> [URL] {
|
||||
guard let dirContents = FileManager.default.enumerator(at: documentdir, includingPropertiesForKeys: nil, options: []) else {
|
||||
print("uhoh how unfortunate for some reason FileManager.default.enumerator aint workin")
|
||||
throw LibManError.ripenum
|
||||
}
|
||||
let appui = AppUI.shared
|
||||
var urls: [URL] = []
|
||||
try dirContents.forEach() { files in
|
||||
if let file = files as? URL {
|
||||
let getaboutfile = try file.resourceValues(forKeys: [.isRegularFileKey])
|
||||
if let isfile = getaboutfile.isRegularFile, isfile {
|
||||
if ["nca", "nro", "nsp", "nso", "xci"].contains(file.pathExtension.lowercased()) {
|
||||
urls.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let sdmc = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent("sdmc", conformingTo: .folder)
|
||||
let sdfolder = sdmc.appendingPathComponent("switch", conformingTo: .folder)
|
||||
if FileManager.default.fileExists(atPath: sdfolder.path) {
|
||||
if let dirContents = FileManager.default.enumerator(at: sdmc, includingPropertiesForKeys: nil, options: []) {
|
||||
try dirContents.forEach() { files in
|
||||
if let file = files as? URL {
|
||||
let getaboutfile = try file.resourceValues(forKeys: [.isRegularFileKey])
|
||||
if let isfile = getaboutfile.isRegularFile, isfile {
|
||||
if ["nso", "nro"].contains(file.pathExtension.lowercased()) {
|
||||
urls.append(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
appui.insert(games: urls)
|
||||
return urls
|
||||
}
|
||||
|
||||
func games(from urls: [URL], core: inout Core) {
|
||||
core.games = urls.reduce(into: [EmulationGame]()) { partialResult, element in
|
||||
let iscustom = element.startAccessingSecurityScopedResource()
|
||||
let information = AppUI.shared.information(for: element)
|
||||
let game = EmulationGame(developer: information.developer, fileURL: element, imageData: information.iconData, title: information.title)
|
||||
if iscustom {
|
||||
element.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
partialResult.append(game)
|
||||
}
|
||||
}
|
||||
let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
var YuzuCore = Core(games: [], root: directory)
|
||||
games(from: try getromsfromdir(), core: &YuzuCore)
|
||||
return YuzuCore
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import Foundation
|
||||
|
||||
class FolderMonitor {
|
||||
private var folderDescriptor: Int32 = -1
|
||||
private var folderMonitorSource: DispatchSourceFileSystemObject?
|
||||
private let folderURL: URL
|
||||
private let onFolderChange: () -> Void
|
||||
init(folderURL: URL, onFolderChange: @escaping () -> Void) {
|
||||
self.folderURL = folderURL
|
||||
self.onFolderChange = onFolderChange
|
||||
startMonitoring()
|
||||
}
|
||||
private func startMonitoring() {
|
||||
folderDescriptor = open(folderURL.path, O_EVTONLY)
|
||||
guard folderDescriptor != -1 else {
|
||||
print("Failed to open folder descriptor.")
|
||||
return
|
||||
}
|
||||
|
||||
folderMonitorSource = DispatchSource.makeFileSystemObjectSource(
|
||||
fileDescriptor: folderDescriptor,
|
||||
eventMask: .write,
|
||||
queue: DispatchQueue.global()
|
||||
)
|
||||
folderMonitorSource?.setEventHandler { [weak self] in
|
||||
self?.folderDidChange()
|
||||
}
|
||||
folderMonitorSource?.setCancelHandler {
|
||||
close(self.folderDescriptor)
|
||||
}
|
||||
folderMonitorSource?.resume()
|
||||
}
|
||||
|
||||
private func folderDidChange() {
|
||||
// Detect the change and call the refreshcore function
|
||||
print("Folder changed! New file added or removed.")
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
self?.onFolderChange()
|
||||
}
|
||||
}
|
||||
deinit {
|
||||
folderMonitorSource?.cancel()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
import Combine
|
||||
|
||||
struct SettingsView: View {
|
||||
@ObservedObject var core: EmulationViewModel
|
||||
@State var showprompt = false
|
||||
|
||||
@AppStorage("icon") var iconused = 1
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GameIconView: View {
|
||||
var game: EmulationGame
|
||||
@Binding var selectedGame: EmulationGame?
|
||||
@State var startgame: Bool = false
|
||||
@State var timesTapped: Int = 0
|
||||
|
||||
var isSelected: Bool {
|
||||
selectedGame == game
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(
|
||||
destination: EmulationView(game: game).toolbar(.hidden, for: .tabBar),
|
||||
isActive: $startgame,
|
||||
label: {
|
||||
EmptyView()
|
||||
}
|
||||
)
|
||||
VStack(spacing: 5) {
|
||||
if isSelected {
|
||||
Text(game.title)
|
||||
.foregroundColor(.blue)
|
||||
.font(.title2)
|
||||
}
|
||||
if let uiImage = UIImage(data: game.imageData) {
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: isSelected ? 200 : 180, height: isSelected ? 200 : 180)
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
isSelected ? RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.blue, lineWidth: 5)
|
||||
: nil
|
||||
)
|
||||
.onTapGesture {
|
||||
if isSelected {
|
||||
startgame = true
|
||||
print(isSelected)
|
||||
}
|
||||
if !isSelected {
|
||||
selectedGame = game
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "questionmark")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 200, height: 200)
|
||||
.cornerRadius(10)
|
||||
.onTapGesture { selectedGame = game }
|
||||
}
|
||||
}
|
||||
.frame(width: 200, height: 250)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove or refactor HomeView and GameCarouselView to not use Core or BottomMenuView(core: core) with Core.
|
||||
// If these are not used in the iOS UI, comment them out to avoid build errors.
|
||||
// struct HomeView: View {
|
||||
// @State private var selectedGame: EmulationGame? = nil
|
||||
|
||||
// @State var core: Core
|
||||
|
||||
// init(selectedGame: EmulationGame? = nil, core: Core) {
|
||||
// _core = State(wrappedValue: core)
|
||||
// self.selectedGame = selectedGame
|
||||
// refreshcore()
|
||||
// }
|
||||
|
||||
// var body: some View {
|
||||
// NavigationStack {
|
||||
// GeometryReader { geometry in
|
||||
// VStack {
|
||||
// GameCarouselView(core: core, selectedGame: $selectedGame)
|
||||
// Spacer()
|
||||
// BottomMenuView(core: core)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .background(Color.gray.opacity(0.1))
|
||||
// .edgesIgnoringSafeArea(.all)
|
||||
// .onAppear {
|
||||
// refreshcore()
|
||||
// if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
// let romsFolderURL = documentsDirectory.appendingPathComponent("roms")
|
||||
// let folderMonitor = FolderMonitor(folderURL: romsFolderURL) {
|
||||
// do {
|
||||
// core = Core(games: [], root: documentsDirectory)
|
||||
// core = try LibraryManager.shared.library()
|
||||
// } catch {
|
||||
// print("Error refreshing core: \(error)")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// func refreshcore() {
|
||||
// print("Loading library...")
|
||||
// do {
|
||||
// core = try LibraryManager.shared.library()
|
||||
// print(core.games)
|
||||
// } catch {
|
||||
// print("Failed to fetch library: \(error)")
|
||||
// return
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// struct GameCarouselView: View {
|
||||
// // let games: [EmulationGame]
|
||||
// @State var core: Core
|
||||
// @Binding var selectedGame: EmulationGame?
|
||||
// var body: some View {
|
||||
// ScrollView(.horizontal, showsIndicators: false) {
|
||||
// HStack(spacing: 20) {
|
||||
// ForEach(core.games) { game in
|
||||
// GameIconView(game: game, selectedGame: $selectedGame)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -0,0 +1,171 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
|
||||
struct GameButtonListView: View {
|
||||
var game: EmulationGame
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 15) {
|
||||
if let image = UIImage(data: game.imageData) {
|
||||
Image(uiImage: image)
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(8)
|
||||
} else {
|
||||
Image(systemName: "photo")
|
||||
.resizable()
|
||||
.frame(width: 60, height: 60)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(game.title)
|
||||
.font(.headline)
|
||||
.foregroundColor(colorScheme == .dark ? Color.white : Color.black)
|
||||
Text(game.developer)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
}
|
||||
|
||||
struct GameListView: View {
|
||||
@State var core: Core
|
||||
@State private var searchText = ""
|
||||
@State var game: Int = 1
|
||||
@State var startgame: Bool = false
|
||||
@Binding var isGridView: Bool
|
||||
@State var showAlert = false
|
||||
@State var alertMessage: Alert? = nil
|
||||
|
||||
var body: some View {
|
||||
let filteredGames = core.games.filter { game in
|
||||
guard let EmulationGame = game as? EmulationGame else { return false }
|
||||
return searchText.isEmpty || EmulationGame.title.localizedCaseInsensitiveContains(searchText)
|
||||
}
|
||||
ScrollView {
|
||||
VStack {
|
||||
VStack(alignment: .leading) {
|
||||
|
||||
if isGridView {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 160))], spacing: 10) {
|
||||
ForEach(0..<filteredGames.count, id: \.self) { index in
|
||||
let game = filteredGames[index] // Use filteredGames here
|
||||
NavigationLink(destination: EmulationView(game: game).toolbar(.hidden, for: .tabBar)) {
|
||||
// GameButtonView(game: game)
|
||||
// .frame(maxWidth: .infinity, minHeight: 200)
|
||||
}
|
||||
.contextMenu {
|
||||
Button(action: {
|
||||
do {
|
||||
try LibraryManager.shared.removerom(filteredGames[index])
|
||||
} catch {
|
||||
showAlert = true
|
||||
alertMessage = Alert(title: Text("Unable to Remove Game"), message: Text(error.localizedDescription))
|
||||
}
|
||||
}) {
|
||||
Text("Remove")
|
||||
}
|
||||
Button(action: {
|
||||
if let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appending(path: "roms") {
|
||||
UIApplication.shared.open(documentsURL, options: [:], completionHandler: nil)
|
||||
}
|
||||
}) {
|
||||
if ProcessInfo.processInfo.isMacCatalystApp {
|
||||
Text("Open in Finder")
|
||||
} else {
|
||||
Text("Open in Files")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: EmulationView(game: game).toolbar(.hidden, for: .tabBar)) {
|
||||
Text("Launch")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyVStack() {
|
||||
ForEach(0..<filteredGames.count, id: \.self) { index in
|
||||
let game = filteredGames[index] // Use filteredGames here
|
||||
NavigationLink(destination: EmulationView(game: game).toolbar(.hidden, for: .tabBar)) {
|
||||
GameButtonListView(game: game)
|
||||
.frame(maxWidth: .infinity, minHeight: 75)
|
||||
}
|
||||
.contextMenu {
|
||||
Button(action: {
|
||||
do {
|
||||
try LibraryManager.shared.removerom(filteredGames[index])
|
||||
try FileManager.default.removeItem(atPath: game.fileURL.path)
|
||||
} catch {
|
||||
showAlert = true
|
||||
alertMessage = Alert(title: Text("Unable to Remove Game"), message: Text(error.localizedDescription))
|
||||
}
|
||||
}) {
|
||||
Text("Remove")
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
if let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appending(path: "roms") {
|
||||
UIApplication.shared.open(documentsURL, options: [:], completionHandler: nil)
|
||||
}
|
||||
}) {
|
||||
if ProcessInfo.processInfo.isMacCatalystApp {
|
||||
Text("Open in Finder")
|
||||
} else {
|
||||
Text("Open in Files")
|
||||
}
|
||||
}
|
||||
NavigationLink(destination: EmulationView(game: game).toolbar(.hidden, for: .tabBar)) {
|
||||
Text("Launch")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.searchable(text: $searchText)
|
||||
.padding()
|
||||
}
|
||||
.onAppear {
|
||||
refreshcore()
|
||||
|
||||
if let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
|
||||
let romsFolderURL = documentsDirectory.appendingPathComponent("roms")
|
||||
|
||||
let folderMonitor = FolderMonitor(folderURL: romsFolderURL) {
|
||||
do {
|
||||
core = Core(games: [], root: documentsDirectory)
|
||||
core = try LibraryManager.shared.library()
|
||||
} catch {
|
||||
print("Error refreshing core: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.alert(isPresented: $showAlert) {
|
||||
alertMessage ?? Alert(title: Text("Error Not Found"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func refreshcore() {
|
||||
do {
|
||||
core = try LibraryManager.shared.library()
|
||||
} catch {
|
||||
print("Failed to fetch library: \(error)")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
|
||||
|
||||
public struct Joystick: View {
|
||||
@State var iscool: Bool? = nil
|
||||
var id: Int {
|
||||
if onscreenjoy {
|
||||
return 8
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
|
||||
|
||||
let appui = AppUI.shared
|
||||
|
||||
@ObservedObject public var joystickMonitor = JoystickMonitor()
|
||||
private let dragDiameter: CGFloat = 160
|
||||
private let shape: JoystickShape = .circle
|
||||
|
||||
public var body: some View {
|
||||
VStack{
|
||||
JoystickBuilder(
|
||||
monitor: self.joystickMonitor,
|
||||
width: self.dragDiameter,
|
||||
shape: .circle,
|
||||
background: {
|
||||
// Example Background
|
||||
RoundedRectangle(cornerRadius: 8).fill(Color.gray.opacity(0))
|
||||
},
|
||||
foreground: {
|
||||
// Example Thumb
|
||||
Circle().fill(Color.gray)
|
||||
},
|
||||
locksInPlace: false)
|
||||
.onChange(of: self.joystickMonitor.xyPoint) { newValue in
|
||||
let scaledX = Float(newValue.x)
|
||||
let scaledY = Float(-newValue.y) // my dumbass broke this by having -y instead of y :/ (well it appears that with the new joystick code, its supposed to be -y)
|
||||
joystickMonitor.objectWillChange
|
||||
print("Joystick Position: (\(scaledX), \(scaledY))")
|
||||
|
||||
if iscool != nil {
|
||||
appui.thumbstickMoved(analog: .right, x: scaledX, y: scaledY, controllerid: id)
|
||||
} else {
|
||||
appui.thumbstickMoved(analog: .left, x: scaledX, y: scaledY, controllerid: id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
class KeyboardHostingController<Content: View>: UIHostingController<Content> {
|
||||
override var canBecomeFirstResponder: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
becomeFirstResponder() // Make sure the view can become the first responder
|
||||
}
|
||||
|
||||
override var keyCommands: [UIKeyCommand]? {
|
||||
return [
|
||||
UIKeyCommand(input: UIKeyInputUpArrow, modifierFlags: [], action: #selector(handleKeyCommand)),
|
||||
UIKeyCommand(input: UIKeyInputDownArrow, modifierFlags: [], action: #selector(handleKeyCommand)),
|
||||
UIKeyCommand(input: UIKeyInputLeftArrow, modifierFlags: [], action: #selector(handleKeyCommand)),
|
||||
UIKeyCommand(input: UIKeyInputRightArrow, modifierFlags: [], action: #selector(handleKeyCommand)),
|
||||
UIKeyCommand(input: "w", modifierFlags: [], action: #selector(handleKeyCommand)),
|
||||
UIKeyCommand(input: "s", modifierFlags: [], action: #selector(handleKeyCommand)),
|
||||
UIKeyCommand(input: "a", modifierFlags: [], action: #selector(handleKeyCommand)),
|
||||
UIKeyCommand(input: "d", modifierFlags: [], action: #selector(handleKeyCommand))
|
||||
]
|
||||
}
|
||||
|
||||
@objc func handleKeyCommand(_ sender: UIKeyCommand) {
|
||||
if let input = sender.input {
|
||||
switch input {
|
||||
case UIKeyInputUpArrow:
|
||||
print("Up Arrow Pressed")
|
||||
case UIKeyInputDownArrow:
|
||||
print("Down Arrow Pressed")
|
||||
case UIKeyInputLeftArrow:
|
||||
print("Left Arrow Pressed")
|
||||
case UIKeyInputRightArrow:
|
||||
print("Right Arrow Pressed")
|
||||
case "w":
|
||||
print("W Key Pressed")
|
||||
case "s":
|
||||
print("S Key Pressed")
|
||||
case "a":
|
||||
print("A Key Pressed")
|
||||
case "d":
|
||||
print("D Key Pressed")
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyboardSupportView: UIViewControllerRepresentable {
|
||||
let content: Text
|
||||
|
||||
func makeUIViewController(context: Context) -> KeyboardHostingController<Text> {
|
||||
return KeyboardHostingController(rootView: content)
|
||||
}
|
||||
|
||||
func updateUIViewController(_ uiViewController: KeyboardHostingController<Text>, context: Context) {
|
||||
// Handle any updates needed
|
||||
}
|
||||
}
|
||||
|
||||
struct KeyboardView: View {
|
||||
var body: some View {
|
||||
KeyboardSupportView(content: Text(""))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
struct LibraryView: View {
|
||||
@State private var selectedGame: EmulationGame? = nil
|
||||
@Binding var urlgame: EmulationGame?
|
||||
@ObservedObject var core: EmulationViewModel
|
||||
|
||||
var binding: Binding<Bool> {
|
||||
Binding(
|
||||
get: { urlgame != nil },
|
||||
set: { newValue in
|
||||
if !newValue {
|
||||
urlgame = nil
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
TopBarView()
|
||||
|
||||
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||
Spacer()
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [
|
||||
GridItem(.adaptive(minimum: 140), spacing: 10)
|
||||
], spacing: 10) {
|
||||
ForEach(core.games, id: \EmulationGame.id) { game in
|
||||
GameCardView(game: game)
|
||||
.frame(width: 140, height: 160)
|
||||
.onTapGesture {
|
||||
selectedGame = game
|
||||
urlgame = game
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
BottomMenuView(core: core)
|
||||
}
|
||||
|
||||
NavigationLink(
|
||||
destination: EmulationView(game: urlgame),
|
||||
isActive: binding,
|
||||
label: {
|
||||
EmptyView()
|
||||
}
|
||||
)
|
||||
.hidden()
|
||||
}
|
||||
}
|
||||
.background(Color.gray.opacity(0.1))
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
.onAppear {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GameCardView: View {
|
||||
let game: EmulationGame
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Rectangle()
|
||||
.fill(Color.gray)
|
||||
.frame(height: 120)
|
||||
|
||||
Text(game.title)
|
||||
.font(.caption)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
}
|
||||
.background(Color.white)
|
||||
.cornerRadius(8)
|
||||
.shadow(radius: 4)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2024 Pomelo, Stossy11
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
infix operator --: LogicalDisjunctionPrecedence
|
||||
|
||||
func --(lhs: Bool, rhs: Bool) -> Bool {
|
||||
return lhs || rhs
|
||||
}
|
||||
|
||||
class YuzuFileManager {
|
||||
static var shared = YuzuFileManager()
|
||||
func directories() -> [String : [String : String]] {
|
||||
[
|
||||
"themes" : [:],
|
||||
"amiibo" : [:],
|
||||
"cache" : [:],
|
||||
"config" : [:],
|
||||
"crash_dumps" : [:],
|
||||
"dump" : [:],
|
||||
"keys" : [:],
|
||||
"load" : [:],
|
||||
"log" : [:],
|
||||
"nand" : [:],
|
||||
"play_time" : [:],
|
||||
"roms" : [:],
|
||||
"screenshots" : [:],
|
||||
"sdmc" : [:],
|
||||
"shader" : [:],
|
||||
"tas" : [:],
|
||||
"icons" : [:]
|
||||
]
|
||||
}
|
||||
|
||||
func createdirectories() throws {
|
||||
let documentdir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
try directories().forEach() { directory, filename in
|
||||
let directoryURL = documentdir.appendingPathComponent(directory)
|
||||
|
||||
if !FileManager.default.fileExists(atPath: directoryURL.path) {
|
||||
print("creating dir at \(directoryURL.path)") // yippee
|
||||
try FileManager.default.createDirectory(at: directoryURL, withIntermediateDirectories: false, attributes: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@main
|
||||
struct PomeloApp: App {
|
||||
@StateObject private var core = EmulationViewModel(game: nil)
|
||||
var body: some Scene {
|
||||
WindowGroup { ContentView(core: core) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2021 Eden Emulator Project
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import SwiftUI
|
||||
|
||||
public protocol PolarCoordinate {
|
||||
/// The direction the thumb handle is pointing, up is 0° and right is 90°
|
||||
var degrees: CGFloat { get set }
|
||||
/// The thumb handle's distance from the center/origin
|
||||
var distance: CGFloat { get set }
|
||||
}
|
||||
|
||||
public struct PolarPoint: PolarCoordinate {
|
||||
/// The direction from origin, up is 0° and right is 90°
|
||||
public var degrees: CGFloat
|
||||
/// The distance from center/origin
|
||||
public var distance: CGFloat
|
||||
|
||||
public static let zero: PolarPoint = PolarPoint(degrees: 0, distance: 0)
|
||||
|
||||
public init(degrees: CGFloat, distance: CGFloat) {
|
||||
self.degrees = degrees
|
||||
self.distance = distance
|
||||
}
|
||||
}
|
||||
|
||||
/// The type of background shape used for the touch/click hitbox
|
||||
///
|
||||
/// Rect will allow every coordinate to be used by the joystick's thumb position
|
||||
/// Circle will limit the position output to the circular area defined by the midpoint and diameter/width
|
||||
public enum JoystickShape {
|
||||
/// Will allow the cursor to go from 0,0 to 0, width, and width, 0
|
||||
case rect
|
||||
/// Will limit the curser to a circular area surrounding the center of the joystick
|
||||
/// This cannot reach the corners but can reach min and max for both the x and y axis any edge's midpoint
|
||||
case circle
|
||||
}
|
||||
|
||||
public extension CGPoint {
|
||||
internal static func +(_ lhs: CGPoint, _ rhs: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y)
|
||||
}
|
||||
|
||||
internal static func -(_ lhs: CGPoint, _ rhs: CGPoint) -> CGPoint {
|
||||
return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
|
||||
}
|
||||
|
||||
internal static func *(_ lhs: CGPoint, _ rhs: CGFloat) -> CGPoint {
|
||||
return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs)
|
||||
}
|
||||
|
||||
func distance(to point: CGPoint) -> CGFloat {
|
||||
return sqrt(pow((point.x - x), 2) + pow((point.y - y), 2))
|
||||
}
|
||||
|
||||
func getPointOnCircle(radius: CGFloat, radian: CGFloat) -> CGPoint {
|
||||
let x = self.x + radius * cos(radian)
|
||||
let y = self.y + radius * sin(radian)
|
||||
|
||||
return CGPoint(x: x, y: y)
|
||||
}
|
||||
|
||||
func getRadian(pointOnCircle: CGPoint) -> CGFloat {
|
||||
let originX = pointOnCircle.x - self.x
|
||||
let originY = pointOnCircle.y - self.y
|
||||
var radian = atan2(originY, originX)
|
||||
while radian < 0 {
|
||||
radian += CGFloat(2 * Double.pi)
|
||||
}
|
||||
return radian
|
||||
}
|
||||
|
||||
func getPolarPoint(from origin: CGPoint = CGPoint.zero) -> PolarPoint {
|
||||
let deltaX = self.x - origin.x
|
||||
let deltaY = self.y - origin.y
|
||||
let radians = -1 * atan2(deltaY, deltaX)
|
||||
let degrees = radians * (180.0 / CGFloat.pi)
|
||||
let distance = self.distance(to: origin)
|
||||
|
||||
guard degrees < 0 else {
|
||||
return PolarPoint(degrees: degrees, distance: distance)
|
||||
}
|
||||
return PolarPoint(degrees: degrees + 360.0, distance: distance)
|
||||
}
|
||||
}
|
||||
|
||||
public class JoystickMonitor: ObservableObject {
|
||||
@Published public var xyPoint: CGPoint = .zero
|
||||
@Published public var polarPoint: PolarPoint = .zero
|
||||
|
||||
public init() { }
|
||||
}
|
||||
|
||||
/// A convenience SwiftUI ViewModifier to make a view behave like a Joystick
|
||||
public struct JoystickGestureRecognizer: ViewModifier {
|
||||
@ObservedObject public var joystickMonitor: JoystickMonitor
|
||||
/// The size of the control area in which the drag gesture is monitored and reported, is diameter for a circular Joystick
|
||||
private var width: CGFloat
|
||||
/// The shape of the hitbox for the position output of the Joystick Thumb position
|
||||
private var shapeType: JoystickShape
|
||||
/// The center point of the Joystick where it goes to rest when not being used in `locksInPlace` is false
|
||||
private let midPoint: CGPoint
|
||||
/// Determines whether or not the Joystick Thumb control goes back to the center point when released
|
||||
private let locksInPlace: Bool
|
||||
@Binding private(set) public var thumbPosition: CGPoint
|
||||
|
||||
/// Creates a custom joystick with the following configuration
|
||||
///
|
||||
/// parameter joystickMonitor: An object used to monitor the valid position of the thumb on the Joystick
|
||||
/// parameter width: Width of the joystick control area, for a circular Joystick this is the diameter
|
||||
/// parameter type: Shape of the hitbox for the position output of the Joystick Thumb position
|
||||
/// parameter background: The view displayed as the Joystick background
|
||||
/// parameter foreground: The view displayed as the Joystick Thumb Control
|
||||
/// parameter locksInPlace: Determines if the thumb control returns to the center point when released
|
||||
public init(thumbPosition: Binding<CGPoint>, monitor: JoystickMonitor, width: CGFloat, type: JoystickShape, locksInPlace locks: Bool = false) {
|
||||
self.joystickMonitor = monitor
|
||||
self._thumbPosition = thumbPosition
|
||||
self.width = width
|
||||
self.midPoint = CGPoint(x: width / 2, y: width / 2)
|
||||
self.shapeType = type
|
||||
self.locksInPlace = locks
|
||||
}
|
||||
|
||||
/// Produce the correct shape of Joystick
|
||||
public func body(content: Content) -> some View {
|
||||
switch self.shapeType {
|
||||
case .rect:
|
||||
rectBody(content)
|
||||
case .circle:
|
||||
circleBody(content)
|
||||
}
|
||||
}
|
||||
|
||||
internal func getValidThumbCoordinate(for value: inout CGFloat) {
|
||||
if value <= 0 {
|
||||
value = 0
|
||||
} else if value > width {
|
||||
value = self.width
|
||||
}
|
||||
}
|
||||
|
||||
internal func validateCoordinate(_ emitPoint: inout CGPoint) {
|
||||
emitPoint = emitPoint * 2
|
||||
if emitPoint.x > width {
|
||||
emitPoint.x = width
|
||||
} else if emitPoint.x < -width {
|
||||
emitPoint.x = -width
|
||||
}
|
||||
if emitPoint.y > width {
|
||||
emitPoint.y = width
|
||||
} else if emitPoint.y < -width {
|
||||
emitPoint.y = -width
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the coordinates of the user's thumb to the JoystickMonitor, which emits an object change since it is an observable
|
||||
internal func emitPosition(for xyPoint: CGPoint) {
|
||||
var emitPoint = xyPoint
|
||||
validateCoordinate(&emitPoint)
|
||||
self.joystickMonitor.xyPoint = emitPoint
|
||||
self.joystickMonitor.polarPoint = emitPoint.getPolarPoint(from: self.midPoint)
|
||||
}
|
||||
|
||||
/// Provides a Rectangular area in which the Joystick control can move within and report values for
|
||||
///
|
||||
/// - parameter content: The view for which to apply the Joystick listener/DragGesture
|
||||
public func rectBody(_ content: Content) -> some View {
|
||||
content
|
||||
.contentShape(Rectangle())
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0, coordinateSpace: .local)
|
||||
.onChanged({ value in
|
||||
var thumbX = value.location.x
|
||||
var thumbY = value.location.y
|
||||
self.getValidThumbCoordinate(for: &thumbX)
|
||||
self.getValidThumbCoordinate(for: &thumbY)
|
||||
self.thumbPosition = CGPoint(x: thumbX, y: thumbY)
|
||||
let position = value.location - self.midPoint
|
||||
self.emitPosition(for: position)
|
||||
})
|
||||
.onEnded({ value in
|
||||
if !locksInPlace {
|
||||
self.thumbPosition = self.midPoint
|
||||
self.emitPosition(for: .zero)
|
||||
}
|
||||
})
|
||||
.exclusively(
|
||||
before:
|
||||
LongPressGesture(minimumDuration: 0.0, maximumDistance: 0.0)
|
||||
.onEnded({ _ in
|
||||
if !locksInPlace {
|
||||
self.thumbPosition = self.midPoint
|
||||
self.emitPosition(for: .zero)
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/// Provides a Circular area in which the Joystick control can move within and report values forr
|
||||
///
|
||||
/// - parameter content: The view for which to apply the Joystick listener/DragGesture
|
||||
public func circleBody(_ content: Content) -> some View {
|
||||
content
|
||||
.contentShape(Circle())
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0, coordinateSpace: .local)
|
||||
.onChanged() { value in
|
||||
let distance = self.midPoint.distance(to: value.location)
|
||||
if distance > self.width / 2 {
|
||||
// Limit to radius
|
||||
let k = (self.width / 2) / distance
|
||||
let position = (value.location - self.midPoint) * k
|
||||
// Order matters
|
||||
self.thumbPosition = position + self.midPoint
|
||||
self.emitPosition(for: position)
|
||||
} else {
|
||||
self.thumbPosition = value.location
|
||||
let position = value.location - self.midPoint
|
||||
self.emitPosition(for: position)
|
||||
}
|
||||
}
|
||||
.onEnded({ value in
|
||||
if !locksInPlace {
|
||||
self.thumbPosition = self.midPoint
|
||||
self.emitPosition(for: .zero)
|
||||
}
|
||||
})
|
||||
.exclusively(
|
||||
before:
|
||||
LongPressGesture(minimumDuration: 0.0, maximumDistance: 0.0)
|
||||
.onEnded({ _ in
|
||||
if !locksInPlace {
|
||||
self.thumbPosition = self.midPoint
|
||||
self.emitPosition(for: .zero)
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience SwiftUI struct to make a Joystick control
|
||||
public struct JoystickBuilder<background: View, foreground: View>: View {
|
||||
/// The width of the joystick control area, for a circular Joystick this is the diameter
|
||||
private(set) public var width: CGFloat
|
||||
/// The shape of the hitbox for the position output of the Joystick Thumb position
|
||||
private(set) public var controlShape: JoystickShape
|
||||
|
||||
@ObservedObject private(set) public var joystickMonitor: JoystickMonitor
|
||||
@State private(set) public var thumbPosition: CGPoint = .zero
|
||||
/// The view displayed as the Joystick background, which also holds a Joystick DragGesture recognizer
|
||||
@ViewBuilder public var controlBackground: () -> background
|
||||
/// The view displayed as the Joystick Thumb Control, which also holds a Joystick DragGesture recognizer
|
||||
@ViewBuilder public var controlThumb: () -> foreground
|
||||
/// Determines whether or not the Joystick Thumb control goes back to the center point when released
|
||||
private let locksInPlace: Bool
|
||||
|
||||
/// Creates a custom joystick with two views that are passed to it
|
||||
///
|
||||
/// parameter position: Will output the valid position of the thumb on the Joystick, from 0 to width
|
||||
/// parameter width: Width of the joystick control area, for a circular Joystick this is the diameter
|
||||
/// parameter shape: Shape of the hitbox for the position output of the Joystick Thumb position
|
||||
/// parameter background: The view displayed as the Joystick background
|
||||
/// parameter foreground: The view displayed as the Joystick Thumb Control
|
||||
/// parameter locksInPlace: Determines if the thumb control returns to the center point when released
|
||||
public init(monitor: JoystickMonitor, width: CGFloat, shape: JoystickShape, @ViewBuilder background: @escaping () -> background, @ViewBuilder foreground: @escaping () -> foreground, locksInPlace locks: Bool) {
|
||||
self.joystickMonitor = monitor
|
||||
self.width = width
|
||||
self.controlShape = shape
|
||||
self.controlBackground = background
|
||||
self.controlThumb = foreground
|
||||
self.locksInPlace = locks
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
controlBackground()
|
||||
.frame(width: self.width, height: self.width)
|
||||
.joystickGestureRecognizer(thumbPosition: self.$thumbPosition, monitor: self.joystickMonitor, width: self.width, shape: self.controlShape, locksInPlace: self.locksInPlace)
|
||||
.overlay(
|
||||
controlThumb()
|
||||
.frame(width: self.width / 4, height: self.width / 4)
|
||||
.position(x: self.thumbPosition.x, y: self.thumbPosition.y)
|
||||
.joystickGestureRecognizer(thumbPosition: self.$thumbPosition, monitor: self.joystickMonitor, width: self.width, shape: self.controlShape, locksInPlace: self.locksInPlace)
|
||||
)
|
||||
.onAppear(perform: {
|
||||
let midPoint = self.width / 2
|
||||
self.thumbPosition = CGPoint(x: midPoint, y: midPoint)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public extension View {
|
||||
/// Convenience modifier for adding a Joystick recognizer
|
||||
/// Creates a custom joystick with the following configuration
|
||||
///
|
||||
/// parameter joystickMonitor: An object used to monitor the valid position of the thumb on the Joystick
|
||||
/// parameter width: Width of the joystick control area, for a circular Joystick this is the diameter
|
||||
/// parameter shape: (.rect || .circle) - Shape of the hitbox for the position output of the Joystick Thumb position
|
||||
/// parameter background: The view displayed as the Joystick background
|
||||
/// parameter foreground: The view displayed as the Joystick Thumb Control
|
||||
/// parameter locksInPlace: default false - Determines if the thumb control returns to the center point when released
|
||||
/// parameter locksInPlace: default false - Determines if the thumb control returns to the center point when released
|
||||
func joystickGestureRecognizer(thumbPosition: Binding<CGPoint>, monitor: JoystickMonitor, width: CGFloat, shape: JoystickShape, locksInPlace locks: Bool = false) -> some View {
|
||||
modifier(JoystickGestureRecognizer(thumbPosition: thumbPosition, monitor: monitor, width: width, type: shape, locksInPlace: locks))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#define VMA_IMPLEMENTATION
|
||||
#include "video_core/vulkan_common/vma.h"
|
||||
@@ -454,7 +454,6 @@ else()
|
||||
else()
|
||||
target_compile_options(video_core PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Wconversion>)
|
||||
endif()
|
||||
|
||||
target_compile_options(video_core PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
||||
|
||||
# xbyak
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
@@ -13,9 +13,14 @@
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable : 4189 )
|
||||
#elif defined(__clang__)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wunused-variable"
|
||||
#endif
|
||||
#include "vk_mem_alloc.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning( pop )
|
||||
#elif defined(__clang__)
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
@@ -22,3 +22,27 @@ target_link_libraries(web_service PRIVATE common network nlohmann_json::nlohmann
|
||||
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(web_service PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
if (IOS)
|
||||
# TODO: this is horrific
|
||||
find_package(OpenSSL 3 QUIET)
|
||||
if (OpenSSL_FOUND)
|
||||
target_link_libraries(web_service PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
if (NOT IOS)
|
||||
target_compile_definitions(web_service PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
endif()
|
||||
else()
|
||||
find_package(OpenSSL 3 QUIET PATHS /usr/local/opt/openssl /opt/homebrew/opt/openssl NO_DEFAULT_PATH)
|
||||
if (OpenSSL_FOUND)
|
||||
target_link_libraries(web_service PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
if (NOT IOS)
|
||||
target_compile_definitions(web_service PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "OpenSSL not found or version is less than 3. Some features in web_service may be disabled.")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
find_package(OpenSSL REQUIRED)
|
||||
target_link_libraries(web_service PRIVATE OpenSSL::SSL OpenSSL::Crypto)
|
||||
target_compile_definitions(web_service PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
endif()
|
||||
|
||||
@@ -363,7 +363,7 @@ if (APPLE)
|
||||
if (CMAKE_GENERATOR MATCHES "Xcode")
|
||||
set(_icons "${_dist}/eden.icon")
|
||||
|
||||
set_target_properties(eden PROPERTIES
|
||||
set_target_properties(yuzu PROPERTIES
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME eden
|
||||
MACOSX_BUNDLE_ICON_FILE eden
|
||||
# Also force xcode to manage signing for us.
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${EXECUTABLE_NAME}</string>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string></string>
|
||||
<key>CFBundleIconFile</key>
|
||||
|
||||
Reference in New Issue
Block a user