forked from MeloNX/MeloNX
Compare commits
27 Commits
e924da52ec
...
80148ac69a
Author | SHA1 | Date | |
---|---|---|---|
80148ac69a | |||
|
7417ddfeef | ||
|
f66590203a | ||
|
f091e6c5ea | ||
|
75a66586b2 | ||
|
3207e1e739 | ||
|
a6b4f2d91f | ||
|
93af19e200 | ||
|
172f364f62 | ||
|
05666c918d | ||
b45af91523 | |||
|
0bfca4c488 | ||
|
14ce5102fb | ||
|
12ab8bc3e2 | ||
aaefc0a9e5 | |||
2dd31138fe | |||
0c835b064d | |||
a1ca0b99a4 | |||
7b67ffb39a | |||
4a901d3cba | |||
|
6a8722e23b | ||
|
85ad96109e | ||
|
ebe1d183ef | ||
|
6345e6421d | ||
|
8ca88def54 | ||
e372f6eb35 | |||
76b82243a7 |
@ -17,8 +17,8 @@ tab_width = 4
|
|||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
# JSON files
|
# Markdown, JSON, YAML, props and csproj files
|
||||||
[*.json]
|
[*.{md,json,yml,props,csproj}]
|
||||||
|
|
||||||
# Indentation and spacing
|
# Indentation and spacing
|
||||||
indent_size = 2
|
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'"
|
# 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
|
dotnet_diagnostic.CA1862.severity = none
|
||||||
|
|
||||||
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
[src/Ryujinx/UI/ViewModels/**.cs]
|
||||||
# Disable "mark members as static" rule for services
|
# Disable "mark members as static" rule for ViewModels
|
||||||
dotnet_diagnostic.CA1822.severity = none
|
dotnet_diagnostic.CA1822.severity = none
|
||||||
|
|
||||||
[src/Ryujinx.Ava/UI/ViewModels/**.cs]
|
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
||||||
# Disable "mark members as static" rule for ViewModels
|
# Disable "mark members as static" rule for services
|
||||||
dotnet_diagnostic.CA1822.severity = none
|
dotnet_diagnostic.CA1822.severity = none
|
||||||
|
|
||||||
[src/Ryujinx.Tests/Cpu/*.cs]
|
[src/Ryujinx.Tests/Cpu/*.cs]
|
||||||
|
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -23,7 +23,7 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Log file
|
label: Log file
|
||||||
description: A log file will help our developers to better diagnose and fix the issue.
|
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:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
@ -83,4 +83,4 @@ body:
|
|||||||
- Additional info about your environment:
|
- Additional info about your environment:
|
||||||
- Any other information relevant to your issue.
|
- Any other information relevant to your issue.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
1
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,6 +1,7 @@
|
|||||||
name: Feature Request
|
name: Feature Request
|
||||||
description: Suggest a new feature for Ryujinx.
|
description: Suggest a new feature for Ryujinx.
|
||||||
title: "[Feature Request]"
|
title: "[Feature Request]"
|
||||||
|
labels: enhancement
|
||||||
body:
|
body:
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: overview
|
id: overview
|
||||||
|
22
.github/dependabot.yml
vendored
22
.github/dependabot.yml
vendored
@ -7,18 +7,34 @@ updates:
|
|||||||
labels:
|
labels:
|
||||||
- "infra"
|
- "infra"
|
||||||
reviewers:
|
reviewers:
|
||||||
- marysaka
|
- TSRBerry
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: "ci"
|
prefix: "ci"
|
||||||
|
|
||||||
- package-ecosystem: nuget
|
- package-ecosystem: nuget
|
||||||
directory: /
|
directory: /
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 10
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
labels:
|
labels:
|
||||||
- "infra"
|
- "infra"
|
||||||
reviewers:
|
reviewers:
|
||||||
- marysaka
|
- TSRBerry
|
||||||
commit-message:
|
commit-message:
|
||||||
prefix: nuget
|
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
2
.github/labeler.yml
vendored
@ -20,7 +20,7 @@ gpu:
|
|||||||
|
|
||||||
gui:
|
gui:
|
||||||
- changed-files:
|
- 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:
|
horizon:
|
||||||
- changed-files:
|
- changed-files:
|
||||||
|
7
.github/reviewers.yml
vendored
7
.github/reviewers.yml
vendored
@ -1,31 +1,24 @@
|
|||||||
audio:
|
|
||||||
- marysaka
|
|
||||||
|
|
||||||
cpu:
|
cpu:
|
||||||
- gdkchan
|
- gdkchan
|
||||||
- riperiperi
|
- riperiperi
|
||||||
- marysaka
|
|
||||||
- LDj3SNuD
|
- LDj3SNuD
|
||||||
|
|
||||||
gpu:
|
gpu:
|
||||||
- gdkchan
|
- gdkchan
|
||||||
- riperiperi
|
- riperiperi
|
||||||
- marysaka
|
|
||||||
|
|
||||||
gui:
|
gui:
|
||||||
- Ack77
|
- Ack77
|
||||||
- emmauss
|
- emmauss
|
||||||
- TSRBerry
|
- TSRBerry
|
||||||
- marysaka
|
|
||||||
|
|
||||||
horizon:
|
horizon:
|
||||||
- gdkchan
|
- gdkchan
|
||||||
- Ack77
|
- Ack77
|
||||||
- marysaka
|
|
||||||
- TSRBerry
|
- TSRBerry
|
||||||
|
|
||||||
infra:
|
infra:
|
||||||
- marysaka
|
|
||||||
- TSRBerry
|
- TSRBerry
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
87
.github/workflows/build.yml
vendored
87
.github/workflows/build.yml
vendored
@ -10,28 +10,17 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
|
||||||
configuration: [Debug, Release]
|
configuration: [Debug, Release]
|
||||||
include:
|
platform:
|
||||||
- os: ubuntu-latest
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
OS_NAME: Linux x64
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
RELEASE_ZIP_OS_NAME: linux_x64
|
- { name: osx-x64, os: macos-13, zip_os_name: osx_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
|
|
||||||
|
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
steps:
|
steps:
|
||||||
@ -40,7 +29,7 @@ jobs:
|
|||||||
- uses: actions/setup-dotnet@v4
|
- uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
global-json-file: global.json
|
global-json-file: global.json
|
||||||
|
|
||||||
- name: Overwrite csc problem matcher
|
- name: Overwrite csc problem matcher
|
||||||
run: echo "::add-matcher::.github/csc.json"
|
run: echo "::add-matcher::.github/csc.json"
|
||||||
|
|
||||||
@ -49,6 +38,16 @@ jobs:
|
|||||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||||
shell: bash
|
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
|
- 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
|
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 }}"
|
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
retry-codes: 139
|
retry-codes: 139
|
||||||
|
if: matrix.platform.name != 'linux-arm64'
|
||||||
|
|
||||||
- name: Publish Ryujinx
|
- 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
|
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.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
- name: Publish Ryujinx.Headless.SDL2
|
- 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
|
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.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
- name: Publish Ryujinx.Ava
|
- name: Publish Ryujinx.Gtk3
|
||||||
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
|
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.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
- name: Set executable bit
|
- name: Set executable bit
|
||||||
run: |
|
run: |
|
||||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||||
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/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
|
chmod +x ./publish_gtk/Ryujinx.Gtk3 ./publish_gtk/Ryujinx.sh
|
||||||
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||||
|
|
||||||
- name: Upload Ryujinx artifact
|
- name: Upload Ryujinx artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
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
|
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
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
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
|
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
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
name: gtk-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||||
path: publish_ava
|
path: publish_gtk
|
||||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
name: macOS Universal (${{ matrix.configuration }})
|
name: macOS Universal (${{ matrix.configuration }})
|
||||||
@ -135,19 +135,24 @@ jobs:
|
|||||||
id: git_short_hash
|
id: git_short_hash
|
||||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
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: |
|
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
|
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||||
run: |
|
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"
|
./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
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||||
path: "publish_ava/*.tar.gz"
|
path: "publish/*.tar.gz"
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
|
|
||||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||||
|
2
.github/workflows/checks.yml
vendored
2
.github/workflows/checks.yml
vendored
@ -8,7 +8,7 @@ on:
|
|||||||
- '!.github/**'
|
- '!.github/**'
|
||||||
- '!*.yml'
|
- '!*.yml'
|
||||||
- '!*.config'
|
- '!*.config'
|
||||||
- '!README.md'
|
- '!*.md'
|
||||||
- '.github/workflows/*.yml'
|
- '.github/workflows/*.yml'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
72
.github/workflows/flatpak.yml
vendored
72
.github/workflows/flatpak.yml
vendored
@ -51,38 +51,76 @@ jobs:
|
|||||||
- name: Restore Nuget packages
|
- 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.
|
# 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
|
# 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
|
- name: Generate nuget_sources.json
|
||||||
shell: python
|
shell: python
|
||||||
run: |
|
run: |
|
||||||
|
import hashlib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import base64
|
import base64
|
||||||
import binascii
|
import binascii
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
sources = []
|
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:
|
def create_source_from_external(name, version):
|
||||||
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
|
full_dir_path = Path(os.environ["NUGET_PACKAGES"]).joinpath(name).joinpath(version)
|
||||||
|
os.makedirs(full_dir_path, exist_ok=True)
|
||||||
|
|
||||||
sources.append({
|
filename = "{}.{}.nupkg".format(name, version)
|
||||||
'type': 'file',
|
url = "https://api.nuget.org/v3-flatcontainer/{}/{}/{}".format(
|
||||||
'url': url,
|
name, version, filename
|
||||||
'sha512': sha512,
|
)
|
||||||
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
|
|
||||||
'dest-filename': filename,
|
|
||||||
})
|
|
||||||
|
|
||||||
with open('flathub/nuget_sources.json', 'w') as fp:
|
print(f"Processing {url}...")
|
||||||
json.dump(sources, fp, indent=4)
|
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
|
- name: Update flatpak metadata
|
||||||
id: metadata
|
id: metadata
|
||||||
|
41
.github/workflows/mako.yml
vendored
Normal file
41
.github/workflows/mako.yml
vendored
Normal 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 }}
|
10
.github/workflows/nightly_pr_comment.yml
vendored
10
.github/workflows/nightly_pr_comment.yml
vendored
@ -39,24 +39,24 @@ jobs:
|
|||||||
return core.error(`No artifacts found`);
|
return core.error(`No artifacts found`);
|
||||||
}
|
}
|
||||||
let body = `Download the artifacts for this pull request:\n`;
|
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_headless_artifacts = `\n\n <details><summary>GUI-less (SDL2)</summary>\n`;
|
||||||
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
let hidden_debug_artifacts = `\n\n <details><summary>Only for Developers</summary>\n`;
|
||||||
for (const art of artifacts) {
|
for (const art of artifacts) {
|
||||||
if(art.name.includes('Debug')) {
|
if(art.name.includes('Debug')) {
|
||||||
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
hidden_debug_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
} else if(art.name.includes('ava-ryujinx')) {
|
} else if(art.name.includes('gtk-ryujinx')) {
|
||||||
hidden_avalonia_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
hidden_gtk_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
} else if(art.name.includes('sdl2-ryujinx-headless')) {
|
||||||
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
hidden_headless_artifacts += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
||||||
} else {
|
} else {
|
||||||
body += `\n* [${art.name}](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
|
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_headless_artifacts += `\n</details>`;
|
||||||
hidden_debug_artifacts += `\n</details>`;
|
hidden_debug_artifacts += `\n</details>`;
|
||||||
body += hidden_avalonia_artifacts;
|
body += hidden_gtk_artifacts;
|
||||||
body += hidden_headless_artifacts;
|
body += hidden_headless_artifacts;
|
||||||
body += hidden_debug_artifacts;
|
body += hidden_debug_artifacts;
|
||||||
|
|
||||||
|
19
.github/workflows/pr_triage.yml
vendored
19
.github/workflows/pr_triage.yml
vendored
@ -21,27 +21,8 @@ jobs:
|
|||||||
repository: Ryujinx/Ryujinx
|
repository: Ryujinx/Ryujinx
|
||||||
ref: master
|
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
|
- name: Update labels based on changes
|
||||||
uses: actions/labeler@v5
|
uses: actions/labeler@v5
|
||||||
with:
|
with:
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
dot: 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 }}
|
|
||||||
|
76
.github/workflows/release.yml
vendored
76
.github/workflows/release.yml
vendored
@ -10,7 +10,7 @@ on:
|
|||||||
- '*.yml'
|
- '*.yml'
|
||||||
- '*.json'
|
- '*.json'
|
||||||
- '*.config'
|
- '*.config'
|
||||||
- 'README.md'
|
- '*.md'
|
||||||
|
|
||||||
concurrency: release
|
concurrency: release
|
||||||
|
|
||||||
@ -44,30 +44,34 @@ jobs:
|
|||||||
sha: context.sha
|
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:
|
release:
|
||||||
name: Release ${{ matrix.OS_NAME }}
|
name: Release for ${{ matrix.platform.name }}
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.platform.os }}
|
||||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-latest, windows-latest ]
|
platform:
|
||||||
include:
|
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||||
- os: ubuntu-latest
|
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||||
OS_NAME: Linux x64
|
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||||
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
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-dotnet@v4
|
- uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
global-json-file: global.json
|
global-json-file: global.json
|
||||||
|
|
||||||
- name: Overwrite csc problem matcher
|
- name: Overwrite csc problem matcher
|
||||||
run: echo "::add-matcher::.github/csc.json"
|
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_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_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_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
|
shell: bash
|
||||||
|
|
||||||
- name: Create output dir
|
- name: Create output dir
|
||||||
@ -92,42 +97,36 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
run: |
|
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.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.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.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
|
||||||
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
|
|
||||||
|
|
||||||
- name: Packing Windows builds
|
- name: Packing Windows builds
|
||||||
if: matrix.os == 'windows-latest'
|
if: matrix.platform.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_gtk
|
pushd publish_ava
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
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
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
7z a ../release_output/sdl2-ryujinx-headless-${{ 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
|
|
||||||
|
|
||||||
pushd publish_ava
|
|
||||||
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
|
||||||
popd
|
popd
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Packing Linux builds
|
- name: Packing Linux builds
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_gtk
|
pushd publish_ava
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
cp publish/Ryujinx publish/Ryujinx.Ava
|
||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
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
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
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
|
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.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
|
|
||||||
popd
|
popd
|
||||||
shell: bash
|
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_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_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_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
|
shell: bash
|
||||||
|
|
||||||
- name: Publish macOS Ryujinx.Ava
|
- name: Publish macOS Ryujinx
|
||||||
run: |
|
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
|
./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
|
- name: Publish macOS Ryujinx.Headless.SDL2
|
||||||
run: |
|
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
|
./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
|
||||||
|
37
Compile.md
37
Compile.md
@ -12,23 +12,16 @@ Before you begin, ensure you have the following installed:
|
|||||||
|
|
||||||
### 1. Clone the Repository and Build Ryujinx
|
### 1. Clone the Repository and Build Ryujinx
|
||||||
|
|
||||||
Open a terminal and run:
|
Open a terminal and run (your password will not be shown in the 2nd command):
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://git.743378673.xyz/MeloNX/MeloNX.git
|
git clone https://git.743378673.xyz/MeloNX/MeloNX.git
|
||||||
cd MeloNX
|
|
||||||
./compile.sh
|
|
||||||
```
|
|
||||||
You may need to run this command if compilation fails, then run the `./compile.sh` command again (You will need to put in your user password. Your password will not be shown at all.)
|
|
||||||
```
|
|
||||||
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
|
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
|
||||||
```
|
```
|
||||||
|
|
||||||
|
However, if you only need to update MeloNX, make sure you have cd into the directory then run this
|
||||||
However, if you only need to update MeloNX, make sure you have cd into the directory then run this then skip to step 5
|
|
||||||
```
|
```
|
||||||
git pull
|
git pull
|
||||||
./compile.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Open the Xcode Project
|
### 2. Open the Xcode Project
|
||||||
@ -41,13 +34,7 @@ src/MeloNX/MeloNX.xcodeproj
|
|||||||
|
|
||||||
Double-click to open it in **Xcode**.
|
Double-click to open it in **Xcode**.
|
||||||
|
|
||||||
### 3. Configure the Project Settings
|
### 3. Configure Signing & Capabilities
|
||||||
|
|
||||||
- In **Xcode**, select the **MeloNX** project.
|
|
||||||
- Under the **General** tab, find `Ryujinx.Headless.SDL2.dylib`.
|
|
||||||
- Set its **Embed setting** to **"Embed & Sign"**.
|
|
||||||
|
|
||||||
### 4. Configure Signing & Capabilities
|
|
||||||
|
|
||||||
- In **Xcode**, go to **Signing & Capabilities**.
|
- In **Xcode**, go to **Signing & Capabilities**.
|
||||||
- Set the **Team** to your **Apple Developer account** (free or paid).
|
- Set the **Team** to your **Apple Developer account** (free or paid).
|
||||||
@ -59,23 +46,31 @@ Double-click to open it in **Xcode**.
|
|||||||
|
|
||||||
*(Replace `<your-name>` with your actual name or identifier.)*
|
*(Replace `<your-name>` with your actual name or identifier.)*
|
||||||
|
|
||||||
### 5. Connect Your Device
|
### 4. Connect Your Device
|
||||||
|
|
||||||
Ensure your **iPhone/iPad** is **connected** and **selected** (Next to MeloNX with the arrow) in Xcode.
|
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 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
|
- 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.
|
- 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")
|
### 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. Build and Run
|
### 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.
|
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
|
- When running on your device, Click the **Spray Can Button** below the Run button
|
||||||
- Right Click where it says "> MeloNX PID XXXX"
|
- Right Click where it says "> MeloNX PID XXXX"
|
||||||
- Press Detach in the Context Menu.
|
- Press Detach in the Context Menu.
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Now you're all set! 🚀 If you encounter issues, please join the discord at https://melonx.org
|
Now you're all set! 🚀 If you encounter issues, please join the discord at https://melonx.org
|
||||||
```
|
|
@ -3,52 +3,50 @@
|
|||||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageVersion Include="Avalonia" Version="11.0.5" />
|
<PackageVersion Include="Avalonia" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.5" />
|
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.5" />
|
<PackageVersion Include="Avalonia.Desktop" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.5" />
|
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.5" />
|
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.10" />
|
||||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.3" />
|
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.18" />
|
||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.3" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.18" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<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="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
<PackageVersion Include="DynamicData" Version="7.14.2" />
|
<PackageVersion Include="DynamicData" Version="9.0.4" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.4" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
<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="LibHac" Version="0.19.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.0.1" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
<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="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="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
<PackageVersion Include="OpenTK.Core" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
|
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
<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.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.3" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
<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="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.21.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.EXT" Version="2.21.0" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.22.0" />
|
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
<PackageVersion Include="SkiaSharp" Version="2.88.7" />
|
||||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageVersion Include="SkiaSharp.NativeAssets.Linux" Version="2.88.7" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
|
|
||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
11
README.md
11
README.md
@ -6,8 +6,6 @@
|
|||||||
|
|
||||||
<h1 align="center">MeloNX</h1>
|
<h1 align="center">MeloNX</h1>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
MeloNX enables Nintendo Switch game emulation on iOS using the Ryujinx iOS code base.
|
MeloNX enables Nintendo Switch game emulation on iOS using the Ryujinx iOS code base.
|
||||||
</p>
|
</p>
|
||||||
@ -24,7 +22,7 @@ MeloNX works on iPhone XS/XR and later and iPad 8th Gen and later. Check out the
|
|||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
- MeloNX is made for iOS 17+, on iOS 15 - 16 MeloNX can be installed but will have issues or may not work at all.
|
- MeloNX is made for iOS 17+, on iOS 15 - 16 MeloNX can be installed but may have issues or not work at all.
|
||||||
- MeloNX needs Xcode or a Paid Apple Developer Account. SideStore support may come soon (SideStore Side Issue)
|
- MeloNX needs Xcode or a Paid Apple Developer Account. SideStore support may come soon (SideStore Side Issue)
|
||||||
- MeloNX needs JIT
|
- MeloNX needs JIT
|
||||||
- Recommended Device: iPhone 15 Pro or newer.
|
- Recommended Device: iPhone 15 Pro or newer.
|
||||||
@ -96,12 +94,11 @@ If having Issues installing firmware (Make sure your Keys are installed first)
|
|||||||
- **Input**
|
- **Input**
|
||||||
|
|
||||||
We currently have support for keyboard, touch input, JoyCon input support, and nearly all controllers.
|
We currently have support for keyboard, touch input, JoyCon input support, and nearly all controllers.
|
||||||
Motion controls are natively supported in most cases; for dual-JoyCon motion support, DS4Windows or BetterJoy are currently required.
|
Motion controls are natively supported in most cases.
|
||||||
In all scenarios, you can set up everything inside the input configuration menu.
|
|
||||||
|
|
||||||
- **DLC & Modifications**
|
- **DLC & Modifications**
|
||||||
|
|
||||||
MeloNX does not support add-on content/downloadable content.
|
MeloNX supports DLC + Game Update Add-ons.
|
||||||
Mods (romfs, exefs, and runtime mods such as cheats) are supported;
|
Mods (romfs, exefs, and runtime mods such as cheats) are supported;
|
||||||
|
|
||||||
- **Configuration**
|
- **Configuration**
|
||||||
|
14
Ryujinx.sln
14
Ryujinx.sln
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.1.32228.430
|
VisualStudioVersion = 17.1.32228.430
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests", "src\Ryujinx.Tests\Ryujinx.Tests.csproj", "{EBB55AEA-C7D7-4DEB-BF96-FA1789E225E9}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -69,9 +69,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Headless.SDL2", "sr
|
|||||||
EndProject
|
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}"
|
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
|
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
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Generators", "src\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj", "{6AE2A5E8-4C5A-48B9-997B-E1455C0355C6}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -79,7 +79,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Vulkan", "
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spv.Generator", "src\Spv.Generator\Spv.Generator.csproj", "{2BCB3D7A-38C0-4FE7-8FDA-374C6AD56D0E}"
|
||||||
EndProject
|
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
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon.Common", "src\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj", "{77F96ECE-4952-42DB-A528-DED25572A573}"
|
||||||
EndProject
|
EndProject
|
||||||
@ -87,6 +87,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Horizon", "src\Ryuj
|
|||||||
EndProject
|
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}"
|
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
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{7F55A45D-4E1D-4A36-ADD3-87F29A285AA2}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/CSharpVarKeywordUsage/ForOtherTypes/@EntryValue">UseExplicitType</s:String>
|
<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/CSharpVarKeywordUsage/ForSimpleTypes/@EntryValue">UseExplicitType</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></s:String>
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=a0b4bc4d_002Dd13b_002D4a37_002Db37e_002Dc9c6864e4302/@EntryIndexedValue"><Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Types and namespaces"><ElementKinds><Kind Name="NAMESPACE" /><Kind Name="CLASS" /><Kind Name="STRUCT" /><Kind Name="ENUM" /><Kind Name="DELEGATE" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb"><ExtraRule Prefix="I" Suffix="" Style="AaBb" /></Policy></Policy></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/=ASET/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Astc/@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>
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Luma/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
# Define the destination directory (hardcoded)
|
# Define the destination directory (hardcoded)
|
||||||
DESTINATION_DIR="src/MeloNX/Dependencies/Dynamic\ Libraries/Ryujinx.Headless.SDL2.dylib"
|
DESTINATION_DIR="src/MeloNX/Dependencies/Dynamic\ Libraries/Ryujinx.Headless.SDL2.dylib"
|
||||||
|
|
44
distribution/ios/get_dotnet.sh
Executable file
44
distribution/ios/get_dotnet.sh
Executable 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"
|
38
distribution/ios/set_current_version.sh
Normal file
38
distribution/ios/set_current_version.sh
Normal 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"
|
@ -4,7 +4,7 @@ Name=Ryujinx
|
|||||||
Type=Application
|
Type=Application
|
||||||
Icon=Ryujinx
|
Icon=Ryujinx
|
||||||
Exec=Ryujinx.sh %f
|
Exec=Ryujinx.sh %f
|
||||||
Comment=Plays Nintendo Switch applications
|
Comment=A Nintendo Switch Emulator
|
||||||
GenericName=Nintendo Switch Emulator
|
GenericName=Nintendo Switch Emulator
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Game;Emulator;
|
Categories=Game;Emulator;
|
||||||
|
15
distribution/linux/Ryujinx.sh
Normal file → Executable file
15
distribution/linux/Ryujinx.sh
Normal file → Executable file
@ -1,20 +1,23 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
SCRIPT_DIR=$(dirname "$(realpath "$0")")
|
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
|
if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
|
||||||
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$SCRIPT_DIR/Ryujinx" ]; then
|
||||||
|
RYUJINX_BIN="Ryujinx"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$RYUJINX_BIN" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
||||||
|
|
||||||
if command -v gamemoderun > /dev/null 2>&1; then
|
if command -v gamemoderun > /dev/null 2>&1; then
|
||||||
COMMAND="$COMMAND gamemoderun"
|
COMMAND="$COMMAND gamemoderun"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
exec $COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
||||||
|
@ -14,8 +14,8 @@ mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
|
|||||||
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
|
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
|
||||||
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
|
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
|
||||||
|
|
||||||
# Copy executables first
|
# Copy executable and nsure executable can be executed
|
||||||
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
cp "$PUBLISH_DIRECTORY/Ryujinx" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||||
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||||
|
|
||||||
# Then all libraries
|
# Then all libraries
|
||||||
|
@ -22,9 +22,9 @@ EXTRA_ARGS=$8
|
|||||||
|
|
||||||
if [ "$VERSION" == "1.1.0" ];
|
if [ "$VERSION" == "1.1.0" ];
|
||||||
then
|
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
|
else
|
||||||
RELEASE_TAR_FILE_NAME=test-ava-ryujinx-$VERSION-macos_universal.app.tar
|
RELEASE_TAR_FILE_NAME=ryujinx-$VERSION-macos_universal.app.tar
|
||||||
fi
|
fi
|
||||||
|
|
||||||
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
|
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_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS)
|
||||||
|
|
||||||
dotnet restore
|
dotnet restore
|
||||||
dotnet build -c "$CONFIGURATION" 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.Ava
|
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.Ava
|
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)
|
# Get rid of the support library for ARMeilleure for x64 (that's only for arm64)
|
||||||
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
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"
|
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"
|
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||||
rm "$RELEASE_TAR_FILE_NAME"
|
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
|
popd
|
||||||
|
|
||||||
echo "Done"
|
echo "Done"
|
8
distribution/macos/shortcut-launch-script.sh
Normal file
8
distribution/macos/shortcut-launch-script.sh
Normal 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}
|
@ -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.
|
To be added. Many project files will contain basic XML docs for key functions and classes in the meantime.
|
||||||
|
|
||||||
Other Information
|
|
||||||
=================
|
|
||||||
|
|
||||||
- N/A
|
|
||||||
|
@ -237,7 +237,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
long originalPosition = _stream.Position;
|
long originalPosition = _stream.Position;
|
||||||
|
|
||||||
_stream.Seek(0, SeekOrigin.Begin);
|
_stream.Seek(0, SeekOrigin.Begin);
|
||||||
_stream.Read(code, 0, code.Length);
|
_stream.ReadExactly(code, 0, code.Length);
|
||||||
_stream.Seek(originalPosition, SeekOrigin.Begin);
|
_stream.Seek(originalPosition, SeekOrigin.Begin);
|
||||||
|
|
||||||
RelocInfo relocInfo;
|
RelocInfo relocInfo;
|
||||||
|
@ -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];
|
int selectedNextUse = freePositions[selectedReg];
|
||||||
|
|
||||||
// Intervals starts and ends at odd positions, unless they span an entire
|
// 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;
|
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.
|
// The "visited" state is stored in the MSB of the local's value.
|
||||||
const ulong VisitedMask = 1ul << 63;
|
const ulong VisitedMask = 1ul << 63;
|
||||||
|
|
||||||
bool IsVisited(Operand local)
|
static bool IsVisited(Operand local)
|
||||||
{
|
{
|
||||||
return (local.GetValueUnsafe() & VisitedMask) != 0;
|
return (local.GetValueUnsafe() & VisitedMask) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetVisited(Operand local)
|
static void SetVisited(Operand local)
|
||||||
{
|
{
|
||||||
local.GetValueUnsafe() |= VisitedMask;
|
local.GetValueUnsafe() |= VisitedMask;
|
||||||
}
|
}
|
||||||
@ -826,9 +839,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
{
|
{
|
||||||
dest.NumberLocal(_intervals.Count);
|
dest.NumberLocal(_intervals.Count);
|
||||||
|
|
||||||
_intervals.Add(new LiveInterval(dest));
|
LiveInterval interval = new LiveInterval(dest);
|
||||||
|
_intervals.Add(interval);
|
||||||
|
|
||||||
SetVisited(dest);
|
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()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
public LiveRange CurrRange;
|
public LiveRange CurrRange;
|
||||||
|
|
||||||
public LiveInterval Parent;
|
public LiveInterval Parent;
|
||||||
|
public LiveInterval CopySource;
|
||||||
|
|
||||||
public UseList Uses;
|
public UseList Uses;
|
||||||
public LiveIntervalList Children;
|
public LiveIntervalList Children;
|
||||||
@ -37,6 +38,7 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
private ref LiveRange CurrRange => ref _data->CurrRange;
|
private ref LiveRange CurrRange => ref _data->CurrRange;
|
||||||
private ref LiveRange PrevRange => ref _data->PrevRange;
|
private ref LiveRange PrevRange => ref _data->PrevRange;
|
||||||
private ref LiveInterval Parent => ref _data->Parent;
|
private ref LiveInterval Parent => ref _data->Parent;
|
||||||
|
private ref LiveInterval CopySource => ref _data->CopySource;
|
||||||
private ref UseList Uses => ref _data->Uses;
|
private ref UseList Uses => ref _data->Uses;
|
||||||
private ref LiveIntervalList Children => ref _data->Children;
|
private ref LiveIntervalList Children => ref _data->Children;
|
||||||
|
|
||||||
@ -78,6 +80,25 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
|||||||
Register = register;
|
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()
|
public void Reset()
|
||||||
{
|
{
|
||||||
PrevRange = default;
|
PrevRange = default;
|
||||||
|
@ -1444,7 +1444,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
|
|
||||||
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
|
Span<byte> buffer = new byte[jump.JumpPosition - _stream.Position];
|
||||||
|
|
||||||
_stream.Read(buffer);
|
_stream.ReadExactly(buffer);
|
||||||
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
|
_stream.Seek(ReservedBytesForJump, SeekOrigin.Current);
|
||||||
|
|
||||||
codeStream.Write(buffer);
|
codeStream.Write(buffer);
|
||||||
|
@ -517,7 +517,10 @@ namespace ARMeilleure.Decoders
|
|||||||
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
|
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
|
||||||
SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, 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("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("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("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create);
|
||||||
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
|
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
|
||||||
SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, 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("<<<<01101000xxxxxxxxxxxxxx01xxxx", InstName.Pkh, InstEmit32.Pkh, OpCode32AluRsImm.Create);
|
||||||
SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
|
SetA32("11110101xx01xxxx1111xxxxxxxxxxxx", InstName.Pld, InstEmit32.Nop, OpCode32.Create);
|
||||||
SetA32("11110111xx01xxxx1111xxxxxxx0xxxx", 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("<<<<011011111111xxxx11110011xxxx", InstName.Rbit, InstEmit32.Rbit, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create);
|
SetA32("<<<<011010111111xxxx11110011xxxx", InstName.Rev, InstEmit32.Rev, OpCode32AluReg.Create);
|
||||||
SetA32("<<<<011010111111xxxx11111011xxxx", InstName.Rev16, InstEmit32.Rev16, 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("<<<<00000100xxxxxxxxxxxx1001xxxx", InstName.Umaal, InstEmit32.Umaal, OpCode32AluUmull.Create);
|
||||||
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
SetA32("<<<<0000101xxxxxxxxxxxxx1001xxxx", InstName.Umlal, InstEmit32.Umlal, OpCode32AluUmull.Create);
|
||||||
SetA32("<<<<0000100xxxxxxxxxxxxx1001xxxx", InstName.Umull, InstEmit32.Umull, 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("<<<<0110111xxxxxxxxxxxxxxx01xxxx", InstName.Usat, InstEmit32.Usat, OpCode32Sat.Create);
|
||||||
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
|
SetA32("<<<<01101110xxxxxxxx11110011xxxx", InstName.Usat16, InstEmit32.Usat16, OpCode32Sat16.Create);
|
||||||
SetA32("<<<<01100101xxxxxxxx11111111xxxx", InstName.Usub8, InstEmit32.Usub8, OpCode32AluReg.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("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
||||||
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, 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("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||||
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_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);
|
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("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
|
||||||
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.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("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("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||||
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, 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("111100100x10xxxxxxxx1011xxx0xxxx", InstName.Vqdmulh, InstEmit32.Vqdmulh, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
SetAsimd("111100111x11<<10xxxx00101xx0xxx0", InstName.Vqmovn, InstEmit32.Vqmovn, OpCode32SimdMovn.Create, OpCode32SimdMovn.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx001001x0xxx0", InstName.Vqmovun, InstEmit32.Vqmovun, 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("1111001x1x>>>xxxxxxx100101x1xxx0", InstName.Vqrshrn, InstEmit32.Vqrshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.CreateT32);
|
||||||
SetAsimd("111100111x>>>xxxxxxx100001x1xxx0", InstName.Vqrshrun, InstEmit32.Vqrshrun, 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);
|
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("111100101x>>>xxxxxxx0101>xx1xxxx", InstName.Vshl, InstEmit32.Vshl, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("1111001x0xxxxxxxxxxx0100xxx0xxxx", InstName.Vshl, InstEmit32.Vshl_I, OpCode32SimdReg.Create, OpCode32SimdReg.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("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("1111001x1x>>>xxxxxxx0000>xx1xxxx", InstName.Vshr, InstEmit32.Vshr, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("111100101x>>>xxxxxxx100000x1xxx0", InstName.Vshrn, InstEmit32.Vshrn, OpCode32SimdShImmNarrow.Create, OpCode32SimdShImmNarrow.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("1111001x1x>>>xxxxxxx0001>xx1xxxx", InstName.Vsra, InstEmit32.Vsra, OpCode32SimdShImm.Create, OpCode32SimdShImm.CreateT32);
|
||||||
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
SetAsimd("111101001x00xxxxxxxx0000xxx0xxxx", InstName.Vst1, InstEmit32.Vst1, OpCode32SimdMemSingle.Create, OpCode32SimdMemSingle.CreateT32);
|
||||||
SetAsimd("111101001x00xxxxxxxx0100xx0xxxxx", 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("111100100x10xxxxxxxx1101xxx0xxxx", InstName.Vsub, InstEmit32.Vsub_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
SetAsimd("1111001x1x<<xxxxxxx00010x0x0xxxx", InstName.Vsubl, InstEmit32.Vsubl_I, OpCode32SimdRegLong.Create, OpCode32SimdRegLong.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("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("111100111x11xxxxxxxx10xxxxx0xxxx", InstName.Vtbl, InstEmit32.Vtbl, OpCode32SimdTbl.Create, OpCode32SimdTbl.CreateT32);
|
||||||
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
SetAsimd("111100111x11<<10xxxx00001xx0xxxx", InstName.Vtrn, InstEmit32.Vtrn, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||||
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
SetAsimd("111100100x<<xxxxxxxx1000xxx1xxxx", InstName.Vtst, InstEmit32.Vtst, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||||
|
@ -2,6 +2,8 @@ using ARMeilleure.Decoders;
|
|||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using static ARMeilleure.Instructions.InstEmitAluHelper;
|
using static ARMeilleure.Instructions.InstEmitAluHelper;
|
||||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||||
@ -19,6 +21,12 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand n = GetAluN(context);
|
Operand n = GetAluN(context);
|
||||||
Operand m = GetAluM(context, setCarry: false);
|
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);
|
Operand res = context.Add(n, m);
|
||||||
|
|
||||||
if (ShouldSetFlags(context))
|
if (ShouldSetFlags(context))
|
||||||
@ -284,6 +292,16 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitAluStore(context, res);
|
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)
|
public static void Rbit(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
Operand m = GetAluM(context);
|
Operand m = GetAluM(context);
|
||||||
@ -467,6 +485,12 @@ namespace ARMeilleure.Instructions
|
|||||||
Operand n = GetAluN(context);
|
Operand n = GetAluN(context);
|
||||||
Operand m = GetAluM(context, setCarry: false);
|
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);
|
Operand res = context.Subtract(n, m);
|
||||||
|
|
||||||
if (ShouldSetFlags(context))
|
if (ShouldSetFlags(context))
|
||||||
@ -546,6 +570,46 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitHsub8(context, unsigned: true);
|
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)
|
public static void Usat(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32Sat op = (OpCode32Sat)context.CurrOp;
|
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)
|
private static void EmitAluStore(ArmEmitterContext context, Operand value)
|
||||||
{
|
{
|
||||||
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
IOpCode32Alu op = (IOpCode32Alu)context.CurrOp;
|
||||||
|
@ -403,19 +403,25 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
return EmitHostMappedPointer(context, address);
|
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
|
Operand ptBase = !context.HasPtc
|
||||||
? Const(context.Memory.PageTablePointer.ToInt64())
|
? Const(context.Memory.PageTablePointer.ToInt64())
|
||||||
: Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol);
|
: Const(context.Memory.PageTablePointer.ToInt64(), Ptc.PageTableSymbol);
|
||||||
|
|
||||||
Operand ptOffset = context.ShiftRightUI(address, Const(PageBits));
|
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)))));
|
return context.Add(address, context.Load(OperandType.I64, context.Add(ptBase, context.ShiftLeft(ptOffset, Const(3)))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2426,7 +2426,11 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
|
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));
|
context.Copy(GetVec(op.Rd), context.VectorZeroUpper96(res));
|
||||||
}
|
}
|
||||||
@ -2451,7 +2455,11 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
else if (Optimizations.FastFP && Optimizations.UseSse41 && sizeF == 0)
|
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)
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
|
@ -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)
|
public static void Vpaddl(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
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);
|
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)
|
public static void Vqsub(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
OpCode32SimdReg op = (OpCode32SimdReg)context.CurrOp;
|
||||||
|
@ -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).
|
// VRINTZ (floating-point).
|
||||||
public static void Vrint_Z(ArmEmitterContext context)
|
public static void Vrint_Z(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
|
@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
|
|||||||
context.Copy(GetVecA32(op.Qd), res);
|
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
|
// Narrow
|
||||||
|
|
||||||
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
||||||
|
@ -191,6 +191,26 @@ namespace ARMeilleure.Instructions
|
|||||||
context.Copy(GetVecA32(op.Qd), res);
|
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)
|
public static void Vtbl(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;
|
OpCode32SimdTbl op = (OpCode32SimdTbl)context.CurrOp;
|
||||||
|
@ -116,7 +116,7 @@ namespace ARMeilleure.Instructions
|
|||||||
}
|
}
|
||||||
else if (shift >= eSize)
|
else if (shift >= eSize)
|
||||||
{
|
{
|
||||||
if ((op.RegisterSize == RegisterSize.Simd64))
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
{
|
{
|
||||||
Operand res = context.VectorZeroUpper64(GetVec(op.Rd));
|
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)
|
public static void Sqshrn_S(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
if (Optimizations.UseAdvSimd)
|
if (Optimizations.UseAdvSimd)
|
||||||
@ -1593,6 +1603,99 @@ namespace ARMeilleure.Instructions
|
|||||||
Saturating = 1 << 3,
|
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)
|
private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None)
|
||||||
{
|
{
|
||||||
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
|
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
|
||||||
|
@ -106,6 +106,38 @@ namespace ARMeilleure.Instructions
|
|||||||
context.Copy(GetVecA32(op.Qd), res);
|
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)
|
public static void Vshr(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||||
@ -130,6 +162,36 @@ namespace ARMeilleure.Instructions
|
|||||||
EmitVectorUnaryNarrowOp32(context, (op1) => context.ShiftRightUI(op1, Const(shift)));
|
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)
|
public static void Vsra(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
OpCode32SimdShImm op = (OpCode32SimdShImm)context.CurrOp;
|
||||||
|
@ -384,7 +384,9 @@ namespace ARMeilleure.Instructions
|
|||||||
Sqrshrn_V,
|
Sqrshrn_V,
|
||||||
Sqrshrun_S,
|
Sqrshrun_S,
|
||||||
Sqrshrun_V,
|
Sqrshrun_V,
|
||||||
|
Sqshl_Si,
|
||||||
Sqshl_V,
|
Sqshl_V,
|
||||||
|
Sqshl_Vi,
|
||||||
Sqshrn_S,
|
Sqshrn_S,
|
||||||
Sqshrn_V,
|
Sqshrn_V,
|
||||||
Sqshrun_S,
|
Sqshrun_S,
|
||||||
@ -525,6 +527,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Pld,
|
Pld,
|
||||||
Pop,
|
Pop,
|
||||||
Push,
|
Push,
|
||||||
|
Qadd16,
|
||||||
Rev,
|
Rev,
|
||||||
Revsh,
|
Revsh,
|
||||||
Rsb,
|
Rsb,
|
||||||
@ -569,6 +572,10 @@ namespace ARMeilleure.Instructions
|
|||||||
Umaal,
|
Umaal,
|
||||||
Umlal,
|
Umlal,
|
||||||
Umull,
|
Umull,
|
||||||
|
Uqadd16,
|
||||||
|
Uqadd8,
|
||||||
|
Uqsub16,
|
||||||
|
Uqsub8,
|
||||||
Usat,
|
Usat,
|
||||||
Usat16,
|
Usat16,
|
||||||
Usub8,
|
Usub8,
|
||||||
@ -635,6 +642,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vorn,
|
Vorn,
|
||||||
Vorr,
|
Vorr,
|
||||||
Vpadd,
|
Vpadd,
|
||||||
|
Vpadal,
|
||||||
Vpaddl,
|
Vpaddl,
|
||||||
Vpmax,
|
Vpmax,
|
||||||
Vpmin,
|
Vpmin,
|
||||||
@ -642,6 +650,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vqdmulh,
|
Vqdmulh,
|
||||||
Vqmovn,
|
Vqmovn,
|
||||||
Vqmovun,
|
Vqmovun,
|
||||||
|
Vqrdmulh,
|
||||||
Vqrshrn,
|
Vqrshrn,
|
||||||
Vqrshrun,
|
Vqrshrun,
|
||||||
Vqshrn,
|
Vqshrn,
|
||||||
@ -654,6 +663,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vrintm,
|
Vrintm,
|
||||||
Vrintn,
|
Vrintn,
|
||||||
Vrintp,
|
Vrintp,
|
||||||
|
Vrintr,
|
||||||
Vrintx,
|
Vrintx,
|
||||||
Vrshr,
|
Vrshr,
|
||||||
Vrshrn,
|
Vrshrn,
|
||||||
@ -662,6 +672,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vshll,
|
Vshll,
|
||||||
Vshr,
|
Vshr,
|
||||||
Vshrn,
|
Vshrn,
|
||||||
|
Vsli,
|
||||||
Vst1,
|
Vst1,
|
||||||
Vst2,
|
Vst2,
|
||||||
Vst3,
|
Vst3,
|
||||||
@ -678,6 +689,7 @@ namespace ARMeilleure.Instructions
|
|||||||
Vsub,
|
Vsub,
|
||||||
Vsubl,
|
Vsubl,
|
||||||
Vsubw,
|
Vsubw,
|
||||||
|
Vswp,
|
||||||
Vtbl,
|
Vtbl,
|
||||||
Vtrn,
|
Vtrn,
|
||||||
Vtst,
|
Vtst,
|
||||||
|
@ -91,54 +91,54 @@ namespace ARMeilleure.Instructions
|
|||||||
#region "Read"
|
#region "Read"
|
||||||
public static byte ReadByte(ulong address)
|
public static byte ReadByte(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<byte>(address);
|
return GetMemoryManager().ReadGuest<byte>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ushort ReadUInt16(ulong address)
|
public static ushort ReadUInt16(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<ushort>(address);
|
return GetMemoryManager().ReadGuest<ushort>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static uint ReadUInt32(ulong address)
|
public static uint ReadUInt32(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<uint>(address);
|
return GetMemoryManager().ReadGuest<uint>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ulong ReadUInt64(ulong address)
|
public static ulong ReadUInt64(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<ulong>(address);
|
return GetMemoryManager().ReadGuest<ulong>(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static V128 ReadVector128(ulong address)
|
public static V128 ReadVector128(ulong address)
|
||||||
{
|
{
|
||||||
return GetMemoryManager().ReadTracked<V128>(address);
|
return GetMemoryManager().ReadGuest<V128>(address);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region "Write"
|
#region "Write"
|
||||||
public static void WriteByte(ulong address, byte value)
|
public static void WriteByte(ulong address, byte value)
|
||||||
{
|
{
|
||||||
GetMemoryManager().Write(address, value);
|
GetMemoryManager().WriteGuest(address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteUInt16(ulong address, ushort 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)
|
public static void WriteUInt32(ulong address, uint value)
|
||||||
{
|
{
|
||||||
GetMemoryManager().Write(address, value);
|
GetMemoryManager().WriteGuest(address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void WriteUInt64(ulong address, ulong 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)
|
public static void WriteVector128(ulong address, V128 value)
|
||||||
{
|
{
|
||||||
GetMemoryManager().Write(address, value);
|
GetMemoryManager().WriteGuest(address, value);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
@ -4,7 +4,5 @@ namespace ARMeilleure.Memory
|
|||||||
{
|
{
|
||||||
IJitMemoryBlock Allocate(ulong size);
|
IJitMemoryBlock Allocate(ulong size);
|
||||||
IJitMemoryBlock Reserve(ulong size);
|
IJitMemoryBlock Reserve(ulong size);
|
||||||
|
|
||||||
ulong GetPageSize();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,17 @@ namespace ARMeilleure.Memory
|
|||||||
/// <returns>The data</returns>
|
/// <returns>The data</returns>
|
||||||
T ReadTracked<T>(ulong va) where T : unmanaged;
|
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>
|
/// <summary>
|
||||||
/// Writes data to CPU mapped memory.
|
/// Writes data to CPU mapped memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -36,6 +47,17 @@ namespace ARMeilleure.Memory
|
|||||||
/// <param name="value">Data to be written</param>
|
/// <param name="value">Data to be written</param>
|
||||||
void Write<T>(ulong va, T value) where T : unmanaged;
|
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>
|
/// <summary>
|
||||||
/// Gets a read-only span of data from CPU mapped memory.
|
/// Gets a read-only span of data from CPU mapped memory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -35,18 +35,29 @@ namespace ARMeilleure.Memory
|
|||||||
/// Allows invalid access from JIT code to the rest of the program, but is faster.
|
/// Allows invalid access from JIT code to the rest of the program, but is faster.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
HostMappedUnsafe,
|
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)
|
public static bool IsHostMapped(this MemoryManagerType type)
|
||||||
{
|
{
|
||||||
return type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
|
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)
|
public static bool IsHostMappedOrTracked(this MemoryManagerType type)
|
||||||
{
|
{
|
||||||
return type == MemoryManagerType.HostTracked || type == MemoryManagerType.HostMapped || type == MemoryManagerType.HostMappedUnsafe;
|
return type.IsHostMapped() || type.IsHostTracked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,63 +1,14 @@
|
|||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.Memory;
|
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using ARMeilleure.Translation.Cache;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||||
|
|
||||||
namespace ARMeilleure.Signal
|
namespace ARMeilleure.Signal
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
public static class NativeSignalHandlerGenerator
|
||||||
struct SignalHandlerRange
|
|
||||||
{
|
{
|
||||||
public int IsActive;
|
public const int MaxTrackedRanges = 8;
|
||||||
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;
|
|
||||||
|
|
||||||
private const int StructAddressOffset = 0;
|
private const int StructAddressOffset = 0;
|
||||||
private const int StructWriteOffset = 4;
|
private const int StructWriteOffset = 4;
|
||||||
@ -70,124 +21,7 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
||||||
|
|
||||||
private static ulong _pageSize;
|
private static Operand EmitGenericRegionCheck(EmitterContext context, IntPtr signalStructPtr, Operand faultAddress, Operand isWrite, int rangeStructSize)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
Operand inRegionLocal = context.AllocateLocal(OperandType.I32);
|
Operand inRegionLocal = context.AllocateLocal(OperandType.I32);
|
||||||
context.Copy(inRegionLocal, Const(0));
|
context.Copy(inRegionLocal, Const(0));
|
||||||
@ -196,7 +30,7 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
for (int i = 0; i < MaxTrackedRanges; i++)
|
for (int i = 0; i < MaxTrackedRanges; i++)
|
||||||
{
|
{
|
||||||
ulong rangeBaseOffset = (ulong)(RangeOffset + i * Unsafe.SizeOf<SignalHandlerRange>());
|
ulong rangeBaseOffset = (ulong)(RangeOffset + i * rangeStructSize);
|
||||||
|
|
||||||
Operand nextLabel = Label();
|
Operand nextLabel = Label();
|
||||||
|
|
||||||
@ -210,13 +44,12 @@ namespace ARMeilleure.Signal
|
|||||||
// Is the fault address within this tracked region?
|
// Is the fault address within this tracked region?
|
||||||
Operand inRange = context.BitwiseAnd(
|
Operand inRange = context.BitwiseAnd(
|
||||||
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
|
context.ICompare(faultAddress, rangeAddress, Comparison.GreaterOrEqualUI),
|
||||||
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI)
|
context.ICompare(faultAddress, rangeEndAddress, Comparison.LessUI));
|
||||||
);
|
|
||||||
|
|
||||||
// Only call tracking if in range.
|
// Only call tracking if in range.
|
||||||
context.BranchIfFalse(nextLabel, inRange, BasicBlockFrequency.Cold);
|
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.
|
// 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));
|
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.
|
// Tracking action should be non-null to call it, otherwise assume false return.
|
||||||
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
context.BranchIfFalse(skipActionLabel, trackingActionPtr);
|
||||||
Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite);
|
Operand result = context.Call(trackingActionPtr, OperandType.I64, offset, Const(1UL), isWrite);
|
||||||
context.Copy(inRegionLocal, result);
|
context.Copy(inRegionLocal, context.ICompareNotEqual(result, Const(0UL)));
|
||||||
|
|
||||||
|
GenerateFaultAddressPatchCode(context, faultAddress, result);
|
||||||
|
|
||||||
context.MarkLabel(skipActionLabel);
|
context.MarkLabel(skipActionLabel);
|
||||||
|
|
||||||
@ -269,8 +104,7 @@ namespace ARMeilleure.Signal
|
|||||||
Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(EsrOffset)));
|
Operand esr = context.Load(OperandType.I64, context.Add(ctxPtr, Const(EsrOffset)));
|
||||||
return context.BitwiseAnd(esr, Const(0x40ul));
|
return context.BitwiseAnd(esr, Const(0x40ul));
|
||||||
}
|
}
|
||||||
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
|
||||||
{
|
{
|
||||||
const ulong ErrOffset = 4; // __es.__err
|
const ulong ErrOffset = 4; // __es.__err
|
||||||
Operand err = context.Load(OperandType.I64, context.Add(ctxPtr, Const(ErrOffset)));
|
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)));
|
Operand esr = context.Load(OperandType.I64, context.Add(auxPtr, Const(8ul)));
|
||||||
return context.BitwiseAnd(esr, Const(0x40ul));
|
return context.BitwiseAnd(esr, Const(0x40ul));
|
||||||
}
|
}
|
||||||
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
|
||||||
{
|
{
|
||||||
const int ErrOffset = 192; // uc_mcontext.gregs[REG_ERR]
|
const int ErrOffset = 192; // uc_mcontext.gregs[REG_ERR]
|
||||||
Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(ErrOffset)));
|
Operand err = context.Load(OperandType.I64, context.Add(ucontextPtr, Const(ErrOffset)));
|
||||||
@ -322,7 +155,7 @@ namespace ARMeilleure.Signal
|
|||||||
throw new PlatformNotSupportedException();
|
throw new PlatformNotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UnixExceptionHandler GenerateUnixSignalHandler(IntPtr signalStructPtr)
|
public static byte[] GenerateUnixSignalHandler(IntPtr signalStructPtr, int rangeStructSize)
|
||||||
{
|
{
|
||||||
EmitterContext context = new();
|
EmitterContext context = new();
|
||||||
|
|
||||||
@ -335,7 +168,7 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
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();
|
Operand endLabel = Label();
|
||||||
|
|
||||||
@ -367,10 +200,10 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
OperandType[] argTypes = new OperandType[] { OperandType.I32, OperandType.I64, OperandType.I64 };
|
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();
|
EmitterContext context = new();
|
||||||
|
|
||||||
@ -399,7 +232,7 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
Operand isWrite = context.ICompareNotEqual(writeFlag, Const(0L)); // Normalize to 0/1.
|
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();
|
Operand endLabel = Label();
|
||||||
|
|
||||||
@ -421,7 +254,88 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
OperandType[] argTypes = new OperandType[] { OperandType.I64 };
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ using ARMeilleure.IntermediateRepresentation;
|
|||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using Ryujinx.Common.Memory.PartialUnmaps;
|
using Ryujinx.Common.Memory.PartialUnmaps;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||||
|
|
||||||
namespace ARMeilleure.Signal
|
namespace ARMeilleure.Signal
|
||||||
@ -10,8 +10,28 @@ namespace ARMeilleure.Signal
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods.
|
/// Methods to handle signals caused by partial unmaps. See the structs for C# implementations of the methods.
|
||||||
/// </summary>
|
/// </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)
|
public static Operand EmitRetryFromAccessViolation(EmitterContext context)
|
||||||
{
|
{
|
||||||
IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState;
|
IntPtr partialRemapStatePtr = PartialUnmapState.GlobalState;
|
||||||
@ -20,7 +40,7 @@ namespace ARMeilleure.Signal
|
|||||||
// Get the lock first.
|
// Get the lock first.
|
||||||
EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
|
EmitNativeReaderLockAcquire(context, IntPtr.Add(partialRemapStatePtr, PartialUnmapState.PartialUnmapLockOffset));
|
||||||
|
|
||||||
IntPtr getCurrentThreadId = WindowsSignalHandlerRegistration.GetCurrentThreadIdFunc();
|
IntPtr getCurrentThreadId = GetCurrentThreadIdFunc();
|
||||||
Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32);
|
Operand threadId = context.Call(Const((ulong)getCurrentThreadId), OperandType.I32);
|
||||||
Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0));
|
Operand threadIndex = EmitThreadLocalMapIntGetOrReserve(context, localCountsPtr, threadId, Const(0));
|
||||||
|
|
||||||
@ -137,17 +157,6 @@ namespace ARMeilleure.Signal
|
|||||||
return context.Add(structsPtr, context.SignExtend32(OperandType.I64, offset));
|
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)
|
private static void EmitAtomicAddI32(EmitterContext context, Operand ptr, Operand additive)
|
||||||
{
|
{
|
||||||
Operand loop = Label();
|
Operand loop = Label();
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -25,7 +25,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
private static ReservedRegion _jitRegion;
|
private static ReservedRegion _jitRegion;
|
||||||
private static JitCacheInvalidation _jitCacheInvalidator;
|
private static JitCacheInvalidation _jitCacheInvalidator;
|
||||||
|
|
||||||
private static CacheMemoryAllocator _cacheAllocator;
|
private static List<CacheMemoryAllocator> _cacheAllocators = [];
|
||||||
|
|
||||||
private static readonly List<CacheEntry> _cacheEntries = new();
|
private static readonly List<CacheEntry> _cacheEntries = new();
|
||||||
|
|
||||||
@ -42,31 +42,42 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
public static void Initialize(IJitMemoryAllocator allocator)
|
public static void Initialize(IJitMemoryAllocator allocator)
|
||||||
{
|
{
|
||||||
if (_initialized)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
{
|
{
|
||||||
if (_initialized)
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstRegion = new ReservedRegion(allocator, CacheSize);
|
|
||||||
|
|
||||||
|
|
||||||
_jitRegions.Add(firstRegion);
|
|
||||||
_activeRegionIndex = 0;
|
_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())
|
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS() && !OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
JitUnwindWindows.InstallFunctionTableHandler(
|
JitUnwindWindows.InstallFunctionTableHandler(
|
||||||
@ -106,8 +117,15 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
if (OperatingSystem.IsIOS())
|
if (OperatingSystem.IsIOS())
|
||||||
{
|
{
|
||||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
if (deferProtect)
|
||||||
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
{
|
||||||
|
_deferredRxProtect.Enqueue((funcOffset, code.Length));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReprotectAsExecutable(targetRegion, funcOffset, code.Length);
|
||||||
|
JitSupportDarwinAot.Invalidate(funcPtr, (ulong)code.Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
else if (OperatingSystem.IsMacOS()&& RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
@ -162,7 +180,7 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||||
{
|
{
|
||||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
_cacheAllocators[_activeRegionIndex].Free(funcOffset, AlignCodeSize(entry.Size));
|
||||||
_cacheEntries.RemoveAt(entryIndex);
|
_cacheEntries.RemoveAt(entryIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,16 +220,12 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
alignment = 0x4000;
|
alignment = 0x4000;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = _activeRegionIndex; i < _jitRegions.Count; i++)
|
int allocOffset = _cacheAllocators[_activeRegionIndex].Allocate(ref codeSize, alignment);
|
||||||
{
|
|
||||||
int allocOffset = _cacheAllocator.Allocate(ref codeSize, alignment);
|
|
||||||
|
|
||||||
if (allocOffset >= 0)
|
if (allocOffset >= 0)
|
||||||
{
|
{
|
||||||
_jitRegions[i].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
_jitRegions[_activeRegionIndex].ExpandIfNeeded((ulong)allocOffset + (ulong)codeSize);
|
||||||
_activeRegionIndex = i;
|
return allocOffset;
|
||||||
return allocOffset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int exhaustedRegion = _activeRegionIndex;
|
int exhaustedRegion = _activeRegionIndex;
|
||||||
@ -221,9 +235,11 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
int newRegionNumber = _activeRegionIndex;
|
int newRegionNumber = _activeRegionIndex;
|
||||||
|
|
||||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
Logger.Info?.Print(LogClass.Cpu, $"JIT Cache Region {exhaustedRegion} exhausted, creating new Cache Region {_activeRegionIndex} ({((long)(_activeRegionIndex + 1) * CacheSize)} Total Allocation).");
|
||||||
|
|
||||||
int allocOffsetNew = _cacheAllocator.Allocate(ref codeSize, alignment);
|
_cacheAllocators.Add(new CacheMemoryAllocator(CacheSize));
|
||||||
|
|
||||||
|
int allocOffsetNew = _cacheAllocators[_activeRegionIndex].Allocate(ref codeSize, alignment);
|
||||||
if (allocOffsetNew < 0)
|
if (allocOffsetNew < 0)
|
||||||
{
|
{
|
||||||
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
throw new OutOfMemoryException("Failed to allocate in new Cache Region!");
|
||||||
|
@ -11,7 +11,7 @@ namespace ARMeilleure.Translation
|
|||||||
private int[] _postOrderMap;
|
private int[] _postOrderMap;
|
||||||
|
|
||||||
public int LocalsCount { get; private set; }
|
public int LocalsCount { get; private set; }
|
||||||
public BasicBlock Entry { get; }
|
public BasicBlock Entry { get; private set; }
|
||||||
public IntrusiveList<BasicBlock> Blocks { get; }
|
public IntrusiveList<BasicBlock> Blocks { get; }
|
||||||
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
|
public BasicBlock[] PostOrderBlocks => _postOrderBlocks;
|
||||||
public int[] PostOrderMap => _postOrderMap;
|
public int[] PostOrderMap => _postOrderMap;
|
||||||
@ -34,6 +34,15 @@ namespace ARMeilleure.Translation
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateEntry(BasicBlock newEntry)
|
||||||
|
{
|
||||||
|
newEntry.AddSuccessor(Entry);
|
||||||
|
|
||||||
|
Entry = newEntry;
|
||||||
|
Blocks.AddFirst(newEntry);
|
||||||
|
Update();
|
||||||
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
RemoveUnreachableBlocks(Blocks);
|
RemoveUnreachableBlocks(Blocks);
|
||||||
|
@ -30,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\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 ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@ -858,8 +858,14 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
Stopwatch sw = Stopwatch.StartNew();
|
Stopwatch sw = Stopwatch.StartNew();
|
||||||
|
|
||||||
threads.ForEach((thread) => thread.Start());
|
foreach (var thread in threads)
|
||||||
threads.ForEach((thread) => thread.Join());
|
{
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
foreach (var thread in threads)
|
||||||
|
{
|
||||||
|
thread.Join();
|
||||||
|
}
|
||||||
|
|
||||||
threads.Clear();
|
threads.Clear();
|
||||||
|
|
||||||
|
@ -89,6 +89,17 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
public static void RunPass(ControlFlowGraph cfg, ExecutionMode mode)
|
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.
|
// Compute local register inputs and outputs used inside blocks.
|
||||||
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
|
RegisterMask[] localInputs = new RegisterMask[cfg.Blocks.Count];
|
||||||
RegisterMask[] localOutputs = 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.
|
// 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.
|
// 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 vecMask = globalInputs[block.Index].VecMask;
|
||||||
long intMask = globalInputs[block.Index].IntMask;
|
long intMask = globalInputs[block.Index].IntMask;
|
||||||
|
@ -57,9 +57,6 @@ namespace ARMeilleure.Translation
|
|||||||
private Thread[] _backgroundTranslationThreads;
|
private Thread[] _backgroundTranslationThreads;
|
||||||
private volatile int _threadCount;
|
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)
|
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
|
||||||
{
|
{
|
||||||
_allocator = allocator;
|
_allocator = allocator;
|
||||||
@ -79,11 +76,6 @@ namespace ARMeilleure.Translation
|
|||||||
Stubs = new TranslatorStubs(FunctionTable);
|
Stubs = new TranslatorStubs(FunctionTable);
|
||||||
|
|
||||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||||
|
|
||||||
if (memory.Type.IsHostMappedOrTracked())
|
|
||||||
{
|
|
||||||
NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
public IPtcLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
|
||||||
@ -105,8 +97,6 @@ namespace ARMeilleure.Translation
|
|||||||
{
|
{
|
||||||
if (Interlocked.Increment(ref _threadCount) == 1)
|
if (Interlocked.Increment(ref _threadCount) == 1)
|
||||||
{
|
{
|
||||||
IsReadyForTranslation.WaitOne();
|
|
||||||
|
|
||||||
if (_ptc.State == PtcState.Enabled)
|
if (_ptc.State == PtcState.Enabled)
|
||||||
{
|
{
|
||||||
Debug.Assert(Functions.Count == 0);
|
Debug.Assert(Functions.Count == 0);
|
||||||
|
@ -80,7 +80,10 @@ namespace ARMeilleure.Translation
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Monitor.Wait(Sync);
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
Monitor.Wait(Sync);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
src/MeloNX/MeloNX.xcconfig
Normal file
13
src/MeloNX/MeloNX.xcconfig
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// MeloNX.xcconfig
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 06/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
// Configuration settings file format documentation can be found at:
|
||||||
|
// https://help.apple.com/xcode/#/dev745c5c974
|
||||||
|
|
||||||
|
VERSION = 1.6.0
|
||||||
|
|
||||||
|
DOTNET = /usr/local/share/dotnet/dotnet
|
@ -25,12 +25,20 @@
|
|||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
4E0DED342D05695D00FEF007 /* SwiftUIJoystick in Frameworks */ = {isa = PBXBuildFile; productRef = 4E0DED332D05695D00FEF007 /* SwiftUIJoystick */; };
|
||||||
|
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */; };
|
||||||
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
4E8A80772D5FDD2D0041B48F /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||||
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
4EA5AE822D16807500AD0B9F /* SwiftSVG in Frameworks */ = {isa = PBXBuildFile; productRef = 4EA5AE812D16807500AD0B9F /* SwiftSVG */; };
|
||||||
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
CA8F9C322D3F5AB200D7E586 /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4E80AA622CD7122800029585 /* GameController.framework */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
4E2953AB2D803BC9000497CD /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = BD43C6212D1B248D003BBC42;
|
||||||
|
remoteInfo = com.Stossy11.MeloNX.RyujinxAg;
|
||||||
|
};
|
||||||
4E80A99E2CD6F54700029585 /* PBXContainerItemProxy */ = {
|
4E80A99E2CD6F54700029585 /* PBXContainerItemProxy */ = {
|
||||||
isa = PBXContainerItemProxy;
|
isa = PBXContainerItemProxy;
|
||||||
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
containerPortal = 4E80A9852CD6F54500029585 /* Project object */;
|
||||||
@ -78,12 +86,12 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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; };
|
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; };
|
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; };
|
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; };
|
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; };
|
4E80AA622CD7122800029585 /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
|
||||||
5650564A2D2A758600C8BB1E /* dotnet.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = dotnet.xcconfig; 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>"; };
|
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 */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@ -222,7 +230,7 @@
|
|||||||
4E80A9842CD6F54500029585 = {
|
4E80A9842CD6F54500029585 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5650564A2D2A758600C8BB1E /* dotnet.xcconfig */,
|
4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */,
|
||||||
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
|
BD43C6282D1B2514003BBC42 /* Ryujinx.Headless.SDL2.dylib */,
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */,
|
4E80A98F2CD6F54500029585 /* MeloNX */,
|
||||||
4E80A9A02CD6F54700029585 /* MeloNXTests */,
|
4E80A9A02CD6F54700029585 /* MeloNXTests */,
|
||||||
@ -260,7 +268,7 @@
|
|||||||
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
|
buildConfigurationList = BD43C61E2D1B23AB003BBC42 /* Build configuration list for PBXLegacyTarget "Ryujinx" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
);
|
);
|
||||||
buildToolPath = /usr/local/share/dotnet/dotnet;
|
buildToolPath = "$(DOTNET)";
|
||||||
buildWorkingDirectory = "$(SRCROOT)/../..";
|
buildWorkingDirectory = "$(SRCROOT)/../..";
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
@ -286,6 +294,7 @@
|
|||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
dependencies = (
|
dependencies = (
|
||||||
|
4E2953AC2D803BC9000497CD /* PBXTargetDependency */,
|
||||||
);
|
);
|
||||||
fileSystemSynchronizedGroups = (
|
fileSystemSynchronizedGroups = (
|
||||||
4E80A98F2CD6F54500029585 /* MeloNX */,
|
4E80A98F2CD6F54500029585 /* MeloNX */,
|
||||||
@ -353,7 +362,7 @@
|
|||||||
attributes = {
|
attributes = {
|
||||||
BuildIndependentTargetsInParallel = 1;
|
BuildIndependentTargetsInParallel = 1;
|
||||||
LastSwiftUpdateCheck = 1620;
|
LastSwiftUpdateCheck = 1620;
|
||||||
LastUpgradeCheck = 1610;
|
LastUpgradeCheck = 1620;
|
||||||
TargetAttributes = {
|
TargetAttributes = {
|
||||||
4E80A98C2CD6F54500029585 = {
|
4E80A98C2CD6F54500029585 = {
|
||||||
CreatedOnToolsVersion = 16.1;
|
CreatedOnToolsVersion = 16.1;
|
||||||
@ -406,6 +415,7 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
4E12B23C2D797CFA00FB2271 /* MeloNX.xcconfig in Resources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -443,7 +453,7 @@
|
|||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
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 */
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
@ -472,6 +482,11 @@
|
|||||||
/* End PBXSourcesBuildPhase section */
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXTargetDependency section */
|
/* Begin PBXTargetDependency section */
|
||||||
|
4E2953AC2D803BC9000497CD /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = BD43C6212D1B248D003BBC42 /* com.Stossy11.MeloNX.RyujinxAg */;
|
||||||
|
targetProxy = 4E2953AB2D803BC9000497CD /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
|
4E80A99F2CD6F54700029585 /* PBXTargetDependency */ = {
|
||||||
isa = PBXTargetDependency;
|
isa = PBXTargetDependency;
|
||||||
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
target = 4E80A98C2CD6F54500029585 /* MeloNX */;
|
||||||
@ -492,6 +507,7 @@
|
|||||||
/* Begin XCBuildConfiguration section */
|
/* Begin XCBuildConfiguration section */
|
||||||
4E80A9AF2CD6F54700029585 /* Debug */ = {
|
4E80A9AF2CD6F54700029585 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@ -561,6 +577,7 @@
|
|||||||
};
|
};
|
||||||
4E80A9B02CD6F54700029585 /* Release */ = {
|
4E80A9B02CD6F54700029585 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@ -626,6 +643,7 @@
|
|||||||
};
|
};
|
||||||
4E80A9B22CD6F54700029585 /* Debug */ = {
|
4E80A9B22CD6F54700029585 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
@ -674,8 +692,28 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(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;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
||||||
@ -761,8 +799,51 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/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",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.3.0;
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@ -774,6 +855,7 @@
|
|||||||
};
|
};
|
||||||
4E80A9B32CD6F54700029585 /* Release */ = {
|
4E80A9B32CD6F54700029585 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
@ -822,8 +904,28 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(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;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = MeloNX/Info.plist;
|
INFOPLIST_FILE = MeloNX/Info.plist;
|
||||||
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
INFOPLIST_KEY_GCSupportsControllerUserInteraction = YES;
|
||||||
@ -909,8 +1011,51 @@
|
|||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/Dynamic\\ Libraries",
|
||||||
|
"$(PROJECT_DIR)/MeloNX/Dependencies/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",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 1.3.0;
|
MARKETING_VERSION = "$(VERSION)";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
PRODUCT_BUNDLE_IDENTIFIER = com.stossy11.MeloNX;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@ -922,6 +1067,7 @@
|
|||||||
};
|
};
|
||||||
4E80A9B52CD6F54700029585 /* Debug */ = {
|
4E80A9B52CD6F54700029585 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@ -941,6 +1087,7 @@
|
|||||||
};
|
};
|
||||||
4E80A9B62CD6F54700029585 /* Release */ = {
|
4E80A9B62CD6F54700029585 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
@ -960,6 +1107,7 @@
|
|||||||
};
|
};
|
||||||
4E80A9B82CD6F54700029585 /* Debug */ = {
|
4E80A9B82CD6F54700029585 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@ -977,6 +1125,7 @@
|
|||||||
};
|
};
|
||||||
4E80A9B92CD6F54700029585 /* Release */ = {
|
4E80A9B92CD6F54700029585 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
@ -994,7 +1143,9 @@
|
|||||||
};
|
};
|
||||||
BD43C61F2D1B23AB003BBC42 /* Debug */ = {
|
BD43C61F2D1B23AB003BBC42 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEBUGGING_SYMBOLS = YES;
|
DEBUGGING_SYMBOLS = YES;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
@ -1010,7 +1161,9 @@
|
|||||||
};
|
};
|
||||||
BD43C6202D1B23AB003BBC42 /* Release */ = {
|
BD43C6202D1B23AB003BBC42 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
@ -1022,6 +1175,7 @@
|
|||||||
};
|
};
|
||||||
BD43C6232D1B248D003BBC42 /* Debug */ = {
|
BD43C6232D1B248D003BBC42 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
@ -1031,6 +1185,7 @@
|
|||||||
};
|
};
|
||||||
BD43C6242D1B248D003BBC42 /* Release */ = {
|
BD43C6242D1B248D003BBC42 /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 4E12B23B2D797CFA00FB2271 /* MeloNX.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
DEVELOPMENT_TEAM = 95J8WZ4TN8;
|
||||||
|
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1610"
|
LastUpgradeVersion = "1620"
|
||||||
version = "1.7">
|
version = "1.7">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
@ -64,7 +64,6 @@
|
|||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
enableGPUValidationMode = "1"
|
enableGPUValidationMode = "1"
|
||||||
showGraphicsOverview = "Yes"
|
|
||||||
allowLocationSimulation = "YES">
|
allowLocationSimulation = "YES">
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
@ -100,5 +99,24 @@
|
|||||||
<ArchiveAction
|
<ArchiveAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Release"
|
||||||
revealArchiveInOrganizer = "YES">
|
revealArchiveInOrganizer = "YES">
|
||||||
|
<PreActions>
|
||||||
|
<ExecutionAction
|
||||||
|
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||||
|
<ActionContent
|
||||||
|
title = "Run Script"
|
||||||
|
scriptText = "REPO_DIR="$(cd "${SRCROOT}/../../" && pwd)" SCRIPT_PATH="$REPO_DIR/distribution/ios/set_current_version.sh" echo "hi" sh "${SCRIPT_PATH}" "
|
||||||
|
shellToInvoke = "/bin/bash">
|
||||||
|
<EnvironmentBuildable>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "4E80A98C2CD6F54500029585"
|
||||||
|
BuildableName = "MeloNX.app"
|
||||||
|
BlueprintName = "MeloNX"
|
||||||
|
ReferencedContainer = "container:MeloNX.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</EnvironmentBuildable>
|
||||||
|
</ActionContent>
|
||||||
|
</ExecutionAction>
|
||||||
|
</PreActions>
|
||||||
</ArchiveAction>
|
</ArchiveAction>
|
||||||
</Scheme>
|
</Scheme>
|
||||||
|
@ -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="$(cd "${SRCROOT}/../../" && pwd)" SCRIPT_PATH="$REPO_DIR/distribution/ios/get_dotnet.sh" echo "Xcode is located at: $DEVELOPER_DIR" sh "${SCRIPT_PATH}" "
|
||||||
|
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>
|
@ -48,13 +48,21 @@ void install_firmware(const char* inputPtr);
|
|||||||
|
|
||||||
char* installed_firmware_version();
|
char* installed_firmware_version();
|
||||||
|
|
||||||
|
void set_native_window(void *layerPtr);
|
||||||
|
|
||||||
void stop_emulation();
|
void stop_emulation();
|
||||||
|
|
||||||
|
void initialize();
|
||||||
|
|
||||||
int main_ryujinx_sdl(int argc, char **argv);
|
int main_ryujinx_sdl(int argc, char **argv);
|
||||||
|
|
||||||
int get_current_fps();
|
int get_current_fps();
|
||||||
|
|
||||||
void initialize();
|
void touch_began(float x, float y, int index);
|
||||||
|
|
||||||
|
void touch_moved(float x, float y, int index);
|
||||||
|
|
||||||
|
void touch_ended(int index);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,19 @@
|
|||||||
|
|
||||||
import Foundation
|
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 {
|
func checkMemoryPermissions(at address: UnsafeRawPointer) -> Bool {
|
||||||
var region: vm_address_t = vm_address_t(UInt(bitPattern: address))
|
var region: vm_address_t = vm_address_t(UInt(bitPattern: address))
|
||||||
var regionSize: vm_size_t = 0
|
var regionSize: vm_size_t = 0
|
||||||
@ -27,8 +40,7 @@ func checkMemoryPermissions(at address: UnsafeRawPointer) -> Bool {
|
|||||||
|
|
||||||
return info.protection & VM_PROT_EXECUTE != 0
|
return info.protection & VM_PROT_EXECUTE != 0
|
||||||
}
|
}
|
||||||
|
func allocateTest() -> Bool {
|
||||||
func isJITEnabled() -> Bool {
|
|
||||||
let pageSize = sysconf(_SC_PAGESIZE)
|
let pageSize = sysconf(_SC_PAGESIZE)
|
||||||
let code: [UInt32] = [0x52800540, 0xD65F03C0]
|
let code: [UInt32] = [0x52800540, 0xD65F03C0]
|
||||||
|
|
||||||
@ -40,7 +52,6 @@ func isJITEnabled() -> Bool {
|
|||||||
munmap(jitMemory, pageSize)
|
munmap(jitMemory, pageSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
memcpy(jitMemory, code, code.count)
|
memcpy(jitMemory, code, code.count)
|
||||||
|
|
||||||
if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 {
|
if mprotect(jitMemory, pageSize, PROT_READ | PROT_EXEC) != 0 {
|
||||||
|
@ -11,6 +11,7 @@ func enableJITEB() {
|
|||||||
guard let bundleID = Bundle.main.bundleIdentifier else {
|
guard let bundleID = Bundle.main.bundleIdentifier else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let address = URL(string: "http://[fd00::]:9172/launch_app/\(bundleID)")!
|
let address = URL(string: "http://[fd00::]:9172/launch_app/\(bundleID)")!
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: address) { data, response, error in
|
let task = URLSession.shared.dataTask(with: address) { data, response, error in
|
||||||
@ -18,12 +19,13 @@ func enableJITEB() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
showLaunchAppAlert(jsonData: data!, in: UIApplication.shared.windows.last!.rootViewController!)
|
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
|
return
|
||||||
|
@ -78,8 +78,6 @@ class NativeController: Hashable {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a game controller for the virtual joystick
|
|
||||||
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
|
||||||
controller = SDL_GameControllerOpen(Int32(instanceID))
|
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||||
|
|
||||||
if controller == nil {
|
if controller == nil {
|
||||||
@ -91,10 +89,10 @@ class NativeController: Hashable {
|
|||||||
guard let gamepad = nativeController.extendedGamepad
|
guard let gamepad = nativeController.extendedGamepad
|
||||||
else { return }
|
else { return }
|
||||||
|
|
||||||
setupButtonChangeListener(gamepad.buttonA, for: .B)
|
setupButtonChangeListener(gamepad.buttonA, for: .A)
|
||||||
setupButtonChangeListener(gamepad.buttonB, for: .A)
|
setupButtonChangeListener(gamepad.buttonB, for: .B)
|
||||||
setupButtonChangeListener(gamepad.buttonX, for: .Y)
|
setupButtonChangeListener(gamepad.buttonX, for: .X)
|
||||||
setupButtonChangeListener(gamepad.buttonY, for: .X)
|
setupButtonChangeListener(gamepad.buttonY, for: .Y)
|
||||||
|
|
||||||
setupButtonChangeListener(gamepad.dpad.up, for: .dPadUp)
|
setupButtonChangeListener(gamepad.dpad.up, for: .dPadUp)
|
||||||
setupButtonChangeListener(gamepad.dpad.down, for: .dPadDown)
|
setupButtonChangeListener(gamepad.dpad.down, for: .dPadDown)
|
||||||
|
@ -45,7 +45,9 @@ class VirtualController {
|
|||||||
},
|
},
|
||||||
Rumble: { userdata, lowFreq, highFreq in
|
Rumble: { userdata, lowFreq, highFreq in
|
||||||
print("Rumble with \(lowFreq), \(highFreq)")
|
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
|
return 0
|
||||||
},
|
},
|
||||||
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
RumbleTriggers: { userdata, leftRumble, rightRumble in
|
||||||
@ -68,8 +70,6 @@ class VirtualController {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open a game controller for the virtual joystick
|
|
||||||
let joystick = SDL_JoystickFromInstanceID(instanceID)
|
|
||||||
controller = SDL_GameControllerOpen(Int32(instanceID))
|
controller = SDL_GameControllerOpen(Int32(instanceID))
|
||||||
|
|
||||||
if controller == nil {
|
if controller == nil {
|
||||||
@ -80,7 +80,6 @@ class VirtualController {
|
|||||||
|
|
||||||
static func rumble(lowFreq: Float, highFreq: Float, engine: CHHapticEngine? = nil) {
|
static func rumble(lowFreq: Float, highFreq: Float, engine: CHHapticEngine? = nil) {
|
||||||
do {
|
do {
|
||||||
// Low-frequency haptic pattern
|
|
||||||
let lowFreqPattern = try CHHapticPattern(events: [
|
let lowFreqPattern = try CHHapticPattern(events: [
|
||||||
CHHapticEvent(eventType: .hapticTransient, parameters: [
|
CHHapticEvent(eventType: .hapticTransient, parameters: [
|
||||||
CHHapticEventParameter(parameterID: .hapticIntensity, value: lowFreq),
|
CHHapticEventParameter(parameterID: .hapticIntensity, value: lowFreq),
|
||||||
@ -88,7 +87,7 @@ class VirtualController {
|
|||||||
], relativeTime: 0, duration: 0.2)
|
], relativeTime: 0, duration: 0.2)
|
||||||
], parameters: [])
|
], parameters: [])
|
||||||
|
|
||||||
// High-frequency haptic pattern
|
|
||||||
let highFreqPattern = try CHHapticPattern(events: [
|
let highFreqPattern = try CHHapticPattern(events: [
|
||||||
CHHapticEvent(eventType: .hapticTransient, parameters: [
|
CHHapticEvent(eventType: .hapticTransient, parameters: [
|
||||||
CHHapticEventParameter(parameterID: .hapticIntensity, value: highFreq),
|
CHHapticEventParameter(parameterID: .hapticIntensity, value: highFreq),
|
||||||
@ -96,12 +95,9 @@ class VirtualController {
|
|||||||
], relativeTime: 0.2, duration: 0.2)
|
], relativeTime: 0.2, duration: 0.2)
|
||||||
], parameters: [])
|
], parameters: [])
|
||||||
|
|
||||||
// Mutable engine
|
|
||||||
var engine = engine
|
var engine = engine
|
||||||
|
|
||||||
// If no engine passed, use device engine
|
|
||||||
if engine == nil {
|
if engine == nil {
|
||||||
// Create and start the haptic engine
|
|
||||||
if hapticEngine == nil {
|
if hapticEngine == nil {
|
||||||
hapticEngine = try CHHapticEngine()
|
hapticEngine = try CHHapticEngine()
|
||||||
try hapticEngine?.start()
|
try hapticEngine?.start()
|
||||||
@ -114,13 +110,11 @@ class VirtualController {
|
|||||||
return print("Error creating haptic patterns: hapticEngine is nil")
|
return print("Error creating haptic patterns: hapticEngine is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and play the low-frequency player
|
|
||||||
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
|
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
|
||||||
try lowFreqPlayer.start(atTime: 0)
|
try lowFreqPlayer.start(atTime: 0)
|
||||||
|
|
||||||
// Create and play the high-frequency player after a short delay
|
|
||||||
let highFreqPlayer = try engine.makePlayer(with: highFreqPattern)
|
let highFreqPlayer = try engine.makePlayer(with: highFreqPattern)
|
||||||
try highFreqPlayer.start(atTime: 0.2)
|
try highFreqPlayer.start(atTime: 0)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
print("Error creating haptic patterns: \(error)")
|
print("Error creating haptic patterns: \(error)")
|
||||||
@ -178,10 +172,10 @@ class VirtualController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum VirtualControllerButton: Int {
|
enum VirtualControllerButton: Int {
|
||||||
case B
|
|
||||||
case A
|
case A
|
||||||
case Y
|
case B
|
||||||
case X
|
case X
|
||||||
|
case Y
|
||||||
case back
|
case back
|
||||||
case guide
|
case guide
|
||||||
case start
|
case start
|
||||||
|
@ -1,104 +0,0 @@
|
|||||||
//
|
|
||||||
// Untitled.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 28/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import GameController
|
|
||||||
import UIKit
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var theWindow: UIWindow? = nil
|
|
||||||
extension UIWindow {
|
|
||||||
// Makes the SDLWindow use the current WindowScene instead of making its own window.
|
|
||||||
// Also waits for the window to append the on-screen controller
|
|
||||||
@objc func wdb_makeKeyAndVisible() {
|
|
||||||
let enabled = UserDefaults.standard.bool(forKey: "oldWindowCode")
|
|
||||||
|
|
||||||
if #unavailable(iOS 17.0), enabled {
|
|
||||||
self.windowScene = (UIApplication.shared.connectedScenes.first! as! UIWindowScene)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.wdb_makeKeyAndVisible()
|
|
||||||
theWindow = self
|
|
||||||
|
|
||||||
if #available(iOS 17, *) {
|
|
||||||
Ryujinx.shared.repeatuntilfindLayer()
|
|
||||||
} else if UserDefaults.standard.bool(forKey: "isVirtualController") && enabled {
|
|
||||||
waitForController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - iOS 16 and below Only
|
|
||||||
|
|
||||||
var hostingController: UIHostingController<ControllerView>?
|
|
||||||
func waitForController() {
|
|
||||||
guard let window = theWindow else { return }
|
|
||||||
|
|
||||||
// Function to search for an existing UIHostingController with ControllerView
|
|
||||||
func findGCControllerView(in view: UIView) -> UIHostingController<ControllerView>? {
|
|
||||||
if let hostingVC = view.next as? UIHostingController<ControllerView> {
|
|
||||||
return hostingVC
|
|
||||||
}
|
|
||||||
|
|
||||||
for subview in view.subviews {
|
|
||||||
if let found = findGCControllerView(in: subview) {
|
|
||||||
return found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
let controllerView = ControllerView()
|
|
||||||
let newHostingController = UIHostingController(rootView: controllerView)
|
|
||||||
|
|
||||||
hostingController = newHostingController
|
|
||||||
|
|
||||||
let containerView = newHostingController.view!
|
|
||||||
containerView.backgroundColor = .clear
|
|
||||||
containerView.frame = window.bounds
|
|
||||||
containerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
|
||||||
|
|
||||||
// Timer for controller
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
|
||||||
if findGCControllerView(in: window) == nil {
|
|
||||||
// Adds Virtual Controller Subview
|
|
||||||
window.addSubview(containerView)
|
|
||||||
window.bringSubviewToFront(containerView)
|
|
||||||
|
|
||||||
if let sdlWindow = SDL_GetWindowFromID(1) {
|
|
||||||
SDL_SetWindowPosition(sdlWindow, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Patches makeKeyAndVisible to wdb_makeKeyAndVisible
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -10,9 +10,7 @@ import Foundation
|
|||||||
|
|
||||||
class MTLHud {
|
class MTLHud {
|
||||||
|
|
||||||
var canMetalHud: Bool {
|
@Published var canMetalHud: Bool = false
|
||||||
return openMetalDylib()
|
|
||||||
}
|
|
||||||
|
|
||||||
var isEnabled: Bool {
|
var isEnabled: Bool {
|
||||||
if let getenv = getenv("MTL_HUD_ENABLED") {
|
if let getenv = getenv("MTL_HUD_ENABLED") {
|
||||||
@ -24,7 +22,17 @@ class MTLHud {
|
|||||||
static let shared = MTLHud()
|
static let shared = MTLHud()
|
||||||
|
|
||||||
private init() {
|
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") {
|
if UserDefaults.standard.bool(forKey: "MTL_HUD_ENABLED") {
|
||||||
enable()
|
enable()
|
||||||
} else {
|
} else {
|
||||||
@ -35,16 +43,15 @@ class MTLHud {
|
|||||||
func openMetalDylib() -> Bool {
|
func openMetalDylib() -> Bool {
|
||||||
let path = "/usr/lib/libMTLHud.dylib"
|
let path = "/usr/lib/libMTLHud.dylib"
|
||||||
|
|
||||||
// Load the dynamic library
|
|
||||||
if dlopen(path, RTLD_NOW) != nil {
|
if dlopen(path, RTLD_NOW) != nil {
|
||||||
// Library loaded successfully
|
|
||||||
print("Library loaded from \(path)")
|
print("Library loaded from \(path)")
|
||||||
|
canMetalHud = true
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
// Handle error
|
|
||||||
if let error = String(validatingUTF8: dlerror()) {
|
if let error = String(validatingUTF8: dlerror()) {
|
||||||
print("Error loading library: \(error)")
|
print("Error loading library: \(error)")
|
||||||
}
|
}
|
||||||
|
canMetalHud = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import GameController
|
import GameController
|
||||||
|
import MetalKit
|
||||||
|
import Metal
|
||||||
|
|
||||||
struct Controller: Identifiable, Hashable {
|
struct Controller: Identifiable, Hashable {
|
||||||
var id: String
|
var id: String
|
||||||
@ -37,7 +39,8 @@ class Ryujinx {
|
|||||||
@Published var controllerMap: [Controller] = []
|
@Published var controllerMap: [Controller] = []
|
||||||
@Published var metalLayer: CAMetalLayer? = nil
|
@Published var metalLayer: CAMetalLayer? = nil
|
||||||
@Published var firmwareversion = "0"
|
@Published var firmwareversion = "0"
|
||||||
@Published var emulationUIView = UIView()
|
@Published var emulationUIView: MeloMTKView? = nil
|
||||||
|
@Published var config: Ryujinx.Configuration? = nil
|
||||||
@Published var games: [Game] = []
|
@Published var games: [Game] = []
|
||||||
|
|
||||||
@Published var defMLContentSize: CGFloat?
|
@Published var defMLContentSize: CGFloat?
|
||||||
@ -140,9 +143,11 @@ class Ryujinx {
|
|||||||
throw RyujinxError.alreadyRunning
|
throw RyujinxError.alreadyRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
isRunning = true
|
self.config = config
|
||||||
|
|
||||||
RunLoop.current.perform {
|
RunLoop.current.perform { [self] in
|
||||||
|
|
||||||
|
isRunning = true
|
||||||
|
|
||||||
let url = URL(string: config.gamepath)
|
let url = URL(string: config.gamepath)
|
||||||
|
|
||||||
@ -156,15 +161,17 @@ class Ryujinx {
|
|||||||
var argvPtrs = cArgs
|
var argvPtrs = cArgs
|
||||||
|
|
||||||
// Start the emulation
|
// Start the emulation
|
||||||
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
|
if isRunning {
|
||||||
|
let result = main_ryujinx_sdl(Int32(args.count), &argvPtrs)
|
||||||
if result != 0 {
|
|
||||||
self.isRunning = false
|
|
||||||
if let accessing, accessing {
|
|
||||||
url!.stopAccessingSecurityScopedResource()
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RyujinxError.executionError(code: result)
|
if result != 0 {
|
||||||
|
self.isRunning = false
|
||||||
|
if let accessing, accessing {
|
||||||
|
url!.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
throw RyujinxError.executionError(code: result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
self.isRunning = false
|
self.isRunning = false
|
||||||
@ -180,6 +187,11 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isRunning = false
|
isRunning = false
|
||||||
|
|
||||||
|
self.emulationUIView = nil
|
||||||
|
self.metalLayer = nil
|
||||||
|
|
||||||
|
stop_emulation()
|
||||||
}
|
}
|
||||||
|
|
||||||
var running: Bool {
|
var running: Bool {
|
||||||
@ -334,26 +346,27 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apped any additional arguments
|
|
||||||
args.append(contentsOf: config.additionalArgs)
|
args.append(contentsOf: config.additionalArgs)
|
||||||
|
|
||||||
return args
|
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 {
|
func fetchFirmwareVersion() -> String {
|
||||||
do {
|
let firmwareVersionPointer = installed_firmware_version()
|
||||||
let firmwareVersionPointer = installed_firmware_version()
|
if let pointer = firmwareVersionPointer {
|
||||||
if let pointer = firmwareVersionPointer {
|
let firmwareVersion = String(cString: pointer)
|
||||||
let firmwareVersion = String(cString: pointer)
|
DispatchQueue.main.async {
|
||||||
DispatchQueue.main.async {
|
self.firmwareversion = firmwareVersion
|
||||||
self.firmwareversion = firmwareVersion
|
|
||||||
}
|
|
||||||
return firmwareVersion
|
|
||||||
}
|
}
|
||||||
|
return firmwareVersion
|
||||||
} catch {
|
|
||||||
print(error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "0"
|
return "0"
|
||||||
@ -482,66 +495,6 @@ class Ryujinx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func repeatuntilfindLayer() {
|
|
||||||
Task { @MainActor in
|
|
||||||
while self.metalLayer == nil {
|
|
||||||
let layer = self.getMetalLayer(nil)
|
|
||||||
|
|
||||||
if layer != nil {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.metalLayer = layer
|
|
||||||
}
|
|
||||||
self.metalLayer = layer
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
Thread.sleep(forTimeInterval: 0.1)
|
|
||||||
try await Task.sleep(nanoseconds: 100_000_000)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@MainActor
|
|
||||||
func getMetalLayer(_ window: OpaquePointer?) -> CAMetalLayer? {
|
|
||||||
var window = window
|
|
||||||
if window == nil {
|
|
||||||
window = SDL_GetWindowFromID(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var windowInfo = SDL_SysWMinfo()
|
|
||||||
SDL_GetWindowWMInfo(window, &windowInfo)
|
|
||||||
|
|
||||||
|
|
||||||
guard let uiWindow = windowInfo.info.uikit.window,
|
|
||||||
let rootView = uiWindow.takeUnretainedValue().rootViewController?.view else {
|
|
||||||
print("Unable to get root view")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findMetalLayer(in view: UIView) -> CAMetalLayer? {
|
|
||||||
if let metalLayer = view.layer as? CAMetalLayer {
|
|
||||||
return metalLayer
|
|
||||||
}
|
|
||||||
|
|
||||||
for subview in view.subviews {
|
|
||||||
if let metalLayer = findMetalLayer(in: subview) {
|
|
||||||
return metalLayer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if let existingLayer = findMetalLayer(in: rootView) {
|
|
||||||
print("Found Metal Layer")
|
|
||||||
return existingLayer
|
|
||||||
}
|
|
||||||
print("found nothing")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static func log(_ message: String) {
|
static func log(_ message: String) {
|
||||||
|
@ -32,7 +32,7 @@ struct LaunchGameIntentDef: AppIntent {
|
|||||||
|
|
||||||
let ryujinx = Ryujinx.shared.games
|
let ryujinx = Ryujinx.shared.games
|
||||||
|
|
||||||
let name = findClosestGameName(input: gameName, games: ryujinx.flatMap(\.titleName))
|
let name = findClosestGameName(input: gameName, games: ryujinx.compactMap(\.titleName))
|
||||||
|
|
||||||
let urlString = "melonx://game?name=\(name ?? gameName)"
|
let urlString = "melonx://game?name=\(name ?? gameName)"
|
||||||
print(urlString)
|
print(urlString)
|
||||||
|
@ -63,22 +63,15 @@ public struct Game: Identifiable, Equatable, Hashable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createImage(from gameInfo: GameInfo) -> UIImage? {
|
func createImage(from gameInfo: GameInfo) -> UIImage? {
|
||||||
// Access the struct
|
|
||||||
let gameInfoValue = gameInfo
|
let gameInfoValue = gameInfo
|
||||||
|
|
||||||
// Get the image data
|
|
||||||
let imageSize = Int(gameInfoValue.ImageSize)
|
let imageSize = Int(gameInfoValue.ImageSize)
|
||||||
guard imageSize > 0, imageSize <= 1024 * 1024 else {
|
guard imageSize > 0, imageSize <= 1024 * 1024 else {
|
||||||
print("Invalid image size.")
|
print("Invalid image size.")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the ImageData byte array to Swift's Data
|
|
||||||
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
|
let imageData = Data(bytes: gameInfoValue.ImageData, count: imageSize)
|
||||||
|
|
||||||
// Create a UIImage (or NSImage on macOS)
|
|
||||||
print(imageData)
|
|
||||||
|
|
||||||
return UIImage(data: imageData)
|
return UIImage(data: imageData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
src/MeloNX/MeloNX/App/Models/LatestVersionResponse.swift
Normal file
38
src/MeloNX/MeloNX/App/Models/LatestVersionResponse.swift
Normal 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
|
||||||
|
}
|
@ -1,360 +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
|
|
||||||
|
|
||||||
struct MoltenVKSettings: Codable, Hashable {
|
|
||||||
let string: String
|
|
||||||
var value: String
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ContentView: View {
|
|
||||||
// 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
|
|
||||||
|
|
||||||
// Loading Animation
|
|
||||||
@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] = [ // Default MoltenVK Settings.
|
|
||||||
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"),
|
|
||||||
// Uses more ram but makes performance higher, may add an option in settings to change or enable / disable this value (default 64)
|
|
||||||
MoltenVKSettings(string: "MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE", value: "128"),
|
|
||||||
]
|
|
||||||
|
|
||||||
_settings = State(initialValue: defaultSettings)
|
|
||||||
|
|
||||||
initializeSDL()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Body
|
|
||||||
var body: some View {
|
|
||||||
if game != nil, quits == false {
|
|
||||||
if isLoading {
|
|
||||||
if Air.shared.connected {
|
|
||||||
Text("")
|
|
||||||
.onAppear() {
|
|
||||||
Air.play(AnyView(emulationView))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ZStack {
|
|
||||||
emulationView
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This is when the game starts to stop the animation
|
|
||||||
if #available(iOS 16, *) {
|
|
||||||
EmulationView()
|
|
||||||
.persistentSystemOverlays(.hidden)
|
|
||||||
.onAppear() {
|
|
||||||
isAnimating = false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
EmulationView()
|
|
||||||
.onAppear() {
|
|
||||||
isAnimating = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// This is the main menu view that includes the Settings and the Game Selector
|
|
||||||
mainMenuView
|
|
||||||
.onAppear() {
|
|
||||||
quits = false
|
|
||||||
|
|
||||||
initControllerObservers() // This initializes the Controller Observers that refreshes the controller list when a new controller connecvts.
|
|
||||||
}
|
|
||||||
.onOpenURL() { url in
|
|
||||||
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 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
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() {
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { timer in
|
|
||||||
refreshControllersList()
|
|
||||||
}
|
|
||||||
|
|
||||||
Air.play(AnyView(
|
|
||||||
VStack {
|
|
||||||
Image(systemName: "gamecontroller")
|
|
||||||
.font(.system(size: 300))
|
|
||||||
.foregroundColor(.gray)
|
|
||||||
.padding(.bottom, 10)
|
|
||||||
|
|
||||||
Text("Select Game")
|
|
||||||
.font(.system(size: 150))
|
|
||||||
.bold()
|
|
||||||
}
|
|
||||||
))
|
|
||||||
|
|
||||||
let isJIT = isJITEnabled()
|
|
||||||
if !isJIT {
|
|
||||||
useTrollStore ? askForJIT() : enableJITEB()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Helper Methods
|
|
||||||
var SdlInitFlags: uint = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_VIDEO; // Initialises SDL2 for Events, Game Controller, Joystick, Audio and Video.
|
|
||||||
private func initializeSDL() {
|
|
||||||
setMoltenVKSettings()
|
|
||||||
SDL_SetMainReady() // Sets SDL Ready
|
|
||||||
SDL_iPhoneSetEventPump(SDL_TRUE) // Set iOS Event Pump to true
|
|
||||||
SDL_Init(SdlInitFlags) // Initialises SDL2
|
|
||||||
initialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupEmulation() {
|
|
||||||
patchMakeKeyAndVisible()
|
|
||||||
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 {
|
|
||||||
let controller = controllersList[0]
|
|
||||||
currentControllers.append(controller)
|
|
||||||
} 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)))
|
|
||||||
|
|
||||||
if mVKPreFillBuffer {
|
|
||||||
let setting = MoltenVKSettings(string: "MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS", value: "2")
|
|
||||||
setenv(setting.string, setting.value, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if syncqsubmits {
|
|
||||||
let setting = MoltenVKSettings(string: "MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS", value: "2")
|
|
||||||
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)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Sets MoltenVK Environment Variables
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Array {
|
|
||||||
@inlinable public mutating func mutableForEach(_ body: (inout Element) throws -> Void) rethrows {
|
|
||||||
for index in self.indices {
|
|
||||||
try body(&self[index])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
//
|
|
||||||
// MetalView.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 09/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import MetalKit
|
|
||||||
|
|
||||||
struct MetalView: UIViewRepresentable {
|
|
||||||
|
|
||||||
var airplay: Bool = Air.shared.connected // just in case :3
|
|
||||||
|
|
||||||
func makeUIView(context: Context) -> UIView {
|
|
||||||
let metalLayer = Ryujinx.shared.metalLayer!
|
|
||||||
|
|
||||||
var view = UIView()
|
|
||||||
|
|
||||||
metalLayer.frame = view.bounds
|
|
||||||
if airplay {
|
|
||||||
metalLayer.contentsScale = view.contentScaleFactor
|
|
||||||
} else {
|
|
||||||
Ryujinx.shared.emulationUIView.contentScaleFactor = metalLayer.contentsScale // Right size and Fix Touch :3
|
|
||||||
}
|
|
||||||
|
|
||||||
Ryujinx.shared.emulationUIView = view
|
|
||||||
|
|
||||||
if !Ryujinx.shared.emulationUIView.subviews.contains(where: { $0 == metalLayer }) {
|
|
||||||
Ryujinx.shared.emulationUIView.layer.addSublayer(metalLayer)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ryujinx.shared.emulationUIView
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateUIView(_ uiView: UIView, context: Context) {
|
|
||||||
// nothin
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
//
|
|
||||||
// LogEntry.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 09/02/2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct LogEntry: Identifiable, Equatable {
|
|
||||||
let id = UUID()
|
|
||||||
let text: String
|
|
||||||
|
|
||||||
static func == (lhs: LogEntry, rhs: LogEntry) -> Bool {
|
|
||||||
return lhs.id == rhs.id && lhs.text == rhs.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LogViewer: View {
|
|
||||||
@State private var logs: [LogEntry] = []
|
|
||||||
@State private var latestLogFilePath: String?
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
VStack {
|
|
||||||
ForEach(logs) { log in
|
|
||||||
Text(log.text)
|
|
||||||
.padding(4)
|
|
||||||
.background(Color.black.opacity(0.7))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.cornerRadius(8)
|
|
||||||
.transition(.move(edge: .top).combined(with: .opacity))
|
|
||||||
.animation(.easeOut(duration: 2), value: logs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding()
|
|
||||||
}
|
|
||||||
.edgesIgnoringSafeArea(.all)
|
|
||||||
.onAppear {
|
|
||||||
findNewestLogFile()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findNewestLogFile() {
|
|
||||||
let logsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent("logs")
|
|
||||||
|
|
||||||
guard let directory = logsDirectory else { return }
|
|
||||||
|
|
||||||
do {
|
|
||||||
let logFiles = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: [.contentModificationDateKey], options: .skipsHiddenFiles)
|
|
||||||
|
|
||||||
// Sort files by modification date (newest first)
|
|
||||||
let sortedFiles = logFiles.sorted {
|
|
||||||
(try? $0.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? Date.distantPast >
|
|
||||||
(try? $1.resourceValues(forKeys: [.contentModificationDateKey]).contentModificationDate) ?? Date.distantPast
|
|
||||||
}
|
|
||||||
|
|
||||||
if let newestLogFile = sortedFiles.first {
|
|
||||||
latestLogFilePath = newestLogFile.path
|
|
||||||
startReadingLogFile()
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
print("Error reading log files: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func startReadingLogFile() {
|
|
||||||
guard let path = latestLogFilePath else { return }
|
|
||||||
let fileHandle = try? FileHandle(forReadingAtPath: path)
|
|
||||||
fileHandle?.seekToEndOfFile()
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(forName: .NSFileHandleDataAvailable, object: fileHandle, queue: .main) { _ in
|
|
||||||
if let data = fileHandle?.availableData, !data.isEmpty {
|
|
||||||
if let logLine = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
withAnimation {
|
|
||||||
logs.append(LogEntry(text: logLine))
|
|
||||||
}
|
|
||||||
// Remove old logs after a delay
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
|
||||||
withAnimation {
|
|
||||||
removelogfirst()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fileHandle?.waitForDataInBackgroundAndNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
fileHandle?.waitForDataInBackgroundAndNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
func removelogfirst() {
|
|
||||||
logs.removeFirst()
|
|
||||||
}
|
|
||||||
}
|
|
399
src/MeloNX/MeloNX/App/Views/Main/ContentView.swift
Normal file
399
src/MeloNX/MeloNX/App/Views/Main/ContentView.swift
Normal 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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,29 +11,10 @@ import SwiftUIJoystick
|
|||||||
import CoreMotion
|
import CoreMotion
|
||||||
|
|
||||||
struct ControllerView: View {
|
struct ControllerView: View {
|
||||||
|
|
||||||
@AppStorage("performacehud") var performacehud: Bool = false
|
|
||||||
@AppStorage("quit") var quit: Bool = false
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
GeometryReader { geometry in
|
GeometryReader { geometry in
|
||||||
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad {
|
if geometry.size.height > geometry.size.width && UIDevice.current.userInterfaceIdiom != .pad {
|
||||||
VStack {
|
VStack {
|
||||||
if performacehud {
|
|
||||||
HStack {
|
|
||||||
|
|
||||||
PerformanceOverlayView()
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
// Button("Stop emulation") {
|
|
||||||
// DispatchQueue.main.async {
|
|
||||||
// stop_emulation()
|
|
||||||
// quit = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
VStack {
|
VStack {
|
||||||
@ -67,21 +48,6 @@ struct ControllerView: View {
|
|||||||
} else {
|
} else {
|
||||||
// could be landscape
|
// could be landscape
|
||||||
VStack {
|
VStack {
|
||||||
if performacehud {
|
|
||||||
HStack {
|
|
||||||
PerformanceOverlayView()
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
// Button("Stop emulation") {
|
|
||||||
// DispatchQueue.main.async {
|
|
||||||
// stop_emulation()
|
|
||||||
// quit = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
VStack {
|
VStack {
|
@ -9,15 +9,22 @@ import SwiftUI
|
|||||||
|
|
||||||
// Emulation View
|
// Emulation View
|
||||||
struct EmulationView: View {
|
struct EmulationView: View {
|
||||||
|
@AppStorage("performacehud") var performacehud: Bool = false
|
||||||
@AppStorage("isVirtualController") var isVCA: Bool = true
|
@AppStorage("isVirtualController") var isVCA: Bool = true
|
||||||
@AppStorage("showScreenShotButton") var ssb: Bool = false
|
@AppStorage("showScreenShotButton") var ssb: Bool = false
|
||||||
|
@AppStorage("showlogsgame") var showlogsgame: Bool = false
|
||||||
|
|
||||||
@State var isPresentedThree: Bool = false
|
@State var isPresentedThree: Bool = false
|
||||||
@State var isAirplaying = Air.shared.connected
|
@State var isAirplaying = Air.shared.connected
|
||||||
|
@Binding var startgame: Game?
|
||||||
|
|
||||||
@Environment(\.scenePhase) var scenePhase
|
@Environment(\.scenePhase) var scenePhase
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if isAirplaying {
|
if isAirplaying {
|
||||||
Text("")
|
TouchView()
|
||||||
|
.ignoresSafeArea()
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
.onAppear {
|
.onAppear {
|
||||||
Air.play(AnyView(MetalView().ignoresSafeArea()))
|
Air.play(AnyView(MetalView().ignoresSafeArea()))
|
||||||
}
|
}
|
||||||
@ -33,16 +40,35 @@ struct EmulationView: View {
|
|||||||
ControllerView() // Virtual Controller
|
ControllerView() // Virtual Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Group {
|
||||||
if ssb {
|
VStack {
|
||||||
Group {
|
HStack {
|
||||||
VStack {
|
if performacehud, !showlogsgame {
|
||||||
|
PerformanceOverlayView()
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
if performacehud, showlogsgame {
|
||||||
|
PerformanceOverlayView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack {
|
||||||
|
if showlogsgame, get_current_fps() != 0 {
|
||||||
|
LogFileView(isfps: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if ssb {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
if let screenshot = Ryujinx.shared.emulationUIView.screenshot() {
|
if let screenshot = Ryujinx.shared.emulationUIView?.screenshot() {
|
||||||
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
|
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
@ -52,9 +78,12 @@ struct EmulationView: View {
|
|||||||
.padding()
|
.padding()
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,154 @@
|
|||||||
|
//
|
||||||
|
// MeloMTKView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 03/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import MetalKit
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class MeloMTKView: MTKView {
|
||||||
|
|
||||||
|
private var activeTouches: [UITouch] = []
|
||||||
|
private var ignoredTouches: Set<UITouch> = []
|
||||||
|
|
||||||
|
private let baseWidth: CGFloat = 1280
|
||||||
|
private let baseHeight: CGFloat = 720
|
||||||
|
private var aspectRatio: AspectRatio = .fixed16x9
|
||||||
|
|
||||||
|
func setAspectRatio(_ ratio: AspectRatio) {
|
||||||
|
self.aspectRatio = ratio
|
||||||
|
}
|
||||||
|
|
||||||
|
private func scaleToTargetResolution(_ location: CGPoint) -> CGPoint? {
|
||||||
|
let viewWidth = self.frame.width
|
||||||
|
let viewHeight = self.frame.height
|
||||||
|
|
||||||
|
var scaleX: CGFloat
|
||||||
|
var scaleY: CGFloat
|
||||||
|
var offsetX: CGFloat = 0
|
||||||
|
var offsetY: CGFloat = 0
|
||||||
|
|
||||||
|
var targetAspect: CGFloat
|
||||||
|
|
||||||
|
switch aspectRatio {
|
||||||
|
case .fixed4x3:
|
||||||
|
targetAspect = 4.0 / 3.0
|
||||||
|
case .fixed16x9:
|
||||||
|
targetAspect = 16.0 / 9.0
|
||||||
|
case .fixed16x10:
|
||||||
|
targetAspect = 16.0 / 10.0
|
||||||
|
case .fixed21x9:
|
||||||
|
targetAspect = 21.0 / 9.0
|
||||||
|
case .fixed32x9:
|
||||||
|
targetAspect = 32.0 / 9.0
|
||||||
|
case .stretched:
|
||||||
|
scaleX = baseWidth / viewWidth
|
||||||
|
scaleY = baseHeight / viewHeight
|
||||||
|
|
||||||
|
let adjustedX = location.x
|
||||||
|
let adjustedY = location.y
|
||||||
|
|
||||||
|
let scaledX = max(0, min(adjustedX * scaleX, baseWidth))
|
||||||
|
let scaledY = max(0, min(adjustedY * scaleY, baseHeight))
|
||||||
|
|
||||||
|
return CGPoint(x: scaledX, y: scaledY)
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewAspect = viewWidth / viewHeight
|
||||||
|
|
||||||
|
if viewAspect > targetAspect {
|
||||||
|
let scaledWidth = viewHeight * targetAspect
|
||||||
|
offsetX = (viewWidth - scaledWidth) / 2
|
||||||
|
scaleX = baseWidth / scaledWidth
|
||||||
|
scaleY = baseHeight / viewHeight
|
||||||
|
} else {
|
||||||
|
let scaledHeight = viewWidth / targetAspect
|
||||||
|
offsetY = (viewHeight - scaledHeight) / 2
|
||||||
|
scaleX = baseWidth / viewWidth
|
||||||
|
scaleY = baseHeight / scaledHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
if location.x < offsetX || location.x > (viewWidth - offsetX) ||
|
||||||
|
location.y < offsetY || location.y > (viewHeight - offsetY) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let adjustedX = location.x - offsetX
|
||||||
|
let adjustedY = location.y - offsetY
|
||||||
|
|
||||||
|
let scaledX = max(0, min(adjustedX * scaleX, baseWidth))
|
||||||
|
let scaledY = max(0, min(adjustedY * scaleY, baseHeight))
|
||||||
|
|
||||||
|
return CGPoint(x: scaledX, y: scaledY)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
super.touchesBegan(touches, with: event)
|
||||||
|
|
||||||
|
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
||||||
|
|
||||||
|
for touch in touches {
|
||||||
|
let location = touch.location(in: self)
|
||||||
|
if scaleToTargetResolution(location) == nil {
|
||||||
|
ignoredTouches.insert(touch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
activeTouches.append(touch)
|
||||||
|
let index = activeTouches.firstIndex(of: touch)!
|
||||||
|
|
||||||
|
let scaledLocation = scaleToTargetResolution(location)!
|
||||||
|
print("Touch began at: \(scaledLocation) and \(self.aspectRatio)")
|
||||||
|
touch_began(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
super.touchesEnded(touches, with: event)
|
||||||
|
|
||||||
|
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
||||||
|
|
||||||
|
for touch in touches {
|
||||||
|
if ignoredTouches.contains(touch) {
|
||||||
|
ignoredTouches.remove(touch)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let index = activeTouches.firstIndex(of: touch) {
|
||||||
|
activeTouches.remove(at: index)
|
||||||
|
|
||||||
|
print("Touch ended for index \(index)")
|
||||||
|
touch_ended(Int32(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||||
|
super.touchesMoved(touches, with: event)
|
||||||
|
|
||||||
|
setAspectRatio(Ryujinx.shared.config?.aspectRatio ?? .fixed16x9)
|
||||||
|
|
||||||
|
for touch in touches {
|
||||||
|
if ignoredTouches.contains(touch) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let location = touch.location(in: self)
|
||||||
|
guard let scaledLocation = scaleToTargetResolution(location) else {
|
||||||
|
if let index = activeTouches.firstIndex(of: touch) {
|
||||||
|
activeTouches.remove(at: index)
|
||||||
|
print("Touch left active area, removed index \(index)")
|
||||||
|
touch_ended(Int32(index))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if let index = activeTouches.firstIndex(of: touch) {
|
||||||
|
print("Touch moved to: \(scaledLocation)")
|
||||||
|
touch_moved(Float(scaledLocation.x), Float(scaledLocation.y), Int32(index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// MetalView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 09/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import MetalKit
|
||||||
|
|
||||||
|
struct MetalView: UIViewRepresentable {
|
||||||
|
|
||||||
|
var airplay: Bool = Air.shared.connected // just in case :3
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
|
||||||
|
if Ryujinx.shared.emulationUIView == nil {
|
||||||
|
let view = MeloMTKView()
|
||||||
|
|
||||||
|
guard let metalLayer = view.layer as? CAMetalLayer else {
|
||||||
|
fatalError("[Swift] Error: MTKView's layer is not a CAMetalLayer")
|
||||||
|
}
|
||||||
|
|
||||||
|
metalLayer.device = MTLCreateSystemDefaultDevice()
|
||||||
|
|
||||||
|
let layerPtr = Unmanaged.passUnretained(metalLayer).toOpaque()
|
||||||
|
set_native_window(layerPtr)
|
||||||
|
|
||||||
|
Ryujinx.shared.emulationUIView = view
|
||||||
|
|
||||||
|
|
||||||
|
Ryujinx.shared.metalLayer = metalLayer
|
||||||
|
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
if Double(UIDevice.current.systemVersion)! < 17.0 {
|
||||||
|
|
||||||
|
let uiview = MTKView()
|
||||||
|
let layer = Ryujinx.shared.metalLayer!
|
||||||
|
|
||||||
|
layer.frame = uiview.bounds
|
||||||
|
|
||||||
|
uiview.layer.addSublayer(layer)
|
||||||
|
|
||||||
|
return uiview
|
||||||
|
} else {
|
||||||
|
return Ryujinx.shared.emulationUIView!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIView, context: Context) {
|
||||||
|
// nothin
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// TouchView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 05/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import MetalKit
|
||||||
|
|
||||||
|
struct TouchView: UIViewRepresentable {
|
||||||
|
func makeUIView(context: Context) -> UIView {
|
||||||
|
let view = MeloMTKView()
|
||||||
|
return view
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: UIView, context: Context) {}
|
||||||
|
}
|
@ -134,7 +134,9 @@ struct GameLibraryView: View {
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
isSelectingGameFile.toggle()
|
isSelectingGameFile = true
|
||||||
|
|
||||||
|
isImporting = true
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "plus")
|
Image(systemName: "plus")
|
||||||
}
|
}
|
||||||
@ -171,16 +173,17 @@ struct GameLibraryView: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Text("Mii Maker")
|
Text("Mii Maker")
|
||||||
}
|
}
|
||||||
Button {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
isImporting.toggle()
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Text("Open game from system")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
isSelectingGameFile = false
|
||||||
|
|
||||||
|
isImporting = true
|
||||||
|
} label: {
|
||||||
|
Text("Open Game")
|
||||||
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
let documentsUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
var sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
|
var sharedurl = documentsUrl.absoluteString.replacingOccurrences(of: "file://", with: "shareddocuments://")
|
||||||
@ -211,62 +214,64 @@ struct GameLibraryView: View {
|
|||||||
.onChange(of: searchText) { _ in
|
.onChange(of: searchText) { _ in
|
||||||
isSearching = !searchText.isEmpty
|
isSearching = !searchText.isEmpty
|
||||||
}
|
}
|
||||||
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.zip, .folder, .nsp, .xci]) { result in
|
.fileImporter(isPresented: $isImporting, allowedContentTypes: [.folder, .nsp, .xci, .zip, .item]) { result in
|
||||||
switch result {
|
if isSelectingGameFile {
|
||||||
case .success(let url):
|
switch result {
|
||||||
guard url.startAccessingSecurityScopedResource() else {
|
case .success(let url):
|
||||||
print("Failed to access security-scoped resource")
|
guard url.startAccessingSecurityScopedResource() else {
|
||||||
return
|
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)
|
|
||||||
|
|
||||||
let game = Game.convertGameInfoToGame(gameInfo: gameInfo, url: url)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
startemu = game
|
|
||||||
}
|
}
|
||||||
} catch {
|
defer { url.stopAccessingSecurityScopedResource() }
|
||||||
print(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
case .failure(let err):
|
|
||||||
print("File import failed: \(err.localizedDescription)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fileImporter(isPresented: $isSelectingGameFile, allowedContentTypes: [.nsp, .xci, .zip, .folder]) { 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 fileManager = FileManager.default
|
|
||||||
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
|
||||||
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
|
||||||
|
|
||||||
if !fileManager.fileExists(atPath: romsDirectory.path) {
|
do {
|
||||||
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
let fileManager = FileManager.default
|
||||||
|
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||||
|
let romsDirectory = documentsDirectory.appendingPathComponent("roms")
|
||||||
|
|
||||||
|
if !fileManager.fileExists(atPath: romsDirectory.path) {
|
||||||
|
try fileManager.createDirectory(at: romsDirectory, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
let destinationURL = romsDirectory.appendingPathComponent(url.lastPathComponent)
|
||||||
|
try fileManager.copyItem(at: url, to: destinationURL)
|
||||||
|
|
||||||
|
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
||||||
|
} catch {
|
||||||
|
print("Error copying game file: \(error)")
|
||||||
|
}
|
||||||
|
case .failure(let err):
|
||||||
|
print("File import failed: \(err.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success(let url):
|
||||||
|
guard url.startAccessingSecurityScopedResource() else {
|
||||||
|
print("Failed to access security-scoped resource")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let destinationURL = romsDirectory.appendingPathComponent(url.lastPathComponent)
|
do {
|
||||||
try fileManager.copyItem(at: url, to: destinationURL)
|
let handle = try FileHandle(forReadingFrom: url)
|
||||||
|
let fileExtension = (url.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: url)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
startemu = game
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
|
||||||
Ryujinx.shared.games = Ryujinx.shared.loadGames()
|
case .failure(let err):
|
||||||
} catch {
|
print("File import failed: \(err.localizedDescription)")
|
||||||
print("Error copying game file: \(error)")
|
|
||||||
}
|
}
|
||||||
case .failure(let err):
|
|
||||||
print("File import failed: \(err.localizedDescription)")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sheet(isPresented: $isSelectingGameUpdate) {
|
.sheet(isPresented: $isSelectingGameUpdate) {
|
44
src/MeloNX/MeloNX/App/Views/Main/JIT/JITPopover.swift
Normal file
44
src/MeloNX/MeloNX/App/Views/Main/JIT/JITPopover.swift
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// JITPopover.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 05/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct JITPopover: View {
|
||||||
|
var onJITEnabled: () -> Void
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
@State var isJIT: Bool = false
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
Image(systemName: "cpu.fill")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.foregroundColor(.blue)
|
||||||
|
|
||||||
|
Text("Waiting for JIT")
|
||||||
|
.font(.headline)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
|
||||||
|
Text("JIT (Just-In-Time) compilation allows MeloNX to run code at as fast as possible by translating it dynamically. This is necessary for running this emulator.")
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.onAppear {
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
|
||||||
|
isJIT = isJITEnabled()
|
||||||
|
|
||||||
|
|
||||||
|
if isJIT {
|
||||||
|
dismiss()
|
||||||
|
onJITEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
117
src/MeloNX/MeloNX/App/Views/Main/Logging/Logs.swift
Normal file
117
src/MeloNX/MeloNX/App/Views/Main/Logging/Logs.swift
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
//
|
||||||
|
// LogEntry.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 09/02/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LogFileView: View {
|
||||||
|
@State private var logs: [String] = []
|
||||||
|
@State private var showingLogs = false
|
||||||
|
|
||||||
|
public var isfps: Bool
|
||||||
|
|
||||||
|
private let fileManager = FileManager.default
|
||||||
|
private let maxDisplayLines = 10
|
||||||
|
|
||||||
|
private var dateFormatter: DateFormatter {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateFormat = "yyyy-MM-dd_HH-mm-ss"
|
||||||
|
return formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
ForEach(logs.suffix(maxDisplayLines), id: \.self) { log in
|
||||||
|
Text(log)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(4)
|
||||||
|
.background(Color.black.opacity(0.7))
|
||||||
|
.cornerRadius(4)
|
||||||
|
.transition(.opacity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
startLogFileWatching()
|
||||||
|
}
|
||||||
|
.onChange(of: logs) { newLogs in
|
||||||
|
print("Logs updated: \(newLogs.count) entries")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getLatestLogFile() -> URL? {
|
||||||
|
let logsDirectory = URL.documentsDirectory.appendingPathComponent("Logs")
|
||||||
|
let currentDate = Date()
|
||||||
|
|
||||||
|
do {
|
||||||
|
try fileManager.createDirectory(at: logsDirectory, withIntermediateDirectories: true)
|
||||||
|
|
||||||
|
let logFiles = try fileManager.contentsOfDirectory(at: logsDirectory, includingPropertiesForKeys: [.creationDateKey])
|
||||||
|
.filter {
|
||||||
|
let filename = $0.lastPathComponent
|
||||||
|
guard filename.hasPrefix("MeloNX_") && filename.hasSuffix(".log") else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
let dateString = filename.replacingOccurrences(of: "MeloNX_", with: "").replacingOccurrences(of: ".log", with: "")
|
||||||
|
guard let logDate = dateFormatter.date(from: dateString) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Calendar.current.isDate(logDate, inSameDayAs: currentDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
let sortedLogFiles = logFiles.sorted {
|
||||||
|
$0.lastPathComponent > $1.lastPathComponent
|
||||||
|
}
|
||||||
|
|
||||||
|
return sortedLogFiles.first
|
||||||
|
} catch {
|
||||||
|
print("Error finding log files: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func readLatestLogFile() {
|
||||||
|
guard let logFileURL = getLatestLogFile() else {
|
||||||
|
print("no logs?")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
print(logFileURL)
|
||||||
|
|
||||||
|
do {
|
||||||
|
let logContents = try String(contentsOf: logFileURL)
|
||||||
|
let allLines = logContents.components(separatedBy: .newlines)
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
|
self.logs = Array(allLines)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error reading log file: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func startLogFileWatching() {
|
||||||
|
showingLogs = true
|
||||||
|
self.readLatestLogFile()
|
||||||
|
Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { timer in
|
||||||
|
if showingLogs {
|
||||||
|
self.readLatestLogFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
if isfps {
|
||||||
|
if get_current_fps() != 0 {
|
||||||
|
stopLogFileWatching()
|
||||||
|
timer.invalidate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func stopLogFileWatching() {
|
||||||
|
showingLogs = false
|
||||||
|
}
|
||||||
|
}
|
@ -42,6 +42,12 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
@AppStorage("On-ScreenControllerScale") var controllerScale: Double = 1.0
|
||||||
|
|
||||||
|
@AppStorage("hasbeenfinished") var finishedStorage: Bool = false
|
||||||
|
|
||||||
|
@AppStorage("showlogsloading") var showlogsloading: Bool = true
|
||||||
|
|
||||||
|
@AppStorage("showlogsgame") var showlogsgame: Bool = false
|
||||||
|
|
||||||
@State private var showResolutionInfo = false
|
@State private var showResolutionInfo = false
|
||||||
@State private var showAnisotropicInfo = false
|
@State private var showAnisotropicInfo = false
|
||||||
@State private var showControllerInfo = false
|
@State private var showControllerInfo = false
|
||||||
@ -177,7 +183,7 @@ struct SettingsView: View {
|
|||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
|
|
||||||
Toggle(isOn: $performacehud) {
|
Toggle(isOn: $performacehud) {
|
||||||
labelWithIcon("Performance Overlay", iconName: "speedometer")
|
labelWithIcon("Custom Performance Overlay", iconName: "speedometer")
|
||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
} header: {
|
} header: {
|
||||||
@ -275,7 +281,7 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
// Input Settings
|
// Input Settings
|
||||||
Section {
|
Section {
|
||||||
Toggle(isOn: $config.macroHLE) {
|
Toggle(isOn: $config.handHeldController) {
|
||||||
labelWithIcon("Player 1 to Handheld Input", iconName: "formfitting.gamecontroller")
|
labelWithIcon("Player 1 to Handheld Input", iconName: "formfitting.gamecontroller")
|
||||||
}.tint(.blue)
|
}.tint(.blue)
|
||||||
|
|
||||||
@ -382,7 +388,7 @@ struct SettingsView: View {
|
|||||||
labelWithIcon("Disable PTC", iconName: "cpu")
|
labelWithIcon("Disable PTC", iconName: "cpu")
|
||||||
}.tint(.blue)
|
}.tint(.blue)
|
||||||
|
|
||||||
if let cpuInfo = getCPUInfo(), cpuInfo.hasPrefix("Apple M") {
|
if let gpuInfo = getGPUInfo(), gpuInfo.hasPrefix("Apple M") {
|
||||||
if #available (iOS 16.4, *) {
|
if #available (iOS 16.4, *) {
|
||||||
Toggle(isOn: .constant(false)) {
|
Toggle(isOn: .constant(false)) {
|
||||||
labelWithIcon("Hypervisor", iconName: "bolt")
|
labelWithIcon("Hypervisor", iconName: "bolt")
|
||||||
@ -390,7 +396,7 @@ struct SettingsView: View {
|
|||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
print("CPU Info: \(cpuInfo)")
|
print("CPU Info: \(gpuInfo)")
|
||||||
}
|
}
|
||||||
} else if checkAppEntitlement("com.apple.private.hypervisor") {
|
} else if checkAppEntitlement("com.apple.private.hypervisor") {
|
||||||
Toggle(isOn: $config.hypervisor) {
|
Toggle(isOn: $config.hypervisor) {
|
||||||
@ -398,7 +404,7 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
print("CPU Info: \(cpuInfo)")
|
print("CPU Info: \(gpuInfo)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -446,7 +452,8 @@ struct SettingsView: View {
|
|||||||
.tint(.blue)
|
.tint(.blue)
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
Button {
|
Button {
|
||||||
if let mainWindow = UIApplication.shared.windows.last {
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||||
|
let mainWindow = windowScene.windows.last {
|
||||||
let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
|
let alertController = UIAlertController(title: "About JitStreamer EB", message: "JitStreamer EB is an Amazing Application to Enable JIT on the go, made by one of the best iOS developers of all time jkcoxson <3", preferredStyle: .alert)
|
||||||
|
|
||||||
let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
|
let learnMoreButton = UIAlertAction(title: "Learn More", style: .default) {_ in
|
||||||
@ -475,7 +482,8 @@ struct SettingsView: View {
|
|||||||
}.tint(.blue)
|
}.tint(.blue)
|
||||||
.contextMenu() {
|
.contextMenu() {
|
||||||
Button {
|
Button {
|
||||||
if let mainWindow = UIApplication.shared.windows.last {
|
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||||
|
let mainWindow = windowScene.windows.last {
|
||||||
let alertController = UIAlertController(title: "About MVK: Synchronous Queue Submits", message: "Enable this option if Mario Kart 8 is crashing at Grand Prix mode.", preferredStyle: .alert)
|
let alertController = UIAlertController(title: "About MVK: Synchronous Queue Submits", message: "Enable this option if Mario Kart 8 is crashing at Grand Prix mode.", preferredStyle: .alert)
|
||||||
|
|
||||||
let doneButton = UIAlertAction(title: "OK", style: .cancel, handler: nil)
|
let doneButton = UIAlertAction(title: "OK", style: .cancel, handler: nil)
|
||||||
@ -489,6 +497,14 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DisclosureGroup {
|
DisclosureGroup {
|
||||||
|
Toggle(isOn: $showlogsloading) {
|
||||||
|
labelWithIcon("Show logs while loading", iconName: "text.alignleft")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
|
Toggle(isOn: $showlogsgame) {
|
||||||
|
labelWithIcon("Show logs in-game", iconName: "text.line.magnify")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
Toggle(isOn: $config.debuglogs) {
|
Toggle(isOn: $config.debuglogs) {
|
||||||
labelWithIcon("Debug Logs", iconName: "exclamationmark.bubble")
|
labelWithIcon("Debug Logs", iconName: "exclamationmark.bubble")
|
||||||
}
|
}
|
||||||
@ -529,7 +545,11 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
labelWithIcon("Device: \(getDeviceModel())", iconName: iconName)
|
labelWithIcon("Device: \(getDeviceModel())", iconName: iconName)
|
||||||
|
|
||||||
labelWithIcon("Device Memory: \(String(format: "%.0f GB", Double(totalMemory) / 1_000_000_000))", iconName: "memorychip.fill")
|
if ProcessInfo.processInfo.isiOSAppOnMac {
|
||||||
|
labelWithIcon("Memory: \(String(format: "%.0f GB", Double(totalMemory) / (1024 * 1024 * 1024)))", iconName: "memorychip.fill")
|
||||||
|
} else {
|
||||||
|
labelWithIcon("Device Memory: \(String(format: "%.0f GB", Double(totalMemory) / 1_000_000_000))", iconName: "memorychip.fill")
|
||||||
|
}
|
||||||
|
|
||||||
labelWithIcon("\(deviceType) \(UIDevice.current.systemVersion)", iconName: "applelogo")
|
labelWithIcon("\(deviceType) \(UIDevice.current.systemVersion)", iconName: "applelogo")
|
||||||
|
|
||||||
@ -544,11 +564,6 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
// Advanced
|
// Advanced
|
||||||
Section {
|
Section {
|
||||||
Toggle(isOn: $windowCode) {
|
|
||||||
labelWithIcon("SDL Window", iconName: "macwindow.on.rectangle")
|
|
||||||
}
|
|
||||||
.tint(.blue)
|
|
||||||
|
|
||||||
DisclosureGroup {
|
DisclosureGroup {
|
||||||
|
|
||||||
Toggle(isOn: $mVKPreFillBuffer) {
|
Toggle(isOn: $mVKPreFillBuffer) {
|
||||||
@ -564,9 +579,22 @@ struct SettingsView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
Text("\(String(Int(getpagesize())))")
|
Text("\(String(Int(getpagesize())))")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if MTLHud.shared.canMetalHud {
|
||||||
|
Toggle(isOn: $metalHUDEnabled) {
|
||||||
|
labelWithIcon("Metal Performance HUD", iconName: "speedometer")
|
||||||
|
}
|
||||||
|
.tint(.blue)
|
||||||
|
.onChange(of: metalHUDEnabled) { newValue in
|
||||||
|
MTLHud.shared.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Toggle(isOn: $ignoreJIT) {
|
||||||
|
labelWithIcon("Ignore JIT Popup", iconName: "cpu")
|
||||||
|
}.tint(.blue)
|
||||||
|
|
||||||
TextField("Additional Arguments", text: Binding(
|
TextField("Additional Arguments", text: Binding(
|
||||||
get: {
|
get: {
|
||||||
config.additionalArgs.joined(separator: " ")
|
config.additionalArgs.joined(separator: " ")
|
||||||
@ -580,12 +608,13 @@ struct SettingsView: View {
|
|||||||
.textInputAutocapitalization(.none)
|
.textInputAutocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
Ryujinx.shared.removeFirmware()
|
finishedStorage = false
|
||||||
|
|
||||||
} label: {
|
} label: {
|
||||||
Text("Remove Firmware")
|
Text("Show Setup")
|
||||||
.font(.body)
|
.font(.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -599,11 +628,7 @@ struct SettingsView: View {
|
|||||||
.textCase(nil)
|
.textCase(nil)
|
||||||
.headerProminence(.increased)
|
.headerProminence(.increased)
|
||||||
} footer: {
|
} footer: {
|
||||||
if #available(iOS 17, *) {
|
Text("For advanced users. See page size or add custom arguments for experimental features, \"Metal Performance HUD\" is not needed if you have it enabled in settings. \n \n\(gamepo ? "the cake is a lie" : "")")
|
||||||
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). \n \n\(gamepo ? "the cake is a lie" : "")")
|
|
||||||
} else {
|
|
||||||
Text("For advanced users. See page size or add custom arguments for experimental features. (Please don't touch this if you don't know what you're doing). If the emulation is not showing (you may hear audio in some games), try enabling \"SDL Window\" \n \n\(gamepo ? "the cake is a lie" : "")")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -614,6 +639,8 @@ struct SettingsView: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
if let configs = loadSettings() {
|
if let configs = loadSettings() {
|
||||||
self.config = configs
|
self.config = configs
|
||||||
|
} else {
|
||||||
|
saveSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onChange(of: config) { _ in
|
.onChange(of: config) { _ in
|
||||||
@ -631,6 +658,10 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveSettings() {
|
||||||
|
MeloNX.saveSettings(config: config)
|
||||||
|
}
|
||||||
|
|
||||||
func getDeviceModel() -> String {
|
func getDeviceModel() -> String {
|
||||||
var systemInfo = utsname()
|
var systemInfo = utsname()
|
||||||
uname(&systemInfo)
|
uname(&systemInfo)
|
||||||
@ -641,26 +672,9 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
return identifier
|
return identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func saveSettings() {
|
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
|
|
||||||
print("Saving Settings")
|
|
||||||
#else
|
|
||||||
do {
|
|
||||||
let encoder = JSONEncoder()
|
|
||||||
encoder.outputFormatting = .prettyPrinted
|
|
||||||
let data = try encoder.encode(config)
|
|
||||||
let jsonString = String(data: data, encoding: .utf8)
|
|
||||||
UserDefaults.standard.set(jsonString, forKey: "config")
|
|
||||||
} catch {
|
|
||||||
print("Failed to save settings: \(error)")
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCPUInfo() -> String? {
|
func getGPUInfo() -> String? {
|
||||||
let device = MTLCreateSystemDefaultDevice()
|
let device = MTLCreateSystemDefaultDevice()
|
||||||
|
|
||||||
let gpu = device?.name
|
let gpu = device?.name
|
||||||
@ -669,29 +683,6 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Original loadSettings function assumed to exist
|
|
||||||
func loadSettings() -> Ryujinx.Configuration? {
|
|
||||||
|
|
||||||
#if targetEnvironment(simulator)
|
|
||||||
print("Running on Simulator")
|
|
||||||
|
|
||||||
return Ryujinx.Configuration(gamepath: "")
|
|
||||||
#else
|
|
||||||
guard let jsonString = UserDefaults.standard.string(forKey: "config"),
|
|
||||||
let data = jsonString.data(using: .utf8) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
do {
|
|
||||||
let decoder = JSONDecoder()
|
|
||||||
let configs = try decoder.decode(Ryujinx.Configuration.self, from: data)
|
|
||||||
return configs
|
|
||||||
} catch {
|
|
||||||
print("Failed to load settings: \(error)")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func labelWithIcon(_ text: String, iconName: String, flipimage: Bool? = nil) -> some View {
|
private func labelWithIcon(_ text: String, iconName: String, flipimage: Bool? = nil) -> some View {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
@ -724,7 +715,7 @@ struct SVGView: UIViewRepresentable {
|
|||||||
|
|
||||||
func makeUIView(context: Context) -> UIView {
|
func makeUIView(context: Context) -> UIView {
|
||||||
var svgName = svgName
|
var svgName = svgName
|
||||||
var hammock = UIView()
|
let hammock = UIView()
|
||||||
|
|
||||||
if svgName.hasSuffix(".svg") {
|
if svgName.hasSuffix(".svg") {
|
||||||
svgName.removeLast(4)
|
svgName.removeLast(4)
|
||||||
@ -732,7 +723,7 @@ struct SVGView: UIViewRepresentable {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
let svgLayer = UIView(SVGNamed: svgName) { svgLayer in
|
_ = UIView(svgNamed: svgName) { svgLayer in
|
||||||
svgLayer.fillColor = UIColor(color).cgColor // Apply the provided color
|
svgLayer.fillColor = UIColor(color).cgColor // Apply the provided color
|
||||||
svgLayer.resizeToFit(hammock.frame)
|
svgLayer.resizeToFit(hammock.frame)
|
||||||
hammock.layer.addSublayer(svgLayer)
|
hammock.layer.addSublayer(svgLayer)
|
||||||
@ -748,3 +739,38 @@ struct SVGView: UIViewRepresentable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveSettings(config: Ryujinx.Configuration) {
|
||||||
|
do {
|
||||||
|
let encoder = JSONEncoder()
|
||||||
|
encoder.outputFormatting = .prettyPrinted
|
||||||
|
let data = try encoder.encode(config)
|
||||||
|
|
||||||
|
let fileURL = URL.documentsDirectory.appendingPathComponent("config.json")
|
||||||
|
|
||||||
|
try data.write(to: fileURL)
|
||||||
|
print("Settings saved to: \(fileURL.path)")
|
||||||
|
} catch {
|
||||||
|
print("Failed to save settings: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadSettings() -> Ryujinx.Configuration? {
|
||||||
|
do {
|
||||||
|
let fileURL = URL.documentsDirectory.appendingPathComponent("config.json")
|
||||||
|
|
||||||
|
guard FileManager.default.fileExists(atPath: fileURL.path) else {
|
||||||
|
print("Config file does not exist at: \(fileURL.path)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = try Data(contentsOf: fileURL)
|
||||||
|
|
||||||
|
let decoder = JSONDecoder()
|
||||||
|
let configs = try decoder.decode(Ryujinx.Configuration.self, from: data)
|
||||||
|
return configs
|
||||||
|
} catch {
|
||||||
|
print("Failed to load settings: \(error)")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
//
|
||||||
|
// MeloNXUpdateSheet.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 and Bella on 12/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct MeloNXUpdateSheet: View {
|
||||||
|
let updateInfo: LatestVersionResponse
|
||||||
|
@Binding var isPresented: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
iOSNav {
|
||||||
|
VStack {
|
||||||
|
Text("Version \(updateInfo.version_number) is available. You are currently on Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown").")
|
||||||
|
|
||||||
|
VStack {
|
||||||
|
Text("Changelog:")
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.font(.headline)
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
Text(updateInfo.changelog)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.frame(maxHeight: 400)
|
||||||
|
.background(Color(.secondarySystemBackground))
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 10))
|
||||||
|
}
|
||||||
|
.padding(.top, 15)
|
||||||
|
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
Button(action: {
|
||||||
|
if let url = URL(string: updateInfo.download_link) {
|
||||||
|
UIApplication.shared.open(url)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Download Now")
|
||||||
|
.font(.title3)
|
||||||
|
.bold()
|
||||||
|
.frame(width: 300, height: 40)
|
||||||
|
}
|
||||||
|
.buttonStyle(.borderedProminent)
|
||||||
|
.frame(alignment: .bottom)
|
||||||
|
}
|
||||||
|
.padding(.horizontal)
|
||||||
|
.navigationTitle("Version \(updateInfo.version_number) Available!")
|
||||||
|
.toolbar {
|
||||||
|
Button(action: {
|
||||||
|
isPresented = false
|
||||||
|
}) {
|
||||||
|
Text("Close")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -132,6 +132,7 @@ struct DLCManagerSheet: View {
|
|||||||
private extension DLCManagerSheet {
|
private extension DLCManagerSheet {
|
||||||
static func loadDlc(_ game: Game) -> [DownloadableContentContainer] {
|
static func loadDlc(_ game: Game) -> [DownloadableContentContainer] {
|
||||||
let jsonURL = dlcJsonPath(for: game)
|
let jsonURL = dlcJsonPath(for: game)
|
||||||
|
try? FileManager.default.createDirectory(at: jsonURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||||
guard let data = try? Data(contentsOf: jsonURL),
|
guard let data = try? Data(contentsOf: jsonURL),
|
||||||
var result = try? JSONDecoder().decode([DownloadableContentContainer].self, from: data)
|
var result = try? JSONDecoder().decode([DownloadableContentContainer].self, from: data)
|
||||||
else { return [] }
|
else { return [] }
|
@ -179,6 +179,7 @@ struct UpdateManagerSheet: View {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
let data = try Data(contentsOf: jsonURL)
|
let data = try Data(contentsOf: jsonURL)
|
||||||
|
try FileManager.default.createDirectory(at: jsonURL.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||||
var jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] ?? [:]
|
var jsonDict = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] ?? [:]
|
||||||
|
|
||||||
if let currentSelected = jsonDict["selected"] as? String, currentSelected == newSelection {
|
if let currentSelected = jsonDict["selected"] as? String, currentSelected == newSelection {
|
103
src/MeloNX/MeloNX/App/Views/MeloNXApp.swift
Normal file
103
src/MeloNX/MeloNX/App/Views/MeloNXApp.swift
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
//
|
||||||
|
// MeloNXApp.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 3/11/2024.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
import CryptoKit
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@main
|
||||||
|
struct MeloNXApp: App {
|
||||||
|
|
||||||
|
@State var showed = false
|
||||||
|
@Environment(\.scenePhase) var scenePhase
|
||||||
|
@State var alert: UIAlertController? = nil
|
||||||
|
|
||||||
|
@State var showOutOfDateSheet = false
|
||||||
|
@State var updateInfo: LatestVersionResponse? = nil
|
||||||
|
|
||||||
|
@State var finished = false
|
||||||
|
@AppStorage("hasbeenfinished") var finishedStorage: Bool = false
|
||||||
|
|
||||||
|
var body: some Scene {
|
||||||
|
WindowGroup {
|
||||||
|
if finishedStorage {
|
||||||
|
ContentView()
|
||||||
|
.onAppear {
|
||||||
|
checkLatestVersion()
|
||||||
|
}
|
||||||
|
.sheet(isPresented: Binding(
|
||||||
|
get: { showOutOfDateSheet && updateInfo != nil },
|
||||||
|
set: { newValue in
|
||||||
|
if !newValue {
|
||||||
|
showOutOfDateSheet = false
|
||||||
|
updateInfo = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)) {
|
||||||
|
if let updateInfo = updateInfo {
|
||||||
|
MeloNXUpdateSheet(updateInfo: updateInfo, isPresented: $showOutOfDateSheet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
SetupView(finished: $finished)
|
||||||
|
.onChange(of: finished) { newValue in
|
||||||
|
withAnimation {
|
||||||
|
withAnimation {
|
||||||
|
finishedStorage = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkLatestVersion() {
|
||||||
|
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
|
||||||
|
let strippedAppVersion = appVersion.replacingOccurrences(of: ".", with: "")
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
let urlString = "http://192.168.178.116:8000/api/latest_release"
|
||||||
|
#else
|
||||||
|
let urlString = "https://melonx.org/api/latest_release"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
guard let url = URL(string: urlString) else {
|
||||||
|
print("Invalid URL")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let task = URLSession.shared.dataTask(with: url) { data, response, error in
|
||||||
|
if let error = error {
|
||||||
|
print("Error checking for new version: \(error)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let data = data else {
|
||||||
|
print("No data received")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let latestVersionResponse = try JSONDecoder().decode(LatestVersionResponse.self, from: data)
|
||||||
|
let latestAPIVersionStripped = latestVersionResponse.version_number_stripped
|
||||||
|
|
||||||
|
if Int(strippedAppVersion) ?? 0 > Int(latestAPIVersionStripped) ?? 0 {
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
updateInfo = latestVersionResponse
|
||||||
|
showOutOfDateSheet = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Failed to decode response: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task.resume()
|
||||||
|
}
|
||||||
|
}
|
399
src/MeloNX/MeloNX/App/Views/Setup/SetupView.swift
Normal file
399
src/MeloNX/MeloNX/App/Views/Setup/SetupView.swift
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
//
|
||||||
|
// SetupView.swift
|
||||||
|
// MeloNX
|
||||||
|
//
|
||||||
|
// Created by Stossy11 on 04/03/2025.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import UniformTypeIdentifiers
|
||||||
|
|
||||||
|
struct SetupView: View {
|
||||||
|
@State private var isImportingKeys = false
|
||||||
|
@State private var isImportingFirmware = false
|
||||||
|
@State private var showAlert = false
|
||||||
|
@State private var showSkipAlert = false
|
||||||
|
@State private var alertMessage = ""
|
||||||
|
@State private var keysImported = false
|
||||||
|
@State private var firmImported = false
|
||||||
|
@Binding var finished: Bool
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
iOSNav {
|
||||||
|
ZStack {
|
||||||
|
if UIDevice.current.userInterfaceIdiom == .pad {
|
||||||
|
iPadSetupView(
|
||||||
|
finished: $finished,
|
||||||
|
isImportingKeys: $isImportingKeys,
|
||||||
|
isImportingFirmware: $isImportingFirmware,
|
||||||
|
keysImported: keysImported,
|
||||||
|
firmImported: firmImported
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
iPhoneSetupView(
|
||||||
|
finished: $finished,
|
||||||
|
isImportingKeys: $isImportingKeys,
|
||||||
|
isImportingFirmware: $isImportingFirmware,
|
||||||
|
keysImported: keysImported,
|
||||||
|
firmImported: firmImported
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fileImporter(
|
||||||
|
isPresented: $isImportingKeys,
|
||||||
|
allowedContentTypes: [.item],
|
||||||
|
allowsMultipleSelection: true
|
||||||
|
) { result in
|
||||||
|
handleKeysImport(result: result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fileImporter(
|
||||||
|
isPresented: $isImportingFirmware,
|
||||||
|
allowedContentTypes: [.item],
|
||||||
|
allowsMultipleSelection: false
|
||||||
|
) { result in
|
||||||
|
handleFirmwareImport(result: result)
|
||||||
|
}
|
||||||
|
.alert(alertMessage, isPresented: $showAlert) {
|
||||||
|
Button("OK", role: .cancel) {}
|
||||||
|
}
|
||||||
|
.alert("Skip Setup?", isPresented: $showSkipAlert) {
|
||||||
|
Button("Skip", role: .destructive) { finished = true }
|
||||||
|
Button("Cancel", role: .cancel) {}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
initialize()
|
||||||
|
finished = false
|
||||||
|
keysImported = Ryujinx.shared.checkIfKeysImported()
|
||||||
|
print((Double(Ryujinx.shared.fetchFirmwareVersion()) ?? 0))
|
||||||
|
firmImported = (Ryujinx.shared.fetchFirmwareVersion() != "0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func iPadSetupView(
|
||||||
|
finished: Binding<Bool>,
|
||||||
|
isImportingKeys: Binding<Bool>,
|
||||||
|
isImportingFirmware: Binding<Bool>,
|
||||||
|
keysImported: Bool,
|
||||||
|
firmImported: Bool
|
||||||
|
) -> some View {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
ZStack {
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
Color.blue.opacity(0.1),
|
||||||
|
Color.red.opacity(0.1)
|
||||||
|
]),
|
||||||
|
startPoint: .topLeading,
|
||||||
|
endPoint: .bottomTrailing
|
||||||
|
)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
HStack(spacing: 40) {
|
||||||
|
if geometry.size.width > 800 {
|
||||||
|
VStack(alignment: .center, spacing: 20) {
|
||||||
|
Image(uiImage: UIImage(named: appIcon()) ?? UIImage())
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 200, height: 200)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 40))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 40)
|
||||||
|
.stroke(
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
.blue.opacity(0.6),
|
||||||
|
.red.opacity(0.6)
|
||||||
|
]),
|
||||||
|
startPoint: .leading,
|
||||||
|
endPoint: .trailing
|
||||||
|
),
|
||||||
|
lineWidth: 2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.shadow(color: .black.opacity(0.1), radius: 15, x: 0, y: 6)
|
||||||
|
|
||||||
|
Text("Welcome to MeloNX")
|
||||||
|
.font(.title)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.onTapGesture(count: 2) {
|
||||||
|
showSkipAlert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Set up your Nintendo Switch emulation environment by importing keys and firmware.")
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: 400)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
setupStep(
|
||||||
|
title: "Import Keys",
|
||||||
|
description: "Add your encryption keys",
|
||||||
|
systemImage: "key.fill",
|
||||||
|
isCompleted: keysImported,
|
||||||
|
action: { isImportingKeys.wrappedValue = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
setupStep(
|
||||||
|
title: "Add Firmware",
|
||||||
|
description: "Install Nintendo Switch firmware",
|
||||||
|
systemImage: "square.and.arrow.down",
|
||||||
|
isCompleted: firmImported,
|
||||||
|
isEnabled: keysImported,
|
||||||
|
action: { isImportingFirmware.wrappedValue = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
Button(action: { finished.wrappedValue = true }) {
|
||||||
|
HStack {
|
||||||
|
Text("Finish Setup")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
.background(
|
||||||
|
firmImported && keysImported
|
||||||
|
? Color.blue
|
||||||
|
: Color.blue.opacity(0.3)
|
||||||
|
)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.cornerRadius(12)
|
||||||
|
}
|
||||||
|
.disabled(!(firmImported && keysImported))
|
||||||
|
}
|
||||||
|
.frame(maxWidth: 500)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.navigationTitle("Setup")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func iPhoneSetupView(
|
||||||
|
finished: Binding<Bool>,
|
||||||
|
isImportingKeys: Binding<Bool>,
|
||||||
|
isImportingFirmware: Binding<Bool>,
|
||||||
|
keysImported: Bool,
|
||||||
|
firmImported: Bool
|
||||||
|
) -> some View {
|
||||||
|
ZStack {
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
Color.blue.opacity(0.1),
|
||||||
|
Color.red.opacity(0.1)
|
||||||
|
]),
|
||||||
|
startPoint: .topLeading,
|
||||||
|
endPoint: .bottomTrailing
|
||||||
|
)
|
||||||
|
.ignoresSafeArea()
|
||||||
|
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
ScrollView {
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
Image(uiImage: UIImage(named: appIcon()) ?? UIImage())
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 120, height: 120)
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 24))
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 24)
|
||||||
|
.stroke(
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
.blue.opacity(0.6),
|
||||||
|
.red.opacity(0.6)
|
||||||
|
]),
|
||||||
|
startPoint: .leading,
|
||||||
|
endPoint: .trailing
|
||||||
|
),
|
||||||
|
lineWidth: 2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.shadow(color: .black.opacity(0.1), radius: 12, x: 0, y: 4)
|
||||||
|
.padding(.top, 40)
|
||||||
|
|
||||||
|
Text("Welcome to MeloNX")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
.onTapGesture(count: 2) {
|
||||||
|
showSkipAlert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
setupStep(
|
||||||
|
title: "Import Keys",
|
||||||
|
description: "Add your encryption keys",
|
||||||
|
systemImage: "key.fill",
|
||||||
|
isCompleted: keysImported,
|
||||||
|
action: { isImportingKeys.wrappedValue = true }
|
||||||
|
)
|
||||||
|
|
||||||
|
setupStep(
|
||||||
|
title: "Add Firmware",
|
||||||
|
description: "Install Nintendo Switch firmware",
|
||||||
|
systemImage: "square.and.arrow.down",
|
||||||
|
isCompleted: firmImported,
|
||||||
|
isEnabled: keysImported,
|
||||||
|
action: { isImportingFirmware.wrappedValue = true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finish Button
|
||||||
|
VStack {
|
||||||
|
Button(action: { finished.wrappedValue = true }) {
|
||||||
|
HStack {
|
||||||
|
Text("Finish Setup")
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
.background(
|
||||||
|
firmImported && keysImported
|
||||||
|
? Color.blue
|
||||||
|
: Color.blue.opacity(0.3)
|
||||||
|
)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.cornerRadius(12)
|
||||||
|
}
|
||||||
|
.disabled(!(firmImported && keysImported))
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.navigationTitle("Setup")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupStep(
|
||||||
|
title: String,
|
||||||
|
description: String,
|
||||||
|
systemImage: String,
|
||||||
|
isCompleted: Bool,
|
||||||
|
isEnabled: Bool = true,
|
||||||
|
action: @escaping () -> Void
|
||||||
|
) -> some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: systemImage)
|
||||||
|
.foregroundColor(isCompleted ? .green : .blue)
|
||||||
|
.imageScale(.large)
|
||||||
|
|
||||||
|
VStack(alignment: .leading) {
|
||||||
|
Text(title)
|
||||||
|
.font(.headline)
|
||||||
|
Text(description)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if isCompleted {
|
||||||
|
Image(systemName: "checkmark.circle.fill")
|
||||||
|
.foregroundColor(.green)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color(UIColor.secondarySystemBackground))
|
||||||
|
.cornerRadius(12)
|
||||||
|
}
|
||||||
|
.disabled(!isEnabled || isCompleted)
|
||||||
|
.opacity(isEnabled ? 1.0 : 0.5)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleKeysImport(result: Result<[URL], Error>) {
|
||||||
|
do {
|
||||||
|
let selectedFiles = try result.get()
|
||||||
|
|
||||||
|
guard selectedFiles.count == 2 else {
|
||||||
|
alertMessage = "Please select exactly 2 key files"
|
||||||
|
showAlert = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for fileURL in selectedFiles {
|
||||||
|
guard fileURL.startAccessingSecurityScopedResource() else {
|
||||||
|
alertMessage = "Permission denied to access file"
|
||||||
|
showAlert = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer {
|
||||||
|
fileURL.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
let destinationURL = URL.documentsDirectory.appendingPathComponent("system").appendingPathComponent(fileURL.lastPathComponent)
|
||||||
|
|
||||||
|
try FileManager.default.copyItem(at: fileURL, to: destinationURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
keysImported = Ryujinx.shared.checkIfKeysImported()
|
||||||
|
alertMessage = "Keys imported successfully"
|
||||||
|
showAlert = true
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
alertMessage = "Error importing keys: \(error.localizedDescription)"
|
||||||
|
showAlert = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleFirmwareImport(result: Result<[URL], Error>) {
|
||||||
|
do {
|
||||||
|
let selectedFiles = try result.get()
|
||||||
|
|
||||||
|
guard let fileURL = selectedFiles.first else {
|
||||||
|
alertMessage = "No file selected"
|
||||||
|
showAlert = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard fileURL.startAccessingSecurityScopedResource() else {
|
||||||
|
alertMessage = "Permission denied to access file"
|
||||||
|
showAlert = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer {
|
||||||
|
fileURL.stopAccessingSecurityScopedResource()
|
||||||
|
}
|
||||||
|
|
||||||
|
Ryujinx.shared.installFirmware(firmwarePath: fileURL.path)
|
||||||
|
|
||||||
|
print(Double(Ryujinx.shared.fetchFirmwareVersion()) ?? 0)
|
||||||
|
|
||||||
|
firmImported = (Ryujinx.shared.fetchFirmwareVersion() != "0")
|
||||||
|
alertMessage = "Firmware installed successfully"
|
||||||
|
showAlert = true
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
alertMessage = "Error importing firmware: \(error.localizedDescription)"
|
||||||
|
showAlert = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appIcon(in bundle: Bundle = .main) -> String {
|
||||||
|
guard let icons = bundle.object(forInfoDictionaryKey: "CFBundleIcons") as? [String: Any],
|
||||||
|
|
||||||
|
let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any],
|
||||||
|
|
||||||
|
let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String],
|
||||||
|
|
||||||
|
let iconFileName = iconFiles.last else {
|
||||||
|
|
||||||
|
print("Could not find icons in bundle")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return iconFileName
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
@ -1,263 +0,0 @@
|
|||||||
//
|
|
||||||
// MeloNXApp.swift
|
|
||||||
// MeloNX
|
|
||||||
//
|
|
||||||
// Created by Stossy11 on 3/11/2024.
|
|
||||||
//
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import UIKit
|
|
||||||
import CryptoKit
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@main
|
|
||||||
struct MeloNXApp: App {
|
|
||||||
|
|
||||||
@State var showed = false
|
|
||||||
@Environment(\.scenePhase) var scenePhase
|
|
||||||
@State var alert: UIAlertController? = nil
|
|
||||||
|
|
||||||
var body: some Scene {
|
|
||||||
WindowGroup {
|
|
||||||
ZStack {
|
|
||||||
if showed || DRM != 1 {
|
|
||||||
ContentView()
|
|
||||||
} else {
|
|
||||||
Group {
|
|
||||||
VStack {
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
HStack {
|
|
||||||
Text("Loading...")
|
|
||||||
ProgressView()
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
Text(UIDevice.current.identifierForVendor?.uuidString ?? "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
initR()
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
||||||
.background(Color.black.opacity(1))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func initR() {
|
|
||||||
if DRM == 1 {
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
// drmcheck()
|
|
||||||
InitializeRyujinx() { bool in
|
|
||||||
if bool {
|
|
||||||
print("Ryujinx Files Initialized Successfully")
|
|
||||||
DispatchQueue.main.async { [self] in
|
|
||||||
withAnimation {
|
|
||||||
showed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
|
||||||
InitializeRyujinx() { bool in
|
|
||||||
if !bool, (scenePhase != .background || scenePhase == .inactive) {
|
|
||||||
withAnimation {
|
|
||||||
showed = false
|
|
||||||
}
|
|
||||||
if !(alert?.isViewLoaded ?? false) {
|
|
||||||
alert = showDMCAAlert()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
alert?.dismiss(animated: true)
|
|
||||||
showed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
showDMCAAlert()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func showAlert() -> UIAlertController? {
|
|
||||||
// Create the alert controller
|
|
||||||
if let mainWindow = UIApplication.shared.windows.last {
|
|
||||||
let alertController = UIAlertController(title: "Enter license", message: "Enter license key:", preferredStyle: .alert)
|
|
||||||
|
|
||||||
// Add a text field to the alert
|
|
||||||
alertController.addTextField { textField in
|
|
||||||
textField.placeholder = "Enter key here"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the "OK" action
|
|
||||||
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
|
|
||||||
// Get the text entered in the text field
|
|
||||||
if let textField = alertController.textFields?.first, let enteredText = textField.text {
|
|
||||||
print("Entered text: \(enteredText)")
|
|
||||||
UserDefaults.standard.set(enteredText, forKey: "MeloDRMID")
|
|
||||||
// drmcheck() { bool in
|
|
||||||
// if bool {
|
|
||||||
// showed = true
|
|
||||||
// } else {
|
|
||||||
// exit(0)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
alertController.addAction(okAction)
|
|
||||||
|
|
||||||
// Add a "Cancel" action
|
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
|
||||||
alertController.addAction(cancelAction)
|
|
||||||
|
|
||||||
// Present the alert
|
|
||||||
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
|
||||||
|
|
||||||
return alertController
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func showDMCAAlert() -> UIAlertController? {
|
|
||||||
if let mainWindow = UIApplication.shared.windows.first {
|
|
||||||
let alertController = UIAlertController(title: "Unauthorized Copy Notice", message: "This app was illegally leaked. Please report the download on the MeloNX Discord. In the meantime, check out Pomelo! \n -Stossy11", preferredStyle: .alert)
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
mainWindow.rootViewController!.present(alertController, animated: true, completion: nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return alertController
|
|
||||||
} else {
|
|
||||||
// uhoh
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func drmcheck(completion: @escaping (Bool) -> Void) {
|
|
||||||
if let deviceid = UIDevice.current.identifierForVendor?.uuidString, let base64device = deviceid.data(using: .utf8)?.base64EncodedString() {
|
|
||||||
if let value = UserDefaults.standard.string(forKey: "MeloDRMID") {
|
|
||||||
if let url = URL(string: "https://mx.stossy11.com/auth/\(value)/\(base64device)") {
|
|
||||||
print(url)
|
|
||||||
// Create a URLSession
|
|
||||||
let session = URLSession.shared
|
|
||||||
|
|
||||||
// Create a data task
|
|
||||||
let task = session.dataTask(with: url) { data, response, error in
|
|
||||||
// Handle errors
|
|
||||||
if let error = error {
|
|
||||||
exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check response and data
|
|
||||||
if let response = response as? HTTPURLResponse, response.statusCode == 200 {
|
|
||||||
print("Successfully Recieved API Data")
|
|
||||||
completion(true)
|
|
||||||
} else if let response = response as? HTTPURLResponse, response.statusCode == 201 {
|
|
||||||
print("Successfully Created Auth UUID")
|
|
||||||
completion(true)
|
|
||||||
} else {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start the task
|
|
||||||
task.resume()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func InitializeRyujinx(completion: @escaping (Bool) -> Void) {
|
|
||||||
let path = "aHR0cHM6Ly9teC5zdG9zc3kxMS5jb20v"
|
|
||||||
|
|
||||||
guard let value = Bundle.main.object(forInfoDictionaryKey: "MeloID") as? String, !value.isEmpty else {
|
|
||||||
completion(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (detectRoms(path: path) != value) {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
let configuration = URLSessionConfiguration.default
|
|
||||||
configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
|
|
||||||
configuration.urlCache = nil
|
|
||||||
|
|
||||||
let session = URLSession(configuration: configuration)
|
|
||||||
|
|
||||||
guard let url = URL(string: addFolders(path)!) else {
|
|
||||||
completion(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let task = session.dataTask(with: url) { data, response, error in
|
|
||||||
if error != nil {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
guard let httpResponse = response as? HTTPURLResponse else {
|
|
||||||
completion(false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if httpResponse.statusCode == 200 {
|
|
||||||
completion(true)
|
|
||||||
} else {
|
|
||||||
completion(false)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
task.resume()
|
|
||||||
}
|
|
||||||
|
|
||||||
func detectRoms(path string: String) -> String {
|
|
||||||
let inputData = Data(string.utf8)
|
|
||||||
let romHash = SHA256.hash(data: inputData)
|
|
||||||
return romHash.compactMap { String(format: "%02x", $0) }.joined()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func addFolders(_ folderPath: String) -> String? {
|
|
||||||
let fileManager = FileManager.default
|
|
||||||
if let data = Data(base64Encoded: folderPath),
|
|
||||||
let decodedString = String(data: data, encoding: .utf8), let fileURL = UIDevice.current.identifierForVendor?.uuidString {
|
|
||||||
return decodedString + "auth/" + fileURL + "/"
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
extension String {
|
|
||||||
|
|
||||||
func print() {
|
|
||||||
Swift.print(self)
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
private bool _stillRunning;
|
private bool _stillRunning;
|
||||||
private readonly Thread _updaterThread;
|
private readonly Thread _updaterThread;
|
||||||
|
|
||||||
|
private float _volume;
|
||||||
|
|
||||||
|
public float Volume
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _volume;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_volume = value;
|
||||||
|
|
||||||
|
foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
|
||||||
|
{
|
||||||
|
session.UpdateMasterVolume(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public OpenALHardwareDeviceDriver()
|
public OpenALHardwareDeviceDriver()
|
||||||
{
|
{
|
||||||
_device = ALC.OpenDevice("");
|
_device = ALC.OpenDevice("");
|
||||||
@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
Name = "HardwareDeviceDriver.OpenAL",
|
Name = "HardwareDeviceDriver.OpenAL",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_volume = 1f;
|
||||||
|
|
||||||
_updaterThread.Start();
|
_updaterThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
|
||||||
{
|
{
|
||||||
if (channelCount == 0)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
throw new ArgumentException($"{channelCount}");
|
throw new ArgumentException($"{channelCount}");
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||||
|
|
||||||
_sessions.TryAdd(session, 0);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
|
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
|
||||||
private ulong _playedSampleCount;
|
private ulong _playedSampleCount;
|
||||||
|
private float _volume;
|
||||||
|
|
||||||
private readonly object _lock = new();
|
private readonly object _lock = new();
|
||||||
|
|
||||||
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||||
{
|
{
|
||||||
_driver = driver;
|
_driver = driver;
|
||||||
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
||||||
@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
_targetFormat = GetALFormat();
|
_targetFormat = GetALFormat();
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
_playedSampleCount = 0;
|
_playedSampleCount = 0;
|
||||||
SetVolume(requestedVolume);
|
SetVolume(1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ALFormat GetALFormat()
|
private ALFormat GetALFormat()
|
||||||
@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
|||||||
|
|
||||||
public override void SetVolume(float volume)
|
public override void SetVolume(float volume)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
_volume = volume;
|
||||||
{
|
|
||||||
AL.Source(_sourceId, ALSourcef.Gain, volume);
|
UpdateMasterVolume(_driver.Volume);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetVolume()
|
public override float GetVolume()
|
||||||
{
|
{
|
||||||
AL.GetSource(_sourceId, ALSourcef.Gain, out float volume);
|
return _volume;
|
||||||
|
}
|
||||||
|
|
||||||
return volume;
|
public void UpdateMasterVolume(float newVolume)
|
||||||
|
{
|
||||||
|
lock (_lock)
|
||||||
|
{
|
||||||
|
AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Start()
|
public override void Start()
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user