Compare commits
68 Commits
Author | SHA1 | Date | |
---|---|---|---|
8dd1eb333c | |||
7dc3a62c14 | |||
e59dba42ef | |||
bd6937ae5c | |||
b82e789d4f | |||
4a6724622e | |||
0c73eba3db | |||
a082e14ede | |||
d29da11d5f | |||
ea07328aea | |||
a0b3d82ee0 | |||
609de33b0b | |||
dfc0819e72 | |||
d4803356bb | |||
459efd0db7 | |||
8bb7a3fc97 | |||
628d092fc6 | |||
6c90d50c8e | |||
d56bab1e24 | |||
a37e2d6e44 | |||
25123232bd | |||
8927e0669f | |||
bbed3b9926 | |||
24c8b0edc0 | |||
e5066449a5 | |||
d704bcd93b | |||
c94f0fbb83 | |||
d1b30fbe08 | |||
4505a7f162 | |||
ccbbaddbcb | |||
8bf102d2cd | |||
2adf031830 | |||
bb4a28b525 | |||
a8fbcdae9f | |||
4e81ab4229 | |||
4117c13377 | |||
20a392ad55 | |||
70fcba39de | |||
7795b662a9 | |||
30bdc4544e | |||
f6475cca17 | |||
0335c52254 | |||
b8d992e5a7 | |||
a620cbcc90 | |||
cea204d48e | |||
35fb409e85 | |||
d7ec4308b4 | |||
fbdd390f90 | |||
f33fea3287 | |||
5d3eea40be | |||
cd37c75b82 | |||
43705c2320 | |||
371e6fa24c | |||
1d9b63cc6a | |||
795539bc82 | |||
dd2e851e95 | |||
2ca70eb9a0 | |||
6575952432 | |||
9a28ba72b1 | |||
34a9922b57 | |||
4df22eb867 | |||
f241f88558 | |||
90455a05e6 | |||
edc76883db | |||
427b7d06b5 | |||
331c07807f | |||
a772b073ec | |||
870d9599cc |
@ -17,8 +17,8 @@ tab_width = 4
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# JSON files
|
||||
[*.json]
|
||||
# Markdown, JSON, YAML, props and csproj files
|
||||
[*.{md,json,yml,props,csproj}]
|
||||
|
||||
# Indentation and spacing
|
||||
indent_size = 2
|
||||
|
67
.github/workflows/build.yml
vendored
67
.github/workflows/build.yml
vendored
@ -10,28 +10,17 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: ${{ matrix.platform.name }} (${{ matrix.configuration }})
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
configuration: [Debug, Release]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
OS_NAME: Linux x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
||||
RELEASE_ZIP_OS_NAME: linux_x64
|
||||
|
||||
- os: macOS-latest
|
||||
OS_NAME: macOS x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: osx-x64
|
||||
RELEASE_ZIP_OS_NAME: osx_x64
|
||||
|
||||
- os: windows-latest
|
||||
OS_NAME: Windows x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
- { name: osx-x64, os: macOS-latest, zip_os_name: osx_x64 }
|
||||
|
||||
fail-fast: false
|
||||
steps:
|
||||
@ -49,6 +38,16 @@ jobs:
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- name: Change config filename
|
||||
run: sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/PRConfig\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- 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-latest'
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c "${{ matrix.configuration }}" -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER
|
||||
|
||||
@ -58,46 +57,47 @@ jobs:
|
||||
commands: dotnet test --no-build -c "${{ matrix.configuration }}"
|
||||
timeout-minutes: 10
|
||||
retry-codes: 139
|
||||
if: matrix.platform.name != 'linux-arm64'
|
||||
|
||||
- name: Publish Ryujinx
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Publish Ryujinx.Headless.SDL2
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_sdl2_headless -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Publish Ryujinx.Ava
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
run: dotnet publish -c "${{ matrix.configuration }}" -r "${{ matrix.platform.name }}" -o ./publish_ava -p:Version="${{ env.RYUJINX_BASE_VERSION }}" -p:DebugType=embedded -p:SourceRevisionId="${{ steps.git_short_hash.outputs.result }}" -p:ExtraDefineConstants=DISABLE_UPDATER src/Ryujinx.Ava --self-contained true
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Set executable bit
|
||||
run: |
|
||||
chmod +x ./publish/Ryujinx ./publish/Ryujinx.sh
|
||||
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
|
||||
chmod +x ./publish_ava/Ryujinx.Ava ./publish_ava/Ryujinx.sh
|
||||
if: github.event_name == 'pull_request' && matrix.os == 'ubuntu-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
|
||||
|
||||
- name: Upload Ryujinx artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Upload Ryujinx.Headless.SDL2 artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_sdl2_headless
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
- name: Upload Ryujinx.Ava artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.RELEASE_ZIP_OS_NAME }}
|
||||
name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}
|
||||
path: publish_ava
|
||||
if: github.event_name == 'pull_request' && matrix.os != 'macOS-latest'
|
||||
if: github.event_name == 'pull_request' && matrix.platform.os != 'macOS-latest'
|
||||
|
||||
build_macos:
|
||||
name: macOS Universal (${{ matrix.configuration }})
|
||||
@ -135,6 +135,11 @@ jobs:
|
||||
id: git_short_hash
|
||||
run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT
|
||||
|
||||
- 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.Ava
|
||||
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"
|
||||
|
2
.github/workflows/checks.yml
vendored
2
.github/workflows/checks.yml
vendored
@ -8,7 +8,7 @@ on:
|
||||
- '!.github/**'
|
||||
- '!*.yml'
|
||||
- '!*.config'
|
||||
- '!README.md'
|
||||
- '!*.md'
|
||||
- '.github/workflows/*.yml'
|
||||
|
||||
permissions:
|
||||
|
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 }}
|
19
.github/workflows/pr_triage.yml
vendored
19
.github/workflows/pr_triage.yml
vendored
@ -21,27 +21,8 @@ jobs:
|
||||
repository: Ryujinx/Ryujinx
|
||||
ref: master
|
||||
|
||||
- name: Checkout Ryujinx-Mako
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: Ryujinx/Ryujinx-Mako
|
||||
ref: master
|
||||
path: '.ryujinx-mako'
|
||||
|
||||
- name: Setup Ryujinx-Mako
|
||||
uses: ./.ryujinx-mako/.github/actions/setup-mako
|
||||
|
||||
- name: Update labels based on changes
|
||||
uses: actions/labeler@v5
|
||||
with:
|
||||
sync-labels: true
|
||||
dot: true
|
||||
|
||||
- name: Assign reviewers
|
||||
run: |
|
||||
poetry -n -C .ryujinx-mako run ryujinx-mako update-reviewers ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml
|
||||
shell: bash
|
||||
env:
|
||||
MAKO_APP_ID: ${{ secrets.MAKO_APP_ID }}
|
||||
MAKO_PRIVATE_KEY: ${{ secrets.MAKO_PRIVATE_KEY }}
|
||||
MAKO_INSTALLATION_ID: ${{ secrets.MAKO_INSTALLATION_ID }}
|
||||
|
45
.github/workflows/release.yml
vendored
45
.github/workflows/release.yml
vendored
@ -10,7 +10,7 @@ on:
|
||||
- '*.yml'
|
||||
- '*.json'
|
||||
- '*.config'
|
||||
- 'README.md'
|
||||
- '*.md'
|
||||
|
||||
concurrency: release
|
||||
|
||||
@ -45,22 +45,15 @@ jobs:
|
||||
})
|
||||
|
||||
release:
|
||||
name: Release ${{ matrix.OS_NAME }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Release for ${{ matrix.platform.name }}
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
OS_NAME: Linux x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: linux-x64
|
||||
RELEASE_ZIP_OS_NAME: linux_x64
|
||||
|
||||
- os: windows-latest
|
||||
OS_NAME: Windows x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
platform:
|
||||
- { name: win-x64, os: windows-latest, zip_os_name: win_x64 }
|
||||
- { name: linux-x64, os: ubuntu-latest, zip_os_name: linux_x64 }
|
||||
- { name: linux-arm64, os: ubuntu-latest, zip_os_name: linux_arm64 }
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -85,6 +78,7 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Create output dir
|
||||
@ -92,42 +86,42 @@ jobs:
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_gtk/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_sdl2_headless/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Headless.SDL2 --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.DOTNET_RUNTIME_IDENTIFIER }}" -o ./publish_ava/publish -p:Version="${{ steps.version_info.outputs.build_version }}" -p:SourceRevisionId="${{ steps.version_info.outputs.git_short_hash }}" -p:DebugType=embedded src/Ryujinx.Ava --self-contained true
|
||||
dotnet publish -c Release -r "${{ matrix.platform.name }}" -o ./publish_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_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_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
|
||||
if: matrix.os == 'windows-latest'
|
||||
if: matrix.platform.os == 'windows-latest'
|
||||
run: |
|
||||
pushd publish_gtk
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||
popd
|
||||
|
||||
pushd publish_sdl2_headless
|
||||
7z a ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-win_x64.zip publish
|
||||
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
|
||||
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
- name: Packing Linux builds
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
if: matrix.platform.os == 'ubuntu-latest'
|
||||
run: |
|
||||
pushd publish_gtk
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||
popd
|
||||
|
||||
pushd publish_sdl2_headless
|
||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Headless.SDL2
|
||||
tar -czvf ../release_output/sdl2-ryujinx-headless-${{ steps.version_info.outputs.build_version }}-linux_x64.tar.gz publish
|
||||
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
|
||||
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||
popd
|
||||
shell: bash
|
||||
|
||||
@ -186,6 +180,7 @@ jobs:
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_NAME\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_NAME }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
sed -r --in-place 's/\%\%RYUJINX_CONFIG_FILE_NAME\%\%/Config\.json/g;' src/Ryujinx.Common/ReleaseInformation.cs
|
||||
shell: bash
|
||||
|
||||
- name: Publish macOS Ryujinx.Ava
|
||||
|
@ -8,35 +8,34 @@
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="11.0.7" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="11.0.7" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="11.0.7" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.10" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.10" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="11.0.0.13" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="11.0.0.13" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageVersion Include="DynamicData" Version="7.14.2" />
|
||||
<PackageVersion Include="DynamicData" Version="8.3.27" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="2.0.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.2.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
|
||||
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.3.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="3.0.0" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
|
||||
<PackageVersion Include="NetCoreServer" Version="8.0.7" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.2" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.2" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.3-build14" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.30.0-build32" />
|
||||
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||
@ -45,8 +44,8 @@
|
||||
<PackageVersion Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.16.0" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build32" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.1" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
|
87
README.md
87
README.md
@ -1,21 +1,21 @@
|
||||
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://ryujinx.org/"><img src="https://i.imgur.com/WcCj6Rt.png" alt="Ryujinx" width="150"></a>
|
||||
<a href="https://ryujinx.org/"><img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
|
||||
<br>
|
||||
<b>Ryujinx</b>
|
||||
<br>
|
||||
<sub><sup><b>(REE-YOU-JINX)</b></sup></sub>
|
||||
<br>
|
||||
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
|
||||
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
||||
It was written from scratch and development on the project began in September 2017. Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>. <br />
|
||||
|
||||
It was written from scratch and development on the project began in September 2017.
|
||||
Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
|
||||
<br />
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml">
|
||||
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
|
||||
@ -34,87 +34,111 @@
|
||||
<img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx-Website/master/public/assets/images/shell.png">
|
||||
</p>
|
||||
|
||||
<h5 align="center">
|
||||
|
||||
</h5>
|
||||
|
||||
## Compatibility
|
||||
|
||||
As of April 2023, Ryujinx has been tested on approximately 4,050 titles; over 4,000 boot past menus and into gameplay, with roughly 3,400 of those being considered playable.
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues). Anyone is free to submit a new game test or update an existing game test entry; simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue. Use the search function to see if a game has been tested already!
|
||||
As of October 2023, Ryujinx has been tested on approximately 4,200 titles;
|
||||
over 4,150 boot past menus and into gameplay, with roughly 3,500 of those being considered playable.
|
||||
|
||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
|
||||
|
||||
Anyone is free to submit a new game test or update an existing game test entry;
|
||||
simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue.
|
||||
Use the search function to see if a game has been tested already!
|
||||
|
||||
## Usage
|
||||
|
||||
To run this emulator, your PC must be equipped with at least 8GiB of RAM; failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
|
||||
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
|
||||
failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
|
||||
|
||||
See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator.
|
||||
|
||||
For our Local Wireless and LAN builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
||||
For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
||||
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
|
||||
|
||||
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
|
||||
|
||||
## Latest build
|
||||
|
||||
These builds are compiled automatically for each commit on the master branch. While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken.**
|
||||
These builds are compiled automatically for each commit on the master branch.
|
||||
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
|
||||
|
||||
If you want to see details on updates to the emulator, you can visit our [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog).
|
||||
|
||||
The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/download).
|
||||
|
||||
## Documentation
|
||||
|
||||
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
||||
|
||||
## Building
|
||||
|
||||
If you wish to build the emulator yourself, follow these steps:
|
||||
|
||||
### Step 1
|
||||
Install the X64 version of [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
|
||||
|
||||
Install the [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
|
||||
Make sure your SDK version is higher or equal to the required version specified in [global.json](global.json).
|
||||
|
||||
### Step 2
|
||||
|
||||
Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
|
||||
|
||||
### Step 3
|
||||
|
||||
To build Ryujinx, open a command prompt inside the project directory. You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`. Then type the following command:
|
||||
`dotnet build -c Release -o build`
|
||||
To build Ryujinx, open a command prompt inside the project directory.
|
||||
You can quickly access it on Windows by holding shift in File Explorer, then right clicking and selecting `Open command window here`.
|
||||
Then type the following command: `dotnet build -c Release -o build`
|
||||
the built files will be found in the newly created build directory.
|
||||
|
||||
Ryujinx system files are stored in the `Ryujinx` folder. This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
Ryujinx system files are stored in the `Ryujinx` folder.
|
||||
This folder is located in the user folder, which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
## Features
|
||||
|
||||
- **Audio**
|
||||
- **Audio**
|
||||
|
||||
Audio output is entirely supported, audio input (microphone) isn't supported. We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
|
||||
Audio output is entirely supported, audio input (microphone) isn't supported.
|
||||
We use C# wrappers for [OpenAL](https://openal-soft.org/), and [SDL2](https://www.libsdl.org/) & [libsoundio](http://libsound.io/) as fallbacks.
|
||||
|
||||
- **CPU**
|
||||
|
||||
The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support. It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
|
||||
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster). The fastest option (host, unchecked) is set by default.
|
||||
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads. The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game. NOTE: this feature is enabled by default in the Options menu > System tab. You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch! These improvements are permanent and do not require any extra launches going forward.
|
||||
The CPU emulator, ARMeilleure, emulates an ARMv8 CPU and currently has support for most 64-bit ARMv8 and some of the ARMv7 (and older) instructions, including partial 32-bit support.
|
||||
It translates the ARM code to a custom IR, performs a few optimizations, and turns that into x86 code.
|
||||
There are three memory manager options available depending on the user's preference, leveraging both software-based (slower) and host-mapped modes (much faster).
|
||||
The fastest option (host, unchecked) is set by default.
|
||||
Ryujinx also features an optional Profiled Persistent Translation Cache, which essentially caches translated functions so that they do not need to be translated every time the game loads.
|
||||
The net result is a significant reduction in load times (the amount of time between launching a game and arriving at the title screen) for nearly every game.
|
||||
NOTE: This feature is enabled by default in the Options menu > System tab.
|
||||
You must launch the game at least twice to the title screen or beyond before performance improvements are unlocked on the third launch!
|
||||
These improvements are permanent and do not require any extra launches going forward.
|
||||
|
||||
- **GPU**
|
||||
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively.
|
||||
There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment.
|
||||
These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
|
||||
- **Input**
|
||||
|
||||
We currently have support for keyboard, mouse, 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.
|
||||
We currently have support for keyboard, mouse, 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.
|
||||
In all scenarios, you can set up everything inside the input configuration menu.
|
||||
|
||||
- **DLC & Modifications**
|
||||
|
||||
Ryujinx is able to manage add-on content/downloadable content through the GUI. Mods (romfs, exefs, and runtime mods such as cheats) are also supported; the GUI contains a shortcut to open the respective mods folder for a particular game.
|
||||
Ryujinx is able to manage add-on content/downloadable content through the GUI.
|
||||
Mods (romfs, exefs, and runtime mods such as cheats) are also supported;
|
||||
the GUI contains a shortcut to open the respective mods folder for a particular game.
|
||||
|
||||
- **Configuration**
|
||||
|
||||
The emulator has settings for enabling or disabling some logging, remapping controllers, and more. You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
|
||||
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||
|
||||
## Contact
|
||||
|
||||
If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx). You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions).
|
||||
If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx).
|
||||
You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions).
|
||||
|
||||
## Donations
|
||||
|
||||
@ -134,9 +158,10 @@ All funds received through Patreon are considered a donation to support the proj
|
||||
|
||||
## License
|
||||
|
||||
This software is licensed under the terms of the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license.</a></i><br />
|
||||
This software is licensed under the terms of the [MIT license](LICENSE.txt).
|
||||
This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3.
|
||||
See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY.md) for more details.
|
||||
|
||||
## Credits
|
||||
|
||||
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
||||
|
@ -4,7 +4,7 @@ Name=Ryujinx
|
||||
Type=Application
|
||||
Icon=Ryujinx
|
||||
Exec=Ryujinx.sh %f
|
||||
Comment=Plays Nintendo Switch applications
|
||||
Comment=A Nintendo Switch Emulator
|
||||
GenericName=Nintendo Switch Emulator
|
||||
Terminal=false
|
||||
Categories=Game;Emulator;
|
||||
|
0
distribution/linux/Ryujinx.sh
Normal file → Executable file
0
distribution/linux/Ryujinx.sh
Normal file → Executable file
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}
|
@ -9,7 +9,7 @@ namespace ARMeilleure.Common
|
||||
/// Represents a table of guest address to a value.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntry">Type of the value</typeparam>
|
||||
unsafe class AddressTable<TEntry> : IDisposable where TEntry : unmanaged
|
||||
public unsafe class AddressTable<TEntry> : IDisposable where TEntry : unmanaged
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a level in an <see cref="AddressTable{TEntry}"/>.
|
||||
|
@ -517,7 +517,10 @@ namespace ARMeilleure.Decoders
|
||||
SetA64("0x00111100>>>xxx100111xxxxxxxxxx", InstName.Sqrshrn_V, InstEmit.Sqrshrn_V, OpCodeSimdShImm.Create);
|
||||
SetA64("0111111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_S, InstEmit.Sqrshrun_S, OpCodeSimdShImm.Create);
|
||||
SetA64("0x10111100>>>xxx100011xxxxxxxxxx", InstName.Sqrshrun_V, InstEmit.Sqrshrun_V, OpCodeSimdShImm.Create);
|
||||
SetA64("010111110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Si, InstEmit.Sqshl_Si, OpCodeSimdShImm.Create);
|
||||
SetA64("0>001110<<1xxxxx010011xxxxxxxxxx", InstName.Sqshl_V, InstEmit.Sqshl_V, OpCodeSimdReg.Create);
|
||||
SetA64("0000111100>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
|
||||
SetA64("010011110>>>>xxx011101xxxxxxxxxx", InstName.Sqshl_Vi, InstEmit.Sqshl_Vi, OpCodeSimdShImm.Create);
|
||||
SetA64("0101111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_S, InstEmit.Sqshrn_S, OpCodeSimdShImm.Create);
|
||||
SetA64("0x00111100>>>xxx100101xxxxxxxxxx", InstName.Sqshrn_V, InstEmit.Sqshrn_V, OpCodeSimdShImm.Create);
|
||||
SetA64("0111111100>>>xxx100001xxxxxxxxxx", InstName.Sqshrun_S, InstEmit.Sqshrun_S, OpCodeSimdShImm.Create);
|
||||
@ -872,6 +875,7 @@ namespace ARMeilleure.Decoders
|
||||
SetVfp("<<<<11100x10xxxxxxxx101xx1x0xxxx", InstName.Vnmul, InstEmit32.Vnmul_S, OpCode32SimdRegS.Create, OpCode32SimdRegS.CreateT32);
|
||||
SetVfp("111111101x1110xxxxxx101x01x0xxxx", InstName.Vrint, InstEmit32.Vrint_RM, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("<<<<11101x110110xxxx101x11x0xxxx", InstName.Vrint, InstEmit32.Vrint_Z, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("<<<<11101x110110xxxx101x01x0xxxx", InstName.Vrintr, InstEmit32.Vrintr_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("<<<<11101x110111xxxx101x01x0xxxx", InstName.Vrintx, InstEmit32.Vrintx_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("<<<<11101x110001xxxx101x11x0xxxx", InstName.Vsqrt, InstEmit32.Vsqrt_S, OpCode32SimdS.Create, OpCode32SimdS.CreateT32);
|
||||
SetVfp("111111100xxxxxxxxxxx101xx0x0xxxx", InstName.Vsel, InstEmit32.Vsel, OpCode32SimdSel.Create, OpCode32SimdSel.CreateT32);
|
||||
@ -992,6 +996,7 @@ namespace ARMeilleure.Decoders
|
||||
SetAsimd("1111001x1x000xxxxxxx<<x10x01xxxx", InstName.Vorr, InstEmit32.Vorr_II, OpCode32SimdImm.Create, OpCode32SimdImm.CreateT32);
|
||||
SetAsimd("111100100x<<xxxxxxxx1011x0x1xxxx", InstName.Vpadd, InstEmit32.Vpadd_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
SetAsimd("111100110x00xxxxxxxx1101x0x0xxxx", InstName.Vpadd, InstEmit32.Vpadd_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
SetAsimd("111100111x11<<00xxxx0110xxx0xxxx", InstName.Vpadal, InstEmit32.Vpadal, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||
SetAsimd("111100111x11<<00xxxx0010xxx0xxxx", InstName.Vpaddl, InstEmit32.Vpaddl, OpCode32SimdCmpZ.Create, OpCode32SimdCmpZ.CreateT32);
|
||||
SetAsimd("1111001x0x<<xxxxxxxx1010x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_I, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
SetAsimd("111100110x00xxxxxxxx1111x0x0xxxx", InstName.Vpmax, InstEmit32.Vpmax_V, OpCode32SimdReg.Create, OpCode32SimdReg.CreateT32);
|
||||
|
@ -1115,6 +1115,13 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Vpadal(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
EmitVectorPairwiseTernaryLongOpI32(context, (op1, op2, op3) => context.Add(context.Add(op1, op2), op3), op.Opc != 1);
|
||||
}
|
||||
|
||||
public static void Vpaddl(ArmEmitterContext context)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
@ -578,6 +578,22 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTR (floating-point).
|
||||
public static void Vrintr_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseAdvSimd)
|
||||
{
|
||||
InstEmitSimdHelper32Arm64.EmitScalarUnaryOpF32(context, Intrinsic.Arm64FrintiS);
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitScalarUnaryOpF32(context, (op1) =>
|
||||
{
|
||||
return EmitRoundByRMode(context, op1);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// VRINTZ (floating-point).
|
||||
public static void Vrint_Z(ArmEmitterContext context)
|
||||
{
|
||||
|
@ -673,6 +673,35 @@ namespace ARMeilleure.Instructions
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
public static void EmitVectorPairwiseTernaryLongOpI32(ArmEmitterContext context, Func3I emit, bool signed)
|
||||
{
|
||||
OpCode32Simd op = (OpCode32Simd)context.CurrOp;
|
||||
|
||||
int elems = op.GetBytesCount() >> op.Size;
|
||||
int pairs = elems >> 1;
|
||||
|
||||
Operand res = GetVecA32(op.Qd);
|
||||
|
||||
for (int index = 0; index < pairs; index++)
|
||||
{
|
||||
int pairIndex = index * 2;
|
||||
Operand m1 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex, op.Size, signed);
|
||||
Operand m2 = EmitVectorExtract32(context, op.Qm, op.Im + pairIndex + 1, op.Size, signed);
|
||||
|
||||
if (op.Size == 2)
|
||||
{
|
||||
m1 = signed ? context.SignExtend32(OperandType.I64, m1) : context.ZeroExtend32(OperandType.I64, m1);
|
||||
m2 = signed ? context.SignExtend32(OperandType.I64, m2) : context.ZeroExtend32(OperandType.I64, m2);
|
||||
}
|
||||
|
||||
Operand d1 = EmitVectorExtract32(context, op.Qd, op.Id + index, op.Size + 1, signed);
|
||||
|
||||
res = EmitVectorInsert(context, res, emit(m1, m2, d1), op.Id + index, op.Size + 1);
|
||||
}
|
||||
|
||||
context.Copy(GetVecA32(op.Qd), res);
|
||||
}
|
||||
|
||||
// Narrow
|
||||
|
||||
public static void EmitVectorUnaryNarrowOp32(ArmEmitterContext context, Func1I emit, bool signed = false)
|
||||
|
@ -116,7 +116,7 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
else if (shift >= eSize)
|
||||
{
|
||||
if ((op.RegisterSize == RegisterSize.Simd64))
|
||||
if (op.RegisterSize == RegisterSize.Simd64)
|
||||
{
|
||||
Operand res = context.VectorZeroUpper64(GetVec(op.Rd));
|
||||
|
||||
@ -359,6 +359,16 @@ namespace ARMeilleure.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
public static void Sqshl_Si(ArmEmitterContext context)
|
||||
{
|
||||
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Scalar | ShlRegFlags.Saturating);
|
||||
}
|
||||
|
||||
public static void Sqshl_Vi(ArmEmitterContext context)
|
||||
{
|
||||
EmitShlImmOp(context, signedDst: true, ShlRegFlags.Signed | ShlRegFlags.Saturating);
|
||||
}
|
||||
|
||||
public static void Sqshrn_S(ArmEmitterContext context)
|
||||
{
|
||||
if (Optimizations.UseAdvSimd)
|
||||
@ -1593,6 +1603,99 @@ namespace ARMeilleure.Instructions
|
||||
Saturating = 1 << 3,
|
||||
}
|
||||
|
||||
private static void EmitShlImmOp(ArmEmitterContext context, bool signedDst, ShlRegFlags flags = ShlRegFlags.None)
|
||||
{
|
||||
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
|
||||
bool signed = flags.HasFlag(ShlRegFlags.Signed);
|
||||
bool saturating = flags.HasFlag(ShlRegFlags.Saturating);
|
||||
|
||||
OpCodeSimdShImm op = (OpCodeSimdShImm)context.CurrOp;
|
||||
|
||||
Operand res = context.VectorZero();
|
||||
|
||||
int elems = !scalar ? op.GetBytesCount() >> op.Size : 1;
|
||||
|
||||
for (int index = 0; index < elems; index++)
|
||||
{
|
||||
Operand ne = EmitVectorExtract(context, op.Rn, index, op.Size, signed);
|
||||
|
||||
Operand e = !saturating
|
||||
? EmitShlImm(context, ne, GetImmShl(op), op.Size)
|
||||
: EmitShlImmSatQ(context, ne, GetImmShl(op), op.Size, signed, signedDst);
|
||||
|
||||
res = EmitVectorInsert(context, res, e, index, op.Size);
|
||||
}
|
||||
|
||||
context.Copy(GetVec(op.Rd), res);
|
||||
}
|
||||
|
||||
private static Operand EmitShlImm(ArmEmitterContext context, Operand op, int shiftLsB, int size)
|
||||
{
|
||||
int eSize = 8 << size;
|
||||
|
||||
Debug.Assert(op.Type == OperandType.I64);
|
||||
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
|
||||
|
||||
Operand res = context.AllocateLocal(OperandType.I64);
|
||||
|
||||
if (shiftLsB >= eSize)
|
||||
{
|
||||
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
|
||||
context.Copy(res, shl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand zeroL = Const(0L);
|
||||
context.Copy(res, zeroL);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static Operand EmitShlImmSatQ(ArmEmitterContext context, Operand op, int shiftLsB, int size, bool signedSrc, bool signedDst)
|
||||
{
|
||||
int eSize = 8 << size;
|
||||
|
||||
Debug.Assert(op.Type == OperandType.I64);
|
||||
Debug.Assert(eSize == 8 || eSize == 16 || eSize == 32 || eSize == 64);
|
||||
|
||||
Operand lblEnd = Label();
|
||||
|
||||
Operand res = context.Copy(context.AllocateLocal(OperandType.I64), op);
|
||||
|
||||
if (shiftLsB >= eSize)
|
||||
{
|
||||
context.Copy(res, signedSrc
|
||||
? EmitSignedSignSatQ(context, op, size)
|
||||
: EmitUnsignedSignSatQ(context, op, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand shl = context.ShiftLeft(op, Const(shiftLsB));
|
||||
if (eSize == 64)
|
||||
{
|
||||
Operand sarOrShr = signedSrc
|
||||
? context.ShiftRightSI(shl, Const(shiftLsB))
|
||||
: context.ShiftRightUI(shl, Const(shiftLsB));
|
||||
context.Copy(res, shl);
|
||||
context.BranchIf(lblEnd, sarOrShr, op, Comparison.Equal);
|
||||
context.Copy(res, signedSrc
|
||||
? EmitSignedSignSatQ(context, op, size)
|
||||
: EmitUnsignedSignSatQ(context, op, size));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Copy(res, signedSrc
|
||||
? EmitSignedSrcSatQ(context, shl, size, signedDst)
|
||||
: EmitUnsignedSrcSatQ(context, shl, size, signedDst));
|
||||
}
|
||||
}
|
||||
|
||||
context.MarkLabel(lblEnd);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void EmitShlRegOp(ArmEmitterContext context, ShlRegFlags flags = ShlRegFlags.None)
|
||||
{
|
||||
bool scalar = flags.HasFlag(ShlRegFlags.Scalar);
|
||||
|
@ -384,7 +384,9 @@ namespace ARMeilleure.Instructions
|
||||
Sqrshrn_V,
|
||||
Sqrshrun_S,
|
||||
Sqrshrun_V,
|
||||
Sqshl_Si,
|
||||
Sqshl_V,
|
||||
Sqshl_Vi,
|
||||
Sqshrn_S,
|
||||
Sqshrn_V,
|
||||
Sqshrun_S,
|
||||
@ -635,6 +637,7 @@ namespace ARMeilleure.Instructions
|
||||
Vorn,
|
||||
Vorr,
|
||||
Vpadd,
|
||||
Vpadal,
|
||||
Vpaddl,
|
||||
Vpmax,
|
||||
Vpmin,
|
||||
@ -654,6 +657,7 @@ namespace ARMeilleure.Instructions
|
||||
Vrintm,
|
||||
Vrintn,
|
||||
Vrintp,
|
||||
Vrintr,
|
||||
Vrintx,
|
||||
Vrshr,
|
||||
Vrshrn,
|
||||
|
@ -8,6 +8,7 @@ namespace ARMeilleure.Memory
|
||||
|
||||
void Commit(ulong offset, ulong size);
|
||||
|
||||
void MapAsRw(ulong offset, ulong size);
|
||||
void MapAsRx(ulong offset, ulong size);
|
||||
void MapAsRwx(ulong offset, ulong size);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace ARMeilleure.Memory
|
||||
{
|
||||
class ReservedRegion
|
||||
public class ReservedRegion
|
||||
{
|
||||
public const int DefaultGranularity = 65536; // Mapping granularity in Windows.
|
||||
|
||||
|
@ -5,7 +5,7 @@ using System.Runtime.Versioning;
|
||||
namespace ARMeilleure.Native
|
||||
{
|
||||
[SupportedOSPlatform("macos")]
|
||||
internal static partial class JitSupportDarwin
|
||||
static partial class JitSupportDarwin
|
||||
{
|
||||
[LibraryImport("libarmeilleure-jitsupport", EntryPoint = "armeilleure_jit_memcpy")]
|
||||
public static partial void Copy(IntPtr dst, IntPtr src, ulong n);
|
||||
|
@ -8,7 +8,7 @@ namespace ARMeilleure.Translation
|
||||
/// </summary>
|
||||
/// <typeparam name="TK">Key</typeparam>
|
||||
/// <typeparam name="TV">Value</typeparam>
|
||||
class IntervalTree<TK, TV> where TK : IComparable<TK>
|
||||
public class IntervalTree<TK, TV> where TK : IComparable<TK>
|
||||
{
|
||||
private const int ArrayGrowthSize = 32;
|
||||
|
||||
|
@ -57,9 +57,6 @@ namespace ARMeilleure.Translation
|
||||
private Thread[] _backgroundTranslationThreads;
|
||||
private volatile int _threadCount;
|
||||
|
||||
// FIXME: Remove this once the init logic of the emulator will be redone.
|
||||
public static readonly ManualResetEvent IsReadyForTranslation = new(false);
|
||||
|
||||
public Translator(IJitMemoryAllocator allocator, IMemoryManager memory, bool for64Bits)
|
||||
{
|
||||
_allocator = allocator;
|
||||
@ -76,7 +73,7 @@ namespace ARMeilleure.Translation
|
||||
CountTable = new EntryTable<uint>();
|
||||
Functions = new TranslatorCache<TranslatedFunction>();
|
||||
FunctionTable = new AddressTable<ulong>(for64Bits ? _levels64Bit : _levels32Bit);
|
||||
Stubs = new TranslatorStubs(this);
|
||||
Stubs = new TranslatorStubs(FunctionTable);
|
||||
|
||||
FunctionTable.Fill = (ulong)Stubs.SlowDispatchStub;
|
||||
}
|
||||
@ -100,8 +97,6 @@ namespace ARMeilleure.Translation
|
||||
{
|
||||
if (Interlocked.Increment(ref _threadCount) == 1)
|
||||
{
|
||||
IsReadyForTranslation.WaitOne();
|
||||
|
||||
if (_ptc.State == PtcState.Enabled)
|
||||
{
|
||||
Debug.Assert(Functions.Count == 0);
|
||||
|
@ -1,3 +1,4 @@
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Instructions;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.State;
|
||||
@ -14,11 +15,11 @@ namespace ARMeilleure.Translation
|
||||
/// </summary>
|
||||
class TranslatorStubs : IDisposable
|
||||
{
|
||||
private static readonly Lazy<IntPtr> _slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
|
||||
private readonly Lazy<IntPtr> _slowDispatchStub;
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private readonly Translator _translator;
|
||||
private readonly AddressTable<ulong> _functionTable;
|
||||
private readonly Lazy<IntPtr> _dispatchStub;
|
||||
private readonly Lazy<DispatcherFunction> _dispatchLoop;
|
||||
private readonly Lazy<WrapperFunction> _contextWrapper;
|
||||
@ -83,13 +84,14 @@ namespace ARMeilleure.Translation
|
||||
/// Initializes a new instance of the <see cref="TranslatorStubs"/> class with the specified
|
||||
/// <see cref="Translator"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="translator"><see cref="Translator"/> instance to use</param>
|
||||
/// <param name="functionTable">Function table used to store pointers to the functions that the guest code will call</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="translator"/> is null</exception>
|
||||
public TranslatorStubs(Translator translator)
|
||||
public TranslatorStubs(AddressTable<ulong> functionTable)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(translator);
|
||||
ArgumentNullException.ThrowIfNull(functionTable);
|
||||
|
||||
_translator = translator;
|
||||
_functionTable = functionTable;
|
||||
_slowDispatchStub = new(GenerateSlowDispatchStub, isThreadSafe: true);
|
||||
_dispatchStub = new(GenerateDispatchStub, isThreadSafe: true);
|
||||
_dispatchLoop = new(GenerateDispatchLoop, isThreadSafe: true);
|
||||
_contextWrapper = new(GenerateContextWrapper, isThreadSafe: true);
|
||||
@ -151,15 +153,15 @@ namespace ARMeilleure.Translation
|
||||
context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())));
|
||||
|
||||
// Check if guest address is within range of the AddressTable.
|
||||
Operand masked = context.BitwiseAnd(guestAddress, Const(~_translator.FunctionTable.Mask));
|
||||
Operand masked = context.BitwiseAnd(guestAddress, Const(~_functionTable.Mask));
|
||||
context.BranchIfTrue(lblFallback, masked);
|
||||
|
||||
Operand index = default;
|
||||
Operand page = Const((long)_translator.FunctionTable.Base);
|
||||
Operand page = Const((long)_functionTable.Base);
|
||||
|
||||
for (int i = 0; i < _translator.FunctionTable.Levels.Length; i++)
|
||||
for (int i = 0; i < _functionTable.Levels.Length; i++)
|
||||
{
|
||||
ref var level = ref _translator.FunctionTable.Levels[i];
|
||||
ref var level = ref _functionTable.Levels[i];
|
||||
|
||||
// level.Mask is not used directly because it is more often bigger than 32-bits, so it will not
|
||||
// be encoded as an immediate on x86's bitwise and operation.
|
||||
@ -167,7 +169,7 @@ namespace ARMeilleure.Translation
|
||||
|
||||
index = context.BitwiseAnd(context.ShiftRightUI(guestAddress, Const(level.Index)), mask);
|
||||
|
||||
if (i < _translator.FunctionTable.Levels.Length - 1)
|
||||
if (i < _functionTable.Levels.Length - 1)
|
||||
{
|
||||
page = context.Load(OperandType.I64, context.Add(page, context.ShiftLeft(index, Const(3))));
|
||||
context.BranchIfFalse(lblFallback, page);
|
||||
@ -196,7 +198,7 @@ namespace ARMeilleure.Translation
|
||||
/// Generates a <see cref="SlowDispatchStub"/>.
|
||||
/// </summary>
|
||||
/// <returns>Generated <see cref="SlowDispatchStub"/></returns>
|
||||
private static IntPtr GenerateSlowDispatchStub()
|
||||
private IntPtr GenerateSlowDispatchStub()
|
||||
{
|
||||
var context = new EmitterContext();
|
||||
|
||||
@ -205,8 +207,7 @@ namespace ARMeilleure.Translation
|
||||
Operand guestAddress = context.Load(OperandType.I64,
|
||||
context.Add(nativeContext, Const((ulong)NativeContext.GetDispatchAddressOffset())));
|
||||
|
||||
MethodInfo getFuncAddress = typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress));
|
||||
Operand hostAddress = context.Call(getFuncAddress, guestAddress);
|
||||
Operand hostAddress = context.Call(typeof(NativeInterface).GetMethod(nameof(NativeInterface.GetFunctionAddress)), guestAddress);
|
||||
context.Tailcall(hostAddress, nativeContext);
|
||||
|
||||
var cfg = context.GetControlFlowGraph();
|
||||
|
@ -11,15 +11,15 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dll</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dylib</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.so</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
|
@ -31,7 +31,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
private const int Minus6dBInQ15 = (int)(0.501f * RawQ15One);
|
||||
private const int Minus12dBInQ15 = (int)(0.251f * RawQ15One);
|
||||
|
||||
private static readonly int[] _defaultSurroundToStereoCoefficients = new int[4]
|
||||
private static readonly long[] _defaultSurroundToStereoCoefficients = new long[4]
|
||||
{
|
||||
RawQ15One,
|
||||
Minus3dBInQ15,
|
||||
@ -39,7 +39,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
Minus3dBInQ15,
|
||||
};
|
||||
|
||||
private static readonly int[] _defaultStereoToMonoCoefficients = new int[2]
|
||||
private static readonly long[] _defaultStereoToMonoCoefficients = new long[2]
|
||||
{
|
||||
Minus6dBInQ15,
|
||||
Minus6dBInQ15,
|
||||
@ -62,19 +62,23 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static short DownMixStereoToMono(ReadOnlySpan<int> coefficients, short left, short right)
|
||||
private static short DownMixStereoToMono(ReadOnlySpan<long> coefficients, short left, short right)
|
||||
{
|
||||
return (short)((left * coefficients[0] + right * coefficients[1]) >> Q15Bits);
|
||||
return (short)Math.Clamp((left * coefficients[0] + right * coefficients[1]) >> Q15Bits, short.MinValue, short.MaxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static short DownMixSurroundToStereo(ReadOnlySpan<int> coefficients, short back, short lfe, short center, short front)
|
||||
private static short DownMixSurroundToStereo(ReadOnlySpan<long> coefficients, short back, short lfe, short center, short front)
|
||||
{
|
||||
return (short)((coefficients[3] * back + coefficients[2] * lfe + coefficients[1] * center + coefficients[0] * front + RawQ15HalfOne) >> Q15Bits);
|
||||
return (short)Math.Clamp(
|
||||
(coefficients[3] * back +
|
||||
coefficients[2] * lfe +
|
||||
coefficients[1] * center +
|
||||
coefficients[0] * front + RawQ15HalfOne) >> Q15Bits, short.MinValue, short.MaxValue);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static short[] DownMixSurroundToStereo(ReadOnlySpan<int> coefficients, ReadOnlySpan<short> data)
|
||||
private static short[] DownMixSurroundToStereo(ReadOnlySpan<long> coefficients, ReadOnlySpan<short> data)
|
||||
{
|
||||
int samplePerChannelCount = data.Length / SurroundChannelCount;
|
||||
|
||||
@ -94,7 +98,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static short[] DownMixStereoToMono(ReadOnlySpan<int> coefficients, ReadOnlySpan<short> data)
|
||||
private static short[] DownMixStereoToMono(ReadOnlySpan<long> coefficients, ReadOnlySpan<short> data)
|
||||
{
|
||||
int samplePerChannelCount = data.Length / StereoChannelCount;
|
||||
|
||||
|
@ -12,7 +12,6 @@ using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
{
|
||||
@ -90,8 +89,6 @@ namespace Ryujinx.Ava
|
||||
try
|
||||
{
|
||||
string baseStyle = ConfigurationState.Instance.Ui.BaseStyle;
|
||||
string themePath = ConfigurationState.Instance.Ui.CustomThemePath;
|
||||
bool enableCustomTheme = ConfigurationState.Instance.Ui.EnableCustomTheme;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(baseStyle))
|
||||
{
|
||||
@ -106,24 +103,6 @@ namespace Ryujinx.Ava
|
||||
"Dark" => ThemeVariant.Dark,
|
||||
_ => ThemeVariant.Default,
|
||||
};
|
||||
|
||||
if (enableCustomTheme)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(themePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var themeContent = File.ReadAllText(themePath);
|
||||
var customStyle = AvaloniaRuntimeXamlLoader.Parse<IStyle>(themeContent);
|
||||
|
||||
Styles.Add(customStyle);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Failed to Apply Custom Theme. Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -1,4 +1,3 @@
|
||||
using ARMeilleure.Translation;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
@ -916,7 +915,6 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Device.Gpu.SetGpuThread();
|
||||
Device.Gpu.InitializeShaderCache(_gpuCancellationTokenSource.Token);
|
||||
Translator.IsReadyForTranslation.Set();
|
||||
|
||||
_renderer.Window.ChangeVSyncMode(Device.EnableDeviceVsync);
|
||||
|
||||
@ -980,7 +978,7 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
||||
LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
||||
$"FIFO: {Device.Statistics.GetFifoPercent():00.00} %",
|
||||
$"GPU: {_renderer.GetHardwareInfo().GpuVendor}"));
|
||||
$"GPU: {_renderer.GetHardwareInfo().GpuDriver}"));
|
||||
}
|
||||
|
||||
public async Task ShowExitPrompt()
|
||||
|
@ -54,8 +54,6 @@
|
||||
"GameListContextMenuManageTitleUpdatesToolTip": "Opens the Title Update management window",
|
||||
"GameListContextMenuManageDlc": "Manage DLC",
|
||||
"GameListContextMenuManageDlcToolTip": "Opens the DLC management window",
|
||||
"GameListContextMenuOpenModsDirectory": "Open Mods Directory",
|
||||
"GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods",
|
||||
"GameListContextMenuCacheManagement": "Cache Management",
|
||||
"GameListContextMenuCacheManagementPurgePptc": "Queue PPTC Rebuild",
|
||||
"GameListContextMenuCacheManagementPurgePptcToolTip": "Trigger PPTC to rebuild at boot time on the next game launch",
|
||||
@ -74,6 +72,7 @@
|
||||
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
|
||||
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
|
||||
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
|
||||
"GameListContextMenuCreateShortcutToolTipMacOS": "Create a shortcut in macOS's Applications folder that launches the selected Application",
|
||||
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
|
||||
"StatusBarSystemVersion": "System Version: {0}",
|
||||
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
|
||||
@ -294,13 +293,9 @@
|
||||
"GameListContextMenuRunApplication": "Run Application",
|
||||
"GameListContextMenuToggleFavorite": "Toggle Favorite",
|
||||
"GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game",
|
||||
"SettingsTabGeneralTheme": "Theme",
|
||||
"SettingsTabGeneralThemeCustomTheme": "Custom Theme Path",
|
||||
"SettingsTabGeneralThemeBaseStyle": "Base Style",
|
||||
"SettingsTabGeneralThemeBaseStyleDark": "Dark",
|
||||
"SettingsTabGeneralThemeBaseStyleLight": "Light",
|
||||
"SettingsTabGeneralThemeEnableCustomTheme": "Enable Custom Theme",
|
||||
"ButtonBrowse": "Browse",
|
||||
"SettingsTabGeneralTheme": "Theme:",
|
||||
"SettingsTabGeneralThemeDark": "Dark",
|
||||
"SettingsTabGeneralThemeLight": "Light",
|
||||
"ControllerSettingsConfigureGeneral": "Configure",
|
||||
"ControllerSettingsRumble": "Rumble",
|
||||
"ControllerSettingsRumbleStrongMultiplier": "Strong Rumble Multiplier",
|
||||
@ -387,7 +382,10 @@
|
||||
"DialogUserProfileUnsavedChangesSubMessage": "Do you want to discard your changes?",
|
||||
"DialogControllerSettingsModifiedConfirmMessage": "The current controller settings has been updated.",
|
||||
"DialogControllerSettingsModifiedConfirmSubMessage": "Do you want to save?",
|
||||
"DialogLoadNcaErrorMessage": "{0}. Errored File: {1}",
|
||||
"DialogLoadFileErrorMessage": "{0}. Errored File: {1}",
|
||||
"DialogModAlreadyExistsMessage": "Mod already exists",
|
||||
"DialogModInvalidMessage": "The specified directory does not contain a mod!",
|
||||
"DialogModDeleteNoParentMessage": "Failed to Delete: Could not find the parent directory for mod \"{0}\"!",
|
||||
"DialogDlcNoDlcErrorMessage": "The specified file does not contain a DLC for the selected title!",
|
||||
"DialogPerformanceCheckLoggingEnabledMessage": "You have trace logging enabled, which is designed to be used by developers only.",
|
||||
"DialogPerformanceCheckLoggingEnabledConfirmMessage": "For optimal performance, it's recommended to disable trace logging. Would you like to disable trace logging now?",
|
||||
@ -398,6 +396,8 @@
|
||||
"DialogUpdateAddUpdateErrorMessage": "The specified file does not contain an update for the selected title!",
|
||||
"DialogSettingsBackendThreadingWarningTitle": "Warning - Backend Threading",
|
||||
"DialogSettingsBackendThreadingWarningMessage": "Ryujinx must be restarted after changing this option for it to apply fully. Depending on your platform, you may need to manually disable your driver's own multithreading when using Ryujinx's.",
|
||||
"DialogModManagerDeletionWarningMessage": "You are about to delete the mod: {0}\n\nAre you sure you want to proceed?",
|
||||
"DialogModManagerDeletionAllWarningMessage": "You are about to delete all mods for this title.\n\nAre you sure you want to proceed?",
|
||||
"SettingsTabGraphicsFeaturesOptions": "Features",
|
||||
"SettingsTabGraphicsBackendMultithreading": "Graphics Backend Multithreading:",
|
||||
"CommonAuto": "Auto",
|
||||
@ -432,6 +432,7 @@
|
||||
"DlcManagerRemoveAllButton": "Remove All",
|
||||
"DlcManagerEnableAllButton": "Enable All",
|
||||
"DlcManagerDisableAllButton": "Disable All",
|
||||
"ModManagerDeleteAllButton": "Delete All",
|
||||
"MenuBarOptionsChangeLanguage": "Change Language",
|
||||
"MenuBarShowFileTypes": "Show File Types",
|
||||
"CommonSort": "Sort",
|
||||
@ -506,6 +507,8 @@
|
||||
"EnableInternetAccessTooltip": "Allows the emulated application to connect to the Internet.\n\nGames with a LAN mode can connect to each other when this is enabled and the systems are connected to the same access point. This includes real consoles as well.\n\nDoes NOT allow connecting to Nintendo servers. May cause crashing in certain games that try to connect to the Internet.\n\nLeave OFF if unsure.",
|
||||
"GameListContextMenuManageCheatToolTip": "Manage Cheats",
|
||||
"GameListContextMenuManageCheat": "Manage Cheats",
|
||||
"GameListContextMenuManageModToolTip": "Manage Mods",
|
||||
"GameListContextMenuManageMod": "Manage Mods",
|
||||
"ControllerSettingsStickRange": "Range:",
|
||||
"DialogStopEmulationTitle": "Ryujinx - Stop Emulation",
|
||||
"DialogStopEmulationMessage": "Are you sure you want to stop emulation?",
|
||||
@ -517,8 +520,6 @@
|
||||
"SettingsTabCpuMemory": "CPU Mode",
|
||||
"DialogUpdaterFlatpakNotSupportedMessage": "Please update Ryujinx via FlatHub.",
|
||||
"UpdaterDisabledWarningTitle": "Updater Disabled!",
|
||||
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
|
||||
"GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.",
|
||||
"ControllerSettingsRotate90": "Rotate 90° Clockwise",
|
||||
"IconSize": "Icon Size",
|
||||
"IconSizeTooltip": "Change the size of game icons",
|
||||
@ -590,6 +591,7 @@
|
||||
"Writable": "Writable",
|
||||
"SelectDlcDialogTitle": "Select DLC files",
|
||||
"SelectUpdateDialogTitle": "Select update files",
|
||||
"SelectModDialogTitle": "Select mod directory",
|
||||
"UserProfileWindowTitle": "User Profiles Manager",
|
||||
"CheatWindowTitle": "Cheats Manager",
|
||||
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
|
||||
@ -597,6 +599,7 @@
|
||||
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
||||
"BuildId": "BuildId:",
|
||||
"DlcWindowHeading": "{0} Downloadable Content(s)",
|
||||
"ModWindowHeading": "{0} Mod(s)",
|
||||
"UserProfilesEditProfile": "Edit Selected",
|
||||
"Cancel": "Cancel",
|
||||
"Save": "Save",
|
||||
|
@ -665,7 +665,7 @@ namespace Ryujinx.Modules
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid())
|
||||
if (Program.Version.Contains("dirty") || !ReleaseInformation.IsValid)
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
@ -683,7 +683,7 @@ namespace Ryujinx.Modules
|
||||
#else
|
||||
if (showWarnings)
|
||||
{
|
||||
if (ReleaseInformation.IsFlatHubBuild())
|
||||
if (ReleaseInformation.IsFlatHubBuild)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
ContentDialogHelper.CreateWarningDialog(
|
||||
|
@ -35,7 +35,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Version = ReleaseInformation.GetVersion();
|
||||
Version = ReleaseInformation.Version;
|
||||
|
||||
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
|
||||
{
|
||||
@ -125,8 +125,8 @@ namespace Ryujinx.Ava
|
||||
|
||||
public static void ReloadConfig()
|
||||
{
|
||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.json");
|
||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, "Config.json");
|
||||
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
|
||||
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
|
||||
|
||||
// Now load the configuration as the other subsystems are now registered
|
||||
if (File.Exists(localConfigurationPath))
|
||||
|
@ -43,14 +43,13 @@
|
||||
<PackageReference Include="Avalonia.Markup.Xaml.Loader" />
|
||||
<PackageReference Include="Avalonia.Svg" />
|
||||
<PackageReference Include="Avalonia.Svg.Skia" />
|
||||
<PackageReference Include="jp2masa.Avalonia.Flexbox" />
|
||||
<PackageReference Include="DynamicData" />
|
||||
<PackageReference Include="FluentAvaloniaUI" />
|
||||
|
||||
<PackageReference Include="OpenTK.Core" />
|
||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
@ -79,7 +78,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<Content Include="..\..\distribution\windows\alsoft.ini" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'linux-arm64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
<TargetPath>alsoft.ini</TargetPath>
|
||||
</Content>
|
||||
@ -93,7 +92,7 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == 'linux-arm64'">
|
||||
<Content Include="..\..\distribution\linux\Ryujinx.sh">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
|
@ -9,6 +9,7 @@ using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Applets;
|
||||
using Ryujinx.HLE.HOS.Services.Hid;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -103,7 +104,7 @@ namespace Ryujinx.Ava.UI.Applet
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
SvgSource source = new();
|
||||
SvgSource source = new(default(Uri));
|
||||
|
||||
source.Load(EmbeddedResources.GetStream(path));
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
Click="CreateApplicationShortcut_Click"
|
||||
Header="{locale:Locale GameListContextMenuCreateShortcut}"
|
||||
IsEnabled="{Binding CreateShortcutEnabled}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
|
||||
ToolTip.Tip="{OnPlatform Default={locale:Locale GameListContextMenuCreateShortcutToolTip}, macOS={locale:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Click="OpenUserSaveDirectory_Click"
|
||||
@ -47,13 +47,9 @@
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
|
||||
<MenuItem
|
||||
Click="OpenModsDirectory_Click"
|
||||
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
Click="OpenSdModsDirectory_Click"
|
||||
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
|
||||
Click="OpenModManager_Click"
|
||||
Header="{locale:Locale GameListContextMenuManageMod}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageModToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
|
||||
<MenuItem
|
||||
|
@ -126,29 +126,13 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenModsDirectory_Click(object sender, RoutedEventArgs args)
|
||||
public async void OpenModManager_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
|
||||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
string modsBasePath = ModLoader.GetModsBasePath();
|
||||
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, viewModel.SelectedApplication.TitleId);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenSdModsDirectory_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
|
||||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
string sdModsBasePath = ModLoader.GetSdModsBasePath();
|
||||
string titleModsPath = ModLoader.GetTitleDir(sdModsBasePath, viewModel.SelectedApplication.TitleId);
|
||||
|
||||
OpenHelper.OpenFolder(titleModsPath);
|
||||
await ModManagerWindow.Show(ulong.Parse(viewModel.SelectedApplication.TitleId, NumberStyles.HexNumber), viewModel.SelectedApplication.TitleName);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.UI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
@ -33,11 +32,10 @@
|
||||
SelectionChanged="GameList_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel
|
||||
<WrapPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Stretch"
|
||||
AlignContent="FlexStart"
|
||||
JustifyContent="FlexStart" />
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
|
@ -336,6 +336,11 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
void OverlayOnPositionChanged(object sender, PixelPointEventArgs e)
|
||||
{
|
||||
if (_contentDialogOverlayWindow is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_contentDialogOverlayWindow.Position = parent.PointToScreen(new Point());
|
||||
}
|
||||
|
||||
@ -388,6 +393,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
_contentDialogOverlayWindow.Content = null;
|
||||
_contentDialogOverlayWindow.Close();
|
||||
_contentDialogOverlayWindow = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
32
src/Ryujinx.Ava/UI/Models/ModModel.cs
Normal file
32
src/Ryujinx.Ava/UI/Models/ModModel.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models
|
||||
{
|
||||
public class ModModel : BaseModel
|
||||
{
|
||||
private bool _enabled;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
_enabled = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool InSd { get; }
|
||||
public string Path { get; }
|
||||
public string Name { get; }
|
||||
|
||||
public ModModel(string path, string name, bool enabled, bool inSd)
|
||||
{
|
||||
Path = path;
|
||||
Name = name;
|
||||
Enabled = enabled;
|
||||
InSd = inSd;
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,8 @@ namespace Ryujinx.Ava.UI.Renderer
|
||||
_context.MakeCurrent(_window);
|
||||
}
|
||||
|
||||
public bool HasContext() => _context.IsCurrent;
|
||||
|
||||
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
||||
{
|
||||
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
||||
|
@ -180,7 +180,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_controllerImage))
|
||||
{
|
||||
SvgSource source = new();
|
||||
SvgSource source = new(default(Uri));
|
||||
|
||||
source.Load(EmbeddedResources.GetStream(_controllerImage));
|
||||
|
||||
|
@ -39,6 +39,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
private string _search;
|
||||
private readonly ulong _titleId;
|
||||
private readonly IStorageProvider _storageProvider;
|
||||
|
||||
private static readonly DownloadableContentJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
@ -90,8 +91,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
||||
}
|
||||
|
||||
public IStorageProvider StorageProvider;
|
||||
|
||||
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId)
|
||||
{
|
||||
_virtualFileSystem = virtualFileSystem;
|
||||
@ -100,7 +99,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
StorageProvider = desktop.MainWindow.StorageProvider;
|
||||
_storageProvider = desktop.MainWindow.StorageProvider;
|
||||
}
|
||||
|
||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||
@ -194,7 +193,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadNcaErrorMessage], ex.Message, containerPath));
|
||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadFileErrorMessage], ex.Message, containerPath));
|
||||
});
|
||||
}
|
||||
|
||||
@ -203,7 +202,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
var result = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
||||
AllowMultiple = true,
|
||||
|
@ -357,7 +357,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||
|
||||
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild();
|
||||
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild;
|
||||
|
||||
public string LoadHeading
|
||||
{
|
||||
@ -1350,7 +1350,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public void OpenLogsFolder()
|
||||
{
|
||||
string logPath = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "Logs");
|
||||
string logPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
|
||||
|
||||
if (LoggerModule.LogDirectoryPath != null)
|
||||
{
|
||||
logPath = LoggerModule.LogDirectoryPath;
|
||||
}
|
||||
|
||||
new DirectoryInfo(logPath).Create();
|
||||
|
||||
|
336
src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs
Normal file
336
src/Ryujinx.Ava/UI/ViewModels/ModManagerViewModel.cs
Normal file
@ -0,0 +1,336 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Platform.Storage;
|
||||
using Avalonia.Threading;
|
||||
using DynamicData;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public class ModManagerViewModel : BaseModel
|
||||
{
|
||||
private readonly string _modJsonPath;
|
||||
|
||||
private AvaloniaList<ModModel> _mods = new();
|
||||
private AvaloniaList<ModModel> _views = new();
|
||||
private AvaloniaList<ModModel> _selectedMods = new();
|
||||
|
||||
private string _search;
|
||||
private readonly ulong _applicationId;
|
||||
private readonly IStorageProvider _storageProvider;
|
||||
|
||||
private static readonly ModMetadataJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
|
||||
|
||||
public AvaloniaList<ModModel> Mods
|
||||
{
|
||||
get => _mods;
|
||||
set
|
||||
{
|
||||
_mods = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ModCount));
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<ModModel> Views
|
||||
{
|
||||
get => _views;
|
||||
set
|
||||
{
|
||||
_views = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public AvaloniaList<ModModel> SelectedMods
|
||||
{
|
||||
get => _selectedMods;
|
||||
set
|
||||
{
|
||||
_selectedMods = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public string Search
|
||||
{
|
||||
get => _search;
|
||||
set
|
||||
{
|
||||
_search = value;
|
||||
OnPropertyChanged();
|
||||
Sort();
|
||||
}
|
||||
}
|
||||
|
||||
public string ModCount
|
||||
{
|
||||
get => string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], Mods.Count);
|
||||
}
|
||||
|
||||
public ModManagerViewModel(ulong applicationId)
|
||||
{
|
||||
_applicationId = applicationId;
|
||||
|
||||
_modJsonPath = Path.Combine(AppDataManager.GamesDirPath, applicationId.ToString("x16"), "mods.json");
|
||||
|
||||
if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
_storageProvider = desktop.MainWindow.StorageProvider;
|
||||
}
|
||||
|
||||
LoadMods(applicationId);
|
||||
}
|
||||
|
||||
private void LoadMods(ulong applicationId)
|
||||
{
|
||||
Mods.Clear();
|
||||
SelectedMods.Clear();
|
||||
|
||||
string[] modsBasePaths = [ModLoader.GetSdModsBasePath(), ModLoader.GetModsBasePath()];
|
||||
|
||||
foreach (var path in modsBasePaths)
|
||||
{
|
||||
var inSd = path == ModLoader.GetSdModsBasePath();
|
||||
var modCache = new ModLoader.ModCache();
|
||||
|
||||
ModLoader.QueryContentsDir(modCache, new DirectoryInfo(Path.Combine(path, "contents")), applicationId);
|
||||
|
||||
foreach (var mod in modCache.RomfsDirs)
|
||||
{
|
||||
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd);
|
||||
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
|
||||
{
|
||||
Mods.Add(modModel);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mod in modCache.RomfsContainers)
|
||||
{
|
||||
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd));
|
||||
}
|
||||
|
||||
foreach (var mod in modCache.ExefsDirs)
|
||||
{
|
||||
var modModel = new ModModel(mod.Path.Parent.FullName, mod.Name, mod.Enabled, inSd);
|
||||
if (Mods.All(x => x.Path != mod.Path.Parent.FullName))
|
||||
{
|
||||
Mods.Add(modModel);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mod in modCache.ExefsContainers)
|
||||
{
|
||||
Mods.Add(new ModModel(mod.Path.FullName, mod.Name, mod.Enabled, inSd));
|
||||
}
|
||||
}
|
||||
|
||||
Sort();
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
{
|
||||
Mods.AsObservableChangeSet()
|
||||
.Filter(Filter)
|
||||
.Bind(out var view).AsObservableList();
|
||||
|
||||
_views.Clear();
|
||||
_views.AddRange(view);
|
||||
|
||||
SelectedMods = new(Views.Where(x => x.Enabled));
|
||||
|
||||
OnPropertyChanged(nameof(ModCount));
|
||||
OnPropertyChanged(nameof(Views));
|
||||
OnPropertyChanged(nameof(SelectedMods));
|
||||
}
|
||||
|
||||
private bool Filter(object arg)
|
||||
{
|
||||
if (arg is ModModel content)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(_search) || content.Name.ToLower().Contains(_search.ToLower());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
ModMetadata modData = new();
|
||||
|
||||
foreach (ModModel mod in Mods)
|
||||
{
|
||||
modData.Mods.Add(new Mod
|
||||
{
|
||||
Name = mod.Name,
|
||||
Path = mod.Path,
|
||||
Enabled = SelectedMods.Contains(mod),
|
||||
});
|
||||
}
|
||||
|
||||
JsonHelper.SerializeToFile(_modJsonPath, modData, _serializerContext.ModMetadata);
|
||||
}
|
||||
|
||||
public void Delete(ModModel model)
|
||||
{
|
||||
var isSubdir = true;
|
||||
var pathToDelete = model.Path;
|
||||
var basePath = model.InSd ? ModLoader.GetSdModsBasePath() : ModLoader.GetModsBasePath();
|
||||
var modsDir = ModLoader.GetApplicationDir(basePath, _applicationId.ToString("x16"));
|
||||
|
||||
if (new DirectoryInfo(model.Path).Parent?.FullName == modsDir)
|
||||
{
|
||||
isSubdir = false;
|
||||
}
|
||||
|
||||
if (isSubdir)
|
||||
{
|
||||
var parentDir = String.Empty;
|
||||
|
||||
foreach (var dir in Directory.GetDirectories(modsDir, "*", SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
if (Directory.GetDirectories(dir, "*", SearchOption.AllDirectories).Contains(model.Path))
|
||||
{
|
||||
parentDir = dir;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (parentDir == String.Empty)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||
LocaleKeys.DialogModDeleteNoParentMessage,
|
||||
model.Path));
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Application, $"Deleting mod at \"{pathToDelete}\"");
|
||||
Directory.Delete(pathToDelete, true);
|
||||
|
||||
Mods.Remove(model);
|
||||
OnPropertyChanged(nameof(ModCount));
|
||||
Sort();
|
||||
}
|
||||
|
||||
private void AddMod(DirectoryInfo directory)
|
||||
{
|
||||
string[] directories;
|
||||
|
||||
try
|
||||
{
|
||||
directories = Directory.GetDirectories(directory.ToString(), "*", SearchOption.AllDirectories);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||
LocaleKeys.DialogLoadFileErrorMessage,
|
||||
exception.ToString(),
|
||||
directory));
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var destinationDir = ModLoader.GetApplicationDir(ModLoader.GetSdModsBasePath(), _applicationId.ToString("x16"));
|
||||
|
||||
// TODO: More robust checking for valid mod folders
|
||||
var isDirectoryValid = true;
|
||||
|
||||
if (directories.Length == 0)
|
||||
{
|
||||
isDirectoryValid = false;
|
||||
}
|
||||
|
||||
if (!isDirectoryValid)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogModInvalidMessage]);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var dir in directories)
|
||||
{
|
||||
string dirToCreate = dir.Replace(directory.Parent.ToString(), destinationDir);
|
||||
|
||||
// Mod already exists
|
||||
if (Directory.Exists(dirToCreate))
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(
|
||||
LocaleKeys.DialogLoadFileErrorMessage,
|
||||
LocaleManager.Instance[LocaleKeys.DialogModAlreadyExistsMessage],
|
||||
dirToCreate));
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(dirToCreate);
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(directory.ToString(), "*", SearchOption.AllDirectories);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
File.Copy(file, file.Replace(directory.Parent.ToString(), destinationDir), true);
|
||||
}
|
||||
|
||||
LoadMods(_applicationId);
|
||||
}
|
||||
|
||||
public async void Add()
|
||||
{
|
||||
var result = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.SelectModDialogTitle],
|
||||
AllowMultiple = true,
|
||||
});
|
||||
|
||||
foreach (var folder in result)
|
||||
{
|
||||
AddMod(new DirectoryInfo(folder.Path.LocalPath));
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteAll()
|
||||
{
|
||||
foreach (var mod in Mods)
|
||||
{
|
||||
Delete(mod);
|
||||
}
|
||||
|
||||
Mods.Clear();
|
||||
OnPropertyChanged(nameof(ModCount));
|
||||
Sort();
|
||||
}
|
||||
|
||||
public void EnableAll()
|
||||
{
|
||||
SelectedMods = new(Mods);
|
||||
}
|
||||
|
||||
public void DisableAll()
|
||||
{
|
||||
SelectedMods.Clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -48,7 +48,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private readonly List<string> _gpuIds = new();
|
||||
private KeyboardHotkeys _keyboardHotkeys;
|
||||
private int _graphicsBackendIndex;
|
||||
private string _customThemePath;
|
||||
private int _scalingFilter;
|
||||
private int _scalingFilterLevel;
|
||||
|
||||
@ -160,7 +159,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool IsOpenAlEnabled { get; set; }
|
||||
public bool IsSoundIoEnabled { get; set; }
|
||||
public bool IsSDL2Enabled { get; set; }
|
||||
public bool EnableCustomTheme { get; set; }
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
||||
|
||||
@ -170,20 +168,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public string TimeZone { get; set; }
|
||||
public string ShaderDumpPath { get; set; }
|
||||
|
||||
public string CustomThemePath
|
||||
{
|
||||
get
|
||||
{
|
||||
return _customThemePath;
|
||||
}
|
||||
set
|
||||
{
|
||||
_customThemePath = value;
|
||||
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int Language { get; set; }
|
||||
public int Region { get; set; }
|
||||
public int FsGlobalAccessLogMode { get; set; }
|
||||
@ -426,8 +410,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
GameDirectories.Clear();
|
||||
GameDirectories.AddRange(config.Ui.GameDirs.Value);
|
||||
|
||||
EnableCustomTheme = config.Ui.EnableCustomTheme;
|
||||
CustomThemePath = config.Ui.CustomThemePath;
|
||||
BaseStyleIndex = config.Ui.BaseStyle == "Light" ? 0 : 1;
|
||||
|
||||
// Input
|
||||
@ -515,8 +497,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.Ui.GameDirs.Value = gameDirs;
|
||||
}
|
||||
|
||||
config.Ui.EnableCustomTheme.Value = EnableCustomTheme;
|
||||
config.Ui.CustomThemePath.Value = CustomThemePath;
|
||||
config.Ui.BaseStyle.Value = BaseStyleIndex == 0 ? "Light" : "Dark";
|
||||
|
||||
// Input
|
||||
|
@ -192,7 +192,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)));
|
||||
Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadFileErrorMessage, ex.Message, path)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@
|
||||
<CheckBox IsChecked="{Binding ShowConfirmExit}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralShowConfirmExitDialog}" />
|
||||
</CheckBox>
|
||||
<StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal">
|
||||
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabGeneralHideCursor}"
|
||||
Width="150" />
|
||||
@ -54,6 +54,22 @@
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0, 15, 0, 10" Orientation="Horizontal">
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabGeneralTheme}"
|
||||
Width="150" />
|
||||
<ComboBox SelectedIndex="{Binding BaseStyleIndex}"
|
||||
HorizontalContentAlignment="Left"
|
||||
MinWidth="100">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeLight}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeDark}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralGameDirectories}" />
|
||||
@ -106,64 +122,6 @@
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralTheme}" />
|
||||
<Grid Margin="10,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox
|
||||
IsChecked="{Binding EnableCustomTheme}"
|
||||
ToolTip.Tip="{locale:Locale CustomThemeCheckTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeEnableCustomTheme}" />
|
||||
</CheckBox>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Grid.Row="1"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,10,0,0"
|
||||
Text="{locale:Locale SettingsTabGeneralThemeCustomTheme}"
|
||||
ToolTip.Tip="{locale:Locale CustomThemePathTooltip}" />
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,10,0,0"
|
||||
Text="{Binding CustomThemePath}" />
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="10,10,0,0"
|
||||
Click="BrowseTheme"
|
||||
ToolTip.Tip="{locale:Locale CustomThemeBrowseTooltip}"
|
||||
Content="{locale:Locale ButtonBrowse}" />
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,10,0,0"
|
||||
Text="{locale:Locale SettingsTabGeneralThemeBaseStyle}" />
|
||||
<ComboBox
|
||||
Grid.Column="1"
|
||||
Grid.Row="2"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0,10,0,0"
|
||||
MinWidth="100"
|
||||
SelectedIndex="{Binding BaseStyleIndex}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleLight}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralThemeBaseStyleDark}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
|
@ -61,29 +61,5 @@ namespace Ryujinx.Ava.UI.Views.Settings
|
||||
GameList.SelectedIndex = oldIndex < GameList.ItemCount ? oldIndex : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public async void BrowseTheme(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var window = this.GetVisualRoot() as Window;
|
||||
var result = await window.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.SettingsSelectThemeFileDialogTitle],
|
||||
AllowMultiple = false,
|
||||
FileTypeFilter = new List<FilePickerFileType>
|
||||
{
|
||||
new("xml")
|
||||
{
|
||||
Patterns = new[] { "*.xaml" },
|
||||
AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xaml" },
|
||||
MimeTypes = new[] { "application/xaml+xml" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (result.Count > 0)
|
||||
{
|
||||
ViewModel.CustomThemePath = result[0].Path.LocalPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
@ -40,11 +39,10 @@
|
||||
ItemsSource="{Binding Profiles}">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
AlignContent="FlexStart"
|
||||
JustifyContent="FlexStart" />
|
||||
<WrapPanel
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
|
@ -3,7 +3,6 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
@ -49,13 +48,11 @@
|
||||
Grid.Column="0"
|
||||
Height="80"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<flex:FlexPanel
|
||||
<WrapPanel
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Direction="Column"
|
||||
JustifyContent="SpaceAround"
|
||||
RowSpacing="2">
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
@ -71,7 +68,7 @@
|
||||
Text="(REE-YOU-JINX)"
|
||||
TextAlignment="Center"
|
||||
Width="110" />
|
||||
</flex:FlexPanel>
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
|
@ -41,7 +41,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
InitializeComponent();
|
||||
|
||||
string modsBasePath = ModLoader.GetModsBasePath();
|
||||
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId);
|
||||
string titleModsPath = ModLoader.GetApplicationDir(modsBasePath, titleId);
|
||||
ulong titleIdValue = ulong.Parse(titleId, NumberStyles.HexNumber);
|
||||
|
||||
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
||||
|
@ -263,7 +263,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckLaunchState()
|
||||
private async Task CheckLaunchState()
|
||||
{
|
||||
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
|
||||
{
|
||||
@ -271,23 +271,11 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
if (LinuxHelper.PkExecPath is not null)
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
await ShowVmMaxMapCountDialog();
|
||||
}
|
||||
});
|
||||
await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.Post(async () =>
|
||||
{
|
||||
if (OperatingSystem.IsLinux())
|
||||
{
|
||||
await ShowVmMaxMapCountWarning();
|
||||
}
|
||||
});
|
||||
await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning);
|
||||
}
|
||||
}
|
||||
|
||||
@ -304,12 +292,12 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
ShowKeyErrorOnLoad = false;
|
||||
|
||||
Dispatcher.UIThread.Post(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
|
||||
await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
|
||||
}
|
||||
|
||||
if (ConfigurationState.Instance.CheckUpdatesOnStart.Value && Updater.CanUpdate(false))
|
||||
{
|
||||
Updater.BeginParse(this, false).ContinueWith(task =>
|
||||
await Updater.BeginParse(this, false).ContinueWith(task =>
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}");
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
@ -404,7 +392,9 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
LoadApplications();
|
||||
|
||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
CheckLaunchState();
|
||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
||||
}
|
||||
|
||||
private void SetMainContent(Control content = null)
|
||||
|
179
src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml
Normal file
179
src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml
Normal file
@ -0,0 +1,179 @@
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
Width="500"
|
||||
Height="380"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.UI.Windows.ModManagerWindow"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:ModManagerViewModel"
|
||||
Focusable="True">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Panel
|
||||
Margin="0 0 0 10"
|
||||
Grid.Row="0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Text="{Binding ModCount}" />
|
||||
<StackPanel
|
||||
Margin="10 0"
|
||||
Grid.Column="1"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Name="EnableAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{ReflectionBinding EnableAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="DisableAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{ReflectionBinding DisableAll}">
|
||||
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<TextBox
|
||||
Grid.Column="2"
|
||||
MinHeight="27"
|
||||
MaxHeight="27"
|
||||
HorizontalAlignment="Stretch"
|
||||
Watermark="{locale:Locale Search}"
|
||||
Text="{Binding Search}" />
|
||||
</Grid>
|
||||
</Panel>
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0 0 0 24"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="5"
|
||||
Padding="2.5">
|
||||
<ListBox
|
||||
AutoScrollToSelectedItem="False"
|
||||
SelectionMode="Multiple, Toggle"
|
||||
Background="Transparent"
|
||||
SelectionChanged="OnSelectionChanged"
|
||||
SelectedItems="{Binding SelectedMods, Mode=TwoWay}"
|
||||
ItemsSource="{Binding Views}">
|
||||
<ListBox.DataTemplates>
|
||||
<DataTemplate
|
||||
DataType="models:ModModel">
|
||||
<Panel Margin="10">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
MaxLines="2"
|
||||
TextWrapping="Wrap"
|
||||
TextTrimming="CharacterEllipsis"
|
||||
Text="{Binding Name}" />
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Spacing="10"
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Right">
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="10"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Click="OpenLocation">
|
||||
<ui:SymbolIcon
|
||||
Symbol="OpenFolder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Right"
|
||||
Padding="10"
|
||||
MinWidth="0"
|
||||
MinHeight="0"
|
||||
Click="DeleteMod">
|
||||
<ui:SymbolIcon
|
||||
Symbol="Cancel"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Panel>
|
||||
</DataTemplate>
|
||||
</ListBox.DataTemplates>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
</ListBox>
|
||||
</Border>
|
||||
<Panel
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch">
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Left">
|
||||
<Button
|
||||
Name="AddButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Command="{Binding Add}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="RemoveAllButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Click="DeleteAll">
|
||||
<TextBlock Text="{locale:Locale ModManagerDeleteAllButton}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="10"
|
||||
HorizontalAlignment="Right">
|
||||
<Button
|
||||
Name="SaveButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Click="SaveAndClose">
|
||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||
</Button>
|
||||
<Button
|
||||
Name="CancelButton"
|
||||
MinWidth="90"
|
||||
Margin="5"
|
||||
Click="Close">
|
||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Panel>
|
||||
</Grid>
|
||||
</UserControl>
|
139
src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs
Normal file
139
src/Ryujinx.Ava/UI/Windows/ModManagerWindow.axaml.cs
Normal file
@ -0,0 +1,139 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Styling;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System.Threading.Tasks;
|
||||
using Button = Avalonia.Controls.Button;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
public partial class ModManagerWindow : UserControl
|
||||
{
|
||||
public ModManagerViewModel ViewModel;
|
||||
|
||||
public ModManagerWindow()
|
||||
{
|
||||
DataContext = this;
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public ModManagerWindow(ulong titleId)
|
||||
{
|
||||
DataContext = ViewModel = new ModManagerViewModel(titleId);
|
||||
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public static async Task Show(ulong titleId, string titleName)
|
||||
{
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
PrimaryButtonText = "",
|
||||
SecondaryButtonText = "",
|
||||
CloseButtonText = "",
|
||||
Content = new ModManagerWindow(titleId),
|
||||
Title = string.Format(LocaleManager.Instance[LocaleKeys.ModWindowHeading], titleName, titleId.ToString("X16")),
|
||||
};
|
||||
|
||||
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||
bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false));
|
||||
|
||||
contentDialog.Styles.Add(bottomBorder);
|
||||
|
||||
await contentDialog.ShowAsync();
|
||||
}
|
||||
|
||||
private void SaveAndClose(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.Save();
|
||||
((ContentDialog)Parent).Hide();
|
||||
}
|
||||
|
||||
private void Close(object sender, RoutedEventArgs e)
|
||||
{
|
||||
((ContentDialog)Parent).Hide();
|
||||
}
|
||||
|
||||
private async void DeleteMod(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button)
|
||||
{
|
||||
if (button.DataContext is ModModel model)
|
||||
{
|
||||
var result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogModManagerDeletionWarningMessage, model.Name),
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
ViewModel.Delete(model);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void DeleteAll(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var result = await ContentDialogHelper.CreateConfirmationDialog(
|
||||
LocaleManager.Instance[LocaleKeys.DialogWarning],
|
||||
LocaleManager.Instance[LocaleKeys.DialogModManagerDeletionAllWarningMessage],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogYes],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
LocaleManager.Instance[LocaleKeys.RyujinxConfirm]);
|
||||
|
||||
if (result == UserResult.Yes)
|
||||
{
|
||||
ViewModel.DeleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button button)
|
||||
{
|
||||
if (button.DataContext is ModModel model)
|
||||
{
|
||||
OpenHelper.OpenFolder(model.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
foreach (var content in e.AddedItems)
|
||||
{
|
||||
if (content is ModModel model)
|
||||
{
|
||||
var index = ViewModel.Mods.IndexOf(model);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
ViewModel.Mods[index].Enabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var content in e.RemovedItems)
|
||||
{
|
||||
if (content is ModModel model)
|
||||
{
|
||||
var index = ViewModel.Mods.IndexOf(model);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
ViewModel.Mods[index].Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
@ -6,8 +7,8 @@ namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public static class AppDataManager
|
||||
{
|
||||
public const string DefaultBaseDir = "Ryujinx";
|
||||
public const string DefaultPortableDir = "portable";
|
||||
private const string DefaultBaseDir = "Ryujinx";
|
||||
private const string DefaultPortableDir = "portable";
|
||||
|
||||
// The following 3 are always part of Base Directory
|
||||
private const string GamesDir = "games";
|
||||
@ -63,6 +64,17 @@ namespace Ryujinx.Common.Configuration
|
||||
string userProfilePath = Path.Combine(appDataPath, DefaultBaseDir);
|
||||
string portablePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultPortableDir);
|
||||
|
||||
// On macOS, check for a portable directory next to the app bundle as well.
|
||||
if (OperatingSystem.IsMacOS() && !Directory.Exists(portablePath))
|
||||
{
|
||||
string bundlePath = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", ".."));
|
||||
// Make sure we're actually running within an app bundle.
|
||||
if (bundlePath.EndsWith(".app"))
|
||||
{
|
||||
portablePath = Path.GetFullPath(Path.Combine(bundlePath, "..", DefaultPortableDir));
|
||||
}
|
||||
}
|
||||
|
||||
if (Directory.Exists(portablePath))
|
||||
{
|
||||
BaseDirPath = portablePath;
|
||||
@ -98,8 +110,7 @@ namespace Ryujinx.Common.Configuration
|
||||
string oldConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), DefaultBaseDir);
|
||||
if (Path.Exists(oldConfigPath) && !IsPathSymlink(oldConfigPath) && !Path.Exists(BaseDirPath))
|
||||
{
|
||||
CopyDirectory(oldConfigPath, BaseDirPath);
|
||||
Directory.Delete(oldConfigPath, true);
|
||||
FileSystemUtils.MoveDirectory(oldConfigPath, BaseDirPath);
|
||||
Directory.CreateSymbolicLink(oldConfigPath, BaseDirPath);
|
||||
}
|
||||
}
|
||||
@ -116,41 +127,13 @@ namespace Ryujinx.Common.Configuration
|
||||
}
|
||||
|
||||
// Check if existing old baseDirPath is a symlink, to prevent possible errors.
|
||||
// Should be removed, when the existance of the old directory isn't checked anymore.
|
||||
// Should be removed, when the existence of the old directory isn't checked anymore.
|
||||
private static bool IsPathSymlink(string path)
|
||||
{
|
||||
FileAttributes attributes = File.GetAttributes(path);
|
||||
return (attributes & FileAttributes.ReparsePoint) == FileAttributes.ReparsePoint;
|
||||
}
|
||||
|
||||
private static void CopyDirectory(string sourceDir, string destinationDir)
|
||||
{
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
}
|
||||
|
||||
DirectoryInfo[] subDirs = dir.GetDirectories();
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
if (file.Name == ".DS_Store")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
file.CopyTo(Path.Combine(destinationDir, file.Name));
|
||||
}
|
||||
|
||||
foreach (DirectoryInfo subDir in subDirs)
|
||||
{
|
||||
CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name));
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetModsPath() => CustomModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultModsDir)).FullName;
|
||||
public static string GetSdModsPath() => CustomSdModsPath ?? Directory.CreateDirectory(Path.Combine(BaseDirPath, DefaultSdcardDir, "atmosphere")).FullName;
|
||||
}
|
||||
|
9
src/Ryujinx.Common/Configuration/Mod.cs
Normal file
9
src/Ryujinx.Common/Configuration/Mod.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public class Mod
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Path { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
}
|
14
src/Ryujinx.Common/Configuration/ModMetadata.cs
Normal file
14
src/Ryujinx.Common/Configuration/ModMetadata.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public struct ModMetadata
|
||||
{
|
||||
public List<Mod> Mods { get; set; }
|
||||
|
||||
public ModMetadata()
|
||||
{
|
||||
Mods = new List<Mod>();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true)]
|
||||
[JsonSerializable(typeof(ModMetadata))]
|
||||
public partial class ModMetadataJsonSerializerContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using Ryujinx.Common.SystemInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
@ -22,6 +23,9 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
public readonly struct Log
|
||||
{
|
||||
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]");
|
||||
|
||||
internal readonly LogLevel Level;
|
||||
|
||||
internal Log(LogLevel level)
|
||||
@ -100,7 +104,12 @@ namespace Ryujinx.Common.Logging
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string FormatMessage(LogClass logClass, string caller, string message) => $"{logClass} {caller}: {message}";
|
||||
private static string FormatMessage(LogClass logClass, string caller, string message)
|
||||
{
|
||||
message = message.Replace(_homeDir, _homeDirRedacted);
|
||||
|
||||
return $"{logClass} {caller}: {message}";
|
||||
}
|
||||
}
|
||||
|
||||
public static Log? Debug { get; private set; }
|
||||
|
@ -13,31 +13,71 @@ namespace Ryujinx.Common.Logging.Targets
|
||||
|
||||
string ILogTarget.Name { get => _name; }
|
||||
|
||||
public FileLogTarget(string path, string name)
|
||||
: this(path, name, FileShare.Read, FileMode.Append)
|
||||
{ }
|
||||
public FileLogTarget(string name, FileStream fileStream)
|
||||
{
|
||||
_name = name;
|
||||
_logWriter = new StreamWriter(fileStream);
|
||||
_formatter = new DefaultLogFormatter();
|
||||
}
|
||||
|
||||
public FileLogTarget(string path, string name, FileShare fileShare, FileMode fileMode)
|
||||
public static FileStream PrepareLogFile(string path)
|
||||
{
|
||||
// Ensure directory is present
|
||||
DirectoryInfo logDir = new(Path.Combine(path, "Logs"));
|
||||
DirectoryInfo logDir = new(path);
|
||||
try
|
||||
{
|
||||
logDir.Create();
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Logging directory could not be created '{logDir}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clean up old logs, should only keep 3
|
||||
FileInfo[] files = logDir.GetFiles("*.log").OrderBy((info => info.CreationTime)).ToArray();
|
||||
for (int i = 0; i < files.Length - 2; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
files[i].Delete();
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
|
||||
|
||||
string version = ReleaseInformation.GetVersion();
|
||||
return null;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Old log file could not be deleted '{files[i].FullName}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
string version = ReleaseInformation.Version;
|
||||
|
||||
// Get path for the current time
|
||||
path = Path.Combine(logDir.FullName, $"Ryujinx_{version}_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
|
||||
|
||||
_name = name;
|
||||
_logWriter = new StreamWriter(File.Open(path, fileMode, FileAccess.Write, fileShare));
|
||||
_formatter = new DefaultLogFormatter();
|
||||
try
|
||||
{
|
||||
return File.Open(path, FileMode.Append, FileAccess.Write, FileShare.Read);
|
||||
}
|
||||
catch (UnauthorizedAccessException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (IOException exception)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"Log file could not be created '{path}': {exception}");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Log(object sender, LogEventArgs args)
|
||||
|
@ -744,6 +744,17 @@ namespace Ryujinx.Common.Memory
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array65<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
Array64<T> _other;
|
||||
public readonly int Length => 65;
|
||||
public ref T this[int index] => ref AsSpan()[index];
|
||||
|
||||
[Pure]
|
||||
public Span<T> AsSpan() => MemoryMarshal.CreateSpan(ref _e0, Length);
|
||||
}
|
||||
|
||||
public struct Array73<T> : IArray<T> where T : unmanaged
|
||||
{
|
||||
T _e0;
|
||||
|
@ -63,6 +63,18 @@ namespace Ryujinx.Common.Memory
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray3000 : IArray<byte>
|
||||
{
|
||||
private const int Size = 3000;
|
||||
|
||||
byte _element;
|
||||
|
||||
public readonly int Length => Size;
|
||||
public ref byte this[int index] => ref AsSpan()[index];
|
||||
public Span<byte> AsSpan() => MemoryMarshal.CreateSpan(ref _element, Size);
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 1)]
|
||||
public struct ByteArray4096 : IArray<byte>
|
||||
{
|
||||
|
@ -1,5 +1,3 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ryujinx.Common
|
||||
@ -9,50 +7,25 @@ namespace Ryujinx.Common
|
||||
{
|
||||
private const string FlatHubChannelOwner = "flathub";
|
||||
|
||||
public const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
public const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||
private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
||||
|
||||
public const string ReleaseChannelOwner = "%%RYUJINX_TARGET_RELEASE_CHANNEL_OWNER%%";
|
||||
public const string ReleaseChannelRepo = "%%RYUJINX_TARGET_RELEASE_CHANNEL_REPO%%";
|
||||
|
||||
public static bool IsValid()
|
||||
{
|
||||
return !BuildGitHash.StartsWith("%%") &&
|
||||
public static string ConfigName => !ConfigFileName.StartsWith("%%") ? ConfigFileName : "Config.json";
|
||||
|
||||
public static bool IsValid =>
|
||||
!BuildGitHash.StartsWith("%%") &&
|
||||
!ReleaseChannelName.StartsWith("%%") &&
|
||||
!ReleaseChannelOwner.StartsWith("%%") &&
|
||||
!ReleaseChannelRepo.StartsWith("%%");
|
||||
}
|
||||
!ReleaseChannelRepo.StartsWith("%%") &&
|
||||
!ConfigFileName.StartsWith("%%");
|
||||
|
||||
public static bool IsFlatHubBuild()
|
||||
{
|
||||
return IsValid() && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
}
|
||||
public static bool IsFlatHubBuild => IsValid && ReleaseChannelOwner.Equals(FlatHubChannelOwner);
|
||||
|
||||
public static string GetVersion()
|
||||
{
|
||||
if (IsValid())
|
||||
{
|
||||
return BuildVersion;
|
||||
}
|
||||
|
||||
return Assembly.GetEntryAssembly().GetCustomAttribute<AssemblyInformationalVersionAttribute>().InformationalVersion;
|
||||
}
|
||||
|
||||
#if FORCE_EXTERNAL_BASE_DIR
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
#else
|
||||
public static string GetBaseApplicationDirectory()
|
||||
{
|
||||
if (IsFlatHubBuild() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
return AppDataManager.BaseDirPath;
|
||||
}
|
||||
|
||||
return AppDomain.CurrentDomain.BaseDirectory;
|
||||
}
|
||||
#endif
|
||||
public static string Version => IsValid ? BuildVersion : Assembly.GetEntryAssembly()!.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
}
|
||||
}
|
||||
|
48
src/Ryujinx.Common/Utilities/FileSystemUtils.cs
Normal file
48
src/Ryujinx.Common/Utilities/FileSystemUtils.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
public static class FileSystemUtils
|
||||
{
|
||||
public static void CopyDirectory(string sourceDir, string destinationDir, bool recursive)
|
||||
{
|
||||
// Get information about the source directory
|
||||
var dir = new DirectoryInfo(sourceDir);
|
||||
|
||||
// Check if the source directory exists
|
||||
if (!dir.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException($"Source directory not found: {dir.FullName}");
|
||||
}
|
||||
|
||||
// Cache directories before we start copying
|
||||
DirectoryInfo[] dirs = dir.GetDirectories();
|
||||
|
||||
// Create the destination directory
|
||||
Directory.CreateDirectory(destinationDir);
|
||||
|
||||
// Get the files in the source directory and copy to the destination directory
|
||||
foreach (FileInfo file in dir.GetFiles())
|
||||
{
|
||||
string targetFilePath = Path.Combine(destinationDir, file.Name);
|
||||
file.CopyTo(targetFilePath);
|
||||
}
|
||||
|
||||
// If recursive and copying subdirectories, recursively call this method
|
||||
if (recursive)
|
||||
{
|
||||
foreach (DirectoryInfo subDir in dirs)
|
||||
{
|
||||
string newDestinationDir = Path.Combine(destinationDir, subDir.Name);
|
||||
CopyDirectory(subDir.FullName, newDestinationDir, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void MoveDirectory(string sourceDir, string destinationDir)
|
||||
{
|
||||
CopyDirectory(sourceDir, destinationDir, true);
|
||||
Directory.Delete(sourceDir, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -40,5 +40,9 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
public void PrepareCodeRange(ulong address, ulong size)
|
||||
{
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
src/Ryujinx.Cpu/DummyDiskCacheLoadState.cs
Normal file
17
src/Ryujinx.Cpu/DummyDiskCacheLoadState.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
public class DummyDiskCacheLoadState : IDiskCacheLoadState
|
||||
{
|
||||
#pragma warning disable CS0067 // The event is never used
|
||||
/// <inheritdoc/>
|
||||
public event Action<LoadState, int, int> StateChanged;
|
||||
#pragma warning restore CS0067
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Cancel()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu
|
||||
{
|
||||
/// <summary>
|
||||
/// CPU context interface.
|
||||
/// </summary>
|
||||
public interface ICpuContext
|
||||
public interface ICpuContext : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new execution context that will store thread CPU register state when executing guest code.
|
||||
|
@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
public JitCpuContext(ITickSource tickSource, IMemoryManager memory, bool for64Bit)
|
||||
{
|
||||
_tickSource = tickSource;
|
||||
_translator = new Translator(new JitMemoryAllocator(), memory, for64Bit);
|
||||
_translator = new Translator(new JitMemoryAllocator(forJit: true), memory, for64Bit);
|
||||
|
||||
if (memory.Type.IsHostMapped())
|
||||
{
|
||||
@ -57,5 +57,9 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
_translator.PrepareCodeRange(address, size);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,14 @@ namespace Ryujinx.Cpu.Jit
|
||||
{
|
||||
public class JitMemoryAllocator : IJitMemoryAllocator
|
||||
{
|
||||
private readonly MemoryAllocationFlags _jitFlag;
|
||||
|
||||
public JitMemoryAllocator(bool forJit = false)
|
||||
{
|
||||
_jitFlag = forJit ? MemoryAllocationFlags.Jit : MemoryAllocationFlags.None;
|
||||
}
|
||||
|
||||
public IJitMemoryBlock Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None);
|
||||
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Jit);
|
||||
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve | _jitFlag);
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
|
||||
public void Commit(ulong offset, ulong size) => _impl.Commit(offset, size);
|
||||
public void MapAsRw(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndWrite);
|
||||
public void MapAsRx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndExecute);
|
||||
public void MapAsRwx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadWriteExecute);
|
||||
|
||||
|
32
src/Ryujinx.Cpu/LightningJit/AarchCompiler.cs
Normal file
32
src/Ryujinx.Cpu/LightningJit/AarchCompiler.cs
Normal file
@ -0,0 +1,32 @@
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.Arm32;
|
||||
using Ryujinx.Cpu.LightningJit.Arm64;
|
||||
using Ryujinx.Cpu.LightningJit.State;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit
|
||||
{
|
||||
class AarchCompiler
|
||||
{
|
||||
public static CompiledFunction Compile(
|
||||
CpuPreset cpuPreset,
|
||||
IMemoryManager memoryManager,
|
||||
ulong address,
|
||||
AddressTable<ulong> funcTable,
|
||||
IntPtr dispatchStubPtr,
|
||||
ExecutionMode executionMode,
|
||||
Architecture targetArch)
|
||||
{
|
||||
if (executionMode == ExecutionMode.Aarch64)
|
||||
{
|
||||
return A64Compiler.Compile(cpuPreset, memoryManager, address, funcTable, dispatchStubPtr, targetArch);
|
||||
}
|
||||
else
|
||||
{
|
||||
return A32Compiler.Compile(cpuPreset, memoryManager, address, funcTable, dispatchStubPtr, executionMode == ExecutionMode.Aarch32Thumb, targetArch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
18
src/Ryujinx.Cpu/LightningJit/AddressForm.cs
Normal file
18
src/Ryujinx.Cpu/LightningJit/AddressForm.cs
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit
|
||||
{
|
||||
enum AddressForm : byte
|
||||
{
|
||||
None,
|
||||
OffsetReg,
|
||||
PostIndexed,
|
||||
PreIndexed,
|
||||
SignedScaled,
|
||||
UnsignedScaled,
|
||||
BaseRegister,
|
||||
BasePlusOffset,
|
||||
Literal,
|
||||
StructNoOffset,
|
||||
StructPostIndexedReg,
|
||||
}
|
||||
}
|
30
src/Ryujinx.Cpu/LightningJit/Arm32/A32Compiler.cs
Normal file
30
src/Ryujinx.Cpu/LightningJit/Arm32/A32Compiler.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
static class A32Compiler
|
||||
{
|
||||
public static CompiledFunction Compile(
|
||||
CpuPreset cpuPreset,
|
||||
IMemoryManager memoryManager,
|
||||
ulong address,
|
||||
AddressTable<ulong> funcTable,
|
||||
IntPtr dispatchStubPtr,
|
||||
bool isThumb,
|
||||
Architecture targetArch)
|
||||
{
|
||||
if (targetArch == Architecture.Arm64)
|
||||
{
|
||||
return Compiler.Compile(cpuPreset, memoryManager, address, funcTable, dispatchStubPtr, isThumb);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
106
src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs
Normal file
106
src/Ryujinx.Cpu/LightningJit/Arm32/Block.cs
Normal file
@ -0,0 +1,106 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
class Block
|
||||
{
|
||||
public readonly ulong Address;
|
||||
public readonly ulong EndAddress;
|
||||
public readonly List<InstInfo> Instructions;
|
||||
public readonly bool EndsWithBranch;
|
||||
public readonly bool HasHostCall;
|
||||
public readonly bool HasHostCallSkipContext;
|
||||
public readonly bool IsTruncated;
|
||||
public readonly bool IsLoopEnd;
|
||||
public readonly bool IsThumb;
|
||||
|
||||
public Block(
|
||||
ulong address,
|
||||
ulong endAddress,
|
||||
List<InstInfo> instructions,
|
||||
bool endsWithBranch,
|
||||
bool hasHostCall,
|
||||
bool hasHostCallSkipContext,
|
||||
bool isTruncated,
|
||||
bool isLoopEnd,
|
||||
bool isThumb)
|
||||
{
|
||||
Debug.Assert(isThumb || (int)((endAddress - address) / 4) == instructions.Count);
|
||||
|
||||
Address = address;
|
||||
EndAddress = endAddress;
|
||||
Instructions = instructions;
|
||||
EndsWithBranch = endsWithBranch;
|
||||
HasHostCall = hasHostCall;
|
||||
HasHostCallSkipContext = hasHostCallSkipContext;
|
||||
IsTruncated = isTruncated;
|
||||
IsLoopEnd = isLoopEnd;
|
||||
IsThumb = isThumb;
|
||||
}
|
||||
|
||||
public (Block, Block) SplitAtAddress(ulong address)
|
||||
{
|
||||
int splitIndex = FindSplitIndex(address);
|
||||
|
||||
if (splitIndex < 0)
|
||||
{
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
int splitCount = Instructions.Count - splitIndex;
|
||||
|
||||
// Technically those are valid, but we don't want to create empty blocks.
|
||||
Debug.Assert(splitIndex != 0);
|
||||
Debug.Assert(splitCount != 0);
|
||||
|
||||
Block leftBlock = new(
|
||||
Address,
|
||||
address,
|
||||
Instructions.GetRange(0, splitIndex),
|
||||
false,
|
||||
HasHostCall,
|
||||
HasHostCallSkipContext,
|
||||
false,
|
||||
false,
|
||||
IsThumb);
|
||||
|
||||
Block rightBlock = new(
|
||||
address,
|
||||
EndAddress,
|
||||
Instructions.GetRange(splitIndex, splitCount),
|
||||
EndsWithBranch,
|
||||
HasHostCall,
|
||||
HasHostCallSkipContext,
|
||||
IsTruncated,
|
||||
IsLoopEnd,
|
||||
IsThumb);
|
||||
|
||||
return (leftBlock, rightBlock);
|
||||
}
|
||||
|
||||
private int FindSplitIndex(ulong address)
|
||||
{
|
||||
if (IsThumb)
|
||||
{
|
||||
ulong pc = Address;
|
||||
|
||||
for (int index = 0; index < Instructions.Count; index++)
|
||||
{
|
||||
if (pc == address)
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
pc += Instructions[index].Flags.HasFlag(InstFlags.Thumb16) ? 2UL : 4UL;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return (int)((address - Address) / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
src/Ryujinx.Cpu/LightningJit/Arm32/BranchType.cs
Normal file
15
src/Ryujinx.Cpu/LightningJit/Arm32/BranchType.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
enum BranchType
|
||||
{
|
||||
Branch,
|
||||
Call,
|
||||
IndirectBranch,
|
||||
TableBranchByte,
|
||||
TableBranchHalfword,
|
||||
IndirectCall,
|
||||
SyncPoint,
|
||||
SoftwareInterrupt,
|
||||
ReadCntpct,
|
||||
}
|
||||
}
|
198
src/Ryujinx.Cpu/LightningJit/Arm32/CodeGenContext.cs
Normal file
198
src/Ryujinx.Cpu/LightningJit/Arm32/CodeGenContext.cs
Normal file
@ -0,0 +1,198 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
class CodeGenContext
|
||||
{
|
||||
public CodeWriter CodeWriter { get; }
|
||||
public Assembler Arm64Assembler { get; }
|
||||
public RegisterAllocator RegisterAllocator { get; }
|
||||
|
||||
public MemoryManagerType MemoryManagerType { get; }
|
||||
|
||||
private uint _instructionAddress;
|
||||
|
||||
public bool IsThumb { get; }
|
||||
public uint Pc { get; private set; }
|
||||
public bool InITBlock { get; private set; }
|
||||
|
||||
private InstInfo _nextInstruction;
|
||||
private bool _skipNextInstruction;
|
||||
|
||||
private readonly ArmCondition[] _itConditions;
|
||||
private int _itCount;
|
||||
|
||||
private readonly List<PendingBranch> _pendingBranches;
|
||||
|
||||
private bool _nzcvModified;
|
||||
|
||||
public CodeGenContext(CodeWriter codeWriter, Assembler arm64Assembler, RegisterAllocator registerAllocator, MemoryManagerType mmType, bool isThumb)
|
||||
{
|
||||
CodeWriter = codeWriter;
|
||||
Arm64Assembler = arm64Assembler;
|
||||
RegisterAllocator = registerAllocator;
|
||||
MemoryManagerType = mmType;
|
||||
_itConditions = new ArmCondition[4];
|
||||
_pendingBranches = new();
|
||||
IsThumb = isThumb;
|
||||
}
|
||||
|
||||
public void SetPc(uint address)
|
||||
{
|
||||
// Due to historical reasons, the PC value is always 2 instructions ahead on 32-bit Arm CPUs.
|
||||
Pc = address + (IsThumb ? 4u : 8u);
|
||||
_instructionAddress = address;
|
||||
}
|
||||
|
||||
public void SetNextInstruction(InstInfo info)
|
||||
{
|
||||
_nextInstruction = info;
|
||||
}
|
||||
|
||||
public InstInfo PeekNextInstruction()
|
||||
{
|
||||
return _nextInstruction;
|
||||
}
|
||||
|
||||
public void SetSkipNextInstruction()
|
||||
{
|
||||
_skipNextInstruction = true;
|
||||
}
|
||||
|
||||
public bool ConsumeSkipNextInstruction()
|
||||
{
|
||||
bool skip = _skipNextInstruction;
|
||||
_skipNextInstruction = false;
|
||||
|
||||
return skip;
|
||||
}
|
||||
|
||||
public void AddPendingBranch(InstName name, int offset)
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.Branch, Pc + (uint)offset, 0u, name, CodeWriter.InstructionPointer));
|
||||
}
|
||||
|
||||
public void AddPendingCall(uint targetAddress, uint nextAddress)
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.Call, targetAddress, nextAddress, InstName.BlI, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.EnsureTempGprRegisters(1);
|
||||
RegisterAllocator.MarkGprAsUsed(RegisterUtils.LrRegister);
|
||||
}
|
||||
|
||||
public void AddPendingIndirectBranch(InstName name, uint targetRegister)
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.IndirectBranch, targetRegister, 0u, name, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.MarkGprAsUsed((int)targetRegister);
|
||||
}
|
||||
|
||||
public void AddPendingTableBranch(uint rn, uint rm, bool halfword)
|
||||
{
|
||||
_pendingBranches.Add(new(halfword ? BranchType.TableBranchHalfword : BranchType.TableBranchByte, rn, rm, InstName.Tbb, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.EnsureTempGprRegisters(2);
|
||||
RegisterAllocator.MarkGprAsUsed((int)rn);
|
||||
RegisterAllocator.MarkGprAsUsed((int)rm);
|
||||
}
|
||||
|
||||
public void AddPendingIndirectCall(uint targetRegister, uint nextAddress)
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.IndirectCall, targetRegister, nextAddress, InstName.BlxR, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.EnsureTempGprRegisters(targetRegister == RegisterUtils.LrRegister ? 1 : 0);
|
||||
RegisterAllocator.MarkGprAsUsed((int)targetRegister);
|
||||
RegisterAllocator.MarkGprAsUsed(RegisterUtils.LrRegister);
|
||||
}
|
||||
|
||||
public void AddPendingSyncPoint()
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.SyncPoint, 0, 0, default, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.EnsureTempGprRegisters(1);
|
||||
}
|
||||
|
||||
public void AddPendingBkpt(uint imm)
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.SoftwareInterrupt, imm, _instructionAddress, InstName.Bkpt, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.EnsureTempGprRegisters(1);
|
||||
}
|
||||
|
||||
public void AddPendingSvc(uint imm)
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.SoftwareInterrupt, imm, _instructionAddress, InstName.Svc, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.EnsureTempGprRegisters(1);
|
||||
}
|
||||
|
||||
public void AddPendingUdf(uint imm)
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.SoftwareInterrupt, imm, _instructionAddress, InstName.Udf, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.EnsureTempGprRegisters(1);
|
||||
}
|
||||
|
||||
public void AddPendingReadCntpct(uint rt, uint rt2)
|
||||
{
|
||||
_pendingBranches.Add(new(BranchType.ReadCntpct, rt, rt2, InstName.Mrrc, CodeWriter.InstructionPointer));
|
||||
|
||||
RegisterAllocator.EnsureTempGprRegisters(1);
|
||||
}
|
||||
|
||||
public IEnumerable<PendingBranch> GetPendingBranches()
|
||||
{
|
||||
return _pendingBranches;
|
||||
}
|
||||
|
||||
public void SetItBlockStart(ReadOnlySpan<ArmCondition> conditions)
|
||||
{
|
||||
_itCount = conditions.Length;
|
||||
|
||||
for (int index = 0; index < conditions.Length; index++)
|
||||
{
|
||||
_itConditions[index] = conditions[index];
|
||||
}
|
||||
|
||||
InITBlock = true;
|
||||
}
|
||||
|
||||
public bool ConsumeItCondition(out ArmCondition condition)
|
||||
{
|
||||
if (_itCount != 0)
|
||||
{
|
||||
condition = _itConditions[--_itCount];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
condition = ArmCondition.Al;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void UpdateItState()
|
||||
{
|
||||
if (_itCount == 0)
|
||||
{
|
||||
InITBlock = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetNzcvModified()
|
||||
{
|
||||
_nzcvModified = true;
|
||||
}
|
||||
|
||||
public bool ConsumeNzcvModified()
|
||||
{
|
||||
bool modified = _nzcvModified;
|
||||
_nzcvModified = false;
|
||||
|
||||
return modified;
|
||||
}
|
||||
}
|
||||
}
|
556
src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs
Normal file
556
src/Ryujinx.Cpu/LightningJit/Arm32/Decoder.cs
Normal file
@ -0,0 +1,556 @@
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
static class Decoder<T> where T : IInstEmit
|
||||
{
|
||||
public static MultiBlock DecodeMulti(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb)
|
||||
{
|
||||
List<Block> blocks = new();
|
||||
List<ulong> branchTargets = new();
|
||||
|
||||
while (true)
|
||||
{
|
||||
Block block = Decode(cpuPreset, memoryManager, address, isThumb);
|
||||
|
||||
if (!block.IsTruncated && TryGetBranchTarget(block, out ulong targetAddress))
|
||||
{
|
||||
branchTargets.Add(targetAddress);
|
||||
}
|
||||
|
||||
blocks.Add(block);
|
||||
|
||||
if (block.IsTruncated || !HasNextBlock(block, block.EndAddress - 4UL, branchTargets))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
address = block.EndAddress;
|
||||
}
|
||||
|
||||
branchTargets.Sort();
|
||||
SplitBlocks(blocks, branchTargets);
|
||||
|
||||
return new(blocks);
|
||||
}
|
||||
|
||||
private static bool TryGetBranchTarget(Block block, out ulong targetAddress)
|
||||
{
|
||||
// PC is 2 instructions ahead, since the end address is already one instruction after the last one, we just need to add
|
||||
// another instruction.
|
||||
|
||||
ulong pc = block.EndAddress + (block.IsThumb ? 2UL : 4UL);
|
||||
|
||||
return TryGetBranchTarget(block.Instructions[^1].Name, block.Instructions[^1].Flags, pc, block.Instructions[^1].Encoding, block.IsThumb, out targetAddress);
|
||||
}
|
||||
|
||||
private static bool TryGetBranchTarget(InstName name, InstFlags flags, ulong pc, uint encoding, bool isThumb, out ulong targetAddress)
|
||||
{
|
||||
int originalOffset;
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case InstName.B:
|
||||
if (isThumb)
|
||||
{
|
||||
if (flags.HasFlag(InstFlags.Thumb16))
|
||||
{
|
||||
if ((encoding & (1u << 29)) != 0)
|
||||
{
|
||||
InstImm11b16w11 inst = new(encoding);
|
||||
|
||||
originalOffset = ImmUtils.ExtractT16SImm11Times2(inst.Imm11);
|
||||
}
|
||||
else
|
||||
{
|
||||
InstCondb24w4Imm8b16w8 inst = new(encoding);
|
||||
|
||||
originalOffset = ImmUtils.ExtractT16SImm8Times2(inst.Imm8);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((encoding & (1u << 12)) != 0)
|
||||
{
|
||||
InstSb26w1Imm10b16w10J1b13w1J2b11w1Imm11b0w11 inst = new(encoding);
|
||||
|
||||
originalOffset = ImmUtils.CombineSImm24Times2(inst.Imm11, inst.Imm10, inst.J1, inst.J2, inst.S);
|
||||
}
|
||||
else
|
||||
{
|
||||
InstSb26w1Condb22w4Imm6b16w6J1b13w1J2b11w1Imm11b0w11 inst = new(encoding);
|
||||
|
||||
originalOffset = ImmUtils.CombineSImm20Times2(inst.Imm11, inst.Imm6, inst.J1, inst.J2, inst.S);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
originalOffset = ImmUtils.ExtractSImm24Times4(encoding);
|
||||
}
|
||||
|
||||
targetAddress = pc + (ulong)originalOffset;
|
||||
Debug.Assert((targetAddress & 1) == 0);
|
||||
|
||||
return true;
|
||||
|
||||
case InstName.Cbnz:
|
||||
originalOffset = ImmUtils.ExtractT16UImm5Times2(encoding);
|
||||
targetAddress = pc + (ulong)originalOffset;
|
||||
Debug.Assert((targetAddress & 1) == 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
targetAddress = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SplitBlocks(List<Block> blocks, List<ulong> branchTargets)
|
||||
{
|
||||
int btIndex = 0;
|
||||
|
||||
while (btIndex < branchTargets.Count)
|
||||
{
|
||||
for (int blockIndex = 0; blockIndex < blocks.Count && btIndex < branchTargets.Count; blockIndex++)
|
||||
{
|
||||
Block block = blocks[blockIndex];
|
||||
ulong currentBranchTarget = branchTargets[btIndex];
|
||||
|
||||
while (currentBranchTarget >= block.Address && currentBranchTarget < block.EndAddress)
|
||||
{
|
||||
if (block.Address != currentBranchTarget)
|
||||
{
|
||||
(Block leftBlock, Block rightBlock) = block.SplitAtAddress(currentBranchTarget);
|
||||
|
||||
if (leftBlock != null && rightBlock != null)
|
||||
{
|
||||
blocks.Insert(blockIndex, leftBlock);
|
||||
blocks[blockIndex + 1] = rightBlock;
|
||||
|
||||
block = leftBlock;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Split can only fail in thumb mode, where the instruction size is not fixed.
|
||||
|
||||
Debug.Assert(block.IsThumb);
|
||||
}
|
||||
}
|
||||
|
||||
btIndex++;
|
||||
|
||||
while (btIndex < branchTargets.Count && branchTargets[btIndex] == currentBranchTarget)
|
||||
{
|
||||
btIndex++;
|
||||
}
|
||||
|
||||
if (btIndex >= branchTargets.Count)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currentBranchTarget = branchTargets[btIndex];
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(btIndex < int.MaxValue);
|
||||
btIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasNextBlock(in Block block, ulong pc, List<ulong> branchTargets)
|
||||
{
|
||||
InstFlags lastInstFlags = block.Instructions[^1].Flags;
|
||||
|
||||
// Thumb has separate encodings for conditional and unconditional branch instructions.
|
||||
if (lastInstFlags.HasFlag(InstFlags.Cond) && (block.IsThumb || (ArmCondition)(block.Instructions[^1].Encoding >> 28) < ArmCondition.Al))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (block.Instructions[^1].Name)
|
||||
{
|
||||
case InstName.B:
|
||||
return branchTargets.Contains(pc + 4UL) ||
|
||||
(TryGetBranchTarget(block, out ulong targetAddress) && targetAddress >= pc && targetAddress < pc + 0x1000);
|
||||
|
||||
case InstName.Bx:
|
||||
case InstName.Bxj:
|
||||
return branchTargets.Contains(pc + 4UL);
|
||||
|
||||
case InstName.Cbnz:
|
||||
case InstName.BlI:
|
||||
case InstName.BlxR:
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WritesToPC(block.Instructions[^1].Encoding, block.Instructions[^1].Name, lastInstFlags, block.IsThumb))
|
||||
{
|
||||
return branchTargets.Contains(pc + 4UL);
|
||||
}
|
||||
|
||||
return !block.EndsWithBranch;
|
||||
}
|
||||
|
||||
private static Block Decode(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, bool isThumb)
|
||||
{
|
||||
ulong startAddress = address;
|
||||
|
||||
List<InstInfo> insts = new();
|
||||
|
||||
uint encoding;
|
||||
InstMeta meta;
|
||||
InstFlags extraFlags = InstFlags.None;
|
||||
bool hasHostCall = false;
|
||||
bool hasHostCallSkipContext = false;
|
||||
bool isTruncated = false;
|
||||
|
||||
do
|
||||
{
|
||||
if (!memoryManager.IsMapped(address))
|
||||
{
|
||||
encoding = 0;
|
||||
meta = default;
|
||||
isTruncated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (isThumb)
|
||||
{
|
||||
encoding = (uint)memoryManager.Read<ushort>(address) << 16;
|
||||
address += 2UL;
|
||||
|
||||
extraFlags = InstFlags.Thumb16;
|
||||
|
||||
if (!InstTableT16<T>.TryGetMeta(encoding, cpuPreset.Version, cpuPreset.Features, out meta))
|
||||
{
|
||||
encoding |= memoryManager.Read<ushort>(address);
|
||||
|
||||
if (InstTableT32<T>.TryGetMeta(encoding, cpuPreset.Version, cpuPreset.Features, out meta))
|
||||
{
|
||||
address += 2UL;
|
||||
extraFlags = InstFlags.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
encoding = memoryManager.Read<uint>(address);
|
||||
address += 4UL;
|
||||
|
||||
meta = InstTableA32<T>.GetMeta(encoding, cpuPreset.Version, cpuPreset.Features);
|
||||
}
|
||||
|
||||
if (meta.Name.IsSystemOrCall())
|
||||
{
|
||||
if (!hasHostCall)
|
||||
{
|
||||
hasHostCall = InstEmitSystem.NeedsCall(meta.Name);
|
||||
}
|
||||
|
||||
if (!hasHostCallSkipContext)
|
||||
{
|
||||
hasHostCallSkipContext = meta.Name.IsCall() || InstEmitSystem.NeedsCallSkipContext(meta.Name);
|
||||
}
|
||||
}
|
||||
|
||||
insts.Add(new(encoding, meta.Name, meta.EmitFunc, meta.Flags | extraFlags));
|
||||
}
|
||||
while (!IsControlFlow(encoding, meta.Name, meta.Flags | extraFlags, isThumb));
|
||||
|
||||
bool isLoopEnd = false;
|
||||
|
||||
if (!isTruncated && IsBackwardsBranch(meta.Name, encoding))
|
||||
{
|
||||
isLoopEnd = true;
|
||||
hasHostCallSkipContext = true;
|
||||
}
|
||||
|
||||
return new(
|
||||
startAddress,
|
||||
address,
|
||||
insts,
|
||||
!isTruncated,
|
||||
hasHostCall,
|
||||
hasHostCallSkipContext,
|
||||
isTruncated,
|
||||
isLoopEnd,
|
||||
isThumb);
|
||||
}
|
||||
|
||||
private static bool IsControlFlow(uint encoding, InstName name, InstFlags flags, bool isThumb)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case InstName.B:
|
||||
case InstName.BlI:
|
||||
case InstName.BlxR:
|
||||
case InstName.Bx:
|
||||
case InstName.Bxj:
|
||||
case InstName.Cbnz:
|
||||
case InstName.Tbb:
|
||||
return true;
|
||||
}
|
||||
|
||||
return WritesToPC(encoding, name, flags, isThumb);
|
||||
}
|
||||
|
||||
public static bool WritesToPC(uint encoding, InstName name, InstFlags flags, bool isThumb)
|
||||
{
|
||||
return (GetRegisterWriteMask(encoding, name, flags, isThumb) & (1u << RegisterUtils.PcRegister)) != 0;
|
||||
}
|
||||
|
||||
private static uint GetRegisterWriteMask(uint encoding, InstName name, InstFlags flags, bool isThumb)
|
||||
{
|
||||
uint mask = 0;
|
||||
|
||||
if (isThumb)
|
||||
{
|
||||
if (flags.HasFlag(InstFlags.Thumb16))
|
||||
{
|
||||
if (flags.HasFlag(InstFlags.Rdn))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRdn(flags, encoding);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(InstFlags.Rd))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRdT16(flags, encoding);
|
||||
}
|
||||
|
||||
Debug.Assert(!flags.HasFlag(InstFlags.RdHi));
|
||||
|
||||
if (IsRegisterWrite(flags, InstFlags.Rt))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRtT16(flags, encoding);
|
||||
}
|
||||
|
||||
Debug.Assert(!flags.HasFlag(InstFlags.Rt2));
|
||||
|
||||
if (IsRegisterWrite(flags, InstFlags.Rlist))
|
||||
{
|
||||
mask |= (byte)(encoding >> 16);
|
||||
|
||||
if (name == InstName.Push)
|
||||
{
|
||||
mask |= (encoding >> 10) & 0x4000; // LR
|
||||
}
|
||||
else if (name == InstName.Pop)
|
||||
{
|
||||
mask |= (encoding >> 9) & 0x8000; // PC
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(!flags.HasFlag(InstFlags.WBack));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flags.HasFlag(InstFlags.Rd))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRdT32(flags, encoding);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(InstFlags.RdLo))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRdLoT32(encoding);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(InstFlags.RdHi))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRdHiT32(encoding);
|
||||
}
|
||||
|
||||
if (IsRegisterWrite(flags, InstFlags.Rt) && IsRtWrite(name, encoding) && !IsR15RtEncodingSpecial(name, encoding))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRtT32(encoding);
|
||||
}
|
||||
|
||||
if (IsRegisterWrite(flags, InstFlags.Rt2) && IsRtWrite(name, encoding))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRt2T32(encoding);
|
||||
}
|
||||
|
||||
if (IsRegisterWrite(flags, InstFlags.Rlist))
|
||||
{
|
||||
mask |= (ushort)encoding;
|
||||
}
|
||||
|
||||
if (flags.HasFlag(InstFlags.WBack) && HasWriteBackT32(name, encoding))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRn(encoding); // This is at the same bit position as A32.
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (flags.HasFlag(InstFlags.Rd))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRd(flags, encoding);
|
||||
}
|
||||
|
||||
if (flags.HasFlag(InstFlags.RdHi))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRdHi(encoding);
|
||||
}
|
||||
|
||||
if (IsRegisterWrite(flags, InstFlags.Rt) && IsRtWrite(name, encoding) && !IsR15RtEncodingSpecial(name, encoding))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRt(encoding);
|
||||
}
|
||||
|
||||
if (IsRegisterWrite(flags, InstFlags.Rt2) && IsRtWrite(name, encoding))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRt2(encoding);
|
||||
}
|
||||
|
||||
if (IsRegisterWrite(flags, InstFlags.Rlist))
|
||||
{
|
||||
mask |= (ushort)encoding;
|
||||
}
|
||||
|
||||
if (flags.HasFlag(InstFlags.WBack) && HasWriteBack(name, encoding))
|
||||
{
|
||||
mask |= 1u << RegisterUtils.ExtractRn(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
private static bool IsRtWrite(InstName name, uint encoding)
|
||||
{
|
||||
// Some instructions can move GPR to FP/SIMD or FP/SIMD to GPR depending on the encoding.
|
||||
// Detect those cases so that we can tell if we're actually doing a register write.
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case InstName.VmovD:
|
||||
case InstName.VmovH:
|
||||
case InstName.VmovS:
|
||||
case InstName.VmovSs:
|
||||
return (encoding & (1u << 20)) != 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool HasWriteBack(InstName name, uint encoding)
|
||||
{
|
||||
if (IsLoadStoreMultiple(name))
|
||||
{
|
||||
return (encoding & (1u << 21)) != 0;
|
||||
}
|
||||
|
||||
if (IsVLDnVSTn(name))
|
||||
{
|
||||
return (encoding & 0xf) != RegisterUtils.PcRegister;
|
||||
}
|
||||
|
||||
bool w = (encoding & (1u << 21)) != 0;
|
||||
bool p = (encoding & (1u << 24)) != 0;
|
||||
|
||||
return !p || w;
|
||||
}
|
||||
|
||||
private static bool HasWriteBackT32(InstName name, uint encoding)
|
||||
{
|
||||
if (IsLoadStoreMultiple(name))
|
||||
{
|
||||
return (encoding & (1u << 21)) != 0;
|
||||
}
|
||||
|
||||
if (IsVLDnVSTn(name))
|
||||
{
|
||||
return (encoding & 0xf) != RegisterUtils.PcRegister;
|
||||
}
|
||||
|
||||
return (encoding & (1u << 8)) != 0;
|
||||
}
|
||||
|
||||
private static bool IsLoadStoreMultiple(InstName name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case InstName.Ldm:
|
||||
case InstName.Ldmda:
|
||||
case InstName.Ldmdb:
|
||||
case InstName.LdmE:
|
||||
case InstName.Ldmib:
|
||||
case InstName.LdmU:
|
||||
case InstName.Stm:
|
||||
case InstName.Stmda:
|
||||
case InstName.Stmdb:
|
||||
case InstName.Stmib:
|
||||
case InstName.StmU:
|
||||
case InstName.Fldmx:
|
||||
case InstName.Fstmx:
|
||||
case InstName.Vldm:
|
||||
case InstName.Vstm:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsVLDnVSTn(InstName name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case InstName.Vld11:
|
||||
case InstName.Vld1A:
|
||||
case InstName.Vld1M:
|
||||
case InstName.Vld21:
|
||||
case InstName.Vld2A:
|
||||
case InstName.Vld2M:
|
||||
case InstName.Vld31:
|
||||
case InstName.Vld3A:
|
||||
case InstName.Vld3M:
|
||||
case InstName.Vld41:
|
||||
case InstName.Vld4A:
|
||||
case InstName.Vld4M:
|
||||
case InstName.Vst11:
|
||||
case InstName.Vst1M:
|
||||
case InstName.Vst21:
|
||||
case InstName.Vst2M:
|
||||
case InstName.Vst31:
|
||||
case InstName.Vst3M:
|
||||
case InstName.Vst41:
|
||||
case InstName.Vst4M:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsR15RtEncodingSpecial(InstName name, uint encoding)
|
||||
{
|
||||
if (name == InstName.Vmrs)
|
||||
{
|
||||
return ((encoding >> 16) & 0xf) == 1;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsRegisterWrite(InstFlags flags, InstFlags testFlag)
|
||||
{
|
||||
return flags.HasFlag(testFlag) && !flags.HasFlag(InstFlags.ReadRd);
|
||||
}
|
||||
|
||||
private static bool IsBackwardsBranch(InstName name, uint encoding)
|
||||
{
|
||||
if (name == InstName.B)
|
||||
{
|
||||
return ImmUtils.ExtractSImm24Times4(encoding) < 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
1231
src/Ryujinx.Cpu/LightningJit/Arm32/IInstEmit.cs
Normal file
1231
src/Ryujinx.Cpu/LightningJit/Arm32/IInstEmit.cs
Normal file
File diff suppressed because it is too large
Load Diff
137
src/Ryujinx.Cpu/LightningJit/Arm32/ImmUtils.cs
Normal file
137
src/Ryujinx.Cpu/LightningJit/Arm32/ImmUtils.cs
Normal file
@ -0,0 +1,137 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
static class ImmUtils
|
||||
{
|
||||
public static uint ExpandImm(uint imm)
|
||||
{
|
||||
return BitOperations.RotateRight((byte)imm, (int)(imm >> 8) * 2);
|
||||
}
|
||||
|
||||
public static bool ExpandedImmRotated(uint imm)
|
||||
{
|
||||
return (imm >> 8) != 0;
|
||||
}
|
||||
|
||||
public static uint ExpandImm(uint imm8, uint imm3, uint i)
|
||||
{
|
||||
uint imm = CombineImmU12(imm8, imm3, i);
|
||||
|
||||
if (imm >> 10 == 0)
|
||||
{
|
||||
return ((imm >> 8) & 3) switch
|
||||
{
|
||||
0 => (byte)imm,
|
||||
1 => (byte)imm * 0x00010001u,
|
||||
2 => (byte)imm * 0x01000100u,
|
||||
3 => (byte)imm * 0x01010101u,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return BitOperations.RotateRight(0x80u | (byte)imm, (int)(imm >> 7));
|
||||
}
|
||||
}
|
||||
|
||||
public static bool ExpandedImmRotated(uint imm8, uint imm3, uint i)
|
||||
{
|
||||
uint imm = CombineImmU12(imm8, imm3, i);
|
||||
|
||||
return (imm >> 7) != 0;
|
||||
}
|
||||
|
||||
public static uint CombineImmU5(uint imm2, uint imm3)
|
||||
{
|
||||
return imm2 | (imm3 << 2);
|
||||
}
|
||||
|
||||
public static uint CombineImmU5IImm4(uint i, uint imm4)
|
||||
{
|
||||
return i | (imm4 << 1);
|
||||
}
|
||||
|
||||
public static uint CombineImmU8(uint imm4l, uint imm4h)
|
||||
{
|
||||
return imm4l | (imm4h << 4);
|
||||
}
|
||||
|
||||
public static uint CombineImmU8(uint imm4, uint imm3, uint i)
|
||||
{
|
||||
return imm4 | (imm3 << 4) | (i << 7);
|
||||
}
|
||||
|
||||
public static uint CombineImmU12(uint imm8, uint imm3, uint i)
|
||||
{
|
||||
return imm8 | (imm3 << 8) | (i << 11);
|
||||
}
|
||||
|
||||
public static uint CombineImmU16(uint imm12, uint imm4)
|
||||
{
|
||||
return imm12 | (imm4 << 12);
|
||||
}
|
||||
|
||||
public static uint CombineImmU16(uint imm8, uint imm3, uint i, uint imm4)
|
||||
{
|
||||
return imm8 | (imm3 << 8) | (i << 11) | (imm4 << 12);
|
||||
}
|
||||
|
||||
public static int CombineSImm20Times2(uint imm11, uint imm6, uint j1, uint j2, uint s)
|
||||
{
|
||||
int imm32 = (int)(imm11 | (imm6 << 11) | (j1 << 17) | (j2 << 18) | (s << 19));
|
||||
|
||||
return (imm32 << 13) >> 12;
|
||||
}
|
||||
|
||||
public static int CombineSImm24Times2(uint imm11, uint imm10, uint j1, uint j2, uint s)
|
||||
{
|
||||
uint i1 = j1 ^ s ^ 1;
|
||||
uint i2 = j2 ^ s ^ 1;
|
||||
|
||||
int imm32 = (int)(imm11 | (imm10 << 11) | (i2 << 21) | (i1 << 22) | (s << 23));
|
||||
|
||||
return (imm32 << 8) >> 7;
|
||||
}
|
||||
|
||||
public static int CombineSImm24Times4(uint imm10L, uint imm10H, uint j1, uint j2, uint s)
|
||||
{
|
||||
uint i1 = j1 ^ s ^ 1;
|
||||
uint i2 = j2 ^ s ^ 1;
|
||||
|
||||
int imm32 = (int)(imm10L | (imm10H << 10) | (i2 << 20) | (i1 << 21) | (s << 22));
|
||||
|
||||
return (imm32 << 9) >> 7;
|
||||
}
|
||||
|
||||
public static uint CombineRegisterList(uint registerList, uint m)
|
||||
{
|
||||
return registerList | (m << 14);
|
||||
}
|
||||
|
||||
public static uint CombineRegisterList(uint registerList, uint m, uint p)
|
||||
{
|
||||
return registerList | (m << 14) | (p << 15);
|
||||
}
|
||||
|
||||
public static int ExtractSImm24Times4(uint encoding)
|
||||
{
|
||||
return (int)(encoding << 8) >> 6;
|
||||
}
|
||||
|
||||
public static int ExtractT16UImm5Times2(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> 18) & 0x3e;
|
||||
}
|
||||
|
||||
public static int ExtractT16SImm8Times2(uint encoding)
|
||||
{
|
||||
return (int)(encoding << 24) >> 23;
|
||||
}
|
||||
|
||||
public static int ExtractT16SImm11Times2(uint encoding)
|
||||
{
|
||||
return (int)(encoding << 21) >> 20;
|
||||
}
|
||||
}
|
||||
}
|
2927
src/Ryujinx.Cpu/LightningJit/Arm32/InstDecoders.cs
Normal file
2927
src/Ryujinx.Cpu/LightningJit/Arm32/InstDecoders.cs
Normal file
File diff suppressed because it is too large
Load Diff
63
src/Ryujinx.Cpu/LightningJit/Arm32/InstFlags.cs
Normal file
63
src/Ryujinx.Cpu/LightningJit/Arm32/InstFlags.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
[Flags]
|
||||
enum InstFlags
|
||||
{
|
||||
None = 0,
|
||||
Cond = 1 << 0,
|
||||
Rd = 1 << 1,
|
||||
RdLo = 1 << 2,
|
||||
RdHi = 1 << 3,
|
||||
Rdn = 1 << 4,
|
||||
Dn = 1 << 5,
|
||||
Rt = 1 << 6,
|
||||
Rt2 = 1 << 7,
|
||||
Rlist = 1 << 8,
|
||||
Rd16 = 1 << 9,
|
||||
ReadRd = 1 << 10,
|
||||
WBack = 1 << 11,
|
||||
Thumb16 = 1 << 12,
|
||||
|
||||
RdnDn = Rdn | Dn,
|
||||
RdRd16 = Rd | Rd16,
|
||||
RtRt2 = Rt | Rt2,
|
||||
RdLoRdHi = RdLo | RdHi,
|
||||
RdLoHi = Rd | RdHi,
|
||||
RdRtRead = Rd | RtRead,
|
||||
RdRtReadRd16 = Rd | RtRead | Rd16,
|
||||
RdRt2Read = Rd | Rt2 | RtRead,
|
||||
RdRt2ReadRd16 = Rd | Rt2 | RtRead | Rd16,
|
||||
RtRd16 = Rt | Rd16,
|
||||
RtWBack = Rt | WBack,
|
||||
Rt2WBack = Rt2 | RtWBack,
|
||||
RtRead = Rt | ReadRd,
|
||||
RtReadRd16 = Rt | ReadRd | Rd16,
|
||||
Rt2Read = Rt2 | RtRead,
|
||||
RtReadWBack = RtRead | WBack,
|
||||
Rt2ReadWBack = Rt2 | RtReadWBack,
|
||||
RlistWBack = Rlist | WBack,
|
||||
RlistRead = Rlist | ReadRd,
|
||||
RlistReadWBack = Rlist | ReadRd | WBack,
|
||||
|
||||
CondRd = Cond | Rd,
|
||||
CondRdLoHi = Cond | Rd | RdHi,
|
||||
CondRt = Cond | Rt,
|
||||
CondRt2 = Cond | Rt | Rt2,
|
||||
CondRd16 = Cond | Rd | Rd16,
|
||||
CondWBack = Cond | WBack,
|
||||
CondRdRtRead = Cond | Rd | RtRead,
|
||||
CondRdRt2Read = Cond | Rd | Rt2 | RtRead,
|
||||
CondRtWBack = Cond | RtWBack,
|
||||
CondRt2WBack = Cond | Rt2 | RtWBack,
|
||||
CondRtRead = Cond | RtRead,
|
||||
CondRt2Read = Cond | Rt2 | RtRead,
|
||||
CondRtReadWBack = Cond | RtReadWBack,
|
||||
CondRt2ReadWBack = Cond | Rt2 | RtReadWBack,
|
||||
CondRlist = Cond | Rlist,
|
||||
CondRlistWBack = Cond | Rlist | WBack,
|
||||
CondRlistRead = Cond | Rlist | ReadRd,
|
||||
CondRlistReadWBack = Cond | Rlist | ReadRd | WBack,
|
||||
}
|
||||
}
|
20
src/Ryujinx.Cpu/LightningJit/Arm32/InstInfo.cs
Normal file
20
src/Ryujinx.Cpu/LightningJit/Arm32/InstInfo.cs
Normal file
@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
readonly struct InstInfo
|
||||
{
|
||||
public readonly uint Encoding;
|
||||
public readonly InstName Name;
|
||||
public readonly Action<CodeGenContext, uint> EmitFunc;
|
||||
public readonly InstFlags Flags;
|
||||
|
||||
public InstInfo(uint encoding, InstName name, Action<CodeGenContext, uint> emitFunc, InstFlags flags)
|
||||
{
|
||||
Encoding = encoding;
|
||||
Name = name;
|
||||
EmitFunc = emitFunc;
|
||||
Flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
79
src/Ryujinx.Cpu/LightningJit/Arm32/InstInfoForTable.cs
Normal file
79
src/Ryujinx.Cpu/LightningJit/Arm32/InstInfoForTable.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using Ryujinx.Cpu.LightningJit.Table;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
readonly struct InstInfoForTable : IInstInfo
|
||||
{
|
||||
public uint Encoding { get; }
|
||||
public uint EncodingMask { get; }
|
||||
public InstEncoding[] Constraints { get; }
|
||||
public InstMeta Meta { get; }
|
||||
public IsaVersion Version => Meta.Version;
|
||||
public IsaFeature Feature => Meta.Feature;
|
||||
|
||||
public InstInfoForTable(
|
||||
uint encoding,
|
||||
uint encodingMask,
|
||||
InstEncoding[] constraints,
|
||||
InstName name,
|
||||
Action<CodeGenContext, uint> emitFunc,
|
||||
IsaVersion isaVersion,
|
||||
IsaFeature isaFeature,
|
||||
InstFlags flags)
|
||||
{
|
||||
Encoding = encoding;
|
||||
EncodingMask = encodingMask;
|
||||
Constraints = constraints;
|
||||
Meta = new(name, emitFunc, isaVersion, isaFeature, flags);
|
||||
}
|
||||
|
||||
public InstInfoForTable(
|
||||
uint encoding,
|
||||
uint encodingMask,
|
||||
InstEncoding[] constraints,
|
||||
InstName name,
|
||||
Action<CodeGenContext, uint> emitFunc,
|
||||
IsaVersion isaVersion,
|
||||
InstFlags flags) : this(encoding, encodingMask, constraints, name, emitFunc, isaVersion, IsaFeature.None, flags)
|
||||
{
|
||||
}
|
||||
|
||||
public InstInfoForTable(
|
||||
uint encoding,
|
||||
uint encodingMask,
|
||||
InstName name,
|
||||
Action<CodeGenContext, uint> emitFunc,
|
||||
IsaVersion isaVersion,
|
||||
IsaFeature isaFeature,
|
||||
InstFlags flags) : this(encoding, encodingMask, null, name, emitFunc, isaVersion, isaFeature, flags)
|
||||
{
|
||||
}
|
||||
|
||||
public InstInfoForTable(
|
||||
uint encoding,
|
||||
uint encodingMask,
|
||||
InstName name,
|
||||
Action<CodeGenContext, uint> emitFunc,
|
||||
IsaVersion isaVersion,
|
||||
InstFlags flags) : this(encoding, encodingMask, null, name, emitFunc, isaVersion, IsaFeature.None, flags)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsConstrained(uint encoding)
|
||||
{
|
||||
if (Constraints != null)
|
||||
{
|
||||
foreach (InstEncoding constraint in Constraints)
|
||||
{
|
||||
if ((encoding & constraint.EncodingMask) == constraint.Encoding)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
22
src/Ryujinx.Cpu/LightningJit/Arm32/InstMeta.cs
Normal file
22
src/Ryujinx.Cpu/LightningJit/Arm32/InstMeta.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
readonly struct InstMeta
|
||||
{
|
||||
public readonly InstName Name;
|
||||
public readonly Action<CodeGenContext, uint> EmitFunc;
|
||||
public readonly IsaVersion Version;
|
||||
public readonly IsaFeature Feature;
|
||||
public readonly InstFlags Flags;
|
||||
|
||||
public InstMeta(InstName name, Action<CodeGenContext, uint> emitFunc, IsaVersion isaVersion, IsaFeature isaFeature, InstFlags flags)
|
||||
{
|
||||
Name = name;
|
||||
EmitFunc = emitFunc;
|
||||
Version = isaVersion;
|
||||
Feature = isaFeature;
|
||||
Flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
562
src/Ryujinx.Cpu/LightningJit/Arm32/InstName.cs
Normal file
562
src/Ryujinx.Cpu/LightningJit/Arm32/InstName.cs
Normal file
@ -0,0 +1,562 @@
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
enum InstName
|
||||
{
|
||||
AdcI,
|
||||
AdcR,
|
||||
AdcRr,
|
||||
AddI,
|
||||
AddR,
|
||||
AddRr,
|
||||
AddSpI,
|
||||
AddSpR,
|
||||
Adr,
|
||||
Aesd,
|
||||
Aese,
|
||||
Aesimc,
|
||||
Aesmc,
|
||||
AndI,
|
||||
AndR,
|
||||
AndRr,
|
||||
B,
|
||||
Bfc,
|
||||
Bfi,
|
||||
BicI,
|
||||
BicR,
|
||||
BicRr,
|
||||
Bkpt,
|
||||
BlxR,
|
||||
BlI,
|
||||
Bx,
|
||||
Bxj,
|
||||
Cbnz,
|
||||
Clrbhb,
|
||||
Clrex,
|
||||
Clz,
|
||||
CmnI,
|
||||
CmnR,
|
||||
CmnRr,
|
||||
CmpI,
|
||||
CmpR,
|
||||
CmpRr,
|
||||
Cps,
|
||||
Crc32,
|
||||
Crc32c,
|
||||
Csdb,
|
||||
Dbg,
|
||||
Dcps1,
|
||||
Dcps2,
|
||||
Dcps3,
|
||||
Dmb,
|
||||
Dsb,
|
||||
EorI,
|
||||
EorR,
|
||||
EorRr,
|
||||
Eret,
|
||||
Esb,
|
||||
Fldmx,
|
||||
Fstmx,
|
||||
Hlt,
|
||||
Hvc,
|
||||
Isb,
|
||||
It,
|
||||
Lda,
|
||||
Ldab,
|
||||
Ldaex,
|
||||
Ldaexb,
|
||||
Ldaexd,
|
||||
Ldaexh,
|
||||
Ldah,
|
||||
LdcI,
|
||||
LdcL,
|
||||
Ldm,
|
||||
Ldmda,
|
||||
Ldmdb,
|
||||
Ldmib,
|
||||
LdmE,
|
||||
LdmU,
|
||||
Ldrbt,
|
||||
LdrbI,
|
||||
LdrbL,
|
||||
LdrbR,
|
||||
LdrdI,
|
||||
LdrdL,
|
||||
LdrdR,
|
||||
Ldrex,
|
||||
Ldrexb,
|
||||
Ldrexd,
|
||||
Ldrexh,
|
||||
Ldrht,
|
||||
LdrhI,
|
||||
LdrhL,
|
||||
LdrhR,
|
||||
Ldrsbt,
|
||||
LdrsbI,
|
||||
LdrsbL,
|
||||
LdrsbR,
|
||||
Ldrsht,
|
||||
LdrshI,
|
||||
LdrshL,
|
||||
LdrshR,
|
||||
Ldrt,
|
||||
LdrI,
|
||||
LdrL,
|
||||
LdrR,
|
||||
Mcr,
|
||||
Mcrr,
|
||||
Mla,
|
||||
Mls,
|
||||
Movt,
|
||||
MovI,
|
||||
MovR,
|
||||
MovRr,
|
||||
Mrc,
|
||||
Mrrc,
|
||||
Mrs,
|
||||
MrsBr,
|
||||
MsrBr,
|
||||
MsrI,
|
||||
MsrR,
|
||||
Mul,
|
||||
MvnI,
|
||||
MvnR,
|
||||
MvnRr,
|
||||
Nop,
|
||||
OrnI,
|
||||
OrnR,
|
||||
OrrI,
|
||||
OrrR,
|
||||
OrrRr,
|
||||
Pkh,
|
||||
PldI,
|
||||
PldL,
|
||||
PldR,
|
||||
PliI,
|
||||
PliR,
|
||||
Pop,
|
||||
Pssbb,
|
||||
Push,
|
||||
Qadd,
|
||||
Qadd16,
|
||||
Qadd8,
|
||||
Qasx,
|
||||
Qdadd,
|
||||
Qdsub,
|
||||
Qsax,
|
||||
Qsub,
|
||||
Qsub16,
|
||||
Qsub8,
|
||||
Rbit,
|
||||
Rev,
|
||||
Rev16,
|
||||
Revsh,
|
||||
Rfe,
|
||||
RsbI,
|
||||
RsbR,
|
||||
RsbRr,
|
||||
RscI,
|
||||
RscR,
|
||||
RscRr,
|
||||
Sadd16,
|
||||
Sadd8,
|
||||
Sasx,
|
||||
Sb,
|
||||
SbcI,
|
||||
SbcR,
|
||||
SbcRr,
|
||||
Sbfx,
|
||||
Sdiv,
|
||||
Sel,
|
||||
Setend,
|
||||
Setpan,
|
||||
Sev,
|
||||
Sevl,
|
||||
Sha1c,
|
||||
Sha1h,
|
||||
Sha1m,
|
||||
Sha1p,
|
||||
Sha1su0,
|
||||
Sha1su1,
|
||||
Sha256h,
|
||||
Sha256h2,
|
||||
Sha256su0,
|
||||
Sha256su1,
|
||||
Shadd16,
|
||||
Shadd8,
|
||||
Shasx,
|
||||
Shsax,
|
||||
Shsub16,
|
||||
Shsub8,
|
||||
Smc,
|
||||
Smlabb,
|
||||
Smlad,
|
||||
Smlal,
|
||||
Smlalbb,
|
||||
Smlald,
|
||||
Smlawb,
|
||||
Smlsd,
|
||||
Smlsld,
|
||||
Smmla,
|
||||
Smmls,
|
||||
Smmul,
|
||||
Smuad,
|
||||
Smulbb,
|
||||
Smull,
|
||||
Smulwb,
|
||||
Smusd,
|
||||
Srs,
|
||||
Ssat,
|
||||
Ssat16,
|
||||
Ssax,
|
||||
Ssbb,
|
||||
Ssub16,
|
||||
Ssub8,
|
||||
Stc,
|
||||
Stl,
|
||||
Stlb,
|
||||
Stlex,
|
||||
Stlexb,
|
||||
Stlexd,
|
||||
Stlexh,
|
||||
Stlh,
|
||||
Stm,
|
||||
Stmda,
|
||||
Stmdb,
|
||||
Stmib,
|
||||
StmU,
|
||||
Strbt,
|
||||
StrbI,
|
||||
StrbR,
|
||||
StrdI,
|
||||
StrdR,
|
||||
Strex,
|
||||
Strexb,
|
||||
Strexd,
|
||||
Strexh,
|
||||
Strht,
|
||||
StrhI,
|
||||
StrhR,
|
||||
Strt,
|
||||
StrI,
|
||||
StrR,
|
||||
SubI,
|
||||
SubR,
|
||||
SubRr,
|
||||
SubSpI,
|
||||
SubSpR,
|
||||
Svc,
|
||||
Sxtab,
|
||||
Sxtab16,
|
||||
Sxtah,
|
||||
Sxtb,
|
||||
Sxtb16,
|
||||
Sxth,
|
||||
Tbb,
|
||||
TeqI,
|
||||
TeqR,
|
||||
TeqRr,
|
||||
Tsb,
|
||||
TstI,
|
||||
TstR,
|
||||
TstRr,
|
||||
Uadd16,
|
||||
Uadd8,
|
||||
Uasx,
|
||||
Ubfx,
|
||||
Udf,
|
||||
Udiv,
|
||||
Uhadd16,
|
||||
Uhadd8,
|
||||
Uhasx,
|
||||
Uhsax,
|
||||
Uhsub16,
|
||||
Uhsub8,
|
||||
Umaal,
|
||||
Umlal,
|
||||
Umull,
|
||||
Uqadd16,
|
||||
Uqadd8,
|
||||
Uqasx,
|
||||
Uqsax,
|
||||
Uqsub16,
|
||||
Uqsub8,
|
||||
Usad8,
|
||||
Usada8,
|
||||
Usat,
|
||||
Usat16,
|
||||
Usax,
|
||||
Usub16,
|
||||
Usub8,
|
||||
Uxtab,
|
||||
Uxtab16,
|
||||
Uxtah,
|
||||
Uxtb,
|
||||
Uxtb16,
|
||||
Uxth,
|
||||
Vaba,
|
||||
Vabal,
|
||||
VabdlI,
|
||||
VabdF,
|
||||
VabdI,
|
||||
Vabs,
|
||||
Vacge,
|
||||
Vacgt,
|
||||
Vaddhn,
|
||||
Vaddl,
|
||||
Vaddw,
|
||||
VaddF,
|
||||
VaddI,
|
||||
VandR,
|
||||
VbicI,
|
||||
VbicR,
|
||||
Vbif,
|
||||
Vbit,
|
||||
Vbsl,
|
||||
Vcadd,
|
||||
VceqI,
|
||||
VceqR,
|
||||
VcgeI,
|
||||
VcgeR,
|
||||
VcgtI,
|
||||
VcgtR,
|
||||
VcleI,
|
||||
Vcls,
|
||||
VcltI,
|
||||
Vclz,
|
||||
Vcmla,
|
||||
VcmlaS,
|
||||
Vcmp,
|
||||
Vcmpe,
|
||||
Vcnt,
|
||||
VcvtaAsimd,
|
||||
VcvtaVfp,
|
||||
Vcvtb,
|
||||
VcvtbBfs,
|
||||
VcvtmAsimd,
|
||||
VcvtmVfp,
|
||||
VcvtnAsimd,
|
||||
VcvtnVfp,
|
||||
VcvtpAsimd,
|
||||
VcvtpVfp,
|
||||
VcvtrIv,
|
||||
Vcvtt,
|
||||
VcvttBfs,
|
||||
VcvtBfs,
|
||||
VcvtDs,
|
||||
VcvtHs,
|
||||
VcvtIs,
|
||||
VcvtIv,
|
||||
VcvtVi,
|
||||
VcvtXs,
|
||||
VcvtXv,
|
||||
Vdiv,
|
||||
Vdot,
|
||||
VdotS,
|
||||
VdupR,
|
||||
VdupS,
|
||||
Veor,
|
||||
Vext,
|
||||
Vfma,
|
||||
Vfmal,
|
||||
VfmalS,
|
||||
VfmaBf,
|
||||
VfmaBfs,
|
||||
Vfms,
|
||||
Vfmsl,
|
||||
VfmslS,
|
||||
Vfnma,
|
||||
Vfnms,
|
||||
Vhadd,
|
||||
Vhsub,
|
||||
Vins,
|
||||
Vjcvt,
|
||||
Vld11,
|
||||
Vld1A,
|
||||
Vld1M,
|
||||
Vld21,
|
||||
Vld2A,
|
||||
Vld2M,
|
||||
Vld31,
|
||||
Vld3A,
|
||||
Vld3M,
|
||||
Vld41,
|
||||
Vld4A,
|
||||
Vld4M,
|
||||
Vldm,
|
||||
VldrI,
|
||||
VldrL,
|
||||
Vmaxnm,
|
||||
VmaxF,
|
||||
VmaxI,
|
||||
Vminnm,
|
||||
VminF,
|
||||
VminI,
|
||||
VmlalI,
|
||||
VmlalS,
|
||||
VmlaF,
|
||||
VmlaI,
|
||||
VmlaS,
|
||||
VmlslI,
|
||||
VmlslS,
|
||||
VmlsF,
|
||||
VmlsI,
|
||||
VmlsS,
|
||||
Vmmla,
|
||||
Vmovl,
|
||||
Vmovn,
|
||||
Vmovx,
|
||||
VmovD,
|
||||
VmovH,
|
||||
VmovI,
|
||||
VmovR,
|
||||
VmovRs,
|
||||
VmovS,
|
||||
VmovSr,
|
||||
VmovSs,
|
||||
Vmrs,
|
||||
Vmsr,
|
||||
VmullI,
|
||||
VmullS,
|
||||
VmulF,
|
||||
VmulI,
|
||||
VmulS,
|
||||
VmvnI,
|
||||
VmvnR,
|
||||
Vneg,
|
||||
Vnmla,
|
||||
Vnmls,
|
||||
Vnmul,
|
||||
VornR,
|
||||
VorrI,
|
||||
VorrR,
|
||||
Vpadal,
|
||||
Vpaddl,
|
||||
VpaddF,
|
||||
VpaddI,
|
||||
VpmaxF,
|
||||
VpmaxI,
|
||||
VpminF,
|
||||
VpminI,
|
||||
Vqabs,
|
||||
Vqadd,
|
||||
Vqdmlal,
|
||||
Vqdmlsl,
|
||||
Vqdmulh,
|
||||
Vqdmull,
|
||||
Vqmovn,
|
||||
Vqneg,
|
||||
Vqrdmlah,
|
||||
Vqrdmlsh,
|
||||
Vqrdmulh,
|
||||
Vqrshl,
|
||||
Vqrshrn,
|
||||
VqshlI,
|
||||
VqshlR,
|
||||
Vqshrn,
|
||||
Vqsub,
|
||||
Vraddhn,
|
||||
Vrecpe,
|
||||
Vrecps,
|
||||
Vrev16,
|
||||
Vrev32,
|
||||
Vrev64,
|
||||
Vrhadd,
|
||||
VrintaAsimd,
|
||||
VrintaVfp,
|
||||
VrintmAsimd,
|
||||
VrintmVfp,
|
||||
VrintnAsimd,
|
||||
VrintnVfp,
|
||||
VrintpAsimd,
|
||||
VrintpVfp,
|
||||
VrintrVfp,
|
||||
VrintxAsimd,
|
||||
VrintxVfp,
|
||||
VrintzAsimd,
|
||||
VrintzVfp,
|
||||
Vrshl,
|
||||
Vrshr,
|
||||
Vrshrn,
|
||||
Vrsqrte,
|
||||
Vrsqrts,
|
||||
Vrsra,
|
||||
Vrsubhn,
|
||||
Vsdot,
|
||||
VsdotS,
|
||||
Vsel,
|
||||
Vshll,
|
||||
VshlI,
|
||||
VshlR,
|
||||
Vshr,
|
||||
Vshrn,
|
||||
Vsli,
|
||||
Vsmmla,
|
||||
Vsqrt,
|
||||
Vsra,
|
||||
Vsri,
|
||||
Vst11,
|
||||
Vst1M,
|
||||
Vst21,
|
||||
Vst2M,
|
||||
Vst31,
|
||||
Vst3M,
|
||||
Vst41,
|
||||
Vst4M,
|
||||
Vstm,
|
||||
Vstr,
|
||||
Vsubhn,
|
||||
Vsubl,
|
||||
Vsubw,
|
||||
VsubF,
|
||||
VsubI,
|
||||
VsudotS,
|
||||
Vswp,
|
||||
Vtbl,
|
||||
Vtrn,
|
||||
Vtst,
|
||||
Vudot,
|
||||
VudotS,
|
||||
Vummla,
|
||||
Vusdot,
|
||||
VusdotS,
|
||||
Vusmmla,
|
||||
Vuzp,
|
||||
Vzip,
|
||||
Wfe,
|
||||
Wfi,
|
||||
Yield,
|
||||
}
|
||||
|
||||
static class InstNameExtensions
|
||||
{
|
||||
public static bool IsCall(this InstName name)
|
||||
{
|
||||
return name == InstName.BlI || name == InstName.BlxR;
|
||||
}
|
||||
|
||||
public static bool IsSystem(this InstName name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case InstName.Mcr:
|
||||
case InstName.Mcrr:
|
||||
case InstName.Mrc:
|
||||
case InstName.Mrs:
|
||||
case InstName.MrsBr:
|
||||
case InstName.MsrBr:
|
||||
case InstName.MsrI:
|
||||
case InstName.MsrR:
|
||||
case InstName.Mrrc:
|
||||
case InstName.Svc:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsSystemOrCall(this InstName name)
|
||||
{
|
||||
return name.IsSystem() || name.IsCall();
|
||||
}
|
||||
}
|
||||
}
|
1194
src/Ryujinx.Cpu/LightningJit/Arm32/InstTableA32.cs
Normal file
1194
src/Ryujinx.Cpu/LightningJit/Arm32/InstTableA32.cs
Normal file
File diff suppressed because it is too large
Load Diff
146
src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT16.cs
Normal file
146
src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT16.cs
Normal file
@ -0,0 +1,146 @@
|
||||
using Ryujinx.Cpu.LightningJit.Table;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
static class InstTableT16<T> where T : IInstEmit
|
||||
{
|
||||
private static readonly InstTableLevel<InstInfoForTable> _table;
|
||||
|
||||
static InstTableT16()
|
||||
{
|
||||
InstEncoding[] rmRdndnConstraints = new InstEncoding[]
|
||||
{
|
||||
new(0x00680000, 0x00780000),
|
||||
new(0x00850000, 0x00870000),
|
||||
};
|
||||
|
||||
InstEncoding[] rmConstraints = new InstEncoding[]
|
||||
{
|
||||
new(0x00680000, 0x00780000),
|
||||
};
|
||||
|
||||
InstEncoding[] condCondConstraints = new InstEncoding[]
|
||||
{
|
||||
new(0x0E000000, 0x0F000000),
|
||||
new(0x0F000000, 0x0F000000),
|
||||
};
|
||||
|
||||
InstEncoding[] maskConstraints = new InstEncoding[]
|
||||
{
|
||||
new(0x00000000, 0x000F0000),
|
||||
};
|
||||
|
||||
InstEncoding[] opConstraints = new InstEncoding[]
|
||||
{
|
||||
new(0x18000000, 0x18000000),
|
||||
};
|
||||
|
||||
InstEncoding[] opOpOpOpConstraints = new InstEncoding[]
|
||||
{
|
||||
new(0x00000000, 0x03C00000),
|
||||
new(0x00400000, 0x03C00000),
|
||||
new(0x01400000, 0x03C00000),
|
||||
new(0x01800000, 0x03C00000),
|
||||
};
|
||||
|
||||
List<InstInfoForTable> insts = new()
|
||||
{
|
||||
new(0x41400000, 0xFFC00000, InstName.AdcR, T.AdcRT1, IsaVersion.v80, InstFlags.Rdn),
|
||||
new(0x1C000000, 0xFE000000, InstName.AddI, T.AddIT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0x30000000, 0xF8000000, InstName.AddI, T.AddIT2, IsaVersion.v80, InstFlags.Rdn),
|
||||
new(0x18000000, 0xFE000000, InstName.AddR, T.AddRT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0x44000000, 0xFF000000, rmRdndnConstraints, InstName.AddR, T.AddRT2, IsaVersion.v80, InstFlags.RdnDn),
|
||||
new(0xA8000000, 0xF8000000, InstName.AddSpI, T.AddSpIT1, IsaVersion.v80, InstFlags.RdRd16),
|
||||
new(0xB0000000, 0xFF800000, InstName.AddSpI, T.AddSpIT2, IsaVersion.v80, InstFlags.None),
|
||||
new(0x44680000, 0xFF780000, InstName.AddSpR, T.AddSpRT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x44850000, 0xFF870000, rmConstraints, InstName.AddSpR, T.AddSpRT2, IsaVersion.v80, InstFlags.None),
|
||||
new(0xA0000000, 0xF8000000, InstName.Adr, T.AdrT1, IsaVersion.v80, InstFlags.RdRd16),
|
||||
new(0x40000000, 0xFFC00000, InstName.AndR, T.AndRT1, IsaVersion.v80, InstFlags.Rdn),
|
||||
new(0xD0000000, 0xF0000000, condCondConstraints, InstName.B, T.BT1, IsaVersion.v80, InstFlags.Cond),
|
||||
new(0xE0000000, 0xF8000000, InstName.B, T.BT2, IsaVersion.v80, InstFlags.None),
|
||||
new(0x43800000, 0xFFC00000, InstName.BicR, T.BicRT1, IsaVersion.v80, InstFlags.Rdn),
|
||||
new(0xBE000000, 0xFF000000, InstName.Bkpt, T.BkptT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x47800000, 0xFF870000, InstName.BlxR, T.BlxRT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x47000000, 0xFF870000, InstName.Bx, T.BxT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xB1000000, 0xF5000000, InstName.Cbnz, T.CbnzT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x42C00000, 0xFFC00000, InstName.CmnR, T.CmnRT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x28000000, 0xF8000000, InstName.CmpI, T.CmpIT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x42800000, 0xFFC00000, InstName.CmpR, T.CmpRT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x45000000, 0xFF000000, InstName.CmpR, T.CmpRT2, IsaVersion.v80, InstFlags.None),
|
||||
new(0xB6600000, 0xFFE80000, InstName.Cps, T.CpsT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x40400000, 0xFFC00000, InstName.EorR, T.EorRT1, IsaVersion.v80, InstFlags.Rdn),
|
||||
new(0xBA800000, 0xFFC00000, InstName.Hlt, T.HltT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xBF000000, 0xFF000000, maskConstraints, InstName.It, T.ItT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xC8000000, 0xF8000000, InstName.Ldm, T.LdmT1, IsaVersion.v80, InstFlags.Rlist),
|
||||
new(0x78000000, 0xF8000000, InstName.LdrbI, T.LdrbIT1, IsaVersion.v80, InstFlags.Rt),
|
||||
new(0x5C000000, 0xFE000000, InstName.LdrbR, T.LdrbRT1, IsaVersion.v80, InstFlags.Rt),
|
||||
new(0x88000000, 0xF8000000, InstName.LdrhI, T.LdrhIT1, IsaVersion.v80, InstFlags.Rt),
|
||||
new(0x5A000000, 0xFE000000, InstName.LdrhR, T.LdrhRT1, IsaVersion.v80, InstFlags.Rt),
|
||||
new(0x56000000, 0xFE000000, InstName.LdrsbR, T.LdrsbRT1, IsaVersion.v80, InstFlags.Rt),
|
||||
new(0x5E000000, 0xFE000000, InstName.LdrshR, T.LdrshRT1, IsaVersion.v80, InstFlags.Rt),
|
||||
new(0x68000000, 0xF8000000, InstName.LdrI, T.LdrIT1, IsaVersion.v80, InstFlags.Rt),
|
||||
new(0x98000000, 0xF8000000, InstName.LdrI, T.LdrIT2, IsaVersion.v80, InstFlags.RtRd16),
|
||||
new(0x48000000, 0xF8000000, InstName.LdrL, T.LdrLT1, IsaVersion.v80, InstFlags.RtRd16),
|
||||
new(0x58000000, 0xFE000000, InstName.LdrR, T.LdrRT1, IsaVersion.v80, InstFlags.Rt),
|
||||
new(0x20000000, 0xF8000000, InstName.MovI, T.MovIT1, IsaVersion.v80, InstFlags.RdRd16),
|
||||
new(0x46000000, 0xFF000000, InstName.MovR, T.MovRT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0x00000000, 0xE0000000, opConstraints, InstName.MovR, T.MovRT2, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0x40000000, 0xFE000000, opOpOpOpConstraints, InstName.MovRr, T.MovRrT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x43400000, 0xFFC00000, InstName.Mul, T.MulT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x43C00000, 0xFFC00000, InstName.MvnR, T.MvnRT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0xBF000000, 0xFFFF0000, InstName.Nop, T.NopT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0x43000000, 0xFFC00000, InstName.OrrR, T.OrrRT1, IsaVersion.v80, InstFlags.Rdn),
|
||||
new(0xBC000000, 0xFE000000, InstName.Pop, T.PopT1, IsaVersion.v80, InstFlags.Rlist),
|
||||
new(0xB4000000, 0xFE000000, InstName.Push, T.PushT1, IsaVersion.v80, InstFlags.RlistRead),
|
||||
new(0xBA000000, 0xFFC00000, InstName.Rev, T.RevT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0xBA400000, 0xFFC00000, InstName.Rev16, T.Rev16T1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0xBAC00000, 0xFFC00000, InstName.Revsh, T.RevshT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0x42400000, 0xFFC00000, InstName.RsbI, T.RsbIT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0x41800000, 0xFFC00000, InstName.SbcR, T.SbcRT1, IsaVersion.v80, InstFlags.Rdn),
|
||||
new(0xB6500000, 0xFFF70000, InstName.Setend, T.SetendT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xB6100000, 0xFFF70000, InstName.Setpan, T.SetpanT1, IsaVersion.v81, IsaFeature.FeatPan, InstFlags.None),
|
||||
new(0xBF400000, 0xFFFF0000, InstName.Sev, T.SevT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xBF500000, 0xFFFF0000, InstName.Sevl, T.SevlT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xC0000000, 0xF8000000, InstName.Stm, T.StmT1, IsaVersion.v80, InstFlags.RlistRead),
|
||||
new(0x70000000, 0xF8000000, InstName.StrbI, T.StrbIT1, IsaVersion.v80, InstFlags.RtRead),
|
||||
new(0x54000000, 0xFE000000, InstName.StrbR, T.StrbRT1, IsaVersion.v80, InstFlags.RtRead),
|
||||
new(0x80000000, 0xF8000000, InstName.StrhI, T.StrhIT1, IsaVersion.v80, InstFlags.RtRead),
|
||||
new(0x52000000, 0xFE000000, InstName.StrhR, T.StrhRT1, IsaVersion.v80, InstFlags.RtRead),
|
||||
new(0x60000000, 0xF8000000, InstName.StrI, T.StrIT1, IsaVersion.v80, InstFlags.RtRead),
|
||||
new(0x90000000, 0xF8000000, InstName.StrI, T.StrIT2, IsaVersion.v80, InstFlags.RtReadRd16),
|
||||
new(0x50000000, 0xFE000000, InstName.StrR, T.StrRT1, IsaVersion.v80, InstFlags.RtRead),
|
||||
new(0x1E000000, 0xFE000000, InstName.SubI, T.SubIT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0x38000000, 0xF8000000, InstName.SubI, T.SubIT2, IsaVersion.v80, InstFlags.Rdn),
|
||||
new(0x1A000000, 0xFE000000, InstName.SubR, T.SubRT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0xB0800000, 0xFF800000, InstName.SubSpI, T.SubSpIT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xDF000000, 0xFF000000, InstName.Svc, T.SvcT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xB2400000, 0xFFC00000, InstName.Sxtb, T.SxtbT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0xB2000000, 0xFFC00000, InstName.Sxth, T.SxthT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0x42000000, 0xFFC00000, InstName.TstR, T.TstRT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xDE000000, 0xFF000000, InstName.Udf, T.UdfT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xB2C00000, 0xFFC00000, InstName.Uxtb, T.UxtbT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0xB2800000, 0xFFC00000, InstName.Uxth, T.UxthT1, IsaVersion.v80, InstFlags.Rd),
|
||||
new(0xBF200000, 0xFFFF0000, InstName.Wfe, T.WfeT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xBF300000, 0xFFFF0000, InstName.Wfi, T.WfiT1, IsaVersion.v80, InstFlags.None),
|
||||
new(0xBF100000, 0xFFFF0000, InstName.Yield, T.YieldT1, IsaVersion.v80, InstFlags.None),
|
||||
};
|
||||
|
||||
_table = new(insts);
|
||||
}
|
||||
|
||||
public static bool TryGetMeta(uint encoding, IsaVersion version, IsaFeature features, out InstMeta meta)
|
||||
{
|
||||
if (_table.TryFind(encoding, version, features, out InstInfoForTable info))
|
||||
{
|
||||
meta = info.Meta;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
meta = new(InstName.Udf, T.UdfA1, IsaVersion.v80, IsaFeature.None, InstFlags.None);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
1212
src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT32.cs
Normal file
1212
src/Ryujinx.Cpu/LightningJit/Arm32/InstTableT32.cs
Normal file
File diff suppressed because it is too large
Load Diff
34
src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs
Normal file
34
src/Ryujinx.Cpu/LightningJit/Arm32/MultiBlock.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
class MultiBlock
|
||||
{
|
||||
public readonly List<Block> Blocks;
|
||||
public readonly bool HasHostCall;
|
||||
public readonly bool HasHostCallSkipContext;
|
||||
public readonly bool IsTruncated;
|
||||
|
||||
public MultiBlock(List<Block> blocks)
|
||||
{
|
||||
Blocks = blocks;
|
||||
|
||||
Block block = blocks[0];
|
||||
|
||||
HasHostCall = block.HasHostCall;
|
||||
HasHostCallSkipContext = block.HasHostCallSkipContext;
|
||||
|
||||
for (int index = 1; index < blocks.Count; index++)
|
||||
{
|
||||
block = blocks[index];
|
||||
|
||||
HasHostCall |= block.HasHostCall;
|
||||
HasHostCallSkipContext |= block.HasHostCallSkipContext;
|
||||
}
|
||||
|
||||
block = blocks[^1];
|
||||
|
||||
IsTruncated = block.IsTruncated;
|
||||
}
|
||||
}
|
||||
}
|
20
src/Ryujinx.Cpu/LightningJit/Arm32/PendingBranch.cs
Normal file
20
src/Ryujinx.Cpu/LightningJit/Arm32/PendingBranch.cs
Normal file
@ -0,0 +1,20 @@
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
readonly struct PendingBranch
|
||||
{
|
||||
public readonly BranchType BranchType;
|
||||
public readonly uint TargetAddress;
|
||||
public readonly uint NextAddress;
|
||||
public readonly InstName Name;
|
||||
public readonly int WriterPointer;
|
||||
|
||||
public PendingBranch(BranchType branchType, uint targetAddress, uint nextAddress, InstName name, int writerPointer)
|
||||
{
|
||||
BranchType = branchType;
|
||||
TargetAddress = targetAddress;
|
||||
NextAddress = nextAddress;
|
||||
Name = name;
|
||||
WriterPointer = writerPointer;
|
||||
}
|
||||
}
|
||||
}
|
170
src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs
Normal file
170
src/Ryujinx.Cpu/LightningJit/Arm32/RegisterAllocator.cs
Normal file
@ -0,0 +1,170 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
class RegisterAllocator
|
||||
{
|
||||
public const int MaxTemps = 1;
|
||||
|
||||
private uint _gprMask;
|
||||
private uint _fpSimdMask;
|
||||
|
||||
public int FixedContextRegister { get; }
|
||||
public int FixedPageTableRegister { get; }
|
||||
|
||||
public uint UsedGprsMask { get; private set; }
|
||||
public uint UsedFpSimdMask { get; private set; }
|
||||
|
||||
public RegisterAllocator()
|
||||
{
|
||||
_gprMask = ushort.MaxValue;
|
||||
_fpSimdMask = ushort.MaxValue;
|
||||
|
||||
FixedContextRegister = AllocateTempRegisterWithPreferencing();
|
||||
FixedPageTableRegister = AllocateTempRegisterWithPreferencing();
|
||||
}
|
||||
|
||||
public void MarkGprAsUsed(int index)
|
||||
{
|
||||
UsedGprsMask |= 1u << index;
|
||||
}
|
||||
|
||||
public void MarkFpSimdAsUsed(int index)
|
||||
{
|
||||
UsedFpSimdMask |= 1u << index;
|
||||
}
|
||||
|
||||
public void MarkFpSimdRangeAsUsed(int index, int count)
|
||||
{
|
||||
UsedFpSimdMask |= (uint.MaxValue >> (32 - count)) << index;
|
||||
}
|
||||
|
||||
public Operand RemapGprRegister(int index)
|
||||
{
|
||||
MarkGprAsUsed(index);
|
||||
|
||||
return new Operand(OperandKind.Register, OperandType.I32, (ulong)index);
|
||||
}
|
||||
|
||||
public Operand RemapFpRegister(int index, bool isFP32)
|
||||
{
|
||||
MarkFpSimdAsUsed(index);
|
||||
|
||||
return new Operand(OperandKind.Register, isFP32 ? OperandType.FP32 : OperandType.FP64, (ulong)index);
|
||||
}
|
||||
|
||||
public Operand RemapSimdRegister(int index)
|
||||
{
|
||||
MarkFpSimdAsUsed(index);
|
||||
|
||||
return new Operand(OperandKind.Register, OperandType.V128, (ulong)index);
|
||||
}
|
||||
|
||||
public Operand RemapSimdRegister(int index, int count)
|
||||
{
|
||||
MarkFpSimdRangeAsUsed(index, count);
|
||||
|
||||
return new Operand(OperandKind.Register, OperandType.V128, (ulong)index);
|
||||
}
|
||||
|
||||
public void EnsureTempGprRegisters(int count)
|
||||
{
|
||||
if (count != 0)
|
||||
{
|
||||
Span<int> registers = stackalloc int[count];
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
registers[index] = AllocateTempGprRegister();
|
||||
}
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
FreeTempGprRegister(registers[index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int AllocateTempGprRegister()
|
||||
{
|
||||
int index = AllocateTempRegister(ref _gprMask, AbiConstants.ReservedRegsMask);
|
||||
|
||||
MarkGprAsUsed(index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private int AllocateTempRegisterWithPreferencing()
|
||||
{
|
||||
int firstCalleeSaved = BitOperations.TrailingZeroCount(~_gprMask & AbiConstants.GprCalleeSavedRegsMask);
|
||||
if (firstCalleeSaved < 32)
|
||||
{
|
||||
uint regMask = 1u << firstCalleeSaved;
|
||||
if ((regMask & AbiConstants.ReservedRegsMask) == 0)
|
||||
{
|
||||
_gprMask |= regMask;
|
||||
UsedGprsMask |= regMask;
|
||||
|
||||
return firstCalleeSaved;
|
||||
}
|
||||
}
|
||||
|
||||
return AllocateTempRegister(ref _gprMask, AbiConstants.ReservedRegsMask);
|
||||
}
|
||||
|
||||
public int AllocateTempFpSimdRegister()
|
||||
{
|
||||
int index = AllocateTempRegister(ref _fpSimdMask, 0);
|
||||
|
||||
MarkFpSimdAsUsed(index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public ScopedRegister AllocateTempGprRegisterScoped()
|
||||
{
|
||||
return new(this, new(OperandKind.Register, OperandType.I32, (ulong)AllocateTempGprRegister()));
|
||||
}
|
||||
|
||||
public ScopedRegister AllocateTempFpRegisterScoped(bool isFP32)
|
||||
{
|
||||
return new(this, new(OperandKind.Register, isFP32 ? OperandType.FP32 : OperandType.FP64, (ulong)AllocateTempFpSimdRegister()));
|
||||
}
|
||||
|
||||
public ScopedRegister AllocateTempSimdRegisterScoped()
|
||||
{
|
||||
return new(this, new(OperandKind.Register, OperandType.V128, (ulong)AllocateTempFpSimdRegister()));
|
||||
}
|
||||
|
||||
public void FreeTempGprRegister(int index)
|
||||
{
|
||||
FreeTempRegister(ref _gprMask, index);
|
||||
}
|
||||
|
||||
public void FreeTempFpSimdRegister(int index)
|
||||
{
|
||||
FreeTempRegister(ref _fpSimdMask, index);
|
||||
}
|
||||
|
||||
private static int AllocateTempRegister(ref uint mask, uint reservedMask)
|
||||
{
|
||||
int index = BitOperations.TrailingZeroCount(~(mask | reservedMask));
|
||||
if (index == sizeof(uint) * 8)
|
||||
{
|
||||
throw new InvalidOperationException("No free registers.");
|
||||
}
|
||||
|
||||
mask |= 1u << index;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
private static void FreeTempRegister(ref uint mask, int index)
|
||||
{
|
||||
mask &= ~(1u << index);
|
||||
}
|
||||
}
|
||||
}
|
109
src/Ryujinx.Cpu/LightningJit/Arm32/RegisterUtils.cs
Normal file
109
src/Ryujinx.Cpu/LightningJit/Arm32/RegisterUtils.cs
Normal file
@ -0,0 +1,109 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
static class RegisterUtils
|
||||
{
|
||||
public const int SpRegister = 13;
|
||||
public const int LrRegister = 14;
|
||||
public const int PcRegister = 15;
|
||||
|
||||
private const int RmBit = 0;
|
||||
private const int RdRtBit = 12;
|
||||
private const int RdHiRnBit = 16;
|
||||
|
||||
private const int RdRtT16Bit = 16;
|
||||
private const int RdRtT16AltBit = 24;
|
||||
|
||||
private const int RdRt2RdHiT32Bit = 8;
|
||||
private const int RdT32AltBit = 0;
|
||||
private const int RtRdLoT32Bit = 12;
|
||||
|
||||
public static int ExtractRt(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RdRtBit) & 0xf;
|
||||
}
|
||||
|
||||
public static int ExtractRt2(uint encoding)
|
||||
{
|
||||
return (int)GetRt2((uint)ExtractRt(encoding));
|
||||
}
|
||||
|
||||
public static int ExtractRd(InstFlags flags, uint encoding)
|
||||
{
|
||||
return flags.HasFlag(InstFlags.Rd16) ? ExtractRn(encoding) : ExtractRd(encoding);
|
||||
}
|
||||
|
||||
public static int ExtractRd(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RdRtBit) & 0xf;
|
||||
}
|
||||
|
||||
public static int ExtractRdHi(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RdHiRnBit) & 0xf;
|
||||
}
|
||||
|
||||
public static int ExtractRn(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RdHiRnBit) & 0xf;
|
||||
}
|
||||
|
||||
public static int ExtractRm(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RmBit) & 0xf;
|
||||
}
|
||||
|
||||
public static uint GetRt2(uint rt)
|
||||
{
|
||||
return Math.Min(rt + 1, PcRegister);
|
||||
}
|
||||
|
||||
public static int ExtractRdn(InstFlags flags, uint encoding)
|
||||
{
|
||||
if (flags.HasFlag(InstFlags.Dn))
|
||||
{
|
||||
return ((int)(encoding >> RdRtT16Bit) & 7) | (int)((encoding >> 4) & 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ExtractRdT16(flags, encoding);
|
||||
}
|
||||
}
|
||||
|
||||
public static int ExtractRdT16(InstFlags flags, uint encoding)
|
||||
{
|
||||
return flags.HasFlag(InstFlags.Rd16) ? (int)(encoding >> RdRtT16AltBit) & 7 : (int)(encoding >> RdRtT16Bit) & 7;
|
||||
}
|
||||
|
||||
public static int ExtractRtT16(InstFlags flags, uint encoding)
|
||||
{
|
||||
return flags.HasFlag(InstFlags.Rd16) ? (int)(encoding >> RdRtT16AltBit) & 7 : (int)(encoding >> RdRtT16Bit) & 7;
|
||||
}
|
||||
|
||||
public static int ExtractRdT32(InstFlags flags, uint encoding)
|
||||
{
|
||||
return flags.HasFlag(InstFlags.Rd16) ? (int)(encoding >> RdT32AltBit) & 0xf : (int)(encoding >> RdRt2RdHiT32Bit) & 0xf;
|
||||
}
|
||||
|
||||
public static int ExtractRdLoT32(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RtRdLoT32Bit) & 0xf;
|
||||
}
|
||||
|
||||
public static int ExtractRdHiT32(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RdRt2RdHiT32Bit) & 0xf;
|
||||
}
|
||||
|
||||
public static int ExtractRtT32(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RtRdLoT32Bit) & 0xf;
|
||||
}
|
||||
|
||||
public static int ExtractRt2T32(uint encoding)
|
||||
{
|
||||
return (int)(encoding >> RdRt2RdHiT32Bit) & 0xf;
|
||||
}
|
||||
}
|
||||
}
|
39
src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs
Normal file
39
src/Ryujinx.Cpu/LightningJit/Arm32/ScopedRegister.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32
|
||||
{
|
||||
readonly struct ScopedRegister : IDisposable
|
||||
{
|
||||
private readonly RegisterAllocator _registerAllocator;
|
||||
private readonly Operand _operand;
|
||||
private readonly bool _isAllocated;
|
||||
|
||||
public readonly Operand Operand => _operand;
|
||||
public readonly bool IsAllocated => _isAllocated;
|
||||
|
||||
public ScopedRegister(RegisterAllocator registerAllocator, Operand operand, bool isAllocated = true)
|
||||
{
|
||||
_registerAllocator = registerAllocator;
|
||||
_operand = operand;
|
||||
_isAllocated = isAllocated;
|
||||
}
|
||||
|
||||
public readonly void Dispose()
|
||||
{
|
||||
if (!_isAllocated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_operand.Type.IsInteger())
|
||||
{
|
||||
_registerAllocator.FreeTempGprRegister(_operand.AsInt32());
|
||||
}
|
||||
else
|
||||
{
|
||||
_registerAllocator.FreeTempFpSimdRegister(_operand.AsInt32());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
808
src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs
Normal file
808
src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/Compiler.cs
Normal file
@ -0,0 +1,808 @@
|
||||
using ARMeilleure.Common;
|
||||
using ARMeilleure.Memory;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
{
|
||||
static class Compiler
|
||||
{
|
||||
public const uint UsableGprsMask = 0x7fff;
|
||||
public const uint UsableFpSimdMask = 0xffff;
|
||||
public const uint UsablePStateMask = 0xf0000000;
|
||||
|
||||
private const int Encodable26BitsOffsetLimit = 0x2000000;
|
||||
|
||||
private readonly struct Context
|
||||
{
|
||||
public readonly CodeWriter Writer;
|
||||
public readonly RegisterAllocator RegisterAllocator;
|
||||
public readonly MemoryManagerType MemoryManagerType;
|
||||
public readonly TailMerger TailMerger;
|
||||
public readonly AddressTable<ulong> FuncTable;
|
||||
public readonly IntPtr DispatchStubPointer;
|
||||
|
||||
private readonly RegisterSaveRestore _registerSaveRestore;
|
||||
private readonly IntPtr _pageTablePointer;
|
||||
|
||||
public Context(
|
||||
CodeWriter writer,
|
||||
RegisterAllocator registerAllocator,
|
||||
MemoryManagerType mmType,
|
||||
TailMerger tailMerger,
|
||||
AddressTable<ulong> funcTable,
|
||||
RegisterSaveRestore registerSaveRestore,
|
||||
IntPtr dispatchStubPointer,
|
||||
IntPtr pageTablePointer)
|
||||
{
|
||||
Writer = writer;
|
||||
RegisterAllocator = registerAllocator;
|
||||
MemoryManagerType = mmType;
|
||||
TailMerger = tailMerger;
|
||||
FuncTable = funcTable;
|
||||
_registerSaveRestore = registerSaveRestore;
|
||||
DispatchStubPointer = dispatchStubPointer;
|
||||
_pageTablePointer = pageTablePointer;
|
||||
}
|
||||
|
||||
public readonly int GetReservedStackOffset()
|
||||
{
|
||||
return _registerSaveRestore.GetReservedStackOffset();
|
||||
}
|
||||
|
||||
public readonly void WritePrologueAt(int instructionPointer)
|
||||
{
|
||||
CodeWriter writer = new();
|
||||
Assembler asm = new(writer);
|
||||
|
||||
_registerSaveRestore.WritePrologue(ref asm);
|
||||
|
||||
// If needed, set up the fixed registers with the pointers we will use.
|
||||
// First one is the context pointer (passed as first argument),
|
||||
// second one is the page table or address space base, it is at a fixed memory location and considered constant.
|
||||
|
||||
if (RegisterAllocator.FixedContextRegister != 0)
|
||||
{
|
||||
asm.Mov(Register(RegisterAllocator.FixedContextRegister), Register(0));
|
||||
}
|
||||
|
||||
asm.Mov(Register(RegisterAllocator.FixedPageTableRegister), (ulong)_pageTablePointer);
|
||||
|
||||
LoadFromContext(ref asm);
|
||||
|
||||
// Write the prologue at the specified position in our writer.
|
||||
Writer.WriteInstructionsAt(instructionPointer, writer);
|
||||
}
|
||||
|
||||
public readonly void WriteEpilogueWithoutContext()
|
||||
{
|
||||
Assembler asm = new(Writer);
|
||||
|
||||
_registerSaveRestore.WriteEpilogue(ref asm);
|
||||
}
|
||||
|
||||
public void LoadFromContext()
|
||||
{
|
||||
Assembler asm = new(Writer);
|
||||
|
||||
LoadFromContext(ref asm);
|
||||
}
|
||||
|
||||
private void LoadFromContext(ref Assembler asm)
|
||||
{
|
||||
LoadGprFromContext(ref asm, RegisterAllocator.UsedGprsMask & UsableGprsMask, NativeContextOffsets.GprBaseOffset);
|
||||
LoadFpSimdFromContext(ref asm, RegisterAllocator.UsedFpSimdMask & UsableFpSimdMask, NativeContextOffsets.FpSimdBaseOffset);
|
||||
LoadPStateFromContext(ref asm, UsablePStateMask, NativeContextOffsets.FlagsBaseOffset);
|
||||
}
|
||||
|
||||
public void StoreToContext()
|
||||
{
|
||||
Assembler asm = new(Writer);
|
||||
|
||||
StoreToContext(ref asm);
|
||||
}
|
||||
|
||||
private void StoreToContext(ref Assembler asm)
|
||||
{
|
||||
StoreGprToContext(ref asm, RegisterAllocator.UsedGprsMask & UsableGprsMask, NativeContextOffsets.GprBaseOffset);
|
||||
StoreFpSimdToContext(ref asm, RegisterAllocator.UsedFpSimdMask & UsableFpSimdMask, NativeContextOffsets.FpSimdBaseOffset);
|
||||
StorePStateToContext(ref asm, UsablePStateMask, NativeContextOffsets.FlagsBaseOffset);
|
||||
}
|
||||
|
||||
private void LoadGprFromContext(ref Assembler asm, uint mask, int baseOffset)
|
||||
{
|
||||
Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
|
||||
|
||||
while (mask != 0)
|
||||
{
|
||||
int reg = BitOperations.TrailingZeroCount(mask);
|
||||
int offset = baseOffset + reg * 8;
|
||||
|
||||
if (reg < 31 && (mask & (2u << reg)) != 0 && offset < RegisterSaveRestore.Encodable9BitsOffsetLimit)
|
||||
{
|
||||
mask &= ~(3u << reg);
|
||||
|
||||
asm.LdpRiUn(Register(reg), Register(reg + 1), contextPtr, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
mask &= ~(1u << reg);
|
||||
|
||||
asm.LdrRiUn(Register(reg), contextPtr, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadFpSimdFromContext(ref Assembler asm, uint mask, int baseOffset)
|
||||
{
|
||||
Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
|
||||
|
||||
while (mask != 0)
|
||||
{
|
||||
int reg = BitOperations.TrailingZeroCount(mask);
|
||||
int offset = baseOffset + reg * 16;
|
||||
|
||||
mask &= ~(1u << reg);
|
||||
|
||||
asm.LdrRiUn(Register(reg, OperandType.V128), contextPtr, offset);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadPStateFromContext(ref Assembler asm, uint mask, int baseOffset)
|
||||
{
|
||||
if (mask == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
|
||||
|
||||
using ScopedRegister tempRegister = RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
asm.LdrRiUn(tempRegister.Operand, contextPtr, baseOffset);
|
||||
asm.MsrNzcv(tempRegister.Operand);
|
||||
}
|
||||
|
||||
private void StoreGprToContext(ref Assembler asm, uint mask, int baseOffset)
|
||||
{
|
||||
Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
|
||||
|
||||
while (mask != 0)
|
||||
{
|
||||
int reg = BitOperations.TrailingZeroCount(mask);
|
||||
int offset = baseOffset + reg * 8;
|
||||
|
||||
if (reg < 31 && (mask & (2u << reg)) != 0 && offset < RegisterSaveRestore.Encodable9BitsOffsetLimit)
|
||||
{
|
||||
mask &= ~(3u << reg);
|
||||
|
||||
asm.StpRiUn(Register(reg), Register(reg + 1), contextPtr, offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
mask &= ~(1u << reg);
|
||||
|
||||
asm.StrRiUn(Register(reg), contextPtr, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void StoreFpSimdToContext(ref Assembler asm, uint mask, int baseOffset)
|
||||
{
|
||||
Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
|
||||
|
||||
while (mask != 0)
|
||||
{
|
||||
int reg = BitOperations.TrailingZeroCount(mask);
|
||||
int offset = baseOffset + reg * 16;
|
||||
|
||||
mask &= ~(1u << reg);
|
||||
|
||||
asm.StrRiUn(Register(reg, OperandType.V128), contextPtr, offset);
|
||||
}
|
||||
}
|
||||
|
||||
private void StorePStateToContext(ref Assembler asm, uint mask, int baseOffset)
|
||||
{
|
||||
if (mask == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand contextPtr = Register(RegisterAllocator.FixedContextRegister);
|
||||
|
||||
using ScopedRegister tempRegister = RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempRegister2 = RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
asm.LdrRiUn(tempRegister.Operand, contextPtr, baseOffset);
|
||||
asm.MrsNzcv(tempRegister2.Operand);
|
||||
asm.And(tempRegister.Operand, tempRegister.Operand, InstEmitCommon.Const(0xfffffff));
|
||||
asm.Orr(tempRegister.Operand, tempRegister.Operand, tempRegister2.Operand);
|
||||
asm.StrRiUn(tempRegister.Operand, contextPtr, baseOffset);
|
||||
}
|
||||
}
|
||||
|
||||
public static CompiledFunction Compile(CpuPreset cpuPreset, IMemoryManager memoryManager, ulong address, AddressTable<ulong> funcTable, IntPtr dispatchStubPtr, bool isThumb)
|
||||
{
|
||||
MultiBlock multiBlock = Decoder<InstEmit>.DecodeMulti(cpuPreset, memoryManager, address, isThumb);
|
||||
|
||||
Dictionary<ulong, int> targets = new();
|
||||
|
||||
CodeWriter writer = new();
|
||||
RegisterAllocator regAlloc = new();
|
||||
Assembler asm = new(writer);
|
||||
CodeGenContext cgContext = new(writer, asm, regAlloc, memoryManager.Type, isThumb);
|
||||
ArmCondition lastCondition = ArmCondition.Al;
|
||||
int lastConditionIp = 0;
|
||||
|
||||
// Required for load/store to context.
|
||||
regAlloc.EnsureTempGprRegisters(2);
|
||||
|
||||
ulong pc = address;
|
||||
|
||||
for (int blockIndex = 0; blockIndex < multiBlock.Blocks.Count; blockIndex++)
|
||||
{
|
||||
Block block = multiBlock.Blocks[blockIndex];
|
||||
|
||||
Debug.Assert(block.Address == pc);
|
||||
|
||||
targets.Add(pc, writer.InstructionPointer);
|
||||
|
||||
for (int index = 0; index < block.Instructions.Count; index++)
|
||||
{
|
||||
InstInfo instInfo = block.Instructions[index];
|
||||
|
||||
if (index < block.Instructions.Count - 1)
|
||||
{
|
||||
cgContext.SetNextInstruction(block.Instructions[index + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
cgContext.SetNextInstruction(default);
|
||||
}
|
||||
|
||||
SetConditionalStart(cgContext, ref lastCondition, ref lastConditionIp, instInfo.Name, instInfo.Flags, instInfo.Encoding);
|
||||
|
||||
if (block.IsLoopEnd && index == block.Instructions.Count - 1)
|
||||
{
|
||||
// If this is a loop, the code might run for a long time uninterrupted.
|
||||
// We insert a "sync point" here to ensure the loop can be interrupted if needed.
|
||||
|
||||
cgContext.AddPendingSyncPoint();
|
||||
|
||||
asm.B(0);
|
||||
}
|
||||
|
||||
cgContext.SetPc((uint)pc);
|
||||
|
||||
instInfo.EmitFunc(cgContext, instInfo.Encoding);
|
||||
|
||||
if (cgContext.ConsumeNzcvModified())
|
||||
{
|
||||
ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp);
|
||||
}
|
||||
|
||||
cgContext.UpdateItState();
|
||||
|
||||
pc += instInfo.Flags.HasFlag(InstFlags.Thumb16) ? 2UL : 4UL;
|
||||
}
|
||||
|
||||
if (Decoder<InstEmit>.WritesToPC(block.Instructions[^1].Encoding, block.Instructions[^1].Name, block.Instructions[^1].Flags, block.IsThumb))
|
||||
{
|
||||
// If the block ends with a PC register write, then we have a branch from register.
|
||||
|
||||
InstEmitCommon.SetThumbFlag(cgContext, regAlloc.RemapGprRegister(RegisterUtils.PcRegister));
|
||||
|
||||
cgContext.AddPendingIndirectBranch(block.Instructions[^1].Name, RegisterUtils.PcRegister);
|
||||
|
||||
asm.B(0);
|
||||
}
|
||||
|
||||
ForceConditionalEnd(cgContext, ref lastCondition, lastConditionIp);
|
||||
}
|
||||
|
||||
int reservedStackSize = 0;
|
||||
|
||||
if (multiBlock.HasHostCall)
|
||||
{
|
||||
reservedStackSize = CalculateStackSizeForCallSpill(regAlloc.UsedGprsMask, regAlloc.UsedFpSimdMask, UsablePStateMask);
|
||||
}
|
||||
else if (multiBlock.HasHostCallSkipContext)
|
||||
{
|
||||
reservedStackSize = 2 * sizeof(ulong); // Context and page table pointers.
|
||||
}
|
||||
|
||||
RegisterSaveRestore rsr = new(
|
||||
regAlloc.UsedGprsMask & AbiConstants.GprCalleeSavedRegsMask,
|
||||
regAlloc.UsedFpSimdMask & AbiConstants.FpSimdCalleeSavedRegsMask,
|
||||
OperandType.FP64,
|
||||
multiBlock.HasHostCall || multiBlock.HasHostCallSkipContext,
|
||||
reservedStackSize);
|
||||
|
||||
TailMerger tailMerger = new();
|
||||
|
||||
Context context = new(writer, regAlloc, memoryManager.Type, tailMerger, funcTable, rsr, dispatchStubPtr, memoryManager.PageTablePointer);
|
||||
|
||||
InstInfo lastInstruction = multiBlock.Blocks[^1].Instructions[^1];
|
||||
bool lastInstIsConditional = GetCondition(lastInstruction, isThumb) != ArmCondition.Al;
|
||||
|
||||
if (multiBlock.IsTruncated || lastInstIsConditional || lastInstruction.Name.IsCall() || IsConditionalBranch(lastInstruction))
|
||||
{
|
||||
WriteTailCallConstant(context, ref asm, (uint)pc);
|
||||
}
|
||||
|
||||
IEnumerable<PendingBranch> pendingBranches = cgContext.GetPendingBranches();
|
||||
|
||||
foreach (PendingBranch pendingBranch in pendingBranches)
|
||||
{
|
||||
RewriteBranchInstructionWithTarget(context, pendingBranch, targets);
|
||||
}
|
||||
|
||||
tailMerger.WriteReturn(writer, context.WriteEpilogueWithoutContext);
|
||||
|
||||
context.WritePrologueAt(0);
|
||||
|
||||
return new(writer.AsByteSpan(), (int)(pc - address));
|
||||
}
|
||||
|
||||
private static int CalculateStackSizeForCallSpill(uint gprUseMask, uint fpSimdUseMask, uint pStateUseMask)
|
||||
{
|
||||
// Note that we don't discard callee saved FP/SIMD register because only the lower 64 bits is callee saved,
|
||||
// so if the function is using the full register, that won't be enough.
|
||||
// We could do better, but it's likely not worth it since this case happens very rarely in practice.
|
||||
|
||||
return BitOperations.PopCount(gprUseMask & ~AbiConstants.GprCalleeSavedRegsMask) * 8 +
|
||||
BitOperations.PopCount(fpSimdUseMask) * 16 +
|
||||
(pStateUseMask != 0 ? 8 : 0);
|
||||
}
|
||||
|
||||
private static void SetConditionalStart(
|
||||
CodeGenContext context,
|
||||
ref ArmCondition condition,
|
||||
ref int instructionPointer,
|
||||
InstName name,
|
||||
InstFlags flags,
|
||||
uint encoding)
|
||||
{
|
||||
if (!context.ConsumeItCondition(out ArmCondition currentCond))
|
||||
{
|
||||
currentCond = GetCondition(name, flags, encoding, context.IsThumb);
|
||||
}
|
||||
|
||||
if (currentCond != condition)
|
||||
{
|
||||
WriteConditionalEnd(context, condition, instructionPointer);
|
||||
|
||||
condition = currentCond;
|
||||
|
||||
if (currentCond != ArmCondition.Al)
|
||||
{
|
||||
instructionPointer = context.CodeWriter.InstructionPointer;
|
||||
context.Arm64Assembler.B(currentCond.Invert(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsConditionalBranch(in InstInfo instInfo)
|
||||
{
|
||||
return instInfo.Name == InstName.B && (ArmCondition)(instInfo.Encoding >> 28) != ArmCondition.Al;
|
||||
}
|
||||
|
||||
private static ArmCondition GetCondition(in InstInfo instInfo, bool isThumb)
|
||||
{
|
||||
return GetCondition(instInfo.Name, instInfo.Flags, instInfo.Encoding, isThumb);
|
||||
}
|
||||
|
||||
private static ArmCondition GetCondition(InstName name, InstFlags flags, uint encoding, bool isThumb)
|
||||
{
|
||||
// For branch, we handle conditional execution on the instruction itself.
|
||||
bool hasCond = flags.HasFlag(InstFlags.Cond) && !CanHandleConditionalInstruction(name, encoding, isThumb);
|
||||
|
||||
return hasCond ? (ArmCondition)(encoding >> 28) : ArmCondition.Al;
|
||||
}
|
||||
|
||||
private static bool CanHandleConditionalInstruction(InstName name, uint encoding, bool isThumb)
|
||||
{
|
||||
if (name == InstName.B)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// We can use CSEL for conditional MOV from registers, as long the instruction is not setting flags.
|
||||
// We don't handle thumb right now because the condition comes from the IT block which would be more complicated to handle.
|
||||
if (name == InstName.MovR && !isThumb && (encoding & (1u << 20)) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ForceConditionalEnd(CodeGenContext context, ref ArmCondition condition, int instructionPointer)
|
||||
{
|
||||
WriteConditionalEnd(context, condition, instructionPointer);
|
||||
|
||||
condition = ArmCondition.Al;
|
||||
}
|
||||
|
||||
private static void WriteConditionalEnd(CodeGenContext context, ArmCondition condition, int instructionPointer)
|
||||
{
|
||||
if (condition != ArmCondition.Al)
|
||||
{
|
||||
int delta = context.CodeWriter.InstructionPointer - instructionPointer;
|
||||
uint branchInst = context.CodeWriter.ReadInstructionAt(instructionPointer) | (((uint)delta & 0x7ffff) << 5);
|
||||
Debug.Assert((int)((branchInst & ~0x1fu) << 8) >> 11 == delta * 4);
|
||||
|
||||
context.CodeWriter.WriteInstructionAt(instructionPointer, branchInst);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RewriteBranchInstructionWithTarget(in Context context, in PendingBranch pendingBranch, Dictionary<ulong, int> targets)
|
||||
{
|
||||
switch (pendingBranch.BranchType)
|
||||
{
|
||||
case BranchType.Branch:
|
||||
RewriteBranchInstructionWithTarget(context, pendingBranch.Name, pendingBranch.TargetAddress, pendingBranch.WriterPointer, targets);
|
||||
break;
|
||||
case BranchType.Call:
|
||||
RewriteCallInstructionWithTarget(context, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer);
|
||||
break;
|
||||
case BranchType.IndirectBranch:
|
||||
RewriteIndirectBranchInstructionWithTarget(context, pendingBranch.Name, pendingBranch.TargetAddress, pendingBranch.WriterPointer);
|
||||
break;
|
||||
case BranchType.TableBranchByte:
|
||||
case BranchType.TableBranchHalfword:
|
||||
RewriteTableBranchInstructionWithTarget(
|
||||
context,
|
||||
pendingBranch.BranchType == BranchType.TableBranchHalfword,
|
||||
pendingBranch.TargetAddress,
|
||||
pendingBranch.NextAddress,
|
||||
pendingBranch.WriterPointer);
|
||||
break;
|
||||
case BranchType.IndirectCall:
|
||||
RewriteIndirectCallInstructionWithTarget(context, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer);
|
||||
break;
|
||||
case BranchType.SyncPoint:
|
||||
case BranchType.SoftwareInterrupt:
|
||||
case BranchType.ReadCntpct:
|
||||
RewriteHostCall(context, pendingBranch.Name, pendingBranch.BranchType, pendingBranch.TargetAddress, pendingBranch.NextAddress, pendingBranch.WriterPointer);
|
||||
break;
|
||||
default:
|
||||
Debug.Fail($"Invalid branch type '{pendingBranch.BranchType}'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void RewriteBranchInstructionWithTarget(in Context context, InstName name, uint targetAddress, int branchIndex, Dictionary<ulong, int> targets)
|
||||
{
|
||||
CodeWriter writer = context.Writer;
|
||||
Assembler asm = new(writer);
|
||||
|
||||
int delta;
|
||||
int targetIndex;
|
||||
uint encoding = writer.ReadInstructionAt(branchIndex);
|
||||
|
||||
if (encoding == 0x14000000)
|
||||
{
|
||||
// Unconditional branch.
|
||||
|
||||
if (targets.TryGetValue(targetAddress, out targetIndex))
|
||||
{
|
||||
delta = targetIndex - branchIndex;
|
||||
|
||||
if (delta >= -Encodable26BitsOffsetLimit && delta < Encodable26BitsOffsetLimit)
|
||||
{
|
||||
writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
targetIndex = writer.InstructionPointer;
|
||||
delta = targetIndex - branchIndex;
|
||||
|
||||
writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff));
|
||||
WriteTailCallConstant(context, ref asm, targetAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Conditional branch.
|
||||
|
||||
uint branchMask = 0x7ffff;
|
||||
int branchMax = (int)(branchMask + 1) / 2;
|
||||
|
||||
if (targets.TryGetValue(targetAddress, out targetIndex))
|
||||
{
|
||||
delta = targetIndex - branchIndex;
|
||||
|
||||
if (delta >= -branchMax && delta < branchMax)
|
||||
{
|
||||
writer.WriteInstructionAt(branchIndex, encoding | (uint)((delta & branchMask) << 5));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
targetIndex = writer.InstructionPointer;
|
||||
delta = targetIndex - branchIndex;
|
||||
|
||||
if (delta >= -branchMax && delta < branchMax)
|
||||
{
|
||||
writer.WriteInstructionAt(branchIndex, encoding | (uint)((delta & branchMask) << 5));
|
||||
WriteTailCallConstant(context, ref asm, targetAddress);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the branch target is too far away, we use a regular unconditional branch
|
||||
// instruction instead which has a much higher range.
|
||||
// We branch directly to the end of the function, where we put the conditional branch,
|
||||
// and then branch back to the next instruction or return the branch target depending
|
||||
// on the branch being taken or not.
|
||||
|
||||
uint branchInst = 0x14000000u | ((uint)delta & 0x3ffffff);
|
||||
Debug.Assert((int)(branchInst << 6) >> 4 == delta * 4);
|
||||
|
||||
writer.WriteInstructionAt(branchIndex, branchInst);
|
||||
|
||||
int movedBranchIndex = writer.InstructionPointer;
|
||||
|
||||
writer.WriteInstruction(0u); // Placeholder
|
||||
asm.B((branchIndex + 1 - writer.InstructionPointer) * 4);
|
||||
|
||||
delta = writer.InstructionPointer - movedBranchIndex;
|
||||
|
||||
writer.WriteInstructionAt(movedBranchIndex, encoding | (uint)((delta & branchMask) << 5));
|
||||
WriteTailCallConstant(context, ref asm, targetAddress);
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Assert(name == InstName.B || name == InstName.Cbnz, $"Unknown branch instruction \"{name}\".");
|
||||
}
|
||||
|
||||
private static void RewriteCallInstructionWithTarget(in Context context, uint targetAddress, uint nextAddress, int branchIndex)
|
||||
{
|
||||
CodeWriter writer = context.Writer;
|
||||
Assembler asm = new(writer);
|
||||
|
||||
WriteBranchToCurrentPosition(context, branchIndex);
|
||||
|
||||
asm.Mov(context.RegisterAllocator.RemapGprRegister(RegisterUtils.LrRegister), nextAddress);
|
||||
|
||||
context.StoreToContext();
|
||||
InstEmitFlow.WriteCallWithGuestAddress(
|
||||
writer,
|
||||
ref asm,
|
||||
context.RegisterAllocator,
|
||||
context.TailMerger,
|
||||
context.WriteEpilogueWithoutContext,
|
||||
context.FuncTable,
|
||||
context.DispatchStubPointer,
|
||||
context.GetReservedStackOffset(),
|
||||
nextAddress,
|
||||
InstEmitCommon.Const((int)targetAddress));
|
||||
context.LoadFromContext();
|
||||
|
||||
// Branch back to the next instruction (after the call).
|
||||
asm.B((branchIndex + 1 - writer.InstructionPointer) * 4);
|
||||
}
|
||||
|
||||
private static void RewriteIndirectBranchInstructionWithTarget(in Context context, InstName name, uint targetRegister, int branchIndex)
|
||||
{
|
||||
CodeWriter writer = context.Writer;
|
||||
Assembler asm = new(writer);
|
||||
|
||||
WriteBranchToCurrentPosition(context, branchIndex);
|
||||
|
||||
using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
asm.And(target.Operand, context.RegisterAllocator.RemapGprRegister((int)targetRegister), InstEmitCommon.Const(~1));
|
||||
|
||||
context.StoreToContext();
|
||||
|
||||
if ((name == InstName.Bx && targetRegister == RegisterUtils.LrRegister) ||
|
||||
name == InstName.Ldm ||
|
||||
name == InstName.Ldmda ||
|
||||
name == InstName.Ldmdb ||
|
||||
name == InstName.Ldmib ||
|
||||
name == InstName.Pop)
|
||||
{
|
||||
// Arm32 does not have a return instruction, instead returns are implemented
|
||||
// either using BX LR (for leaf functions), or POP { ... PC }.
|
||||
|
||||
asm.Mov(Register(0), target.Operand);
|
||||
|
||||
context.TailMerger.AddUnconditionalReturn(writer, asm);
|
||||
}
|
||||
else
|
||||
{
|
||||
InstEmitFlow.WriteCallWithGuestAddress(
|
||||
writer,
|
||||
ref asm,
|
||||
context.RegisterAllocator,
|
||||
context.TailMerger,
|
||||
context.WriteEpilogueWithoutContext,
|
||||
context.FuncTable,
|
||||
context.DispatchStubPointer,
|
||||
context.GetReservedStackOffset(),
|
||||
0u,
|
||||
target.Operand,
|
||||
isTail: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void RewriteTableBranchInstructionWithTarget(in Context context, bool halfword, uint rn, uint rm, int branchIndex)
|
||||
{
|
||||
CodeWriter writer = context.Writer;
|
||||
Assembler asm = new(writer);
|
||||
|
||||
WriteBranchToCurrentPosition(context, branchIndex);
|
||||
|
||||
using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
asm.Add(
|
||||
target.Operand,
|
||||
context.RegisterAllocator.RemapGprRegister((int)rn),
|
||||
context.RegisterAllocator.RemapGprRegister((int)rm),
|
||||
ArmShiftType.Lsl,
|
||||
halfword ? 1 : 0);
|
||||
|
||||
InstEmitMemory.WriteAddressTranslation(context.MemoryManagerType, context.RegisterAllocator, asm, target.Operand, target.Operand);
|
||||
|
||||
if (halfword)
|
||||
{
|
||||
asm.LdrhRiUn(target.Operand, target.Operand, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
asm.LdrbRiUn(target.Operand, target.Operand, 0);
|
||||
}
|
||||
|
||||
asm.Add(target.Operand, context.RegisterAllocator.RemapGprRegister(RegisterUtils.PcRegister), target.Operand, ArmShiftType.Lsl, 1);
|
||||
|
||||
context.StoreToContext();
|
||||
|
||||
InstEmitFlow.WriteCallWithGuestAddress(
|
||||
writer,
|
||||
ref asm,
|
||||
context.RegisterAllocator,
|
||||
context.TailMerger,
|
||||
context.WriteEpilogueWithoutContext,
|
||||
context.FuncTable,
|
||||
context.DispatchStubPointer,
|
||||
context.GetReservedStackOffset(),
|
||||
0u,
|
||||
target.Operand,
|
||||
isTail: true);
|
||||
}
|
||||
|
||||
private static void RewriteIndirectCallInstructionWithTarget(in Context context, uint targetRegister, uint nextAddress, int branchIndex)
|
||||
{
|
||||
CodeWriter writer = context.Writer;
|
||||
Assembler asm = new(writer);
|
||||
|
||||
WriteBranchToCurrentPosition(context, branchIndex);
|
||||
|
||||
using ScopedRegister target = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
asm.And(target.Operand, context.RegisterAllocator.RemapGprRegister((int)targetRegister), InstEmitCommon.Const(~1));
|
||||
asm.Mov(context.RegisterAllocator.RemapGprRegister(RegisterUtils.LrRegister), nextAddress);
|
||||
|
||||
context.StoreToContext();
|
||||
InstEmitFlow.WriteCallWithGuestAddress(
|
||||
writer,
|
||||
ref asm,
|
||||
context.RegisterAllocator,
|
||||
context.TailMerger,
|
||||
context.WriteEpilogueWithoutContext,
|
||||
context.FuncTable,
|
||||
context.DispatchStubPointer,
|
||||
context.GetReservedStackOffset(),
|
||||
nextAddress & ~1u,
|
||||
target.Operand);
|
||||
context.LoadFromContext();
|
||||
|
||||
// Branch back to the next instruction (after the call).
|
||||
asm.B((branchIndex + 1 - writer.InstructionPointer) * 4);
|
||||
}
|
||||
|
||||
private static void RewriteHostCall(in Context context, InstName name, BranchType type, uint imm, uint pc, int branchIndex)
|
||||
{
|
||||
CodeWriter writer = context.Writer;
|
||||
Assembler asm = new(writer);
|
||||
|
||||
uint encoding = writer.ReadInstructionAt(branchIndex);
|
||||
int targetIndex = writer.InstructionPointer;
|
||||
int delta = targetIndex - branchIndex;
|
||||
|
||||
writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case BranchType.SyncPoint:
|
||||
InstEmitSystem.WriteSyncPoint(
|
||||
context.Writer,
|
||||
ref asm,
|
||||
context.RegisterAllocator,
|
||||
context.TailMerger,
|
||||
context.GetReservedStackOffset(),
|
||||
context.StoreToContext,
|
||||
context.LoadFromContext);
|
||||
break;
|
||||
case BranchType.SoftwareInterrupt:
|
||||
context.StoreToContext();
|
||||
switch (name)
|
||||
{
|
||||
case InstName.Bkpt:
|
||||
InstEmitSystem.WriteBkpt(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm);
|
||||
break;
|
||||
case InstName.Svc:
|
||||
InstEmitSystem.WriteSvc(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm);
|
||||
break;
|
||||
case InstName.Udf:
|
||||
InstEmitSystem.WriteUdf(context.Writer, context.RegisterAllocator, context.TailMerger, context.GetReservedStackOffset(), pc, imm);
|
||||
break;
|
||||
}
|
||||
context.LoadFromContext();
|
||||
break;
|
||||
case BranchType.ReadCntpct:
|
||||
InstEmitSystem.WriteReadCntpct(context.Writer, context.RegisterAllocator, context.GetReservedStackOffset(), (int)imm, (int)pc);
|
||||
break;
|
||||
default:
|
||||
Debug.Fail($"Invalid branch type '{type}'");
|
||||
break;
|
||||
}
|
||||
|
||||
// Branch back to the next instruction.
|
||||
asm.B((branchIndex + 1 - writer.InstructionPointer) * 4);
|
||||
}
|
||||
|
||||
private static void WriteBranchToCurrentPosition(in Context context, int branchIndex)
|
||||
{
|
||||
CodeWriter writer = context.Writer;
|
||||
|
||||
int targetIndex = writer.InstructionPointer;
|
||||
|
||||
if (branchIndex + 1 == targetIndex)
|
||||
{
|
||||
writer.RemoveLastInstruction();
|
||||
}
|
||||
else
|
||||
{
|
||||
uint encoding = writer.ReadInstructionAt(branchIndex);
|
||||
int delta = targetIndex - branchIndex;
|
||||
|
||||
writer.WriteInstructionAt(branchIndex, encoding | (uint)(delta & 0x3ffffff));
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteTailCallConstant(in Context context, ref Assembler asm, uint address)
|
||||
{
|
||||
context.StoreToContext();
|
||||
InstEmitFlow.WriteCallWithGuestAddress(
|
||||
context.Writer,
|
||||
ref asm,
|
||||
context.RegisterAllocator,
|
||||
context.TailMerger,
|
||||
context.WriteEpilogueWithoutContext,
|
||||
context.FuncTable,
|
||||
context.DispatchStubPointer,
|
||||
context.GetReservedStackOffset(),
|
||||
0u,
|
||||
InstEmitCommon.Const((int)address),
|
||||
isTail: true);
|
||||
}
|
||||
|
||||
private static Operand Register(int register, OperandType type = OperandType.I64)
|
||||
{
|
||||
return new Operand(register, RegisterType.Integer, type);
|
||||
}
|
||||
|
||||
public static void PrintStats()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
8502
src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmit.cs
Normal file
8502
src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmit.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,87 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
{
|
||||
static class InstEmitAbsDiff
|
||||
{
|
||||
public static void Usad8(CodeGenContext context, uint rd, uint rn, uint rm)
|
||||
{
|
||||
using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
for (int b = 0; b < 4; b++)
|
||||
{
|
||||
context.Arm64Assembler.Ubfx(tempN.Operand, rnOperand, b * 8, 8);
|
||||
context.Arm64Assembler.Ubfx(tempM.Operand, rmOperand, b * 8, 8);
|
||||
|
||||
Operand dest = b == 0 ? tempD.Operand : tempD2.Operand;
|
||||
|
||||
context.Arm64Assembler.Sub(dest, tempN.Operand, tempM.Operand);
|
||||
|
||||
EmitAbs(context, dest);
|
||||
|
||||
if (b > 0)
|
||||
{
|
||||
if (b < 3)
|
||||
{
|
||||
context.Arm64Assembler.Add(tempD.Operand, tempD.Operand, dest);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Arm64Assembler.Add(rdOperand, tempD.Operand, dest);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Usada8(CodeGenContext context, uint rd, uint rn, uint rm, uint ra)
|
||||
{
|
||||
using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
Operand raOperand = InstEmitCommon.GetInputGpr(context, ra);
|
||||
|
||||
for (int b = 0; b < 4; b++)
|
||||
{
|
||||
context.Arm64Assembler.Ubfx(tempN.Operand, rnOperand, b * 8, 8);
|
||||
context.Arm64Assembler.Ubfx(tempM.Operand, rmOperand, b * 8, 8);
|
||||
|
||||
Operand dest = b == 0 ? tempD.Operand : tempD2.Operand;
|
||||
|
||||
context.Arm64Assembler.Sub(dest, tempN.Operand, tempM.Operand);
|
||||
|
||||
EmitAbs(context, dest);
|
||||
|
||||
if (b > 0)
|
||||
{
|
||||
context.Arm64Assembler.Add(tempD.Operand, tempD.Operand, dest);
|
||||
}
|
||||
}
|
||||
|
||||
context.Arm64Assembler.Add(rdOperand, tempD.Operand, raOperand);
|
||||
}
|
||||
|
||||
private static void EmitAbs(CodeGenContext context, Operand value)
|
||||
{
|
||||
using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
// r = (value + ((int)value >> 31)) ^ ((int)value >> 31).
|
||||
// Subtracts 1 and then inverts the value if the sign bit is set, same as a conditional negation.
|
||||
|
||||
context.Arm64Assembler.Add(tempRegister.Operand, value, value, ArmShiftType.Asr, 31);
|
||||
context.Arm64Assembler.Eor(value, tempRegister.Operand, value, ArmShiftType.Asr, 31);
|
||||
}
|
||||
}
|
||||
}
|
1105
src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAlu.cs
Normal file
1105
src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitAlu.cs
Normal file
File diff suppressed because it is too large
Load Diff
103
src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitBit.cs
Normal file
103
src/Ryujinx.Cpu/LightningJit/Arm32/Target/Arm64/InstEmitBit.cs
Normal file
@ -0,0 +1,103 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
{
|
||||
static class InstEmitBit
|
||||
{
|
||||
public static void Bfc(CodeGenContext context, uint rd, uint lsb, uint msb)
|
||||
{
|
||||
// This is documented as "unpredictable".
|
||||
if (msb < lsb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
|
||||
context.Arm64Assembler.Bfc(rdOperand, (int)lsb, (int)(msb - lsb + 1));
|
||||
}
|
||||
|
||||
public static void Bfi(CodeGenContext context, uint rd, uint rn, uint lsb, uint msb)
|
||||
{
|
||||
// This is documented as "unpredictable".
|
||||
if (msb < lsb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
|
||||
context.Arm64Assembler.Bfi(rdOperand, rnOperand, (int)lsb, (int)(msb - lsb + 1));
|
||||
}
|
||||
|
||||
public static void Clz(CodeGenContext context, uint rd, uint rm)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Clz(rdOperand, rmOperand);
|
||||
}
|
||||
|
||||
public static void Rbit(CodeGenContext context, uint rd, uint rm)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Rbit(rdOperand, rmOperand);
|
||||
}
|
||||
|
||||
public static void Rev(CodeGenContext context, uint rd, uint rm)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Rev(rdOperand, rmOperand);
|
||||
}
|
||||
|
||||
public static void Rev16(CodeGenContext context, uint rd, uint rm)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Rev16(rdOperand, rmOperand);
|
||||
}
|
||||
|
||||
public static void Revsh(CodeGenContext context, uint rd, uint rm)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Rev16(rdOperand, rmOperand);
|
||||
context.Arm64Assembler.Sxth(rdOperand, rdOperand);
|
||||
}
|
||||
|
||||
public static void Sbfx(CodeGenContext context, uint rd, uint rn, uint lsb, uint widthMinus1)
|
||||
{
|
||||
// This is documented as "unpredictable".
|
||||
if (lsb + widthMinus1 > 31)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
|
||||
context.Arm64Assembler.Sbfx(rdOperand, rnOperand, (int)lsb, (int)widthMinus1 + 1);
|
||||
}
|
||||
|
||||
public static void Ubfx(CodeGenContext context, uint rd, uint rn, uint lsb, uint widthMinus1)
|
||||
{
|
||||
// This is documented as "unpredictable".
|
||||
if (lsb + widthMinus1 > 31)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
|
||||
context.Arm64Assembler.Ubfx(rdOperand, rnOperand, (int)lsb, (int)widthMinus1 + 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,263 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen.Arm64;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
{
|
||||
static class InstEmitCommon
|
||||
{
|
||||
public static Operand Const(int value)
|
||||
{
|
||||
return new(OperandKind.Constant, OperandType.I32, (uint)value);
|
||||
}
|
||||
|
||||
public static Operand GetInputGpr(CodeGenContext context, uint register)
|
||||
{
|
||||
Operand operand = context.RegisterAllocator.RemapGprRegister((int)register);
|
||||
|
||||
if (register == RegisterUtils.PcRegister)
|
||||
{
|
||||
context.Arm64Assembler.Mov(operand, context.Pc);
|
||||
}
|
||||
|
||||
return operand;
|
||||
}
|
||||
|
||||
public static Operand GetOutputGpr(CodeGenContext context, uint register)
|
||||
{
|
||||
return context.RegisterAllocator.RemapGprRegister((int)register);
|
||||
}
|
||||
|
||||
public static void GetCurrentFlags(CodeGenContext context, Operand flagsOut)
|
||||
{
|
||||
context.Arm64Assembler.MrsNzcv(flagsOut);
|
||||
context.Arm64Assembler.Lsr(flagsOut, flagsOut, Const(28));
|
||||
}
|
||||
|
||||
public static void RestoreNzcvFlags(CodeGenContext context, Operand nzcvFlags)
|
||||
{
|
||||
using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
context.Arm64Assembler.Lsl(tempRegister.Operand, nzcvFlags, Const(28));
|
||||
context.Arm64Assembler.MsrNzcv(tempRegister.Operand);
|
||||
}
|
||||
|
||||
public static void RestoreCvFlags(CodeGenContext context, Operand cvFlags)
|
||||
{
|
||||
// Arm64 zeros the carry and overflow flags for logical operations, but Arm32 keeps them unchanged.
|
||||
// This will restore carry and overflow after a operation has zeroed them.
|
||||
|
||||
using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
context.Arm64Assembler.MrsNzcv(tempRegister.Operand);
|
||||
context.Arm64Assembler.Bfi(tempRegister.Operand, cvFlags, 28, 2);
|
||||
context.Arm64Assembler.MsrNzcv(tempRegister.Operand);
|
||||
}
|
||||
|
||||
public static void SetThumbFlag(CodeGenContext context)
|
||||
{
|
||||
Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister);
|
||||
|
||||
using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset);
|
||||
context.Arm64Assembler.Orr(tempRegister.Operand, tempRegister.Operand, Const(1 << 5));
|
||||
context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset);
|
||||
}
|
||||
|
||||
public static void SetThumbFlag(CodeGenContext context, Operand value)
|
||||
{
|
||||
Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister);
|
||||
|
||||
using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset);
|
||||
context.Arm64Assembler.Bfi(tempRegister.Operand, value, 5, 1);
|
||||
context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset);
|
||||
}
|
||||
|
||||
public static void ClearThumbFlag(CodeGenContext context)
|
||||
{
|
||||
Operand ctx = InstEmitSystem.Register(context.RegisterAllocator.FixedContextRegister);
|
||||
|
||||
using ScopedRegister tempRegister = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
context.Arm64Assembler.LdrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset);
|
||||
context.Arm64Assembler.Bfc(tempRegister.Operand, 5, 1);
|
||||
context.Arm64Assembler.StrRiUn(tempRegister.Operand, ctx, NativeContextOffsets.FlagsBaseOffset);
|
||||
}
|
||||
|
||||
public static void EmitSigned16BitPair(CodeGenContext context, uint rd, uint rn, Action<Operand, Operand> elementAction)
|
||||
{
|
||||
using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
Operand rdOperand = GetOutputGpr(context, rd);
|
||||
Operand rnOperand = GetInputGpr(context, rn);
|
||||
|
||||
context.Arm64Assembler.Sxth(tempN.Operand, rnOperand);
|
||||
elementAction(tempD.Operand, tempN.Operand);
|
||||
context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand);
|
||||
|
||||
context.Arm64Assembler.Asr(tempN.Operand, rnOperand, Const(16));
|
||||
elementAction(tempD.Operand, tempN.Operand);
|
||||
context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16);
|
||||
}
|
||||
|
||||
public static void EmitSigned16BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action<Operand, Operand, Operand> elementAction)
|
||||
{
|
||||
using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
Operand rdOperand = GetOutputGpr(context, rd);
|
||||
Operand rnOperand = GetInputGpr(context, rn);
|
||||
Operand rmOperand = GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Sxth(tempN.Operand, rnOperand);
|
||||
context.Arm64Assembler.Sxth(tempM.Operand, rmOperand);
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand);
|
||||
context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand);
|
||||
|
||||
context.Arm64Assembler.Asr(tempN.Operand, rnOperand, Const(16));
|
||||
context.Arm64Assembler.Asr(tempM.Operand, rmOperand, Const(16));
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand);
|
||||
context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16);
|
||||
}
|
||||
|
||||
public static void EmitSigned16BitXPair(CodeGenContext context, uint rd, uint rn, uint rm, Action<Operand, Operand, Operand, int> elementAction)
|
||||
{
|
||||
using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
Operand rdOperand = GetOutputGpr(context, rd);
|
||||
Operand rnOperand = GetInputGpr(context, rn);
|
||||
Operand rmOperand = GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Sxth(tempN.Operand, rnOperand);
|
||||
context.Arm64Assembler.Asr(tempM.Operand, rmOperand, Const(16));
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand, 0);
|
||||
context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand);
|
||||
|
||||
context.Arm64Assembler.Asr(tempN.Operand, rnOperand, Const(16));
|
||||
context.Arm64Assembler.Sxth(tempM.Operand, rmOperand);
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand, 1);
|
||||
context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16);
|
||||
}
|
||||
|
||||
public static void EmitSigned8BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action<Operand, Operand, Operand> elementAction)
|
||||
{
|
||||
Emit8BitPair(context, rd, rn, rm, elementAction, unsigned: false);
|
||||
}
|
||||
|
||||
public static void EmitUnsigned16BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action<Operand, Operand, Operand> elementAction)
|
||||
{
|
||||
using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
Operand rdOperand = GetOutputGpr(context, rd);
|
||||
Operand rnOperand = GetInputGpr(context, rn);
|
||||
Operand rmOperand = GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Uxth(tempN.Operand, rnOperand);
|
||||
context.Arm64Assembler.Uxth(tempM.Operand, rmOperand);
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand);
|
||||
context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand);
|
||||
|
||||
context.Arm64Assembler.Lsr(tempN.Operand, rnOperand, Const(16));
|
||||
context.Arm64Assembler.Lsr(tempM.Operand, rmOperand, Const(16));
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand);
|
||||
context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16);
|
||||
}
|
||||
|
||||
public static void EmitUnsigned16BitXPair(CodeGenContext context, uint rd, uint rn, uint rm, Action<Operand, Operand, Operand, int> elementAction)
|
||||
{
|
||||
using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
Operand rdOperand = GetOutputGpr(context, rd);
|
||||
Operand rnOperand = GetInputGpr(context, rn);
|
||||
Operand rmOperand = GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Uxth(tempN.Operand, rnOperand);
|
||||
context.Arm64Assembler.Lsr(tempM.Operand, rmOperand, Const(16));
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand, 0);
|
||||
context.Arm64Assembler.Uxth(tempD2.Operand, tempD.Operand);
|
||||
|
||||
context.Arm64Assembler.Lsr(tempN.Operand, rnOperand, Const(16));
|
||||
context.Arm64Assembler.Uxth(tempM.Operand, rmOperand);
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand, 1);
|
||||
context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 16);
|
||||
}
|
||||
|
||||
public static void EmitUnsigned8BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action<Operand, Operand, Operand> elementAction)
|
||||
{
|
||||
Emit8BitPair(context, rd, rn, rm, elementAction, unsigned: true);
|
||||
}
|
||||
|
||||
private static void Emit8BitPair(CodeGenContext context, uint rd, uint rn, uint rm, Action<Operand, Operand, Operand> elementAction, bool unsigned)
|
||||
{
|
||||
using ScopedRegister tempD = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempD2 = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempN = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
using ScopedRegister tempM = context.RegisterAllocator.AllocateTempGprRegisterScoped();
|
||||
|
||||
Operand rdOperand = GetOutputGpr(context, rd);
|
||||
Operand rnOperand = GetInputGpr(context, rn);
|
||||
Operand rmOperand = GetInputGpr(context, rm);
|
||||
|
||||
for (int b = 0; b < 4; b++)
|
||||
{
|
||||
if (unsigned)
|
||||
{
|
||||
context.Arm64Assembler.Ubfx(tempN.Operand, rnOperand, b * 8, 8);
|
||||
context.Arm64Assembler.Ubfx(tempM.Operand, rmOperand, b * 8, 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Arm64Assembler.Sbfx(tempN.Operand, rnOperand, b * 8, 8);
|
||||
context.Arm64Assembler.Sbfx(tempM.Operand, rmOperand, b * 8, 8);
|
||||
}
|
||||
|
||||
elementAction(tempD.Operand, tempN.Operand, tempM.Operand);
|
||||
|
||||
if (b == 0)
|
||||
{
|
||||
context.Arm64Assembler.Uxtb(tempD2.Operand, tempD.Operand);
|
||||
}
|
||||
else if (b < 3)
|
||||
{
|
||||
context.Arm64Assembler.Uxtb(tempD.Operand, tempD.Operand);
|
||||
context.Arm64Assembler.Orr(tempD2.Operand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, b * 8);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Arm64Assembler.Orr(rdOperand, tempD2.Operand, tempD.Operand, ArmShiftType.Lsl, 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static uint CombineV(uint low4, uint high1, uint size)
|
||||
{
|
||||
return size == 3 ? CombineV(low4, high1) : CombineVF(high1, low4);
|
||||
}
|
||||
|
||||
public static uint CombineV(uint low4, uint high1)
|
||||
{
|
||||
return low4 | (high1 << 4);
|
||||
}
|
||||
|
||||
public static uint CombineVF(uint low1, uint high4)
|
||||
{
|
||||
return low1 | (high4 << 1);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
{
|
||||
static class InstEmitCrc32
|
||||
{
|
||||
public static void Crc32(CodeGenContext context, uint rd, uint rn, uint rm, uint sz)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Crc32(rdOperand, rnOperand, rmOperand, Math.Min(2, sz));
|
||||
}
|
||||
|
||||
public static void Crc32c(CodeGenContext context, uint rd, uint rn, uint rm, uint sz)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Crc32c(rdOperand, rnOperand, rmOperand, Math.Min(2, sz));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
using Ryujinx.Cpu.LightningJit.CodeGen;
|
||||
|
||||
namespace Ryujinx.Cpu.LightningJit.Arm32.Target.Arm64
|
||||
{
|
||||
static class InstEmitDivide
|
||||
{
|
||||
public static void Sdiv(CodeGenContext context, uint rd, uint rn, uint rm)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Sdiv(rdOperand, rnOperand, rmOperand);
|
||||
}
|
||||
|
||||
public static void Udiv(CodeGenContext context, uint rd, uint rn, uint rm)
|
||||
{
|
||||
Operand rdOperand = InstEmitCommon.GetOutputGpr(context, rd);
|
||||
Operand rnOperand = InstEmitCommon.GetInputGpr(context, rn);
|
||||
Operand rmOperand = InstEmitCommon.GetInputGpr(context, rm);
|
||||
|
||||
context.Arm64Assembler.Udiv(rdOperand, rnOperand, rmOperand);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user