diff --git a/.github/reviewers.yml b/.github/reviewers.yml index f50755cca..052594f23 100644 --- a/.github/reviewers.yml +++ b/.github/reviewers.yml @@ -29,4 +29,4 @@ infra: - TSRBerry default: - - @developers + - '@developers' diff --git a/.github/update_reviewers.py b/.github/update_reviewers.py deleted file mode 100644 index dd1b1e566..000000000 --- a/.github/update_reviewers.py +++ /dev/null @@ -1,87 +0,0 @@ -from pathlib import Path -from typing import List, Set -from github import Auth, Github -from github.Repository import Repository -from github.GithubException import GithubException - -import os -import sys -import yaml - - -def add_reviewers( - reviewers: Set[str], team_reviewers: Set[str], new_entries: List[str] -): - for reviewer in new_entries: - if reviewer.startswith("@"): - team_reviewers.add(reviewer[1:]) - else: - reviewers.add(reviewer) - - -def update_reviewers(config, repo: Repository, pr_id: int) -> int: - pull_request = repo.get_pull(pr_id) - - if not pull_request: - sys.stderr.writable(f"Unknown PR #{pr_id}\n") - return 1 - - if pull_request.draft: - print("Not assigning reviewers for draft PRs") - return 0 - - pull_request_author = pull_request.user.login - reviewers = set() - team_reviewers = set() - - for label in pull_request.labels: - if label.name in config: - add_reviewers(reviewers, team_reviewers, config[label.name]) - - if "default" in config: - add_reviewers(reviewers, team_reviewers, config["default"]) - - if pull_request_author in reviewers: - reviewers.remove(pull_request_author) - - try: - reviewers = list(reviewers) - team_reviewers = list(team_reviewers) - print( - f"Attempting to assign reviewers ({reviewers}) and team_reviewers ({team_reviewers})" - ) - pull_request.create_review_request(reviewers, team_reviewers) - return 0 - except GithubException as e: - sys.stderr.write(f"Cannot assign review request for PR #{pr_id}: {e}\n") - return 1 - - -if __name__ == "__main__": - if len(sys.argv) != 7: - sys.stderr.write("usage: \n") - sys.exit(1) - - app_id = sys.argv[1] - private_key = os.environ[sys.argv[2]] - installation_id = sys.argv[3] - repo_path = sys.argv[4] - pr_id = int(sys.argv[5]) - config_path = Path(sys.argv[6]) - - auth = Auth.AppAuth(app_id, private_key).get_installation_auth(installation_id) - g = Github(auth=auth) - repo = g.get_repo(repo_path) - - if not repo: - sys.stderr.write("Repository not found!\n") - sys.exit(1) - - if not config_path.exists(): - sys.stderr.write(f'Config "{config_path}" not found!\n') - sys.exit(1) - - with open(config_path, "r") as f: - config = yaml.safe_load(f) - - sys.exit(update_reviewers(config, repo, pr_id)) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f0aa2a06a..16058d9f8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -35,7 +35,7 @@ jobs: fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 with: @@ -108,7 +108,7 @@ jobs: configuration: [ Debug, Release ] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 with: @@ -135,9 +135,13 @@ jobs: id: git_short_hash run: echo "result=$(git rev-parse --short "${{ github.sha }}")" >> $GITHUB_OUTPUT - - name: Publish macOS + - name: Publish macOS Ryujinx.Ava run: | - ./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" + ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" + + - name: Publish macOS Ryujinx.Headless.SDL2 + run: | + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ env.RYUJINX_BASE_VERSION }}" "${{ steps.git_short_hash.outputs.result }}" "${{ matrix.configuration }}" "-p:ExtraDefineConstants=DISABLE_UPDATER" - name: Upload Ryujinx.Ava artifact uses: actions/upload-artifact@v3 @@ -145,3 +149,10 @@ jobs: name: ava-ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal path: "publish_ava/*.tar.gz" if: github.event_name == 'pull_request' + + - name: Upload Ryujinx.Headless.SDL2 artifact + uses: actions/upload-artifact@v3 + with: + name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal + path: "publish_headless/*.tar.gz" + if: github.event_name == 'pull_request' \ No newline at end of file diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 25276b72f..2c982d506 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -23,7 +23,7 @@ jobs: format: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index d4380e05f..4c8ba3e17 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -24,7 +24,7 @@ jobs: RYUJINX_VERSION: "${{ inputs.ryujinx_version }}" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: Ryujinx @@ -38,7 +38,7 @@ jobs: run: | echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: repository: flathub/org.ryujinx.Ryujinx token: ${{ secrets.RYUJINX_BOT_PAT }} diff --git a/.github/workflows/pr_triage.yml b/.github/workflows/pr_triage.yml index 448e2c7de..e1c7b8ae8 100644 --- a/.github/workflows/pr_triage.yml +++ b/.github/workflows/pr_triage.yml @@ -12,14 +12,24 @@ jobs: runs-on: ubuntu-latest steps: - # Grab sources to get update_reviewers.py and reviewers.yml + # Grab sources to get latest labeler.yml - name: Fetch sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: - # Ensure we pin the source origin as pull_request_target run under forks. - fetch-depth: 0 - repository: Ryujinx/Ryujinx - ref: master + # Ensure we pin the source origin as pull_request_target run under forks. + fetch-depth: 0 + 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@v4 @@ -27,11 +37,11 @@ jobs: sync-labels: true dot: true - - run: pip3 install PyGithub - - name: Assign reviewers run: | - python3 .github/update_reviewers.py ${{ secrets.MAKO_APP_ID }} "MAKO_PRIVATE_KEY" ${{ secrets.MAKO_INSTALLATION_ID }} ${{ github.repository }} ${{ github.event.pull_request.number }} .github/reviewers.yml + 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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5091944a1..988264a31 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -62,7 +62,7 @@ jobs: DOTNET_RUNTIME_IDENTIFIER: win10-x64 RELEASE_ZIP_OS_NAME: win_x64 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 with: @@ -150,7 +150,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v3 with: @@ -188,15 +188,19 @@ jobs: sed -r --in-place 's/\%\%RYUJINX_TARGET_RELEASE_CHANNEL_REPO\%\%/${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}/g;' src/Ryujinx.Common/ReleaseInformation.cs shell: bash - - name: Publish macOS + - name: Publish macOS Ryujinx.Ava run: | - ./distribution/macos/create_macos_build.sh . publish_tmp publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + ./distribution/macos/create_macos_build_ava.sh . publish_tmp_ava publish_ava ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release + + - name: Publish macOS Ryujinx.Headless.SDL2 + run: | + ./distribution/macos/create_macos_build_headless.sh . publish_tmp_headless publish_headless ./distribution/macos/entitlements.xml "${{ steps.version_info.outputs.build_version }}" "${{ steps.version_info.outputs.git_short_hash }}" Release - name: Pushing new release uses: ncipollo/release-action@v1 with: name: ${{ steps.version_info.outputs.build_version }} - artifacts: "publish_ava/*.tar.gz" + artifacts: "publish_ava/*.tar.gz, publish_headless/*.tar.gz" tag: ${{ steps.version_info.outputs.build_version }} body: "For more information about this release please check out the official [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog)." omitBodyDuringUpdate: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..366eb8435 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,147 @@ +# Contribution to Ryujinx + +You can contribute to Ryujinx with PRs, testing of PRs and issues. Contributing code and other implementations is greatly appreciated alongside simply filing issues for problems you encounter. +Please read the entire document before continuing as it can potentially save everyone involved a significant amount of time. + +# Quick Links + +* [Code Style Documentation](docs/coding-guidelines/coding-style.md) +* [Pull Request Guidelines](docs/workflow/pr-guide.md) + +## Reporting Issues + +We always welcome bug reports, feature proposals and overall feedback. Here are a few tips on how you can make reporting your issue as effective as possible. + +### Identify Where to Report + +The Ryujinx codebase is distributed across multiple repositories in the [Ryujinx organization](https://github.com/Ryujinx). Depending on the feedback you might want to file the issue on a different repo. Here are a few common repos: + +* [Ryujinx/Ryujinx](https://github.com/Ryujinx/Ryujinx) Ryujinx core project files. +* [Ryujinx/Ryujinx-Games-List](https://github.com/Ryujinx/Ryujinx-Games-List) Ryujinx game compatibility list. +* [Ryujinx/Ryujinx-Website](https://github.com/Ryujinx/Ryujinx-Website) Ryujinx website source code. +* [Ryujinx/Ryujinx-Ldn-Website](https://github.com/Ryujinx/Ryujinx-Ldn-Website) Ryujinx LDN website source code. + +### Finding Existing Issues + +Before filing a new issue, please search our [open issues](https://github.com/Ryujinx/Ryujinx/issues) to check if it already exists. + +If you do find an existing issue, please include your own feedback in the discussion. Do consider upvoting (👍 reaction) the original post, as this helps us prioritize popular issues in our backlog. + +### Writing a Good Feature Request + +Please review any feature requests already opened to both check it has not already been suggested, and to familiarize yourself with the format. When ready to submit a proposal, please use the [Feature Request issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=&projects=&template=feature_request.yml&title=%5BFeature+Request%5D). + +### Writing a Good Bug Report + +Good bug reports make it easier for maintainers to verify and root cause the underlying problem. The better a bug report, the faster the problem will be resolved. +Ideally, a bug report should contain the following information: + +* A high-level description of the problem. +* A _minimal reproduction_, i.e. the smallest time commitment/configuration required to reproduce the wrong behavior. This can be in the form of a small homebrew application, or by providing a save file and reproduction steps for a specific game. +* A description of the _expected behavior_, contrasted with the _actual behavior_ observed. +* Information on the environment: OS/distro, CPU, GPU (including driver), RAM etc. +* A Ryujinx log file of the run instance where the issue occurred. Log files can be found in `[Executable Folder]/Logs` and are named chronologically. +* Additional information, e.g. is it a regression from previous versions? Are there any known workarounds? + +When ready to submit a bug report, please use the [Bug Report issue template](https://github.com/Ryujinx/Ryujinx/issues/new?assignees=&labels=bug&projects=&template=bug_report.yml&title=%5BBug%5D). + +## Contributing Changes + +Project maintainers will merge changes that both improve the project and meet our standards for code quality. + +The [Pull Request Guide](docs/workflow/pr-guide.md) and [License](https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt) docs define additional guidance. + +### DOs and DON'Ts + +Please do: + +* **DO** follow our [coding style](docs/coding-guidelines/coding-style.md) (C# code-specific). +* **DO** give priority to the current style of the project or file you're changing even if it diverges from the general guidelines. +* **DO** keep the discussions focused. When a new or related topic comes up + it's often better to create new issue than to side track the discussion. +* **DO** clearly state on an issue that you are going to take on implementing it. +* **DO** blog and tweet (or whatever) about your contributions, frequently! + +Please do not: + +* **DON'T** make PRs for style changes. +* **DON'T** surprise us with big pull requests. Instead, file an issue and talk with us on Discord to start + a discussion so we can agree on a direction before you invest a large amount + of time. +* **DON'T** commit code that you didn't write. If you find code that you think is a good fit to add to Ryujinx, file an issue or talk to us on Discord to start a discussion before proceeding. +* **DON'T** submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it. + +### Suggested Workflow + +We use and recommend the following workflow: + +1. Create or find an issue for your work. + - You can skip this step for trivial changes. + - Get agreement from the team and the community that your proposed change is a good one if it is of significant size or changes core functionality. + - Clearly state that you are going to take on implementing it, if that's the case. You can request that the issue be assigned to you. Note: The issue filer and the implementer don't have to be the same person. +2. Create a personal fork of the repository on GitHub (if you don't already have one). +3. In your fork, create a branch off of main (`git checkout -b mybranch`). + - Branches are useful since they isolate your changes from incoming changes from upstream. They also enable you to create multiple PRs from the same fork. +4. Make and commit your changes to your branch. + - [Build Instructions](https://github.com/Ryujinx/Ryujinx#building) explains how to build and test. + - Commit messages should be clear statements of action and intent. +6. Build the repository with your changes. + - Make sure that the builds are clean. + - Make sure that `dotnet format` has been run and any corrections tested and committed. +7. Create a pull request (PR) against the Ryujinx/Ryujinx repository's **main** branch. + - State in the description what issue or improvement your change is addressing. + - Check if all the Continuous Integration checks are passing. Refer to [Actions](https://github.com/Ryujinx/Ryujinx/actions) to check for outstanding errors. +8. Wait for feedback or approval of your changes from the [core development team](https://github.com/orgs/Ryujinx/teams/developers) + - Details about the pull request [review procedure](docs/workflow/ci/pr-guide.md). +9. When the team members have signed off, and all checks are green, your PR will be merged. + - The next official build will automatically include your change. + - You can delete the branch you used for making the change. + +### Good First Issues + +The team marks the most straightforward issues as [good first issues](https://github.com/Ryujinx/Ryujinx/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). This set of issues is the place to start if you are interested in contributing but new to the codebase. + +### Commit Messages + +Please format commit messages as follows (based on [A Note About Git Commit Messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)): + +``` +Summarize change in 50 characters or less + +Provide more detail after the first line. Leave one blank line below the +summary and wrap all lines at 72 characters or less. + +If the change fixes an issue, leave another blank line after the final +paragraph and indicate which issue is fixed in the specific format +below. + +Fix #42 +``` + +Also do your best to factor commits appropriately, not too large with unrelated things in the same commit, and not too small with the same small change applied N times in N different commits. + +### PR - CI Process + +The [Ryujinx continuous integration](https://github.com/Ryujinx/Ryujinx/actions) (CI) system will automatically perform the required builds and run tests (including the ones you are expected to run) for PRs. Builds and test runs must be clean or have bugs properly filed against flaky/unexpected failures that are unrelated to your change. + +If the CI build fails for any reason, the PR actions tab should be consulted for further information on the failure. There are a few usual suspects for such a failure: +* `dotnet format` has not been run on the PR and has outstanding stylistic issues. +* There is an error within the PR that fails a test or errors the compiler. +* Random failure of the workflow can occasionally result in a CI failure. In this scenario a maintainer will manually restart the job. + +### PR Feedback + +Ryujinx team and community members will provide feedback on your change. Community feedback is highly valued. You may see the absence of team feedback if the community has already provided good review feedback. + +Two Ryujinx team members must review and approve every PR prior to merge. They will often reply with "LGTM, see nit". That means that the PR will be merged once the feedback is resolved. "LGTM" == "looks good to me". + +There are lots of thoughts and [approaches](https://github.com/antlr/antlr4-cpp/blob/master/CONTRIBUTING.md#emoji) for how to efficiently discuss changes. It is best to be clear and explicit with your feedback. Please be patient with people who might not understand the finer details about your approach to feedback. + +#### Copying Changes from Other Projects + +Ryujinx uses some implementations and frameworks from other projects. The following rules must be followed for PRs that include changes from another project: + +- The license of the file is [permissive](https://en.wikipedia.org/wiki/Permissive_free_software_licence). +- The license of the file is left in-tact. +- The contribution is correctly attributed in the [3rd party notices](https://github.com/Ryujinx/Ryujinx/blob/master/distribution/legal/THIRDPARTY.md) file in the repository, as needed. + diff --git a/Directory.Packages.props b/Directory.Packages.props index fbae486c3..cde8742f9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,25 +3,25 @@ true - - - - - - - + + + + + + + - + - - + + @@ -44,9 +44,9 @@ - + - \ No newline at end of file + diff --git a/distribution/macos/create_macos_build.sh b/distribution/macos/create_macos_build_ava.sh similarity index 100% rename from distribution/macos/create_macos_build.sh rename to distribution/macos/create_macos_build_ava.sh diff --git a/distribution/macos/create_macos_build_headless.sh b/distribution/macos/create_macos_build_headless.sh new file mode 100755 index 000000000..a439aef45 --- /dev/null +++ b/distribution/macos/create_macos_build_headless.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +set -e + +if [ "$#" -lt 7 ]; then + echo "usage " + exit 1 +fi + +mkdir -p "$1" +mkdir -p "$2" +mkdir -p "$3" + +BASE_DIR=$(readlink -f "$1") +TEMP_DIRECTORY=$(readlink -f "$2") +OUTPUT_DIRECTORY=$(readlink -f "$3") +ENTITLEMENTS_FILE_PATH=$(readlink -f "$4") +VERSION=$5 +SOURCE_REVISION_ID=$6 +CONFIGURATION=$7 +EXTRA_ARGS=$8 + +if [ "$VERSION" == "1.1.0" ]; +then + RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$CONFIGURATION-$VERSION+$SOURCE_REVISION_ID-macos_universal.tar +else + RELEASE_TAR_FILE_NAME=sdl2-ryujinx-headless-$VERSION-macos_universal.tar +fi + +ARM64_OUTPUT="$TEMP_DIRECTORY/publish_arm64" +X64_OUTPUT="$TEMP_DIRECTORY/publish_x64" +UNIVERSAL_OUTPUT="$OUTPUT_DIRECTORY/publish" +EXECUTABLE_SUB_PATH=Ryujinx.Headless.SDL2 + +rm -rf "$TEMP_DIRECTORY" +mkdir -p "$TEMP_DIRECTORY" + +DOTNET_COMMON_ARGS=(-p:DebugType=embedded -p:Version="$VERSION" -p:SourceRevisionId="$SOURCE_REVISION_ID" --self-contained true $EXTRA_ARGS) + +dotnet restore +dotnet build -c "$CONFIGURATION" src/Ryujinx.Headless.SDL2 +dotnet publish -c "$CONFIGURATION" -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2 +dotnet publish -c "$CONFIGURATION" -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" "${DOTNET_COMMON_ARGS[@]}" src/Ryujinx.Headless.SDL2 + +# Get rid of the support library for ARMeilleure for x64 (that's only for arm64) +rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib" + +# Get rid of libsoundio from arm64 builds as we don't have a arm64 variant +# TODO: remove this once done +rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib" + +rm -rf "$OUTPUT_DIRECTORY" +mkdir -p "$OUTPUT_DIRECTORY" + +# Let's copy one of the two different outputs and remove the executable +cp -R "$ARM64_OUTPUT/" "$UNIVERSAL_OUTPUT" +rm "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" + +# Make it libraries universal +python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_OUTPUT" "$X64_OUTPUT" "$UNIVERSAL_OUTPUT" "**/*.dylib" + +if ! [ -x "$(command -v lipo)" ]; +then + if ! [ -x "$(command -v llvm-lipo-14)" ]; + then + LIPO=llvm-lipo + else + LIPO=llvm-lipo-14 + fi +else + LIPO=lipo +fi + +# Make the executable universal +$LIPO "$ARM64_OUTPUT/$EXECUTABLE_SUB_PATH" "$X64_OUTPUT/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_OUTPUT/$EXECUTABLE_SUB_PATH" -create + +# Now sign it +if ! [ -x "$(command -v codesign)" ]; +then + if ! [ -x "$(command -v rcodesign)" ]; + then + echo "Cannot find rcodesign on your system, please install rcodesign." + exit 1 + fi + + # NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes. + # cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign" + echo "Using rcodesign for ad-hoc signing" + for FILE in "$UNIVERSAL_OUTPUT"/*; do + if [[ $(file "$FILE") == *"Mach-O"* ]]; then + rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$FILE" + fi + done +else + echo "Using codesign for ad-hoc signing" + for FILE in "$UNIVERSAL_OUTPUT"/*; do + if [[ $(file "$FILE") == *"Mach-O"* ]]; then + codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$FILE" + fi + done +fi + +echo "Creating archive" +pushd "$OUTPUT_DIRECTORY" +tar --exclude "publish/Ryujinx.Headless.SDL2" -cvf "$RELEASE_TAR_FILE_NAME" publish 1> /dev/null +python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "publish/Ryujinx.Headless.SDL2" "publish/Ryujinx.Headless.SDL2" +gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz" +rm "$RELEASE_TAR_FILE_NAME" +popd + +echo "Done" \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..2213086f6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,40 @@ +# Documents Index + +This repo includes several documents that explain both high-level and low-level concepts about Ryujinx and its functions. These are very useful for contributors, to get context that can be very difficult to acquire from just reading code. + +Intro to Ryujinx +================== + +Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#. +* 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. +* 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. +* Audio output is entirely supported via C# wrappers for SDL2, with OpenAL & libsoundio as fallbacks. + +Getting Started +=============== + +- [Installing the .NET SDK](https://dotnet.microsoft.com/download) +- [Official .NET Docs](https://docs.microsoft.com/dotnet/core/) + +Contributing (Building, testing, benchmarking, profiling, etc.) +=============== + +If you want to contribute a code change to this repo, start here. + +- [Contributor Guide](../CONTRIBUTING.md) + +Coding Guidelines +================= + +- [C# coding style](coding-guidelines/coding-style.md) +- [Service Implementation Guidelines - WIP](https://gist.github.com/gdkchan/84ba88cd50efbe58d1babfaa7cd7c455) + +Project Docs +================= + +To be added. Many project files will contain basic XML docs for key functions and classes in the meantime. + +Other Information +================= + +- N/A diff --git a/docs/coding-guidelines/coding-style.md b/docs/coding-guidelines/coding-style.md new file mode 100644 index 000000000..9c84055d6 --- /dev/null +++ b/docs/coding-guidelines/coding-style.md @@ -0,0 +1,116 @@ +# C# Coding Style + +The general rule we follow is "use Visual Studio defaults". +Using an IDE that supports the `.editorconfig` standard will make this much simpler. + +1. We use [Allman style](http://en.wikipedia.org/wiki/Indent_style#Allman_style) braces, where each brace begins on a new line. A single line statement block can go without braces but the block must be properly indented on its own line and must not be nested in other statement blocks that use braces (See rule 18 for more details). One exception is that a `using` statement is permitted to be nested within another `using` statement by starting on the following line at the same indentation level, even if the nested `using` contains a controlled block. +2. We use four spaces of indentation (no tabs). +3. We use `_camelCase` for internal and private fields and use `readonly` where possible. Prefix internal and private instance fields with `_`, static fields with `s_` and thread static fields with `t_`. When used on static fields, `readonly` should come after `static` (e.g. `static readonly` not `readonly static`). Public fields should be used sparingly and should use PascalCasing with no prefix when used. +4. We avoid `this.` unless absolutely necessary. +5. We always specify the visibility, even if it's the default (e.g. + `private string _foo` not `string _foo`). Visibility should be the first modifier (e.g. + `public abstract` not `abstract public`). +6. Namespace imports should be specified at the top of the file, *outside* of `namespace` declarations. +7. Avoid more than one empty line at any time. For example, do not have two + blank lines between members of a type. +8. Avoid spurious free spaces. + For example avoid `if (someVar == 0)...`, where the dots mark the spurious free spaces. + Consider enabling "View White Space (Ctrl+R, Ctrl+W)" or "Edit -> Advanced -> View White Space" if using Visual Studio to aid detection. +9. If a file happens to differ in style from these guidelines (e.g. private members are named `m_member` + rather than `_member`), the existing style in that file takes precedence. +10. We only use `var` when the type is explicitly named on the right-hand side, typically due to either `new` or an explicit cast, e.g. `var stream = new FileStream(...)` not `var stream = OpenStandardInput()`. + - Similarly, target-typed `new()` can only be used when the type is explicitly named on the left-hand side, in a variable definition statement or a field definition statement. e.g. `FileStream stream = new(...);`, but not `stream = new(...);` (where the type was specified on a previous line). +11. We use language keywords instead of BCL types (e.g. `int, string, float` instead of `Int32, String, Single`, etc) for both type references as well as method calls (e.g. `int.Parse` instead of `Int32.Parse`). See issue [#13976](https://github.com/dotnet/runtime/issues/13976) for examples. +12. We use PascalCasing to name all our constant local variables and fields. The only exception is for interop code where the constant value should exactly match the name and value of the code you are calling via interop. +13. We use PascalCasing for all method names, including local functions. +14. We use ```nameof(...)``` instead of ```"..."``` whenever possible and relevant. +15. Fields should be specified at the top within type declarations. +16. When including non-ASCII characters in the source code use Unicode escape sequences (\uXXXX) instead of literal characters. Literal non-ASCII characters occasionally get garbled by a tool or editor. +17. When using labels (for goto), indent the label one less than the current indentation. +18. When using a single-statement if, we follow these conventions: + - Never use single-line form (for example: `if (source == null) throw new ArgumentNullException("source");`) + - Using braces is always accepted, and required if any block of an `if`/`else if`/.../`else` compound statement uses braces or if a single statement body spans multiple lines. + - Braces may be omitted only if the body of *every* block associated with an `if`/`else if`/.../`else` compound statement is placed on a single line. +19. Make all internal and private types static or sealed unless derivation from them is required. As with any implementation detail, they can be changed if/when derivation is required in the future. +20. XML docs should be used when writing interfaces or when a class/method is deemed sufficient in scope or complexity. +21. So-called [Magic Numbers](https://en.wikipedia.org/wiki/Magic_number_(programming)) should be defined as named constants before use (for example `for (int i = 56; i < 68; i++)` could read `for (int i = _currentAge; i < _retireAge; i++)`). + This may be ignored for trivial or syntactically common statements. + +An [EditorConfig](https://editorconfig.org "EditorConfig homepage") file (`.editorconfig`) has been provided at the root of the runtime repository, enabling C# auto-formatting conforming to the above guidelines. + +### Example File: + +``ShaderCache.cs:`` + +```C# +using Ryujinx.Common.Configuration; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; +using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.Gpu.Shader.DiskCache; +using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + +namespace Ryujinx.Graphics.Gpu.Shader +{ + /// + /// Memory cache of shader code. + /// + class ShaderCache : IDisposable + { + /// + /// Default flags used on the shader translation process. + /// + public const TranslationFlags DefaultFlags = TranslationFlags.DebugMode; + + private readonly struct TranslatedShader + { + public readonly CachedShaderStage Shader; + public readonly ShaderProgram Program; + + public TranslatedShader(CachedShaderStage shader, ShaderProgram program) + { + Shader = shader; + Program = program; + } + } + ... + + /// + /// Processes the queue of shaders that must save their binaries to the disk cache. + /// + public void ProcessShaderCacheQueue() + { + // Check to see if the binaries for previously compiled shaders are ready, and save them out. + + while (_programsToSaveQueue.TryPeek(out ProgramToSave programToSave)) + { + ProgramLinkStatus result = programToSave.HostProgram.CheckProgramLink(false); + + if (result != ProgramLinkStatus.Incomplete) + { + if (result == ProgramLinkStatus.Success) + { + _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary()); + } + + _programsToSaveQueue.Dequeue(); + } + else + { + break; + } + } + } + } +} +``` + +For other languages, our current best guidance is consistency. When editing files, keep new code and changes consistent with the style in the files. For new files, it should conform to the style for that component. If there is a completely new component, anything that is reasonably broadly accepted is fine. diff --git a/docs/workflow/pr-guide.md b/docs/workflow/pr-guide.md new file mode 100644 index 000000000..cc2c5900b --- /dev/null +++ b/docs/workflow/pr-guide.md @@ -0,0 +1,56 @@ +# Pull Request Guide + +## Contributing Rules + +All contributions to Ryujinx/Ryujinx repository are made via pull requests (PRs) rather than through direct commits. The pull requests are reviewed and merged by the maintainers after a review and at least two approvals from the core development team. + +To merge pull requests, you must have write permissions in the repository. + +## Quick Code Review Rules + +* Do not mix unrelated changes in one pull request. For example, a code style change should never be mixed with a bug fix. +* All changes should follow the existing code style. You can read more about our code style at [docs/coding-guidelines](../coding-guidelines/coding-style.md). +* Adding external dependencies is to be avoided unless not doing so would introduce _significant_ complexity. Any dependency addition should be justified and discussed before merge. +* Use Draft pull requests for changes you are still working on but want early CI loop feedback. When you think your changes are ready for review, [change the status](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-stage-of-a-pull-request) of your pull request. +* Rebase your changes when required or directly requested. Changes should always be commited on top of the upstream branch, not the other way around. +* If you are asked to make changes during the review process do them as a new commit. +* Only resolve GitHub conversations with reviewers once they have been addressed with a commit, or via a mutual agreement. + +## Pull Request Ownership + +Every pull request will have automatically have labels and reviewers assigned. The label not only indicates the code segment which the change touches but also the area reviewers to be assigned. + +If during the code review process a merge conflict occurs, the PR author is responsible for its resolution. Help will be provided if necessary although GitHub makes this easier by allowing simple conflict resolution using the [conflict-editor](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github). + +## Pull Request Builds + +When submitting a PR to the `Ryujinx/Ryujinx` repository, various builds will run validating many areas to ensure we keep developer productivity and product quality high. These various workflows can be tracked in the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of the repository. If the job continues to completion, the build artifacts will be uploaded and posted as a comment in the PR discussion. + +## Review Turnaround Times + +Ryujinx is a project that is maintained by volunteers on a completely free-time basis. As such we cannot guarantee any particular timeframe for pull request review and approval. Weeks to months are common for larger (>500 line) PRs but there are some additional best practises to avoid review purgatory. + +* Make the reviewers life easier wherever possible. Make use of descriptive commit names, code comments and XML docs where applicable. +* If there is disagreement on feedback then always lean on the side of the development team and community over any personal opinion. +* We're human. We miss things. We forget things. If there has been radio silence on your changes for a substantial period of time then do not hesitate to reach out directly either with something simple like "bump" on GitHub or a directly on Discord. + +To re-iterate, make the review as easy for us as possible, respond promptly and be comfortable to interact directly with us for anything else. + +## Merging Pull Requests + +Anyone with write access can merge a pull request manually when the following conditions have been met: + +* The PR has been approved by two reviewers and any other objections are addressed. + * You can request follow up reviews from the original reviewers if they requested changes. +* The PR successfully builds and passes all tests in the Continuous Integration (CI) system. In case of failures, refer to the [Actions](https://github.com/Ryujinx/Ryujinx/actions) tab of your PR. + +Typically, PRs are merged as one commit (squash merges). It creates a simpler history than a Merge Commit. "Special circumstances" are rare, and typically mean that there are a series of cleanly separated changes that will be too hard to understand if squashed together, or for some reason we want to preserve the ability to dissect them. + +## Blocking Pull Request Merging + +If for whatever reason you would like to move your pull request back to an in-progress status to avoid merging it in the current form, you can turn the PR into a draft PR by selecting the option under the reviewers section. Alternatively, you can do that by adding [WIP] prefix to the pull request title. + +## Old Pull Request Policy + +From time to time we will review older PRs and check them for relevance. If we find the PR is inactive or no longer applies, we will close it. As the PR owner, you can simply reopen it if you feel your closed PR needs our attention. + diff --git a/src/ARMeilleure/Diagnostics/Symbols.cs b/src/ARMeilleure/Diagnostics/Symbols.cs index 86469d8bb..d857c079f 100644 --- a/src/ARMeilleure/Diagnostics/Symbols.cs +++ b/src/ARMeilleure/Diagnostics/Symbols.cs @@ -1,6 +1,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Text; namespace ARMeilleure.Diagnostics { @@ -33,7 +34,6 @@ namespace ARMeilleure.Diagnostics public static string Get(ulong address) { - if (_symbols.TryGetValue(address, out string result)) { return result; @@ -48,13 +48,15 @@ namespace ARMeilleure.Diagnostics ulong diff = address - symbol.Start; ulong rem = diff % symbol.ElementSize; - result = symbol.Name + "_" + diff / symbol.ElementSize; + StringBuilder resultBuilder = new(); + resultBuilder.Append($"{symbol.Name}_{diff / symbol.ElementSize}"); if (rem != 0) { - result += "+" + rem; + resultBuilder.Append($"+{rem}"); } + result = resultBuilder.ToString(); _symbols.TryAdd(address, result); return result; diff --git a/src/ARMeilleure/Translation/IntervalTree.cs b/src/ARMeilleure/Translation/IntervalTree.cs index afd89b930..da29d6a68 100644 --- a/src/ARMeilleure/Translation/IntervalTree.cs +++ b/src/ARMeilleure/Translation/IntervalTree.cs @@ -189,7 +189,7 @@ namespace ARMeilleure.Translation { if (start.CompareTo(node.End) < 0) { - if (overlaps.Length >= overlapCount) + if (overlaps.Length <= overlapCount) { Array.Resize(ref overlaps, overlapCount + ArrayGrowthSize); } diff --git a/src/ARMeilleure/Translation/TranslatorCache.cs b/src/ARMeilleure/Translation/TranslatorCache.cs index 11286381b..99ca58dc6 100644 --- a/src/ARMeilleure/Translation/TranslatorCache.cs +++ b/src/ARMeilleure/Translation/TranslatorCache.cs @@ -7,14 +7,14 @@ namespace ARMeilleure.Translation internal class TranslatorCache { private readonly IntervalTree _tree; - private readonly ReaderWriterLock _treeLock; + private readonly ReaderWriterLockSlim _treeLock; public int Count => _tree.Count; public TranslatorCache() { _tree = new IntervalTree(); - _treeLock = new ReaderWriterLock(); + _treeLock = new ReaderWriterLockSlim(); } public bool TryAdd(ulong address, ulong size, T value) @@ -24,70 +24,70 @@ namespace ARMeilleure.Translation public bool AddOrUpdate(ulong address, ulong size, T value, Func updateFactoryCallback) { - _treeLock.AcquireWriterLock(Timeout.Infinite); + _treeLock.EnterWriteLock(); bool result = _tree.AddOrUpdate(address, address + size, value, updateFactoryCallback); - _treeLock.ReleaseWriterLock(); + _treeLock.ExitWriteLock(); return result; } public T GetOrAdd(ulong address, ulong size, T value) { - _treeLock.AcquireWriterLock(Timeout.Infinite); + _treeLock.EnterWriteLock(); value = _tree.GetOrAdd(address, address + size, value); - _treeLock.ReleaseWriterLock(); + _treeLock.ExitWriteLock(); return value; } public bool Remove(ulong address) { - _treeLock.AcquireWriterLock(Timeout.Infinite); + _treeLock.EnterWriteLock(); bool removed = _tree.Remove(address) != 0; - _treeLock.ReleaseWriterLock(); + _treeLock.ExitWriteLock(); return removed; } public void Clear() { - _treeLock.AcquireWriterLock(Timeout.Infinite); + _treeLock.EnterWriteLock(); _tree.Clear(); - _treeLock.ReleaseWriterLock(); + _treeLock.ExitWriteLock(); } public bool ContainsKey(ulong address) { - _treeLock.AcquireReaderLock(Timeout.Infinite); + _treeLock.EnterReadLock(); bool result = _tree.ContainsKey(address); - _treeLock.ReleaseReaderLock(); + _treeLock.ExitReadLock(); return result; } public bool TryGetValue(ulong address, out T value) { - _treeLock.AcquireReaderLock(Timeout.Infinite); + _treeLock.EnterReadLock(); bool result = _tree.TryGet(address, out value); - _treeLock.ReleaseReaderLock(); + _treeLock.ExitReadLock(); return result; } public int GetOverlaps(ulong address, ulong size, ref ulong[] overlaps) { - _treeLock.AcquireReaderLock(Timeout.Infinite); + _treeLock.EnterReadLock(); int count = _tree.Get(address, address + size, ref overlaps); - _treeLock.ReleaseReaderLock(); + _treeLock.ExitReadLock(); return count; } public List AsList() { - _treeLock.AcquireReaderLock(Timeout.Infinite); + _treeLock.EnterReadLock(); List list = _tree.AsList(); - _treeLock.ReleaseReaderLock(); + _treeLock.ExitReadLock(); return list; } diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs index 7ed32800f..73d66dcf4 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs @@ -31,9 +31,18 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command public bool IsEffectEnabled { get; } - public AuxiliaryBufferCommand(uint bufferOffset, byte inputBufferOffset, byte outputBufferOffset, - ref AuxiliaryBufferAddresses sendBufferInfo, bool isEnabled, uint countMax, - CpuAddress outputBuffer, CpuAddress inputBuffer, uint updateCount, uint writeOffset, int nodeId) + public AuxiliaryBufferCommand( + uint bufferOffset, + byte inputBufferOffset, + byte outputBufferOffset, + ref AuxiliaryBufferAddresses sendBufferInfo, + bool isEnabled, + uint countMax, + CpuAddress outputBuffer, + CpuAddress inputBuffer, + uint updateCount, + uint writeOffset, + int nodeId) { Enabled = true; NodeId = nodeId; diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs index f56dd70e3..ac1e581f6 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs @@ -21,7 +21,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command private BiquadFilterParameter _parameter; - public BiquadFilterCommand(int baseIndex, ref BiquadFilterParameter filter, Memory biquadFilterStateMemory, int inputBufferOffset, int outputBufferOffset, bool needInitialization, int nodeId) + public BiquadFilterCommand( + int baseIndex, + ref BiquadFilterParameter filter, + Memory biquadFilterStateMemory, + int inputBufferOffset, + int outputBufferOffset, + bool needInitialization, + int nodeId) { _parameter = filter; BiquadFilterState = biquadFilterStateMemory; diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs index 19a9576f7..3fe106ddf 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs @@ -77,7 +77,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ClearBuffer(int index) { - Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount); + Unsafe.InitBlock((void*)GetBufferPointer(index), 0, SampleCount * sizeof(float)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -89,7 +89,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void CopyBuffer(int outputBufferIndex, int inputBufferIndex) { - Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount); + Unsafe.CopyBlock((void*)GetBufferPointer(outputBufferIndex), (void*)GetBufferPointer(inputBufferIndex), SampleCount * sizeof(float)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs index 01291852e..1d5917bbe 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/CompressorCommand.cs @@ -94,18 +94,18 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command float newMean = inputMovingAverage.Update(FloatingPointHelper.MeanSquare(channelInput), _parameter.InputGain); float y = FloatingPointHelper.Log10(newMean) * 10.0f; - float z = 0.0f; + float z = 1.0f; - bool unknown10OutOfRange = false; + bool unknown10OutOfRange = y >= state.Unknown10; if (newMean < 1.0e-10f) { - z = 1.0f; + y = -100.0f; - unknown10OutOfRange = state.Unknown10 < -100.0f; + unknown10OutOfRange = state.Unknown10 <= -100.0f; } - if (y >= state.Unknown10 || unknown10OutOfRange) + if (unknown10OutOfRange) { float tmpGain; @@ -118,7 +118,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command tmpGain = (y - state.Unknown10) * ((y - state.Unknown10) * -state.CompressorGainReduction); } - z = FloatingPointHelper.DecibelToLinearExtended(tmpGain); + z = FloatingPointHelper.DecibelToLinear(tmpGain); } float unknown4New = z; diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs index 003806cf7..6fa3777f4 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs @@ -88,7 +88,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); Matrix2x2 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, - delayFeedbackCrossGain, delayFeedbackBaseGain); + delayFeedbackCrossGain, delayFeedbackBaseGain); for (int i = 0; i < sampleCount; i++) { @@ -125,9 +125,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); Matrix4x4 delayFeedback = new(delayFeedbackBaseGain, delayFeedbackCrossGain, delayFeedbackCrossGain, 0.0f, - delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, - delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, - 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain); + delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, + delayFeedbackCrossGain, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, + 0.0f, delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain); for (int i = 0; i < sampleCount; i++) @@ -172,11 +172,11 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); Matrix6x6 delayFeedback = new(delayFeedbackBaseGain, 0.0f, delayFeedbackCrossGain, 0.0f, delayFeedbackCrossGain, 0.0f, - 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, - delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f, - delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, - 0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain); + 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, + delayFeedbackCrossGain, delayFeedbackCrossGain, delayFeedbackBaseGain, 0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f, feedbackGain, 0.0f, 0.0f, + delayFeedbackCrossGain, 0.0f, 0.0f, 0.0f, delayFeedbackBaseGain, delayFeedbackCrossGain, + 0.0f, delayFeedbackCrossGain, 0.0f, 0.0f, delayFeedbackCrossGain, delayFeedbackBaseGain); for (int i = 0; i < sampleCount; i++) { diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs index 682098670..f6e1654dd 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/LimiterCommandVersion2.cs @@ -28,7 +28,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command private LimiterParameter _parameter; - public LimiterCommandVersion2(uint bufferOffset, LimiterParameter parameter, Memory state, Memory resultState, bool isEnabled, ulong workBuffer, int nodeId) + public LimiterCommandVersion2( + uint bufferOffset, + LimiterParameter parameter, + Memory state, + Memory resultState, + bool isEnabled, + ulong workBuffer, + int nodeId) { Enabled = true; NodeId = nodeId; diff --git a/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs index f494b3028..874eb8e8b 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs @@ -79,53 +79,57 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ProcessReverbMono(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) { - ProcessReverbGeneric(ref state, - outputBuffers, - inputBuffers, - sampleCount, - _outputEarlyIndicesTableMono, - _targetEarlyDelayLineIndicesTableMono, - _targetOutputFeedbackIndicesTableMono, - _outputIndicesTableMono); + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableMono, + _targetEarlyDelayLineIndicesTableMono, + _targetOutputFeedbackIndicesTableMono, + _outputIndicesTableMono); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ProcessReverbStereo(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) { - ProcessReverbGeneric(ref state, - outputBuffers, - inputBuffers, - sampleCount, - _outputEarlyIndicesTableStereo, - _targetEarlyDelayLineIndicesTableStereo, - _targetOutputFeedbackIndicesTableStereo, - _outputIndicesTableStereo); + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableStereo, + _targetEarlyDelayLineIndicesTableStereo, + _targetOutputFeedbackIndicesTableStereo, + _outputIndicesTableStereo); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ProcessReverbQuadraphonic(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) { - ProcessReverbGeneric(ref state, - outputBuffers, - inputBuffers, - sampleCount, - _outputEarlyIndicesTableQuadraphonic, - _targetEarlyDelayLineIndicesTableQuadraphonic, - _targetOutputFeedbackIndicesTableQuadraphonic, - _outputIndicesTableQuadraphonic); + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableQuadraphonic, + _targetEarlyDelayLineIndicesTableQuadraphonic, + _targetOutputFeedbackIndicesTableQuadraphonic, + _outputIndicesTableQuadraphonic); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ProcessReverbSurround(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount) { - ProcessReverbGeneric(ref state, - outputBuffers, - inputBuffers, - sampleCount, - _outputEarlyIndicesTableSurround, - _targetEarlyDelayLineIndicesTableSurround, - _targetOutputFeedbackIndicesTableSurround, - _outputIndicesTableSurround); + ProcessReverbGeneric( + ref state, + outputBuffers, + inputBuffers, + sampleCount, + _outputEarlyIndicesTableSurround, + _targetEarlyDelayLineIndicesTableSurround, + _targetOutputFeedbackIndicesTableSurround, + _outputIndicesTableSurround); } private unsafe void ProcessReverbGeneric(ref ReverbState state, ReadOnlySpan outputBuffers, ReadOnlySpan inputBuffers, uint sampleCount, ReadOnlySpan outputEarlyIndicesTable, ReadOnlySpan targetEarlyDelayLineIndicesTable, ReadOnlySpan targetOutputFeedbackIndicesTable, ReadOnlySpan outputIndicesTable) diff --git a/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs index b231dbb6a..415e1c195 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs @@ -52,7 +52,7 @@ namespace Ryujinx.Audio.Renderer.Dsp { // NOTE: Nintendo uses an approximation of log10, we don't. // As such, we support the same ranges as Nintendo to avoid unexpected behaviours. - return MathF.Pow(10, MathF.Max(x, 1.0e-10f)); + return MathF.Log10(MathF.Max(x, 1.0e-10f)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -62,7 +62,8 @@ namespace Ryujinx.Audio.Renderer.Dsp foreach (float input in inputs) { - res += (input * input); + float normInput = input * (1f / 32768f); + res += normInput * normInput; } res /= inputs.Length; @@ -81,19 +82,6 @@ namespace Ryujinx.Audio.Renderer.Dsp return MathF.Pow(10.0f, db / 20.0f); } - /// - /// Map decibel to linear in [0, 2] range. - /// - /// The decibel value to convert - /// Converted linear value in [0, 2] range - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static float DecibelToLinearExtended(float db) - { - float tmp = MathF.Log2(DecibelToLinear(db)); - - return MathF.Truncate(tmp) + MathF.Pow(2.0f, tmp - MathF.Truncate(tmp)); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float DegreesToRadians(float degrees) { diff --git a/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs index d209c515b..8134e6b77 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs @@ -20,6 +20,11 @@ namespace Ryujinx.Audio.Renderer.Dsp [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int GetBufferSize(int startSampleOffset, int endSampleOffset, int offset, int count) where T : unmanaged { + if (endSampleOffset < startSampleOffset) + { + return 0; + } + return GetCountToDecode(startSampleOffset, endSampleOffset, offset, count) * Unsafe.SizeOf(); } diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs index 76aff8072..9ee573205 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/CompressorState.cs @@ -3,7 +3,7 @@ using Ryujinx.Audio.Renderer.Parameter.Effect; namespace Ryujinx.Audio.Renderer.Dsp.State { - public class CompressorState + public struct CompressorState { public ExponentialMovingAverage InputMovingAverage; public float Unknown4; @@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State CompressorGainReduction = (1.0f - ratio) / Constants.ChannelCountMax; Unknown10 = threshold - 1.5f; Unknown14 = threshold + 1.5f; - OutputGain = FloatingPointHelper.DecibelToLinearExtended(parameter.OutputGain + makeupGain); + OutputGain = FloatingPointHelper.DecibelToLinear(parameter.OutputGain + makeupGain); } } } diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs index c56fa078a..17ad2a40d 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Renderer.Dsp.State { - public class DelayState + public struct DelayState { public DelayLine[] DelayLines { get; } public float[] LowPassZ { get; set; } @@ -53,7 +53,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State LowPassBaseGain = 1.0f - LowPassFeedbackGain; } - public void UpdateLowPassFilter(ref float tempRawRef, uint channelCount) + public readonly void UpdateLowPassFilter(ref float tempRawRef, uint channelCount) { for (int i = 0; i < channelCount; i++) { diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs index 80d1cb62e..1388bfcef 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/LimiterState.cs @@ -4,7 +4,7 @@ using System; namespace Ryujinx.Audio.Renderer.Dsp.State { - public class LimiterState + public struct LimiterState { public ExponentialMovingAverage[] DetectorAverage; public ExponentialMovingAverage[] CompressionGainAverage; diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs index 5056b750e..e83e0d5fc 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs @@ -4,7 +4,7 @@ using System; namespace Ryujinx.Audio.Renderer.Dsp.State { - public class Reverb3dState + public struct Reverb3dState { private readonly float[] _fdnDelayMinTimes = new float[4] { 5.0f, 6.0f, 13.0f, 14.0f }; private readonly float[] _fdnDelayMaxTimes = new float[4] { 45.704f, 82.782f, 149.94f, 271.58f }; diff --git a/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs b/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs index 2f574f475..f1927b718 100644 --- a/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs +++ b/src/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs @@ -5,7 +5,7 @@ using System; namespace Ryujinx.Audio.Renderer.Dsp.State { - public class ReverbState + public struct ReverbState { private static readonly float[] _fdnDelayTimes = new float[20] { @@ -54,7 +54,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.State // Room 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, // Chamber - 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f, + 0.70f, 0.68f, 0.70f, 0.68f, 0.70f, 0.68f, 0.68f, 0.68f, 0.68f, 0.68f, // Hall 0.50f, 0.70f, 0.70f, 0.68f, 0.50f, 0.68f, 0.68f, 0.70f, 0.68f, 0.00f, // Cathedral diff --git a/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs index 86f92442b..f33d82aa0 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs @@ -264,8 +264,8 @@ namespace Ryujinx.Audio.Renderer.Parameter { uint dataTypeSize = (uint)Unsafe.SizeOf(); - return StartSampleOffset * dataTypeSize <= Size && - EndSampleOffset * dataTypeSize <= Size; + return (ulong)StartSampleOffset * dataTypeSize <= Size && + (ulong)EndSampleOffset * dataTypeSize <= Size; } /// diff --git a/src/Ryujinx.Ava/App.axaml.cs b/src/Ryujinx.Ava/App.axaml.cs index 031e7e447..c1a3ab3e2 100644 --- a/src/Ryujinx.Ava/App.axaml.cs +++ b/src/Ryujinx.Ava/App.axaml.cs @@ -104,7 +104,7 @@ namespace Ryujinx.Ava { "Light" => ThemeVariant.Light, "Dark" => ThemeVariant.Dark, - _ => ThemeVariant.Default + _ => ThemeVariant.Default, }; if (enableCustomTheme) diff --git a/src/Ryujinx.Ava/AppHost.cs b/src/Ryujinx.Ava/AppHost.cs index a8388e9cd..c473cf562 100644 --- a/src/Ryujinx.Ava/AppHost.cs +++ b/src/Ryujinx.Ava/AppHost.cs @@ -3,7 +3,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Input; -using Avalonia.Rendering; using Avalonia.Threading; using LibHac.Tools.FsSystem; using Ryujinx.Audio.Backends.Dummy; @@ -21,6 +20,7 @@ using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common; using Ryujinx.Common.Configuration; +using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.Logging; using Ryujinx.Common.SystemInterop; using Ryujinx.Graphics.GAL; @@ -191,6 +191,7 @@ namespace Ryujinx.Ava ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough; ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState; + ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState; _gpuCancellationTokenSource = new CancellationTokenSource(); _gpuDoneEvent = new ManualResetEvent(false); @@ -412,6 +413,11 @@ namespace Ryujinx.Ava Device.Configuration.MultiplayerLanInterfaceId = e.NewValue; } + private void UpdateMultiplayerModeState(object sender, ReactiveEventArgs e) + { + Device.Configuration.MultiplayerMode = e.NewValue; + } + public void Stop() { _isActive = false; @@ -782,7 +788,8 @@ namespace Ryujinx.Ava ConfigurationState.Instance.Graphics.AspectRatio, ConfigurationState.Instance.System.AudioVolume, ConfigurationState.Instance.System.UseHypervisor, - ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value); + ConfigurationState.Instance.Multiplayer.LanInterfaceId.Value, + ConfigurationState.Instance.Multiplayer.Mode); Device = new Switch(configuration); } diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json index d547f9162..c71241f66 100644 --- a/src/Ryujinx.Ava/Assets/Locales/en_US.json +++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json @@ -546,7 +546,7 @@ "SwkbdMinCharacters": "Must be at least {0} characters long", "SwkbdMinRangeCharacters": "Must be {0}-{1} characters long", "SoftwareKeyboard": "Software Keyboard", - "SoftwareKeyboardModeNumbersOnly": "Must be numbers only", + "SoftwareKeyboardModeNumeric": "Must be 0-9 or '.' only", "SoftwareKeyboardModeAlphabet": "Must be non CJK-characters only", "SoftwareKeyboardModeASCII": "Must be ASCII text only", "DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.", @@ -654,5 +654,8 @@ "NetworkInterfaceDefault": "Default", "PackagingShaders": "Packaging Shaders", "AboutChangelogButton": "View Changelog on GitHub", - "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser." + "AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser.", + "SettingsTabNetworkMultiplayer": "Multiplayer", + "MultiplayerMode": "Mode:", + "MultiplayerModeTooltip": "Change multiplayer mode" } diff --git a/src/Ryujinx.Ava/Common/ApplicationHelper.cs b/src/Ryujinx.Ava/Common/ApplicationHelper.cs index 9e4757538..b8cd06f3d 100644 --- a/src/Ryujinx.Ava/Common/ApplicationHelper.cs +++ b/src/Ryujinx.Ava/Common/ApplicationHelper.cs @@ -15,7 +15,6 @@ using LibHac.Tools.FsSystem.NcaUtils; using Ryujinx.Ava.Common.Locale; using Ryujinx.Ava.UI.Controls; using Ryujinx.Ava.UI.Helpers; -using Ryujinx.Ava.UI.Windows; using Ryujinx.Common.Logging; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Account.Acc; @@ -36,11 +35,9 @@ namespace Ryujinx.Ava.Common private static HorizonClient _horizonClient; private static AccountManager _accountManager; private static VirtualFileSystem _virtualFileSystem; - private static StyleableWindow _owner; - public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient, StyleableWindow owner) + public static void Initialize(VirtualFileSystem virtualFileSystem, AccountManager accountManager, HorizonClient horizonClient) { - _owner = owner; _virtualFileSystem = virtualFileSystem; _horizonClient = horizonClient; _accountManager = accountManager; @@ -148,7 +145,7 @@ namespace Ryujinx.Ava.Common var result = await storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { Title = LocaleManager.Instance[LocaleKeys.FolderDialogExtractTitle], - AllowMultiple = false + AllowMultiple = false, }); if (result.Count == 0) diff --git a/src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs b/src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs index cd1c1510d..8d345ae08 100644 --- a/src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs +++ b/src/Ryujinx.Ava/Common/Locale/LocaleExtension.cs @@ -1,6 +1,7 @@ -using Avalonia.Data; +using Avalonia.Data.Core; using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings; using System; namespace Ryujinx.Ava.Common.Locale @@ -18,11 +19,20 @@ namespace Ryujinx.Ava.Common.Locale { LocaleKeys keyToUse = Key; - ReflectionBindingExtension binding = new($"[{keyToUse}]") - { - Mode = BindingMode.OneWay, - Source = LocaleManager.Instance, - }; + var builder = new CompiledBindingPathBuilder(); + + builder.SetRawSource(LocaleManager.Instance) + .Property(new ClrPropertyInfo("Item", + obj => (LocaleManager.Instance[keyToUse]), + null, + typeof(string)), (weakRef, iPropInfo) => + { + return PropertyInfoAccessorFactory.CreateInpcPropertyAccessor(weakRef, iPropInfo); + }); + + var path = builder.Build(); + + var binding = new CompiledBindingExtension(path); return binding.ProvideValue(serviceProvider); } diff --git a/src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs b/src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs index 856ed6f7e..bff24a81e 100644 --- a/src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs +++ b/src/Ryujinx.Ava/Input/AvaloniaKeyboardDriver.cs @@ -1,6 +1,5 @@ using Avalonia.Controls; using Avalonia.Input; -using Avalonia.Interactivity; using Ryujinx.Ava.Common.Locale; using Ryujinx.Input; using System; diff --git a/src/Ryujinx.Ava/Modules/Updater/Updater.cs b/src/Ryujinx.Ava/Modules/Updater/Updater.cs index 5fa5241df..af7608d34 100644 --- a/src/Ryujinx.Ava/Modules/Updater/Updater.cs +++ b/src/Ryujinx.Ava/Modules/Updater/Updater.cs @@ -82,12 +82,9 @@ namespace Ryujinx.Modules { Logger.Error?.Print(LogClass.Application, "Failed to convert the current Ryujinx version!"); - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - }); + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); _running = false; @@ -114,10 +111,9 @@ namespace Ryujinx.Modules { if (showVersionUpToDate) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); - }); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } _running = false; @@ -134,10 +130,9 @@ namespace Ryujinx.Modules { if (showVersionUpToDate) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); - }); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } _running = false; @@ -149,10 +144,8 @@ namespace Ryujinx.Modules { Logger.Error?.Print(LogClass.Application, exception.Message); - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); - }); + await ContentDialogHelper.CreateErrorDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterFailedToGetVersionMessage]); _running = false; @@ -167,12 +160,9 @@ namespace Ryujinx.Modules { Logger.Error?.Print(LogClass.Application, "Failed to convert the received Ryujinx version from Github!"); - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateWarningDialog( - LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); - }); + await ContentDialogHelper.CreateWarningDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterConvertFailedGithubMessage], + LocaleManager.Instance[LocaleKeys.DialogUpdaterCancelUpdateMessage]); _running = false; @@ -183,10 +173,9 @@ namespace Ryujinx.Modules { if (showVersionUpToDate) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateUpdaterInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], ""); - }); + await ContentDialogHelper.CreateUpdaterInfoDialog( + LocaleManager.Instance[LocaleKeys.DialogUpdaterAlreadyOnLatestVersionMessage], + ""); } _running = false; @@ -212,7 +201,7 @@ namespace Ryujinx.Modules _buildSize = -1; } - Dispatcher.UIThread.Post(async () => + await Dispatcher.UIThread.InvokeAsync(async () => { // Show a message asking the user if they want to update var shouldUpdate = await ContentDialogHelper.CreateChoiceDialog( @@ -222,7 +211,7 @@ namespace Ryujinx.Modules if (shouldUpdate) { - UpdateRyujinx(mainWindow, _buildUrl); + await UpdateRyujinx(mainWindow, _buildUrl); } else { @@ -241,7 +230,7 @@ namespace Ryujinx.Modules return result; } - private static async void UpdateRyujinx(Window parent, string downloadUrl) + private static async Task UpdateRyujinx(Window parent, string downloadUrl) { _updateSuccessful = false; @@ -579,27 +568,24 @@ namespace Ryujinx.Modules } } - private static async void InstallUpdate(TaskDialog taskDialog, string updateFile) + private static void InstallUpdate(TaskDialog taskDialog, string updateFile) { // Extract Update taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting]; taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - await Task.Run(() => + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) { - if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) - { - ExtractTarGzipFile(taskDialog, updateFile, _updateDir); - } - else if (OperatingSystem.IsWindows()) - { - ExtractZipFile(taskDialog, updateFile, _updateDir); - } - else - { - throw new NotSupportedException(); - } - }); + ExtractTarGzipFile(taskDialog, updateFile, _updateDir); + } + else if (OperatingSystem.IsWindows()) + { + ExtractZipFile(taskDialog, updateFile, _updateDir); + } + else + { + throw new NotSupportedException(); + } // Delete downloaded zip File.Delete(updateFile); @@ -613,36 +599,33 @@ namespace Ryujinx.Modules if (!OperatingSystem.IsMacOS()) { // Replace old files - await Task.Run(() => + double count = 0; + foreach (string file in allFiles) { - double count = 0; - foreach (string file in allFiles) + count++; + try { - count++; - try - { - File.Move(file, file + ".ryuold"); + File.Move(file, file + ".ryuold"); - Dispatcher.UIThread.Post(() => - { - taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); - }); - } - catch + Dispatcher.UIThread.InvokeAsync(() => { - Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); - } + taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal); + }); } - - Dispatcher.UIThread.Post(() => + catch { - taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; - taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); - }); + Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file)); + } + } - MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); + Dispatcher.UIThread.InvokeAsync(() => + { + taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles]; + taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal); }); + MoveAllFilesOver(_updatePublishDir, _homeDir, taskDialog); + Directory.Delete(_updateDir, true); } @@ -658,12 +641,11 @@ namespace Ryujinx.Modules { if (showWarnings) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateWarningDialog( + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]); - }); + LocaleManager.Instance[LocaleKeys.DialogUpdaterArchNotSupportedSubMessage]) + ); } return false; @@ -673,12 +655,11 @@ namespace Ryujinx.Modules { if (showWarnings) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateWarningDialog( + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]); - }); + LocaleManager.Instance[LocaleKeys.DialogUpdaterNoInternetSubMessage]) + ); } return false; @@ -688,12 +669,11 @@ namespace Ryujinx.Modules { if (showWarnings) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateWarningDialog( + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildMessage], - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]); - }); + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) + ); } return false; @@ -705,21 +685,19 @@ namespace Ryujinx.Modules { if (ReleaseInformation.IsFlatHubBuild()) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateWarningDialog( + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], - LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]); - }); + LocaleManager.Instance[LocaleKeys.DialogUpdaterFlatpakNotSupportedMessage]) + ); } else { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateWarningDialog( + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateWarningDialog( LocaleManager.Instance[LocaleKeys.UpdaterDisabledWarningTitle], - LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]); - }); + LocaleManager.Instance[LocaleKeys.DialogUpdaterDirtyBuildSubMessage]) + ); } } diff --git a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs b/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs index a2a944297..9fc7c6b6d 100644 --- a/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs +++ b/src/Ryujinx.Ava/UI/Applet/AvaHostUiHandler.cs @@ -106,7 +106,7 @@ namespace Ryujinx.Ava.UI.Applet bool error = false; string inputText = args.InitialText ?? ""; - Dispatcher.UIThread.Post(async () => + Dispatcher.UIThread.InvokeAsync(async () => { try { @@ -149,7 +149,7 @@ namespace Ryujinx.Ava.UI.Applet bool showDetails = false; - Dispatcher.UIThread.Post(async () => + Dispatcher.UIThread.InvokeAsync(async () => { try { diff --git a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml index a70fc8d44..6186b7d93 100644 --- a/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml +++ b/src/Ryujinx.Ava/UI/Applet/ErrorAppletWindow.axaml @@ -6,9 +6,11 @@ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Title="{locale:Locale ErrorWindowTitle}" + xmlns:views="using:Ryujinx.Ava.UI.Applet" Width="450" Height="340" CanResize="False" + x:DataType="views:ErrorAppletWindow" SizeToContent="Height" mc:Ignorable="d" Focusable="True"> @@ -38,7 +40,7 @@ Grid.Column="1" Margin="10" VerticalAlignment="Stretch" - Text="{ReflectionBinding Message}" + Text="{Binding Message}" TextWrapping="Wrap" /> - \ No newline at end of file + diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml index 64b23f987..7e0836065 100644 --- a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml +++ b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml @@ -4,7 +4,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:views="using:Ryujinx.Ava.UI.Controls" Width="400" + x:DataType="views:SwkbdAppletDialog" mc:Ignorable="d" Focusable="True"> diff --git a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs index 210fc9a2b..5a4cd855f 100644 --- a/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs +++ b/src/Ryujinx.Ava/UI/Applet/SwkbdAppletDialog.axaml.cs @@ -136,10 +136,10 @@ namespace Ryujinx.Ava.UI.Controls string localeText; switch (mode) { - case KeyboardMode.NumbersOnly: - localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumbersOnly); + case KeyboardMode.Numeric: + localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumeric); validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText); - _checkInput = text => text.All(char.IsDigit); + _checkInput = text => text.All(NumericCharacterValidation.IsNumeric); break; case KeyboardMode.Alphabet: localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet); diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs index f54e9df8f..d75572e65 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs @@ -54,7 +54,7 @@ namespace Ryujinx.Ava.UI.Controls { if (sender is MenuItem { DataContext: MainWindowViewModel viewModel }) { - OpenSaveDirectory(viewModel, SaveDataType.Account, userId: new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low)); + OpenSaveDirectory(viewModel, SaveDataType.Account, new UserId((ulong)viewModel.AccountManager.LastOpenedUser.UserId.High, (ulong)viewModel.AccountManager.LastOpenedUser.UserId.Low)); } } @@ -62,14 +62,14 @@ namespace Ryujinx.Ava.UI.Controls { var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - OpenSaveDirectory(viewModel, SaveDataType.Device, userId: default); + OpenSaveDirectory(viewModel, SaveDataType.Device, default); } public void OpenBcatSaveDirectory_Click(object sender, RoutedEventArgs args) { var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; - OpenSaveDirectory(viewModel, SaveDataType.Bcat, userId: default); + OpenSaveDirectory(viewModel, SaveDataType.Bcat, default); } private static void OpenSaveDirectory(MainWindowViewModel viewModel, SaveDataType saveDataType, UserId userId) @@ -158,11 +158,12 @@ namespace Ryujinx.Ava.UI.Controls if (viewModel?.SelectedApplication != null) { - UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName), - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogPPTCDeletionMessage, viewModel.SelectedApplication.TitleName), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); if (result == UserResult.Yes) { @@ -205,11 +206,12 @@ namespace Ryujinx.Ava.UI.Controls if (viewModel?.SelectedApplication != null) { - UserResult result = await ContentDialogHelper.CreateConfirmationDialog(LocaleManager.Instance[LocaleKeys.DialogWarning], - LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName), - LocaleManager.Instance[LocaleKeys.InputDialogYes], - LocaleManager.Instance[LocaleKeys.InputDialogNo], - LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); + UserResult result = await ContentDialogHelper.CreateConfirmationDialog( + LocaleManager.Instance[LocaleKeys.DialogWarning], + LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogShaderDeletionMessage, viewModel.SelectedApplication.TitleName), + LocaleManager.Instance[LocaleKeys.InputDialogYes], + LocaleManager.Instance[LocaleKeys.InputDialogNo], + LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); if (result == UserResult.Yes) { @@ -335,13 +337,13 @@ namespace Ryujinx.Ava.UI.Controls } } - public void RunApplication_Click(object sender, RoutedEventArgs args) + public async void RunApplication_Click(object sender, RoutedEventArgs args) { var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel; if (viewModel?.SelectedApplication != null) { - viewModel.LoadApplication(viewModel.SelectedApplication.Path); + await viewModel.LoadApplication(viewModel.SelectedApplication.Path); } } } diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml index 214fc0755..bbdb4c4a7 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationGridView.axaml @@ -46,7 +46,7 @@ @@ -56,10 +56,10 @@ Margin="10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}" - Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}" - Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}" - Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}" + Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}" + Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" + Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" + Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" ClipToBounds="True" CornerRadius="4"> @@ -78,7 +78,7 @@ Margin="0,10,0,0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - IsVisible="{ReflectionBinding $parent[UserControl].DataContext.ShowNames}"> + IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}"> - \ No newline at end of file + diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml index 75bbf9d0d..09011005b 100644 --- a/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml +++ b/src/Ryujinx.Ava/UI/Controls/ApplicationListView.axaml @@ -42,7 +42,7 @@ @@ -67,10 +67,10 @@ Grid.RowSpan="3" Grid.Column="0" Margin="0" - Classes.huge="{ReflectionBinding $parent[UserControl].DataContext.IsGridHuge}" - Classes.large="{ReflectionBinding $parent[UserControl].DataContext.IsGridLarge}" - Classes.normal="{ReflectionBinding $parent[UserControl].DataContext.IsGridMedium}" - Classes.small="{ReflectionBinding $parent[UserControl].DataContext.IsGridSmall}" + Classes.huge="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridHuge}" + Classes.large="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridLarge}" + Classes.normal="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridMedium}" + Classes.small="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).IsGridSmall}" Source="{Binding Icon, Converter={StaticResource ByteImage}}" /> - \ No newline at end of file + diff --git a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs index 3138dd146..83624f5f3 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/AmiiboWindowViewModel.cs @@ -327,7 +327,7 @@ namespace Ryujinx.Ava.UI.ViewModels string imageUrl = _amiiboList.Find(amiibo => amiibo.Equals(selected)).Image; - string usageString = ""; + StringBuilder usageStringBuilder = new(); for (int i = 0; i < _amiiboList.Count; i++) { @@ -341,20 +341,19 @@ namespace Ryujinx.Ava.UI.ViewModels { foreach (AmiiboApiUsage usageItem in item.AmiiboUsage) { - usageString += Environment.NewLine + - $"- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"; + usageStringBuilder.Append($"{Environment.NewLine}- {usageItem.Usage.Replace("/", Environment.NewLine + "-")}"); writable = usageItem.Write; } } } - if (usageString.Length == 0) + if (usageStringBuilder.Length == 0) { - usageString = LocaleManager.Instance[LocaleKeys.Unknown] + "."; + usageStringBuilder.Append($"{LocaleManager.Instance[LocaleKeys.Unknown]}."); } - Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageString}"; + Usage = $"{LocaleManager.Instance[LocaleKeys.Usage]} {(writable ? $" ({LocaleManager.Instance[LocaleKeys.Writable]})" : "")} : {usageStringBuilder}"; } } diff --git a/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs index 74b737511..b88bd3d9c 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs @@ -212,9 +212,9 @@ namespace Ryujinx.Ava.UI.ViewModels { Patterns = new[] { "*.nsp" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, - MimeTypes = new[] { "application/x-nx-nsp" } - } - } + MimeTypes = new[] { "application/x-nx-nsp" }, + }, + }, }); foreach (var file in result) diff --git a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs index aa6e0326f..7a9e4df14 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs @@ -26,6 +26,7 @@ using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.Ui; +using Ryujinx.Input.HLE; using Ryujinx.Modules; using Ryujinx.Ui.App.Common; using Ryujinx.Ui.Common; @@ -39,7 +40,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Image = SixLabors.ImageSharp.Image; -using InputManager = Ryujinx.Input.HLE.InputManager; using Key = Ryujinx.Input.Key; using MissingKeyException = LibHac.Common.Keys.MissingKeyException; using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; @@ -1068,9 +1068,7 @@ namespace Ryujinx.Ava.UI.ViewModels { Logger.Error?.Print(LogClass.Application, ex.ToString()); - static async void Action() => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys); - - Dispatcher.UIThread.Post(Action); + await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys); } } catch (Exception ex) @@ -1163,16 +1161,13 @@ namespace Ryujinx.Ava.UI.ViewModels AppHost?.DisposeContext(); } - private void HandleRelaunch() + private async Task HandleRelaunch() { if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart) { UserChannelPersistence.ShouldRestart = false; - Dispatcher.UIThread.Post(() => - { - LoadApplication(_currentEmulatedGamePath); - }); + await LoadApplication(_currentEmulatedGamePath); } else { @@ -1191,7 +1186,7 @@ namespace Ryujinx.Ava.UI.ViewModels Application.Current.Styles.TryGetResource(args.VSyncEnabled ? "VsyncEnabled" : "VsyncDisabled", - Avalonia.Application.Current.ActualThemeVariant, + Application.Current.ActualThemeVariant, out object color); if (color is not null) @@ -1283,7 +1278,12 @@ namespace Ryujinx.Ava.UI.ViewModels Glyph = Glyph.Grid; } - public async void InstallFirmwareFromFile() + public void SetAspectRatio(AspectRatio aspectRatio) + { + ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio; + } + + public async Task InstallFirmwareFromFile() { var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { @@ -1294,21 +1294,21 @@ namespace Ryujinx.Ava.UI.ViewModels { Patterns = new[] { "*.xci", "*.zip" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" }, - MimeTypes = new[] { "application/x-nx-xci", "application/zip" } + MimeTypes = new[] { "application/x-nx-xci", "application/zip" }, }, new("XCI") { Patterns = new[] { "*.xci" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, - MimeTypes = new[] { "application/x-nx-xci" } + MimeTypes = new[] { "application/x-nx-xci" }, }, new("ZIP") { Patterns = new[] { "*.zip" }, AppleUniformTypeIdentifiers = new[] { "public.zip-archive" }, - MimeTypes = new[] { "application/zip" } + MimeTypes = new[] { "application/zip" }, }, - } + }, }); if (result.Count > 0) @@ -1317,11 +1317,11 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public async void InstallFirmwareFromFolder() + public async Task InstallFirmwareFromFolder() { var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { - AllowMultiple = false + AllowMultiple = false, }); if (result.Count > 0) @@ -1352,7 +1352,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public async void ExitCurrentState() + public async Task ExitCurrentState() { if (WindowState == WindowState.FullScreen) { @@ -1377,7 +1377,7 @@ namespace Ryujinx.Ava.UI.ViewModels } } - public async void ManageProfiles() + public async Task ManageProfiles() { await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); } @@ -1387,7 +1387,7 @@ namespace Ryujinx.Ava.UI.ViewModels AppHost.Device.System.SimulateWakeUpMessage(); } - public async void OpenFile() + public async Task OpenFile() { var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { @@ -1404,7 +1404,7 @@ namespace Ryujinx.Ava.UI.ViewModels "com.ryujinx.xci", "com.ryujinx.nca", "com.ryujinx.nro", - "com.ryujinx.nso" + "com.ryujinx.nso", }, MimeTypes = new[] { @@ -1412,63 +1412,63 @@ namespace Ryujinx.Ava.UI.ViewModels "application/x-nx-xci", "application/x-nx-nca", "application/x-nx-nro", - "application/x-nx-nso" - } + "application/x-nx-nso", + }, }, new("NSP") { Patterns = new[] { "*.nsp" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, - MimeTypes = new[] { "application/x-nx-nsp" } + MimeTypes = new[] { "application/x-nx-nsp" }, }, new("XCI") { Patterns = new[] { "*.xci" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, - MimeTypes = new[] { "application/x-nx-xci" } + MimeTypes = new[] { "application/x-nx-xci" }, }, new("NCA") { Patterns = new[] { "*.nca" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" }, - MimeTypes = new[] { "application/x-nx-nca" } + MimeTypes = new[] { "application/x-nx-nca" }, }, new("NRO") { Patterns = new[] { "*.nro" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" }, - MimeTypes = new[] { "application/x-nx-nro" } + MimeTypes = new[] { "application/x-nx-nro" }, }, new("NSO") { Patterns = new[] { "*.nso" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" }, - MimeTypes = new[] { "application/x-nx-nso" } + MimeTypes = new[] { "application/x-nx-nso" }, }, - } + }, }); if (result.Count > 0) { - LoadApplication(result[0].Path.LocalPath); + await LoadApplication(result[0].Path.LocalPath); } } - public async void OpenFolder() + public async Task OpenFolder() { var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle], - AllowMultiple = false + AllowMultiple = false, }); if (result.Count > 0) { - LoadApplication(result[0].Path.LocalPath); + await LoadApplication(result[0].Path.LocalPath); } } - public async void LoadApplication(string path, bool startFullscreen = false, string titleName = "") + public async Task LoadApplication(string path, bool startFullscreen = false, string titleName = "") { if (AppHost != null) { @@ -1505,35 +1505,30 @@ namespace Ryujinx.Ava.UI.ViewModels this, TopLevel); - async void Action() + if (!await AppHost.LoadGuestApplication()) { - if (!await AppHost.LoadGuestApplication()) - { - AppHost.DisposeContext(); - AppHost = null; + AppHost.DisposeContext(); + AppHost = null; - return; - } - - CanUpdate = false; - - LoadHeading = TitleName = titleName; - - if (string.IsNullOrWhiteSpace(titleName)) - { - LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name); - TitleName = AppHost.Device.Processes.ActiveApplication.Name; - } - - SwitchToRenderer(startFullscreen); - - _currentEmulatedGamePath = path; - - Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; - gameThread.Start(); + return; } - Dispatcher.UIThread.Post(Action); + CanUpdate = false; + + LoadHeading = TitleName = titleName; + + if (string.IsNullOrWhiteSpace(titleName)) + { + LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name); + TitleName = AppHost.Device.Processes.ActiveApplication.Name; + } + + SwitchToRenderer(startFullscreen); + + _currentEmulatedGamePath = path; + + Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; + gameThread.Start(); } public void SwitchToRenderer(bool startFullscreen) @@ -1596,7 +1591,7 @@ namespace Ryujinx.Ava.UI.ViewModels IsGameRunning = false; - Dispatcher.UIThread.InvokeAsync(() => + Dispatcher.UIThread.InvokeAsync(async () => { ShowMenuAndStatusBar = true; ShowContent = true; @@ -1609,7 +1604,7 @@ namespace Ryujinx.Ava.UI.ViewModels AppHost = null; - HandleRelaunch(); + await HandleRelaunch(); }); RendererHostControl.WindowCreated -= RendererHost_Created; diff --git a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs index 2669e8a26..217b0c10e 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/SettingsViewModel.cs @@ -10,6 +10,7 @@ using Ryujinx.Ava.UI.Helpers; using Ryujinx.Ava.UI.Windows; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; +using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.GraphicsDriver; using Ryujinx.Common.Logging; using Ryujinx.Graphics.Vulkan; @@ -54,6 +55,7 @@ namespace Ryujinx.Ava.UI.ViewModels public event Action CloseWindow; public event Action SaveSettingsEvent; private int _networkInterfaceIndex; + private int _multiplayerModeIndex; public int ResolutionScale { @@ -76,14 +78,13 @@ namespace Ryujinx.Ava.UI.ViewModels if (_graphicsBackendMultithreadingIndex != (int)ConfigurationState.Instance.Graphics.BackendThreading.Value) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], + Dispatcher.UIThread.InvokeAsync(() => + ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningMessage], "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], - LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]); - }); + LocaleManager.Instance[LocaleKeys.DialogSettingsBackendThreadingWarningTitle]) + ); } OnPropertyChanged(); @@ -147,6 +148,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool EnableTextureRecompression { get; set; } public bool EnableMacroHLE { get; set; } public bool EnableColorSpacePassthrough { get; set; } + public bool ColorSpacePassthroughAvailable => IsMacOS; public bool EnableFileLog { get; set; } public bool EnableStub { get; set; } public bool EnableInfo { get; set; } @@ -256,6 +258,11 @@ namespace Ryujinx.Ava.UI.ViewModels get => new(_networkInterfaces.Keys); } + public AvaloniaList MultiplayerModes + { + get => new(Enum.GetNames()); + } + public KeyboardHotkeys KeyboardHotkeys { get => _keyboardHotkeys; @@ -277,6 +284,16 @@ namespace Ryujinx.Ava.UI.ViewModels } } + public int MultiplayerModeIndex + { + get => _multiplayerModeIndex; + set + { + _multiplayerModeIndex = value; + ConfigurationState.Instance.Multiplayer.Mode.Value = (MultiplayerMode)_multiplayerModeIndex; + } + } + public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this() { _virtualFileSystem = virtualFileSystem; @@ -484,6 +501,8 @@ namespace Ryujinx.Ava.UI.ViewModels EnableFsAccessLog = config.Logger.EnableFsAccessLog; FsGlobalAccessLogMode = config.System.FsGlobalAccessLogMode; OpenglDebugLevel = (int)config.Logger.GraphicsDebugLevel.Value; + + MultiplayerModeIndex = (int)config.Multiplayer.Mode.Value; } public void SaveSettings() @@ -586,6 +605,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.Logger.GraphicsDebugLevel.Value = (GraphicsDebugLevel)OpenglDebugLevel; config.Multiplayer.LanInterfaceId.Value = _networkInterfaces[NetworkInterfaceList[NetworkInterfaceIndex]]; + config.Multiplayer.Mode.Value = (MultiplayerMode)MultiplayerModeIndex; config.ToFileFormat().SaveConfig(Program.ConfigurationPath); diff --git a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs index e3bca205d..dd0b92a51 100644 --- a/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs +++ b/src/Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Path = System.IO.Path; using SpanHelpers = LibHac.Common.SpanHelpers; @@ -184,18 +185,12 @@ namespace Ryujinx.Ava.UI.ViewModels } else { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]); - }); + Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage])); } } catch (Exception ex) { - Dispatcher.UIThread.Post(async () => - { - await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path)); - }); + Dispatcher.UIThread.InvokeAsync(() => ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, path))); } } } @@ -207,7 +202,7 @@ namespace Ryujinx.Ava.UI.ViewModels SortUpdates(); } - public async void Add() + public async Task Add() { var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { @@ -218,9 +213,9 @@ namespace Ryujinx.Ava.UI.ViewModels { Patterns = new[] { "*.nsp" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, - MimeTypes = new[] { "application/x-nx-nsp" } - } - } + MimeTypes = new[] { "application/x-nx-nsp" }, + }, + }, }); foreach (var file in result) diff --git a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml index 65a66d9e2..2ab42e6ee 100644 --- a/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Input/ControllerInputView.axaml @@ -465,6 +465,7 @@ Maximum="1" TickFrequency="0.01" IsSnapToTickEnabled="True" + SmallChange="0.01" Minimum="0" Value="{ReflectionBinding Configuration.DeadzoneLeft, Mode=TwoWay}" /> @@ -50,6 +51,7 @@ MaxWidth="150" TickFrequency="0.01" IsSnapToTickEnabled="True" + SmallChange="0.01" Maximum="100" Minimum="0" Value="{Binding GyroDeadzone, Mode=TwoWay}" /> diff --git a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml b/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml index 16190d391..f633c0ed2 100644 --- a/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Input/RumbleInputView.axaml @@ -26,6 +26,7 @@ Width="200" TickFrequency="0.01" IsSnapToTickEnabled="True" + SmallChange="0.01" Maximum="10" Minimum="0" Value="{Binding StrongRumble, Mode=TwoWay}" /> @@ -47,6 +48,7 @@ Maximum="10" TickFrequency="0.01" IsSnapToTickEnabled="True" + SmallChange="0.01" Minimum="0" Value="{Binding WeakRumble, Mode=TwoWay}" /> - { - Window.ViewModel.AppHost?.Pause(); - }); + Window.ViewModel.AppHost?.Pause(); } - private async void ResumeEmulation_Click(object sender, RoutedEventArgs e) + private void ResumeEmulation_Click(object sender, RoutedEventArgs e) { - await Task.Run(() => - { - Window.ViewModel.AppHost?.Resume(); - }); + Window.ViewModel.AppHost?.Resume(); } public async void OpenSettings(object sender, RoutedEventArgs e) @@ -132,13 +125,13 @@ namespace Ryujinx.Ava.UI.Views.Main ViewModel.LoadConfigurableHotKeys(); } - public void OpenMiiApplet(object sender, RoutedEventArgs e) + public async void OpenMiiApplet(object sender, RoutedEventArgs e) { string contentPath = ViewModel.ContentManager.GetInstalledContentPath(0x0100000000001009, StorageId.BuiltInSystem, NcaContentType.Program); if (!string.IsNullOrEmpty(contentPath)) { - ViewModel.LoadApplication(contentPath, false, "Mii Applet"); + await ViewModel.LoadApplication(contentPath, false, "Mii Applet"); } } @@ -196,8 +189,7 @@ namespace Ryujinx.Ava.UI.Views.Main { if (FileAssociationHelper.Install()) { - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], - string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); } else { @@ -209,8 +201,7 @@ namespace Ryujinx.Ava.UI.Views.Main { if (FileAssociationHelper.Uninstall()) { - await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], - string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); + await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage], string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty); } else { diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml b/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml index 58e06a1c2..32524740b 100644 --- a/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Main/MainStatusBarView.axaml @@ -6,6 +6,7 @@ xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale" xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:config="clr-namespace:Ryujinx.Common.Configuration;assembly=Ryujinx.Common" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Ryujinx.Ava.UI.Views.Main.MainStatusBarView" x:DataType="viewModels:MainWindowViewModel"> @@ -112,15 +113,52 @@ Background="Gray" BorderThickness="1" IsVisible="{Binding !ShowLoadProgress}" /> - + Content="{Binding AspectRatioStatusText}" + Click="AspectRatioStatus_OnClick" + ToolTip.Tip="{locale:Locale AspectRatioTooltip}"> + + + + + + + + + + + + + + Enum.GetNames(typeof(AspectRatio)).Length - 1 ? AspectRatio.Fixed4x3 : aspectRatio + 1; } diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml b/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml index f5a177424..34624b222 100644 --- a/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml +++ b/src/Ryujinx.Ava/UI/Views/Main/MainViewControls.axaml @@ -56,6 +56,7 @@ Margin="5,-10,5,0" VerticalAlignment="Center" IsSnapToTickEnabled="True" + SmallChange="1" Maximum="4" Minimum="1" TickFrequency="1" diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml index 1e3bb4ba4..d5039c131 100644 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Settings/SettingsGraphicsView.axaml @@ -77,6 +77,7 @@ diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml b/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml index 6ce1bb94f..9bb814631 100644 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml +++ b/src/Ryujinx.Ava/UI/Views/Settings/SettingsNetworkView.axaml @@ -23,21 +23,34 @@ HorizontalAlignment="Stretch" Orientation="Vertical" Spacing="10"> + + + + + + + ToolTip.Tip="{locale:Locale EnableInternetAccessTooltip}" /> + Text="{locale:Locale SettingsTabNetworkInterface}" + ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}" + Width="200" /> + ToolTip.Tip="{locale:Locale NetworkInterfaceTooltip}" + HorizontalContentAlignment="Left" + ItemsSource="{Binding NetworkInterfaceList}" + Width="250" /> diff --git a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs index a38a88655..6d9299dda 100644 --- a/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs +++ b/src/Ryujinx.Ava/UI/Views/Settings/SettingsUIView.axaml.cs @@ -34,7 +34,7 @@ namespace Ryujinx.Ava.UI.Views.Settings { var result = await window.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions { - AllowMultiple = false + AllowMultiple = false, }); if (result.Count > 0) @@ -75,9 +75,9 @@ namespace Ryujinx.Ava.UI.Views.Settings { Patterns = new[] { "*.xaml" }, AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xaml" }, - MimeTypes = new[] { "application/xaml+xml" } - } - } + MimeTypes = new[] { "application/xaml+xml" }, + }, + }, }); if (result.Count > 0) diff --git a/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs b/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs index e9bf4408c..fabfaa4e8 100644 --- a/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs +++ b/src/Ryujinx.Ava/UI/Views/User/UserProfileImageSelectorView.axaml.cs @@ -75,9 +75,9 @@ namespace Ryujinx.Ava.UI.Views.User { Patterns = new[] { "*.jpg", "*.jpeg", "*.png", "*.bmp" }, AppleUniformTypeIdentifiers = new[] { "public.jpeg", "public.png", "com.microsoft.bmp" }, - MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" } - } - } + MimeTypes = new[] { "image/jpeg", "image/png", "image/bmp" }, + }, + }, }); if (result.Count > 0) diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml index b9cbcb9cc..8a5da5cc2 100644 --- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml +++ b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml @@ -11,6 +11,7 @@ Height="500" MinWidth="500" MinHeight="500" + x:DataType="window:CheatWindow" WindowStartupLocation="CenterOwner" mc:Ignorable="d" Focusable="True"> @@ -40,7 +41,7 @@ HorizontalAlignment="Center" VerticalAlignment="Center" LineHeight="18" - Text="{ReflectionBinding Heading}" + Text="{Binding Heading}" TextAlignment="Center" TextWrapping="Wrap" /> + ItemsSource="{Binding LoadedCheats}">