mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-04-10 03:18:55 +02:00
Compare commits
28 commits
8db6b82bdf
...
00656d6841
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00656d6841 | ||
|
|
71b34ed9ca | ||
|
|
d65171e843 | ||
|
|
4ea038957b | ||
|
|
11970a1c09 | ||
|
|
2563c30292 | ||
|
|
f77e5e85bc | ||
|
|
e169ad7c1c | ||
|
|
2cd15c0a22 | ||
|
|
89318e50db | ||
|
|
fca8306b82 | ||
|
|
fb788b5aef | ||
|
|
6c583d261c | ||
|
|
d098fd2747 | ||
|
|
c97226699b | ||
|
|
c74f4461f3 | ||
|
|
1ee4e6f957 | ||
|
|
edbc4b251f | ||
|
|
8a96c7c0fd | ||
|
|
9f5b4e9099 | ||
|
|
dfaff2473f | ||
|
|
32c8e5d8d2 | ||
|
|
cbdeb74342 | ||
|
|
e35252f0d3 | ||
|
|
40d27f28ff | ||
|
|
9423a33fc2 | ||
|
|
afec66f598 | ||
|
|
a022560991 |
45 changed files with 3277 additions and 78 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)
|
*.cmake|*.sh|*CMakeLists.txt)
|
||||||
begin="#"
|
begin="#"
|
||||||
;;
|
;;
|
||||||
*.kt*|*.cpp|*.h)
|
*.kt*|*.cpp|*.h|*.swift|*.mm)
|
||||||
begin="//"
|
begin="//"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
|
|
@ -193,7 +193,7 @@ if [ "$UPDATE" = "true" ]; then
|
||||||
begin="#"
|
begin="#"
|
||||||
shell=true
|
shell=true
|
||||||
;;
|
;;
|
||||||
*.kt*|*.cpp|*.h)
|
*.kt*|*.cpp|*.h|*.swift|*.mm)
|
||||||
begin="//"
|
begin="//"
|
||||||
shell="false"
|
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)
|
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_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" OFF)
|
||||||
|
|
@ -283,7 +283,7 @@ if (YUZU_ROOM)
|
||||||
add_compile_definitions(YUZU_ROOM)
|
add_compile_definitions(YUZU_ROOM)
|
||||||
endif()
|
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)
|
if(CXX_APPLE OR CXX_CLANG)
|
||||||
# libc++ has stop_token and jthread as experimental
|
# libc++ has stop_token and jthread as experimental
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
|
||||||
|
|
@ -487,7 +487,12 @@ if (APPLE)
|
||||||
# Umbrella framework for everything GUI-related
|
# Umbrella framework for everything GUI-related
|
||||||
find_library(COCOA_LIBRARY Cocoa REQUIRED)
|
find_library(COCOA_LIBRARY Cocoa REQUIRED)
|
||||||
find_library(IOKIT_LIBRARY IOKit REQUIRED)
|
find_library(IOKIT_LIBRARY IOKit REQUIRED)
|
||||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
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)
|
elseif (WIN32)
|
||||||
# Target Windows 10
|
# Target Windows 10
|
||||||
add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00)
|
add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,8 @@
|
||||||
"version": "1.57",
|
"version": "1.57",
|
||||||
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
"find_args": "CONFIG OPTIONAL_COMPONENTS headers context system fiber filesystem",
|
||||||
"patches": [
|
"patches": [
|
||||||
"0001-clang-cl.patch"
|
"0001-clang-cl.patch",
|
||||||
|
"0002-ios-fix.patch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
- [Arch Linux](#arch-linux)
|
- [Arch Linux](#arch-linux)
|
||||||
- [Gentoo Linux](#gentoo-linux)
|
- [Gentoo Linux](#gentoo-linux)
|
||||||
- [macOS](#macos)
|
- [macOS](#macos)
|
||||||
|
- [iOS](#ios)
|
||||||
- [Solaris](#solaris)
|
- [Solaris](#solaris)
|
||||||
- [HaikuOS](#haikuos)
|
- [HaikuOS](#haikuos)
|
||||||
- [OpenBSD](#openbsd)
|
- [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.
|
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
|
## Solaris
|
||||||
|
|
||||||
Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability.
|
Always consult [the OpenIndiana package list](https://pkg.openindiana.org/hipster/en/index.shtml) to cross-verify availability.
|
||||||
|
|
|
||||||
19
externals/cmake-modules/DetectArchitecture.cmake
vendored
19
externals/cmake-modules/DetectArchitecture.cmake
vendored
|
|
@ -38,13 +38,18 @@ This file is based off of Yuzu and Dynarmic.
|
||||||
if (CMAKE_OSX_ARCHITECTURES)
|
if (CMAKE_OSX_ARCHITECTURES)
|
||||||
set(MULTIARCH_BUILD 1)
|
set(MULTIARCH_BUILD 1)
|
||||||
set(ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}")
|
set(ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}")
|
||||||
|
if (IOS)
|
||||||
# hope and pray the architecture names match
|
# TODO: Right... the toolchain file won't properly accomodate OSX_ARCHITECTURE
|
||||||
foreach(ARCH IN ${CMAKE_OSX_ARCHITECTURES})
|
# they aren't defining it as a list properly I assume?
|
||||||
set(ARCHITECTURE_${ARCH} 1 PARENT_SCOPE)
|
set(ARCHITECTURE_arm64 1 PARENT_SCOPE)
|
||||||
add_definitions(-DARCHITECTURE_${ARCH}=1)
|
add_definitions(-DARCHITECTURE_arm64=1)
|
||||||
endforeach()
|
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()
|
return()
|
||||||
endif()
|
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)
|
set(CXX_APPLE ON)
|
||||||
endif()
|
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
|
# https://gitlab.kitware.com/cmake/cmake/-/merge_requests/11112
|
||||||
# This works totally fine on MinGW64, but not CLANG{,ARM}64
|
# This works totally fine on MinGW64, but not CLANG{,ARM}64
|
||||||
if(MINGW AND CXX_CLANG)
|
if(MINGW AND CXX_CLANG)
|
||||||
|
|
|
||||||
3
externals/cpmfile.json
vendored
3
externals/cpmfile.json
vendored
|
|
@ -110,7 +110,8 @@
|
||||||
],
|
],
|
||||||
"patches": [
|
"patches": [
|
||||||
"0001-netbsd-fix.patch",
|
"0001-netbsd-fix.patch",
|
||||||
"0002-allow-static-only.patch"
|
"0002-allow-static-only.patch",
|
||||||
|
"0003-ios-fix.patch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"spirv-headers": {
|
"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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
# SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
|
|
@ -11,9 +11,9 @@ set(FFmpeg_HWACCEL_FLAGS)
|
||||||
set(FFmpeg_HWACCEL_INCLUDE_DIRS)
|
set(FFmpeg_HWACCEL_INCLUDE_DIRS)
|
||||||
set(FFmpeg_HWACCEL_LDFLAGS)
|
set(FFmpeg_HWACCEL_LDFLAGS)
|
||||||
|
|
||||||
if (UNIX AND NOT ANDROID)
|
if (UNIX AND NOT ANDROID AND NOT IOS)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
if (NOT ANDROID)
|
if (NOT ANDROID AND NOT IOS)
|
||||||
pkg_check_modules(LIBVA libva)
|
pkg_check_modules(LIBVA libva)
|
||||||
pkg_check_modules(CUDA cuda)
|
pkg_check_modules(CUDA cuda)
|
||||||
pkg_check_modules(FFNVCODEC ffnvcodec)
|
pkg_check_modules(FFNVCODEC ffnvcodec)
|
||||||
|
|
@ -182,6 +182,10 @@ else()
|
||||||
find_program(BASH_PROGRAM bash REQUIRED)
|
find_program(BASH_PROGRAM bash REQUIRED)
|
||||||
|
|
||||||
set(FFmpeg_CROSS_COMPILE_FLAGS "")
|
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)
|
if (ANDROID)
|
||||||
string(TOLOWER "${CMAKE_HOST_SYSTEM_NAME}" FFmpeg_HOST_SYSTEM_NAME)
|
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}")
|
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="--ld-path=${TOOLCHAIN}/bin/ld.lld"
|
||||||
--extra-ldflags="-nostdlib"
|
--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()
|
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(
|
add_custom_command(
|
||||||
OUTPUT
|
OUTPUT
|
||||||
${FFmpeg_MAKEFILE}
|
${FFmpeg_MAKEFILE}
|
||||||
|
|
|
||||||
|
|
@ -255,4 +255,8 @@ if (ANDROID)
|
||||||
target_include_directories(yuzu-android PRIVATE android/app/src/main)
|
target_include_directories(yuzu-android PRIVATE android/app/src/main)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (IOS)
|
||||||
|
add_subdirectory(ios)
|
||||||
|
endif()
|
||||||
|
|
||||||
include(GenerateDepHashes)
|
include(GenerateDepHashes)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2025 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.fragments
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
@ -19,7 +19,6 @@ import androidx.navigation.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.transition.MaterialSharedAxis
|
import com.google.android.material.transition.MaterialSharedAxis
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.AddonAdapter
|
import org.yuzu.yuzu_emu.adapters.AddonAdapter
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentAddonsBinding
|
||||||
|
|
@ -42,7 +41,7 @@ class AddonsFragment : Fragment() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
addonViewModel.onOpenAddons(args.game)
|
addonViewModel.onAddonsViewCreated(args.game)
|
||||||
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||||
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||||
|
|
@ -122,12 +121,14 @@ class AddonsFragment : Fragment() {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
addonViewModel.refreshAddons()
|
addonViewModel.onAddonsViewStarted(args.game)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
if (activity?.isChangingConfigurations != true) {
|
||||||
|
addonViewModel.onCloseAddons()
|
||||||
|
}
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
addonViewModel.onCloseAddons()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val installAddon =
|
val installAddon =
|
||||||
|
|
@ -167,7 +168,7 @@ class AddonsFragment : Fragment() {
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
return@newInstance errorMessage
|
return@newInstance errorMessage
|
||||||
}
|
}
|
||||||
addonViewModel.refreshAddons()
|
addonViewModel.refreshAddons(force = true)
|
||||||
return@newInstance getString(R.string.addon_installed_successfully)
|
return@newInstance getString(R.string.addon_installed_successfully)
|
||||||
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
class AddonViewModel : ViewModel() {
|
class AddonViewModel : ViewModel() {
|
||||||
private val _patchList = MutableStateFlow(mutableListOf<Patch>())
|
private val _patchList = MutableStateFlow<List<Patch>>(emptyList())
|
||||||
val addonList get() = _patchList.asStateFlow()
|
val addonList get() = _patchList.asStateFlow()
|
||||||
|
|
||||||
private val _showModInstallPicker = MutableStateFlow(false)
|
private val _showModInstallPicker = MutableStateFlow(false)
|
||||||
|
|
@ -31,34 +31,62 @@ class AddonViewModel : ViewModel() {
|
||||||
val addonToDelete = _addonToDelete.asStateFlow()
|
val addonToDelete = _addonToDelete.asStateFlow()
|
||||||
|
|
||||||
var game: Game? = null
|
var game: Game? = null
|
||||||
|
private var loadedGameKey: String? = null
|
||||||
|
|
||||||
private val isRefreshing = AtomicBoolean(false)
|
private val isRefreshing = AtomicBoolean(false)
|
||||||
|
private val pendingRefresh = AtomicBoolean(false)
|
||||||
|
|
||||||
fun onOpenAddons(game: Game) {
|
fun onAddonsViewCreated(game: Game) {
|
||||||
this.game = game
|
this.game = game
|
||||||
refreshAddons()
|
refreshAddons(commitEmpty = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun refreshAddons() {
|
fun onAddonsViewStarted(game: Game) {
|
||||||
if (isRefreshing.get() || game == null) {
|
this.game = game
|
||||||
|
val hasLoadedCurrentGame = loadedGameKey == gameKey(game)
|
||||||
|
refreshAddons(force = !hasLoadedCurrentGame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshAddons(force: Boolean = false, commitEmpty: Boolean = true) {
|
||||||
|
val currentGame = game ?: return
|
||||||
|
val currentGameKey = gameKey(currentGame)
|
||||||
|
if (!force && loadedGameKey == currentGameKey) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isRefreshing.set(true)
|
if (!isRefreshing.compareAndSet(false, true)) {
|
||||||
|
if (force) {
|
||||||
|
pendingRefresh.set(true)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
withContext(Dispatchers.IO) {
|
try {
|
||||||
val patchList = (
|
val patches = withContext(Dispatchers.IO) {
|
||||||
NativeLibrary.getPatchesForFile(game!!.path, game!!.programId)
|
NativeLibrary.getPatchesForFile(currentGame.path, currentGame.programId)
|
||||||
?: emptyArray()
|
} ?: return@launch
|
||||||
).toMutableList()
|
|
||||||
|
val patchList = patches.toMutableList()
|
||||||
patchList.sortBy { it.name }
|
patchList.sortBy { it.name }
|
||||||
|
|
||||||
// Ensure only one update is enabled
|
// Ensure only one update is enabled
|
||||||
ensureSingleUpdateEnabled(patchList)
|
ensureSingleUpdateEnabled(patchList)
|
||||||
|
|
||||||
removeDuplicates(patchList)
|
removeDuplicates(patchList)
|
||||||
|
if (patchList.isEmpty() && !commitEmpty) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
if (gameKey(game ?: return@launch) != currentGameKey) {
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
_patchList.value = patchList
|
_patchList.value = patchList.toList()
|
||||||
|
loadedGameKey = currentGameKey
|
||||||
|
} finally {
|
||||||
isRefreshing.set(false)
|
isRefreshing.set(false)
|
||||||
|
if (pendingRefresh.compareAndSet(true, false)) {
|
||||||
|
refreshAddons(force = true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -119,17 +147,26 @@ class AddonViewModel : ViewModel() {
|
||||||
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
PatchType.DLC -> NativeLibrary.removeDLC(patch.programId)
|
||||||
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
PatchType.Mod -> NativeLibrary.removeMod(patch.programId, patch.name)
|
||||||
}
|
}
|
||||||
refreshAddons()
|
refreshAddons(force = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCloseAddons() {
|
fun onCloseAddons() {
|
||||||
if (_patchList.value.isEmpty()) {
|
val currentGame = game ?: run {
|
||||||
|
_patchList.value = emptyList()
|
||||||
|
loadedGameKey = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val currentList = _patchList.value
|
||||||
|
if (currentList.isEmpty()) {
|
||||||
|
_patchList.value = emptyList()
|
||||||
|
loadedGameKey = null
|
||||||
|
game = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
NativeConfig.setDisabledAddons(
|
NativeConfig.setDisabledAddons(
|
||||||
game!!.programId,
|
currentGame.programId,
|
||||||
_patchList.value.mapNotNull {
|
currentList.mapNotNull {
|
||||||
if (it.enabled) {
|
if (it.enabled) {
|
||||||
null
|
null
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -148,7 +185,8 @@ class AddonViewModel : ViewModel() {
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
)
|
)
|
||||||
NativeConfig.saveGlobalConfig()
|
NativeConfig.saveGlobalConfig()
|
||||||
_patchList.value.clear()
|
_patchList.value = emptyList()
|
||||||
|
loadedGameKey = null
|
||||||
game = null
|
game = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,4 +197,8 @@ class AddonViewModel : ViewModel() {
|
||||||
fun showModNoticeDialog(show: Boolean) {
|
fun showModNoticeDialog(show: Boolean) {
|
||||||
_showModNoticeDialog.value = show
|
_showModNoticeDialog.value = show
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun gameKey(game: Game): String {
|
||||||
|
return "${game.programId}|${game.path}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -642,7 +642,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addonViewModel.refreshAddons()
|
addonViewModel.refreshAddons(force = true)
|
||||||
|
|
||||||
val separator = System.lineSeparator() ?: "\n"
|
val separator = System.lineSeparator() ?: "\n"
|
||||||
val installResult = StringBuilder()
|
val installResult = StringBuilder()
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,14 @@ extern std::atomic<bool> g_has_battery;
|
||||||
|
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#include <TargetConditionals.h>
|
#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/IOPSKeys.h>
|
||||||
#include <IOKit/ps/IOPowerSources.h>
|
#include <IOKit/ps/IOPowerSources.h>
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__)
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -48,7 +51,9 @@ namespace Common {
|
||||||
info.percentage = g_battery_percentage.load(std::memory_order_relaxed);
|
info.percentage = g_battery_percentage.load(std::memory_order_relaxed);
|
||||||
info.charging = g_is_charging.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);
|
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
|
#elif defined(__APPLE__) && TARGET_OS_MAC
|
||||||
CFTypeRef info_ref = IOPSCopyPowerSourcesInfo();
|
CFTypeRef info_ref = IOPSCopyPowerSourcesInfo();
|
||||||
CFArrayRef sources = IOPSCopyPowerSourcesList(info_ref);
|
CFArrayRef sources = IOPSCopyPowerSourcesList(info_ref);
|
||||||
|
|
@ -96,7 +101,6 @@ namespace Common {
|
||||||
#else
|
#else
|
||||||
info.has_battery = false;
|
info.has_battery = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,11 @@
|
||||||
#include <sys/random.h>
|
#include <sys/random.h>
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#include <sys/types.h>
|
#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>
|
#include <sys/random.h>
|
||||||
|
#endif
|
||||||
#include <mach/vm_map.h>
|
#include <mach/vm_map.h>
|
||||||
#include <mach/mach.h>
|
#include <mach/mach.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -706,6 +706,7 @@ struct Values {
|
||||||
Setting<bool> pause_tas_on_load{linkage, true, "pause_tas_on_load", Category::Controls};
|
Setting<bool> pause_tas_on_load{linkage, true, "pause_tas_on_load", Category::Controls};
|
||||||
Setting<bool> tas_enable{linkage, false, "tas_enable", Category::Controls};
|
Setting<bool> tas_enable{linkage, false, "tas_enable", Category::Controls};
|
||||||
Setting<bool> tas_loop{linkage, false, "tas_loop", Category::Controls};
|
Setting<bool> tas_loop{linkage, false, "tas_loop", Category::Controls};
|
||||||
|
Setting<bool> tas_show_recording_dialog{linkage, true, "tas_show_recording_dialog", Category::Controls};
|
||||||
|
|
||||||
Setting<bool> mouse_panning{
|
Setting<bool> mouse_panning{
|
||||||
linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false};
|
linkage, false, "mouse_panning", Category::Controls, Specialization::Default, false};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <dynarmic/interface/halt_reason.h>
|
#include "dynarmic/interface/halt_reason.h"
|
||||||
|
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <dynarmic/interface/A32/a32.h>
|
#include "dynarmic/interface/A32/a32.h"
|
||||||
#include <dynarmic/interface/code_page.h>
|
#include "dynarmic/interface/code_page.h"
|
||||||
|
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
|
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <ankerl/unordered_dense.h>
|
#include <ankerl/unordered_dense.h>
|
||||||
|
|
||||||
#include <dynarmic/interface/A64/a64.h>
|
#include "dynarmic/interface/A64/a64.h"
|
||||||
#include <dynarmic/interface/code_page.h>
|
#include "dynarmic/interface/code_page.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/hash.h"
|
#include "common/hash.h"
|
||||||
#include "core/arm/arm_interface.h"
|
#include "core/arm/arm_interface.h"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include <dynarmic/interface/A32/coprocessor.h>
|
#include "dynarmic/interface/A32/coprocessor.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <dynarmic/interface/exclusive_monitor.h>
|
#include "dynarmic/interface/exclusive_monitor.h"
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/arm/exclusive_monitor.h"
|
#include "core/arm/exclusive_monitor.h"
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wshadow"
|
#pragma GCC diagnostic ignored "-Wshadow"
|
||||||
|
|
||||||
#include <dynarmic/frontend/A64/a64_types.h>
|
#include "dynarmic/frontend/A64/a64_types.h"
|
||||||
#include <dynarmic/frontend/A64/decoder/a64.h>
|
#include "dynarmic/frontend/A64/decoder/a64.h"
|
||||||
#include <dynarmic/frontend/imm.h>
|
#include "dynarmic/frontend/imm.h"
|
||||||
|
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
|
@ -18,17 +21,14 @@ namespace Service::Audio {
|
||||||
void LoopProcess(Core::System& system) {
|
void LoopProcess(Core::System& system) {
|
||||||
auto server_manager = std::make_unique<ServerManager>(system);
|
auto server_manager = std::make_unique<ServerManager>(system);
|
||||||
|
|
||||||
server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
|
|
||||||
server_manager->RegisterNamedService("audin:u", std::make_shared<IAudioInManager>(system));
|
server_manager->RegisterNamedService("audin:u", std::make_shared<IAudioInManager>(system));
|
||||||
server_manager->RegisterNamedService("audout:u", std::make_shared<IAudioOutManager>(system));
|
server_manager->RegisterNamedService("audout:u", std::make_shared<IAudioOutManager>(system));
|
||||||
server_manager->RegisterNamedService(
|
// Depends on audout:u and audin:u on ctor!
|
||||||
"audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system));
|
server_manager->RegisterNamedService("audctl", std::make_shared<IAudioController>(system));
|
||||||
server_manager->RegisterNamedService("audrec:u",
|
server_manager->RegisterNamedService("audrec:a", std::make_shared<IFinalOutputRecorderManagerForApplet>(system));
|
||||||
std::make_shared<IFinalOutputRecorderManager>(system));
|
server_manager->RegisterNamedService("audrec:u", std::make_shared<IFinalOutputRecorderManager>(system));
|
||||||
server_manager->RegisterNamedService("audren:u",
|
server_manager->RegisterNamedService("audren:u", std::make_shared<IAudioRendererManager>(system));
|
||||||
std::make_shared<IAudioRendererManager>(system));
|
server_manager->RegisterNamedService("hwopus", std::make_shared<IHardwareOpusDecoderManager>(system));
|
||||||
server_manager->RegisterNamedService("hwopus",
|
|
||||||
std::make_shared<IHardwareOpusDecoderManager>(system));
|
|
||||||
ServerManager::RunServer(std::move(server_manager));
|
ServerManager::RunServer(std::move(server_manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <boost/icl/interval_set.hpp>
|
#include <boost/icl/interval_set.hpp>
|
||||||
#include <dynarmic/interface/A64/a64.h>
|
#include "dynarmic/interface/A64/a64.h"
|
||||||
#include <dynarmic/interface/A64/config.h>
|
#include "dynarmic/interface/A64/config.h"
|
||||||
#include <dynarmic/interface/code_page.h>
|
#include "dynarmic/interface/code_page.h"
|
||||||
|
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ add_library(dynarmic STATIC
|
||||||
frontend/decoder/matcher.h
|
frontend/decoder/matcher.h
|
||||||
frontend/imm.cpp
|
frontend/imm.cpp
|
||||||
frontend/imm.h
|
frontend/imm.h
|
||||||
|
interface/halt_reason.h
|
||||||
interface/exclusive_monitor.h
|
interface/exclusive_monitor.h
|
||||||
interface/optimization_flags.h
|
interface/optimization_flags.h
|
||||||
ir/acc_type.h
|
ir/acc_type.h
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <boost/container/static_vector.hpp>
|
#include <boost/container/static_vector.hpp>
|
||||||
|
|
||||||
#include <dynarmic/common/spin_lock.h>
|
#include "dynarmic/common/spin_lock.h"
|
||||||
|
|
||||||
namespace Dynarmic {
|
namespace Dynarmic {
|
||||||
|
|
||||||
|
|
|
||||||
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();
|
||||||
|
}
|
||||||
|
|
@ -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-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
|
@ -35,6 +35,7 @@ void ConfigureTasDialog::LoadConfiguration() {
|
||||||
ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue());
|
ui->tas_enable->setChecked(Settings::values.tas_enable.GetValue());
|
||||||
ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue());
|
ui->tas_loop_script->setChecked(Settings::values.tas_loop.GetValue());
|
||||||
ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue());
|
ui->tas_pause_on_load->setChecked(Settings::values.pause_tas_on_load.GetValue());
|
||||||
|
ui->tas_show_recording_dialog->setChecked(Settings::values.tas_show_recording_dialog.GetValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureTasDialog::ApplyConfiguration() {
|
void ConfigureTasDialog::ApplyConfiguration() {
|
||||||
|
|
@ -42,6 +43,7 @@ void ConfigureTasDialog::ApplyConfiguration() {
|
||||||
Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked());
|
Settings::values.tas_enable.SetValue(ui->tas_enable->isChecked());
|
||||||
Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked());
|
Settings::values.tas_loop.SetValue(ui->tas_loop_script->isChecked());
|
||||||
Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked());
|
Settings::values.pause_tas_on_load.SetValue(ui->tas_pause_on_load->isChecked());
|
||||||
|
Settings::values.tas_show_recording_dialog.SetValue(ui->tas_show_recording_dialog->isChecked());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
|
void ConfigureTasDialog::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,13 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item row="3" column="0" colspan="4">
|
||||||
|
<widget class="QCheckBox" name="tas_show_recording_dialog">
|
||||||
|
<property name="text">
|
||||||
|
<string>Show recording dialog</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
||||||
|
|
@ -3665,13 +3665,17 @@ void MainWindow::OnTasRecord() {
|
||||||
|
|
||||||
const bool is_recording = input_subsystem->GetTas()->Record();
|
const bool is_recording = input_subsystem->GetTas()->Record();
|
||||||
if (!is_recording) {
|
if (!is_recording) {
|
||||||
is_tas_recording_dialog_active = true;
|
if (Settings::values.tas_show_recording_dialog.GetValue()) {
|
||||||
|
is_tas_recording_dialog_active = true;
|
||||||
|
|
||||||
bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
|
bool answer = question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
|
||||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||||
|
|
||||||
input_subsystem->GetTas()->SaveRecording(answer);
|
input_subsystem->GetTas()->SaveRecording(answer);
|
||||||
is_tas_recording_dialog_active = false;
|
is_tas_recording_dialog_active = false;
|
||||||
|
} else {
|
||||||
|
input_subsystem->GetTas()->SaveRecording(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
OnTasStateChanged();
|
OnTasStateChanged();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue