1
0
forked from MeloNX/MeloNX

Compare commits

...

119 Commits

Author SHA1 Message Date
24536b6397 更新 README.md 2025-03-18 09:26:16 +00:00
ff4b59730a 更新 src/MeloNX/MeloNX/App/Views/Main/ControllerView/ControllerView.swift 2025-03-18 09:21:48 +00:00
fd4bc5ccbb Merge pull request '上传文件至 src/MeloNX/MeloNX/App/Views/Main/ControllerView/Joystick' (#1) from phoenixr-patch-1 into XC-ios-ht
Reviewed-on: #1
2025-03-18 09:19:19 +00:00
ce66d4091d 上传文件至 src/MeloNX/MeloNX/App/Views/Main/ControllerView/Joystick
FTG joystick
2025-03-18 09:14:41 +00:00
ceab2f0ac8 Fix spelling mistake in README 2025-03-16 10:05:09 +00:00
7986859398 Update README.md 2025-03-16 10:02:34 +00:00
c4506da8a1 Update README.md 2025-03-15 21:10:43 +00:00
9f72c9da10 Update README.md 2025-03-15 21:03:36 +00:00
ba0c49f545 Update README With Experimental Free Developer Account guide 2025-03-15 21:02:49 +00:00
80148ac69a :sus64: 2025-03-13 06:42:26 +00:00
Stossy11
7417ddfeef Update version number 2025-03-13 17:18:42 +11:00
Stossy11
f66590203a Fix Setup 2025-03-13 09:40:18 +11:00
stossy11
f091e6c5ea Update README.md 2025-03-12 22:36:03 +00:00
stossy11
75a66586b2 Update README.md 2025-03-12 22:32:21 +00:00
Stossy11
3207e1e739 Merge pull request 'Fix for updates and DLCs' (#21) from XITRIX/MeloNX:updates-fix into XC-ios-ht 2025-03-13 09:30:28 +11:00
Stossy11
a6b4f2d91f Rewrite the menu Code, Add Metal HUD to advanced options and more 2025-03-13 09:05:21 +11:00
Stossy11
93af19e200 Fix Super Mario 3D World 2025-03-09 13:22:18 +11:00
Stossy11
172f364f62 Fix Contorller. 2025-03-09 10:14:53 +11:00
Stossy11
05666c918d Fix dependency 2025-03-09 09:10:37 +11:00
b45af91523 Merge pull request 'Feat: Implement Latest Ryujinx core into MeloNX' (#18) from melo-latest-ryu into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#18
2025-03-08 21:40:32 +00:00
Stossy11
0bfca4c488 Fix controller and logging 2025-03-09 08:00:08 +11:00
Stossy11
14ce5102fb Fix emulation 2025-03-08 21:06:46 +11:00
Stossy11
12ab8bc3e2 Merge Latest Ryujinx (Unstable) 2025-03-08 10:13:40 +11:00
aaefc0a9e5 Update Compile.md 2025-03-07 20:58:23 +00:00
2dd31138fe Update README.md 2025-03-07 12:09:51 +00:00
0c835b064d Update Compile.md 2025-03-07 02:29:11 +00:00
a1ca0b99a4 Update Compile.md 2025-03-07 01:41:43 +00:00
Fen
7b67ffb39a revert 4a901d3cba22e0606e5cb0208a1cad95c4198db7
revert Popular demand....
2025-03-07 01:25:52 +00:00
Fen
4a901d3cba Popular demand.... 2025-03-07 01:19:21 +00:00
Stossy11
6a8722e23b Make MeloNX compiling all in the xcode project 2025-03-06 19:05:11 +11:00
Stossy11
85ad96109e FIx Memory being +1 GB 2025-03-06 12:50:08 +11:00
Stossy11
ebe1d183ef Fix Mii maker 2025-03-06 12:09:38 +11:00
Stossy11
6345e6421d Fix Version Number 2025-03-06 11:58:46 +11:00
Stossy11
8ca88def54 Rewrite Display Code and more 2025-03-06 11:57:43 +11:00
e372f6eb35 Merge pull request 'Update ReadMe to have accurate information' (#14) from TechGuy/MeloNX:XC-ios-ht into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#14
2025-03-04 07:15:10 +00:00
76b82243a7 Update README.md 2025-03-04 07:09:00 +00:00
Stossy11
e924da52ec add back SDL Window option 2025-03-01 21:31:33 +11:00
Stossy11
ea2eff15c6 New gitignore 2025-03-01 21:06:09 +11:00
Stossy11
c6ff0b60bf Adds pull #12 and changes version number 2025-03-01 21:03:07 +11:00
Stossy11
edc56316cc add iOS 15 support back, fix the need for SDL Window and more 2025-03-01 20:54:57 +11:00
Stossy11
8df465a959 implement an all-swift approach for detecting JIT (adds support for detecting JIT on iOS 18.4+), adds an easter egg, and more 2025-03-01 20:54:57 +11:00
527ac3fb23 Update README.md 2025-02-20 20:54:03 +00:00
8e60f6dc50 Update README.md 2025-02-19 07:42:45 +00:00
Stossy11
3b99631dfb Implement JIT Cache Regions and Add Handheld Input 2025-02-19 11:16:50 +11:00
cb33b04f2b Merge pull request 'GCController wrapper' (#6) from XITRIX/MeloNX:Controller-fix into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#6
2025-02-17 01:20:48 +00:00
500f3d5b9e Fix for virtual controller detach | pass gamepad haptic engine 2025-02-17 00:39:04 +01:00
ac4e5d394e Hide non wrapped controllers 2025-02-17 00:39:03 +01:00
f2d078f80b GCController wrapper 2025-02-17 00:39:03 +01:00
004a81fa60 Merge pull request 'DLC manager implemented' (#11) from XITRIX/MeloNX:DLC-support into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#11
2025-02-16 23:33:30 +00:00
ddf634ecb6 DLC manager implemented 2025-02-17 00:28:05 +01:00
Stossy11
cce876c6f5 Change version number 2025-02-17 07:40:56 +11:00
ebfb39c132 Merge pull request 'Correct device req + Add Xcode to compiling req' (#10) from C4ndyF1sh/MeloNX:device-req-and-compiling-req into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#10
2025-02-16 20:15:56 +00:00
b3bb9cefcf Merge pull request 'Update manager fix' (#8) from XITRIX/MeloNX:Update-manager-fix into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#8
2025-02-16 20:14:58 +00:00
8c54134699 Update Compile.md 2025-02-16 17:57:59 +00:00
e8537df246 Update README.md 2025-02-16 17:55:44 +00:00
8c6dd455f2 Game update path fix 2025-02-16 15:46:46 +01:00
2a7cfa5650 GameInfo UI enhancement 2025-02-16 13:52:12 +01:00
df2b17ddd6 UI enhancements 2025-02-16 13:41:57 +01:00
757fb1f6d1 Update manager fix 2025-02-16 13:41:57 +01:00
Stossy11
e741039304 Add Entitlement Checker and Memory 2025-02-16 17:29:09 +11:00
Stossy11
fd0ce75f67 Fix whatever happened 2025-02-16 16:22:20 +11:00
Stossy11
0e80bd3d51 Add game update manager 2025-02-16 14:01:31 +11:00
Stossy11
f95281899c a bunch of changes 2025-02-16 12:17:12 +11:00
802a8d7bae Merge pull request 'Added title update functionality' (#7) from XITRIX/MeloNX:Title-update into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#7
2025-02-16 00:04:42 +00:00
7277e1fa9b Add title update functionality 2025-02-15 20:34:36 +01:00
27312d4f31 Null pointer fix 2025-02-15 20:34:36 +01:00
4ffb0ff617 Update Compile.md 2025-02-15 01:37:00 +00:00
a358dcdfc4 Update Compile.md 2025-02-15 01:35:48 +00:00
08ee9b18ea Update LICENSE.txt 2025-02-13 12:15:27 +00:00
aadc258187 Update LICENSE.txt 2025-02-13 12:13:53 +00:00
1c75d22190 Update README.md to reflect the new LICENSE 2025-02-13 11:38:35 +00:00
57c297369a Add New License 2025-02-13 11:37:34 +00:00
56544db198 Move Around Steps a little 2025-02-13 04:37:28 +00:00
6ec2ad2841 Add Update Guide 2025-02-13 04:36:35 +00:00
9d6c7d9900 Merge pull request 'Fix broken link to compatibility' (#5) from Vishram1123/MeloNX:XC-ios-ht into XC-ios-ht
Reviewed-on: MeloNX/MeloNX#5
2025-02-13 03:18:12 +00:00
9ddc6a969c Fix broken link to compatibility 2025-02-13 02:20:20 +00:00
Stossy11
1b69c0bdc6 Set Version to 1.1.0 2025-02-13 12:56:20 +11:00
Stossy11
18d98755f6 Add Disable vSync 2025-02-13 12:47:53 +11:00
Stossy11
c6de4abce3 Fix vertical controllers 2025-02-13 10:00:01 +11:00
Stossy11
e5c5e8572e Fix Keyboard and add Disable PTC mode 2025-02-13 10:00:01 +11:00
c0e8570293 Update README.md 2025-02-12 21:17:11 +00:00
c8a3124cca Update Compile.md 2025-02-12 13:04:56 +00:00
2c389c899a Update Compile.md 2025-02-12 12:34:44 +00:00
11571aca6e add xcode-select --switch to Compile.md 2025-02-12 10:23:48 +00:00
Stossy11
e04e689bc4 Fix spelling mistake 2025-02-12 21:06:31 +11:00
5c903626cc Update Site Workflow 2025-02-12 09:20:17 +00:00
9ca187a8c4 Fix Icon 2025-02-12 07:55:02 +00:00
cac3853d96 Update README.md 2025-02-12 07:49:51 +00:00
fff70a2dba Update README.md 2025-02-12 07:49:01 +00:00
4da30e332c Update Compile.md 2025-02-12 07:41:05 +00:00
Stossy11
114ba3eb57 Remove func MainThread and add SDL Window code back 2025-02-12 18:19:51 +11:00
Stossy11
839ddab589 small UI chanes 2025-02-12 18:19:51 +11:00
Bella
00a06c4dc8
Replace GitHub Actions workflow with Gitea workflow for notifying API on release 2025-02-12 19:55:06 +13:00
efbeebafcb (hopefully this work) Add: Action for adding release to site 2025-02-12 06:00:59 +00:00
Stossy11
b85758ba88 FIx starting the emulation on iOS 16 and below. 2025-02-12 08:39:37 +11:00
Stossy11
46196daf39 Fix Keyboard issues 2025-02-11 20:43:21 +11:00
Stossy11
eb4a4593ea Add Software Keyboard, Edit maxSets and more 2025-02-11 20:22:50 +11:00
Stossy11
c3ade6f5cd Add Built in JitStreamer-EB implementation, rework the shortcut and more 2025-02-10 23:27:06 +11:00
Stossy11
007cb026a4 Add Intent to Launch Game and change how DRM works 2025-02-10 17:46:24 +11:00
Stossy11
4f3e49a90c DRM Update 2025-02-10 10:54:59 +11:00
Stossy11
2d5f1d8015 Add Airplay 2025-02-09 16:31:26 +11:00
Stossy11
f57d24706b Add new SwiftUI Emulation View 2025-02-09 15:49:10 +11:00
Bella
a2c3f6d624
Add games, delete games and game info view 2025-02-09 00:15:37 +13:00
Stossy11
9c014e6f87 Fix missing chunkSize 2025-02-08 20:44:12 +11:00
Stossy11
c8db129402 Add comments to explain stuff and other stuff 2025-02-07 10:33:43 +11:00
Stossy11
0b6518d7e3 re-add MVK Config Prefill but make it default to true 2025-02-02 00:40:05 +11:00
Stossy11
cb114fbb68 Edit MoltenVK and remove "MVK: Pre-Fill Metal Command Buffers" due to not being needed anymore 2025-02-02 00:36:33 +11:00
Stossy11
f2ea6448dc make toggle not able to be dragged 2025-02-01 18:19:07 +11:00
Stossy11
9fa29efaf4 Add Aspect Ratio, Clean up some code 2025-02-01 17:02:13 +11:00
Stossy11
a166494e33 Fix Controllers, Add Auto Controller, Fix touch screen, Rewrite SDL Controller Detection. 2025-02-01 16:37:58 +11:00
1d16bf0c94 Add MVK options into settings instead of preset settings 2025-01-20 21:23:54 -08:00
Stossy11
7008ce4f23 Disable MVK_CONFIG_USE_METAL_PRIVATE_API, as it isn't needed and causes more overhead 2025-01-21 01:13:35 +11:00
Stossy11
52fd0bf79b Add detection for Hypervisor 2025-01-21 00:31:41 +11:00
Stossy11
0cc5476d87 Fix settings 2025-01-21 00:21:34 +11:00
7dde0d254a Add multiple missing settings 2025-01-20 05:11:01 -08:00
Stossy11
11305c2aac Sign Hypervisor 2025-01-20 23:50:57 +11:00
Stossy11
9ce29d6ad1 Hypervisor 2025-01-20 23:22:42 +11:00
Stossy11
5ee90c81e9 Fix some stuff :3 2025-01-20 22:10:12 +11:00
Stossy11
26fe33703d Fix LibMoltenVK 2025-01-20 19:42:33 +11:00
1345 changed files with 56302 additions and 25676 deletions

View File

@ -17,8 +17,8 @@ tab_width = 4
end_of_line = lf
insert_final_newline = true
# JSON files
[*.json]
# Markdown, JSON, YAML, props and csproj files
[*.{md,json,yml,props,csproj}]
# Indentation and spacing
indent_size = 2
@ -259,12 +259,12 @@ dotnet_diagnostic.CA1861.severity = none
# Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'"
dotnet_diagnostic.CA1862.severity = none
[src/Ryujinx.HLE/HOS/Services/**.cs]
# Disable "mark members as static" rule for services
[src/Ryujinx/UI/ViewModels/**.cs]
# Disable "mark members as static" rule for ViewModels
dotnet_diagnostic.CA1822.severity = none
[src/Ryujinx.Ava/UI/ViewModels/**.cs]
# Disable "mark members as static" rule for ViewModels
[src/Ryujinx.HLE/HOS/Services/**.cs]
# Disable "mark members as static" rule for services
dotnet_diagnostic.CA1822.severity = none
[src/Ryujinx.Tests/Cpu/*.cs]

View File

@ -0,0 +1,21 @@
name: Notify API on Release
on:
release:
types: [published]
jobs:
notify-api:
runs-on: debian-trixie
steps:
- name: Send API Call for New Release
run: |
curl -X POST http://melonx.org/api/new_release \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${{ secrets.MELONX_GITEA_API_KEY }}" \
-d '{
"version_number": "${{ github.event.release.tag_name }}",
"download_link": "${{ github.event.release.html_url }}",
"changelog": "${{ github.event.release.body }}",
"is_latest": true
}'

View File

@ -23,7 +23,7 @@ body:
attributes:
label: Log file
description: A log file will help our developers to better diagnose and fix the issue.
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. You can drag and drop the log on to the text area
placeholder: Logs files can be found under "Logs" folder in Ryujinx program folder. They can also be accessed by opening Ryujinx, then going to File > Open Logs Folder. You can drag and drop the log on to the text area (do not copy paste).
validations:
required: true
- type: input
@ -83,4 +83,4 @@ body:
- Additional info about your environment:
- Any other information relevant to your issue.
validations:
required: false
required: false

View File

@ -1,6 +1,7 @@
name: Feature Request
description: Suggest a new feature for Ryujinx.
title: "[Feature Request]"
labels: enhancement
body:
- type: textarea
id: overview

View File

@ -7,18 +7,34 @@ updates:
labels:
- "infra"
reviewers:
- marysaka
- TSRBerry
commit-message:
prefix: "ci"
- package-ecosystem: nuget
directory: /
open-pull-requests-limit: 5
open-pull-requests-limit: 10
schedule:
interval: daily
labels:
- "infra"
reviewers:
- marysaka
- TSRBerry
commit-message:
prefix: nuget
groups:
Avalonia:
patterns:
- "*Avalonia*"
Silk.NET:
patterns:
- "Silk.NET*"
OpenTK:
patterns:
- "OpenTK*"
SixLabors:
patterns:
- "SixLabors*"
NUnit:
patterns:
- "NUnit*"

2
.github/labeler.yml vendored
View File

@ -20,7 +20,7 @@ gpu:
gui:
- changed-files:
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.Ui.Common/**', 'src/Ryujinx.Ui.LocaleGenerator/**', 'src/Ryujinx.Ava/**']
- any-glob-to-any-file: ['src/Ryujinx/**', 'src/Ryujinx.UI.Common/**', 'src/Ryujinx.UI.LocaleGenerator/**', 'src/Ryujinx.Gtk3/**']
horizon:
- changed-files:

View File

@ -1,31 +1,24 @@
audio:
- marysaka
cpu:
- gdkchan
- riperiperi
- marysaka
- LDj3SNuD
gpu:
- gdkchan
- riperiperi
- marysaka
gui:
- Ack77
- emmauss
- TSRBerry
- marysaka
horizon:
- gdkchan
- Ack77
- marysaka
- TSRBerry
infra:
- marysaka
- TSRBerry
default:

View File

@ -10,28 +10,17 @@ env:
jobs:
build:
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
runs-on: ${{ matrix.os }}
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
runs-on: ${{ matrix.platform.os }}
timeout-minutes: 45
strategy:
matrix:
os: [ubuntu-latest, macOS-latest, windows-latest]
configuration: [Debug, Release]
include:
- os: ubuntu-latest
OS_NAME: Linux x64
DOTNET_RUNTIME_IDENTIFIER: linux-x64
RELEASE_ZIP_OS_NAME: linux_x64
- os: macOS-latest
OS_NAME: macOS x64
DOTNET_RUNTIME_IDENTIFIER: osx-x64
RELEASE_ZIP_OS_NAME: osx_x64
- os: windows-latest
OS_NAME: Windows x64
DOTNET_RUNTIME_IDENTIFIER: win-x64
RELEASE_ZIP_OS_NAME: win_x64
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
- { name: osx-x64, os: macos-13, zip_os_name: osx_x64 }
fail-fast: false
steps:
@ -40,7 +29,7 @@ jobs:
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
@ -49,6 +38,16 @@ jobs:
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
shell: bash
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Change config filename for macOS
run: sed -r -i '' 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request' && matrix.platform.os == 'macos-13'
- name: Build
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
@ -58,46 +57,47 @@ jobs:
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
timeout-minutes: 10
retry-codes: 139
if: matrix.platform.name != 'linux-arm64'
- name: Publish Ryujinx
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Publish Ryujinx.Headless.SDL2
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Publish Ryujinx.Ava
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
- name: Publish Ryujinx.Gtk3
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_gtk -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Gtk3 --self-contained true
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Set executable bit
run: |
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4
with:
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_sdl2_headless
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Upload Ryujinx.Ava artifact
- name: Upload Ryujinx.Gtk3 artifact
uses: actions/upload-artifact@v4
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
path: publish_ava
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
path: publish_gtk
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
build_macos:
name: macOS Universal (${{ matrix.configuration }})
@ -135,19 +135,24 @@ jobs:
id: git_short_hash
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
- name: Publish macOS Ryujinx.Ava
- name: Change config filename
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
if: github.event_name == 'pull_request'
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
./distribution/macos/create_macos_build_ava.sh . publish_tmp publish ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER"
- name: Upload Ryujinx.Ava artifact
- name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4
with:
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish_ava/*.tar.gz"
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
path: "publish/*.tar.gz"
if: github.event_name == 'pull_request'
- name: Upload Ryujinx.Headless.SDL2 artifact

View File

@ -8,7 +8,7 @@ on:
- '!.github/**'
- '!*.yml'
- '!*.config'
- '!README.md'
- '!*.md'
- '.github/workflows/*.yml'
permissions:

View File

@ -51,38 +51,76 @@ jobs:
- name: Restore Nuget packages
# With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing.
# So we just publish to grab the dependencies
run: dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
run: |
dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
dotnet publish -c Release -r linux-arm64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
- name: Generate nuget_sources.json
shell: python
run: |
import hashlib
from pathlib import Path
import base64
import binascii
import json
import os
import urllib.request
sources = []
for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'):
name = path.parent.parent.name
version = path.parent.name
filename = '{}.{}.nupkg'.format(name, version)
url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename)
with path.open() as fp:
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
def create_source_from_external(name, version):
full_dir_path = Path(os.environ["NUGET_PACKAGES"]).joinpath(name).joinpath(version)
os.makedirs(full_dir_path, exist_ok=True)
sources.append({
'type': 'file',
'url': url,
'sha512': sha512,
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
'dest-filename': filename,
})
filename = "{}.{}.nupkg".format(name, version)
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
name, version, filename
)
with open('flathub/nuget_sources.json', 'w') as fp:
json.dump(sources, fp, indent=4)
print(f"Processing {url}...")
response = urllib.request.urlopen(url)
sha512 = hashlib.sha512(response.read()).hexdigest()
return {
"type": "file",
"url": url,
"sha512": sha512,
"dest": os.environ["NUGET_SOURCES_DESTDIR"],
"dest-filename": filename,
}
has_added_x64_apphost = False
for path in Path(os.environ["NUGET_PACKAGES"]).glob("**/*.nupkg.sha512"):
name = path.parent.parent.name
version = path.parent.name
filename = "{}.{}.nupkg".format(name, version)
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
name, version, filename
)
with path.open() as fp:
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode("ascii")
sources.append(
{
"type": "file",
"url": url,
"sha512": sha512,
"dest": os.environ["NUGET_SOURCES_DESTDIR"],
"dest-filename": filename,
}
)
# .NET will not add current installed application host to the list, force inject it here.
if not has_added_x64_apphost and name.startswith('microsoft.netcore.app.host'):
sources.append(create_source_from_external("microsoft.netcore.app.host.linux-x64", version))
has_added_x64_apphost = True
with open("flathub/nuget_sources.json", "w") as fp:
json.dump(sources, fp, indent=4)
- name: Update flatpak metadata
id: metadata

41
.github/workflows/mako.yml vendored Normal file
View File

@ -0,0 +1,41 @@
name: Mako
on:
discussion:
types: [created, edited, answered, unanswered, category_changed]
discussion_comment:
types: [created, edited]
gollum:
issue_comment:
types: [created, edited]
issues:
types: [opened, edited, reopened, pinned, milestoned, demilestoned, assigned, unassigned, labeled, unlabeled]
pull_request_target:
types: [opened, edited, reopened, synchronize, ready_for_review, assigned, unassigned]
jobs:
tasks:
name: Run Ryujinx tasks
permissions:
actions: read
contents: read
discussions: write
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
if: github.event_name == 'pull_request_target'
with:
# Ensure we pin the source origin as pull_request_target run under forks.
fetch-depth: 0
repository: Ryujinx/Ryujinx
ref: master
- name: Run Mako command
uses: Ryujinx/Ryujinx-Mako@master
with:
command: exec-ryujinx-tasks
args: --event-name "${{ github.event_name }}" --event-path "${{ github.event_path }}" -w "${{ github.workspace }}" "${{ github.repository }}" "${{ github.run_id }}"
app_id: ${{ secrets.MAKO_APP_ID }}
private_key: ${{ secrets.MAKO_PRIVATE_KEY }}
installation_id: ${{ secrets.MAKO_INSTALLATION_ID }}

View File

@ -39,24 +39,24 @@ jobs:
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
let hidden_avalonia_artifacts = `\n\n <details><summary>Experimental GUI (Avalonia)</summary>\n`;
let hidden_gtk_artifacts = `\n\n <details><summary>Old GUI (GTK3)</summary>\n`;
let hidden_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
for (const art of artifacts) {
if(art.name.includes('Debug')) {
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('ava-ryujinx')) {
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('gtk-ryujinx')) {
hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else if(art.name.includes('sdl2-ryujinx-headless')) {
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
} else {
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
}
hidden_avalonia_artifacts += `\n</details>`;
hidden_gtk_artifacts += `\n</details>`;
hidden_headless_artifacts += `\n</details>`;
hidden_debug_artifacts += `\n</details>`;
body += hidden_avalonia_artifacts;
body += hidden_gtk_artifacts;
body += hidden_headless_artifacts;
body += hidden_debug_artifacts;

View File

@ -21,27 +21,8 @@ jobs:
repository: Ryujinx/Ryujinx
ref: master
- name: Checkout Ryujinx-Mako
uses: actions/checkout@v4
with:
repository: Ryujinx/Ryujinx-Mako
ref: master
path: '.ryujinx-mako'
- name: Setup Ryujinx-Mako
uses: ./.ryujinx-mako/.github/actions/setup-mako
- name: Update labels based on changes
uses: actions/labeler@v5
with:
sync-labels: true
dot: true
- name: Assign reviewers
run: |
poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml
shell: bash
env:
MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }}
MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }}
MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }}

View File

@ -10,7 +10,7 @@ on:
- '*.yml'
- '*.json'
- '*.config'
- 'README.md'
- '*.md'
concurrency: release
@ -44,30 +44,34 @@ jobs:
sha: context.sha
})
- name: Create release
uses: ncipollo/release-action@v1
with:
name: ${{ steps.version_info.outputs.build_version }}
tag: ${{ steps.version_info.outputs.build_version }}
body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)."
omitBodyDuringUpdate: true
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
token: ${{ secrets.RELEASE_TOKEN }}
release:
name: Release ${{ matrix.OS_NAME }}
runs-on: ${{ matrix.os }}
name: Release for ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
include:
- os: ubuntu-latest
OS_NAME: Linux x64
DOTNET_RUNTIME_IDENTIFIER: linux-x64
RELEASE_ZIP_OS_NAME: linux_x64
- os: windows-latest
OS_NAME: Windows x64
DOTNET_RUNTIME_IDENTIFIER: win-x64
RELEASE_ZIP_OS_NAME: win_x64
platform:
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Overwrite csc problem matcher
run: echo "::add-matcher::.github/csc.json"
@ -85,6 +89,7 @@ jobs:
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Create output dir
@ -92,42 +97,36 @@ jobs:
- name: Publish
run: |
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
- name: Packing Windows builds
if: matrix.os == 'windows-latest'
if: matrix.platform.os == 'windows-latest'
run: |
pushd publish_gtk
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
pushd publish_ava
cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
pushd publish_sdl2_headless
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
popd
pushd publish_ava
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
popd
shell: bash
- name: Packing Linux builds
if: matrix.os == 'ubuntu-latest'
if: matrix.platform.os == 'ubuntu-latest'
run: |
pushd publish_gtk
chmod +x publish/Ryujinx.sh publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
pushd publish_ava
cp publish/Ryujinx publish/Ryujinx.Ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
pushd publish_sdl2_headless
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
popd
pushd publish_ava
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
popd
shell: bash
@ -186,12 +185,13 @@ jobs:
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
shell: bash
- name: Publish macOS Ryujinx.Ava
- name: Publish macOS Ryujinx
run: |
./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release
- name: Publish macOS Ryujinx.Headless.SDL2
run: |
./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release

64
.gitignore vendored
View File

@ -176,3 +176,67 @@ PublishProfiles/
# Glade backup files
*.glade~
src/MeloNX/MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib
# SWIFT GITIGNORE
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## User settings
xcuserdata/
## Obj-C/Swift specific
*.hmap
## App packaging
*.ipa
*.dSYM.zip
*.dSYM
## Playgrounds
timeline.xctimeline
playground.xcworkspace
# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
# Package.pins
# Package.resolved
# *.xcodeproj
#
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
# hence it is not needed unless you have added a package configuration file to your project
# .swiftpm
.build/
# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/
#
# Add this line if you want to avoid checking in source code from the Xcode workspace
# *.xcworkspace
# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts
Carthage/Build/
# fastlane
#
# It is recommended to not store the screenshots in the git repo.
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/#source-control
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

View File

@ -1,33 +1,76 @@
# How to compile MeloNX using macOS
# Compiling MeloNX on macOS
## Prerequisites
- [.NET 8.0](<https://dotnet.microsoft.com/en-us/download/dotnet/8.0>)
- A computer with macOS
## Compiling
1. Clone the Git Repo and build Ryujinx
```
git clone https://github.com/melonx-emu/melonx/tree/XC-ios-ht
cd melonx
./compile.sh -x
```
Before you begin, ensure you have the following installed:
2. Open the Xcode project, stored at MeloNX/src/MeloNX
- [**.NET 8.0**](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
- [**Xcode**](https://apps.apple.com/de/app/xcode/id497799835?l=en-GB&mt=12$0)
- A Mac running **macOS**
3. Make sure `Ryujinx.SDL2.Headless.dylib` is set to `Embed & Sign` in the General settings for the Xcode project
4. Signing & Capabilities
Change your 'Team' to your Developer Account (free or paid) and change Bundle Identifier to
`com.*your name*.MeloNX`
## Compilation Steps
6. Build and Run
`CMD+R`
### 1. Clone the Repository and Build Ryujinx
7. Check the [post-setup guide](<https://github.com/melonx-emu/melonx/tree/XC-ios-ht/postsetup.md>)
Open a terminal and run (your password will not be shown in the 2nd command):
## If you don't have a paid developer account
Make sure these entitlements are removed if you don't have a paid Apple Developer account
```sh
git clone https://git.743378673.xyz/MeloNX/MeloNX.git
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
```
Extended Virtual Addressing
Increased Debugging Memory Limit
However, if you only need to update MeloNX, make sure you have cd into the directory then run this
```
git pull
```
### 2. Open the Xcode Project
Navigate to the **Xcode project file** located at:
```
src/MeloNX/MeloNX.xcodeproj
```
Double-click to open it in **Xcode**.
### 3. Configure Signing & Capabilities
- In **Xcode**, go to **Signing & Capabilities**.
- Set the **Team** to your **Apple Developer account** (free or paid).
- Change the **Bundle Identifier** to:
```
com.<your-name>.MeloNX
```
*(Replace `<your-name>` with your actual name or identifier.)*
### 4. Connect Your Device
Ensure your **iPhone/iPad** is **connected** and **selected** (Next to MeloNX with the arrow) in Xcode.
- You may need to install the iOS SDK. it will say next to MeloNX with the arrow saying "iOS XX Not Installed (GET)"
- You will be need to press GET and wait for it to finish downloading and installing
- Then you will be able to select your device and Build and Run.
### Make Sure you do **NOT** select the Simulator. (Which is the Generic names and the ones with the non-coloured icons, e.g. "iPhone 16 Pro")
### 6. Configure the Project Settings
Click the **Run (▶️) button** in Xcode to compile MeloNX and wait it will fail with Undefined Symbol(s) the first time, Thats normal.
- When it fails the first time, do this:
- In **Xcode**, select the **MeloNX** project.
- Under the **General** tab, find `Ryujinx.Headless.SDL2.dylib`.
- Set its **Embed setting** to **"Embed & Sign"**.
### 5. Build and Run
Click the **Run (▶️) button** in Xcode to compile and launch MeloNX.
- When running on your device, Click the **Spray Can Button** below the Run button
- Right Click where it says "> MeloNX PID XXXX"
- Press Detach in the Context Menu.
---
Now you're all set! 🚀 If you encounter issues, please join the discord at https://melonx.org

View File

@ -3,52 +3,50 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Avalonia" Version="11.0.5" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.5" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.5" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.5" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.3" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.3" />
<PackageVersion Include="Avalonia" Version="11.0.10" />
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
<PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
<PackageVersion Include="Concentus" Version="1.1.7" />
<PackageVersion Include="Concentus" Version="2.2.0" />
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageVersion Include="DynamicData" Version="7.14.2" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.4" />
<PackageVersion Include="DynamicData" Version="9.0.4" />
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
<PackageVersion Include="LibHac" Version="0.19.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.1" />
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
<PackageVersion Include="NUnit" Version="3.13.3" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
<PackageVersion Include="OpenTK.Core" Version="4.8.1" />
<PackageVersion Include="OpenTK.Graphics" Version="4.8.1" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.1" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.3" />
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
<PackageVersion Include="shaderc.net" Version="0.1.0" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
<PackageVersion Include="SPB" Version="0.0.4-build28" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
<PackageVersion Include="SkiaSharp" Version="2.88.7" />
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
<PackageVersion Include="SPB" Version="0.0.4-build32" />
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
<PackageVersion Include="System.Management" Version="8.0.0" />
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
</ItemGroup>
</Project>
</Project>

View File

@ -1,9 +1,15 @@
MIT License
MeloNX License
Copyright (c) Ryujinx Team and Contributors
Copyright (c) MeloNX Team and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person (except anyone who has previously attempted or is currently attempting to merge MeloNX with Pomelo) obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Every file is under this license, and all copies must be redistributed under the same license.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Anyone who attempts or has attempted to merge MeloNX with Pomelo, or otherwise use this source code in conjunction with Pomelo, is prohibited from using, copying, modifying, or distributing the source code without first obtaining explicit, written permission from Stossy11.
Additionally, the names of the developers or contributors to this project may not be used to endorse or promote products derived from this software without specific, prior written permission from the respective developer(s).
Ryujinx is licensed under the MIT License. Copyright (c) Ryujinx contributors. All rights to Ryujinx are held by its respective copyright holders, and its use is subject to the terms of the MIT License.

46
MeloNX-hv.entitlements Normal file
View File

@ -0,0 +1,46 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>get-task-allow</key>
<true/>
<key>com.apple.developer.kernel.increased-memory-limit</key>
<true/>
<key>com.apple.developer.kernel.extended-virtual-addressing</key>
<true/>
<key>com.apple.private.iokit.IOServiceSetAuthorizationID</key>
<true/>
<key>com.apple.security.exception.iokit-user-client-class</key>
<array>
<string>AGXCommandQueue</string>
<string>AGXDevice</string>
<string>AGXDeviceUserClient</string>
<string>AGXSharedUserClient</string>
<string>AppleUSBHostDeviceUserClient</string>
<string>AppleUSBHostInterfaceUserClient</string>
<string>IOSurfaceRootUserClient</string>
<string>IOAccelContext</string>
<string>IOAccelContext2</string>
<string>IOAccelDevice</string>
<string>IOAccelDevice2</string>
<string>IOAccelSharedUserClient</string>
<string>IOAccelSharedUserClient2</string>
<string>IOAccelSubmitter2</string>
</array>
<key>com.apple.system.diagnostics.iokit-properties</key>
<true/>
<key>com.apple.vm.device-access</key>
<true/>
<key>com.apple.private.hypervisor</key>
<true/>
<key>com.apple.private.memorystatus</key>
<true/>
<key>com.apple.private.security.no-sandbox</key>
<true/>
<key>com.apple.private.security.storage.AppDataContainers</key>
<true/>
<key>com.apple.private.security.storage.MobileDocuments</key>
<true/>
<key>platform-application</key>
<true/>
</dict>
</plist>

170
README.md
View File

@ -1,22 +1,178 @@
<p align="center">
<a href="https://github.com/MeloNX-Emu/MeloNX">
<img src="https://github.com/MeloNX-Emu/melonx-emu.github.io/blob/main/favicon.png?raw=true" alt="MeloNX Logo" width="120">
<a href="https://melonx.org">
<img src="https://melonx.org/static/imgs/MeloNX.svg" alt="MeloNX Logo" width="120">
</a>
</p>
<h1 align="center">MeloNX</h1>
<p align="center">
MeloNX enables Nintendo Switch game emulation on iOS using the Ryujinx iOS code base.
</p>
<p align="center">
MeloNX is an iOS Nintendo Switch emulator based on Ryujinx, written primarily in C#. Designed to bring accurate performance and a user-friendly interface to iOS, MeloNX makes Switch games accessible on Apple devices.
Developed from the ground up, MeloNX is open-source and available on Github under the <a href="https://github.com/MeloNX-Emu/MeloNX/blob/master/LICENSE.txt" target="_blank">MIT license</a>. <br
Developed from the ground up, MeloNX is open-source and available on Github under the <a href="https://github.com/MeloNX-Emu/MeloNX/blob/master/LICENSE.txt" target="_blank">MeloNX license (Based on MIT)</a>. <br
</p>
## Compatibility
# Compatibility
MeloNX works on iPhone X and later and iPad 7th Gen and later. A lot of games work.
MeloNX works on iPhone XS/XR and later and iPad 8th Gen and later. Check out the Compatibility on the <a href="https://melonx.org/compatibility/" target="_blank">website</a>.
## Usage
# Change
Modified the button layout in landscape mode and added a new joystick designed for FTG.
Tested only on iPhone 13 Pro Max running iOS 16.6.1.
Modified by PhoenixR.
To run MeloNX on your iOS device, at least 4GB of RAM is recommended to ensure stability. For full instructions, refer to our [Setup Guide](https://github.com/MeloNX-Emu/MeloNX/wiki/Setup-Guide).
# Usage
## FAQ
- MeloNX is made for iOS 17+, on iOS 15 - 16 MeloNX can be installed but may have issues or not work at all.
- MeloNX cannot be Sideloaded normally and requires the use of the following Installation Guide(s).
- MeloNX requires JIT
- Recommended Device: iPhone 15 Pro or newer.
- Low-End Recommended Device: iPhone 13 Pro.
## How to install
### Paid Developer Account
1. **Sideload the App**
- Use any sideloading tool that supports Apple IDs.
2. **Enable Entitlements**
- Visit [Apple Developer Identifiers](https://developer.apple.com/account/resources/identifiers).
- Locate **MeloNX** and enable the following entitlements:
- `Increased Memory Limit`
- `Increased Debugging Memory Limit`
3. **Reinstall the App**
- Delete the existing installation.
- Sideload the app again with the updated entitlements.
4. **Enable JIT**
- Use your preferred method to enable Just-In-Time (JIT) compilation.
- We reccomend using [JitStreamer](https://jkcoxson.com/jitstreamer)
5. **Add Necessary Files**
If having Issues installing firmware (Make sure your Keys are installed first)
- If needed, install firmware and keys from **Ryujinx Desktop** (or forks).
- Copy the **bis** and **system** folders
### Free Developer Account (Experimental)
1. **Sideload MeloNX**
- Use [SideStore](https://sidestore.io/) or [AltStore](https://altstore.io/) (**NOT** AltStore PAL).
2. **Sideload the Entitlement App**
- Install [this app](https://github.com/hugeBlack/GetMoreRam/releases/download/nightly/Entitlement.ipa) using [SideStore](https://sidestore.io/) or [AltStore](https://altstore.io/) (**NOT** AltStore PAL).
3. **Sign In to Your Account**
- Open **Settings** in the entitlement app and sign in with your Apple ID.
4. **Refresh App IDs**
- Navigate to the **App IDs** page.
- Tap **Refresh** to update the list.
5. **Enable Increased Memory Limit**
- Select **MeloNX** (should be like "com.stossy11.MeloNX" or some variation) from the list.
- Tap **Add Increased Memory Limit**.
6. **Reinstall MeloNX**
- Delete the existing installation.
- Sideload the app again using SideStore or AltStore.
7. **Verify Increased Memory Limit**
- Open MeloNX and check if the **Increased Memory Limit** is enabled.
8. **Add Necessary Files**
If having Issues installing firmware (Make sure your keys are installed first)
- If needed, install firmware and keys from **Ryujinx Desktop** (or forks).
- Copy the **bis** and **system** folders
9. **Enable JIT**
- Use your preferred method to enable Just-In-Time (JIT) compilation.
- We recommend using [JitStreamer](https://jkcoxson.com/jitstreamer)
### TrollStore
As Said in FAQ:
> MeloNX is made for iOS 17+, on iOS 15 - 16 MeloNX can be installed but may have issues or not work at all.
1. **Install MeloNX with TrollStore**
2. **Add Necessary Files**
3. **Enable TrollStore JIT**
- MeloNX includes automatic JIT using the TrollStore URL Scheme
- Open MeloNX Settings
- Scroll down and enable the "TrollStore JIT" toggle
- Profit
### Free Developer Account (Xcode)
**NOTE: These Xcode builds are nightly and may have unfinished features.**
1. **Compile Guide**
- Visit the [guide here](https://git.743378673.xyz/MeloNX/MeloNX/src/branch/XC-ios-ht/Compile.md).
2. **Add Necessary Files**
If having Issues installing firmware (Make sure your keys are installed first)
- If needed, install firmware and keys from **Ryujinx Desktop** (or forks).
- Copy the **bis** and **system** folders
## Features
- **Audio**
Audio output is entirely supported, audio input (microphone) isn't supported.
We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
- **CPU**
The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support.
It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster).
The fastest option (host, unchecked) is set by default.
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads.
The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game.
NOTE: This feature is enabled by default, You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch!
These improvements are permanent and do not require any extra launches going forward.
- **GPU**
The GPU emulator emulates the Switch's Maxwell GPU using Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
- **Input**
We currently have support for keyboard, touch input, JoyCon input support, and nearly all controllers.
Motion controls are natively supported in most cases.
- **DLC & Modifications**
MeloNX supports DLC + Game Update Add-ons.
Mods (romfs, exefs, and runtime mods such as cheats) are supported;
- **Configuration**
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
## License
This software is licensed under the terms of the [MeloNX license (Based on MIT License)](LICENSE.txt).
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
## Credits
- [Ryujinx](https://github.com/ryujinx-mirror/ryujinx) is used for the base of this emulator. (link is to ryujinx-mirror since they were supportive)
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.1.32228.430
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Gtk3", "src\Ryujinx.Gtk3\Ryujinx.Gtk3.csproj", "{074045D4-3ED2-4711-9169-E385F2BFB5A0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
EndProject
@ -69,9 +69,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.FFmpeg", "src\Ryujinx.Graphics.Nvdec.FFmpeg\Ryujinx.Graphics.Nvdec.FFmpeg.csproj", "{BEE1C184-C9A4-410B-8DFC-FB74D5C93AEB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ava", "src\Ryujinx.Ava\Ryujinx.Ava.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx", "src\Ryujinx\Ryujinx.csproj", "{7C1B2721-13DA-4B62-B046-C626605ECCE6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.Common", "src\Ryujinx.Ui.Common\Ryujinx.Ui.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.Common", "src\Ryujinx.UI.Common\Ryujinx.UI.Common.csproj", "{BA161CA0-CD65-4E6E-B644-51C8D1E542DC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
EndProject
@ -79,7 +79,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Ui.LocaleGenerator", "src\Ryujinx.Ui.LocaleGenerator\Ryujinx.Ui.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.UI.LocaleGenerator", "src\Ryujinx.UI.LocaleGenerator\Ryujinx.UI.LocaleGenerator.csproj", "{77D01AD9-2C98-478E-AE1D-8F7100738FB4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
EndProject
@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Kernel.Generators", "src\Ryujinx.Horizon.Kernel.Generators\Ryujinx.Horizon.Kernel.Generators.csproj", "{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE.Generators", "src\Ryujinx.HLE.Generators\Ryujinx.HLE.Generators.csproj", "{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -249,6 +251,10 @@ Global
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B575BCDE-2FD8-4A5D-8756-31CDD7FE81F0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -4,6 +4,8 @@
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"&gt;&lt;ElementKinds&gt;&lt;Kind Name="NAMESPACE" /&gt;&lt;Kind Name="CLASS" /&gt;&lt;Kind Name="STRUCT" /&gt;&lt;Kind Name="ENUM" /&gt;&lt;Kind Name="DELEGATE" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"&gt;&lt;ExtraRule Prefix="I" Suffix="" Style="AaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ASET/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>

View File

@ -1,6 +1,5 @@
#!/bin/bash
# Define the destination directory (hardcoded)
DESTINATION_DIR="src/MeloNX/Dependencies/Dynamic\ Libraries/Ryujinx.Headless.SDL2.dylib"

44
distribution/ios/get_dotnet.sh Executable file
View File

@ -0,0 +1,44 @@
#!/bin/bash
XCCONFIG_FILE="${SRCROOT}/MeloNX.xcconfig"
# Define the common paths to search for dotnet, including user-specific directories
SEARCH_PATHS=(
"/usr/local/share/dotnet"
"/usr/local/bin"
"/usr/bin"
"/bin"
"/opt"
"/Library/Frameworks"
"$HOME/.dotnet"
"$HOME/Developer"
)
# Initialize DOTNET_PATH as empty
DOTNET_PATH=""
# Search in the defined paths
for path in "${SEARCH_PATHS[@]}"; do
if [ -d "$path" ]; then
DOTNET_PATH=$(find "$path" -name dotnet -type f -print -quit 2>/dev/null)
if [ -n "$DOTNET_PATH" ]; then
break
fi
fi
done
# Check if the path was found
if [ -z "$DOTNET_PATH" ]; then
echo "Error: dotnet path not found."
exit 1
fi
echo "dotnet path: $DOTNET_PATH"
# Escape the path for sed
ESCAPED_PATH=$(echo "$DOTNET_PATH" | sed 's/\//\\\//g')
# Update the xcconfig file
sed -i '' "s/^DOTNET = .*/DOTNET = $ESCAPED_PATH/g" "$XCCONFIG_FILE"
echo "Updated MeloNX.xcconfig with DOTNET path: $DOTNET_PATH"

View File

@ -0,0 +1,38 @@
#!/bin/bash
GITEA_URL="https://git.743378673.xyz/"
REPO="MeloNX"
XCCONFIG_FILE="${SRCROOT}/MeloNX.xcconfig"
INCREMENT_PATCH=false
# Check for --patch argument
if [[ "$1" == "--patch" ]]; then
INCREMENT_PATCH=true
fi
# Fetch latest tag from Gitea
LATEST_VERSION=$(curl -s "${GITEA_URL}/api/v1/repos/${REPO}/${REPO}/tags" | jq -r '.[].name' | sort -V | tail -n1)
if [ -z "$LATEST_VERSION" ]; then
echo "Error: Could not fetch latest tag from Gitea"
exit 1
fi
echo "Latest version: $LATEST_VERSION"
# Split version into major, minor, and patch
IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST_VERSION"
# Increment version based on argument
if $INCREMENT_PATCH; then
NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
else
NEW_VERSION="$MAJOR.$((MINOR + 1)).0"
fi
echo "New version: $NEW_VERSION"
sed -i '' "s/^VERSION = $LATEST_VERSION$/VERSION = $NEW_VERSION/g" "$XCCONFIG_FILE"
echo "Updated MeloNX.xcconfig with version $NEW_VERSION"

View File

@ -4,7 +4,7 @@ Name=Ryujinx
Type=Application
Icon=Ryujinx
Exec=Ryujinx.sh %f
Comment=Plays Nintendo Switch applications
Comment=A Nintendo Switch Emulator
GenericName=Nintendo Switch Emulator
Terminal=false
Categories=Game;Emulator;

15
distribution/linux/Ryujinx.sh Normal file → Executable file
View File

@ -1,20 +1,23 @@
#!/bin/sh
SCRIPT_DIR=$(dirname "$(realpath "$0")")
RYUJINX_BIN="Ryujinx"
if [ -f "$SCRIPT_DIR/Ryujinx.Ava" ]; then
RYUJINX_BIN="Ryujinx.Ava"
fi
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
RYUJINX_BIN="Ryujinx.Headless.SDL2"
fi
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
RYUJINX_BIN="Ryujinx"
fi
if [ -z "$RYUJINX_BIN" ]; then
exit 1
fi
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
if command -v gamemoderun > /dev/null 2>&1; then
COMMAND="$COMMAND gamemoderun"
fi
$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"

View File

@ -14,8 +14,8 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
# Copy executables first
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
# Copy executable and nsure executable can be executed
cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
# Then all libraries

View File

@ -22,9 +22,9 @@ EXTRA_ARGS=$8
if [ "$VERSION" == "1.1.0" ];
then
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=ryujinx-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.app.tar
else
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
fi
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
@ -38,9 +38,9 @@ mkdir -p "$TEMP_DIRECTORY"
DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
dotnet restore
dotnet build -c "$CONFIGURATION" src/Ryujinx.Ava
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Ava
dotnet build -c "$CONFIGURATION" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
@ -108,6 +108,13 @@ tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf "$RELEASE_TAR_FILE_NAME"
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
rm "$RELEASE_TAR_FILE_NAME"
# Create legacy update package for Avalonia to not left behind old testers.
if [ "$VERSION" != "1.1.0" ];
then
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
fi
popd
echo "Done"

View File

@ -0,0 +1,8 @@
#!/bin/sh
launch_arch="$(uname -m)"
if [ "$(sysctl -in sysctl.proc_translated)" = "1" ]
then
launch_arch="arm64"
fi
arch -$launch_arch {0} {1}

View File

@ -33,8 +33,3 @@ Project Docs
=================
To be added. Many project files will contain basic XML docs for key functions and classes in the meantime.
Other Information
=================
- N/A

View File

@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
long originalPosition = _stream.Position;
_stream.Seek(0, SeekOrigin.Begin);
_stream.Read(code, 0, code.Length);
_stream.ReadExactly(code, 0, code.Length);
_stream.Seek(originalPosition, SeekOrigin.Begin);
RelocInfo relocInfo;

View File

@ -251,7 +251,20 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
int selectedReg = GetHighestValueIndex(freePositions);
// If this is a copy destination variable, we prefer the register used for the copy source.
// If the register is available, then the copy can be eliminated later as both source
// and destination will use the same register.
int selectedReg;
if (current.TryGetCopySourceRegister(out int preferredReg) && freePositions[preferredReg] >= current.GetEnd())
{
selectedReg = preferredReg;
}
else
{
selectedReg = GetHighestValueIndex(freePositions);
}
int selectedNextUse = freePositions[selectedReg];
// Intervals starts and ends at odd positions, unless they span an entire
@ -431,7 +444,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
}
}
private static int GetHighestValueIndex(Span<int> span)
private static int GetHighestValueIndex(ReadOnlySpan<int> span)
{
int highest = int.MinValue;
@ -798,12 +811,12 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
// The "visited" state is stored in the MSB of the local's value.
const ulong VisitedMask = 1ul << 63;
bool IsVisited(Operand local)
static bool IsVisited(Operand local)
{
return (local.GetValueUnsafe() & VisitedMask) != 0;
}
void SetVisited(Operand local)
static void SetVisited(Operand local)
{
local.GetValueUnsafe() |= VisitedMask;
}
@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
{
dest.NumberLocal(_intervals.Count);
_intervals.Add(new LiveInterval(dest));
LiveInterval interval = new LiveInterval(dest);
_intervals.Add(interval);
SetVisited(dest);
// If this is a copy (or copy-like operation), set the copy source interval as well.
// This is used for register preferencing later on, which allows the copy to be eliminated
// in some cases.
if (node.Instruction == Instruction.Copy || node.Instruction == Instruction.ZeroExtend32)
{
Operand source = node.GetSource(0);
if (source.Kind == OperandKind.LocalVariable &&
source.GetLocalNumber() > 0 &&
(node.Instruction == Instruction.Copy || source.Type == OperandType.I32))
{
interval.SetCopySource(_intervals[source.GetLocalNumber()]);
}
}
}
}
}

View File

@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
public LiveRange CurrRange;
public LiveInterval Parent;
public LiveInterval CopySource;
public UseList Uses;
public LiveIntervalList Children;
@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
private ref LiveRange CurrRange => ref _data->CurrRange;
private ref LiveRange PrevRange => ref _data->PrevRange;
private ref LiveInterval Parent => ref _data->Parent;
private ref LiveInterval CopySource => ref _data->CopySource;
private ref UseList Uses => ref _data->Uses;
private ref LiveIntervalList Children => ref _data->Children;
@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
Register = register;
}
public void SetCopySource(LiveInterval copySource)
{
CopySource = copySource;
}
public bool TryGetCopySourceRegister(out int copySourceRegIndex)
{
if (CopySource._data != null)
{
copySourceRegIndex = CopySource.Register.Index;
return true;
}
copySourceRegIndex = 0;
return false;
}
public void Reset()
{
PrevRange = default;

View File

@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
_stream.Read(buffer);
_stream.ReadExactly(buffer);
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
codeStream.Write(buffer);

View File

@ -517,7 +517,10 @@ namespace ARMeilleure.Decoders
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create);
SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create);
SetA64("010111110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Si, InstEmit.Sqshl_Si, OpCodeSimdShImm.Create);
SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create);
SetA64("0000111100>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
SetA64("010011110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create);
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create);
@ -743,6 +746,7 @@ namespace ARMeilleure.Decoders
SetA32("<<<<01101000xxxxxxxxxxxxxx01xxxx", InstName.Pkh, InstEmit32.Pkh, OpCode32AluRsImm.Create);
SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
SetA32("11110111xx01xxxx1111xxxxxxx0xxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
SetA32("<<<<01100010xxxxxxxx11110001xxxx", InstName.Qadd16, InstEmit32.Qadd16, OpCode32AluReg.Create);
SetA32("<<<<011011111111xxxx11110011xxxx", InstName.Rbit, InstEmit32.Rbit, OpCode32AluReg.Create);
SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create);
SetA32("<<<<011010111111xxxx11111011xxxx", InstName.Rev16, InstEmit32.Rev16, OpCode32AluReg.Create);
@ -819,6 +823,10 @@ namespace ARMeilleure.Decoders
SetA32("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, OpCode32AluUmull.Create);
SetA32("<<<<01100110xxxxxxxx11110001xxxx", InstName.Uqadd16, InstEmit32.Uqadd16, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11111001xxxx", InstName.Uqadd8, InstEmit32.Uqadd8, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11110111xxxx", InstName.Uqsub16, InstEmit32.Uqsub16, OpCode32AluReg.Create);
SetA32("<<<<01100110xxxxxxxx11111111xxxx", InstName.Uqsub8, InstEmit32.Uqsub8, OpCode32AluReg.Create);
SetA32("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.Create);
@ -872,6 +880,7 @@ namespace ARMeilleure.Decoders
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
@ -992,6 +1001,7 @@ namespace ARMeilleure.Decoders
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<00xxxx0110xxx0xxxx", InstName.Vpadal, InstEmit32.Vpadal, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
@ -1002,6 +1012,8 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
SetAsimd("111100110x01xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("111100110x10xxxxxxxx1011xxx0xxxx", InstName.Vqrdmulh, InstEmit32.Vqrdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx100100x1xxx0", InstName.Vqshrn, InstEmit32.Vqshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
@ -1023,8 +1035,10 @@ namespace ARMeilleure.Decoders
SetAsimd("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx101000x1xxxx", InstName.Vshll, InstEmit32.Vshll, OpCode32SimdShImmLong.Create, OpCode32SimdShImmLong.CreateT32); // A1 encoding.
SetAsimd("111100111x11<<10xxxx001100x0xxxx", InstName.Vshll, InstEmit32.Vshll2, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32); // A2 encoding.
SetAsimd("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
SetAsimd("111100111x>>>xxxxxxx0101>xx1xxxx", InstName.Vsli, InstEmit32.Vsli_I, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
@ -1049,6 +1063,7 @@ namespace ARMeilleure.Decoders
SetAsimd("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.CreateT32);
SetAsimd("1111001x1x<<xxxxxxx00011x0x0xxxx", InstName.Vsubw, InstEmit32.Vsubw_I, OpCode32SimdRegWide.Create, OpCode32SimdRegWide.CreateT32);
SetAsimd("111100111x110010xxxx00000xx0xxxx", InstName.Vswp, InstEmit32.Vswp, OpCode32Simd.Create, OpCode32Simd.CreateT32);
SetAsimd("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);

View File

@ -2,6 +2,8 @@ using ARMeilleure.Decoders;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.State;
using ARMeilleure.Translation;
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using static ARMeilleure.Instructions.InstEmitAluHelper;
using static ARMeilleure.Instructions.InstEmitHelper;
@ -19,6 +21,12 @@ namespace ARMeilleure.Instructions
Operand n = GetAluN(context);
Operand m = GetAluM(context, setCarry: false);
if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12)
{
// For ADR, PC is always 4 bytes aligned, even in Thumb mode.
n = context.BitwiseAnd(n, Const(~3u));
}
Operand res = context.Add(n, m);
if (ShouldSetFlags(context))
@ -284,6 +292,16 @@ namespace ARMeilleure.Instructions
EmitAluStore(context, res);
}
public static void Qadd16(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitSigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateRange(context, d, context.Add(n, m), 16, unsigned: false, setQ: false);
}));
}
public static void Rbit(ArmEmitterContext context)
{
Operand m = GetAluM(context);
@ -467,6 +485,12 @@ namespace ARMeilleure.Instructions
Operand n = GetAluN(context);
Operand m = GetAluM(context, setCarry: false);
if (op.Rn == RegisterAlias.Aarch32Pc && op is OpCodeT32AluImm12)
{
// For ADR, PC is always 4 bytes aligned, even in Thumb mode.
n = context.BitwiseAnd(n, Const(~3u));
}
Operand res = context.Subtract(n, m);
if (ShouldSetFlags(context))
@ -546,6 +570,46 @@ namespace ARMeilleure.Instructions
EmitHsub8(context, unsigned: true);
}
public static void Uqadd16(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqadd(context, d, context.Add(n, m), 16);
}));
}
public static void Uqadd8(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqadd(context, d, context.Add(n, m), 8);
}));
}
public static void Uqsub16(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned16BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqsub(context, d, context.Subtract(n, m), 16);
}));
}
public static void Uqsub8(ArmEmitterContext context)
{
OpCode32AluReg op = (OpCode32AluReg)context.CurrOp;
SetIntA32(context, op.Rd, EmitUnsigned8BitPair(context, GetIntA32(context, op.Rn), GetIntA32(context, op.Rm), (d, n, m) =>
{
EmitSaturateUqsub(context, d, context.Subtract(n, m), 8);
}));
}
public static void Usat(ArmEmitterContext context)
{
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
@ -922,6 +986,251 @@ namespace ARMeilleure.Instructions
}
}
private static void EmitSaturateRange(ArmEmitterContext context, Operand result, Operand value, uint saturateTo, bool unsigned, bool setQ = true)
{
Debug.Assert(saturateTo <= 32);
Debug.Assert(!unsigned || saturateTo < 32);
if (!unsigned && saturateTo == 32)
{
// No saturation possible for this case.
context.Copy(result, value);
return;
}
else if (saturateTo == 0)
{
// Result is always zero if we saturate 0 bits.
context.Copy(result, Const(0));
return;
}
Operand satValue;
if (unsigned)
{
// Negative values always saturate (to zero).
// So we must always ignore the sign bit when masking, so that the truncated value will differ from the original one.
satValue = context.BitwiseAnd(value, Const((int)(uint.MaxValue >> (32 - (int)saturateTo))));
}
else
{
satValue = context.ShiftLeft(value, Const(32 - (int)saturateTo));
satValue = context.ShiftRightSI(satValue, Const(32 - (int)saturateTo));
}
// If the result is 0, the values are equal and we don't need saturation.
Operand lblNoSat = Label();
context.BranchIfFalse(lblNoSat, context.Subtract(value, satValue));
// Saturate and set Q flag.
if (unsigned)
{
if (saturateTo == 31)
{
// Only saturation case possible when going from 32 bits signed to 32 or 31 bits unsigned
// is when the signed input is negative, as all positive values are representable on a 31 bits range.
satValue = Const(0);
}
else
{
satValue = context.ShiftRightSI(value, Const(31));
satValue = context.BitwiseNot(satValue);
satValue = context.ShiftRightUI(satValue, Const(32 - (int)saturateTo));
}
}
else
{
if (saturateTo == 1)
{
satValue = context.ShiftRightSI(value, Const(31));
}
else
{
satValue = Const(uint.MaxValue >> (33 - (int)saturateTo));
satValue = context.BitwiseExclusiveOr(satValue, context.ShiftRightSI(value, Const(31)));
}
}
if (setQ)
{
SetFlag(context, PState.QFlag, Const(1));
}
context.Copy(result, satValue);
Operand lblExit = Label();
context.Branch(lblExit);
context.MarkLabel(lblNoSat);
context.Copy(result, value);
context.MarkLabel(lblExit);
}
private static void EmitSaturateUqadd(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
{
Debug.Assert(saturateTo <= 32);
if (saturateTo == 32)
{
// No saturation possible for this case.
context.Copy(result, value);
return;
}
else if (saturateTo == 0)
{
// Result is always zero if we saturate 0 bits.
context.Copy(result, Const(0));
return;
}
// If the result is 0, the values are equal and we don't need saturation.
Operand lblNoSat = Label();
context.BranchIfFalse(lblNoSat, context.ShiftRightUI(value, Const((int)saturateTo)));
// Saturate.
context.Copy(result, Const(uint.MaxValue >> (32 - (int)saturateTo)));
Operand lblExit = Label();
context.Branch(lblExit);
context.MarkLabel(lblNoSat);
context.Copy(result, value);
context.MarkLabel(lblExit);
}
private static void EmitSaturateUqsub(ArmEmitterContext context, Operand result, Operand value, uint saturateTo)
{
Debug.Assert(saturateTo <= 32);
if (saturateTo == 32)
{
// No saturation possible for this case.
context.Copy(result, value);
return;
}
else if (saturateTo == 0)
{
// Result is always zero if we saturate 0 bits.
context.Copy(result, Const(0));
return;
}
// If the result is 0, the values are equal and we don't need saturation.
Operand lblNoSat = Label();
context.BranchIf(lblNoSat, value, Const(0), Comparison.GreaterOrEqual);
// Saturate.
// Assumes that the value can only underflow, since this is only used for unsigned subtraction.
context.Copy(result, Const(0));
Operand lblExit = Label();
context.Branch(lblExit);
context.MarkLabel(lblNoSat);
context.Copy(result, value);
context.MarkLabel(lblExit);
}
private static Operand EmitSigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
Operand tempD = context.AllocateLocal(OperandType.I32);
Operand tempN = context.SignExtend16(OperandType.I32, rn);
Operand tempM = context.SignExtend16(OperandType.I32, rm);
elementAction(tempD, tempN, tempM);
Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
tempN = context.ShiftRightSI(rn, Const(16));
tempM = context.ShiftRightSI(rm, Const(16));
elementAction(tempD, tempN, tempM);
return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
}
private static Operand EmitUnsigned16BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
Operand tempD = context.AllocateLocal(OperandType.I32);
Operand tempN = context.ZeroExtend16(OperandType.I32, rn);
Operand tempM = context.ZeroExtend16(OperandType.I32, rm);
elementAction(tempD, tempN, tempM);
Operand tempD2 = context.ZeroExtend16(OperandType.I32, tempD);
tempN = context.ShiftRightUI(rn, Const(16));
tempM = context.ShiftRightUI(rm, Const(16));
elementAction(tempD, tempN, tempM);
return context.BitwiseOr(tempD2, context.ShiftLeft(tempD, Const(16)));
}
private static Operand EmitSigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
return Emit8BitPair(context, rn, rm, elementAction, unsigned: false);
}
private static Operand EmitUnsigned8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction)
{
return Emit8BitPair(context, rn, rm, elementAction, unsigned: true);
}
private static Operand Emit8BitPair(ArmEmitterContext context, Operand rn, Operand rm, Action<Operand, Operand, Operand> elementAction, bool unsigned)
{
Operand tempD = context.AllocateLocal(OperandType.I32);
Operand result = default;
for (int b = 0; b < 4; b++)
{
Operand nByte = b != 0 ? context.ShiftRightUI(rn, Const(b * 8)) : rn;
Operand mByte = b != 0 ? context.ShiftRightUI(rm, Const(b * 8)) : rm;
if (unsigned)
{
nByte = context.ZeroExtend8(OperandType.I32, nByte);
mByte = context.ZeroExtend8(OperandType.I32, mByte);
}
else
{
nByte = context.SignExtend8(OperandType.I32, nByte);
mByte = context.SignExtend8(OperandType.I32, mByte);
}
elementAction(tempD, nByte, mByte);
if (b == 0)
{
result = context.ZeroExtend8(OperandType.I32, tempD);
}
else if (b < 3)
{
result = context.BitwiseOr(result, context.ShiftLeft(context.ZeroExtend8(OperandType.I32, tempD), Const(b * 8)));
}
else
{
result = context.BitwiseOr(result, context.ShiftLeft(tempD, Const(24)));
}
}
return result;
}
private static void EmitAluStore(ArmEmitterContext context, Operand value)
{
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;

View File

@ -403,19 +403,25 @@ namespace ARMeilleure.Instructions
{
return EmitHostMappedPointer(context, address);
}
else if (context.Memory.Type == MemoryManagerType.HostTracked)
else if (context.Memory.Type.IsHostTracked())
{
if (address.Type == OperandType.I32)
{
address = context.ZeroExtend32(OperandType.I64, address);
}
if (context.Memory.Type == MemoryManagerType.HostTracked)
{
Operand mask = Const(ulong.MaxValue >> (64 - context.Memory.AddressSpaceBits));
address = context.BitwiseAnd(address, mask);
}
Operand ptBase = !context.HasPtc
? Const(context.Memory.PageTablePointer.ToInt64())
: Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol);
Operand ptOffset = context.ShiftRightUI(address, Const(PageBits));
if (ptOffset.Type == OperandType.I32)
{
ptOffset = context.ZeroExtend32(OperandType.I64, ptOffset);
}
return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3)))));
}

View File

@ -2426,7 +2426,11 @@ namespace ARMeilleure.Instructions
}
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
{
Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtss, GetVec(op.Rn)), scalar: true);
// RSQRTSS handles subnormals as zero, which differs from Arm, so we can't use it here.
Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtss, GetVec(op.Rn));
res = context.AddIntrinsic(Intrinsic.X86Rcpss, res);
res = EmitSse41Round32Exp8OpF(context, res, scalar: true);
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
}
@ -2451,7 +2455,11 @@ namespace ARMeilleure.Instructions
}
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
{
Operand res = EmitSse41Round32Exp8OpF(context, context.AddIntrinsic(Intrinsic.X86Rsqrtps, GetVec(op.Rn)), scalar: false);
// RSQRTPS handles subnormals as zero, which differs from Arm, so we can't use it here.
Operand res = context.AddIntrinsic(Intrinsic.X86Sqrtps, GetVec(op.Rn));
res = context.AddIntrinsic(Intrinsic.X86Rcpps, res);
res = EmitSse41Round32Exp8OpF(context, res, scalar: false);
if (op.RegisterSize == RegisterSize.Simd64)
{

View File

@ -1115,6 +1115,13 @@ namespace ARMeilleure.Instructions
}
}
public static void Vpadal(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1);
}
public static void Vpaddl(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
@ -1239,6 +1246,33 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => EmitSatQ(context, op1, 8 << op.Size, signedSrc: true, signedDst: false), signed: true);
}
public static void Vqrdmulh(ArmEmitterContext context)
{
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
int eSize = 8 << op.Size;
EmitVectorBinaryOpI32(context, (op1, op2) =>
{
if (op.Size == 2)
{
op1 = context.SignExtend32(OperandType.I64, op1);
op2 = context.SignExtend32(OperandType.I64, op2);
}
Operand res = context.Multiply(op1, op2);
res = context.Add(res, Const(res.Type, 1L << (eSize - 2)));
res = context.ShiftRightSI(res, Const(eSize - 1));
res = EmitSatQ(context, res, eSize, signedSrc: true, signedDst: true);
if (op.Size == 2)
{
res = context.ConvertI64ToI32(res);
}
return res;
}, signed: true);
}
public static void Vqsub(ArmEmitterContext context)
{
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;

View File

@ -578,6 +578,22 @@ namespace ARMeilleure.Instructions
}
}
// VRINTR (floating-point).
public static void Vrintr_S(ArmEmitterContext context)
{
if (Optimizations.UseAdvSimd)
{
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS);
}
else
{
EmitScalarUnaryOpF32(context, (op1) =>
{
return EmitRoundByRMode(context, op1);
});
}
}
// VRINTZ (floating-point).
public static void Vrint_Z(ArmEmitterContext context)
{

View File

@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
int elems = op.GetBytesCount() >> op.Size;
int pairs = elems >> 1;
Operand res = GetVecA32(op.Qd);
for (int index = 0; index < pairs; index++)
{
int pairIndex = index * 2;
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
if (op.Size == 2)
{
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
}
Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed);
res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1);
}
context.Copy(GetVecA32(op.Qd), res);
}
// Narrow
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)

View File

@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void Vswp(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
if (op.Q)
{
Operand temp = context.Copy(GetVecA32(op.Qd));
context.Copy(GetVecA32(op.Qd), GetVecA32(op.Qm));
context.Copy(GetVecA32(op.Qm), temp);
}
else
{
Operand temp = ExtractScalar(context, OperandType.I64, op.Vd);
InsertScalar(context, op.Vd, ExtractScalar(context, OperandType.I64, op.Vm));
InsertScalar(context, op.Vm, temp);
}
}
public static void Vtbl(ArmEmitterContext context)
{
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;

View File

@ -116,7 +116,7 @@ namespace ARMeilleure.Instructions
}
else if (shift >= eSize)
{
if ((op.RegisterSize == RegisterSize.Simd64))
if (op.RegisterSize == RegisterSize.Simd64)
{
Operand res = context.VectorZeroUpper64(GetVec(op.Rd));
@ -359,6 +359,16 @@ namespace ARMeilleure.Instructions
}
}
public static void Sqshl_Si(ArmEmitterContext context)
{
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Scalar | ShlRegFlags.Saturating);
}
public static void Sqshl_Vi(ArmEmitterContext context)
{
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Saturating);
}
public static void Sqshrn_S(ArmEmitterContext context)
{
if (Optimizations.UseAdvSimd)
@ -1593,6 +1603,99 @@ namespace ARMeilleure.Instructions
Saturating = 1 << 3,
}
private static void EmitShlImmOp(ArmEmitterContext context, bool signedDst, ShlRegFlags flags = ShlRegFlags.None)
{
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
bool signed = flags.HasFlag(ShlRegFlags.Signed);
bool saturating = flags.HasFlag(ShlRegFlags.Saturating);
OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp;
Operand res = context.VectorZero();
int elems = !scalar ? op.GetBytesCount() >> op.Size : 1;
for (int index = 0; index < elems; index++)
{
Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed);
Operand e = !saturating
? EmitShlImm(context, ne, GetImmShl(op), op.Size)
: EmitShlImmSatQ(context, ne, GetImmShl(op), op.Size, signed, signedDst);
res = EmitVectorInsert(context, res, e, index, op.Size);
}
context.Copy(GetVec(op.Rd), res);
}
private static Operand EmitShlImm(ArmEmitterContext context, Operand op, int shiftLsB, int size)
{
int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
Operand res = context.AllocateLocal(OperandType.I64);
if (shiftLsB >= eSize)
{
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
context.Copy(res, shl);
}
else
{
Operand zeroL = Const(0L);
context.Copy(res, zeroL);
}
return res;
}
private static Operand EmitShlImmSatQ(ArmEmitterContext context, Operand op, int shiftLsB, int size, bool signedSrc, bool signedDst)
{
int eSize = 8 << size;
Debug.Assert(op.Type == OperandType.I64);
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
Operand lblEnd = Label();
Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op);
if (shiftLsB >= eSize)
{
context.Copy(res, signedSrc
? EmitSignedSignSatQ(context, op, size)
: EmitUnsignedSignSatQ(context, op, size));
}
else
{
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
if (eSize == 64)
{
Operand sarOrShr = signedSrc
? context.ShiftRightSI(shl, Const(shiftLsB))
: context.ShiftRightUI(shl, Const(shiftLsB));
context.Copy(res, shl);
context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal);
context.Copy(res, signedSrc
? EmitSignedSignSatQ(context, op, size)
: EmitUnsignedSignSatQ(context, op, size));
}
else
{
context.Copy(res, signedSrc
? EmitSignedSrcSatQ(context, shl, size, signedDst)
: EmitUnsignedSrcSatQ(context, shl, size, signedDst));
}
}
context.MarkLabel(lblEnd);
return res;
}
private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None)
{
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);

View File

@ -106,6 +106,38 @@ namespace ARMeilleure.Instructions
context.Copy(GetVecA32(op.Qd), res);
}
public static void Vshll2(ArmEmitterContext context)
{
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
Operand res = context.VectorZero();
int elems = op.GetBytesCount() >> op.Size;
for (int index = 0; index < elems; index++)
{
Operand me = EmitVectorExtract32(context, op.Qm, op.Im + index, op.Size, !op.U);
if (op.Size == 2)
{
if (op.U)
{
me = context.ZeroExtend32(OperandType.I64, me);
}
else
{
me = context.SignExtend32(OperandType.I64, me);
}
}
me = context.ShiftLeft(me, Const(8 << op.Size));
res = EmitVectorInsert(context, res, me, index, op.Size + 1);
}
context.Copy(GetVecA32(op.Qd), res);
}
public static void Vshr(ArmEmitterContext context)
{
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
@ -130,6 +162,36 @@ namespace ARMeilleure.Instructions
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
}
public static void Vsli_I(ArmEmitterContext context)
{
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
int shift = op.Shift;
int eSize = 8 << op.Size;
ulong mask = shift != 0 ? ulong.MaxValue >> (64 - shift) : 0UL;
Operand res = GetVec(op.Qd);
int elems = op.GetBytesCount() >> op.Size;
for (int index = 0; index < elems; index++)
{
Operand me = EmitVectorExtractZx(context, op.Qm, op.Im + index, op.Size);
Operand neShifted = context.ShiftLeft(me, Const(shift));
Operand de = EmitVectorExtractZx(context, op.Qd, op.Id + index, op.Size);
Operand deMasked = context.BitwiseAnd(de, Const(mask));
Operand e = context.BitwiseOr(neShifted, deMasked);
res = EmitVectorInsert(context, res, e, op.Id + index, op.Size);
}
context.Copy(GetVec(op.Qd), res);
}
public static void Vsra(ArmEmitterContext context)
{
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;

View File

@ -384,7 +384,9 @@ namespace ARMeilleure.Instructions
Sqrshrn_V,
Sqrshrun_S,
Sqrshrun_V,
Sqshl_Si,
Sqshl_V,
Sqshl_Vi,
Sqshrn_S,
Sqshrn_V,
Sqshrun_S,
@ -525,6 +527,7 @@ namespace ARMeilleure.Instructions
Pld,
Pop,
Push,
Qadd16,
Rev,
Revsh,
Rsb,
@ -569,6 +572,10 @@ namespace ARMeilleure.Instructions
Umaal,
Umlal,
Umull,
Uqadd16,
Uqadd8,
Uqsub16,
Uqsub8,
Usat,
Usat16,
Usub8,
@ -635,6 +642,7 @@ namespace ARMeilleure.Instructions
Vorn,
Vorr,
Vpadd,
Vpadal,
Vpaddl,
Vpmax,
Vpmin,
@ -642,6 +650,7 @@ namespace ARMeilleure.Instructions
Vqdmulh,
Vqmovn,
Vqmovun,
Vqrdmulh,
Vqrshrn,
Vqrshrun,
Vqshrn,
@ -654,6 +663,7 @@ namespace ARMeilleure.Instructions
Vrintm,
Vrintn,
Vrintp,
Vrintr,
Vrintx,
Vrshr,
Vrshrn,
@ -662,6 +672,7 @@ namespace ARMeilleure.Instructions
Vshll,
Vshr,
Vshrn,
Vsli,
Vst1,
Vst2,
Vst3,
@ -678,6 +689,7 @@ namespace ARMeilleure.Instructions
Vsub,
Vsubl,
Vsubw,
Vswp,
Vtbl,
Vtrn,
Vtst,

View File

@ -91,54 +91,54 @@ namespace ARMeilleure.Instructions
#region "Read"
public static byte ReadByte(ulong address)
{
return GetMemoryManager().ReadTracked<byte>(address);
return GetMemoryManager().ReadGuest<byte>(address);
}
public static ushort ReadUInt16(ulong address)
{
return GetMemoryManager().ReadTracked<ushort>(address);
return GetMemoryManager().ReadGuest<ushort>(address);
}
public static uint ReadUInt32(ulong address)
{
return GetMemoryManager().ReadTracked<uint>(address);
return GetMemoryManager().ReadGuest<uint>(address);
}
public static ulong ReadUInt64(ulong address)
{
return GetMemoryManager().ReadTracked<ulong>(address);
return GetMemoryManager().ReadGuest<ulong>(address);
}
public static V128 ReadVector128(ulong address)
{
return GetMemoryManager().ReadTracked<V128>(address);
return GetMemoryManager().ReadGuest<V128>(address);
}
#endregion
#region "Write"
public static void WriteByte(ulong address, byte value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt16(ulong address, ushort value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt32(ulong address, uint value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteUInt64(ulong address, ulong value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
public static void WriteVector128(ulong address, V128 value)
{
GetMemoryManager().Write(address, value);
GetMemoryManager().WriteGuest(address, value);
}
#endregion

View File

@ -4,7 +4,5 @@ namespace ARMeilleure.Memory
{
IJitMemoryBlock Allocate(ulong size);
IJitMemoryBlock Reserve(ulong size);
ulong GetPageSize();
}
}

View File

@ -28,6 +28,17 @@ namespace ARMeilleure.Memory
/// <returns>The data</returns>
T ReadTracked<T>(ulong va) where T : unmanaged;
/// <summary>
/// Reads data from CPU mapped memory, from guest code. (with read tracking)
/// </summary>
/// <typeparam name="T">Type of the data being read</typeparam>
/// <param name="va">Virtual address of the data in memory</param>
/// <returns>The data</returns>
T ReadGuest<T>(ulong va) where T : unmanaged
{
return ReadTracked<T>(va);
}
/// <summary>
/// Writes data to CPU mapped memory.
/// </summary>
@ -36,6 +47,17 @@ namespace ARMeilleure.Memory
/// <param name="value">Data to be written</param>
void Write<T>(ulong va, T value) where T : unmanaged;
/// <summary>
/// Writes data to CPU mapped memory, from guest code.
/// </summary>
/// <typeparam name="T">Type of the data being written</typeparam>
/// <param name="va">Virtual address to write the data into</param>
/// <param name="value">Data to be written</param>
void WriteGuest<T>(ulong va, T value) where T : unmanaged
{
Write(va, value);
}
/// <summary>
/// Gets a read-only span of data from CPU mapped memory.
/// </summary>

View File

@ -35,18 +35,29 @@ namespace ARMeilleure.Memory
/// Allows invalid access from JIT code to the rest of the program, but is faster.
/// </summary>
HostMappedUnsafe,
/// <summary>
/// High level implementation using a software flat page table for address translation
/// without masking the address and no support for handling invalid or non-contiguous memory access.
/// </summary>
HostTrackedUnsafe,
}
static class MemoryManagerTypeExtensions
public static class MemoryManagerTypeExtensions
{
public static bool IsHostMapped(this MemoryManagerType type)
{
return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
}
public static bool IsHostTracked(this MemoryManagerType type)
{
return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostTrackedUnsafe;
}
public static bool IsHostMappedOrTracked(this MemoryManagerType type)
{
return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
return type.IsHostMapped() || type.IsHostTracked();
}
}
}

View File

@ -7,6 +7,7 @@ namespace ARMeilleure.Memory
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
public IJitMemoryBlock Block { get; }
public IJitMemoryAllocator Allocator { get; }
public IntPtr Pointer => Block.Pointer;
@ -21,6 +22,7 @@ namespace ARMeilleure.Memory
granularity = DefaultGranularity;
}
Allocator = allocator;
Block = allocator.Reserve(maxSize);
_maxSize = maxSize;
_sizeGranularity = granularity;

View File

@ -1,63 +1,14 @@
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Memory;
using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using ARMeilleure.Translation.Cache;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.Signal
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct SignalHandlerRange
public static class NativeSignalHandlerGenerator
{
public int IsActive;
public nuint RangeAddress;
public nuint RangeEndAddress;
public IntPtr ActionPointer;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct SignalHandlerConfig
{
/// <summary>
/// The byte offset of the faulting address in the SigInfo or ExceptionRecord struct.
/// </summary>
public int StructAddressOffset;
/// <summary>
/// The byte offset of the write flag in the SigInfo or ExceptionRecord struct.
/// </summary>
public int StructWriteOffset;
/// <summary>
/// The sigaction handler that was registered before this one. (unix only)
/// </summary>
public nuint UnixOldSigaction;
/// <summary>
/// The type of the previous sigaction. True for the 3 argument variant. (unix only)
/// </summary>
public int UnixOldSigaction3Arg;
public SignalHandlerRange Range0;
public SignalHandlerRange Range1;
public SignalHandlerRange Range2;
public SignalHandlerRange Range3;
public SignalHandlerRange Range4;
public SignalHandlerRange Range5;
public SignalHandlerRange Range6;
public SignalHandlerRange Range7;
}
public static class NativeSignalHandler
{
private delegate void UnixExceptionHandler(int sig, IntPtr info, IntPtr ucontext);
[UnmanagedFunctionPointer(CallingConvention.Winapi)]
private delegate int VectoredExceptionHandler(IntPtr exceptionInfo);
private const int MaxTrackedRanges = 8;
public const int MaxTrackedRanges = 8;
private const int StructAddressOffset = 0;
private const int StructWriteOffset = 4;
@ -70,124 +21,7 @@ namespace ARMeilleure.Signal
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
private static ulong _pageSize;
private static ulong _pageMask;
private static readonly IntPtr _handlerConfig;
private static IntPtr _signalHandlerPtr;
private static IntPtr _signalHandlerHandle;
private static readonly object _lock = new();
private static bool _initialized;
static NativeSignalHandler()
{
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
ref SignalHandlerConfig config = ref GetConfigRef();
config = new SignalHandlerConfig();
}
public static void Initialize(IJitMemoryAllocator allocator)
{
JitCache.Initialize(allocator);
}
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
{
if (_initialized)
{
return;
}
lock (_lock)
{
if (_initialized)
{
return;
}
_pageSize = pageSize;
_pageMask = pageSize - 1;
ref SignalHandlerConfig config = ref GetConfigRef();
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
{
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateUnixSignalHandler(_handlerConfig));
if (customSignalHandlerFactory != null)
{
_signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr);
}
var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
config.UnixOldSigaction = (nuint)(ulong)old.sa_handler;
config.UnixOldSigaction3Arg = old.sa_flags & 4;
}
else
{
config.StructAddressOffset = 40; // ExceptionInformation1
config.StructWriteOffset = 32; // ExceptionInformation0
_signalHandlerPtr = Marshal.GetFunctionPointerForDelegate(GenerateWindowsSignalHandler(_handlerConfig));
if (customSignalHandlerFactory != null)
{
_signalHandlerPtr = customSignalHandlerFactory(IntPtr.Zero, _signalHandlerPtr);
}
_signalHandlerHandle = WindowsSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
}
_initialized = true;
}
}
private static unsafe ref SignalHandlerConfig GetConfigRef()
{
return ref Unsafe.AsRef<SignalHandlerConfig>((void*)_handlerConfig);
}
public static unsafe bool AddTrackedRegion(nuint address, nuint endAddress, IntPtr action)
{
var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0;
for (int i = 0; i < MaxTrackedRanges; i++)
{
if (ranges[i].IsActive == 0)
{
ranges[i].RangeAddress = address;
ranges[i].RangeEndAddress = endAddress;
ranges[i].ActionPointer = action;
ranges[i].IsActive = 1;
return true;
}
}
return false;
}
public static unsafe bool RemoveTrackedRegion(nuint address)
{
var ranges = &((SignalHandlerConfig*)_handlerConfig)->Range0;
for (int i = 0; i < MaxTrackedRanges; i++)
{
if (ranges[i].IsActive == 1 && ranges[i].RangeAddress == address)
{
ranges[i].IsActive = 0;
return true;
}
}
return false;
}
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite)
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize)
{
Operand inRegionLocal = context.AllocateLocal(OperandType.I32);
context.Copy(inRegionLocal, Const(0));
@ -196,7 +30,7 @@ namespace ARMeilleure.Signal
for (int i = 0; i < MaxTrackedRanges; i++)
{
ulong rangeBaseOffset = (ulong)(RangeOffset + i * Unsafe.SizeOf<SignalHandlerRange>());
ulong rangeBaseOffset = (ulong)(RangeOffset + i * rangeStructSize);
Operand nextLabel = Label();
@ -210,13 +44,12 @@ namespace ARMeilleure.Signal
// Is the fault address within this tracked region?
Operand inRange = context.BitwiseAnd(
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)
);
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI));
// Only call tracking if in range.
context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold);
Operand offset = context.BitwiseAnd(context.Subtract(faultAddress, rangeAddress), Const(~_pageMask));
Operand offset = context.Subtract(faultAddress, rangeAddress);
// Call the tracking action, with the pointer's relative offset to the base address.
Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20));
@ -227,8 +60,10 @@ namespace ARMeilleure.Signal
// Tracking action should be non-null to call it, otherwise assume false return.
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite);
context.Copy(inRegionLocal, result);
Operand result = context.Call(trackingActionPtr, OperandType.I64, offset, Const(1UL), isWrite);
context.Copy(inRegionLocal, context.ICompareNotEqual(result, Const(0UL)));
GenerateFaultAddressPatchCode(context, faultAddress, result);
context.MarkLabel(skipActionLabel);
@ -269,8 +104,7 @@ namespace ARMeilleure.Signal
Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(EsrOffset)));
return context.BitwiseAnd(esr, Const(0x40ul));
}
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
{
const ulong ErrOffset = 4; // __es.__err
Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(ErrOffset)));
@ -310,8 +144,7 @@ namespace ARMeilleure.Signal
Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul)));
return context.BitwiseAnd(esr, Const(0x40ul));
}
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
{
const int ErrOffset = 192; // uc_mcontext.gregs[REG_ERR]
Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(ErrOffset)));
@ -322,7 +155,7 @@ namespace ARMeilleure.Signal
throw new PlatformNotSupportedException();
}
private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize)
{
EmitterContext context = new();
@ -335,7 +168,7 @@ namespace ARMeilleure.Signal
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite);
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize);
Operand endLabel = Label();
@ -367,10 +200,10 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 };
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<UnixExceptionHandler>();
return Compiler.Compile(cfg, argTypes, OperandType.None, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code;
}
private static VectoredExceptionHandler GenerateWindowsSignalHandler(IntPtr signalStructPtr)
public static byte[] GenerateWindowsSignalHandler(IntPtr signalStructPtr, int rangeStructSize)
{
EmitterContext context = new();
@ -399,7 +232,7 @@ namespace ARMeilleure.Signal
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite);
Operand isInRegion = EmitGenericRegionCheck(context, signalStructPtr, faultAddress, isWrite, rangeStructSize);
Operand endLabel = Label();
@ -421,7 +254,88 @@ namespace ARMeilleure.Signal
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Map<VectoredExceptionHandler>();
return Compiler.Compile(cfg, argTypes, OperandType.I32, CompilerOptions.HighCq, RuntimeInformation.ProcessArchitecture).Code;
}
private static void GenerateFaultAddressPatchCode(EmitterContext context, Operand faultAddress, Operand newAddress)
{
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
if (SupportsFaultAddressPatchingForHostOs())
{
Operand lblSkip = Label();
context.BranchIf(lblSkip, faultAddress, newAddress, Comparison.Equal);
Operand ucontextPtr = context.LoadArgument(OperandType.I64, 2);
Operand pcCtxAddress = default;
ulong baseRegsOffset = 0;
if (OperatingSystem.IsLinux())
{
pcCtxAddress = context.Add(ucontextPtr, Const(440UL));
baseRegsOffset = 184UL;
}
else if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
{
ucontextPtr = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(48UL)));
pcCtxAddress = context.Add(ucontextPtr, Const(272UL));
baseRegsOffset = 16UL;
}
Operand pc = context.Load(OperandType.I64, pcCtxAddress);
Operand reg = GetAddressRegisterFromArm64Instruction(context, pc);
Operand reg64 = context.ZeroExtend32(OperandType.I64, reg);
Operand regCtxAddress = context.Add(ucontextPtr, context.Add(context.ShiftLeft(reg64, Const(3)), Const(baseRegsOffset)));
Operand regAddress = context.Load(OperandType.I64, regCtxAddress);
Operand addressDelta = context.Subtract(regAddress, faultAddress);
context.Store(regCtxAddress, context.Add(newAddress, addressDelta));
context.MarkLabel(lblSkip);
}
}
}
private static Operand GetAddressRegisterFromArm64Instruction(EmitterContext context, Operand pc)
{
Operand inst = context.Load(OperandType.I32, pc);
Operand reg = context.AllocateLocal(OperandType.I32);
Operand isSysInst = context.ICompareEqual(context.BitwiseAnd(inst, Const(0xFFF80000)), Const(0xD5080000));
Operand lblSys = Label();
Operand lblEnd = Label();
context.BranchIfTrue(lblSys, isSysInst, BasicBlockFrequency.Cold);
context.Copy(reg, context.BitwiseAnd(context.ShiftRightUI(inst, Const(5)), Const(0x1F)));
context.Branch(lblEnd);
context.MarkLabel(lblSys);
context.Copy(reg, context.BitwiseAnd(inst, Const(0x1F)));
context.MarkLabel(lblEnd);
return reg;
}
public static bool SupportsFaultAddressPatchingForHost()
{
return SupportsFaultAddressPatchingForHostArch() && SupportsFaultAddressPatchingForHostOs();
}
private static bool SupportsFaultAddressPatchingForHostArch()
{
return RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
}
private static bool SupportsFaultAddressPatchingForHostOs()
{
return OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS();
}
}
}

View File

@ -2,7 +2,7 @@ using ARMeilleure.IntermediateRepresentation;
using ARMeilleure.Translation;
using Ryujinx.Common.Memory.PartialUnmaps;
using System;
using System.Runtime.InteropServices;
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
namespace ARMeilleure.Signal
@ -10,8 +10,28 @@ namespace ARMeilleure.Signal
/// <summary>
/// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods.
/// </summary>
internal static class WindowsPartialUnmapHandler
internal static partial class WindowsPartialUnmapHandler
{
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")]
private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
private static IntPtr _getCurrentThreadIdPtr;
public static IntPtr GetCurrentThreadIdFunc()
{
if (_getCurrentThreadIdPtr == IntPtr.Zero)
{
IntPtr handle = LoadLibrary("kernel32.dll");
_getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId");
}
return _getCurrentThreadIdPtr;
}
public static Operand EmitRetryFromAccessViolation(EmitterContext context)
{
IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState;
@ -20,7 +40,7 @@ namespace ARMeilleure.Signal
// Get the lock first.
EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
IntPtr getCurrentThreadId = WindowsSignalHandlerRegistration.GetCurrentThreadIdFunc();
IntPtr getCurrentThreadId = GetCurrentThreadIdFunc();
Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32);
Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0));
@ -137,17 +157,6 @@ namespace ARMeilleure.Signal
return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset));
}
#pragma warning disable IDE0051 // Remove unused private member
private static void EmitThreadLocalMapIntRelease(EmitterContext context, IntPtr threadLocalMapPtr, Operand threadId, Operand index)
{
Operand offset = context.Multiply(index, Const(sizeof(int)));
Operand idsPtr = Const((ulong)IntPtr.Add(threadLocalMapPtr, ThreadLocalMap<int>.ThreadIdsOffset));
Operand idPtr = context.Add(idsPtr, context.SignExtend32(OperandType.I64, offset));
context.CompareAndSwap(idPtr, threadId, Const(0));
}
#pragma warning restore IDE0051
private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive)
{
Operand loop = Label();

View File

@ -1,44 +0,0 @@
using System;
using System.Runtime.InteropServices;
namespace ARMeilleure.Signal
{
unsafe partial class WindowsSignalHandlerRegistration
{
[LibraryImport("kernel32.dll")]
private static partial IntPtr AddVectoredExceptionHandler(uint first, IntPtr handler);
[LibraryImport("kernel32.dll")]
private static partial ulong RemoveVectoredExceptionHandler(IntPtr handle);
[LibraryImport("kernel32.dll", SetLastError = true, EntryPoint = "LoadLibraryA")]
private static partial IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)] string lpFileName);
[LibraryImport("kernel32.dll", SetLastError = true)]
private static partial IntPtr GetProcAddress(IntPtr hModule, [MarshalAs(UnmanagedType.LPStr)] string procName);
private static IntPtr _getCurrentThreadIdPtr;
public static IntPtr RegisterExceptionHandler(IntPtr action)
{
return AddVectoredExceptionHandler(1, action);
}
public static bool RemoveExceptionHandler(IntPtr handle)
{
return RemoveVectoredExceptionHandler(handle) != 0;
}
public static IntPtr GetCurrentThreadIdFunc()
{
if (_getCurrentThreadIdPtr == IntPtr.Zero)
{
IntPtr handle = LoadLibrary("kernel32.dll");
_getCurrentThreadIdPtr = GetProcAddress(handle, "GetCurrentThreadId");
}
return _getCurrentThreadIdPtr;
}
}
}

View File

@ -3,6 +3,7 @@ using ARMeilleure.CodeGen.Unwinding;
using ARMeilleure.Memory;
using ARMeilleure.Native;
using Ryujinx.Memory;
using Ryujinx.Common.Logging;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -15,52 +16,73 @@ namespace ARMeilleure.Translation.Cache
static partial class JitCache
{
private static readonly int _pageSize = (int)MemoryBlock.GetPageSize();
private static readonly int _pageMask = _pageSize - 8;
private static readonly int _pageMask = _pageSize - 4;
private const int CodeAlignment = 4; // Bytes.
private const int CacheSize = 2047 * 1024 * 1024;
private const int CacheSizeIOS = 64 * 1024 * 1024;
private const int CacheSize = 128 * 1024 * 1024;
private const int CacheSizeIOS = 128 * 1024 * 1024;
private static ReservedRegion _jitRegion;
private static JitCacheInvalidation _jitCacheInvalidator;
private static CacheMemoryAllocator _cacheAllocator;
private static List<CacheMemoryAllocator> _cacheAllocators = [];
private static readonly List<CacheEntry> _cacheEntries = new();
private static readonly object _lock = new();
private static bool _initialized;
private static readonly List<ReservedRegion> _jitRegions = new();
private static int _activeRegionIndex = 0;
[SupportedOSPlatform("windows")]
[LibraryImport("kernel32.dll", SetLastError = true)]
public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
public static void Initialize(IJitMemoryAllocator allocator)
{
if (_initialized)
{
return;
}
lock (_lock)
{
if (_initialized)
{
return;
if (OperatingSystem.IsWindows())
{
// JitUnwindWindows.RemoveFunctionTableHandler(
// _jitRegions[0].Pointer);
}
for (int i = 0; i < _jitRegions.Count; i++)
{
_jitRegions[i].Dispose();
}
_jitRegions.Clear();
_cacheAllocators.Clear();
}
else
{
_initialized = true;
}
_jitRegion = new ReservedRegion(allocator, (ulong)(OperatingSystem.IsIOS() ? CacheSizeIOS : CacheSize));
_activeRegionIndex = 0;
var firstRegion = new ReservedRegion(allocator, CacheSize);
_jitRegions.Add(firstRegion);
CacheMemoryAllocator firstCacheAllocator = new(CacheSize);
_cacheAllocators.Add(firstCacheAllocator);
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
{
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
}
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
if (OperatingSystem.IsWindows())
{
JitUnwindWindows.InstallFunctionTableHandler(_jitRegion.Pointer, CacheSize, _jitRegion.Pointer + Allocate(_pageSize));
JitUnwindWindows.InstallFunctionTableHandler(
firstRegion.Pointer, CacheSize, firstRegion.Pointer + Allocate(_pageSize)
);
}
_initialized = true;
@ -73,7 +95,9 @@ namespace ARMeilleure.Translation.Cache
{
while (_deferredRxProtect.TryDequeue(out var result))
{
ReprotectAsExecutable(result.funcOffset, result.length);
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
ReprotectAsExecutable(targetRegion, result.funcOffset, result.length);
}
}
@ -87,7 +111,8 @@ namespace ARMeilleure.Translation.Cache
int funcOffset = Allocate(code.Length, deferProtect);
IntPtr funcPtr = _jitRegion.Pointer + funcOffset;
ReservedRegion targetRegion = _jitRegions[_activeRegionIndex];
IntPtr funcPtr = targetRegion.Pointer + funcOffset;
if (OperatingSystem.IsIOS())
{
@ -98,8 +123,7 @@ namespace ARMeilleure.Translation.Cache
}
else
{
ReprotectAsExecutable(funcOffset, code.Length);
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
}
}
@ -115,9 +139,9 @@ namespace ARMeilleure.Translation.Cache
}
else
{
ReprotectAsWritable(funcOffset, code.Length);
ReprotectAsWritable(targetRegion, funcOffset, code.Length);
Marshal.Copy(code, 0, funcPtr, code.Length);
ReprotectAsExecutable(funcOffset, code.Length);
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
{
@ -139,41 +163,50 @@ namespace ARMeilleure.Translation.Cache
{
if (OperatingSystem.IsIOS())
{
return;
// return;
}
lock (_lock)
{
Debug.Assert(_initialized);
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
foreach (var region in _jitRegions)
{
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
if (pointer.ToInt64() < region.Pointer.ToInt64() ||
pointer.ToInt64() >= (region.Pointer + CacheSize).ToInt64())
{
continue;
}
int funcOffset = (int)(pointer.ToInt64() - region.Pointer.ToInt64());
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
{
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
_cacheEntries.RemoveAt(entryIndex);
}
return;
}
}
}
private static void ReprotectAsWritable(int offset, int size)
private static void ReprotectAsWritable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
_jitRegion.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
region.Block.MapAsRwx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static void ReprotectAsExecutable(int offset, int size)
private static void ReprotectAsExecutable(ReservedRegion region, int offset, int size)
{
int endOffs = offset + size;
int regionStart = offset & ~_pageMask;
int regionEnd = (endOffs + _pageMask) & ~_pageMask;
_jitRegion.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
region.Block.MapAsRx((ulong)regionStart, (ulong)(regionEnd - regionStart));
}
private static int Allocate(int codeSize, bool deferProtect = false)
@ -187,20 +220,33 @@ namespace ARMeilleure.Translation.Cache
alignment = 0x4000;
}
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(ref codeSize, alignment);
//DEBUG: Show JIT Memory Allocation
//Console.WriteLine($"{allocOffset:x8}: {codeSize:x8} {alignment:x8}");
if (allocOffset < 0)
if (allocOffset >= 0)
{
throw new OutOfMemoryException("JIT Cache exhausted.");
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
return allocOffset;
}
_jitRegion.ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
int exhaustedRegion = _activeRegionIndex;
var newRegion = new ReservedRegion(_jitRegions[0].Allocator, CacheSize);
_jitRegions.Add(newRegion);
_activeRegionIndex = _jitRegions.Count - 1;
return allocOffset;
int newRegionNumber = _activeRegionIndex;
Logger.Info?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize)} Total Allocation).");
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(ref codeSize, alignment);
if (allocOffsetNew < 0)
{
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
}
newRegion.ExpandIfNeeded((ulong)allocOffsetNew + (ulong)codeSize);
return allocOffsetNew;
}
private static int AlignCodeSize(int codeSize, bool deferProtect = false)

View File

@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
private int[] _postOrderMap;
public int LocalsCount { get; private set; }
public BasicBlock Entry { get; }
public BasicBlock Entry { get; private set; }
public IntrusiveList<BasicBlock> Blocks { get; }
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
public int[] PostOrderMap => _postOrderMap;
@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
return result;
}
public void UpdateEntry(BasicBlock newEntry)
{
newEntry.AddSuccessor(Entry);
Entry = newEntry;
Blocks.AddFirst(newEntry);
Update();
}
public void Update()
{
RemoveUnreachableBlocks(Blocks);

View File

@ -30,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
private const string OuterHeaderMagicString = "PTCohd\0\0";
private const string InnerHeaderMagicString = "PTCihd\0\0";
private const uint InternalVersion = 5518; //! To be incremented manually for each change to the ARMeilleure project.
private const uint InternalVersion = 6950; //! To be incremented manually for each change to the ARMeilleure project.
private const string ActualDir = "0";
private const string BackupDir = "1";
@ -858,8 +858,14 @@ namespace ARMeilleure.Translation.PTC
Stopwatch sw = Stopwatch.StartNew();
threads.ForEach((thread) => thread.Start());
threads.ForEach((thread) => thread.Join());
foreach (var thread in threads)
{
thread.Start();
}
foreach (var thread in threads)
{
thread.Join();
}
threads.Clear();

View File

@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
{
if (cfg.Entry.Predecessors.Count != 0)
{
// We expect the entry block to have no predecessors.
// This is required because we have a implicit context load at the start of the function,
// but if there is a jump to the start of the function, the context load would trash the modified values.
// Here we insert a new entry block that will jump to the existing entry block.
BasicBlock newEntry = new BasicBlock(cfg.Blocks.Count);
cfg.UpdateEntry(newEntry);
}
// Compute local register inputs and outputs used inside blocks.
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
RegisterMask[] localOutputs = new RegisterMask[cfg.Blocks.Count];
@ -201,7 +212,7 @@ namespace ARMeilleure.Translation
// The only block without any predecessor should be the entry block.
// It always needs a context load as it is the first block to run.
if (block.Predecessors.Count == 0 || hasContextLoad)
if (block == cfg.Entry || hasContextLoad)
{
long vecMask = globalInputs[block.Index].VecMask;
long intMask = globalInputs[block.Index].IntMask;

View File

@ -57,9 +57,6 @@ namespace ARMeilleure.Translation
private Thread[] _backgroundTranslationThreads;
private volatile int _threadCount;
// FIXME: Remove this once the init logic of the emulator will be redone.
public static readonly ManualResetEvent IsReadyForTranslation = new(false);
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
{
_allocator = allocator;
@ -79,11 +76,6 @@ namespace ARMeilleure.Translation
Stubs = new TranslatorStubs(FunctionTable);
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
if (memory.Type.IsHostMappedOrTracked())
{
NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize());
}
}
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
@ -105,8 +97,6 @@ namespace ARMeilleure.Translation
{
if (Interlocked.Increment(ref _threadCount) == 1)
{
IsReadyForTranslation.WaitOne();
if (_ptc.State == PtcState.Enabled)
{
Debug.Assert(Functions.Count == 0);

View File

@ -80,7 +80,10 @@ namespace ARMeilleure.Translation
return true;
}
Monitor.Wait(Sync);
if (!_disposed)
{
Monitor.Wait(Sync);
}
}
}

View File

@ -1,11 +1,13 @@
//
// dotnet.xcconfig
// MeloNX.xcconfig
// MeloNX
//
// Created by June P on 12/25/24.
// Created by Stossy11 on 06/03/2025.
//
// Configuration settings file format documentation can be found at:
// https://help.apple.com/xcode/#/dev745c5c974
DOTNET_PATH = $(HOME)/.dotnet/dotnet
VERSION = 1.6.0
DOTNET = /usr/local/share/dotnet/dotnet

View File

@ -25,12 +25,20 @@
/* Begin PBXBuildFile section */
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
4E4854022D138D7600A446A6 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
5650564B2D2A758600C8BB1E /* dotnet.xcconfig.example in Resources */ = {isa = PBXBuildFile; fileRef = 5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */; };
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
4E2953AB2D803BC9000497CD /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
proxyType = 1;
remoteGlobalIDString = BD43C6212D1B248D003BBC42;
remoteInfo = com.Stossy11.MeloNX.RyujinxAg;
};
4E80A99E2CD6F54700029585 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
@ -55,6 +63,16 @@
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
4E50F49E2D5CC28B0080F1D1 /* Embed Watch Content */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "$(CONTENTS_FOLDER_PATH)/Watch";
dstSubfolderSpec = 16;
files = (
);
name = "Embed Watch Content";
runOnlyForDeploymentPostprocessing = 0;
};
4E80AA092CD6FAA800029585 /* Embed Libraries */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@ -68,16 +86,17 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = MeloNX.xcconfig; sourceTree = "<group>"; };
4E7023A52D5A98E2002C7183 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
4E80A98D2CD6F54500029585 /* MeloNX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MeloNX.app; sourceTree = BUILT_PRODUCTS_DIR; };
4E80A99D2CD6F54700029585 /* MeloNXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4E80A9A72CD6F54700029585 /* MeloNXUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MeloNXUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */ = {isa = PBXFileReference; lastKnownFileType = text; path = dotnet.xcconfig.example; sourceTree = "<group>"; };
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = Ryujinx.Headless.SDL2.dylib; path = "MeloNX/Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
565056492D2A756A00C8BB1E /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
CA8F9C2D2D3F5A3A00D7E586 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
@ -87,16 +106,23 @@
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
5650564D2D2A75B300C8BB1E /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
CA0AE31D2D3EECBC00F6D350 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */ = {
isa = PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet;
attributesByRelativePath = {
"Dependencies/Dynamic Libraries/Hypervisor.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib" = (
CodeSignOnCopy,
);
"Dependencies/Dynamic Libraries/SoftwareKeyboard.framework" = (
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework" = (
CodeSignOnCopy,
RemoveHeadersOnCopy,
);
"Dependencies/Dynamic Libraries/libMoltenVK.dylib" = (
CodeSignOnCopy,
);
"Dependencies/Dynamic Libraries/libavcodec.dylib" = (
CodeSignOnCopy,
);
@ -146,10 +172,12 @@
};
buildPhase = 4E80AA092CD6FAA800029585 /* Embed Libraries */;
membershipExceptions = (
"Dependencies/Dynamic Libraries/Hypervisor.framework",
"Dependencies/Dynamic Libraries/libavcodec.dylib",
"Dependencies/Dynamic Libraries/libavutil.dylib",
"Dependencies/Dynamic Libraries/libMoltenVK.dylib",
"Dependencies/Dynamic Libraries/Ryujinx.Headless.SDL2.dylib",
"Dependencies/Dynamic Libraries/SoftwareKeyboard.framework",
"Dependencies/Dynamic Libraries/RyujinxKeyboard.framework",
Dependencies/XCFrameworks/libavcodec.xcframework,
Dependencies/XCFrameworks/libavfilter.xcframework,
Dependencies/XCFrameworks/libavformat.xcframework,
@ -165,7 +193,7 @@
/* End PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
4E80A98F2CD6F54500029585 /* MeloNX */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (565056492D2A756A00C8BB1E /* PBXFileSystemSynchronizedBuildFileExceptionSet */, 5650564D2D2A75B300C8BB1E /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = MeloNX; sourceTree = "<group>"; };
4E80A98F2CD6F54500029585 /* MeloNX */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (CA8F9C2D2D3F5A3A00D7E586 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, CA0AE31D2D3EECBC00F6D350 /* PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = MeloNX; sourceTree = "<group>"; };
4E80A9A02CD6F54700029585 /* MeloNXTests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MeloNXTests; sourceTree = "<group>"; };
4E80A9AA2CD6F54700029585 /* MeloNXUITests */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MeloNXUITests; sourceTree = "<group>"; };
/* End PBXFileSystemSynchronizedRootGroup section */
@ -175,9 +203,10 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
4E4854022D138D7600A446A6 /* GameController.framework in Frameworks */,
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */,
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */,
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */,
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -201,7 +230,7 @@
4E80A9842CD6F54500029585 = {
isa = PBXGroup;
children = (
5650564A2D2A758600C8BB1E /* dotnet.xcconfig.example */,
4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */,
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
4E80A98F2CD6F54500029585 /* MeloNX */,
4E80A9A02CD6F54700029585 /* MeloNXTests */,
@ -224,6 +253,7 @@
4E80AA192CD700F500029585 /* Frameworks */ = {
isa = PBXGroup;
children = (
4E7023A52D5A98E2002C7183 /* UIKit.framework */,
4E80AA622CD7122800029585 /* GameController.framework */,
);
name = Frameworks;
@ -238,7 +268,7 @@
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
buildPhases = (
);
buildToolPath = "$(DOTNET_PATH)";
buildToolPath = "$(DOTNET)";
buildWorkingDirectory = "$(SRCROOT)/../..";
dependencies = (
);
@ -259,10 +289,12 @@
4E80A98A2CD6F54500029585 /* Frameworks */,
4E80A98B2CD6F54500029585 /* Resources */,
4E80AA092CD6FAA800029585 /* Embed Libraries */,
4E50F49E2D5CC28B0080F1D1 /* Embed Watch Content */,
);
buildRules = (
);
dependencies = (
4E2953AC2D803BC9000497CD /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
4E80A98F2CD6F54500029585 /* MeloNX */,
@ -329,8 +361,8 @@
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1610;
LastUpgradeCheck = 1610;
LastSwiftUpdateCheck = 1620;
LastUpgradeCheck = 1620;
TargetAttributes = {
4E80A98C2CD6F54500029585 = {
CreatedOnToolsVersion = 16.1;
@ -383,7 +415,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5650564B2D2A758600C8BB1E /* dotnet.xcconfig.example in Resources */,
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -421,7 +453,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "cd ../..\nmv src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/native/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic\\ Libraries/Ryujinx.Headless.SDL2.dylib\n";
shellScript = "cd ../..\nmv src/Ryujinx.Headless.SDL2/bin/Release/net8.0/ios-arm64/publish/Ryujinx.Headless.SDL2.dylib src/MeloNX/MeloNX/Dependencies/Dynamic\\ Libraries/Ryujinx.Headless.SDL2.dylib\n";
};
/* End PBXShellScriptBuildPhase section */
@ -450,6 +482,11 @@
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
4E2953AC2D803BC9000497CD /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
targetProxy = 4E2953AB2D803BC9000497CD /* PBXContainerItemProxy */;
};
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
@ -470,6 +507,7 @@
/* Begin XCBuildConfiguration section */
4E80A9AF2CD6F54700029585 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@ -539,6 +577,7 @@
};
4E80A9B02CD6F54700029585 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
@ -604,6 +643,7 @@
};
4E80A9B22CD6F54700029585 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@ -622,18 +662,70 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GCC_OPTIMIZATION_LEVEL = z;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MeloNX/Info.plist;
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
INFOPLIST_KEY_GCSupportsGameMode = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "MeloNX needs access to your Photo Library in order to save images";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@ -655,174 +747,103 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 0.0.8;
MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@ -834,6 +855,7 @@
};
4E80A9B32CD6F54700029585 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
@ -852,18 +874,70 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
GCC_OPTIMIZATION_LEVEL = fast;
GCC_OPTIMIZATION_LEVEL = z;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MeloNX/Info.plist;
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
INFOPLIST_KEY_GCSupportsGameMode = YES;
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.games";
INFOPLIST_KEY_LSSupportsOpeningDocumentsInPlace = YES;
INFOPLIST_KEY_NSPhotoLibraryAddUsageDescription = "MeloNX needs access to your Photo Library in order to save images";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UIRequiresFullScreen = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
INFOPLIST_KEY_UISupportsDocumentBrowser = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = (
@ -885,174 +959,103 @@
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/XCFrameworks",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Core/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
);
MARKETING_VERSION = 0.0.8;
MARKETING_VERSION = "$(VERSION)";
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
@ -1064,6 +1067,7 @@
};
4E80A9B52CD6F54700029585 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -1083,6 +1087,7 @@
};
4E80A9B62CD6F54700029585 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
@ -1102,6 +1107,7 @@
};
4E80A9B82CD6F54700029585 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@ -1119,6 +1125,7 @@
};
4E80A9B92CD6F54700029585 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
@ -1136,7 +1143,9 @@
};
BD43C61F2D1B23AB003BBC42 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CODE_SIGN_STYLE = Automatic;
DEBUGGING_SYMBOLS = YES;
DEBUG_INFORMATION_FORMAT = dwarf;
@ -1152,7 +1161,9 @@
};
BD43C6202D1B23AB003BBC42 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CODE_SIGN_STYLE = Automatic;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 95J8WZ4TN8;
@ -1164,6 +1175,7 @@
};
BD43C6232D1B248D003BBC42 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 95J8WZ4TN8;
@ -1173,6 +1185,7 @@
};
BD43C6242D1B248D003BBC42 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = 95J8WZ4TN8;

View File

@ -1,5 +1,5 @@
{
"originHash" : "1b46f7a56d6f994a826e31441c25b929398800cf38b3e9be23ae6e0ef8fc32c7",
"originHash" : "d611b071fbe94fdc9900a07a218340eab4ce2c3c7168bf6542f2830c0400a72b",
"pins" : [
{
"identity" : "swiftsvg",

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1610"
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
@ -64,7 +64,6 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
showGraphicsOverview = "Yes"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
@ -100,5 +99,24 @@
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "REPO_DIR=&quot;$(cd &quot;${SRCROOT}/../../&quot; &amp;&amp; pwd)&quot;&#10;SCRIPT_PATH=&quot;$REPO_DIR/distribution/ios/set_current_version.sh&quot;&#10;&#10;echo &quot;hi&quot;&#10;&#10;sh &quot;${SCRIPT_PATH}&quot;&#10;"
shellToInvoke = "/bin/bash">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "4E80A98C2CD6F54500029585"
BuildableName = "MeloNX.app"
BlueprintName = "MeloNX"
ReferencedContainer = "container:MeloNX.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1620"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Script"
scriptText = "REPO_DIR=&quot;$(cd &quot;${SRCROOT}/../../&quot; &amp;&amp; pwd)&quot;&#10;SCRIPT_PATH=&quot;$REPO_DIR/distribution/ios/get_dotnet.sh&quot;&#10;&#10;echo &quot;Xcode is located at: $DEVELOPER_DIR&quot;&#10;&#10;sh &quot;${SCRIPT_PATH}&quot;&#10;"
shellToInvoke = "/bin/bash">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BD43C61D2D1B23AB003BBC42"
BuildableName = "Ryujinx"
BlueprintName = "Ryujinx"
ReferencedContainer = "container:MeloNX.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BD43C61D2D1B23AB003BBC42"
BuildableName = "Ryujinx"
BlueprintName = "Ryujinx"
ReferencedContainer = "container:MeloNX.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BD43C61D2D1B23AB003BBC42"
BuildableName = "Ryujinx"
BlueprintName = "Ryujinx"
ReferencedContainer = "container:MeloNX.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>MeloNX.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Ryujinx.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>3</integer>
</dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>4</integer>
</dict>
</dict>
</dict>
</plist>

View File

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
uuid = "271EB822-2830-4016-A3D7-CA2DEBEDCD27"
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "499F5405-B63B-4623-9332-1E44FC449FD0"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeloNX/Views/GamesList/GameListView.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "309"
endingLineNumber = "309"
landmarkName = "loadGames()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "0BB7C122-8933-48E8-ABA3-1ABB39594258"
shouldBeEnabled = "No"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "MeloNX/Models/Game.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
landmarkName = "createImage(from:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>MeloNX.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Ryujinx.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>4E80A98C2CD6F54500029585</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>4E80A99C2CD6F54700029585</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>4E80A9A62CD6F54700029585</key>
<dict>
<key>primary</key>
<true/>
</dict>
</dict>
</dict>
</plist>

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>MeloNX.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
<key>Ryujinx.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>2</integer>
</dict>
<key>com.Stossy11.MeloNX.RyujinxAg.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict>
</dict>
</dict>
</plist>

View File

@ -0,0 +1,58 @@
//
// EntitlementChecker.swift
// MeloNX
//
// Created by Stossy11 on 15/02/2025.
//
import Foundation
import Security
typealias SecTaskRef = OpaquePointer
@_silgen_name("SecTaskCopyValueForEntitlement")
func SecTaskCopyValueForEntitlement(
_ task: SecTaskRef,
_ entitlement: NSString,
_ error: NSErrorPointer
) -> CFTypeRef?
@_silgen_name("SecTaskCreateFromSelf")
func SecTaskCreateFromSelf(
_ allocator: CFAllocator?
) -> SecTaskRef?
@_silgen_name("SecTaskCopyValuesForEntitlements")
func SecTaskCopyValuesForEntitlements(
_ task: SecTaskRef,
_ entitlements: CFArray,
_ error: UnsafeMutablePointer<Unmanaged<CFError>?>?
) -> CFDictionary?
func checkAppEntitlements(_ ents: [String]) -> [String: Any] {
guard let task = SecTaskCreateFromSelf(nil) else {
print("Failed to create SecTask")
return [:]
}
guard let entitlements = SecTaskCopyValuesForEntitlements(task, ents as CFArray, nil) else {
print("Failed to get entitlements")
return [:]
}
return (entitlements as? [String: Any]) ?? [:]
}
func checkAppEntitlement(_ ent: String) -> Bool {
guard let task = SecTaskCreateFromSelf(nil) else {
print("Failed to create SecTask")
return false
}
guard let entitlements = SecTaskCopyValueForEntitlement(task, ent as NSString, nil) else {
print("Failed to get entitlements")
return false
}
return entitlements.boolValue != nil && entitlements.boolValue
}

View File

@ -5,11 +5,15 @@
// Created by Stossy11 on 3/11/2024.
//
#define DRM 0
#define CS_DEBUGGED 0x10000000
#ifndef RyujinxHeader
#define RyujinxHeader
#import "SDL2/SDL.h"
#include <SDL2/SDL.h>
#include <SDL2/SDL_syswm.h>
#ifdef __cplusplus
extern "C" {
@ -18,28 +22,47 @@ extern "C" {
struct GameInfo {
long FileSize;
char TitleName[512];
long TitleId;
char TitleId[32];
char Developer[256];
int Version;
unsigned char* ImageData;
char Version[16];
unsigned char* ImageData;
unsigned int ImageSize;
};
struct DlcNcaListItem {
char Path[256];
unsigned long TitleId;
};
struct DlcNcaList {
bool success;
unsigned int size;
struct DlcNcaListItem* items;
};
extern struct GameInfo get_game_info(int, char*);
extern struct DlcNcaList get_dlc_nca_list(const char* titleIdPtr, const char* pathPtr);
void install_firmware(const char* inputPtr);
char* installed_firmware_version();
void set_native_window(void *layerPtr);
void stop_emulation();
void initialize();
int main_ryujinx_sdl(int argc, char **argv);
int get_current_fps();
void initialize();
void touch_began(float x, float y, int index);
const char* get_game_controllers();
void touch_moved(float x, float y, int index);
void touch_ended(int index);
#ifdef __cplusplus
}

View File

@ -0,0 +1,64 @@
//
// IsJITEnabled.swift
// MeloNX
//
// Created by Stossy11 on 10/02/2025.
//
import Foundation
@_silgen_name("csops")
func csops(pid: Int32, ops: Int32, useraddr: UnsafeMutableRawPointer?, usersize: Int32) -> Int32
func isJITEnabled() -> Bool {
var flags: Int = 0
if checkAppEntitlement("dynamic-codesigning") {
return allocateTest()
}
return csops(pid: getpid(), ops: 0, useraddr: &flags, usersize: Int32(MemoryLayout.size(ofValue: flags))) == 0 && (flags & Int(CS_DEBUGGED)) != 0 ? allocateTest() : false
}
func checkMemoryPermissions(at address: UnsafeRawPointer) -> Bool {
var region: vm_address_t = vm_address_t(UInt(bitPattern: address))
var regionSize: vm_size_t = 0
var info = vm_region_basic_info_64()
var infoCount = mach_msg_type_number_t(MemoryLayout<vm_region_basic_info_64>.size / MemoryLayout<integer_t>.size)
var objectName: mach_port_t = UInt32(MACH_PORT_NULL)
let result = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(infoCount)) {
vm_region_64(mach_task_self_, &region, &regionSize, VM_REGION_BASIC_INFO_64, $0, &infoCount, &objectName)
}
}
if result != KERN_SUCCESS {
print("Failed to reach \(address)")
return false
}
return info.protection & VM_PROT_EXECUTE != 0
}
func allocateTest() -> Bool {
let pageSize = sysconf(_SC_PAGESIZE)
let code: [UInt32] = [0x52800540, 0xD65F03C0]
guard let jitMemory = mmap(nil, pageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0), jitMemory != MAP_FAILED else {
return false
}
defer {
munmap(jitMemory, pageSize)
}
memcpy(jitMemory, code, code.count)
if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 {
return false
}
let checkMem = checkMemoryPermissions(at: jitMemory)
return checkMem
}

View File

@ -0,0 +1,80 @@
//
// EnableJIT.swift
// MeloNX
//
// Created by Stossy11 on 10/02/2025.
//
import Foundation
func enableJITEB() {
guard let bundleID = Bundle.main.bundleIdentifier else {
return
}
let address = URL(string: "http://[fd00::]:9172/launch_app/\(bundleID)")!
let task = URLSession.shared.dataTask(with: address) { data, response, error in
if error != nil {
return
}
DispatchQueue.main.async {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let lastWindow = windowScene.windows.last {
showLaunchAppAlert(jsonData: data!, in: lastWindow.rootViewController!)
} else {
fatalError("Unable to get Window")
}
}
return
}
task.resume()
}
struct LaunchApp: Codable {
let ok: Bool
let error: String?
let launching: Bool
let position: Int?
let mounting: Bool
}
func showLaunchAppAlert(jsonData: Data, in viewController: UIViewController) {
do {
let result = try JSONDecoder().decode(LaunchApp.self, from: jsonData)
var message = ""
if let error = result.error {
message = "Error: \(error)"
} else if result.mounting {
message = "App is mounting..."
} else if result.launching {
message = "App is launching..."
} else {
message = "App launch status unknown."
}
if let position = result.position {
message += "\nPosition: \(position)"
}
let alert = UIAlertController(title: "Launch Status", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
DispatchQueue.main.async {
viewController.present(alert, animated: true)
}
} catch {
let alert = UIAlertController(title: "Decoding Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
DispatchQueue.main.async {
viewController.present(alert, animated: true)
}
}
}

View File

@ -1,27 +0,0 @@
#if __has_feature(modules)
@import UIKit;
@import Foundation;
#else
#import "UIKit/UIKit.h"
#import "Foundation/Foundation.h"
#endif
#define DISPATCH_ASYNC_START dispatch_async(dispatch_get_main_queue(), ^{
#define DISPATCH_ASYNC_CLOSE });
#define PT_TRACE_ME 0
extern int ptrace(int, pid_t, caddr_t, int);
#define CS_DEBUGGED 0x10000000
extern int csops(
pid_t pid,
unsigned int ops,
void *useraddr,
size_t usersize
);
extern BOOL getEntitlementValue(NSString *key);
extern BOOL isJITEnabled(void);
#define DLOG(format, ...) ShowAlert(@"DEBUG", [NSString stringWithFormat:@"\n %s [Line %d] \n %@", __PRETTY_FUNCTION__, __LINE__, [NSString stringWithFormat:format, ##__VA_ARGS__]])
void ShowAlert(NSString* title, NSString* message, _Bool* showok);

View File

@ -1,91 +0,0 @@
#import "utils.h"
typedef struct __SecTask * SecTaskRef;
extern CFTypeRef SecTaskCopyValueForEntitlement(
SecTaskRef task,
NSString* entitlement,
CFErrorRef _Nullable *error
)
__attribute__((weak_import));
extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator)
__attribute__((weak_import));
BOOL getEntitlementValue(NSString *key)
{
if (SecTaskCreateFromSelf == NULL || SecTaskCopyValueForEntitlement == NULL)
return NO;
SecTaskRef sec_task = SecTaskCreateFromSelf(NULL);
if(!sec_task) return NO;
CFTypeRef value = SecTaskCopyValueForEntitlement(sec_task, key, nil);
if (value != nil)
{
CFRelease(value);
}
CFRelease(sec_task);
return value != nil && [(__bridge id)value boolValue];
}
BOOL isJITEnabled(void)
{
if (getEntitlementValue(@"dynamic-codesigning"))
{
return YES;
}
int flags;
csops(getpid(), 0, &flags, sizeof(flags));
return (flags & CS_DEBUGGED) != 0;
}
void ShowAlert(NSString* title, NSString* message, _Bool* showok)
{
DISPATCH_ASYNC_START
UIWindow* mainWindow = [[UIApplication sharedApplication] windows].lastObject;
UIAlertController *alert = [UIAlertController alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
if (showok) {
[alert addAction:[UIAlertAction actionWithTitle:@"ok!"
style:UIAlertActionStyleDefault
handler:nil]];
}
[mainWindow.rootViewController presentViewController:alert
animated:true
completion:nil];
DISPATCH_ASYNC_CLOSE
}
#import <UIKit/UIKit.h>
__attribute__((constructor)) static void entry(int argc, char **argv)
{
if (isJITEnabled()) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"JIT"];
[defaults synchronize]; // Ensure the value is saved immediately
} else {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:NO forKey:@"JIT"];
[defaults synchronize]; // Ensure the value is saved immediately
}
if (getEntitlementValue(@"com.apple.developer.kernel.increased-memory-limit")) {
NSLog(@"Entitlement Does Exist");
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"increased-memory-limit"];
[defaults synchronize]; // Ensure the value is saved immediately
}
if (getEntitlementValue(@"com.apple.developer.kernel.increased-debugging-memory-limit")) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"increased-debugging-memory-limit"];
[defaults synchronize]; // Ensure the value is saved immediately
}
if (getEntitlementValue(@"com.apple.developer.kernel.extended-virtual-addressing")) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:YES forKey:@"extended-virtual-addressing"];
[defaults synchronize]; // Ensure the value is saved immediately
}
}

View File

@ -0,0 +1,235 @@
//
// NativeController.swift
// MeloNX
//
// Created by XITRIX on 15/02/2025.
//
import CoreHaptics
import GameController
class NativeController: Hashable {
private var instanceID: SDL_JoystickID = -1
private var controller: OpaquePointer?
private var nativeController: GCController
private let controllerHaptics: CHHapticEngine?
public var controllername: String { "GC - \(nativeController.vendorName ?? "Unknown")" }
init(_ controller: GCController) {
nativeController = controller
controllerHaptics = nativeController.haptics?.createEngine(withLocality: .default)
try? controllerHaptics?.start()
setupHandheldController()
}
deinit {
cleanup()
}
private func setupHandheldController() {
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))
}
var joystickDesc = SDL_VirtualJoystickDesc(
version: UInt16(SDL_VIRTUAL_JOYSTICK_DESC_VERSION),
type: Uint16(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue),
naxes: 6,
nbuttons: 15,
nhats: 1,
vendor_id: 0,
product_id: 0,
padding: 0,
button_mask: 0,
axis_mask: 0,
name: (controllername as NSString).utf8String,
userdata: Unmanaged.passUnretained(self).toOpaque(),
Update: { userdata in
// Update joystick state here
},
SetPlayerIndex: { userdata, playerIndex in
print("Player index set to \(playerIndex)")
},
Rumble: { userdata, lowFreq, highFreq in
print("Rumble with \(lowFreq), \(highFreq)")
guard let userdata else { return 0 }
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq), engine: _self.controllerHaptics)
return 0
},
RumbleTriggers: { userdata, leftRumble, rightRumble in
print("Trigger rumble with \(leftRumble), \(rightRumble)")
return 0
},
SetLED: { userdata, red, green, blue in
print("Set LED to RGB(\(red), \(green), \(blue))")
return 0
},
SendEffect: { userdata, data, size in
print("Effect sent with size \(size)")
return 0
}
)
instanceID = SDL_JoystickAttachVirtualEx(&joystickDesc)// SDL_JoystickAttachVirtual(SDL_JoystickType(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue), 6, 15, 1)
if instanceID < 0 {
print("Failed to create virtual joystick: \(String(cString: SDL_GetError()))")
return
}
controller = SDL_GameControllerOpen(Int32(instanceID))
if controller == nil {
print("Failed to create virtual controller: \(String(cString: SDL_GetError()))")
return
}
if #available(iOS 16, *) {
guard let gamepad = nativeController.extendedGamepad
else { return }
setupButtonChangeListener(gamepad.buttonA, for: .A)
setupButtonChangeListener(gamepad.buttonB, for: .B)
setupButtonChangeListener(gamepad.buttonX, for: .X)
setupButtonChangeListener(gamepad.buttonY, for: .Y)
setupButtonChangeListener(gamepad.dpad.up, for: .dPadUp)
setupButtonChangeListener(gamepad.dpad.down, for: .dPadDown)
setupButtonChangeListener(gamepad.dpad.left, for: .dPadLeft)
setupButtonChangeListener(gamepad.dpad.right, for: .dPadRight)
setupButtonChangeListener(gamepad.leftShoulder, for: .leftShoulder)
setupButtonChangeListener(gamepad.rightShoulder, for: .rightShoulder)
gamepad.leftThumbstickButton.map { setupButtonChangeListener($0, for: .leftStick) }
gamepad.rightThumbstickButton.map { setupButtonChangeListener($0, for: .rightStick) }
setupButtonChangeListener(gamepad.buttonMenu, for: .start)
gamepad.buttonOptions.map { setupButtonChangeListener($0, for: .back) }
setupStickChangeListener(gamepad.leftThumbstick, for: .left)
setupStickChangeListener(gamepad.rightThumbstick, for: .right)
setupTriggerChangeListener(gamepad.leftTrigger, for: .left)
setupTriggerChangeListener(gamepad.rightTrigger, for: .right)
}
}
func setupButtonChangeListener(_ button: GCControllerButtonInput, for key: VirtualControllerButton) {
button.valueChangedHandler = { [unowned self] _, _, pressed in
setButtonState(pressed ? 1 : 0, for: key)
}
}
func setupStickChangeListener(_ button: GCControllerDirectionPad, for key: ThumbstickType) {
button.valueChangedHandler = { [unowned self] _, xValue, yValue in
let scaledX = Sint16(xValue * 32767.0)
let scaledY = -Sint16(yValue * 32767.0)
switch key {
case .left:
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTX.rawValue))
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTY.rawValue))
case .right:
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTY.rawValue))
}
}
}
func setupTriggerChangeListener(_ button: GCControllerButtonInput, for key: ThumbstickType) {
button.valueChangedHandler = { [unowned self] _, value, pressed in
// print("Value: \(value), Is pressed: \(pressed)")
let axis: SDL_GameControllerAxis = (key == .left) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
let scaledValue = Sint16(value * 32767.0)
updateAxisValue(value: scaledValue, forAxis: axis)
}
}
static func rumble(lowFreq: Float, highFreq: Float) {
do {
// Low-frequency haptic pattern
let lowFreqPattern = try CHHapticPattern(events: [
CHHapticEvent(eventType: .hapticTransient, parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: lowFreq),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5)
], relativeTime: 0, duration: 0.2)
], parameters: [])
// High-frequency haptic pattern
let highFreqPattern = try CHHapticPattern(events: [
CHHapticEvent(eventType: .hapticTransient, parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: highFreq),
CHHapticEventParameter(parameterID: .hapticSharpness, value: 1.0)
], relativeTime: 0.2, duration: 0.2)
], parameters: [])
// Create and start the haptic engine
let engine = try CHHapticEngine()
try engine.start()
// Create and play the low-frequency player
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
try lowFreqPlayer.start(atTime: 0)
// Create and play the high-frequency player after a short delay
let highFreqPlayer = try engine.makePlayer(with: highFreqPattern)
try highFreqPlayer.start(atTime: 0.2)
} catch {
print("Error creating haptic patterns: \(error)")
}
}
func updateAxisValue(value: Sint16, forAxis axis: SDL_GameControllerAxis) {
guard controller != nil else { return }
let joystick = SDL_JoystickFromInstanceID(instanceID)
SDL_JoystickSetVirtualAxis(joystick, axis.rawValue, value)
}
func thumbstickMoved(_ stick: ThumbstickType, x: Double, y: Double) {
let scaleFactor = 32767.0 / 160
let scaledX = Int16(min(32767.0, max(-32768.0, x * scaleFactor)))
let scaledY = Int16(min(32767.0, max(-32768.0, y * scaleFactor)))
if stick == .right {
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTX.rawValue))
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_RIGHTY.rawValue))
} else { // ThumbstickType.left
updateAxisValue(value: scaledX, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTX.rawValue))
updateAxisValue(value: scaledY, forAxis: SDL_GameControllerAxis(SDL_CONTROLLER_AXIS_LEFTY.rawValue))
}
}
func setButtonState(_ state: Uint8, for button: VirtualControllerButton) {
guard controller != nil else { return }
// print("Button: \(button.rawValue) {state: \(state)}")
if (button == .leftTrigger || button == .rightTrigger) && (state == 1 || state == 0) {
let axis: SDL_GameControllerAxis = (button == .leftTrigger) ? SDL_CONTROLLER_AXIS_TRIGGERLEFT : SDL_CONTROLLER_AXIS_TRIGGERRIGHT
let value: Int = (state == 1) ? 32767 : 0
updateAxisValue(value: Sint16(value), forAxis: axis)
} else {
let joystick = SDL_JoystickFromInstanceID(instanceID)
SDL_JoystickSetVirtualButton(joystick, Int32(button.rawValue), state)
}
}
func cleanup() {
if let controller {
SDL_JoystickDetachVirtual(instanceID)
SDL_GameControllerClose(controller)
self.controller = nil
}
}
func hash(into hasher: inout Hasher) {
hasher.combine(nativeController)
}
static func == (lhs: NativeController, rhs: NativeController) -> Bool {
lhs.nativeController == rhs.nativeController
}
}

View File

@ -20,12 +20,10 @@ class VirtualController {
}
private func setupVirtualController() {
// Initialize SDL if not already initialized
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))
}
// Create virtual controller
var joystickDesc = SDL_VirtualJoystickDesc(
version: UInt16(SDL_VIRTUAL_JOYSTICK_DESC_VERSION),
type: Uint16(SDL_JOYSTICK_TYPE_GAMECONTROLLER.rawValue),
@ -47,7 +45,9 @@ class VirtualController {
},
Rumble: { userdata, lowFreq, highFreq in
print("Rumble with \(lowFreq), \(highFreq)")
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
if UIDevice.current.userInterfaceIdiom == .phone {
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
}
return 0
},
RumbleTriggers: { userdata, leftRumble, rightRumble in
@ -70,8 +70,6 @@ class VirtualController {
return
}
// Open a game controller for the virtual joystick
let joystick = SDL_JoystickFromInstanceID(instanceID)
controller = SDL_GameControllerOpen(Int32(instanceID))
if controller == nil {
@ -80,9 +78,8 @@ class VirtualController {
}
}
static func rumble(lowFreq: Float, highFreq: Float) {
static func rumble(lowFreq: Float, highFreq: Float, engine: CHHapticEngine? = nil) {
do {
// Low-frequency haptic pattern
let lowFreqPattern = try CHHapticPattern(events: [
CHHapticEvent(eventType: .hapticTransient, parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: lowFreq),
@ -90,7 +87,7 @@ class VirtualController {
], relativeTime: 0, duration: 0.2)
], parameters: [])
// High-frequency haptic pattern
let highFreqPattern = try CHHapticPattern(events: [
CHHapticEvent(eventType: .hapticTransient, parameters: [
CHHapticEventParameter(parameterID: .hapticIntensity, value: highFreq),
@ -98,23 +95,34 @@ class VirtualController {
], relativeTime: 0.2, duration: 0.2)
], parameters: [])
// Create and start the haptic engine
let engine = try CHHapticEngine()
try engine.start()
var engine = engine
if engine == nil {
if hapticEngine == nil {
hapticEngine = try CHHapticEngine()
try hapticEngine?.start()
}
engine = hapticEngine
}
guard let engine else {
return print("Error creating haptic patterns: hapticEngine is nil")
}
// Create and play the low-frequency player
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
try lowFreqPlayer.start(atTime: 0)
// Create and play the high-frequency player after a short delay
let highFreqPlayer = try engine.makePlayer(with: highFreqPattern)
try highFreqPlayer.start(atTime: 0.2)
try highFreqPlayer.start(atTime: 0)
} catch {
print("Error creating haptic patterns: \(error)")
}
}
private static var hapticEngine: CHHapticEngine?
func updateAxisValue(value: Sint16, forAxis axis: SDL_GameControllerAxis) {
guard controller != nil else { return }
@ -164,10 +172,10 @@ class VirtualController {
}
enum VirtualControllerButton: Int {
case B
case A
case Y
case B
case X
case Y
case back
case guide
case start

View File

@ -1,80 +0,0 @@
//
// VirtualController.swift
// MeloNX
//
// Created by Stossy11 on 28/11/2024.
//
import Foundation
import GameController
import UIKit
import SwiftUI
func waitforcontroller() {
if let window = theWindow {
// Function to recursively search for GCControllerView
func findGCControllerView(in view: UIView) -> UIView? {
// Check if current view is GCControllerView
if String(describing: type(of: view)) == "ControllerView" {
return view
}
// Search through subviews
for subview in view.subviews {
if let found = findGCControllerView(in: subview) {
return found
}
}
return nil
}
let controllerView = ControllerView()
let controllerHostingController = UIHostingController(rootView: controllerView)
let containerView = TransparentHostingContainerView(frame: window.bounds)
containerView.backgroundColor = .clear
controllerHostingController.view.frame = containerView.bounds
controllerHostingController.view.backgroundColor = .clear
containerView.addSubview(controllerHostingController.view)
class LandscapeViewController: UIViewController {
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .landscape
}
override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation {
return .landscapeLeft
}
}
let landscapeVC = LandscapeViewController()
landscapeVC.modalPresentationStyle = .fullScreen
window.rootViewController?.present(landscapeVC, animated: false, completion: nil)
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
if findGCControllerView(in: window) == nil {
window.addSubview(containerView)
window.bringSubviewToFront(containerView)
timer.invalidate()
} else {
timer.invalidate()
}
}
}
}
class TransparentHostingContainerView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// Check if the point is within the subviews of this container
let view = super.hitTest(point, with: event)
print(view)
// Return nil if the touch is outside visible content (passes through to views below)
return view === self ? nil : view
}
}

View File

@ -1,43 +0,0 @@
//
// Untitled.swift
// MeloNX
//
// Created by Stossy11 on 28/11/2024.
//
import Foundation
import GameController
import UIKit
var theWindow: UIWindow? = nil
extension UIWindow {
@objc func wdb_makeKeyAndVisible() {
if #available(iOS 13.0, *) {
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
}
self.wdb_makeKeyAndVisible()
theWindow = self
if UserDefaults.standard.bool(forKey: "isVirtualController") {
if let window = theWindow {
waitforcontroller()
}
}
}
}
func patchMakeKeyAndVisible() {
let uiwindowClass = UIWindow.self
if let m1 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.makeKeyAndVisible)),
let m2 = class_getInstanceMethod(uiwindowClass, #selector(UIWindow.wdb_makeKeyAndVisible)) {
method_exchangeImplementations(m1, m2)
}
}

View File

@ -0,0 +1,18 @@
//
// Screenshot.swift
// MeloNX
//
// Created by Stossy11 on 09/02/2025.
//
import UIKit
extension UIView {
func screenshot() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
defer { UIGraphicsEndImageContext() }
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
return UIGraphicsGetImageFromCurrentImageContext()
}
}

View File

@ -0,0 +1,28 @@
//
// AspectRatio.swift
// MeloNX
//
// Created by Stossy11 on 16/02/2025.
//
import Foundation
public enum AspectRatio: String, Codable, CaseIterable {
case fixed4x3 = "Fixed4x3"
case fixed16x9 = "Fixed16x9"
case fixed16x10 = "Fixed16x10"
case fixed21x9 = "Fixed21x9"
case fixed32x9 = "Fixed32x9"
case stretched = "Stretched"
var displayName: String {
switch self {
case .fixed4x3: return "4:3"
case .fixed16x9: return "16:9 (Default)"
case .fixed16x10: return "16:10"
case .fixed21x9: return "21:9"
case .fixed32x9: return "32:9"
case .stretched: return "Stretched (Full Screen)"
}
}
}

View File

@ -0,0 +1,52 @@
//
// Language.swift
// MeloNX
//
// Created by Stossy11 on 16/02/2025.
//
import Foundation
public enum SystemLanguage: String, Codable, CaseIterable {
case japanese = "Japanese"
case americanEnglish = "AmericanEnglish"
case french = "French"
case german = "German"
case italian = "Italian"
case spanish = "Spanish"
case chinese = "Chinese"
case korean = "Korean"
case dutch = "Dutch"
case portuguese = "Portuguese"
case russian = "Russian"
case taiwanese = "Taiwanese"
case britishEnglish = "BritishEnglish"
case canadianFrench = "CanadianFrench"
case latinAmericanSpanish = "LatinAmericanSpanish"
case simplifiedChinese = "SimplifiedChinese"
case traditionalChinese = "TraditionalChinese"
case brazilianPortuguese = "BrazilianPortuguese"
var displayName: String {
switch self {
case .japanese: return "Japanese"
case .americanEnglish: return "American English"
case .french: return "French"
case .german: return "German"
case .italian: return "Italian"
case .spanish: return "Spanish"
case .chinese: return "Chinese"
case .korean: return "Korean"
case .dutch: return "Dutch"
case .portuguese: return "Portuguese"
case .russian: return "Russian"
case .taiwanese: return "Taiwanese"
case .britishEnglish: return "British English"
case .canadianFrench: return "Canadian French"
case .latinAmericanSpanish: return "Latin American Spanish"
case .simplifiedChinese: return "Simplified Chinese"
case .traditionalChinese: return "Traditional Chinese"
case .brazilianPortuguese: return "Brazilian Portuguese"
}
}
}

View File

@ -0,0 +1,31 @@
//
// Region.swift
// MeloNX
//
// Created by Stossy11 on 16/02/2025.
//
import Foundation
public enum SystemRegionCode: String, Codable, CaseIterable {
case japan = "Japan"
case usa = "USA"
case europe = "Europe"
case australia = "Australia"
case china = "China"
case korea = "Korea"
case taiwan = "Taiwan"
var displayName: String {
switch self {
case .japan: return "Japan"
case .usa: return "United States"
case .europe: return "Europe"
case .australia: return "Australia"
case .china: return "China"
case .korea: return "Korea"
case .taiwan: return "Taiwan"
}
}
}

View File

@ -10,9 +10,7 @@ import Foundation
class MTLHud {
var canMetalHud: Bool {
return openMetalDylib()
}
@Published var canMetalHud: Bool = false
var isEnabled: Bool {
if let getenv = getenv("MTL_HUD_ENABLED") {
@ -24,7 +22,17 @@ class MTLHud {
static let shared = MTLHud()
private init() {
openMetalDylib()
let _ = openMetalDylib() // i'm fixing the warnings just because you said i suck at coding Autumn (propenchiefer,
https://youtu.be/tc65SNOTMz4 7:23)
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
enable()
} else {
disable()
}
}
func toggle() {
print(UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED"))
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
enable()
} else {
@ -35,16 +43,15 @@ class MTLHud {
func openMetalDylib() -> Bool {
let path = "/usr/lib/libMTLHud.dylib"
// Load the dynamic library
if dlopen(path, RTLD_NOW) != nil {
// Library loaded successfully
print("Library loaded from \(path)")
canMetalHud = true
return true
} else {
// Handle error
if let error = String(validatingUTF8: dlerror()) {
print("Error loading library: \(error)")
}
canMetalHud = false
return false
}
}

View File

@ -8,6 +8,8 @@
import Foundation
import SwiftUI
import GameController
import MetalKit
import Metal
struct Controller: Identifiable, Hashable {
var id: String
@ -28,17 +30,30 @@ struct iOSNav<Content: View>: View {
}
}
class Ryujinx {
private var isRunning = false
let virtualController = VirtualController()
@Published var controllerMap: [Controller] = []
@State var firmwareversion = "0"
@Published var metalLayer: CAMetalLayer? = nil
@Published var firmwareversion = "0"
@Published var emulationUIView: MeloMTKView? = nil
@Published var config: Ryujinx.Configuration? = nil
@Published var games: [Game] = []
@Published var defMLContentSize: CGFloat?
var shouldMetal: Bool {
metalLayer == nil
}
static let shared = Ryujinx()
private init() {}
private init() {
self.games = loadGames()
}
public struct Configuration : Codable, Equatable {
var gamepath: String
@ -49,12 +64,23 @@ class Ryujinx {
var nintendoinput: Bool
var enableInternet: Bool
var listinputids: Bool
var fullscreen: Bool
var aspectRatio: AspectRatio
var memoryManagerMode: String
var disableShaderCache: Bool
var hypervisor: Bool
var disableDockedMode: Bool
var enableTextureRecompression: Bool
var additionalArgs: [String]
var maxAnisotropy: Float
var macroHLE: Bool
var ignoreMissingServices: Bool
var expandRam: Bool
var dfsIntegrityChecks: Bool
var disablePTC: Bool
var disablevsync: Bool
var language: SystemLanguage
var regioncode: SystemRegionCode
var handHeldController: Bool
init(gamepath: String,
@ -62,22 +88,33 @@ class Ryujinx {
debuglogs: Bool = false,
tracelogs: Bool = false,
listinputids: Bool = false,
fullscreen: Bool = true,
memoryManagerMode: String = "HostMapped",
aspectRatio: AspectRatio = .fixed16x9,
memoryManagerMode: String = "HostMappedUnsafe",
disableShaderCache: Bool = false,
disableDockedMode: Bool = false,
nintendoinput: Bool = true,
enableInternet: Bool = false,
enableTextureRecompression: Bool = true,
additionalArgs: [String] = [],
resscale: Float = 1.00
resscale: Float = 1.00,
maxAnisotropy: Float = 0,
macroHLE: Bool = false,
ignoreMissingServices: Bool = false,
hypervisor: Bool = false,
expandRam: Bool = false,
dfsIntegrityChecks: Bool = false,
disablePTC: Bool = false,
disablevsync: Bool = false,
language: SystemLanguage = .americanEnglish,
regioncode: SystemRegionCode = .usa,
handHeldController: Bool = false
) {
self.gamepath = gamepath
self.inputids = inputids
self.debuglogs = debuglogs
self.tracelogs = tracelogs
self.listinputids = listinputids
self.fullscreen = fullscreen
self.aspectRatio = aspectRatio
self.disableShaderCache = disableShaderCache
self.disableDockedMode = disableDockedMode
self.enableTextureRecompression = enableTextureRecompression
@ -86,6 +123,17 @@ class Ryujinx {
self.resscale = resscale
self.nintendoinput = nintendoinput
self.enableInternet = enableInternet
self.maxAnisotropy = maxAnisotropy
self.macroHLE = macroHLE
self.expandRam = expandRam
self.ignoreMissingServices = ignoreMissingServices
self.hypervisor = hypervisor
self.dfsIntegrityChecks = dfsIntegrityChecks
self.disablePTC = disablePTC
self.disablevsync = disablevsync
self.language = language
self.regioncode = regioncode
self.handHeldController = handHeldController
}
}
@ -95,14 +143,17 @@ class Ryujinx {
throw RyujinxError.alreadyRunning
}
isRunning = true
self.config = config
RunLoop.current.perform {
let url = URL(string: config.gamepath)!
RunLoop.current.perform { [self] in
isRunning = true
let url = URL(string: config.gamepath)
do {
let args = self.buildCommandLineArgs(from: config)
let accessing = url.startAccessingSecurityScopedResource()
let accessing = url?.startAccessingSecurityScopedResource()
// Convert Arguments to ones that Ryujinx can Read
let cArgs = args.map { strdup($0) }
@ -110,15 +161,17 @@ class Ryujinx {
var argvPtrs = cArgs
// Start the emulation
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
if result != 0 {
self.isRunning = false
if accessing {
url.stopAccessingSecurityScopedResource()
}
if isRunning {
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
throw RyujinxError.executionError(code: result)
if result != 0 {
self.isRunning = false
if let accessing, accessing {
url!.stopAccessingSecurityScopedResource()
}
throw RyujinxError.executionError(code: result)
}
}
} catch {
self.isRunning = false
@ -134,11 +187,64 @@ class Ryujinx {
}
isRunning = false
self.emulationUIView = nil
self.metalLayer = nil
stop_emulation()
}
var running: Bool {
return isRunning
}
func loadGames() -> [Game] {
let fileManager = FileManager.default
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return [] }
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
if (!fileManager.fileExists(atPath: romsDirectory.path)) {
do {
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
} catch {
print("Failed to create roms directory: \(error)")
}
}
var games: [Game] = []
do {
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
for fileURLCandidate in files {
if fileURLCandidate.pathExtension == "zip" {
continue
}
do {
let handle = try FileHandle(forReadingFrom: fileURLCandidate)
let fileExtension = (fileURLCandidate.pathExtension as NSString).utf8String
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
let gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: fileURLCandidate)
games.append(game)
} catch {
print(error)
}
}
return games
} catch {
print("Error loading games from roms folder: \(error)")
return games
}
}
private func buildCommandLineArgs(from config: Configuration) -> [String] {
var args: [String] = []
@ -158,23 +264,55 @@ class Ryujinx {
// We don't need this. Ryujinx should handle it fine :3
// this also causes crashes in some games :3
if config.fullscreen {
args.append(contentsOf: ["--aspect-ratio", "Stretched"])
}
args.append(contentsOf: ["--system-language", config.language.rawValue])
args.append(contentsOf: ["--system-region", config.regioncode.rawValue])
args.append(contentsOf: ["--aspect-ratio", config.aspectRatio.rawValue])
if config.nintendoinput {
args.append("--correct-controller")
}
if config.disablePTC {
args.append("--disable-ptc")
}
// args.append("--disable-vsync")
if config.disablevsync {
args.append("--disable-vsync")
}
if config.hypervisor {
args.append("--use-hypervisor")
}
if config.dfsIntegrityChecks {
args.append("--disable-fs-integrity-checks")
}
if config.resscale != 1.0 {
args.append(contentsOf: ["--resolution-scale", String(config.resscale)])
}
if config.expandRam {
args.append(contentsOf: ["--expand-ram", String(config.expandRam)])
}
if config.ignoreMissingServices {
// args.append(contentsOf: ["--ignore-missing-services"])
args.append("--ignore-missing-services")
}
if config.maxAnisotropy != 0 {
args.append(contentsOf: ["--max-anisotropy", String(config.maxAnisotropy)])
}
if !config.macroHLE {
args.append("--disable-macro-hle")
}
if !config.disableShaderCache { // same with disableShaderCache
args.append("--disable-shader-cache")
}
@ -187,43 +325,48 @@ class Ryujinx {
}
if config.debuglogs {
args.append(contentsOf: ["--enable-debug-logs"])
args.append("--enable-debug-logs")
}
if config.tracelogs {
args.append(contentsOf: ["--enable-trace-logs"])
args.append("--enable-trace-logs")
}
// List the input ids
if config.listinputids {
args.append(contentsOf: ["--list-inputs-ids"])
args.append("--list-inputs-ids")
}
// Append the input ids (limit to 4 just in case)
// Append the input ids (limit to 8 (used to be 4) just in case)
if !config.inputids.isEmpty {
config.inputids.prefix(4).enumerated().forEach { index, inputId in
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
config.inputids.prefix(8).enumerated().forEach { index, inputId in
if config.handHeldController {
args.append(contentsOf: ["\(index == 0 ? "--input-id-handheld" : "--input-id-\(index + 1)")", inputId])
} else {
args.append(contentsOf: ["--input-id-\(index + 1)", inputId])
}
}
}
// Apped any additional arguments
args.append(contentsOf: config.additionalArgs)
return args
}
func checkIfKeysImported() -> Bool {
let keysDirectory = URL.documentsDirectory.appendingPathComponent("system")
let keysFile = keysDirectory.appendingPathComponent("prod.keys")
return FileManager.default.fileExists(atPath: keysFile.path)
}
func fetchFirmwareVersion() -> String {
do {
let firmwareVersionPointer = installed_firmware_version()
if let pointer = firmwareVersionPointer {
let firmwareVersion = String(cString: pointer)
DispatchQueue.main.async {
self.firmwareversion = firmwareVersion
}
return firmwareVersion
let firmwareVersionPointer = installed_firmware_version()
if let pointer = firmwareVersionPointer {
let firmwareVersion = String(cString: pointer)
DispatchQueue.main.async {
self.firmwareversion = firmwareVersion
}
} catch {
print(error)
return firmwareVersion
}
return "0"
@ -242,36 +385,82 @@ class Ryujinx {
self.firmwareversion = version
}
}
func getConnectedControllers() -> [Controller] {
guard let jsonPtr = get_game_controllers() else {
func getDlcNcaList(titleId: String, path: String) -> [DownloadableContentNca] {
guard let titleIdCString = titleId.cString(using: .utf8),
let pathCString = path.cString(using: .utf8)
else {
print("Invalid path")
return []
}
// Convert the unmanaged memory (C string) to a Swift String
let jsonString = String(cString: jsonPtr)
var controllers: [Controller] = []
// Splitting the string by newline
let lines = jsonString.components(separatedBy: "\n")
// Parsing each line
for line in lines {
if line.contains(":") {
let parts = line.components(separatedBy: ":")
if parts.count == 2 {
let id = parts[0].trimmingCharacters(in: .whitespacesAndNewlines)
let name = parts[1].trimmingCharacters(in: .whitespacesAndNewlines)
controllers.append(Controller(id: id, name: name))
}
}
let listPointer = get_dlc_nca_list(titleIdCString, pathCString)
print("DLC parcing success: \(listPointer.success)")
guard listPointer.success else { return [] }
let list = Array(UnsafeBufferPointer(start: listPointer.items, count: Int(listPointer.size)))
return list.map { item in
.init(fullPath: withUnsafePointer(to: item.Path) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}, titleId: item.TitleId, enabled: true)
}
}
private func generateGamepadId(joystickIndex: Int32) -> String? {
let guid = SDL_JoystickGetDeviceGUID(joystickIndex)
if guid.data.0 == 0 && guid.data.1 == 0 && guid.data.2 == 0 && guid.data.3 == 0 {
return nil
}
let reorderedGUID: [UInt8] = [
guid.data.3, guid.data.2, guid.data.1, guid.data.0,
guid.data.5, guid.data.4,
guid.data.7, guid.data.6,
guid.data.8, guid.data.9,
guid.data.10, guid.data.11, guid.data.12, guid.data.13, guid.data.14, guid.data.15
]
let guidString = reorderedGUID.map { String(format: "%02X", $0) }.joined().lowercased()
func substring(_ str: String, _ start: Int, _ end: Int) -> String {
let startIdx = str.index(str.startIndex, offsetBy: start)
let endIdx = str.index(str.startIndex, offsetBy: end)
return String(str[startIdx..<endIdx])
}
let formattedGUID = "\(substring(guidString, 0, 8))-\(substring(guidString, 8, 12))-\(substring(guidString, 12, 16))-\(substring(guidString, 16, 20))-\(substring(guidString, 20, 32))"
return "\(joystickIndex)-\(formattedGUID)"
}
func getConnectedControllers() -> [Controller] {
var controllers: [Controller] = []
let numJoysticks = SDL_NumJoysticks()
for i in 0..<numJoysticks {
if let controller = SDL_GameControllerOpen(i) {
let guid = generateGamepadId(joystickIndex: i)
let name = String(cString: SDL_GameControllerName(controller))
print("Controller \(i): \(name), GUID: \(guid ?? "")")
guard let guid else {
SDL_GameControllerClose(controller)
return []
}
controllers.append(Controller(id: guid, name: name))
SDL_GameControllerClose(controller)
}
}
return controllers
}
func removeFirmware() {
@ -305,7 +494,7 @@ class Ryujinx {
print("Error removing folder: \(error)")
}
}
static func log(_ message: String) {
@ -314,4 +503,3 @@ class Ryujinx {
}

View File

@ -0,0 +1,86 @@
//
// LaunchGameIntentDef.swift
// MeloNX
//
// Created by Stossy11 on 10/02/2025.
//
import Foundation
import SwiftUI
import Intents
import AppIntents
@available(iOS 16.0, *)
struct LaunchGameIntentDef: AppIntent {
static let title: LocalizedStringResource = "Launch Game"
static var description = IntentDescription("Launches the Selected Game.")
@Parameter(title: "Game", optionsProvider: GameOptionsProvider())
var gameName: String
static var parameterSummary: some ParameterSummary {
Summary("Launch \(\.$gameName)")
}
static var openAppWhenRun: Bool = true
@MainActor
func perform() async throws -> some IntentResult {
let ryujinx = Ryujinx.shared.games
let name = findClosestGameName(input: gameName, games: ryujinx.compactMap(\.titleName))
let urlString = "melonx://game?name=\(name ?? gameName)"
print(urlString)
if let url = URL(string: urlString) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
return .result()
}
func levenshteinDistance(_ a: String, _ b: String) -> Int {
let aCount = a.count
let bCount = b.count
var matrix = [[Int]](repeating: [Int](repeating: 0, count: bCount + 1), count: aCount + 1)
for i in 0...aCount {
matrix[i][0] = i
}
for j in 0...bCount {
matrix[0][j] = j
}
for i in 1...aCount {
for j in 1...bCount {
let cost = a[a.index(a.startIndex, offsetBy: i - 1)] == b[b.index(b.startIndex, offsetBy: j - 1)] ? 0 : 1
matrix[i][j] = min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + cost)
}
}
return matrix[aCount][bCount]
}
func findClosestGameName(input: String, games: [String]) -> String? {
let closestGame = games.min { a, b in
let distanceA = levenshteinDistance(input, a)
let distanceB = levenshteinDistance(input, b)
return distanceA < distanceB
}
return closestGame
}
}
@available(iOS 16.0, *)
struct GameOptionsProvider: DynamicOptionsProvider {
func results() async throws -> [String] {
let dynamicGames = Ryujinx.shared.loadGames()
return dynamicGames.map { $0.titleName }
}
}

View File

@ -0,0 +1,77 @@
//
// GameInfo.swift
// MeloNX
//
// Created by Stossy11 on 9/12/2024.
//
import SwiftUI
import UniformTypeIdentifiers
public struct Game: Identifiable, Equatable, Hashable {
public var id: URL { fileURL }
var containerFolder: URL
var fileType: UTType
var fileURL: URL
var titleName: String
var titleId: String
var developer: String
var version: String
var icon: UIImage?
static func convertGameInfoToGame(gameInfo: GameInfo, url: URL) -> Game {
var gameInfo = gameInfo
var gameTemp = Game(containerFolder: url.deletingLastPathComponent(), fileType: .item, fileURL: url, titleName: "", titleId: "", developer: "", version: "")
gameTemp.titleName = withUnsafePointer(to: &gameInfo.TitleName) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
gameTemp.developer = withUnsafePointer(to: &gameInfo.Developer) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
gameTemp.titleId = withUnsafePointer(to: &gameInfo.TitleId) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
gameTemp.version = withUnsafePointer(to: &gameInfo.Version) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
let imageSize = Int(gameInfo.ImageSize)
if imageSize > 0, imageSize <= 1024 * 1024 {
let imageData = Data(bytes: gameInfo.ImageData, count: imageSize)
gameTemp.icon = UIImage(data: imageData)
} else {
print("Invalid image size.")
}
return gameTemp
}
func createImage(from gameInfo: GameInfo) -> UIImage? {
let gameInfoValue = gameInfo
let imageSize = Int(gameInfoValue.ImageSize)
guard imageSize > 0, imageSize <= 1024 * 1024 else {
print("Invalid image size.")
return nil
}
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
return UIImage(data: imageData)
}
}

View File

@ -0,0 +1,38 @@
//
// LatestVersionResponse.swift
// MeloNX
//
// Created by Bella on 12/03/2025.
//
struct LatestVersionResponse: Codable {
let version_number: String
let version_number_stripped: String
let changelog: String
let download_link: String
#if DEBUG
static let example1 = LatestVersionResponse(
version_number: "1.0.0",
version_number_stripped: "100",
changelog: """
- Rewrite Display Code (SDL isn't used for display anymore)
- Add New Onboarding / Setup
- Better Performance
- Remove "SDL Window" option in settings
- Fix JIT Cache Regions
- Fix how JIT is detected in Settings
- Fix ABYX being swapped on controller.
- Settings are now a config.json file
- Fix Performance Overlay not showing when Virtual Controller is hidden
- Add displaying logs when Loading or in-game
- Fix Launching games from outside of the roms folder
- Add Waiting for JIT popup
- Fix spesific Games
- Added Back Herobrine ("You were supposed to be the hero, Bryan")
""",
download_link: "https://example.com"
)
#endif
}

View File

@ -1,308 +0,0 @@
//
// ContentView.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import SwiftUI
// import SDL2
import GameController
import Darwin
import UIKit
import MetalKit
// import SDL
import SoftwareKeyboard
struct MoltenVKSettings: Codable, Hashable {
let string: String
var value: String
}
struct ContentView: View {
// MARK: - Properties
@State private var theWindow: UIWindow?
@State private var game: Game?
@State private var controllersList: [Controller] = []
@State private var currentControllers: [Controller] = []
@State private var config: Ryujinx.Configuration
@State var settings: [MoltenVKSettings]
@AppStorage("useTrollStore") var useTrollStore: Bool = false
@State private var isVirtualControllerActive: Bool = false
@AppStorage("isVirtualController") var isVCA: Bool = true
@State var onscreencontroller: Controller = Controller(id: "", name: "")
@AppStorage("JIT") var isJITEnabled: Bool = false
@State var isMK8: Bool = false
@AppStorage("quit") var quit: Bool = false
@State var quits: Bool = false
@State private var clumpOffset: CGFloat = -100
private let clumpWidth: CGFloat = 100
private let animationDuration: Double = 1.0
@State private var isAnimating = false
@State var isLoading = true
// MARK: - Initialization
init() {
let defaultConfig = loadSettings() ?? Ryujinx.Configuration(gamepath: "")
_config = State(initialValue: defaultConfig)
let defaultSettings: [MoltenVKSettings] = [
// MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "1"),
// MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2"),
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "0"),
// MoltenVKSettings(string: "MVK_CONFIG_RESUME_LOST_DEVICE", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "0")
]
_settings = State(initialValue: defaultSettings)
print("JIT Enabled: \(isJITEnabled)")
initializeSDL()
}
// MARK: - Body
var body: some View {
if game != nil, quits == false {
if isLoading {
emulationView
.onAppear() {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
timer.invalidate()
quits = quit
if quits {
quit = false
timer.invalidate()
}
}
}
} else {
VStack {
}
.onAppear() {
isAnimating = false
}
}
} else {
mainMenuView
.onAppear() {
quits = false
}
}
}
// MARK: - View Components
private var emulationView: some View {
GeometryReader { screenGeometry in
ZStack {
HStack(spacing: screenGeometry.size.width * 0.04) {
if let icon = game?.icon {
Image(uiImage: icon)
.resizable()
.frame(
width: min(screenGeometry.size.width * 0.25, 250),
height: min(screenGeometry.size.width * 0.25, 250)
)
.clipShape(RoundedRectangle(cornerRadius: 16))
.shadow(color: .black.opacity(0.5), radius: 10, x: 0, y: 5)
}
VStack(alignment: .leading, spacing: screenGeometry.size.height * 0.015) {
Text("Loading \(game?.titleName ?? "Game")")
.font(.system(size: min(screenGeometry.size.width * 0.04, 32)))
.foregroundColor(.white)
GeometryReader { geometry in
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
ZStack(alignment: .leading) {
// Background track
Rectangle()
.cornerRadius(10)
.frame(width: containerWidth, height: min(screenGeometry.size.height * 0.015, 12))
.foregroundColor(.gray.opacity(0.3))
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
// Animated loading bar
Rectangle()
.cornerRadius(10)
.frame(width: clumpWidth, height: min(screenGeometry.size.height * 0.015, 12))
.foregroundColor(.blue)
.shadow(color: .blue.opacity(0.5), radius: 4, x: 0, y: 2)
.offset(x: isAnimating ? containerWidth : -clumpWidth)
.animation(
Animation.linear(duration: 1.0)
.repeatForever(autoreverses: false),
value: isAnimating
)
}
.clipShape(RoundedRectangle(cornerRadius: 16))
.onAppear {
isAnimating = true
setupEmulation()
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
if get_current_fps() != 0 {
isLoading = false
isAnimating = false
timer.invalidate()
}
print(get_current_fps())
}
}
}
.frame(height: min(screenGeometry.size.height * 0.015, 12))
.frame(width: min(screenGeometry.size.width * 0.35, 350))
}
}
.padding(.horizontal, screenGeometry.size.width * 0.06)
.padding(.vertical, screenGeometry.size.height * 0.05)
.position(
x: screenGeometry.size.width / 2,
y: screenGeometry.size.height * 0.5
)
}
}
}
private var mainMenuView: some View {
MainTabView(startemu: $game, config: $config, MVKconfig: $settings, controllersList: $controllersList, currentControllers: $currentControllers, onscreencontroller: $onscreencontroller)
.onAppear() {
refreshControllersList()
let isJIT = UserDefaults.standard.bool(forKey: "JIT-ENABLED")
if !isJIT, useTrollStore {
askForJIT()
}
}
}
// MARK: - Helper Methods
var SdlInitFlags: uint = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO;
private func initializeSDL() {
setMoltenVKSettings()
SDL_SetMainReady()
SDL_iPhoneSetEventPump(SDL_TRUE)
SDL_Init(SdlInitFlags)
initialize()
}
private func setupEmulation() {
patchMakeKeyAndVisible()
if (currentControllers.first(where: { $0 == onscreencontroller }) != nil) {
isVCA = true
DispatchQueue.main.async {
start(displayid: 1)
}
} else {
isVCA = false
DispatchQueue.main.async {
start(displayid: 1)
}
}
}
private func refreshControllersList() {
controllersList = Ryujinx.shared.getConnectedControllers()
if let onscreen = controllersList.first(where: { $0.name == Ryujinx.shared.virtualController.controllername }) {
self.onscreencontroller = onscreen
}
controllersList.removeAll(where: { $0.id == "0"})
if controllersList.count > 2 {
let controller = controllersList[2]
currentControllers.append(controller)
} else if let controller = controllersList.first(where: { $0.id == onscreencontroller.id }), !controllersList.isEmpty {
currentControllers.append(controller)
}
}
func showAlert(title: String, message: String, showOk: Bool, completion: @escaping (Bool) -> Void) {
DispatchQueue.main.async {
if let mainWindow = UIApplication.shared.windows.last {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
if showOk {
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
completion(true)
}
alert.addAction(okAction)
} else {
completion(false)
}
mainWindow.rootViewController?.present(alert, animated: true, completion: nil)
}
}
}
private func start(displayid: UInt32) {
guard let game else { return }
config.gamepath = game.fileURL.path
config.inputids = Array(Set(currentControllers.map(\.id)))
if game.titleName.lowercased() == "super mario odyssey" {
let setting = MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "1")
setenv(setting.string, setting.value, 1)
}
if config.inputids.isEmpty {
config.inputids.append("0")
}
do {
try Ryujinx.shared.start(with: config)
} catch {
print("Error: \(error.localizedDescription)")
}
}
private func setMoltenVKSettings() {
settings.forEach { setting in
setenv(setting.string, setting.value, 1)
}
}
}
// MARK: - Helper Functions
func loadSettings() -> Ryujinx.Configuration? {
guard let jsonString = UserDefaults.standard.string(forKey: "config"),
let data = jsonString.data(using: .utf8) else {
return nil
}
do {
return try JSONDecoder().decode(Ryujinx.Configuration.self, from: data)
} catch {
print("Failed to load settings: \(error)")
return nil
}
}

View File

@ -1,493 +0,0 @@
//
// GameListView.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import SwiftUI
import UniformTypeIdentifiers
struct GameLibraryView: View {
@Binding var startemu: Game?
@State private var games: [Game] = []
@State private var searchText = ""
@State private var isSearching = false
@AppStorage("recentGames") private var recentGamesData: Data = Data()
@State private var recentGames: [Game] = []
@Environment(\.colorScheme) var colorScheme
@State var firmwareInstaller = false
@State var firmwareversion = "0"
@State var isImporting: Bool = false
@State var startgame = false
var filteredGames: [Game] {
if searchText.isEmpty {
return games
}
return games.filter {
$0.titleName.localizedCaseInsensitiveContains(searchText) ||
$0.developer.localizedCaseInsensitiveContains(searchText)
}
}
var body: some View {
iOSNav {
ScrollView {
LazyVStack(alignment: .leading, spacing: 20) {
if !isSearching {
Text("Games")
.font(.system(size: 34, weight: .bold))
.padding(.horizontal)
.padding(.top, 12)
}
if games.isEmpty {
VStack(spacing: 16) {
Image(systemName: "gamecontroller.fill")
.font(.system(size: 64))
.foregroundColor(.secondary.opacity(0.7))
.padding(.top, 60)
Text("No Games Found")
.font(.title2.bold())
.foregroundColor(.primary)
Text("Add ROM, Keys and Firmware to get started")
.font(.subheadline)
.foregroundColor(.secondary)
}
.frame(maxWidth: .infinity)
.padding(.top, 40)
} else {
if !isSearching && !recentGames.isEmpty {
VStack(alignment: .leading, spacing: 12) {
Text("Recent")
.font(.title2.bold())
.padding(.horizontal)
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 16) {
ForEach(recentGames) { game in
RecentGameCard(game: game, startemu: $startemu)
.onTapGesture {
addToRecentGames(game)
startemu = game
}
}
}
.padding(.horizontal)
}
}
VStack(alignment: .leading, spacing: 12) {
Text("All Games")
.font(.title2.bold())
.padding(.horizontal)
LazyVStack(spacing: 2) {
ForEach(filteredGames) { game in
GameListRow(game: game, startemu: $startemu)
.onTapGesture {
addToRecentGames(game)
}
}
}
}
} else {
LazyVStack(spacing: 2) {
ForEach(filteredGames) { game in
GameListRow(game: game, startemu: $startemu)
.onTapGesture {
addToRecentGames(game)
}
}
}
}
}
}
.onAppear {
loadGames()
loadRecentGames()
let firmware = Ryujinx.shared.fetchFirmwareVersion()
firmwareversion = (firmware == "" ? "0" : firmware)
}
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Menu {
Text("Firmware Version: \(firmwareversion)")
.tint(.white)
if firmwareversion == "0" {
Button {
firmwareInstaller.toggle()
} label: {
Text("Install Firmware")
}
} else {
Button {
Ryujinx.shared.removeFirmware()
let firmware = Ryujinx.shared.fetchFirmwareVersion()
firmwareversion = (firmware == "" ? "0" : firmware)
} label: {
Text("Remove Firmware")
}
Button {
let game = Game(containerFolder: URL(string: "none")!, fileType: .item, fileURL: URL(string: "MiiMaker")!, titleName: "Mii Maker", titleId: "0", developer: "Nintendo", version: firmwareversion)
self.startemu = game
} label: {
Text("Mii Maker")
}
Button {
isImporting.toggle()
} label: {
Text("Open game from system")
}
}
Button {
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
let furl = URL(string: sharedurl)!
if UIApplication.shared.canOpenURL(furl) {
UIApplication.shared.open(furl, options: [:])
}
} label: {
Text("Show MeloNX Folder")
}
} label: {
Image(systemName: "ellipsis.circle")
.foregroundColor(.blue)
}
}
}
}
.background(Color(.systemGroupedBackground))
.searchable(text: $searchText)
.onChange(of: searchText) { _ in
isSearching = !searchText.isEmpty
}
.fileImporter(isPresented: $firmwareInstaller, allowedContentTypes: [.item]) { result in
switch result {
case .success(let url):
do {
let fun = url.startAccessingSecurityScopedResource()
let path = url.path
Ryujinx.shared.installFirmware(firmwarePath: path)
firmwareversion = (Ryujinx.shared.fetchFirmwareVersion() == "" ? "0" : Ryujinx.shared.fetchFirmwareVersion())
if fun {
url.stopAccessingSecurityScopedResource()
}
}
case .failure(let error):
print(error)
}
}
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.zip, .data]) { result in
switch result {
case .success(let url):
guard url.startAccessingSecurityScopedResource() else {
print("Failed to access security-scoped resource")
return
}
defer { url.stopAccessingSecurityScopedResource() }
do {
let handle = try FileHandle(forReadingFrom: url)
let fileExtension = (url.pathExtension as NSString).utf8String
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
var gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
var game = Game(containerFolder: url.deletingLastPathComponent(), fileType: .item, fileURL: url, titleName: "", titleId: "", developer: "", version: "")
game.titleName = withUnsafePointer(to: &gameInfo.TitleName) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.developer = withUnsafePointer(to: &gameInfo.Developer) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.titleId = String(gameInfo.TitleId)
print(String(gameInfo.TitleId))
game.version = String(gameInfo.Version)
game.icon = game.createImage(from: gameInfo)
DispatchQueue.main.async {
startemu = game
}
} catch {
print(error)
}
case .failure(let err):
print("File import failed: \(err.localizedDescription)")
}
}
}
private func addToRecentGames(_ game: Game) {
recentGames.removeAll { $0.id == game.id }
recentGames.insert(game, at: 0)
if recentGames.count > 5 {
recentGames = Array(recentGames.prefix(5))
}
saveRecentGames()
}
private func saveRecentGames() {
do {
let encoder = JSONEncoder()
let data = try encoder.encode(recentGames)
recentGamesData = data
} catch {
print("Error saving recent games: \(error)")
}
}
private func loadRecentGames() {
do {
let decoder = JSONDecoder()
recentGames = try decoder.decode([Game].self, from: recentGamesData)
} catch {
print("Error loading recent games: \(error)")
recentGames = []
}
}
private func loadGames() {
let fileManager = FileManager.default
guard let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
// Check if "roms" folder exists; if not, create it
if !fileManager.fileExists(atPath: romsDirectory.path) {
do {
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
} catch {
print("Failed to create roms directory: \(error)")
}
}
games = []
// Load games only from "roms" folder
do {
let files = try fileManager.contentsOfDirectory(at: romsDirectory, includingPropertiesForKeys: nil)
files.forEach { fileURLCandidate in
do {
let handle = try FileHandle(forReadingFrom: fileURLCandidate)
let fileExtension = (fileURLCandidate.pathExtension as NSString).utf8String
let extensionPtr = UnsafeMutablePointer<CChar>(mutating: fileExtension)
var gameInfo = get_game_info(handle.fileDescriptor, extensionPtr)
var game = Game(containerFolder: romsDirectory, fileType: .item, fileURL: fileURLCandidate, titleName: "", titleId: "", developer: "", version: "")
game.titleName = withUnsafePointer(to: &gameInfo.TitleName) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.developer = withUnsafePointer(to: &gameInfo.Developer) {
$0.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout.size(ofValue: $0)) {
String(cString: $0)
}
}
game.titleId = String(gameInfo.TitleId)
game.version = String(gameInfo.Version)
game.icon = game.createImage(from: gameInfo)
games.append(game)
} catch {
print(error)
}
}
} catch {
print("Error loading games from roms folder: \(error)")
}
}
}
extension Game: Codable {
enum CodingKeys: String, CodingKey {
case titleName, titleId, developer, version, fileURL
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
titleName = try container.decode(String.self, forKey: .titleName)
titleId = try container.decode(String.self, forKey: .titleId)
developer = try container.decode(String.self, forKey: .developer)
version = try container.decode(String.self, forKey: .version)
fileURL = try container.decode(URL.self, forKey: .fileURL)
// Initialize other properties
self.containerFolder = fileURL.deletingLastPathComponent()
self.fileType = .item
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(titleName, forKey: .titleName)
try container.encode(titleId, forKey: .titleId)
try container.encode(developer, forKey: .developer)
try container.encode(version, forKey: .version)
try container.encode(fileURL, forKey: .fileURL)
}
}
struct RecentGameCard: View {
let game: Game
@Binding var startemu: Game?
@Environment(\.colorScheme) var colorScheme
var body: some View {
Button(action: {
startemu = game
}) {
VStack(alignment: .leading, spacing: 8) {
if let icon = game.icon {
Image(uiImage: icon)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 140, height: 140)
.cornerRadius(12)
} else {
ZStack {
RoundedRectangle(cornerRadius: 12)
.fill(colorScheme == .dark ?
Color(.systemGray5) : Color(.systemGray6))
.frame(width: 140, height: 140)
Image(systemName: "gamecontroller.fill")
.font(.system(size: 40))
.foregroundColor(.gray)
}
}
VStack(alignment: .leading, spacing: 2) {
Text(game.titleName)
.font(.subheadline.bold())
.lineLimit(1)
Text(game.developer)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
}
.padding(.horizontal, 4)
}
}
.buttonStyle(.plain)
}
}
struct GameListRow: View {
let game: Game
@Binding var startemu: Game?
@Environment(\.colorScheme) var colorScheme
var body: some View {
Button(action: {
startemu = game
}) {
HStack(spacing: 16) {
// Game Icon
if let icon = game.icon {
Image(uiImage: icon)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 45, height: 45)
.cornerRadius(8)
} else {
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(colorScheme == .dark ?
Color(.systemGray5) : Color(.systemGray6))
.frame(width: 45, height: 45)
Image(systemName: "gamecontroller.fill")
.font(.system(size: 20))
.foregroundColor(.gray)
}
}
// Game Info
VStack(alignment: .leading, spacing: 2) {
Text(game.titleName)
.font(.body)
.foregroundColor(.primary)
Text(game.developer)
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
Image(systemName: "play.circle.fill")
.font(.title2)
.foregroundColor(.accentColor)
.opacity(0.8)
}
.padding(.horizontal)
.padding(.vertical, 8)
.background(Color(.systemBackground))
.contextMenu {
Button {
startemu = game
} label: {
Label("Play Now", systemImage: "play.fill")
}
Button {
// Add info action
} label: {
Label("Game Info", systemImage: "info.circle")
}
}
}
.buttonStyle(.plain)
}
}

View File

@ -0,0 +1,399 @@
//
// ContentView.swift
// MeloNX
//
// Created by Stossy11 on 3/11/2024.
//
import SwiftUI
import GameController
import Darwin
import UIKit
import MetalKit
struct MoltenVKSettings: Codable, Hashable {
let string: String
var value: String
}
struct ContentView: View {
// MARK: - Properties
// Games
@State private var game: Game?
// Controllers
@State private var controllersList: [Controller] = []
@State private var currentControllers: [Controller] = []
@State var onscreencontroller: Controller = Controller(id: "", name: "")
@State var nativeControllers: [GCController: NativeController] = [:]
@State private var isVirtualControllerActive: Bool = false
@AppStorage("isVirtualController") var isVCA: Bool = true
// Settings and Configuration
@State private var config: Ryujinx.Configuration
@State var settings: [MoltenVKSettings]
@AppStorage("useTrollStore") var useTrollStore: Bool = false
// JIT
@AppStorage("jitStreamerEB") var jitStreamerEB: Bool = false
// Other Configuration
@State var isMK8: Bool = false
@AppStorage("quit") var quit: Bool = false
@State var quits: Bool = false
@AppStorage("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS") var mVKPreFillBuffer: Bool = true
@AppStorage("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS") var syncqsubmits: Bool = true
@AppStorage("ignoreJIT") var ignoreJIT: Bool = false
// Loading Animation
@AppStorage("showlogsloading") var showlogsloading: Bool = true
@State private var clumpOffset: CGFloat = -100
private let clumpWidth: CGFloat = 100
private let animationDuration: Double = 1.0
@State private var isAnimating = false
@State var isLoading = true
@State var jitNotEnabled = false
// MARK: - SDL
var sdlInitFlags: UInt32 = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO
// MARK: - Initialization
init() {
var defaultConfig = loadSettings()
if defaultConfig == nil {
saveSettings(config: .init(gamepath: ""))
defaultConfig = loadSettings()
}
_config = State(initialValue: defaultConfig!)
let defaultSettings: [MoltenVKSettings] = [
MoltenVKSettings(string: "MVK_USE_METAL_PRIVATE_API", value: "1"),
MoltenVKSettings(string: "MVK_CONFIG_USE_METAL_PRIVATE_API", value: "1"),
MoltenVKSettings(string: "MVK_DEBUG", value: "0"),
MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "0"),
MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "0"),
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "512"),
]
_settings = State(initialValue: defaultSettings)
initializeSDL()
}
// MARK: - Body
var body: some View {
if game != nil && (!jitNotEnabled || ignoreJIT) {
gameView
} else if game != nil && jitNotEnabled {
jitErrorView
} else {
mainMenuView
}
}
// MARK: - View Components
private var gameView: some View {
ZStack {
if #available(iOS 16, *) {
EmulationView(startgame: $game)
.persistentSystemOverlays(.hidden)
} else {
EmulationView(startgame: $game)
}
if isLoading {
ZStack {
Color.black.opacity(0.8)
emulationView.ignoresSafeArea(.all)
}
.edgesIgnoringSafeArea(.all)
.ignoresSafeArea(.all)
}
}
}
private var jitErrorView: some View {
Text("")
.sheet(isPresented: $jitNotEnabled) {
JITPopover() {
jitNotEnabled = false
}
.interactiveDismissDisabled()
}
}
private var mainMenuView: some View {
MainTabView(
startemu: $game,
config: $config,
MVKconfig: $settings,
controllersList: $controllersList,
currentControllers: $currentControllers,
onscreencontroller: $onscreencontroller
)
.onAppear {
quits = false
let _ = loadSettings()
isLoading = true
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
refreshControllersList()
}
print(MTLHud.shared.isEnabled)
initControllerObservers()
Air.play(AnyView(
VStack {
Image(systemName: "gamecontroller")
.font(.system(size: 300))
.foregroundColor(.gray)
.padding(.bottom, 10)
Text("Select Game")
.font(.system(size: 150))
.bold()
}
))
checkJitStatus()
}
.onOpenURL { url in
handleDeepLink(url)
}
}
private var emulationView: some View {
GeometryReader { screenGeometry in
ZStack {
gameLoadingContent(screenGeometry: screenGeometry)
HStack{
VStack {
if showlogsloading {
LogFileView(isfps: true)
.frame(alignment: .topLeading)
}
Spacer()
}
Spacer()
}
}
}
}
// MARK: - Helper Methods
private func gameLoadingContent(screenGeometry: GeometryProxy) -> some View {
HStack(spacing: screenGeometry.size.width * 0.04) {
if let icon = game?.icon {
Image(uiImage: icon)
.resizable()
.frame(
width: min(screenGeometry.size.width * 0.25, 250),
height: min(screenGeometry.size.width * 0.25, 250)
)
.clipShape(RoundedRectangle(cornerRadius: 16))
.shadow(color: .black.opacity(0.5), radius: 10, x: 0, y: 5)
}
VStack(alignment: .leading, spacing: screenGeometry.size.height * 0.015) {
Text("Loading \(game?.titleName ?? "Game")")
.font(.system(size: min(screenGeometry.size.width * 0.04, 32)))
.foregroundColor(.white)
loadingProgressBar(screenGeometry: screenGeometry)
}
}
.padding(.horizontal, screenGeometry.size.width * 0.06)
.padding(.vertical, screenGeometry.size.height * 0.05)
.position(
x: screenGeometry.size.width / 2,
y: screenGeometry.size.height * 0.5
)
}
private func loadingProgressBar(screenGeometry: GeometryProxy) -> some View {
GeometryReader { geometry in
let containerWidth = min(screenGeometry.size.width * 0.35, 350)
ZStack(alignment: .leading) {
Rectangle()
.cornerRadius(10)
.frame(width: containerWidth, height: min(screenGeometry.size.height * 0.015, 12))
.foregroundColor(.gray.opacity(0.3))
.shadow(color: .black.opacity(0.2), radius: 4, x: 0, y: 2)
Rectangle()
.cornerRadius(10)
.frame(width: clumpWidth, height: min(screenGeometry.size.height * 0.015, 12))
.foregroundColor(.blue)
.shadow(color: .blue.opacity(0.5), radius: 4, x: 0, y: 2)
.offset(x: isAnimating ? containerWidth : -clumpWidth)
.animation(
Animation.linear(duration: 1.0)
.repeatForever(autoreverses: false),
value: isAnimating
)
}
.clipShape(RoundedRectangle(cornerRadius: 16))
.onAppear {
isAnimating = true
setupEmulation()
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { timer in
if get_current_fps() != 0 {
withAnimation {
isLoading = false
isAnimating = false
}
timer.invalidate()
}
}
}
}
.frame(height: min(screenGeometry.size.height * 0.015, 12))
.frame(width: min(screenGeometry.size.width * 0.35, 350))
}
private func initializeSDL() {
setMoltenVKSettings()
SDL_SetMainReady()
SDL_iPhoneSetEventPump(SDL_TRUE)
SDL_Init(sdlInitFlags)
initialize()
}
private func initControllerObservers() {
NotificationCenter.default.addObserver(
forName: .GCControllerDidConnect,
object: nil,
queue: .main
) { notification in
if let controller = notification.object as? GCController {
print("Controller connected: \(controller.productCategory)")
nativeControllers[controller] = .init(controller)
refreshControllersList()
}
}
NotificationCenter.default.addObserver(
forName: .GCControllerDidDisconnect,
object: nil,
queue: .main
) { notification in
if let controller = notification.object as? GCController {
print("Controller disconnected: \(controller.productCategory)")
nativeControllers[controller]?.cleanup()
nativeControllers[controller] = nil
refreshControllersList()
}
}
}
private func setupEmulation() {
isVCA = (currentControllers.first(where: { $0 == onscreencontroller }) != nil)
DispatchQueue.main.async {
start(displayid: 1)
}
}
private func refreshControllersList() {
controllersList = Ryujinx.shared.getConnectedControllers()
if let onscreen = controllersList.first(where: { $0.name == Ryujinx.shared.virtualController.controllername }) {
self.onscreencontroller = onscreen
}
controllersList.removeAll(where: { $0.id == "0" || (!$0.name.starts(with: "GC - ") && $0 != onscreencontroller) })
controllersList.mutableForEach { $0.name = $0.name.replacingOccurrences(of: "GC - ", with: "") }
currentControllers = []
if controllersList.count == 1 {
currentControllers.append(controllersList[0])
} else if (controllersList.count - 1) >= 1 {
for controller in controllersList {
if controller.id != onscreencontroller.id && !currentControllers.contains(where: { $0.id == controller.id }) {
currentControllers.append(controller)
}
}
}
}
private func start(displayid: UInt32) {
guard let game else { return }
config.gamepath = game.fileURL.path
config.inputids = Array(Set(currentControllers.map(\.id)))
configureEnvironmentVariables()
if config.inputids.isEmpty {
config.inputids.append("0")
}
do {
try Ryujinx.shared.start(with: config)
} catch {
print("Error: \(error.localizedDescription)")
}
}
private func configureEnvironmentVariables() {
if mVKPreFillBuffer {
setenv("MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", "2", 1)
}
if syncqsubmits {
setenv("MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", "2", 1)
}
}
private func setMoltenVKSettings() {
settings.forEach { setting in
setenv(setting.string, setting.value, 1)
}
}
private func checkJitStatus() {
jitNotEnabled = !isJITEnabled()
if jitNotEnabled {
if useTrollStore {
askForJIT()
} else if jitStreamerEB {
enableJITEB()
} else {
print("no JIT")
}
}
}
private func handleDeepLink(_ url: URL) {
if let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
components.host == "game" {
if let text = components.queryItems?.first(where: { $0.name == "id" })?.value {
game = Ryujinx.shared.games.first(where: { $0.titleId == text })
} else if let text = components.queryItems?.first(where: { $0.name == "name" })?.value {
game = Ryujinx.shared.games.first(where: { $0.titleName == text })
}
}
}
}
extension Array {
@inlinable public mutating func mutableForEach(_ body: (inout Element) throws -> Void) rethrows {
for index in self.indices {
try body(&self[index])
}
}
}

View File

@ -11,29 +11,10 @@ import SwiftUIJoystick
import CoreMotion
struct ControllerView: View {
@AppStorage("performacehud") var performacehud: Bool = false
@AppStorage("quit") var quit: Bool = false
var body: some View {
GeometryReader { geometry in
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad {
VStack {
if performacehud {
HStack {
PerformanceOverlayView()
Spacer()
// Button("Stop emulation") {
// DispatchQueue.main.async {
// stop_emulation()
// quit = true
// }
// }
}
}
Spacer()
VStack {
@ -45,15 +26,14 @@ struct ControllerView: View {
DPadView()
}
}
.padding()
Spacer()
VStack {
ShoulderButtonsViewRight()
ZStack {
Joystick(iscool: true) // hope this works
Joystick(iscool: true) // hope this works JoyStick
ABXYView()
}
}
.padding()
}
HStack {
@ -63,26 +43,11 @@ struct ControllerView: View {
.padding(.horizontal, 40)
}
}
.padding(.bottom, geometry.size.height / 3.2) // very broken
}
} else {
// could be landscape
VStack {
if performacehud {
HStack {
PerformanceOverlayView()
Spacer()
// Button("Stop emulation") {
// DispatchQueue.main.async {
// stop_emulation()
// quit = true
// }
// }
}
}
/*VStack {
Spacer()
VStack {
@ -92,27 +57,28 @@ struct ControllerView: View {
VStack {
ShoulderButtonsViewLeft()
ZStack {
Joystick()
DPadView()
//Joystick()
Joystick4FTG()
//DPadView() Disable landscape Dpad
}
}
HStack {
// Spacer()
VStack {
// Spacer()
ButtonView(button: .back) // Adding the + button
ButtonView(button: .back) // Adding the - button
}
Spacer()
VStack {
// Spacer()
ButtonView(button: .start) // Adding the - button
ButtonView(button: .start) // Adding the + button
}
// Spacer()
}
VStack {
ShoulderButtonsViewRight()
ZStack {
Joystick(iscool: true) // hope this work s
//Joystick(iscool: true) // hope this work Disable right Joystick
ABXYView()
}
}
@ -120,6 +86,35 @@ struct ControllerView: View {
}
// .padding(.bottom, geometry.size.height / 11) // also extremally broken (
}*/
VStack {
Spacer() //
HStack(alignment: .bottom) {
VStack(alignment: .leading) {
//ShoulderButtonsViewLeft()
Joystick4FTG() //
//.frame(maxWidth: .infinity, alignment: .leading)
}
.offset(x: 10, y: -5) // +
HStack {
VStack {
ButtonView(button: .back) // -
}
Spacer()
VStack {
ButtonView(button: .start) // +
}
}
VStack {
ShoulderButtonsViewRight()
ZStack {
ABXYView()
}
}.offset(x: 30, y: 25) // 50 25
}
.padding(.bottom, 10) // padding
}
}
}
@ -130,12 +125,15 @@ struct ControllerView: View {
struct ShoulderButtonsViewLeft: View {
@State var width: CGFloat = 160
@State var height: CGFloat = 20
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
var body: some View {
HStack {
ButtonView(button: .leftTrigger)
.padding(.horizontal)
ButtonView(button: .leftShoulder)
.padding(.horizontal)
//move left Trigger
// ButtonView(button: .leftTrigger)
// .padding(.horizontal)
//ButtonView(button: .leftShoulder)
// .padding(.horizontal)
}
.frame(width: width, height: height)
.onAppear() {
@ -143,19 +141,33 @@ struct ShoulderButtonsViewLeft: View {
width *= 1.2
height *= 1.2
}
width *= CGFloat(controllerScale)
height *= CGFloat(controllerScale)
}
}
}
struct ShoulderButtonsViewRight: View {
@State var width: CGFloat = 160
@State var height: CGFloat = 20
@State var width: CGFloat = 100
@State var height: CGFloat = 60
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
var body: some View {
HStack {
ButtonView(button: .rightShoulder)
.padding(.horizontal)
ButtonView(button: .rightTrigger)
.padding(.horizontal)
}
HStack{
//put left Trigger there
ButtonView(button: .leftTrigger)
.padding(.horizontal)
ButtonView(button: .leftShoulder)
.padding(.horizontal)
}
.frame(width: width, height: height)
.onAppear() {
@ -163,12 +175,16 @@ struct ShoulderButtonsViewRight: View {
width *= 1.2
height *= 1.2
}
width *= CGFloat(controllerScale)
height *= CGFloat(controllerScale)
}
}
}
struct DPadView: View {
@State var size: CGFloat = 145
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
var body: some View {
VStack {
ButtonView(button: .dPadUp)
@ -185,13 +201,17 @@ struct DPadView: View {
if UIDevice.current.systemName.contains("iPadOS") {
size *= 1.2
}
size *= CGFloat(controllerScale)
}
}
}
struct ABXYView: View {
@State var size: CGFloat = 145
var body: some View {
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
/*var body: some View {
VStack {
ButtonView(button: .X)
HStack {
@ -202,23 +222,41 @@ struct ABXYView: View {
ButtonView(button: .B)
.padding(.horizontal)
}
*/
//
var body: some View {
VStack(spacing: 5){
HStack(spacing: 5) {
ButtonView(button: .Y)
ButtonView(button: .B)
}
HStack(spacing: 5) {
ButtonView(button: .X)
ButtonView(button: .A)
}
}
.frame(width: size, height: size)
.onAppear() {
if UIDevice.current.systemName.contains("iPadOS") {
size *= 1.2
}
size *= CGFloat(controllerScale)
}
}
}
struct ButtonView: View {
var button: VirtualControllerButton
@State var width: CGFloat = 45
@State var height: CGFloat = 45
@State var width: CGFloat = 90
@State var height: CGFloat = 90
@State var isPressed = false
@AppStorage("onscreenhandheld") var onscreenjoy: Bool = false
@Environment(\.colorScheme) var colorScheme
@Environment(\.presentationMode) var presentationMode
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
@ -227,14 +265,14 @@ struct ButtonView: View {
.resizable()
.frame(width: width, height: height)
.foregroundColor(colorScheme == .dark ? Color.gray : Color.gray)
.opacity(isPressed ? 0.4 : 0.7)
.opacity(isPressed ? 0.3 : 0.6)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { _ in
if !self.isPressed {
self.isPressed = true
Ryujinx.shared.virtualController.setButtonState(1, for: button)
Haptics.shared.play(.heavy)
Haptics.shared.play(.light)
}
}
.onEnded { _ in
@ -257,6 +295,9 @@ struct ButtonView: View {
width *= 1.2
height *= 1.2
}
width *= CGFloat(controllerScale)
height *= CGFloat(controllerScale)
}
}

View File

@ -0,0 +1,592 @@
//
// Joystick4FTG.swift
// MeloNX
//
// Created by RZH on 2025/3/7.
//
import SwiftUI
public struct Joystick4FTG: View {
//
@State var iscool: Bool? = nil
@State private var joystickPositionText = ""
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
var dragDiameter: CGFloat {
var selfs = CGFloat(160)
selfs *= controllerScale
if UIDevice.current.systemName.contains("iPadOS") {
return selfs * 1.2
}
return selfs
}
public var body: some View {
VStack{
//
//FTGJoystick
/*FTGJoystickBuilder { relativePosition in
let scaledX = Double(relativePosition.x )
let scaledY = Double(relativePosition.y )
//print("Joystick Position: (\(scaledX), \(scaledY))")
*/
//
/*
EnhancedEightWayJoystick{relativePosition in
let scaledX = Double(relativePosition.x * 10)
let scaledY = Double(relativePosition.y * 10)
let formattedText = String(format: "X: %5.2f, Y: %5.2f", scaledX, scaledY)
print("Joystick Position: \(formattedText)")
//
//self.joystickPositionText = formattedText
*/
/*
//
FreeRoamJoystick { position in
let scaledX = Double(position.x * 10)
let scaledY = Double(position.y * 10)
//print("Joystick Position: \(formattedText)")
//
//self.joystickPositionText = formattedText
*/
//
/*
NineGridJoystick { position in
let scaledX = Double(position.x * 10)
let scaledY = Double(position.y * 10)
*/
//
DynamicNineGridJoystick{ position in
let scaledX = Double(position.x * 10)
let scaledY = Double(position.y * 10)
if iscool != nil {
Ryujinx.shared.virtualController.thumbstickMoved(.right, x: scaledX, y: scaledY)
} else {
Ryujinx.shared.virtualController.thumbstickMoved(.left, x: scaledX, y: scaledY)
}
}
}
}
}
struct EnhancedEightWayJoystick: View {
var onDirectionChanged: ((x: Int, y: Int)) -> Void
@State private var currentDirection: (x: Int, y: Int) = (0, 0)
private let radius: CGFloat = 50
private let centerPoint = CGPoint(x: 50, y: 50)
var body: some View {
GeometryReader { geometry in
ZStack {
//
Circle()
.fill(Color.gray.opacity(0.2))
.frame(width: 200, height: 200)
.contentShape(Circle())
//
Circle()
.fill(Color.white.opacity(0.7))
.frame(width: 75, height: 75)
.offset(x: CGFloat(currentDirection.x), y: CGFloat(currentDirection.y))
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { gesture in
let location = gesture.location
//使Geometrydxdy
let viewFrame = geometry.frame(in: .local)
let center = CGPoint(
x: viewFrame.midX,
y: viewFrame.midY
)
//let dx = location.x - centerPoint.x
//let dy = location.y - centerPoint.y
let dx = location.x - center.x
let dy = location.y - center.y
//
let radians = atan2(dy, dx)
var degrees = radians * 180 / .pi
if degrees < 0 { degrees += 360 }
//
let direction = get45DegreeDirection(from: degrees)
//
if direction.x != currentDirection.x || direction.y != currentDirection.y {
currentDirection = direction
onDirectionChanged(direction)
}
}
.onEnded { _ in
currentDirection = (0, 0)
onDirectionChanged((0, 0))
}
)
}
}
// 45
//Y
//scale 168
private func get45DegreeDirection(from degrees: CGFloat) -> (x: Int, y: Int) {
switch degrees {
// (337.5°-22.5°)
case 337.5...360, 0..<22.5:
return (50, 0)
// (22.5°-67.5°)
case 22.5..<67.5:
//return (50, -50)
return (50, 50)
// (67.5°-112.5°)
case 67.5..<112.5:
//return (0, -50)
return (0, 50)
// (112.5°-157.5°)
case 112.5..<157.5:
//return (-50, -50)
return (-50, 50)
// (157.5°-202.5°)
case 157.5..<202.5:
return (-50, 0)
// (202.5°-247.5°)
case 202.5..<247.5:
//return (-50, 50)
return (-50, -50)
// (247.5°-292.5°)
case 247.5..<292.5:
//return (0, 50)
return (0, -50)
// (292.5°-337.5°)
case 292.5..<337.5:
//return (50, 50)
return (50, -50)
default:
return (0, 0)
}
}
}
struct FreeRoamJoystick: View {
var onPositionChanged: (CGPoint) -> Void
private let trackRadius: CGFloat = 90
private let thumbRadius: CGFloat = 75
@State private var isEightWay = 0 // 0: 1:
@State private var position = CGPoint.zero
@State private var viewCenter: CGPoint = .zero
var body: some View {
GeometryReader { geometry in
ZStack {
Spacer()
//
Circle()
.stroke(Color.gray.opacity(0.5), lineWidth: 4)
.frame(width: trackRadius*2, height: trackRadius*2)
.position(x: geometry.size.width/2, y: geometry.size.height/2)
.contentShape(Circle()) // 1
//
let maxMoveRadius = trackRadius - thumbRadius
Circle()
.fill(Color.white.opacity(0.4))
.frame(width: thumbRadius*2, height: thumbRadius*2)
//.offset(x: position.x, y: position.y)
.offset(
x: min(max(position.x, -maxMoveRadius), maxMoveRadius),
y: min(max(position.y, -maxMoveRadius), maxMoveRadius)
)
.position(x: geometry.size.width/2, y: geometry.size.height/2)
}
.gesture(
DragGesture(minimumDistance: 0) // 2
.onChanged { gesture in
let viewFrame = geometry.frame(in: .local)
let touchPoint = gesture.location
//
let center = CGPoint(
x: viewFrame.midX,
y: viewFrame.midY
)
let delta = CGPoint(
x: touchPoint.x - center.x,
y: touchPoint.y - center.y
)
//
let distance = sqrt(delta.x * delta.x + delta.y * delta.y)
let clampedDelta = distance > trackRadius ? CGPoint(
x: delta.x * trackRadius / distance,
y: delta.y * trackRadius / distance
) : delta
var test_data = clampedDelta
//
//
if isEightWay == 1 {
test_data = getEightDirection(for: test_data)
}
//position = clampedDelta
position = test_data
//onPositionChanged(clampedDelta)
onPositionChanged(test_data)
}
.onEnded { _ in
position = .zero
onPositionChanged(.zero)
}
)
.onAppear {
viewCenter = CGPoint(
x: geometry.size.width/2,
y: geometry.size.height/2
)
}
}
.frame(width: trackRadius, height: trackRadius)
//.frame(alignment: .bottomLeading)
}
private func getEightDirection(for delta: CGPoint) -> CGPoint {
let angle = atan2(-delta.y, delta.x)
let degrees = angle * 180 / .pi
let normalized = degrees < 0 ? degrees + 360 : degrees
switch normalized {
case 337.5...360, 0..<22.5: //
return CGPoint(x: trackRadius, y: 0)
case 22.5..<67.5: //
return CGPoint(x: trackRadius, y: -trackRadius)
case 67.5..<112.5: //
return CGPoint(x: 0, y: -trackRadius)
case 112.5..<157.5: //
return CGPoint(x: -trackRadius, y: -trackRadius)
case 157.5..<202.5: //
return CGPoint(x: -trackRadius, y: 0)
case 202.5..<247.5: //
return CGPoint(x: -trackRadius, y: trackRadius)
case 247.5..<292.5: //
return CGPoint(x: 0, y: trackRadius)
case 292.5..<337.5: //
return CGPoint(x: trackRadius, y: trackRadius)
default:
return .zero
}
}
}
struct NineGridJoystick: View {
var onDirectionChanged: (CGPoint) -> Void
private let trackRadius: CGFloat = 90
private let thumbRadius: CGFloat = 35
@State private var position = CGPoint.zero
@State private var activeColor = Color.white.opacity(0.4)
//
private var gridThreshold: CGFloat {
trackRadius / 3 // 56
}
var body: some View {
GeometryReader { geometry in
ZStack {
//
gridBackgroundView()
//
Circle()
.fill(activeColor)
.frame(width: thumbRadius*2, height: thumbRadius*2)
.offset(x: position.x, y: position.y)
.position(x: geometry.size.width/2, y: geometry.size.height/2)
//.animation(.spring(), value: position)
}
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { gesture in
let center = CGPoint(x: geometry.size.width/2, y: geometry.size.height/2)
let delta = CGPoint(
x: gesture.location.x - center.x,
y: gesture.location.y - center.y
)
let direction = getGridDirection(for: delta)
updatePosition(to: direction)
}
.onEnded { _ in
resetPosition()
}
)
}
.frame(width: trackRadius*2, height: trackRadius*2)
}
// MARK: -
private func gridBackgroundView() -> some View {
ZStack {
// 线
Path { path in
// 线
path.move(to: CGPoint(x: trackRadius - gridThreshold, y: 0))
path.addLine(to: CGPoint(x: trackRadius - gridThreshold, y: trackRadius*2))
path.move(to: CGPoint(x: trackRadius + gridThreshold, y: 0))
path.addLine(to: CGPoint(x: trackRadius + gridThreshold, y: trackRadius*2))
// 线
path.move(to: CGPoint(x: 0, y: trackRadius - gridThreshold))
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius - gridThreshold))
path.move(to: CGPoint(x: 0, y: trackRadius + gridThreshold))
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius + gridThreshold))
}
.stroke(Color.gray.opacity(0.3), lineWidth: 2)
//
Rectangle()
.fill(Color.gray.opacity(0.1))
.frame(width: gridThreshold*2, height: gridThreshold*2)
}
}
// MARK: -
private func getGridDirection(for delta: CGPoint) -> CGPoint {
let xVal = delta.x
let yVal = delta.y
switch (xVal, yVal) {
case (..<(-gridThreshold), ..<(-gridThreshold)): //
return CGPoint(x: -trackRadius, y: -trackRadius)
case ((-gridThreshold)...gridThreshold, ..<(-gridThreshold)): //
return CGPoint(x: 0, y: -trackRadius)
case (gridThreshold..., ..<(-gridThreshold)): //
return CGPoint(x: trackRadius, y: -trackRadius)
case (..<(-gridThreshold), (-gridThreshold)...gridThreshold): //
return CGPoint(x: -trackRadius, y: 0)
case ((-gridThreshold)...gridThreshold, (-gridThreshold)...gridThreshold): //
return .zero
case (gridThreshold..., (-gridThreshold)...gridThreshold): //
return CGPoint(x: trackRadius, y: 0)
case (..<(-gridThreshold), gridThreshold...): //
return CGPoint(x: -trackRadius, y: trackRadius)
case ((-gridThreshold)...gridThreshold, gridThreshold...): //
return CGPoint(x: 0, y: trackRadius)
case (gridThreshold..., gridThreshold...): //
return CGPoint(x: trackRadius, y: trackRadius)
default:
return .zero
}
}
// MARK: -
private func updatePosition(to direction: CGPoint) {
//withAnimation(.spring()) {
position = direction
activeColor = direction == .zero ?
Color.white.opacity(0.4) :
Color.blue.opacity(0.6)
onDirectionChanged(direction)
//}
}
private func resetPosition() {
//withAnimation(.spring()) {
position = .zero
activeColor = Color.white.opacity(0.4)
onDirectionChanged(.zero)
//}
}
}
struct DynamicNineGridJoystick: View {
var onDirectionChanged: (CGPoint) -> Void
private let trackRadius: CGFloat = 100
private let thumbRadius: CGFloat = 35
@State private var position = CGPoint.zero
@State private var activeColor = Color.white.opacity(0.4)
@State private var lastDirection: CGPoint = .zero
//
private var gridThreshold: CGFloat { trackRadius / 3 }
private let hapticGenerator = UIImpactFeedbackGenerator(style: .soft)
var body: some View {
GeometryReader { geometry in
ZStack {
//
gridBackgroundView()
//
Circle()
.fill(activeColor)
.frame(width: thumbRadius*2, height: thumbRadius*2)
.offset(x: position.x, y: position.y)
.position(x: geometry.size.width/2, y: geometry.size.height/2)
//.animation(.spring(), value: position)
}
//
.contentShape(Rectangle()) // 使GeometryReader
.gesture(
DragGesture(minimumDistance: 0)
.onChanged { gesture in
handleGestureChange(
location: gesture.location,
center: CGPoint(x: geometry.size.width/2,
y: geometry.size.height/2)
)
}
.onEnded { _ in
resetPosition()
}
)
}
.frame(width: trackRadius*2, height: trackRadius*2)
.onAppear {
hapticGenerator.prepare()
}
}
// MARK: -
private func handleGestureChange(location: CGPoint, center: CGPoint) {
let delta = CGPoint(
x: location.x - center.x,
y: location.y - center.y
)
let currentDirection = getGridDirection(for: delta)
//
if currentDirection != lastDirection {
updatePosition(to: currentDirection)
triggerHapticFeedback()
lastDirection = currentDirection
}
}
// MARK: -
private func getGridDirection(for delta: CGPoint) -> CGPoint {
let xVal = delta.x
let yVal = delta.y
//
let isLeft = xVal < -gridThreshold
let isRight = xVal > gridThreshold
let isUp = yVal < -gridThreshold
let isDown = yVal > gridThreshold
switch (isLeft, isRight, isUp, isDown) {
case (true, false, true, false): //
return CGPoint(x: -trackRadius, y: -trackRadius)
case (false, false, true, false): //
return CGPoint(x: 0, y: -trackRadius)
case (false, true, true, false): //
return CGPoint(x: trackRadius, y: -trackRadius)
case (true, false, false, false): //
return CGPoint(x: -trackRadius, y: 0)
case (false, false, false, false): //
return .zero
case (false, true, false, false): //
return CGPoint(x: trackRadius, y: 0)
case (true, false, false, true): //
return CGPoint(x: -trackRadius, y: trackRadius)
case (false, false, false, true): //
return CGPoint(x: 0, y: trackRadius)
case (false, true, false, true): //
return CGPoint(x: trackRadius, y: trackRadius)
default: // 线
return calculateEdgeCase(delta: delta)
}
}
//
private func calculateEdgeCase(delta: CGPoint) -> CGPoint {
let angle = atan2(delta.y, delta.x) * 180 / .pi
let normalized = angle < 0 ? angle + 360 : angle
switch normalized {
case 45..<135: //
return CGPoint(x: 0, y: -trackRadius)
case 135..<225: //
return CGPoint(x: -trackRadius, y: 0)
case 225..<315: //
return CGPoint(x: 0, y: trackRadius)
default: //
return CGPoint(x: trackRadius, y: 0)
}
}
// MARK: -
private func updatePosition(to direction: CGPoint) {
//withAnimation(.interactiveSpring()) {
position = direction
activeColor = direction == .zero ?
Color.white.opacity(0.4) :
Color.blue.opacity(0.6)
onDirectionChanged(direction)
}
private func resetPosition() {
//withAnimation(.spring()) {
position = .zero
activeColor = Color.white.opacity(0.4)
lastDirection = .zero
onDirectionChanged(.zero)
//}
}
private func triggerHapticFeedback() {
guard position != .zero else { return }
hapticGenerator.impactOccurred(intensity: 0.7)
}
// MARK: -
private func gridBackgroundView() -> some View {
ZStack {
// 线
Path { path in
let offsets = [-gridThreshold, 0, gridThreshold]
for offset in offsets {
path.move(to: CGPoint(x: trackRadius + offset, y: 0))
path.addLine(to: CGPoint(x: trackRadius + offset, y: trackRadius*2))
path.move(to: CGPoint(x: 0, y: trackRadius + offset))
path.addLine(to: CGPoint(x: trackRadius*2, y: trackRadius + offset))
}
}
.stroke(Color.gray.opacity(0.3), lineWidth: 2)
//
RoundedRectangle(cornerRadius: 12)
.fill(Color.gray.opacity(0.1))
.frame(width: gridThreshold*2, height: gridThreshold*2)
}
}
}

View File

@ -13,11 +13,14 @@ public struct Joystick: View {
@State var iscool: Bool? = nil
@ObservedObject public var joystickMonitor = JoystickMonitor()
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
var dragDiameter: CGFloat {
var selfs = CGFloat(160)
selfs *= controllerScale
if UIDevice.current.systemName.contains("iPadOS") {
return selfs * 1.2
}
return selfs
}
private let shape: JoystickShape = .circle

View File

@ -0,0 +1,143 @@
// Credit https://github.com/heestand-xyz/AirKit
import UIKit
import SwiftUI
public class Air {
static let shared = Air()
public var connected: Bool = false {
didSet {
connectionCallbacks.forEach({ $0(connected) })
}
}
var connectionCallbacks: [(Bool) -> ()] = []
var airScreen: UIScreen?
var airWindow: UIWindow?
var hostingController: UIHostingController<AnyView>?
var appIsActive: Bool { UIApplication.shared.applicationState == .active }
init() {
NotificationCenter.default.addObserver(self, selector: #selector(didConnect),
name: UIScreen.didConnectNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didDisconnect),
name: UIScreen.didDisconnectNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive),
name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(willResignActive),
name: UIApplication.willResignActiveNotification, object: nil)
}
private func check() {
if let connectedScreen = UIScreen.screens.first(where: { $0 != .main }) {
add(screen: connectedScreen) { success in
guard success else { return }
self.connected = true
}
}
}
public static func play(_ view: AnyView) {
Air.shared.hostingController = UIHostingController<AnyView>(rootView: view)
Air.shared.check()
}
public static func stop() {
Air.shared.remove()
Air.shared.hostingController = nil
}
public static func connection(_ callback: @escaping (Bool) -> ()) {
Air.shared.connectionCallbacks.append(callback)
}
@objc func didConnect(sender: NSNotification) {
print("AirKit - Connect")
self.connected = true
guard let screen: UIScreen = sender.object as? UIScreen else { return }
add(screen: screen) { success in
guard success else { return }
self.connected = true
}
}
func add(screen: UIScreen, completion: @escaping (Bool) -> ()) {
print("AirKit - Add Screen")
airScreen = screen
airWindow = UIWindow(frame: airScreen!.bounds)
guard let viewController: UIViewController = hostingController else {
print("AirKit - Add - Failed: Hosting Controller Not Found")
completion(false)
return
}
findWindowScene(for: airScreen!) { windowScene in
guard let airWindowScene: UIWindowScene = windowScene else {
print("AirKit - Add - Failed: Window Scene Not Found")
completion(false)
return
}
self.airWindow?.rootViewController = viewController
self.airWindow?.windowScene = airWindowScene
self.airWindow?.isHidden = false
print("AirKit - Add Screen - Done")
completion(true)
}
}
func findWindowScene(for screen: UIScreen, shouldRecurse: Bool = true, completion: @escaping (UIWindowScene?) -> ()) {
print("AirKit - Find Window Scene")
var matchingWindowScene: UIWindowScene? = nil
let scenes = UIApplication.shared.connectedScenes
for scene in scenes {
if let windowScene = scene as? UIWindowScene {
if windowScene.screen == screen {
matchingWindowScene = windowScene
break
}
}
}
guard let windowScene: UIWindowScene = matchingWindowScene else {
DispatchQueue.main.async {
self.findWindowScene(for: screen, shouldRecurse: false) { windowScene in
completion(windowScene)
}
}
return
}
completion(windowScene)
}
@objc func didDisconnect() {
print("AirKit - Disconnect")
remove()
connected = false
}
func remove() {
print("AirKit - Remove")
airWindow = nil
airScreen = nil
}
@objc func didBecomeActive() {
print("AirKit - App Active")
}
@objc func willResignActive() {
print("AirKit - App Inactive")
}
}

View File

@ -0,0 +1,13 @@
import UIKit
import SwiftUI
public extension View {
func airPlay() -> some View {
print("AirKit - airPlay")
Air.play(AnyView(self))
return self
}
}

Some files were not shown because too many files have changed in this diff Show More