Compare commits

...

51 commits

Author SHA1 Message Date
crueter
3cc2caf4fe Fix build script
Signed-off-by: crueter <crueter@eden-emu.dev>
2026-03-29 20:50:03 +00:00
lizzie
5d6cea5cf9 AAAAAA 2026-03-29 20:50:03 +00:00
crueter
0a8cbe6d8d Build fixes
Signed-off-by: crueter <crueter@eden-emu.dev>
2026-03-29 20:50:03 +00:00
lizzie
0b0778f38b add basic ios shit 2026-03-29 20:50:03 +00:00
crueter
4b7cf24cc5 Revert stbi
Signed-off-by: crueter <crueter@eden-emu.dev>
2026-03-29 20:50:03 +00:00
crueter
f1acc2887e fix libs
Signed-off-by: crueter <crueter@eden-emu.dev>
2026-03-29 20:50:03 +00:00
crueter
3dc0dcef83 Fix most build errors
Signed-off-by: crueter <crueter@eden-emu.dev>
2026-03-29 20:50:03 +00:00
crueter
cf84258120 Update CPMUtil, and fix script
Signed-off-by: crueter <crueter@eden-emu.dev>
2026-03-29 20:50:03 +00:00
lizzie
93e9f7ea3e DISABLE BY DEFAULT ON IOS FFS 2026-03-29 20:50:03 +00:00
lizzie
7b720d0d3c fix life 2026-03-29 20:50:03 +00:00
lizzie
22e822f788 fix sirit i think, add ios-aarch64 2026-03-29 20:50:03 +00:00
lizzie
67a0039a7d changes? 2026-03-29 20:50:03 +00:00
lizzie
39878272e1 fix shit? 2026-03-29 20:50:03 +00:00
lizzie
697fa4f10a fix #include "common/logging.h" 2026-03-29 20:50:03 +00:00
lizzie
36da1f1cb8 fix IOS again fucking objc bridge 2026-03-29 20:50:03 +00:00
lizzie
4801476a29 fix? 2026-03-29 20:50:03 +00:00
lizzie
ef664e3764 properly use bridging header, fix headers 2026-03-29 20:50:03 +00:00
lizzie
326f840b2f fix swift driver I HOPE 2026-03-29 20:50:03 +00:00
lizzie
237f7ca725 license 2026-03-29 20:50:03 +00:00
lizzie
7ae28eb89c [temporary c++ shit] 2026-03-29 20:50:03 +00:00
lizzie
d4ccdd4e13 bit of cmake fuckery 2026-03-29 20:50:03 +00:00
lizzie
680394f339 use language generator exprs 2026-03-29 20:50:03 +00:00
lizzie
36c8b3a126 $<$<COMPILE_LANGUAGE:C,CXX>:-Werror=missing-declarations> 2026-03-29 20:50:03 +00:00
lizzie
36506b04a5 proper linkings?! 2026-03-29 20:50:03 +00:00
lizzie
d25307ceee bridge changes/fixes 2026-03-29 20:50:03 +00:00
lizzie
db35ed37ce fix license 2026-03-29 20:50:03 +00:00
lizzie
561c00de90 fix xcode 2? 2026-03-29 20:50:03 +00:00
lizzie
616e598d2e fix xcode paths? 2026-03-29 20:50:03 +00:00
lizzie
812bceb2ed fx 2026-03-29 20:50:03 +00:00
lizzie
81f3646db4 fx 2026-03-29 20:50:03 +00:00
lizzie
0b85a3f063 fix boost 2026-03-29 20:50:03 +00:00
lizzie
68c8899159 fx 2026-03-29 20:50:03 +00:00
lizzie
42dffccc64 fix stuff 2026-03-29 20:50:03 +00:00
lizzie
d58d7b3682 stupid macos 2026-03-29 20:50:02 +00:00
lizzie
83a7c5db13 fix1 2026-03-29 20:50:02 +00:00
lizzie
d683e902a7 fx 2026-03-29 20:50:02 +00:00
lizzie
b86f28b2d8 fix spirv-tools 2026-03-29 20:50:02 +00:00
lizzie
f01ae5fcf6 fixes for ios spirv tools 2026-03-29 20:50:02 +00:00
lizzie
47459d5a44 fix license 2026-03-29 20:50:02 +00:00
lizzie
e2e281f957 fix ffmpeg 2026-03-29 20:50:02 +00:00
lizzie
2c002d9721 fx 2026-03-29 20:50:02 +00:00
lizzie
833a3118b4 fx 2026-03-29 20:50:02 +00:00
lizzie
43d6d9b3db license 2026-03-29 20:50:02 +00:00
lizzie
66fea843d4 ios toolchain cmake 2026-03-29 20:50:02 +00:00
lizzie
9f3656d969 license 2026-03-29 20:50:02 +00:00
lizzie
6192b6a182 license headers 2026-03-29 20:50:02 +00:00
lizzie
c296153d71 flatten + cmake 2026-03-29 20:50:02 +00:00
lizzie
1d072f1244 flatten 2026-03-29 20:50:02 +00:00
lizzie
5d725d6d08 loicense 2026-03-29 20:50:02 +00:00
lizzie
2f9687dfcf modernize #1 2026-03-29 20:50:02 +00:00
lizzie
7811457de5 sudachi ios stuff 2026-03-29 20:50:02 +00:00
64 changed files with 3410 additions and 107 deletions

19
.ci/ios/build.sh Executable file
View file

@ -0,0 +1,19 @@
#!/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 \
"$@"
cmake --build build/ios -t eden-ios --config Release

1180
.ci/ios/ios-toolchain.cmake Normal file

File diff suppressed because it is too large Load diff

View file

@ -115,7 +115,7 @@ for file in $FILES; do
*.cmake|*.sh|*CMakeLists.txt)
begin="#"
;;
*.kt*|*.cpp|*.h|*.qml)
*.kt*|*.cpp|*.h|*.qml|*.swift|*.mm)
begin="//"
;;
*)

View file

@ -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_()
{

View file

@ -0,0 +1,33 @@
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 7ab2319..333e325 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -151,9 +151,11 @@ add_custom_command(OUTPUT ${SPIRV_TOOLS_BUILD_VERSION_INC}
COMMENT "Update build-version.inc in the SPIRV-Tools build directory (if necessary).")
# Convenience target for standalone generation of the build-version.inc file.
# This is not required for any dependence chain.
-add_custom_target(spirv-tools-build-version
- DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC})
-set_property(TARGET spirv-tools-build-version PROPERTY FOLDER "SPIRV-Tools build")
+if (NOT IOS)
+ add_custom_target(spirv-tools-build-version
+ DEPENDS ${SPIRV_TOOLS_BUILD_VERSION_INC})
+ set_property(TARGET spirv-tools-build-version PROPERTY FOLDER "SPIRV-Tools build")
+endif()
list(APPEND PCH_DEPENDS
${CORE_TABLES_HEADER_INC_FILE}
@@ -338,8 +340,11 @@ function(spirv_tools_default_target_options target)
)
set_property(TARGET ${target} PROPERTY FOLDER "SPIRV-Tools libraries")
spvtools_check_symbol_exports(${target})
- add_dependencies(${target}
- spirv-tools-build-version core_tables extinst_tables)
+ if (IOS)
+ add_dependencies(${target} core_tables extinst_tables)
+ else ()
+ add_dependencies(${target} spirv-tools-build-version core_tables extinst_tables)
+ endif()
endfunction()
if (SPIRV_TOOLS_BUILD_SHARED)

View file

@ -21,6 +21,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()
@ -42,7 +65,7 @@ if (PLATFORM_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 "PLATFORM_LINUX" OFF)
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "PLATFORM_LINUX OR WIN32 OR (APPLE AND NOT IOS)" OFF)
if (YUZU_STATIC_ROOM)
set(YUZU_ROOM ON)
set(YUZU_ROOM_STANDALONE ON)
@ -67,9 +90,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")
@ -170,31 +199,32 @@ if (MSVC AND NOT CXX_CLANG)
set(CMAKE_CXX_FLAGS_INIT "${CMAKE_CXX_FLAGS_INIT} /W3 /WX-")
endif()
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Build SDL2 from external source" OFF "NOT MSVC;NOT ANDROID" OFF)
cmake_dependent_option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${MSVC}" "NOT ANDROID" OFF)
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()
# TODO(crueter): Cleanup, each dep that has a bundled option should allow to choose between bundled, external, system
cmake_dependent_option(YUZU_USE_EXTERNAL_SDL2 "Build SDL2 from external source" OFF "NOT MSVC;NOT ANDROID" OFF)
cmake_dependent_option(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 build" "${EXT_DEFAULT}" "NOT ANDROID" OFF)
# TODO(crueter): did not find header 'AudioHardware.h' in framework 'CoreAudio'
cmake_dependent_option(ENABLE_CUBEB "Enables the cubeb audio backend" ON "NOT IOS" OFF)
# ffmpeg
option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${PLATFORM_SUN}" "NOT WIN32 AND NOT ANDROID" OFF)
# 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 PLATFORM_LINUX OR PLATFORM_FREEBSD OR APPLE" OFF)
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR PLATFORM_FREEBSD OR (APPLE AND NOT IOS)" OFF)
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF)
mark_as_advanced(FORCE ENABLE_OPENGL)
@ -212,10 +242,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 PLATFORM_LINUX" OFF)
@ -283,7 +313,7 @@ if (YUZU_ROOM)
add_compile_definitions(YUZU_ROOM)
endif()
if ((ANDROID OR APPLE OR UNIX) AND (NOT PLATFORM_LINUX OR ANDROID) AND NOT WIN32)
if ((ANDROID OR APPLE OR UNIX OR IOS) AND (NOT PLATFORM_LINUX OR ANDROID) AND NOT WIN32)
if(CXX_APPLE OR CXX_CLANG)
# libc++ has stop_token and jthread as experimental
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
@ -359,7 +389,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)
@ -484,9 +517,15 @@ endfunction()
# Platform-specific library requirements
# Put these BEFORE EXTERNALS or Boost WILL die
# =============================================
if (APPLE)
foreach(fw Carbon Metal Cocoa IOKit CoreVideo CoreMedia)
set(_libs Metal IOKit CoreVideo CoreMedia)
if (IOS)
list(APPEND _libs objc)
else()
list(APPEND _libs Carbon Cocoa)
endif()
foreach(fw ${_libs})
find_library(${fw}_LIBRARY ${fw} REQUIRED)
list(APPEND PLATFORM_LIBRARIES ${${fw}_LIBRARY})
endforeach()

View file

@ -3,7 +3,7 @@
set(CPM_SOURCE_CACHE "${PROJECT_SOURCE_DIR}/.cache/cpm" CACHE STRING "" FORCE)
if(MSVC OR ANDROID)
if(MSVC OR ANDROID OR IOS)
set(BUNDLED_DEFAULT ON)
else()
set(BUNDLED_DEFAULT OFF)
@ -690,8 +690,10 @@ function(AddCIPackage)
set(pkgname linux-amd64)
elseif(PLATFORM_LINUX AND ARCHITECTURE_arm64)
set(pkgname linux-aarch64)
elseif(APPLE)
elseif(APPLE AND NOT IOS)
set(pkgname macos-universal)
elseif(IOS AND ARCHITECTURE_arm64)
set(pkgname ios-aarch64)
endif()
if (DEFINED pkgname AND NOT "${pkgname}" IN_LIST DISABLED_PLATFORMS)

View file

@ -17,7 +17,8 @@
"version": "1.57",
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
"patches": [
"0001-clang-cl.patch"
"0001-clang-cl.patch",
"0002-ios-fix.patch"
]
},
"fmt": {

View file

@ -18,3 +18,4 @@
- `linux-amd64`
- `linux-aarch64`
- `macos-universal`
- `ios-aarch64`

View file

@ -61,7 +61,8 @@ In order: OpenSSL CI, Boost (tag + artifact), Opus (options + find_args), discor
"version": "3.6.0",
"min_version": "1.1.1",
"disabled_platforms": [
"macos-universal"
"macos-universal",
"ios-aarch64"
]
},
"boost": {

View file

@ -4,6 +4,7 @@
- [Arch Linux](#arch-linux)
- [Gentoo Linux](#gentoo-linux)
- [macOS](#macos)
- [iOS](#ios)
- [Solaris](#solaris)
- [HaikuOS](#haikuos)
- [OpenBSD](#openbsd)
@ -31,6 +32,16 @@ If you're having issues with building, always consult that ebuild.
macOS is largely untested. Expect crashes, significant Vulkan issues, and other fun stuff.
## 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
```
## Solaris
Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability.

View file

@ -228,6 +228,10 @@ if (VulkanMemoryAllocator_ADDED)
endif()
# httplib
if (IOS)
set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF)
endif()
AddJsonPackage(httplib)
# cpp-jwt

View file

@ -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}")

View file

@ -51,6 +51,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)

View file

@ -23,7 +23,7 @@
"package": "sirit",
"name": "sirit",
"repo": "eden-emulator/sirit",
"version": "1.0.4"
"version": "1.0.5"
},
"httplib": {
"repo": "yhirose/cpp-httplib",
@ -36,7 +36,8 @@
"0002-fix-zstd.patch"
],
"options": [
"HTTPLIB_REQUIRE_OPENSSL ON"
"HTTPLIB_REQUIRE_OPENSSL ON",
"HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON"
]
},
"cpp-jwt": {
@ -111,7 +112,8 @@
],
"patches": [
"0001-netbsd-fix.patch",
"0002-allow-static-only.patch"
"0002-allow-static-only.patch",
"0003-ios-fix.patch"
]
},
"spirv-headers": {

View file

@ -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: 2021 yuzu Emulator Project
@ -11,9 +11,9 @@ set(FFmpeg_HWACCEL_FLAGS)
set(FFmpeg_HWACCEL_INCLUDE_DIRS)
set(FFmpeg_HWACCEL_LDFLAGS)
if (UNIX AND NOT ANDROID)
if (UNIX AND NOT ANDROID AND NOT IOS)
find_package(PkgConfig REQUIRED)
if (NOT ANDROID)
if (NOT ANDROID AND NOT IOS)
pkg_check_modules(LIBVA libva)
pkg_check_modules(CUDA cuda)
pkg_check_modules(FFNVCODEC ffnvcodec)
@ -182,6 +182,10 @@ else()
find_program(BASH_PROGRAM bash REQUIRED)
set(FFmpeg_CROSS_COMPILE_FLAGS "")
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
# `--disable-vdpau` is needed to avoid linking issues
set(FFmpeg_CC ${CMAKE_C_COMPILER_LAUNCHER} ${CMAKE_C_COMPILER})
set(FFmpeg_CXX ${CMAKE_CXX_COMPILER_LAUNCHER} ${CMAKE_CXX_COMPILER})
if (ANDROID)
string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" FFmpeg_HOST_SYSTEM_NAME)
set(TOOLCHAIN "${ANDROID_NDK}/toolchains/llvm/prebuilt/${FFmpeg_HOST_SYSTEM_NAME}-${CMAKE_HOST_SYSTEM_PROCESSOR}")
@ -197,12 +201,22 @@ else()
--extra-ldflags="--ld-path=${TOOLCHAIN}/bin/ld.lld"
--extra-ldflags="-nostdlib"
)
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
)
endif()
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
# `--disable-vdpau` is needed to avoid linking issues
set(FFmpeg_CC ${CMAKE_C_COMPILER_LAUNCHER} ${CMAKE_C_COMPILER})
set(FFmpeg_CXX ${CMAKE_CXX_COMPILER_LAUNCHER} ${CMAKE_CXX_COMPILER})
add_custom_command(
OUTPUT
${FFmpeg_MAKEFILE}

View file

@ -24,7 +24,8 @@ if (MINGW OR PLATFORM_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()

View file

@ -127,13 +127,15 @@ else()
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=all>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=extra>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=missing-declarations>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=shadow>
$<$<COMPILE_LANGUAGE:C,CXX>:-Werror=unused>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-attributes>
$<$<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)
@ -249,4 +251,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)

View file

@ -144,7 +144,8 @@ add_library(
zstd_compression.cpp
zstd_compression.h
fs/ryujinx_compat.h fs/ryujinx_compat.cpp
fs/symlink.h fs/symlink.cpp)
fs/symlink.h fs/symlink.cpp
httplib.h)
if(WIN32)
target_sources(common PRIVATE windows/timer_resolution.cpp
@ -242,7 +243,7 @@ else()
target_link_libraries(common PUBLIC Boost::headers)
endif()
target_link_libraries(common PUBLIC Boost::filesystem Boost::context)
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib)
if (lz4_ADDED)
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)

View file

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

View file

@ -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__)

9
src/common/httplib.h Normal file
View file

@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#define CPPHTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include <httplib.h>

View file

@ -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: 2013 Dolphin Emulator Project
@ -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

View file

@ -1264,12 +1264,15 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
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()
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
target_compile_definitions(core PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
# TODO

View file

@ -1,10 +1,10 @@
// 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
#include <dynarmic/interface/halt_reason.h>
#include "dynarmic/src/dynarmic/interface/halt_reason.h"
#include "core/arm/arm_interface.h"

View file

@ -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"

View file

@ -10,12 +10,12 @@
#include <memory>
#include <ankerl/unordered_dense.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/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;

View file

@ -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 {

View file

@ -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"

View file

@ -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

View file

@ -15,9 +15,7 @@
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#include <httplib.h>
#endif
#include "common/httplib.h"
#include <chrono>
#include <cstring>
@ -37,7 +35,7 @@
namespace Service::News {
namespace {
constexpr const char* GitHubAPI_EdenReleases = "/repos/eden-emulator/Releases/releases";
[[maybe_unused]] constexpr const char* GitHubAPI_EdenReleases = "/repos/eden-emulator/Releases/releases";
// Cached logo data
std::vector<u8> default_logo_small;
@ -104,7 +102,6 @@ std::vector<u8> TryLoadFromDisk(const std::filesystem::path& path) {
std::vector<u8> DownloadImage(const std::string& url_path, const std::filesystem::path& cache_path) {
LOG_INFO(Service_BCAT, "Downloading image: https://eden-emu.dev{}", url_path);
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
try {
httplib::Client cli("https://eden-emu.dev");
cli.set_follow_location(true);
@ -128,7 +125,6 @@ std::vector<u8> DownloadImage(const std::string& url_path, const std::filesystem
} catch (...) {
LOG_WARNING(Service_BCAT, "Failed to download: {}", url_path);
}
#endif
return {};
}
@ -233,7 +229,6 @@ void WriteCachedJson(std::string_view json) {
std::optional<std::string> DownloadReleasesJson() {
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
try {
httplib::SSLClient cli{"api.github.com", 443};
cli.set_connection_timeout(10);
@ -255,7 +250,7 @@ std::optional<std::string> DownloadReleasesJson() {
} catch (...) {
LOG_WARNING(Service_BCAT, " failed to download releases");
}
#endif
return std::nullopt;
}

View file

@ -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"

View file

@ -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 {

View file

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

View file

@ -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 {

View file

@ -14,7 +14,7 @@
#include <memory>
#include <optional>
#include "dynarmic/interface/optimization_flags.h"
#include "../optimization_flags.h"
namespace Dynarmic {
class ExclusiveMonitor;

View file

@ -1,3 +1,6 @@
// 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) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
@ -11,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 {

View file

@ -22,8 +22,6 @@ if (ENABLE_UPDATE_CHECKER)
target_sources(frontend_common PRIVATE
update_checker.cpp
update_checker.h)
target_compile_definitions(frontend_common PUBLIC CPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(frontend_common PRIVATE OpenSSL::SSL OpenSSL::Crypto)
endif()

View file

@ -13,7 +13,7 @@
#include "common/scm_rev.h"
#include "update_checker.h"
#include <httplib.h>
#include "common/httplib.h"
#ifdef YUZU_BUNDLED_OPENSSL
#include <openssl/cert.h>

View file

@ -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 */

105
src/ios/AppUI.swift Normal file
View file

@ -0,0 +1,105 @@
// 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
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()
}
}

View file

@ -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

View file

@ -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

93
src/ios/AppUIObjC.h Normal file
View file

@ -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

251
src/ios/AppUIObjC.mm Normal file
View file

@ -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

73
src/ios/CMakeLists.txt Normal file
View file

@ -0,0 +1,73 @@
# 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
PomeloApp.swift
ContentView.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 SDL2::SDL2 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})

19
src/ios/Config.h Normal file
View file

@ -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

11
src/ios/Config.mm Normal file
View file

@ -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

19
src/ios/ContentView.swift Normal file
View file

@ -0,0 +1,19 @@
// 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 AppUI
struct ContentView: View {
// @State var core = Core(games: [], root: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0])
var body: some View {
// HomeView(core: core).onAppear() {
// Air.play(AnyView(
// Text("Select Game").font(.system(size: 100))
// ))
// // rest of death
// }
}
}

103
src/ios/EmulationSession.h Normal file
View file

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

474
src/ios/EmulationSession.mm Normal file
View file

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

86
src/ios/EmulationWindow.h Normal file
View file

@ -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

View file

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

19
src/ios/PomeloApp.swift Normal file
View file

@ -0,0 +1,19 @@
// 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
infix operator --: LogicalDisjunctionPrecedence
func --(lhs: Bool, rhs: Bool) -> Bool {
return lhs || rhs
}
@main
struct PomeloApp: App {
var body: some Scene {
WindowGroup { ContentView() }
}
}

5
src/ios/VMA.cpp Normal file
View file

@ -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"

View file

@ -50,7 +50,6 @@ if (USE_DISCORD_PRESENCE)
if (YUZU_USE_BUNDLED_OPENSSL)
target_link_libraries(qt_common PUBLIC OpenSSL::SSL OpenSSL::Crypto)
target_compile_definitions(qt_common PRIVATE CPPHTTPLIB_OPENSSL_SUPPORT)
endif()
target_compile_definitions(qt_common PUBLIC USE_DISCORD_PRESENCE)

View file

@ -9,7 +9,7 @@
#include <QEventLoop>
#include <boost/algorithm/string/replace.hpp>
#include <httplib.h>
#include "common/httplib.h"
#include <discord_rpc.h>
#include <fmt/format.h>

View file

@ -369,7 +369,6 @@ else()
else()
target_compile_options(video_core PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Werror=conversion>)
endif()
target_compile_options(video_core PRIVATE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
# xbyak

View file

@ -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

View file

@ -1,3 +1,6 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2018 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later

View file

@ -16,7 +16,7 @@
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
#endif
#include <httplib.h>
#include "common/httplib.h"
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

View file

@ -369,7 +369,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.

View file

@ -83,7 +83,7 @@ ci_package() {
android-aarch64 android-x86_64 \
solaris-amd64 freebsd-amd64 openbsd-amd64 \
linux-amd64 linux-aarch64 \
macos-universal; do
macos-universal ios-aarch64; do
echo "-- * platform $platform"
case $DISABLED in

View file

@ -151,7 +151,7 @@ mingw-amd64 mingw-arm64
android-aarch64 android-x86_64
solaris-amd64 freebsd-amd64 openbsd-amd64
linux-amd64 linux-aarch64
macos-universal"
macos-universal ios-aarch64"
DISABLED_PLATFORMS="$reply"
fi