mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-10 03:18:55 +02:00
Compare commits
24 commits
8db6b82bdf
...
7e88092998
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e88092998 | ||
|
|
8d99782267 | ||
|
|
93f69afe37 | ||
|
|
f23af26096 | ||
|
|
2649b82ff5 | ||
|
|
e7717708f1 | ||
|
|
ab77cb7537 | ||
|
|
94dcf09617 | ||
|
|
4a065abff2 | ||
|
|
dbd0c65df0 | ||
|
|
94cc8727e8 | ||
|
|
2089d508a2 | ||
|
|
d001c9bcba | ||
|
|
74a0456a5f | ||
|
|
3f1f3db6d8 | ||
|
|
7f97290516 | ||
|
|
a751088abf | ||
|
|
cfab359439 | ||
|
|
56e207210c | ||
|
|
a89c8ba5fc | ||
|
|
70620eb635 | ||
|
|
9e16918f3c | ||
|
|
0d80fccbcd | ||
|
|
feaedd02f7 |
29 changed files with 3167 additions and 25 deletions
34
.ci/ios/build.sh
Executable file
34
.ci/ios/build.sh
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
#!/bin/sh -ex
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
WORK_DIR="$PWD"
|
||||
export IOS_SDK="$(xcrun --sdk iphoneos --show-sdk-path)"
|
||||
|
||||
[ ! -z "$IOS_SDK" ]
|
||||
|
||||
cmake -G Xcode -B build \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$WORK_DIR/.ci/ios/ios-toolchain.cmake" \
|
||||
-DPLATFORM=OS64 \
|
||||
-DDEPLOYMENT_TARGET=16.0 \
|
||||
-DCOCOA_LIBRARY="$IOS_SDK/System/Library/Frameworks/Cocoa.framework" \
|
||||
-DCMAKE_C_COMPILER="$(xcrun --sdk iphoneos clang -arch arm64)" \
|
||||
-DCMAKE_CXX_COMPILER="$(xcrun --sdk iphoneos clang++ -arch arm64)" \
|
||||
-DENABLE_LIBUSB=OFF \
|
||||
-DENABLE_UPDATE_CHECKER=OFF \
|
||||
-DENABLE_QT=OFF \
|
||||
-DENABLE_OPENSSL=OFF \
|
||||
-DENABLE_WEB_SERVICE=OFF \
|
||||
-DENABLE_CUBEB=OFF \
|
||||
-DYUZU_ROOM=OFF \
|
||||
-DYUZU_ROOM_STANDALONE=OFF \
|
||||
-DYUZU_STATIC_ROOM=OFF \
|
||||
-DYUZU_CMD=OFF \
|
||||
-DUSE_DISCORD_PRESENCE=OFF \
|
||||
-DYUZU_USE_EXTERNAL_FFMPEG=ON \
|
||||
-DYUZU_USE_EXTERNAL_SDL2=ON \
|
||||
-DCPMUTIL_FORCE_BUNDLED=ON \
|
||||
-DCMAKE_BUILD_TYPE=Release
|
||||
|
||||
cmake --build build
|
||||
1180
.ci/ios/ios-toolchain.cmake
Normal file
1180
.ci/ios/ios-toolchain.cmake
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -115,7 +115,7 @@ for file in $FILES; do
|
|||
*.cmake|*.sh|*CMakeLists.txt)
|
||||
begin="#"
|
||||
;;
|
||||
*.kt*|*.cpp|*.h)
|
||||
*.kt*|*.cpp|*.h|*.swift|*.mm)
|
||||
begin="//"
|
||||
;;
|
||||
*)
|
||||
|
|
@ -193,7 +193,7 @@ if [ "$UPDATE" = "true" ]; then
|
|||
begin="#"
|
||||
shell=true
|
||||
;;
|
||||
*.kt*|*.cpp|*.h)
|
||||
*.kt*|*.cpp|*.h|*.swift|*.mm)
|
||||
begin="//"
|
||||
shell="false"
|
||||
;;
|
||||
|
|
|
|||
31
.patch/boost/0002-ios-fix.patch
Normal file
31
.patch/boost/0002-ios-fix.patch
Normal 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_()
|
||||
{
|
||||
33
.patch/spirv-tools/0003-ios-fix.patch
Normal file
33
.patch/spirv-tools/0003-ios-fix.patch
Normal 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)
|
||||
|
|
@ -212,7 +212,7 @@ 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)
|
||||
|
|
@ -283,7 +283,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")
|
||||
|
|
@ -487,7 +487,12 @@ if (APPLE)
|
|||
# Umbrella framework for everything GUI-related
|
||||
find_library(COCOA_LIBRARY Cocoa REQUIRED)
|
||||
find_library(IOKIT_LIBRARY IOKit REQUIRED)
|
||||
if (IOS)
|
||||
find_library(OBJC_LIBRARY objc REQUIRED)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY} ${OBJC_LIBRARY})
|
||||
else()
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
||||
endif()
|
||||
elseif (WIN32)
|
||||
# Target Windows 10
|
||||
add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00)
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -38,13 +38,18 @@ This file is based off of Yuzu and Dynarmic.
|
|||
if (CMAKE_OSX_ARCHITECTURES)
|
||||
set(MULTIARCH_BUILD 1)
|
||||
set(ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}")
|
||||
|
||||
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 PARENT_SCOPE)
|
||||
add_definitions(-DARCHITECTURE_arm64=1)
|
||||
else ()
|
||||
# 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()
|
||||
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
|
|
|||
6
externals/cmake-modules/DetectPlatform.cmake
vendored
6
externals/cmake-modules/DetectPlatform.cmake
vendored
|
|
@ -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)
|
||||
|
|
|
|||
3
externals/cpmfile.json
vendored
3
externals/cpmfile.json
vendored
|
|
@ -110,7 +110,8 @@
|
|||
],
|
||||
"patches": [
|
||||
"0001-netbsd-fix.patch",
|
||||
"0002-allow-static-only.patch"
|
||||
"0002-allow-static-only.patch",
|
||||
"0003-ios-fix.patch"
|
||||
]
|
||||
},
|
||||
"spirv-headers": {
|
||||
|
|
|
|||
28
externals/ffmpeg/CMakeLists.txt
vendored
28
externals/ffmpeg/CMakeLists.txt
vendored
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -255,4 +255,8 @@ if (ANDROID)
|
|||
target_include_directories(yuzu-android PRIVATE android/app/src/main)
|
||||
endif()
|
||||
|
||||
if (IOS)
|
||||
add_subdirectory(ios)
|
||||
endif()
|
||||
|
||||
include(GenerateDepHashes)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ add_library(dynarmic STATIC
|
|||
frontend/decoder/matcher.h
|
||||
frontend/imm.cpp
|
||||
frontend/imm.h
|
||||
interface/halt_reason.h
|
||||
interface/exclusive_monitor.h
|
||||
interface/optimization_flags.h
|
||||
ir/acc_type.h
|
||||
|
|
|
|||
14
src/ios/AppUI-Bridging-Header.h
Normal file
14
src/ios/AppUI-Bridging-Header.h
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// AppUI-Bridging-Header.h - Sudachi
|
||||
// Created by Jarrod Norwell on 4/3/2024.
|
||||
//
|
||||
|
||||
#ifndef AppUI_Bridging_Header_h
|
||||
#define AppUI_Bridging_Header_h
|
||||
|
||||
#import "Wrapper/AppUIObjC.h"
|
||||
|
||||
#endif /* AppUI_Bridging_Header_h */
|
||||
107
src/ios/AppUI.swift
Normal file
107
src/ios/AppUI.swift
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// AppUI.swift - Sudachi
|
||||
// Created by Jarrod Norwell on 4/3/2024.
|
||||
//
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
29
src/ios/AppUIGameInformation.h
Normal file
29
src/ios/AppUIGameInformation.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// AppUIGameInformation.h - Sudachi
|
||||
// Created by Jarrod Norwell on 1/20/24.
|
||||
//
|
||||
|
||||
#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
|
||||
442
src/ios/AppUIGameInformation.mm
Normal file
442
src/ios/AppUIGameInformation.mm
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// AppUIGameInformation.mm - Sudachi
|
||||
// Created by Jarrod Norwell on 1/20/24.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "AppUIGameInformation.h"
|
||||
#import "EmulationSession/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/yuzu_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/log.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
|
||||
95
src/ios/AppUIObjC.h
Normal file
95
src/ios/AppUIObjC.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// AppUIObjC.h - Sudachi
|
||||
// Created by Jarrod Norwell on 1/8/24.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MetalKit/MetalKit.h>
|
||||
|
||||
#import "AppUIGameInformation/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
|
||||
263
src/ios/AppUIObjC.mm
Normal file
263
src/ios/AppUIObjC.mm
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// AppUIObjC.mm - Sudachi
|
||||
// Created by Jarrod Norwell on 1/8/24.
|
||||
//
|
||||
|
||||
#import "AppUIObjC.h"
|
||||
|
||||
#import "Config/Config.h"
|
||||
#import "EmulationSession/EmulationSession.h"
|
||||
#import "DirectoryManager/DirectoryManager.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/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.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/yuzu_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"
|
||||
|
||||
|
||||
#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.shader_backend.SetValue(Settings::ShaderBackend::SpirV);
|
||||
// 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((__bridge CA::MetalLayer*)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((__bridge CA::MetalLayer*)layer, size);
|
||||
}
|
||||
|
||||
-(void) settingsChanged {
|
||||
Config{"config", Config::ConfigType::GlobalConfig};
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
29
src/ios/CMakeLists.txt
Normal file
29
src/ios/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# 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
|
||||
)
|
||||
|
||||
target_link_libraries(eden-ios PRIVATE common core input_common frontend_common video_core glad)
|
||||
target_link_libraries(eden-ios PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
|
||||
target_link_libraries(eden-ios PRIVATE SDL2::SDL2)
|
||||
create_target_directory_groups(eden-ios)
|
||||
target_compile_options(eden-ios PRIVATE
|
||||
-Wno-conversion
|
||||
-Wno-unused-variable
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers)
|
||||
19
src/ios/Config.h
Normal file
19
src/ios/Config.h
Normal 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
11
src/ios/Config.mm
Normal 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
|
||||
101
src/ios/EmulationSession.h
Normal file
101
src/ios/EmulationSession.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// EmulationSession.h - Sudachi
|
||||
// Created by Jarrod Norwell on 1/20/24.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#import <QuartzCore/CAMetalLayer.h>
|
||||
|
||||
#import <Metal/Metal.hpp>
|
||||
#import "EmulationWindow/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();
|
||||
CA::MetalLayer* NativeWindow() const;
|
||||
void SetNativeWindow(CA::MetalLayer* 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;
|
||||
CA::MetalLayer* 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;
|
||||
};
|
||||
531
src/ios/EmulationSession.mm
Normal file
531
src/ios/EmulationSession.mm
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// EmulationSession.m - Sudachi
|
||||
// Created by Jarrod Norwell on 1/20/24.
|
||||
//
|
||||
|
||||
#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/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.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/yuzu_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;
|
||||
}
|
||||
|
||||
CA::MetalLayer* EmulationSession::NativeWindow() const {
|
||||
return m_native_window;
|
||||
}
|
||||
|
||||
void EmulationSession::SetNativeWindow(CA::MetalLayer* 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({
|
||||
nullptr, // Amiibo Settings
|
||||
nullptr, // Controller Selector
|
||||
nullptr, // Error Display
|
||||
nullptr, // Mii Editor
|
||||
nullptr, // Parental Controls
|
||||
nullptr, // Photo Viewer
|
||||
nullptr, // Profile Selector
|
||||
nullptr, // std::move(android_keyboard), // Software Keyboard
|
||||
nullptr, // Web Browser
|
||||
});
|
||||
|
||||
// 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({
|
||||
nullptr, // Amiibo Settings
|
||||
nullptr, // Controller Selector
|
||||
nullptr, // Error Display
|
||||
nullptr, // Mii Editor
|
||||
nullptr, // Parental Controls
|
||||
nullptr, // Photo Viewer
|
||||
nullptr, // Profile Selector
|
||||
nullptr, // std::move(android_keyboard), // Software Keyboard
|
||||
nullptr, // Web Browser
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
static Core::SystemResultStatus RunEmulation(const std::string& filepath,
|
||||
const size_t program_index,
|
||||
const bool frontend_initiated) {
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
SCOPE_EXIT {
|
||||
MicroProfileShutdown();
|
||||
};
|
||||
|
||||
LOG_INFO(Frontend, "starting");
|
||||
|
||||
if (filepath.empty()) {
|
||||
LOG_CRITICAL(Frontend, "failed to load: filepath empty!");
|
||||
return Core::SystemResultStatus::ErrorLoader;
|
||||
}
|
||||
|
||||
SCOPE_EXIT {
|
||||
EmulationSession::GetInstance().ShutdownEmulation();
|
||||
};
|
||||
|
||||
jconst result = EmulationSession::GetInstance().InitializeEmulation(filepath, program_index,
|
||||
frontend_initiated);
|
||||
if (result != Core::SystemResultStatus::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
EmulationSession::GetInstance().RunEmulation();
|
||||
|
||||
return Core::SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
83
src/ios/EmulationWindow.h
Normal file
83
src/ios/EmulationWindow.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// EmulationWindow.h - Sudachi
|
||||
// Created by Jarrod Norwell on 1/18/24.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#import <Metal/Metal.hpp>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <UIKit/UIKit.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, CA::MetalLayer* surface, CGSize size,
|
||||
std::shared_ptr<Common::DynamicLibrary> driver_library);
|
||||
|
||||
~EmulationWindow() = default;
|
||||
|
||||
void OnSurfaceChanged(CA::MetalLayer* 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
|
||||
84
src/ios/EmulationWindow.mm
Normal file
84
src/ios/EmulationWindow.mm
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
//
|
||||
// EmulationWindow.mm - Sudachi
|
||||
// Created by Jarrod Norwell on 1/18/24.
|
||||
//
|
||||
|
||||
#import "EmulationWindow.h"
|
||||
#import "EmulationSession/EmulationSession.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#include "common/logging/log.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(CA::MetalLayer* 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 = reinterpret_cast<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, CA::MetalLayer* surface, CGSize size, std::shared_ptr<Common::DynamicLibrary> driver_library) : m_input_subsystem{input_subsystem}, m_size{size}, m_driver_library{driver_library} {
|
||||
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();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue