mirror of
https://git.ryujinx.app/ryubing/ryujinx.git
synced 2025-09-12 13:05:26 +00:00
Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4ee1dff497 | ||
|
b2a35ecf6c | ||
|
5f6d9eef6b | ||
|
40a488799e | ||
|
f9f037a951 | ||
|
456f1ec739 | ||
|
df9450d2ad | ||
|
a989d28e03 | ||
|
cb31d79164 | ||
|
290e7c5ec8 | ||
|
4d311dfc1a | ||
|
5b6e3521d8 |
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@@ -7,6 +7,7 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
|
- 'docs/**'
|
||||||
- '*.yml'
|
- '*.yml'
|
||||||
- '*.json'
|
- '*.json'
|
||||||
- '*.config'
|
- '*.config'
|
||||||
@@ -102,9 +103,7 @@ jobs:
|
|||||||
if: matrix.platform.os == 'windows-latest'
|
if: matrix.platform.os == 'windows-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_ava
|
pushd publish_ava
|
||||||
cp publish/Ryujinx.exe publish/Ryujinx.Ava.exe
|
|
||||||
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
7z a ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
||||||
7z a ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.zip publish
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
@@ -116,10 +115,8 @@ jobs:
|
|||||||
if: matrix.platform.os == 'ubuntu-latest'
|
if: matrix.platform.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
pushd publish_ava
|
pushd publish_ava
|
||||||
cp publish/Ryujinx publish/Ryujinx.Ava
|
chmod +x publish/Ryujinx.sh publish/Ryujinx
|
||||||
chmod +x publish/Ryujinx.sh publish/Ryujinx.Ava publish/Ryujinx
|
|
||||||
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
tar -czvf ../release_output/ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
||||||
tar -czvf ../release_output/test-ava-ryujinx-${{ steps.version_info.outputs.build_version }}-${{ matrix.platform.zip_os_name }}.tar.gz publish
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
pushd publish_sdl2_headless
|
pushd publish_sdl2_headless
|
||||||
|
57
README.md
57
README.md
@@ -1,6 +1,6 @@
|
|||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<br>
|
<br>
|
||||||
<a href="https://ryujinx.org/"><img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
|
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/master/distribution/misc/Logo.svg" alt="Ryujinx" width="150"></a>
|
||||||
<br>
|
<br>
|
||||||
<b>Ryujinx</b>
|
<b>Ryujinx</b>
|
||||||
<br>
|
<br>
|
||||||
@@ -9,29 +9,34 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Ryujinx is an open-source Nintendo Switch emulator, created by gdkchan, written in C#.
|
Ryujinx is an open-source Nintendo Switch emulator, originally created by gdkchan, written in C#.
|
||||||
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
This emulator aims at providing excellent accuracy and performance, a user-friendly interface and consistent builds.
|
||||||
It was written from scratch and development on the project began in September 2017.
|
It was written from scratch and development on the project began in September 2017.
|
||||||
Ryujinx is available on Github under the <a href="https://github.com/Ryujinx/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
|
Ryujinx is available on Github under the <a href="https://github.com/GreemDev/Ryujinx/blob/master/LICENSE.txt" target="_blank">MIT license</a>.
|
||||||
<br />
|
<br />
|
||||||
</p>
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
On October 1st 2024, Ryujinx was discontinued as the creator was forced to abandon the project.
|
||||||
|
This fork is intended to be a direct continuation for existing Ryujinx users.
|
||||||
|
Guides and documentation will not be provided at this time, though you can find the old ones on the Internet Archive.
|
||||||
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml">
|
<a href="https://github.com/GreemDev/Ryujinx/actions/workflows/release.yml">
|
||||||
<img src="https://github.com/Ryujinx/Ryujinx/actions/workflows/release.yml/badge.svg"
|
<img src="https://github.com/GreemDev/Ryujinx/actions/workflows/release.yml/badge.svg"
|
||||||
alt="">
|
alt="">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://crwd.in/ryujinx">
|
<a href="https://crwd.in/ryujinx">
|
||||||
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
|
<img src="https://badges.crowdin.net/ryujinx/localized.svg"
|
||||||
alt="">
|
alt="">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.com/invite/VkQYXAZ">
|
<a href="https://discord.gg/dHPrkBkkyA">
|
||||||
<img src="https://img.shields.io/discord/410208534861447168?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
|
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryujinx&logo=discord&logoColor=white"
|
||||||
alt="Discord">
|
alt="Discord">
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<img src="https://raw.githubusercontent.com/Ryujinx/Ryujinx-Website/master/public/assets/images/shell.png">
|
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
@@ -39,8 +44,6 @@
|
|||||||
As of May 2024, Ryujinx has been tested on approximately 4,300 titles;
|
As of May 2024, Ryujinx has been tested on approximately 4,300 titles;
|
||||||
over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable.
|
over 4,100 boot past menus and into gameplay, with roughly 3,550 of those being considered playable.
|
||||||
|
|
||||||
You can check out the compatibility list [here](https://github.com/Ryujinx/Ryujinx-Games-List/issues).
|
|
||||||
|
|
||||||
Anyone is free to submit a new game test or update an existing game test entry;
|
Anyone is free to submit a new game test or update an existing game test entry;
|
||||||
simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue.
|
simply follow the new issue template and testing guidelines, or post as a reply to the applicable game issue.
|
||||||
Use the search function to see if a game has been tested already!
|
Use the search function to see if a game has been tested already!
|
||||||
@@ -50,22 +53,11 @@ Use the search function to see if a game has been tested already!
|
|||||||
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
|
To run this emulator, your PC must be equipped with at least 8GiB of RAM;
|
||||||
failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
|
failing to meet this requirement may result in a poor gameplay experience or unexpected crashes.
|
||||||
|
|
||||||
See our [Setup & Configuration Guide](https://github.com/Ryujinx/Ryujinx/wiki/Ryujinx-Setup-&-Configuration-Guide) on how to set up the emulator.
|
|
||||||
|
|
||||||
For our Local Wireless (LDN) builds, see our [Multiplayer: Local Play/Local Wireless Guide
|
|
||||||
](https://github.com/Ryujinx/Ryujinx/wiki/Multiplayer-(LDN-Local-Wireless)-Guide).
|
|
||||||
|
|
||||||
Avalonia UI comes with translations for various languages. See [Crowdin](https://crwd.in/ryujinx) for more information.
|
|
||||||
|
|
||||||
## Latest build
|
## Latest build
|
||||||
|
|
||||||
These builds are compiled automatically for each commit on the master branch.
|
These builds are compiled automatically for each commit on the master branch.
|
||||||
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
|
While we strive to ensure optimal stability and performance prior to pushing an update, our automated builds **may be unstable or completely broken**.
|
||||||
|
|
||||||
If you want to see details on updates to the emulator, you can visit our [Changelog](https://github.com/Ryujinx/Ryujinx/wiki/Changelog).
|
|
||||||
|
|
||||||
The latest automatic build for Windows, macOS, and Linux can be found on the [Official Website](https://ryujinx.org/download).
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
If you are planning to contribute or just want to learn more about this project please read through our [documentation](docs/README.md).
|
||||||
@@ -81,7 +73,7 @@ Make sure your SDK version is higher or equal to the required version specified
|
|||||||
|
|
||||||
### Step 2
|
### Step 2
|
||||||
|
|
||||||
Either use `git clone https://github.com/Ryujinx/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
|
Either use `git clone https://github.com/GreemDev/Ryujinx` on the command line to clone the repository or use Code --> Download zip button to get the files.
|
||||||
|
|
||||||
### Step 3
|
### Step 3
|
||||||
|
|
||||||
@@ -135,27 +127,6 @@ This folder is located in the user folder, which can be accessed by clicking `Op
|
|||||||
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
|
The emulator has settings for enabling or disabling some logging, remapping controllers, and more.
|
||||||
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
You can configure all of them through the graphical interface or manually through the config file, `Config.json`, found in the user folder which can be accessed by clicking `Open Ryujinx Folder` under the File menu in the GUI.
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
If you have contributions, suggestions, need emulator support or just want to get in touch with the team, join our [Discord server](https://discord.com/invite/Ryujinx).
|
|
||||||
You may also review our [FAQ](https://github.com/Ryujinx/Ryujinx/wiki/Frequently-Asked-Questions).
|
|
||||||
|
|
||||||
## Donations
|
|
||||||
|
|
||||||
If you'd like to support the project financially, Ryujinx has an active Patreon campaign.
|
|
||||||
|
|
||||||
<a href="https://www.patreon.com/ryujinx">
|
|
||||||
<img src="https://images.squarespace-cdn.com/content/v1/560c1d39e4b0b4fae0c9cf2a/1567548955044-WVD994WZP76EWF15T0L3/Patreon+Button.png?format=500w" width="150">
|
|
||||||
</a>
|
|
||||||
|
|
||||||
All developers working on the project do so in their free time, but the project has several expenses:
|
|
||||||
* Hackable Nintendo Switch consoles to reverse-engineer the hardware
|
|
||||||
* Additional computer hardware for testing purposes (e.g. GPUs to diagnose graphical bugs, etc.)
|
|
||||||
* Licenses for various software development tools (e.g. Jetbrains, IDA)
|
|
||||||
* Web hosting and infrastructure maintenance (e.g. LDN servers)
|
|
||||||
|
|
||||||
All funds received through Patreon are considered a donation to support the project. Patrons receive early access to progress reports and exclusive access to developer interviews.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This software is licensed under the terms of the [MIT license](LICENSE.txt).
|
This software is licensed under the terms of the [MIT license](LICENSE.txt).
|
||||||
|
@@ -109,12 +109,6 @@ python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" "$RELEASE_TAR_FILE_NAME" "
|
|||||||
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
gzip -9 < "$RELEASE_TAR_FILE_NAME" > "$RELEASE_TAR_FILE_NAME.gz"
|
||||||
rm "$RELEASE_TAR_FILE_NAME"
|
rm "$RELEASE_TAR_FILE_NAME"
|
||||||
|
|
||||||
# Create legacy update package for Avalonia to not left behind old testers.
|
|
||||||
if [ "$VERSION" != "1.1.0" ];
|
|
||||||
then
|
|
||||||
cp $RELEASE_TAR_FILE_NAME.gz test-ava-ryujinx-$VERSION-macos_universal.app.tar.gz
|
|
||||||
fi
|
|
||||||
|
|
||||||
popd
|
popd
|
||||||
|
|
||||||
echo "Done"
|
echo "Done"
|
BIN
docs/shell.png
Normal file
BIN
docs/shell.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 905 KiB |
@@ -309,7 +309,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
ReadOnlySpan<byte> infosBytes = new(stream.PositionPointer, innerHeader.InfosLength);
|
ReadOnlySpan<byte> infosBytes = new(stream.PositionPointer, innerHeader.InfosLength);
|
||||||
stream.Seek(innerHeader.InfosLength, SeekOrigin.Current);
|
stream.Seek(innerHeader.InfosLength, SeekOrigin.Current);
|
||||||
|
|
||||||
Hash128 infosHash = XXHash128.ComputeHash(infosBytes);
|
Hash128 infosHash = Hash128.ComputeHash(infosBytes);
|
||||||
|
|
||||||
if (innerHeader.InfosHash != infosHash)
|
if (innerHeader.InfosHash != infosHash)
|
||||||
{
|
{
|
||||||
@@ -321,7 +321,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
ReadOnlySpan<byte> codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan<byte>.Empty;
|
ReadOnlySpan<byte> codesBytes = (int)innerHeader.CodesLength > 0 ? new(stream.PositionPointer, (int)innerHeader.CodesLength) : ReadOnlySpan<byte>.Empty;
|
||||||
stream.Seek(innerHeader.CodesLength, SeekOrigin.Current);
|
stream.Seek(innerHeader.CodesLength, SeekOrigin.Current);
|
||||||
|
|
||||||
Hash128 codesHash = XXHash128.ComputeHash(codesBytes);
|
Hash128 codesHash = Hash128.ComputeHash(codesBytes);
|
||||||
|
|
||||||
if (innerHeader.CodesHash != codesHash)
|
if (innerHeader.CodesHash != codesHash)
|
||||||
{
|
{
|
||||||
@@ -333,7 +333,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
ReadOnlySpan<byte> relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength);
|
ReadOnlySpan<byte> relocsBytes = new(stream.PositionPointer, innerHeader.RelocsLength);
|
||||||
stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current);
|
stream.Seek(innerHeader.RelocsLength, SeekOrigin.Current);
|
||||||
|
|
||||||
Hash128 relocsHash = XXHash128.ComputeHash(relocsBytes);
|
Hash128 relocsHash = Hash128.ComputeHash(relocsBytes);
|
||||||
|
|
||||||
if (innerHeader.RelocsHash != relocsHash)
|
if (innerHeader.RelocsHash != relocsHash)
|
||||||
{
|
{
|
||||||
@@ -345,7 +345,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
ReadOnlySpan<byte> unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength);
|
ReadOnlySpan<byte> unwindInfosBytes = new(stream.PositionPointer, innerHeader.UnwindInfosLength);
|
||||||
stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current);
|
stream.Seek(innerHeader.UnwindInfosLength, SeekOrigin.Current);
|
||||||
|
|
||||||
Hash128 unwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes);
|
Hash128 unwindInfosHash = Hash128.ComputeHash(unwindInfosBytes);
|
||||||
|
|
||||||
if (innerHeader.UnwindInfosHash != unwindInfosHash)
|
if (innerHeader.UnwindInfosHash != unwindInfosHash)
|
||||||
{
|
{
|
||||||
@@ -478,10 +478,10 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
Debug.Assert(stream.Position == stream.Length);
|
Debug.Assert(stream.Position == stream.Length);
|
||||||
|
|
||||||
innerHeader.InfosHash = XXHash128.ComputeHash(infosBytes);
|
innerHeader.InfosHash = Hash128.ComputeHash(infosBytes);
|
||||||
innerHeader.CodesHash = XXHash128.ComputeHash(codesBytes);
|
innerHeader.CodesHash = Hash128.ComputeHash(codesBytes);
|
||||||
innerHeader.RelocsHash = XXHash128.ComputeHash(relocsBytes);
|
innerHeader.RelocsHash = Hash128.ComputeHash(relocsBytes);
|
||||||
innerHeader.UnwindInfosHash = XXHash128.ComputeHash(unwindInfosBytes);
|
innerHeader.UnwindInfosHash = Hash128.ComputeHash(unwindInfosBytes);
|
||||||
|
|
||||||
innerHeader.SetHeaderHash();
|
innerHeader.SetHeaderHash();
|
||||||
|
|
||||||
@@ -907,7 +907,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize)
|
public static Hash128 ComputeHash(IMemoryManager memory, ulong address, ulong guestSize)
|
||||||
{
|
{
|
||||||
return XXHash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
|
return Hash128.ComputeHash(memory.GetSpan(address, checked((int)(guestSize))));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc)
|
public void WriteCompiledFunction(ulong address, ulong guestSize, Hash128 hash, bool highCq, CompiledFunction compiledFunc)
|
||||||
@@ -1036,14 +1036,14 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||||
|
|
||||||
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
|
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsHeaderValid()
|
public bool IsHeaderValid()
|
||||||
{
|
{
|
||||||
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||||
|
|
||||||
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
|
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1071,14 +1071,14 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||||
|
|
||||||
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]);
|
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsHeaderValid()
|
public bool IsHeaderValid()
|
||||||
{
|
{
|
||||||
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
Span<InnerHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||||
|
|
||||||
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
|
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<InnerHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -209,7 +209,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
Hash128 expectedHash = DeserializeStructure<Hash128>(stream);
|
Hash128 expectedHash = DeserializeStructure<Hash128>(stream);
|
||||||
|
|
||||||
Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
|
Hash128 actualHash = Hash128.ComputeHash(GetReadOnlySpan(stream));
|
||||||
|
|
||||||
if (actualHash != expectedHash)
|
if (actualHash != expectedHash)
|
||||||
{
|
{
|
||||||
@@ -313,7 +313,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
Debug.Assert(stream.Position == stream.Length);
|
Debug.Assert(stream.Position == stream.Length);
|
||||||
|
|
||||||
stream.Seek(Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
|
stream.Seek(Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
|
||||||
Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
|
Hash128 hash = Hash128.ComputeHash(GetReadOnlySpan(stream));
|
||||||
|
|
||||||
stream.Seek(0L, SeekOrigin.Begin);
|
stream.Seek(0L, SeekOrigin.Begin);
|
||||||
SerializeStructure(stream, hash);
|
SerializeStructure(stream, hash);
|
||||||
@@ -374,14 +374,14 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
{
|
{
|
||||||
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||||
|
|
||||||
HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
|
HeaderHash = Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsHeaderValid()
|
public bool IsHeaderValid()
|
||||||
{
|
{
|
||||||
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
|
||||||
|
|
||||||
return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
|
return Hash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader)[..(Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())]) == HeaderHash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -492,7 +492,7 @@ namespace Ryujinx.Common.Collections
|
|||||||
Start = start;
|
Start = start;
|
||||||
End = end;
|
End = end;
|
||||||
Max = end;
|
Max = end;
|
||||||
Values = new List<RangeNode<TKey, TValue>> { new RangeNode<TKey, TValue>(start, end, value) };
|
Values = [ new RangeNode<TKey, TValue>(start, end, value) ];
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,6 +2,7 @@ using Ryujinx.Common.GraphicsDriver.NVAPI;
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace Ryujinx.Common.GraphicsDriver
|
namespace Ryujinx.Common.GraphicsDriver
|
||||||
{
|
{
|
||||||
|
@@ -1,48 +1,556 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Intrinsics.X86;
|
||||||
|
using System.Runtime.Intrinsics;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
namespace Ryujinx.Common
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct Hash128 : IEquatable<Hash128>
|
public struct Hash128(ulong low, ulong high) : IEquatable<Hash128>
|
||||||
{
|
{
|
||||||
public ulong Low;
|
public ulong Low = low;
|
||||||
public ulong High;
|
public ulong High = high;
|
||||||
|
|
||||||
public Hash128(ulong low, ulong high)
|
public readonly override string ToString() => $"{High:x16}{Low:x16}";
|
||||||
|
|
||||||
|
public static bool operator ==(Hash128 x, Hash128 y) => x.Equals(y);
|
||||||
|
|
||||||
|
public static bool operator !=(Hash128 x, Hash128 y) => !x.Equals(y);
|
||||||
|
|
||||||
|
public readonly override bool Equals(object obj) => obj is Hash128 hash128 && Equals(hash128);
|
||||||
|
|
||||||
|
public readonly bool Equals(Hash128 cmpObj) => Low == cmpObj.Low && High == cmpObj.High;
|
||||||
|
|
||||||
|
public readonly override int GetHashCode() => HashCode.Combine(Low, High);
|
||||||
|
|
||||||
|
public static Hash128 ComputeHash(ReadOnlySpan<byte> input) => Xxh3128bitsInternal(input, Xxh3KSecret, 0UL);
|
||||||
|
|
||||||
|
#region Hash computation
|
||||||
|
|
||||||
|
private const int StripeLen = 64;
|
||||||
|
private const int AccNb = StripeLen / sizeof(ulong);
|
||||||
|
private const int SecretConsumeRate = 8;
|
||||||
|
private const int SecretLastAccStart = 7;
|
||||||
|
private const int SecretMergeAccsStart = 11;
|
||||||
|
private const int SecretSizeMin = 136;
|
||||||
|
private const int MidSizeStartOffset = 3;
|
||||||
|
private const int MidSizeLastOffset = 17;
|
||||||
|
|
||||||
|
private const uint Prime32_1 = 0x9E3779B1U;
|
||||||
|
private const uint Prime32_2 = 0x85EBCA77U;
|
||||||
|
private const uint Prime32_3 = 0xC2B2AE3DU;
|
||||||
|
private const uint Prime32_4 = 0x27D4EB2FU;
|
||||||
|
private const uint Prime32_5 = 0x165667B1U;
|
||||||
|
|
||||||
|
private const ulong Prime64_1 = 0x9E3779B185EBCA87UL;
|
||||||
|
private const ulong Prime64_2 = 0xC2B2AE3D27D4EB4FUL;
|
||||||
|
private const ulong Prime64_3 = 0x165667B19E3779F9UL;
|
||||||
|
private const ulong Prime64_4 = 0x85EBCA77C2B2AE63UL;
|
||||||
|
private const ulong Prime64_5 = 0x27D4EB2F165667C5UL;
|
||||||
|
|
||||||
|
private static readonly ulong[] _xxh3InitAcc =
|
||||||
|
[
|
||||||
|
Prime32_3,
|
||||||
|
Prime64_1,
|
||||||
|
Prime64_2,
|
||||||
|
Prime64_3,
|
||||||
|
Prime64_4,
|
||||||
|
Prime32_2,
|
||||||
|
Prime64_5,
|
||||||
|
Prime32_1
|
||||||
|
];
|
||||||
|
|
||||||
|
private static ReadOnlySpan<byte> Xxh3KSecret =>
|
||||||
|
[
|
||||||
|
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
|
||||||
|
0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
|
||||||
|
0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
|
||||||
|
0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
|
||||||
|
0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
|
||||||
|
0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
|
||||||
|
0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
|
||||||
|
0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
|
||||||
|
0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
|
||||||
|
0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
|
||||||
|
0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
|
||||||
|
0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e
|
||||||
|
];
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong Mult32To64(ulong x, ulong y) => (uint)x * (ulong)(uint)y;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static Hash128 Mult64To128(ulong lhs, ulong rhs)
|
||||||
{
|
{
|
||||||
Low = low;
|
ulong high = Math.BigMul(lhs, rhs, out ulong low);
|
||||||
High = high;
|
|
||||||
|
return new Hash128
|
||||||
|
{
|
||||||
|
Low = low,
|
||||||
|
High = high,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly override string ToString()
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong Mul128Fold64(ulong lhs, ulong rhs)
|
||||||
{
|
{
|
||||||
return $"{High:x16}{Low:x16}";
|
Hash128 product = Mult64To128(lhs, rhs);
|
||||||
|
|
||||||
|
return product.Low ^ product.High;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator ==(Hash128 x, Hash128 y)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong XorShift64(ulong v64, int shift)
|
||||||
{
|
{
|
||||||
return x.Equals(y);
|
Debug.Assert(0 <= shift && shift < 64);
|
||||||
|
|
||||||
|
return v64 ^ (v64 >> shift);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator !=(Hash128 x, Hash128 y)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong Xxh3Avalanche(ulong h64)
|
||||||
{
|
{
|
||||||
return !x.Equals(y);
|
h64 = XorShift64(h64, 37);
|
||||||
|
h64 *= 0x165667919E3779F9UL;
|
||||||
|
h64 = XorShift64(h64, 32);
|
||||||
|
|
||||||
|
return h64;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly override bool Equals(object obj)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong Xxh64Avalanche(ulong h64)
|
||||||
{
|
{
|
||||||
return obj is Hash128 hash128 && Equals(hash128);
|
h64 ^= h64 >> 33;
|
||||||
|
h64 *= Prime64_2;
|
||||||
|
h64 ^= h64 >> 29;
|
||||||
|
h64 *= Prime64_3;
|
||||||
|
h64 ^= h64 >> 32;
|
||||||
|
|
||||||
|
return h64;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly bool Equals(Hash128 cmpObj)
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private unsafe static void Xxh3Accumulate512(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
|
||||||
{
|
{
|
||||||
return Low == cmpObj.Low && High == cmpObj.High;
|
if (Avx2.IsSupported)
|
||||||
|
{
|
||||||
|
fixed (ulong* pAcc = acc)
|
||||||
|
{
|
||||||
|
fixed (byte* pInput = input, pSecret = secret)
|
||||||
|
{
|
||||||
|
Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
|
||||||
|
Vector256<byte>* xInput = (Vector256<byte>*)pInput;
|
||||||
|
Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
|
||||||
|
|
||||||
|
for (ulong i = 0; i < StripeLen / 32; i++)
|
||||||
|
{
|
||||||
|
Vector256<byte> dataVec = xInput[i];
|
||||||
|
Vector256<byte> keyVec = xSecret[i];
|
||||||
|
Vector256<byte> dataKey = Avx2.Xor(dataVec, keyVec);
|
||||||
|
Vector256<uint> dataKeyLo = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
|
||||||
|
Vector256<ulong> product = Avx2.Multiply(dataKey.AsUInt32(), dataKeyLo);
|
||||||
|
Vector256<uint> dataSwap = Avx2.Shuffle(dataVec.AsUInt32(), 0b01001110);
|
||||||
|
Vector256<ulong> sum = Avx2.Add(xAcc[i], dataSwap.AsUInt64());
|
||||||
|
xAcc[i] = Avx2.Add(product, sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Sse2.IsSupported)
|
||||||
|
{
|
||||||
|
fixed (ulong* pAcc = acc)
|
||||||
|
{
|
||||||
|
fixed (byte* pInput = input, pSecret = secret)
|
||||||
|
{
|
||||||
|
Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
|
||||||
|
Vector128<byte>* xInput = (Vector128<byte>*)pInput;
|
||||||
|
Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
|
||||||
|
|
||||||
|
for (ulong i = 0; i < StripeLen / 16; i++)
|
||||||
|
{
|
||||||
|
Vector128<byte> dataVec = xInput[i];
|
||||||
|
Vector128<byte> keyVec = xSecret[i];
|
||||||
|
Vector128<byte> dataKey = Sse2.Xor(dataVec, keyVec);
|
||||||
|
Vector128<uint> dataKeyLo = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
|
||||||
|
Vector128<ulong> product = Sse2.Multiply(dataKey.AsUInt32(), dataKeyLo);
|
||||||
|
Vector128<uint> dataSwap = Sse2.Shuffle(dataVec.AsUInt32(), 0b01001110);
|
||||||
|
Vector128<ulong> sum = Sse2.Add(xAcc[i], dataSwap.AsUInt64());
|
||||||
|
xAcc[i] = Sse2.Add(product, sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < AccNb; i++)
|
||||||
|
{
|
||||||
|
ulong dataVal = BinaryPrimitives.ReadUInt64LittleEndian(input[(i * sizeof(ulong))..]);
|
||||||
|
ulong dataKey = dataVal ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
|
||||||
|
acc[i ^ 1] += dataVal;
|
||||||
|
acc[i] += Mult32To64((uint)dataKey, dataKey >> 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly override int GetHashCode()
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private unsafe static void Xxh3ScrambleAcc(Span<ulong> acc, ReadOnlySpan<byte> secret)
|
||||||
{
|
{
|
||||||
return HashCode.Combine(Low, High);
|
if (Avx2.IsSupported)
|
||||||
|
{
|
||||||
|
fixed (ulong* pAcc = acc)
|
||||||
|
{
|
||||||
|
fixed (byte* pSecret = secret)
|
||||||
|
{
|
||||||
|
Vector256<uint> prime32 = Vector256.Create(Prime32_1);
|
||||||
|
Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
|
||||||
|
Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
|
||||||
|
|
||||||
|
for (ulong i = 0; i < StripeLen / 32; i++)
|
||||||
|
{
|
||||||
|
Vector256<ulong> accVec = xAcc[i];
|
||||||
|
Vector256<ulong> shifted = Avx2.ShiftRightLogical(accVec, 47);
|
||||||
|
Vector256<ulong> dataVec = Avx2.Xor(accVec, shifted);
|
||||||
|
|
||||||
|
Vector256<byte> keyVec = xSecret[i];
|
||||||
|
Vector256<uint> dataKey = Avx2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
|
||||||
|
|
||||||
|
Vector256<uint> dataKeyHi = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
|
||||||
|
Vector256<ulong> prodLo = Avx2.Multiply(dataKey, prime32);
|
||||||
|
Vector256<ulong> prodHi = Avx2.Multiply(dataKeyHi, prime32);
|
||||||
|
|
||||||
|
xAcc[i] = Avx2.Add(prodLo, Avx2.ShiftLeftLogical(prodHi, 32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else if (Sse2.IsSupported)
|
||||||
|
{
|
||||||
|
fixed (ulong* pAcc = acc)
|
||||||
|
{
|
||||||
|
fixed (byte* pSecret = secret)
|
||||||
|
{
|
||||||
|
Vector128<uint> prime32 = Vector128.Create(Prime32_1);
|
||||||
|
Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
|
||||||
|
Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
|
||||||
|
|
||||||
|
for (ulong i = 0; i < StripeLen / 16; i++)
|
||||||
|
{
|
||||||
|
Vector128<ulong> accVec = xAcc[i];
|
||||||
|
Vector128<ulong> shifted = Sse2.ShiftRightLogical(accVec, 47);
|
||||||
|
Vector128<ulong> dataVec = Sse2.Xor(accVec, shifted);
|
||||||
|
|
||||||
|
Vector128<byte> keyVec = xSecret[i];
|
||||||
|
Vector128<uint> dataKey = Sse2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
|
||||||
|
|
||||||
|
Vector128<uint> dataKeyHi = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
|
||||||
|
Vector128<ulong> prodLo = Sse2.Multiply(dataKey, prime32);
|
||||||
|
Vector128<ulong> prodHi = Sse2.Multiply(dataKeyHi, prime32);
|
||||||
|
|
||||||
|
xAcc[i] = Sse2.Add(prodLo, Sse2.ShiftLeftLogical(prodHi, 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int i = 0; i < AccNb; i++)
|
||||||
|
{
|
||||||
|
ulong key64 = BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
|
||||||
|
ulong acc64 = acc[i];
|
||||||
|
acc64 = XorShift64(acc64, 47);
|
||||||
|
acc64 ^= key64;
|
||||||
|
acc64 *= Prime32_1;
|
||||||
|
acc[i] = acc64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void Xxh3Accumulate(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, int nbStripes)
|
||||||
|
{
|
||||||
|
for (int n = 0; n < nbStripes; n++)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> inData = input[(n * StripeLen)..];
|
||||||
|
Xxh3Accumulate512(acc, inData, secret[(n * SecretConsumeRate)..]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Xxh3HashLongInternalLoop(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
|
||||||
|
{
|
||||||
|
int nbStripesPerBlock = (secret.Length - StripeLen) / SecretConsumeRate;
|
||||||
|
int blockLen = StripeLen * nbStripesPerBlock;
|
||||||
|
int nbBlocks = (input.Length - 1) / blockLen;
|
||||||
|
|
||||||
|
Debug.Assert(secret.Length >= SecretSizeMin);
|
||||||
|
|
||||||
|
for (int n = 0; n < nbBlocks; n++)
|
||||||
|
{
|
||||||
|
Xxh3Accumulate(acc, input[(n * blockLen)..], secret, nbStripesPerBlock);
|
||||||
|
Xxh3ScrambleAcc(acc, secret[^StripeLen..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.Assert(input.Length > StripeLen);
|
||||||
|
|
||||||
|
int nbStripes = (input.Length - 1 - (blockLen * nbBlocks)) / StripeLen;
|
||||||
|
Debug.Assert(nbStripes <= (secret.Length / SecretConsumeRate));
|
||||||
|
Xxh3Accumulate(acc, input[(nbBlocks * blockLen)..], secret, nbStripes);
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> p = input[^StripeLen..];
|
||||||
|
Xxh3Accumulate512(acc, p, secret[(secret.Length - StripeLen - SecretLastAccStart)..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong Xxh3Mix2Accs(Span<ulong> acc, ReadOnlySpan<byte> secret)
|
||||||
|
{
|
||||||
|
return Mul128Fold64(
|
||||||
|
acc[0] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret),
|
||||||
|
acc[1] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]));
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong Xxh3MergeAccs(Span<ulong> acc, ReadOnlySpan<byte> secret, ulong start)
|
||||||
|
{
|
||||||
|
ulong result64 = start;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
result64 += Xxh3Mix2Accs(acc[(2 * i)..], secret[(16 * i)..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Xxh3Avalanche(result64);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SkipLocalsInit]
|
||||||
|
private static Hash128 Xxh3HashLong128bInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
|
||||||
|
{
|
||||||
|
Span<ulong> acc = stackalloc ulong[AccNb];
|
||||||
|
_xxh3InitAcc.CopyTo(acc);
|
||||||
|
|
||||||
|
Xxh3HashLongInternalLoop(acc, input, secret);
|
||||||
|
|
||||||
|
Debug.Assert(acc.Length == 8);
|
||||||
|
Debug.Assert(secret.Length >= acc.Length * sizeof(ulong) + SecretMergeAccsStart);
|
||||||
|
|
||||||
|
return new Hash128
|
||||||
|
{
|
||||||
|
Low = Xxh3MergeAccs(acc, secret[SecretMergeAccsStart..], (ulong)input.Length * Prime64_1),
|
||||||
|
High = Xxh3MergeAccs(
|
||||||
|
acc,
|
||||||
|
secret[(secret.Length - acc.Length * sizeof(ulong) - SecretMergeAccsStart)..],
|
||||||
|
~((ulong)input.Length * Prime64_2)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Hash128 Xxh3Len1To3128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
Debug.Assert(1 <= input.Length && input.Length <= 3);
|
||||||
|
|
||||||
|
byte c1 = input[0];
|
||||||
|
byte c2 = input[input.Length >> 1];
|
||||||
|
byte c3 = input[^1];
|
||||||
|
|
||||||
|
uint combinedL = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | ((uint)input.Length << 8);
|
||||||
|
uint combinedH = BitOperations.RotateLeft(BinaryPrimitives.ReverseEndianness(combinedL), 13);
|
||||||
|
ulong bitFlipL = (BinaryPrimitives.ReadUInt32LittleEndian(secret) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[4..])) + seed;
|
||||||
|
ulong bitFlipH = (BinaryPrimitives.ReadUInt32LittleEndian(secret[8..]) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[12..])) - seed;
|
||||||
|
ulong keyedLo = combinedL ^ bitFlipL;
|
||||||
|
ulong keyedHi = combinedH ^ bitFlipH;
|
||||||
|
|
||||||
|
return new Hash128
|
||||||
|
{
|
||||||
|
Low = Xxh64Avalanche(keyedLo),
|
||||||
|
High = Xxh64Avalanche(keyedHi),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Hash128 Xxh3Len4To8128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
Debug.Assert(4 <= input.Length && input.Length <= 8);
|
||||||
|
|
||||||
|
seed ^= BinaryPrimitives.ReverseEndianness((uint)seed) << 32;
|
||||||
|
|
||||||
|
uint inputLo = BinaryPrimitives.ReadUInt32LittleEndian(input);
|
||||||
|
uint inputHi = BinaryPrimitives.ReadUInt32LittleEndian(input[^4..]);
|
||||||
|
ulong input64 = inputLo + ((ulong)inputHi << 32);
|
||||||
|
ulong bitFlip = (BinaryPrimitives.ReadUInt64LittleEndian(secret[16..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[24..])) + seed;
|
||||||
|
ulong keyed = input64 ^ bitFlip;
|
||||||
|
|
||||||
|
Hash128 m128 = Mult64To128(keyed, Prime64_1 + ((ulong)input.Length << 2));
|
||||||
|
|
||||||
|
m128.High += m128.Low << 1;
|
||||||
|
m128.Low ^= m128.High >> 3;
|
||||||
|
|
||||||
|
m128.Low = XorShift64(m128.Low, 35);
|
||||||
|
m128.Low *= 0x9FB21C651E98DF25UL;
|
||||||
|
m128.Low = XorShift64(m128.Low, 28);
|
||||||
|
m128.High = Xxh3Avalanche(m128.High);
|
||||||
|
|
||||||
|
return m128;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Hash128 Xxh3Len9To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
Debug.Assert(9 <= input.Length && input.Length <= 16);
|
||||||
|
|
||||||
|
ulong bitFlipL = (BinaryPrimitives.ReadUInt64LittleEndian(secret[32..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[40..])) - seed;
|
||||||
|
ulong bitFlipH = (BinaryPrimitives.ReadUInt64LittleEndian(secret[48..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[56..])) + seed;
|
||||||
|
ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
|
||||||
|
ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[^8..]);
|
||||||
|
|
||||||
|
Hash128 m128 = Mult64To128(inputLo ^ inputHi ^ bitFlipL, Prime64_1);
|
||||||
|
m128.Low += ((ulong)input.Length - 1) << 54;
|
||||||
|
inputHi ^= bitFlipH;
|
||||||
|
m128.High += inputHi + Mult32To64((uint)inputHi, Prime32_2 - 1);
|
||||||
|
m128.Low ^= BinaryPrimitives.ReverseEndianness(m128.High);
|
||||||
|
|
||||||
|
Hash128 h128 = Mult64To128(m128.Low, Prime64_2);
|
||||||
|
h128.High += m128.High * Prime64_2;
|
||||||
|
h128.Low = Xxh3Avalanche(h128.Low);
|
||||||
|
h128.High = Xxh3Avalanche(h128.High);
|
||||||
|
|
||||||
|
return h128;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Hash128 Xxh3Len0To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
Debug.Assert(input.Length <= 16);
|
||||||
|
|
||||||
|
if (input.Length > 8)
|
||||||
|
{
|
||||||
|
return Xxh3Len9To16128b(input, secret, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.Length >= 4)
|
||||||
|
{
|
||||||
|
return Xxh3Len4To8128b(input, secret, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.Length != 0)
|
||||||
|
{
|
||||||
|
return Xxh3Len1To3128b(input, secret, seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
Hash128 h128 = new();
|
||||||
|
ulong bitFlipL = BinaryPrimitives.ReadUInt64LittleEndian(secret[64..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[72..]);
|
||||||
|
ulong bitFlipH = BinaryPrimitives.ReadUInt64LittleEndian(secret[80..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[88..]);
|
||||||
|
h128.Low = Xxh64Avalanche(seed ^ bitFlipL);
|
||||||
|
h128.High = Xxh64Avalanche(seed ^ bitFlipH);
|
||||||
|
|
||||||
|
return h128;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong Xxh3Mix16b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
|
||||||
|
ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
|
||||||
|
|
||||||
|
return Mul128Fold64(
|
||||||
|
inputLo ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret) + seed),
|
||||||
|
inputHi ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]) - seed));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Hash128 Xxh128Mix32b(Hash128 acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> input2, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
acc.Low += Xxh3Mix16b(input, secret, seed);
|
||||||
|
acc.Low ^= BinaryPrimitives.ReadUInt64LittleEndian(input2) + BinaryPrimitives.ReadUInt64LittleEndian(input2[8..]);
|
||||||
|
acc.High += Xxh3Mix16b(input2, secret[16..], seed);
|
||||||
|
acc.High ^= BinaryPrimitives.ReadUInt64LittleEndian(input) + BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Hash128 Xxh3Len17To128128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
Debug.Assert(secret.Length >= SecretSizeMin);
|
||||||
|
Debug.Assert(16 < input.Length && input.Length <= 128);
|
||||||
|
|
||||||
|
Hash128 acc = new()
|
||||||
|
{
|
||||||
|
Low = (ulong)input.Length * Prime64_1,
|
||||||
|
High = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (input.Length > 32)
|
||||||
|
{
|
||||||
|
if (input.Length > 64)
|
||||||
|
{
|
||||||
|
if (input.Length > 96)
|
||||||
|
{
|
||||||
|
acc = Xxh128Mix32b(acc, input[48..], input[^64..], secret[96..], seed);
|
||||||
|
}
|
||||||
|
acc = Xxh128Mix32b(acc, input[32..], input[^48..], secret[64..], seed);
|
||||||
|
}
|
||||||
|
acc = Xxh128Mix32b(acc, input[16..], input[^32..], secret[32..], seed);
|
||||||
|
}
|
||||||
|
acc = Xxh128Mix32b(acc, input, input[^16..], secret, seed);
|
||||||
|
|
||||||
|
Hash128 h128 = new()
|
||||||
|
{
|
||||||
|
Low = acc.Low + acc.High,
|
||||||
|
High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
|
||||||
|
};
|
||||||
|
h128.Low = Xxh3Avalanche(h128.Low);
|
||||||
|
h128.High = 0UL - Xxh3Avalanche(h128.High);
|
||||||
|
|
||||||
|
return h128;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Hash128 Xxh3Len129To240128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
Debug.Assert(secret.Length >= SecretSizeMin);
|
||||||
|
Debug.Assert(128 < input.Length && input.Length <= 240);
|
||||||
|
|
||||||
|
Hash128 acc = new();
|
||||||
|
|
||||||
|
int nbRounds = input.Length / 32;
|
||||||
|
acc.Low = (ulong)input.Length * Prime64_1;
|
||||||
|
acc.High = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(32 * i)..], seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.Low = Xxh3Avalanche(acc.Low);
|
||||||
|
acc.High = Xxh3Avalanche(acc.High);
|
||||||
|
Debug.Assert(nbRounds >= 4);
|
||||||
|
|
||||||
|
for (int i = 4; i < nbRounds; i++)
|
||||||
|
{
|
||||||
|
acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(MidSizeStartOffset + 32 * (i - 4))..], seed);
|
||||||
|
}
|
||||||
|
|
||||||
|
acc = Xxh128Mix32b(acc, input[^16..], input[^32..], secret[(SecretSizeMin - MidSizeLastOffset - 16)..], 0UL - seed);
|
||||||
|
|
||||||
|
Hash128 h128 = new()
|
||||||
|
{
|
||||||
|
Low = acc.Low + acc.High,
|
||||||
|
High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
|
||||||
|
};
|
||||||
|
h128.Low = Xxh3Avalanche(h128.Low);
|
||||||
|
h128.High = 0UL - Xxh3Avalanche(h128.High);
|
||||||
|
|
||||||
|
return h128;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Hash128 Xxh3128bitsInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
||||||
|
{
|
||||||
|
Debug.Assert(secret.Length >= SecretSizeMin);
|
||||||
|
|
||||||
|
return input.Length switch
|
||||||
|
{
|
||||||
|
<= 16 => Xxh3Len0To16128b(input, secret, seed),
|
||||||
|
<= 128 => Xxh3Len17To128128b(input, secret, seed),
|
||||||
|
<= 240 => Xxh3Len129To240128b(input, secret, seed),
|
||||||
|
_ => Xxh3HashLong128bInternal(input, secret)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -24,7 +24,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
public readonly struct Log
|
public readonly struct Log
|
||||||
{
|
{
|
||||||
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
private static readonly string _homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir).FullName, "[redacted]");
|
private static readonly string _homeDirRedacted = Path.Combine(Directory.GetParent(_homeDir)!.FullName, "[redacted]");
|
||||||
|
|
||||||
internal readonly LogLevel Level;
|
internal readonly LogLevel Level;
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ namespace Ryujinx.Common.Logging
|
|||||||
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : new Log?(); break;
|
case LogLevel.AccessLog : AccessLog = enabled ? new Log(LogLevel.AccessLog) : new Log?(); break;
|
||||||
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break;
|
case LogLevel.Stub : Stub = enabled ? new Log(LogLevel.Stub) : new Log?(); break;
|
||||||
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break;
|
case LogLevel.Trace : Trace = enabled ? new Log(LogLevel.Trace) : new Log?(); break;
|
||||||
default: throw new ArgumentException("Unknown Log Level");
|
default: throw new ArgumentException("Unknown Log Level", nameof(logLevel));
|
||||||
#pragma warning restore IDE0055
|
#pragma warning restore IDE0055
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ namespace Ryujinx.Common
|
|||||||
private const string FlatHubChannelOwner = "flathub";
|
private const string FlatHubChannelOwner = "flathub";
|
||||||
|
|
||||||
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
private const string BuildVersion = "%%RYUJINX_BUILD_VERSION%%";
|
||||||
private const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
public const string BuildGitHash = "%%RYUJINX_BUILD_GIT_HASH%%";
|
||||||
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
private const string ReleaseChannelName = "%%RYUJINX_TARGET_RELEASE_CHANNEL_NAME%%";
|
||||||
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
private const string ConfigFileName = "%%RYUJINX_CONFIG_FILE_NAME%%";
|
||||||
|
|
||||||
|
@@ -75,16 +75,11 @@ namespace Ryujinx.Common.Utilities
|
|||||||
|
|
||||||
if (char.IsUpper(c))
|
if (char.IsUpper(c))
|
||||||
{
|
{
|
||||||
if (i == 0 || char.IsUpper(name[i - 1]))
|
if (!(i == 0 || char.IsUpper(name[i - 1])))
|
||||||
{
|
|
||||||
builder.Append(char.ToLowerInvariant(c));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.Append('_');
|
builder.Append('_');
|
||||||
|
|
||||||
builder.Append(char.ToLowerInvariant(c));
|
builder.Append(char.ToLowerInvariant(c));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
builder.Append(c);
|
builder.Append(c);
|
||||||
|
@@ -5,14 +5,16 @@ namespace Ryujinx.Common.Utilities
|
|||||||
{
|
{
|
||||||
public static class UInt128Utils
|
public static class UInt128Utils
|
||||||
{
|
{
|
||||||
public static UInt128 FromHex(string hex)
|
public static UInt128 FromHex(string hex) =>
|
||||||
{
|
new(
|
||||||
return new UInt128(ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber), ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber));
|
ulong.Parse(hex.AsSpan(0, 16), NumberStyles.HexNumber),
|
||||||
}
|
ulong.Parse(hex.AsSpan(16), NumberStyles.HexNumber)
|
||||||
|
);
|
||||||
|
|
||||||
public static UInt128 CreateRandom()
|
public static Int128 NextInt128(this Random rand) =>
|
||||||
{
|
new((ulong)rand.NextInt64(), (ulong)rand.NextInt64());
|
||||||
return new UInt128((ulong)Random.Shared.NextInt64(), (ulong)Random.Shared.NextInt64());
|
|
||||||
}
|
public static UInt128 NextUInt128(this Random rand) =>
|
||||||
|
new((ulong)rand.NextInt64(), (ulong)rand.NextInt64());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,548 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers.Binary;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Numerics;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.Intrinsics;
|
|
||||||
using System.Runtime.Intrinsics.X86;
|
|
||||||
|
|
||||||
namespace Ryujinx.Common
|
|
||||||
{
|
|
||||||
public static class XXHash128
|
|
||||||
{
|
|
||||||
private const int StripeLen = 64;
|
|
||||||
private const int AccNb = StripeLen / sizeof(ulong);
|
|
||||||
private const int SecretConsumeRate = 8;
|
|
||||||
private const int SecretLastAccStart = 7;
|
|
||||||
private const int SecretMergeAccsStart = 11;
|
|
||||||
private const int SecretSizeMin = 136;
|
|
||||||
private const int MidSizeStartOffset = 3;
|
|
||||||
private const int MidSizeLastOffset = 17;
|
|
||||||
|
|
||||||
private const uint Prime32_1 = 0x9E3779B1U;
|
|
||||||
private const uint Prime32_2 = 0x85EBCA77U;
|
|
||||||
private const uint Prime32_3 = 0xC2B2AE3DU;
|
|
||||||
private const uint Prime32_4 = 0x27D4EB2FU;
|
|
||||||
private const uint Prime32_5 = 0x165667B1U;
|
|
||||||
|
|
||||||
private const ulong Prime64_1 = 0x9E3779B185EBCA87UL;
|
|
||||||
private const ulong Prime64_2 = 0xC2B2AE3D27D4EB4FUL;
|
|
||||||
private const ulong Prime64_3 = 0x165667B19E3779F9UL;
|
|
||||||
private const ulong Prime64_4 = 0x85EBCA77C2B2AE63UL;
|
|
||||||
private const ulong Prime64_5 = 0x27D4EB2F165667C5UL;
|
|
||||||
|
|
||||||
private static readonly ulong[] _xxh3InitAcc = {
|
|
||||||
Prime32_3,
|
|
||||||
Prime64_1,
|
|
||||||
Prime64_2,
|
|
||||||
Prime64_3,
|
|
||||||
Prime64_4,
|
|
||||||
Prime32_2,
|
|
||||||
Prime64_5,
|
|
||||||
Prime32_1,
|
|
||||||
};
|
|
||||||
|
|
||||||
private static ReadOnlySpan<byte> Xxh3KSecret => new byte[]
|
|
||||||
{
|
|
||||||
0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
|
|
||||||
0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
|
|
||||||
0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
|
|
||||||
0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
|
|
||||||
0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
|
|
||||||
0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
|
|
||||||
0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
|
|
||||||
0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
|
|
||||||
0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
|
|
||||||
0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
|
|
||||||
0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
|
|
||||||
0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e,
|
|
||||||
};
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong Mult32To64(ulong x, ulong y)
|
|
||||||
{
|
|
||||||
return (uint)x * (ulong)(uint)y;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static Hash128 Mult64To128(ulong lhs, ulong rhs)
|
|
||||||
{
|
|
||||||
ulong high = Math.BigMul(lhs, rhs, out ulong low);
|
|
||||||
|
|
||||||
return new Hash128
|
|
||||||
{
|
|
||||||
Low = low,
|
|
||||||
High = high,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong Mul128Fold64(ulong lhs, ulong rhs)
|
|
||||||
{
|
|
||||||
Hash128 product = Mult64To128(lhs, rhs);
|
|
||||||
|
|
||||||
return product.Low ^ product.High;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong XorShift64(ulong v64, int shift)
|
|
||||||
{
|
|
||||||
Debug.Assert(0 <= shift && shift < 64);
|
|
||||||
|
|
||||||
return v64 ^ (v64 >> shift);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong Xxh3Avalanche(ulong h64)
|
|
||||||
{
|
|
||||||
h64 = XorShift64(h64, 37);
|
|
||||||
h64 *= 0x165667919E3779F9UL;
|
|
||||||
h64 = XorShift64(h64, 32);
|
|
||||||
|
|
||||||
return h64;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong Xxh64Avalanche(ulong h64)
|
|
||||||
{
|
|
||||||
h64 ^= h64 >> 33;
|
|
||||||
h64 *= Prime64_2;
|
|
||||||
h64 ^= h64 >> 29;
|
|
||||||
h64 *= Prime64_3;
|
|
||||||
h64 ^= h64 >> 32;
|
|
||||||
|
|
||||||
return h64;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private unsafe static void Xxh3Accumulate512(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
|
|
||||||
{
|
|
||||||
if (Avx2.IsSupported)
|
|
||||||
{
|
|
||||||
fixed (ulong* pAcc = acc)
|
|
||||||
{
|
|
||||||
fixed (byte* pInput = input, pSecret = secret)
|
|
||||||
{
|
|
||||||
Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
|
|
||||||
Vector256<byte>* xInput = (Vector256<byte>*)pInput;
|
|
||||||
Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
|
|
||||||
|
|
||||||
for (ulong i = 0; i < StripeLen / 32; i++)
|
|
||||||
{
|
|
||||||
Vector256<byte> dataVec = xInput[i];
|
|
||||||
Vector256<byte> keyVec = xSecret[i];
|
|
||||||
Vector256<byte> dataKey = Avx2.Xor(dataVec, keyVec);
|
|
||||||
Vector256<uint> dataKeyLo = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
|
|
||||||
Vector256<ulong> product = Avx2.Multiply(dataKey.AsUInt32(), dataKeyLo);
|
|
||||||
Vector256<uint> dataSwap = Avx2.Shuffle(dataVec.AsUInt32(), 0b01001110);
|
|
||||||
Vector256<ulong> sum = Avx2.Add(xAcc[i], dataSwap.AsUInt64());
|
|
||||||
xAcc[i] = Avx2.Add(product, sum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Sse2.IsSupported)
|
|
||||||
{
|
|
||||||
fixed (ulong* pAcc = acc)
|
|
||||||
{
|
|
||||||
fixed (byte* pInput = input, pSecret = secret)
|
|
||||||
{
|
|
||||||
Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
|
|
||||||
Vector128<byte>* xInput = (Vector128<byte>*)pInput;
|
|
||||||
Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
|
|
||||||
|
|
||||||
for (ulong i = 0; i < StripeLen / 16; i++)
|
|
||||||
{
|
|
||||||
Vector128<byte> dataVec = xInput[i];
|
|
||||||
Vector128<byte> keyVec = xSecret[i];
|
|
||||||
Vector128<byte> dataKey = Sse2.Xor(dataVec, keyVec);
|
|
||||||
Vector128<uint> dataKeyLo = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
|
|
||||||
Vector128<ulong> product = Sse2.Multiply(dataKey.AsUInt32(), dataKeyLo);
|
|
||||||
Vector128<uint> dataSwap = Sse2.Shuffle(dataVec.AsUInt32(), 0b01001110);
|
|
||||||
Vector128<ulong> sum = Sse2.Add(xAcc[i], dataSwap.AsUInt64());
|
|
||||||
xAcc[i] = Sse2.Add(product, sum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < AccNb; i++)
|
|
||||||
{
|
|
||||||
ulong dataVal = BinaryPrimitives.ReadUInt64LittleEndian(input[(i * sizeof(ulong))..]);
|
|
||||||
ulong dataKey = dataVal ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
|
|
||||||
acc[i ^ 1] += dataVal;
|
|
||||||
acc[i] += Mult32To64((uint)dataKey, dataKey >> 32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private unsafe static void Xxh3ScrambleAcc(Span<ulong> acc, ReadOnlySpan<byte> secret)
|
|
||||||
{
|
|
||||||
if (Avx2.IsSupported)
|
|
||||||
{
|
|
||||||
fixed (ulong* pAcc = acc)
|
|
||||||
{
|
|
||||||
fixed (byte* pSecret = secret)
|
|
||||||
{
|
|
||||||
Vector256<uint> prime32 = Vector256.Create(Prime32_1);
|
|
||||||
Vector256<ulong>* xAcc = (Vector256<ulong>*)pAcc;
|
|
||||||
Vector256<byte>* xSecret = (Vector256<byte>*)pSecret;
|
|
||||||
|
|
||||||
for (ulong i = 0; i < StripeLen / 32; i++)
|
|
||||||
{
|
|
||||||
Vector256<ulong> accVec = xAcc[i];
|
|
||||||
Vector256<ulong> shifted = Avx2.ShiftRightLogical(accVec, 47);
|
|
||||||
Vector256<ulong> dataVec = Avx2.Xor(accVec, shifted);
|
|
||||||
|
|
||||||
Vector256<byte> keyVec = xSecret[i];
|
|
||||||
Vector256<uint> dataKey = Avx2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
|
|
||||||
|
|
||||||
Vector256<uint> dataKeyHi = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
|
|
||||||
Vector256<ulong> prodLo = Avx2.Multiply(dataKey, prime32);
|
|
||||||
Vector256<ulong> prodHi = Avx2.Multiply(dataKeyHi, prime32);
|
|
||||||
|
|
||||||
xAcc[i] = Avx2.Add(prodLo, Avx2.ShiftLeftLogical(prodHi, 32));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Sse2.IsSupported)
|
|
||||||
{
|
|
||||||
fixed (ulong* pAcc = acc)
|
|
||||||
{
|
|
||||||
fixed (byte* pSecret = secret)
|
|
||||||
{
|
|
||||||
Vector128<uint> prime32 = Vector128.Create(Prime32_1);
|
|
||||||
Vector128<ulong>* xAcc = (Vector128<ulong>*)pAcc;
|
|
||||||
Vector128<byte>* xSecret = (Vector128<byte>*)pSecret;
|
|
||||||
|
|
||||||
for (ulong i = 0; i < StripeLen / 16; i++)
|
|
||||||
{
|
|
||||||
Vector128<ulong> accVec = xAcc[i];
|
|
||||||
Vector128<ulong> shifted = Sse2.ShiftRightLogical(accVec, 47);
|
|
||||||
Vector128<ulong> dataVec = Sse2.Xor(accVec, shifted);
|
|
||||||
|
|
||||||
Vector128<byte> keyVec = xSecret[i];
|
|
||||||
Vector128<uint> dataKey = Sse2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
|
|
||||||
|
|
||||||
Vector128<uint> dataKeyHi = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
|
|
||||||
Vector128<ulong> prodLo = Sse2.Multiply(dataKey, prime32);
|
|
||||||
Vector128<ulong> prodHi = Sse2.Multiply(dataKeyHi, prime32);
|
|
||||||
|
|
||||||
xAcc[i] = Sse2.Add(prodLo, Sse2.ShiftLeftLogical(prodHi, 32));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
for (int i = 0; i < AccNb; i++)
|
|
||||||
{
|
|
||||||
ulong key64 = BinaryPrimitives.ReadUInt64LittleEndian(secret[(i * sizeof(ulong))..]);
|
|
||||||
ulong acc64 = acc[i];
|
|
||||||
acc64 = XorShift64(acc64, 47);
|
|
||||||
acc64 ^= key64;
|
|
||||||
acc64 *= Prime32_1;
|
|
||||||
acc[i] = acc64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static void Xxh3Accumulate(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, int nbStripes)
|
|
||||||
{
|
|
||||||
for (int n = 0; n < nbStripes; n++)
|
|
||||||
{
|
|
||||||
ReadOnlySpan<byte> inData = input[(n * StripeLen)..];
|
|
||||||
Xxh3Accumulate512(acc, inData, secret[(n * SecretConsumeRate)..]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Xxh3HashLongInternalLoop(Span<ulong> acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
|
|
||||||
{
|
|
||||||
int nbStripesPerBlock = (secret.Length - StripeLen) / SecretConsumeRate;
|
|
||||||
int blockLen = StripeLen * nbStripesPerBlock;
|
|
||||||
int nbBlocks = (input.Length - 1) / blockLen;
|
|
||||||
|
|
||||||
Debug.Assert(secret.Length >= SecretSizeMin);
|
|
||||||
|
|
||||||
for (int n = 0; n < nbBlocks; n++)
|
|
||||||
{
|
|
||||||
Xxh3Accumulate(acc, input[(n * blockLen)..], secret, nbStripesPerBlock);
|
|
||||||
Xxh3ScrambleAcc(acc, secret[^StripeLen..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(input.Length > StripeLen);
|
|
||||||
|
|
||||||
int nbStripes = (input.Length - 1 - (blockLen * nbBlocks)) / StripeLen;
|
|
||||||
Debug.Assert(nbStripes <= (secret.Length / SecretConsumeRate));
|
|
||||||
Xxh3Accumulate(acc, input[(nbBlocks * blockLen)..], secret, nbStripes);
|
|
||||||
|
|
||||||
ReadOnlySpan<byte> p = input[^StripeLen..];
|
|
||||||
Xxh3Accumulate512(acc, p, secret[(secret.Length - StripeLen - SecretLastAccStart)..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong Xxh3Mix2Accs(Span<ulong> acc, ReadOnlySpan<byte> secret)
|
|
||||||
{
|
|
||||||
return Mul128Fold64(
|
|
||||||
acc[0] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret),
|
|
||||||
acc[1] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]));
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong Xxh3MergeAccs(Span<ulong> acc, ReadOnlySpan<byte> secret, ulong start)
|
|
||||||
{
|
|
||||||
ulong result64 = start;
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
result64 += Xxh3Mix2Accs(acc[(2 * i)..], secret[(16 * i)..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Xxh3Avalanche(result64);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SkipLocalsInit]
|
|
||||||
private static Hash128 Xxh3HashLong128bInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret)
|
|
||||||
{
|
|
||||||
Span<ulong> acc = stackalloc ulong[AccNb];
|
|
||||||
_xxh3InitAcc.CopyTo(acc);
|
|
||||||
|
|
||||||
Xxh3HashLongInternalLoop(acc, input, secret);
|
|
||||||
|
|
||||||
Debug.Assert(acc.Length == 8);
|
|
||||||
Debug.Assert(secret.Length >= acc.Length * sizeof(ulong) + SecretMergeAccsStart);
|
|
||||||
|
|
||||||
return new Hash128
|
|
||||||
{
|
|
||||||
Low = Xxh3MergeAccs(acc, secret[SecretMergeAccsStart..], (ulong)input.Length * Prime64_1),
|
|
||||||
High = Xxh3MergeAccs(
|
|
||||||
acc,
|
|
||||||
secret[(secret.Length - acc.Length * sizeof(ulong) - SecretMergeAccsStart)..],
|
|
||||||
~((ulong)input.Length * Prime64_2)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Hash128 Xxh3Len1To3128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
Debug.Assert(1 <= input.Length && input.Length <= 3);
|
|
||||||
|
|
||||||
byte c1 = input[0];
|
|
||||||
byte c2 = input[input.Length >> 1];
|
|
||||||
byte c3 = input[^1];
|
|
||||||
|
|
||||||
uint combinedL = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | ((uint)input.Length << 8);
|
|
||||||
uint combinedH = BitOperations.RotateLeft(BinaryPrimitives.ReverseEndianness(combinedL), 13);
|
|
||||||
ulong bitFlipL = (BinaryPrimitives.ReadUInt32LittleEndian(secret) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[4..])) + seed;
|
|
||||||
ulong bitFlipH = (BinaryPrimitives.ReadUInt32LittleEndian(secret[8..]) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret[12..])) - seed;
|
|
||||||
ulong keyedLo = combinedL ^ bitFlipL;
|
|
||||||
ulong keyedHi = combinedH ^ bitFlipH;
|
|
||||||
|
|
||||||
return new Hash128
|
|
||||||
{
|
|
||||||
Low = Xxh64Avalanche(keyedLo),
|
|
||||||
High = Xxh64Avalanche(keyedHi),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Hash128 Xxh3Len4To8128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
Debug.Assert(4 <= input.Length && input.Length <= 8);
|
|
||||||
|
|
||||||
seed ^= BinaryPrimitives.ReverseEndianness((uint)seed) << 32;
|
|
||||||
|
|
||||||
uint inputLo = BinaryPrimitives.ReadUInt32LittleEndian(input);
|
|
||||||
uint inputHi = BinaryPrimitives.ReadUInt32LittleEndian(input[^4..]);
|
|
||||||
ulong input64 = inputLo + ((ulong)inputHi << 32);
|
|
||||||
ulong bitFlip = (BinaryPrimitives.ReadUInt64LittleEndian(secret[16..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[24..])) + seed;
|
|
||||||
ulong keyed = input64 ^ bitFlip;
|
|
||||||
|
|
||||||
Hash128 m128 = Mult64To128(keyed, Prime64_1 + ((ulong)input.Length << 2));
|
|
||||||
|
|
||||||
m128.High += m128.Low << 1;
|
|
||||||
m128.Low ^= m128.High >> 3;
|
|
||||||
|
|
||||||
m128.Low = XorShift64(m128.Low, 35);
|
|
||||||
m128.Low *= 0x9FB21C651E98DF25UL;
|
|
||||||
m128.Low = XorShift64(m128.Low, 28);
|
|
||||||
m128.High = Xxh3Avalanche(m128.High);
|
|
||||||
|
|
||||||
return m128;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Hash128 Xxh3Len9To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
Debug.Assert(9 <= input.Length && input.Length <= 16);
|
|
||||||
|
|
||||||
ulong bitFlipL = (BinaryPrimitives.ReadUInt64LittleEndian(secret[32..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[40..])) - seed;
|
|
||||||
ulong bitFlipH = (BinaryPrimitives.ReadUInt64LittleEndian(secret[48..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[56..])) + seed;
|
|
||||||
ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
|
|
||||||
ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[^8..]);
|
|
||||||
|
|
||||||
Hash128 m128 = Mult64To128(inputLo ^ inputHi ^ bitFlipL, Prime64_1);
|
|
||||||
m128.Low += ((ulong)input.Length - 1) << 54;
|
|
||||||
inputHi ^= bitFlipH;
|
|
||||||
m128.High += inputHi + Mult32To64((uint)inputHi, Prime32_2 - 1);
|
|
||||||
m128.Low ^= BinaryPrimitives.ReverseEndianness(m128.High);
|
|
||||||
|
|
||||||
Hash128 h128 = Mult64To128(m128.Low, Prime64_2);
|
|
||||||
h128.High += m128.High * Prime64_2;
|
|
||||||
h128.Low = Xxh3Avalanche(h128.Low);
|
|
||||||
h128.High = Xxh3Avalanche(h128.High);
|
|
||||||
|
|
||||||
return h128;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Hash128 Xxh3Len0To16128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
Debug.Assert(input.Length <= 16);
|
|
||||||
|
|
||||||
if (input.Length > 8)
|
|
||||||
{
|
|
||||||
return Xxh3Len9To16128b(input, secret, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.Length >= 4)
|
|
||||||
{
|
|
||||||
return Xxh3Len4To8128b(input, secret, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.Length != 0)
|
|
||||||
{
|
|
||||||
return Xxh3Len1To3128b(input, secret, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
Hash128 h128 = new();
|
|
||||||
ulong bitFlipL = BinaryPrimitives.ReadUInt64LittleEndian(secret[64..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[72..]);
|
|
||||||
ulong bitFlipH = BinaryPrimitives.ReadUInt64LittleEndian(secret[80..]) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret[88..]);
|
|
||||||
h128.Low = Xxh64Avalanche(seed ^ bitFlipL);
|
|
||||||
h128.High = Xxh64Avalanche(seed ^ bitFlipH);
|
|
||||||
|
|
||||||
return h128;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong Xxh3Mix16b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
|
|
||||||
ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
|
|
||||||
|
|
||||||
return Mul128Fold64(
|
|
||||||
inputLo ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret) + seed),
|
|
||||||
inputHi ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret[8..]) - seed));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Hash128 Xxh128Mix32b(Hash128 acc, ReadOnlySpan<byte> input, ReadOnlySpan<byte> input2, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
acc.Low += Xxh3Mix16b(input, secret, seed);
|
|
||||||
acc.Low ^= BinaryPrimitives.ReadUInt64LittleEndian(input2) + BinaryPrimitives.ReadUInt64LittleEndian(input2[8..]);
|
|
||||||
acc.High += Xxh3Mix16b(input2, secret[16..], seed);
|
|
||||||
acc.High ^= BinaryPrimitives.ReadUInt64LittleEndian(input) + BinaryPrimitives.ReadUInt64LittleEndian(input[8..]);
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Hash128 Xxh3Len17To128128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
Debug.Assert(secret.Length >= SecretSizeMin);
|
|
||||||
Debug.Assert(16 < input.Length && input.Length <= 128);
|
|
||||||
|
|
||||||
Hash128 acc = new()
|
|
||||||
{
|
|
||||||
Low = (ulong)input.Length * Prime64_1,
|
|
||||||
High = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (input.Length > 32)
|
|
||||||
{
|
|
||||||
if (input.Length > 64)
|
|
||||||
{
|
|
||||||
if (input.Length > 96)
|
|
||||||
{
|
|
||||||
acc = Xxh128Mix32b(acc, input[48..], input[^64..], secret[96..], seed);
|
|
||||||
}
|
|
||||||
acc = Xxh128Mix32b(acc, input[32..], input[^48..], secret[64..], seed);
|
|
||||||
}
|
|
||||||
acc = Xxh128Mix32b(acc, input[16..], input[^32..], secret[32..], seed);
|
|
||||||
}
|
|
||||||
acc = Xxh128Mix32b(acc, input, input[^16..], secret, seed);
|
|
||||||
|
|
||||||
Hash128 h128 = new()
|
|
||||||
{
|
|
||||||
Low = acc.Low + acc.High,
|
|
||||||
High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
|
|
||||||
};
|
|
||||||
h128.Low = Xxh3Avalanche(h128.Low);
|
|
||||||
h128.High = 0UL - Xxh3Avalanche(h128.High);
|
|
||||||
|
|
||||||
return h128;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Hash128 Xxh3Len129To240128b(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
Debug.Assert(secret.Length >= SecretSizeMin);
|
|
||||||
Debug.Assert(128 < input.Length && input.Length <= 240);
|
|
||||||
|
|
||||||
Hash128 acc = new();
|
|
||||||
|
|
||||||
int nbRounds = input.Length / 32;
|
|
||||||
acc.Low = (ulong)input.Length * Prime64_1;
|
|
||||||
acc.High = 0;
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(32 * i)..], seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
acc.Low = Xxh3Avalanche(acc.Low);
|
|
||||||
acc.High = Xxh3Avalanche(acc.High);
|
|
||||||
Debug.Assert(nbRounds >= 4);
|
|
||||||
|
|
||||||
for (int i = 4; i < nbRounds; i++)
|
|
||||||
{
|
|
||||||
acc = Xxh128Mix32b(acc, input[(32 * i)..], input[(32 * i + 16)..], secret[(MidSizeStartOffset + 32 * (i - 4))..], seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
acc = Xxh128Mix32b(acc, input[^16..], input[^32..], secret[(SecretSizeMin - MidSizeLastOffset - 16)..], 0UL - seed);
|
|
||||||
|
|
||||||
Hash128 h128 = new()
|
|
||||||
{
|
|
||||||
Low = acc.Low + acc.High,
|
|
||||||
High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2,
|
|
||||||
};
|
|
||||||
h128.Low = Xxh3Avalanche(h128.Low);
|
|
||||||
h128.High = 0UL - Xxh3Avalanche(h128.High);
|
|
||||||
|
|
||||||
return h128;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Hash128 Xxh3128bitsInternal(ReadOnlySpan<byte> input, ReadOnlySpan<byte> secret, ulong seed)
|
|
||||||
{
|
|
||||||
Debug.Assert(secret.Length >= SecretSizeMin);
|
|
||||||
|
|
||||||
if (input.Length <= 16)
|
|
||||||
{
|
|
||||||
return Xxh3Len0To16128b(input, secret, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.Length <= 128)
|
|
||||||
{
|
|
||||||
return Xxh3Len17To128128b(input, secret, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.Length <= 240)
|
|
||||||
{
|
|
||||||
return Xxh3Len129To240128b(input, secret, seed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Xxh3HashLong128bInternal(input, secret);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Hash128 ComputeHash(ReadOnlySpan<byte> input)
|
|
||||||
{
|
|
||||||
return Xxh3128bitsInternal(input, Xxh3KSecret, 0UL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.MME
|
|||||||
{
|
{
|
||||||
ref var entry = ref _table[i];
|
ref var entry = ref _table[i];
|
||||||
|
|
||||||
var hash = XXHash128.ComputeHash(mc[..entry.Length]);
|
var hash = Hash128.ComputeHash(mc[..entry.Length]);
|
||||||
if (hash == entry.Hash)
|
if (hash == entry.Hash)
|
||||||
{
|
{
|
||||||
if (IsMacroHLESupported(caps, entry.Name))
|
if (IsMacroHLESupported(caps, entry.Name))
|
||||||
|
@@ -223,7 +223,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
|||||||
|
|
||||||
foreach (var entry in Table)
|
foreach (var entry in Table)
|
||||||
{
|
{
|
||||||
Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(entry.Code));
|
Hash128 hash = Hash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(entry.Code));
|
||||||
|
|
||||||
string[] constants = new string[entry.Constants != null ? entry.Constants.Length : 0];
|
string[] constants = new string[entry.Constants != null ? entry.Constants.Length : 0];
|
||||||
|
|
||||||
|
@@ -62,7 +62,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed.Blender
|
|||||||
currentCode = currentCode[..codeLength];
|
currentCode = currentCode[..codeLength];
|
||||||
}
|
}
|
||||||
|
|
||||||
Hash128 hash = XXHash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(currentCode));
|
Hash128 hash = Hash128.ComputeHash(MemoryMarshal.Cast<uint, byte>(currentCode));
|
||||||
|
|
||||||
descriptor = default;
|
descriptor = default;
|
||||||
|
|
||||||
|
@@ -453,7 +453,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
/// <returns>Hash of the data</returns>
|
/// <returns>Hash of the data</returns>
|
||||||
private static uint CalcHash(ReadOnlySpan<byte> data)
|
private static uint CalcHash(ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
return (uint)XXHash128.ComputeHash(data).Low;
|
return (uint)Hash128.ComputeHash(data).Low;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -588,21 +588,15 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
// LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead
|
// LibHac.NcaHeader's DecryptHeader doesn't check if HeaderKey is empty and throws InvalidDataException instead
|
||||||
// So, we check it early for a better user experience.
|
// So, we check it early for a better user experience.
|
||||||
if (_virtualFileSystem.KeySet.HeaderKey.IsZeros())
|
if (_virtualFileSystem.KeySet.HeaderKey.IsZeros())
|
||||||
{
|
|
||||||
throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers.");
|
throw new MissingKeyException("HeaderKey is empty. Cannot decrypt NCA headers.");
|
||||||
}
|
|
||||||
|
|
||||||
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
|
Dictionary<ulong, List<(NcaContentType type, string path)>> updateNcas = new();
|
||||||
|
|
||||||
if (Directory.Exists(firmwarePackage))
|
if (Directory.Exists(firmwarePackage))
|
||||||
{
|
|
||||||
return VerifyAndGetVersionDirectory(firmwarePackage);
|
return VerifyAndGetVersionDirectory(firmwarePackage);
|
||||||
}
|
|
||||||
|
|
||||||
if (!File.Exists(firmwarePackage))
|
if (!File.Exists(firmwarePackage))
|
||||||
{
|
|
||||||
throw new FileNotFoundException("Firmware file does not exist.");
|
throw new FileNotFoundException("Firmware file does not exist.");
|
||||||
}
|
|
||||||
|
|
||||||
FileInfo info = new(firmwarePackage);
|
FileInfo info = new(firmwarePackage);
|
||||||
|
|
||||||
@@ -612,30 +606,22 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
case ".zip":
|
case ".zip":
|
||||||
using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage))
|
using (ZipArchive archive = ZipFile.OpenRead(firmwarePackage))
|
||||||
{
|
|
||||||
return VerifyAndGetVersionZip(archive);
|
return VerifyAndGetVersionZip(archive);
|
||||||
}
|
|
||||||
case ".xci":
|
case ".xci":
|
||||||
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
||||||
|
|
||||||
if (xci.HasPartition(XciPartitionType.Update))
|
if (!xci.HasPartition(XciPartitionType.Update))
|
||||||
{
|
throw new InvalidFirmwarePackageException("Update not found in xci file.");
|
||||||
|
|
||||||
XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
|
XciPartition partition = xci.OpenPartition(XciPartitionType.Update);
|
||||||
|
|
||||||
return VerifyAndGetVersion(partition);
|
return VerifyAndGetVersion(partition);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return null;
|
||||||
throw new InvalidFirmwarePackageException("Update not found in xci file.");
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
|
SystemVersion VerifyAndGetVersionDirectory(string firmwareDirectory)
|
||||||
{
|
=> VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
|
||||||
return VerifyAndGetVersion(new LocalFileSystem(firmwareDirectory));
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
|
SystemVersion VerifyAndGetVersionZip(ZipArchive archive)
|
||||||
{
|
{
|
||||||
@@ -925,8 +911,6 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
return systemVersion;
|
return systemVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SystemVersion GetCurrentFirmwareVersion()
|
public SystemVersion GetCurrentFirmwareVersion()
|
||||||
|
@@ -63,7 +63,7 @@ namespace Ryujinx.HLE.HOS.Services.Mii
|
|||||||
|
|
||||||
public CreateId MakeCreateId()
|
public CreateId MakeCreateId()
|
||||||
{
|
{
|
||||||
UInt128 value = UInt128Utils.CreateRandom();
|
UInt128 value = Random.Shared.NextUInt128();
|
||||||
|
|
||||||
// Ensure the random ID generated is valid as a create id.
|
// Ensure the random ID generated is valid as a create id.
|
||||||
value &= ~new UInt128(0xC0, 0);
|
value &= ~new UInt128(0xC0, 0);
|
||||||
|
@@ -78,7 +78,7 @@ namespace Ryujinx.HLE.HOS.Services.Nifm.StaticService
|
|||||||
|
|
||||||
NetworkProfileData networkProfile = new()
|
NetworkProfileData networkProfile = new()
|
||||||
{
|
{
|
||||||
Uuid = UInt128Utils.CreateRandom(),
|
Uuid = Random.Shared.NextUInt128(),
|
||||||
};
|
};
|
||||||
|
|
||||||
networkProfile.IpSettingData.IpAddressSetting = new IpAddressSetting(interfaceProperties, unicastAddress);
|
networkProfile.IpSettingData.IpAddressSetting = new IpAddressSetting(interfaceProperties, unicastAddress);
|
||||||
|
@@ -12,7 +12,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
|
|||||||
|
|
||||||
public SteadyClockCore()
|
public SteadyClockCore()
|
||||||
{
|
{
|
||||||
_clockSourceId = UInt128Utils.CreateRandom();
|
_clockSourceId = Random.Shared.NextUInt128();
|
||||||
_isRtcResetDetected = false;
|
_isRtcResetDetected = false;
|
||||||
_isInitialized = false;
|
_isInitialized = false;
|
||||||
}
|
}
|
||||||
|
@@ -36,7 +36,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.Clock
|
|||||||
return new SteadyClockTimePoint
|
return new SteadyClockTimePoint
|
||||||
{
|
{
|
||||||
TimePoint = 0,
|
TimePoint = 0,
|
||||||
ClockSourceId = UInt128Utils.CreateRandom(),
|
ClockSourceId = Random.Shared.NextUInt128(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ namespace Ryujinx.HLE
|
|||||||
public TamperMachine TamperMachine { get; }
|
public TamperMachine TamperMachine { get; }
|
||||||
public IHostUIHandler UIHandler { get; }
|
public IHostUIHandler UIHandler { get; }
|
||||||
|
|
||||||
public bool EnableDeviceVsync { get; set; } = true;
|
public bool EnableDeviceVsync { get; set; }
|
||||||
|
|
||||||
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
|
public bool IsFrameAvailable => Gpu.Window.IsFrameAvailable;
|
||||||
|
|
||||||
|
@@ -17,10 +17,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
|
|
||||||
public bool TextProcessingEnabled
|
public bool TextProcessingEnabled
|
||||||
{
|
{
|
||||||
get
|
get => Volatile.Read(ref _canProcessInput);
|
||||||
{
|
|
||||||
return Volatile.Read(ref _canProcessInput);
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
@@ -10,6 +10,7 @@ using Ryujinx.HLE.HOS.Services.Am.AppletOE.ApplicationProxyService.ApplicationPr
|
|||||||
using Ryujinx.HLE.UI;
|
using Ryujinx.HLE.UI;
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
|
using Ryujinx.Input.SDL2;
|
||||||
using Ryujinx.SDL2.Common;
|
using Ryujinx.SDL2.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
@@ -12,7 +12,10 @@ namespace Ryujinx.Input.SDL2
|
|||||||
{
|
{
|
||||||
private bool HasConfiguration => _configuration != null;
|
private bool HasConfiguration => _configuration != null;
|
||||||
|
|
||||||
private record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From);
|
private readonly record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From)
|
||||||
|
{
|
||||||
|
public bool IsValid => To is not GamepadButtonInputId.Unbound && From is not GamepadButtonInputId.Unbound;
|
||||||
|
}
|
||||||
|
|
||||||
private StandardControllerInputConfig _configuration;
|
private StandardControllerInputConfig _configuration;
|
||||||
|
|
||||||
@@ -144,18 +147,16 @@ namespace Ryujinx.Input.SDL2
|
|||||||
|
|
||||||
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
public void Rumble(float lowFrequency, float highFrequency, uint durationMs)
|
||||||
{
|
{
|
||||||
if (Features.HasFlag(GamepadFeaturesFlag.Rumble))
|
if (!Features.HasFlag(GamepadFeaturesFlag.Rumble)) return;
|
||||||
{
|
|
||||||
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
ushort lowFrequencyRaw = (ushort)(lowFrequency * ushort.MaxValue);
|
||||||
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
ushort highFrequencyRaw = (ushort)(highFrequency * ushort.MaxValue);
|
||||||
|
|
||||||
if (durationMs == uint.MaxValue)
|
if (durationMs == uint.MaxValue)
|
||||||
{
|
{
|
||||||
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0)
|
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, SDL_HAPTIC_INFINITY) != 0)
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (durationMs > SDL_HAPTIC_INFINITY)
|
else if (durationMs > SDL_HAPTIC_INFINITY)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
|
Logger.Error?.Print(LogClass.Hid, $"Unsupported rumble duration {durationMs}");
|
||||||
@@ -163,28 +164,22 @@ namespace Ryujinx.Input.SDL2
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
|
if (SDL_GameControllerRumble(_gamepadHandle, lowFrequencyRaw, highFrequencyRaw, durationMs) != 0)
|
||||||
{
|
|
||||||
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
Logger.Error?.Print(LogClass.Hid, "Rumble is not supported on this game controller.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3 GetMotionData(MotionInputId inputId)
|
public Vector3 GetMotionData(MotionInputId inputId)
|
||||||
{
|
{
|
||||||
SDL_SensorType sensorType = SDL_SensorType.SDL_SENSOR_INVALID;
|
SDL_SensorType sensorType = inputId switch
|
||||||
|
{
|
||||||
|
MotionInputId.Accelerometer => SDL_SensorType.SDL_SENSOR_ACCEL,
|
||||||
|
MotionInputId.Gyroscope => SDL_SensorType.SDL_SENSOR_GYRO,
|
||||||
|
_ => SDL_SensorType.SDL_SENSOR_INVALID
|
||||||
|
};
|
||||||
|
|
||||||
if (inputId == MotionInputId.Accelerometer)
|
if (!Features.HasFlag(GamepadFeaturesFlag.Motion) || sensorType is SDL_SensorType.SDL_SENSOR_INVALID)
|
||||||
{
|
return Vector3.Zero;
|
||||||
sensorType = SDL_SensorType.SDL_SENSOR_ACCEL;
|
|
||||||
}
|
|
||||||
else if (inputId == MotionInputId.Gyroscope)
|
|
||||||
{
|
|
||||||
sensorType = SDL_SensorType.SDL_SENSOR_GYRO;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Features.HasFlag(GamepadFeaturesFlag.Motion) && sensorType != SDL_SensorType.SDL_SENSOR_INVALID)
|
|
||||||
{
|
|
||||||
const int ElementCount = 3;
|
const int ElementCount = 3;
|
||||||
|
|
||||||
unsafe
|
unsafe
|
||||||
@@ -193,37 +188,23 @@ namespace Ryujinx.Input.SDL2
|
|||||||
|
|
||||||
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (IntPtr)values, ElementCount);
|
int result = SDL_GameControllerGetSensorData(_gamepadHandle, sensorType, (IntPtr)values, ElementCount);
|
||||||
|
|
||||||
if (result == 0)
|
if (result != 0)
|
||||||
{
|
return Vector3.Zero;
|
||||||
|
|
||||||
Vector3 value = new(values[0], values[1], values[2]);
|
Vector3 value = new(values[0], values[1], values[2]);
|
||||||
|
|
||||||
if (inputId == MotionInputId.Gyroscope)
|
return inputId switch
|
||||||
{
|
{
|
||||||
return RadToDegree(value);
|
MotionInputId.Gyroscope => RadToDegree(value),
|
||||||
}
|
MotionInputId.Accelerometer => GsToMs2(value),
|
||||||
|
_ => value
|
||||||
if (inputId == MotionInputId.Accelerometer)
|
};
|
||||||
{
|
|
||||||
return GsToMs2(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Vector3.Zero;
|
private static Vector3 RadToDegree(Vector3 rad) => rad * (180 / MathF.PI);
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector3 RadToDegree(Vector3 rad)
|
private static Vector3 GsToMs2(Vector3 gs) => gs / SDL_STANDARD_GRAVITY;
|
||||||
{
|
|
||||||
return rad * (180 / MathF.PI);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector3 GsToMs2(Vector3 gs)
|
|
||||||
{
|
|
||||||
return gs / SDL_STANDARD_GRAVITY;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetConfiguration(InputConfig configuration)
|
public void SetConfiguration(InputConfig configuration)
|
||||||
{
|
{
|
||||||
@@ -278,16 +259,13 @@ namespace Ryujinx.Input.SDL2
|
|||||||
lock (_userMappingLock)
|
lock (_userMappingLock)
|
||||||
{
|
{
|
||||||
if (_buttonsUserMapping.Count == 0)
|
if (_buttonsUserMapping.Count == 0)
|
||||||
{
|
|
||||||
return rawState;
|
return rawState;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
// ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator
|
||||||
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
foreach (ButtonMappingEntry entry in _buttonsUserMapping)
|
||||||
{
|
{
|
||||||
if (entry.From == GamepadButtonInputId.Unbound || entry.To == GamepadButtonInputId.Unbound)
|
if (!entry.IsValid) continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not touch state of button already pressed
|
// Do not touch state of button already pressed
|
||||||
if (!result.IsPressed(entry.To))
|
if (!result.IsPressed(entry.To))
|
||||||
@@ -316,9 +294,8 @@ namespace Ryujinx.Input.SDL2
|
|||||||
public (float, float) GetStick(StickInputId inputId)
|
public (float, float) GetStick(StickInputId inputId)
|
||||||
{
|
{
|
||||||
if (inputId == StickInputId.Unbound)
|
if (inputId == StickInputId.Unbound)
|
||||||
{
|
|
||||||
return (0.0f, 0.0f);
|
return (0.0f, 0.0f);
|
||||||
}
|
|
||||||
|
|
||||||
(short stickX, short stickY) = GetStickXY(inputId);
|
(short stickX, short stickY) = GetStickXY(inputId);
|
||||||
|
|
||||||
@@ -351,6 +328,7 @@ namespace Ryujinx.Input.SDL2
|
|||||||
return (resultX, resultY);
|
return (resultX, resultY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
private (short, short) GetStickXY(StickInputId inputId) =>
|
private (short, short) GetStickXY(StickInputId inputId) =>
|
||||||
inputId switch
|
inputId switch
|
||||||
{
|
{
|
||||||
@@ -365,13 +343,11 @@ namespace Ryujinx.Input.SDL2
|
|||||||
|
|
||||||
public bool IsPressed(GamepadButtonInputId inputId)
|
public bool IsPressed(GamepadButtonInputId inputId)
|
||||||
{
|
{
|
||||||
if (inputId == GamepadButtonInputId.LeftTrigger)
|
switch (inputId)
|
||||||
{
|
{
|
||||||
|
case GamepadButtonInputId.LeftTrigger:
|
||||||
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold;
|
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT)) > _triggerThreshold;
|
||||||
}
|
case GamepadButtonInputId.RightTrigger:
|
||||||
|
|
||||||
if (inputId == GamepadButtonInputId.RightTrigger)
|
|
||||||
{
|
|
||||||
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold;
|
return ConvertRawStickValue(SDL_GameControllerGetAxis(_gamepadHandle, SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT)) > _triggerThreshold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,12 +1,11 @@
|
|||||||
using Ryujinx.Common.Configuration.Hid;
|
using Ryujinx.Common.Configuration.Hid;
|
||||||
using Ryujinx.Input;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace Ryujinx.Headless.SDL2
|
namespace Ryujinx.Input.SDL2
|
||||||
{
|
{
|
||||||
class SDL2Mouse : IMouse
|
public class SDL2Mouse : IMouse
|
||||||
{
|
{
|
||||||
private SDL2MouseDriver _driver;
|
private SDL2MouseDriver _driver;
|
||||||
|
|
@@ -1,6 +1,5 @@
|
|||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Input;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
@@ -8,9 +7,9 @@ using System.Numerics;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using static SDL2.SDL;
|
using static SDL2.SDL;
|
||||||
|
|
||||||
namespace Ryujinx.Headless.SDL2
|
namespace Ryujinx.Input.SDL2
|
||||||
{
|
{
|
||||||
class SDL2MouseDriver : IGamepadDriver
|
public class SDL2MouseDriver : IGamepadDriver
|
||||||
{
|
{
|
||||||
private const int CursorHideIdleTime = 5; // seconds
|
private const int CursorHideIdleTime = 5; // seconds
|
||||||
|
|
||||||
@@ -44,7 +43,7 @@ namespace Ryujinx.Headless.SDL2
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static MouseButton DriverButtonToMouseButton(uint rawButton)
|
private static MouseButton DriverButtonToMouseButton(uint rawButton)
|
||||||
{
|
{
|
||||||
Debug.Assert(rawButton > 0 && rawButton <= (int)MouseButton.Count);
|
Debug.Assert(rawButton is > 0 and <= (int)MouseButton.Count);
|
||||||
|
|
||||||
return (MouseButton)(rawButton - 1);
|
return (MouseButton)(rawButton - 1);
|
||||||
}
|
}
|
@@ -143,7 +143,7 @@ namespace Ryujinx.SDL2.Common
|
|||||||
|
|
||||||
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
|
OnJoystickDisconnected?.Invoke(evnt.cbutton.which);
|
||||||
}
|
}
|
||||||
else if (evnt.type == SDL_EventType.SDL_WINDOWEVENT || evnt.type == SDL_EventType.SDL_MOUSEBUTTONDOWN || evnt.type == SDL_EventType.SDL_MOUSEBUTTONUP)
|
else if (evnt.type is SDL_EventType.SDL_WINDOWEVENT or SDL_EventType.SDL_MOUSEBUTTONDOWN or SDL_EventType.SDL_MOUSEBUTTONUP)
|
||||||
{
|
{
|
||||||
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
|
if (_registeredWindowHandlers.TryGetValue(evnt.window.windowID, out Action<SDL_Event> handler))
|
||||||
{
|
{
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
using DiscordRPC;
|
using DiscordRPC;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
|
using LibHac.Bcat;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.HLE.Loaders.Processes;
|
||||||
using Ryujinx.UI.App.Common;
|
using Ryujinx.UI.App.Common;
|
||||||
using Ryujinx.UI.Common.Configuration;
|
using Ryujinx.UI.Common.Configuration;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -13,7 +15,7 @@ namespace Ryujinx.UI.Common
|
|||||||
{
|
{
|
||||||
public static Timestamps StartedAt { get; set; }
|
public static Timestamps StartedAt { get; set; }
|
||||||
|
|
||||||
private const string Description = "A simple, experimental Nintendo Switch emulator.";
|
private static readonly string _description = $"v{ReleaseInformation.Version} {ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}@{ReleaseInformation.BuildGitHash}";
|
||||||
private const string ApplicationId = "1293250299716173864";
|
private const string ApplicationId = "1293250299716173864";
|
||||||
|
|
||||||
private const int ApplicationByteLimit = 128;
|
private const int ApplicationByteLimit = 128;
|
||||||
@@ -29,7 +31,7 @@ namespace Ryujinx.UI.Common
|
|||||||
Assets = new Assets
|
Assets = new Assets
|
||||||
{
|
{
|
||||||
LargeImageKey = "ryujinx",
|
LargeImageKey = "ryujinx",
|
||||||
LargeImageText = Description
|
LargeImageText = TruncateToByteLength(_description)
|
||||||
},
|
},
|
||||||
Details = "Main Menu",
|
Details = "Main Menu",
|
||||||
State = "Idling",
|
State = "Idling",
|
||||||
@@ -62,19 +64,21 @@ namespace Ryujinx.UI.Common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SwitchToPlayingState(string titleId, ApplicationMetadata appMeta)
|
public static void SwitchToPlayingState(ApplicationMetadata appMeta, ProcessResult procRes)
|
||||||
{
|
{
|
||||||
_discordClient?.SetPresence(new RichPresence
|
_discordClient?.SetPresence(new RichPresence
|
||||||
{
|
{
|
||||||
Assets = new Assets
|
Assets = new Assets
|
||||||
{
|
{
|
||||||
LargeImageKey = _discordGameAssets.Contains(titleId.ToLower()) ? titleId : "game",
|
LargeImageKey = _discordGameAssetKeys.Contains(procRes.ProgramIdText.ToLower()) ? procRes.ProgramIdText : "game",
|
||||||
LargeImageText = TruncateToByteLength(appMeta.Title),
|
LargeImageText = TruncateToByteLength($"{appMeta.Title} | {procRes.DisplayVersion}"),
|
||||||
SmallImageKey = "ryujinx",
|
SmallImageKey = "ryujinx",
|
||||||
SmallImageText = Description
|
SmallImageText = TruncateToByteLength(_description)
|
||||||
},
|
},
|
||||||
Details = TruncateToByteLength($"Playing {appMeta.Title}"),
|
Details = TruncateToByteLength($"Playing {appMeta.Title}"),
|
||||||
State = $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}",
|
State = appMeta.LastPlayed.HasValue
|
||||||
|
? $"Total play time: {appMeta.TimePlayed.Humanize(2, false)}"
|
||||||
|
: "Never played",
|
||||||
Timestamps = Timestamps.Now
|
Timestamps = Timestamps.Now
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -111,11 +115,12 @@ namespace Ryujinx.UI.Common
|
|||||||
_discordClient?.Dispose();
|
_discordClient?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly string[] _discordGameAssets = [
|
private static readonly string[] _discordGameAssetKeys = [
|
||||||
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
|
"01002da013484000", // The Legend of Zelda: Skyward Sword HD
|
||||||
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
|
"01007ef00011e000", // The Legend of Zelda: Breath of the Wild
|
||||||
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
|
"0100f2c0115b6000", // The Legend of Zelda: Tears of the Kingdom
|
||||||
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
|
"01008cf01baac000", // The Legend of Zelda: Echoes of Wisdom
|
||||||
|
"01006bb00c6f0000", // The Legend of Zelda: Link's Awakening
|
||||||
|
|
||||||
"0100000000010000", // SUPER MARIO ODYSSEY
|
"0100000000010000", // SUPER MARIO ODYSSEY
|
||||||
"010015100b514000", // Super Mario Bros. Wonder
|
"010015100b514000", // Super Mario Bros. Wonder
|
||||||
@@ -124,6 +129,9 @@ namespace Ryujinx.UI.Common
|
|||||||
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
|
"010028600ebda000", // Super Mario 3D World + Bowser's Fury
|
||||||
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
|
"0100ecd018ebe000", // Paper Mario: The Thousand-Year Door
|
||||||
|
|
||||||
|
"010048701995e000", // Luigi's Mansion 2 HD
|
||||||
|
"0100dca0064a6000", // Luigi's Mansion 3
|
||||||
|
|
||||||
"01008f6008c5e000", // Pokémon Violet
|
"01008f6008c5e000", // Pokémon Violet
|
||||||
"0100abf008968000", // Pokémon Sword
|
"0100abf008968000", // Pokémon Sword
|
||||||
"01008db008c2c000", // Pokémon Shield
|
"01008db008c2c000", // Pokémon Shield
|
||||||
@@ -133,10 +141,11 @@ namespace Ryujinx.UI.Common
|
|||||||
"0100aa80194b0000", // Pikmin 1
|
"0100aa80194b0000", // Pikmin 1
|
||||||
"0100d680194b2000", // Pikmin 2
|
"0100d680194b2000", // Pikmin 2
|
||||||
"0100f4c009322000", // Pikmin 3 Deluxe
|
"0100f4c009322000", // Pikmin 3 Deluxe
|
||||||
"0100b7c00933a000", // Pikmin
|
"0100b7c00933a000", // Pikmin 4
|
||||||
|
|
||||||
"0100c2500fc20000", // Splatoon 3
|
"0100c2500fc20000", // Splatoon 3
|
||||||
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
|
"0100ba0018500000", // Splatoon 3: Splatfest World Premiere
|
||||||
|
"01007820196a6000", // Red Dead Redemption
|
||||||
"0100744001588000", // Cars 3: Driven to Win
|
"0100744001588000", // Cars 3: Driven to Win
|
||||||
"01006f8002326000", // Animal Crossing: New Horizons
|
"01006f8002326000", // Animal Crossing: New Horizons
|
||||||
"0100853015e86000", // No Man's Sky
|
"0100853015e86000", // No Man's Sky
|
||||||
|
@@ -75,12 +75,11 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception) { }
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
outError = error;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,19 +95,15 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
|
string baseApplicationExtension = Path.GetExtension(baseApplicationPath).ToLowerInvariant();
|
||||||
|
|
||||||
// NOTE: We don't force homebrew developers to install a system firmware.
|
// NOTE: We don't force homebrew developers to install a system firmware.
|
||||||
if (baseApplicationExtension == ".nro" || baseApplicationExtension == ".nso")
|
if (baseApplicationExtension is not (".nro" or ".nso"))
|
||||||
{
|
|
||||||
error = UserError.Success;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return IsFirmwareValid(contentManager, out error);
|
return IsFirmwareValid(contentManager, out error);
|
||||||
|
|
||||||
|
error = UserError.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
error = UserError.ApplicationNotFound;
|
error = UserError.ApplicationNotFound;
|
||||||
|
|
||||||
return false;
|
return error is UserError.Success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -140,11 +140,11 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
|
|
||||||
argsList.Add($"\"{appFilePath}\"");
|
argsList.Add($"\"{appFilePath}\"");
|
||||||
|
|
||||||
return String.Join(" ", argsList);
|
return string.Join(" ", argsList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a Icon (.ico) file using the source bitmap image at the specified file path.
|
/// Creates an Icon (.ico) file using the source bitmap image at the specified file path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="source">The source bitmap image that will be saved as an .ico file</param>
|
/// <param name="source">The source bitmap image that will be saved as an .ico file</param>
|
||||||
/// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>
|
/// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>
|
||||||
|
@@ -8,9 +8,7 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, string pauseString = "")
|
public static string ActiveApplicationTitle(ProcessResult activeProcess, string applicationVersion, string pauseString = "")
|
||||||
{
|
{
|
||||||
if (activeProcess == null)
|
if (activeProcess == null)
|
||||||
{
|
return string.Empty;
|
||||||
return String.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
|
string titleNameSection = string.IsNullOrWhiteSpace(activeProcess.Name) ? string.Empty : $" {activeProcess.Name}";
|
||||||
string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
|
string titleVersionSection = string.IsNullOrWhiteSpace(activeProcess.DisplayVersion) ? string.Empty : $" v{activeProcess.DisplayVersion}";
|
||||||
@@ -19,12 +17,9 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
|
|
||||||
string appTitle = $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
string appTitle = $"Ryujinx {applicationVersion} -{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(pauseString))
|
return !string.IsNullOrEmpty(pauseString)
|
||||||
{
|
? appTitle + $" ({pauseString})"
|
||||||
appTitle += $" ({pauseString})";
|
: appTitle;
|
||||||
}
|
|
||||||
|
|
||||||
return appTitle;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -87,10 +87,7 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
|
|
||||||
foreach (string path in titleUpdateMetadata.Paths)
|
foreach (string path in titleUpdateMetadata.Paths)
|
||||||
{
|
{
|
||||||
if (!File.Exists(path))
|
if (!File.Exists(path)) continue;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -99,21 +96,14 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
Dictionary<ulong, ContentMetaData> updates =
|
Dictionary<ulong, ContentMetaData> updates =
|
||||||
pfs.GetContentData(ContentMetaType.Patch, vfs, checkLevel);
|
pfs.GetContentData(ContentMetaType.Patch, vfs, checkLevel);
|
||||||
|
|
||||||
Nca patchNca = null;
|
|
||||||
Nca controlNca = null;
|
|
||||||
|
|
||||||
if (!updates.TryGetValue(applicationIdBase, out ContentMetaData content))
|
if (!updates.TryGetValue(applicationIdBase, out ContentMetaData content))
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
patchNca = content.GetNcaByType(vfs.KeySet, ContentType.Program);
|
Nca patchNca = content.GetNcaByType(vfs.KeySet, ContentType.Program);
|
||||||
controlNca = content.GetNcaByType(vfs.KeySet, ContentType.Control);
|
Nca controlNca = content.GetNcaByType(vfs.KeySet, ContentType.Control);
|
||||||
|
|
||||||
if (controlNca == null || patchNca == null)
|
if (controlNca is null || patchNca is null)
|
||||||
{
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
ApplicationControlProperty controlData = new();
|
ApplicationControlProperty controlData = new();
|
||||||
|
|
||||||
@@ -138,7 +128,7 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
catch (InvalidDataException)
|
catch (InvalidDataException)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application,
|
Logger.Warning?.Print(LogClass.Application,
|
||||||
$"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {path}");
|
$"The header key is incorrect or missing and therefore the NCA header content type check has failed. Malformed File: {path}");
|
||||||
}
|
}
|
||||||
catch (IOException exception)
|
catch (IOException exception)
|
||||||
{
|
{
|
||||||
@@ -155,8 +145,6 @@ namespace Ryujinx.UI.Common.Helper
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static string PathToGameUpdatesJson(ulong applicationIdBase)
|
private static string PathToGameUpdatesJson(ulong applicationIdBase)
|
||||||
{
|
=> Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "updates.json");
|
||||||
return Path.Combine(AppDataManager.GamesDirPath, applicationIdBase.ToString("x16"), "updates.json");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,9 +19,14 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
public class App : Application
|
public class App : Application
|
||||||
{
|
{
|
||||||
|
internal static string FormatTitle(LocaleKeys? windowTitleKey = null)
|
||||||
|
=> windowTitleKey is null
|
||||||
|
? $"Ryujinx {Program.Version}"
|
||||||
|
: $"Ryujinx {Program.Version} - {LocaleManager.Instance[windowTitleKey.Value]}";
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
Name = $"Ryujinx {Program.Version}";
|
Name = FormatTitle();
|
||||||
|
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
|
||||||
@@ -112,7 +117,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, "Failed to Apply Theme. A restart is needed to apply the selected theme");
|
Logger.Warning?.Print(LogClass.Application, "Failed to apply theme. A restart is needed to apply the selected theme.");
|
||||||
|
|
||||||
ShowRestartDialog();
|
ShowRestartDialog();
|
||||||
}
|
}
|
||||||
|
@@ -367,12 +367,12 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
|
|
||||||
var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
|
var colorType = e.IsBgra ? SKColorType.Bgra8888 : SKColorType.Rgba8888;
|
||||||
using SKBitmap bitmap = new SKBitmap(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
|
using SKBitmap bitmap = new(new SKImageInfo(e.Width, e.Height, colorType, SKAlphaType.Premul));
|
||||||
|
|
||||||
Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length);
|
Marshal.Copy(e.Data, 0, bitmap.GetPixels(), e.Data.Length);
|
||||||
|
|
||||||
using SKBitmap bitmapToSave = new SKBitmap(bitmap.Width, bitmap.Height);
|
using SKBitmap bitmapToSave = new(bitmap.Width, bitmap.Height);
|
||||||
using SKCanvas canvas = new SKCanvas(bitmapToSave);
|
using SKCanvas canvas = new(bitmapToSave);
|
||||||
|
|
||||||
canvas.Clear(SKColors.Black);
|
canvas.Clear(SKColors.Black);
|
||||||
|
|
||||||
@@ -785,12 +785,11 @@ namespace Ryujinx.Ava
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
ApplicationMetadata appMeta = ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText,
|
||||||
{
|
appMetadata => appMetadata.UpdatePreGame()
|
||||||
appMetadata.UpdatePreGame();
|
);
|
||||||
});
|
|
||||||
|
|
||||||
DiscordIntegrationModule.SwitchToPlayingState(Device.Processes.ActiveApplication.ProgramIdText, appMeta);
|
DiscordIntegrationModule.SwitchToPlayingState(appMeta, Device.Processes.ActiveApplication);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -57,40 +57,32 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
{
|
{
|
||||||
// Check if the localized string needs to be formatted.
|
// Check if the localized string needs to be formatted.
|
||||||
if (_dynamicValues.TryGetValue(key, out var dynamicValue))
|
if (_dynamicValues.TryGetValue(key, out var dynamicValue))
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return string.Format(value, dynamicValue);
|
return string.Format(value, dynamicValue);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch
|
||||||
{
|
{
|
||||||
// If formatting failed use the default text instead.
|
// If formatting failed use the default text instead.
|
||||||
if (_localeDefaultStrings.TryGetValue(key, out value))
|
if (_localeDefaultStrings.TryGetValue(key, out value))
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return string.Format(value, dynamicValue);
|
return string.Format(value, dynamicValue);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch
|
||||||
{
|
{
|
||||||
// If formatting the default text failed return the key.
|
// If formatting the default text failed return the key.
|
||||||
return key.ToString();
|
return key.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the locale doesn't contain the key return the default one.
|
// If the locale doesn't contain the key return the default one.
|
||||||
if (_localeDefaultStrings.TryGetValue(key, out string defaultValue))
|
return _localeDefaultStrings.TryGetValue(key, out string defaultValue)
|
||||||
{
|
? defaultValue
|
||||||
return defaultValue;
|
: key.ToString(); // If the locale text doesn't exist return the key.
|
||||||
}
|
|
||||||
|
|
||||||
// If the locale text doesn't exist return the key.
|
|
||||||
return key.ToString();
|
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
@@ -100,14 +92,12 @@ namespace Ryujinx.Ava.Common.Locale
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsRTL()
|
public bool IsRTL() =>
|
||||||
{
|
_localeLanguageCode switch
|
||||||
return _localeLanguageCode switch
|
|
||||||
{
|
{
|
||||||
"ar_SA" or "he_IL" => true,
|
"ar_SA" or "he_IL" => true,
|
||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
|
public string UpdateAndGetDynamicValue(LocaleKeys key, params object[] values)
|
||||||
{
|
{
|
||||||
|
@@ -182,42 +182,34 @@ namespace Ryujinx.Ava
|
|||||||
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration.Value;
|
UseHardwareAcceleration = ConfigurationState.Instance.EnableHardwareAcceleration.Value;
|
||||||
|
|
||||||
// Check if graphics backend was overridden
|
// Check if graphics backend was overridden
|
||||||
if (CommandLineState.OverrideGraphicsBackend != null)
|
if (CommandLineState.OverrideGraphicsBackend is not null)
|
||||||
|
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = CommandLineState.OverrideGraphicsBackend.ToLower() switch
|
||||||
{
|
{
|
||||||
if (CommandLineState.OverrideGraphicsBackend.ToLower() == "opengl")
|
"opengl" => GraphicsBackend.OpenGl,
|
||||||
{
|
"vulkan" => GraphicsBackend.Vulkan,
|
||||||
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
|
_ => ConfigurationState.Instance.Graphics.GraphicsBackend
|
||||||
}
|
};
|
||||||
else if (CommandLineState.OverrideGraphicsBackend.ToLower() == "vulkan")
|
|
||||||
{
|
|
||||||
ConfigurationState.Instance.Graphics.GraphicsBackend.Value = GraphicsBackend.Vulkan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if docked mode was overriden.
|
// Check if docked mode was overriden.
|
||||||
if (CommandLineState.OverrideDockedMode.HasValue)
|
if (CommandLineState.OverrideDockedMode.HasValue)
|
||||||
{
|
|
||||||
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
|
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
|
||||||
}
|
|
||||||
|
|
||||||
// Check if HideCursor was overridden.
|
// Check if HideCursor was overridden.
|
||||||
if (CommandLineState.OverrideHideCursor is not null)
|
if (CommandLineState.OverrideHideCursor is not null)
|
||||||
{
|
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor.ToLower() switch
|
||||||
ConfigurationState.Instance.HideCursor.Value = CommandLineState.OverrideHideCursor!.ToLower() switch
|
|
||||||
{
|
{
|
||||||
"never" => HideCursorMode.Never,
|
"never" => HideCursorMode.Never,
|
||||||
"onidle" => HideCursorMode.OnIdle,
|
"onidle" => HideCursorMode.OnIdle,
|
||||||
"always" => HideCursorMode.Always,
|
"always" => HideCursorMode.Always,
|
||||||
_ => ConfigurationState.Instance.HideCursor.Value,
|
_ => ConfigurationState.Instance.HideCursor,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// Check if hardware-acceleration was overridden.
|
// Check if hardware-acceleration was overridden.
|
||||||
if (CommandLineState.OverrideHardwareAcceleration != null)
|
if (CommandLineState.OverrideHardwareAcceleration != null)
|
||||||
{
|
|
||||||
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
|
UseHardwareAcceleration = CommandLineState.OverrideHardwareAcceleration.Value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static void PrintSystemInfo()
|
private static void PrintSystemInfo()
|
||||||
{
|
{
|
||||||
|
@@ -1705,7 +1705,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
Title = $"Ryujinx {Program.Version}";
|
Title = App.FormatTitle();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -118,7 +118,7 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="10">
|
Spacing="10">
|
||||||
<Button
|
<Button Name="GitHubRepoButton"
|
||||||
MinWidth="30"
|
MinWidth="30"
|
||||||
MinHeight="30"
|
MinHeight="30"
|
||||||
MaxWidth="30"
|
MaxWidth="30"
|
||||||
@@ -127,7 +127,6 @@
|
|||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Click="Button_OnClick"
|
Click="Button_OnClick"
|
||||||
CornerRadius="15"
|
CornerRadius="15"
|
||||||
Tag="https://github.com/GreemDev/Ryujinx"
|
|
||||||
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
|
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
|
||||||
<Image Source="{Binding GithubLogo}" />
|
<Image Source="{Binding GithubLogo}" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -140,7 +139,7 @@
|
|||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Click="Button_OnClick"
|
Click="Button_OnClick"
|
||||||
CornerRadius="15"
|
CornerRadius="15"
|
||||||
Tag="https://discordapp.com/invite/N2FmfVc"
|
Tag="https://discord.gg/dHPrkBkkyA"
|
||||||
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
|
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
|
||||||
<Image Source="{Binding DiscordLogo}" />
|
<Image Source="{Binding DiscordLogo}" />
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -7,6 +7,7 @@ using FluentAvalonia.UI.Controls;
|
|||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.UI.Common.Helper;
|
using Ryujinx.UI.Common.Helper;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Button = Avalonia.Controls.Button;
|
using Button = Avalonia.Controls.Button;
|
||||||
@@ -20,6 +21,9 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
DataContext = new AboutWindowViewModel();
|
DataContext = new AboutWindowViewModel();
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
GitHubRepoButton.Tag =
|
||||||
|
$"https://github.com/{ReleaseInformation.ReleaseChannelOwner}/{ReleaseInformation.ReleaseChannelRepo}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task Show()
|
public static async Task Show()
|
||||||
@@ -46,9 +50,9 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
private void Button_OnClick(object sender, RoutedEventArgs e)
|
private void Button_OnClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Button button)
|
if (sender is Button { Tag: { } url })
|
||||||
{
|
{
|
||||||
OpenHelper.OpenUrl(button.Tag.ToString());
|
OpenHelper.OpenUrl(url.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.Amiibo];
|
Title = App.FormatTitle(LocaleKeys.Amiibo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AmiiboWindow()
|
public AmiiboWindow()
|
||||||
@@ -31,7 +31,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
if (Program.PreviewerDetached)
|
if (Program.PreviewerDetached)
|
||||||
{
|
{
|
||||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.Amiibo];
|
Title = App.FormatTitle(LocaleKeys.Amiibo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +43,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
{
|
{
|
||||||
if (ViewModel.AmiiboSelectedIndex > -1)
|
if (ViewModel.AmiiboSelectedIndex > -1)
|
||||||
{
|
{
|
||||||
AmiiboApi amiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
ScannedAmiibo = ViewModel.AmiiboList[ViewModel.AmiiboSelectedIndex];
|
||||||
ScannedAmiibo = amiibo;
|
|
||||||
IsScanned = true;
|
IsScanned = true;
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle];
|
Title = App.FormatTitle(LocaleKeys.CheatWindowTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
||||||
@@ -51,7 +51,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
_enabledCheatsPath = Path.Combine(titleModsPath, "cheats", "enabled.txt");
|
||||||
|
|
||||||
string[] enabled = Array.Empty<string>();
|
string[] enabled = [];
|
||||||
|
|
||||||
if (File.Exists(_enabledCheatsPath))
|
if (File.Exists(_enabledCheatsPath))
|
||||||
{
|
{
|
||||||
@@ -95,30 +95,19 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle];
|
Title = App.FormatTitle(LocaleKeys.CheatWindowTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
if (NoCheatsFound)
|
if (NoCheatsFound)
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
List<string> enabledCheats = new();
|
var enabledCheats = LoadedCheats.SelectMany(it => it.SubNodes)
|
||||||
|
.Where(it => it.IsEnabled)
|
||||||
|
.Select(it => it.BuildIdKey);
|
||||||
|
|
||||||
foreach (var cheats in LoadedCheats)
|
Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath)!);
|
||||||
{
|
|
||||||
foreach (var cheat in cheats.SubNodes)
|
|
||||||
{
|
|
||||||
if (cheat.IsEnabled)
|
|
||||||
{
|
|
||||||
enabledCheats.Add(cheat.BuildIdKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(_enabledCheatsPath));
|
|
||||||
|
|
||||||
File.WriteAllLines(_enabledCheatsPath, enabledCheats);
|
File.WriteAllLines(_enabledCheatsPath, enabledCheats);
|
||||||
|
|
||||||
|
@@ -79,7 +79,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
UiHandler = new AvaHostUIHandler(this);
|
UiHandler = new AvaHostUIHandler(this);
|
||||||
|
|
||||||
ViewModel.Title = $"Ryujinx {Program.Version}";
|
ViewModel.Title = App.FormatTitle();
|
||||||
|
|
||||||
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
// NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
|
||||||
StatusBarHeight = StatusBarView.StatusBar.MinHeight;
|
StatusBarHeight = StatusBarView.StatusBar.MinHeight;
|
||||||
@@ -443,10 +443,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
Initialize();
|
Initialize();
|
||||||
|
|
||||||
/// <summary>
|
PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged;
|
||||||
/// Subscribe to the ColorValuesChanged event
|
|
||||||
/// </summary>
|
|
||||||
PlatformSettings.ColorValuesChanged += OnPlatformColorValuesChanged;
|
|
||||||
|
|
||||||
ViewModel.Initialize(
|
ViewModel.Initialize(
|
||||||
ContentManager,
|
ContentManager,
|
||||||
@@ -467,7 +464,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
_appLibraryAppsSubscription?.Dispose();
|
_appLibraryAppsSubscription?.Dispose();
|
||||||
_appLibraryAppsSubscription = ApplicationLibrary.Applications
|
_appLibraryAppsSubscription = ApplicationLibrary.Applications
|
||||||
.Connect()
|
.Connect()
|
||||||
.ObserveOn(SynchronizationContext.Current)
|
.ObserveOn(SynchronizationContext.Current!)
|
||||||
.Bind(ViewModel.Applications)
|
.Bind(ViewModel.Applications)
|
||||||
.Subscribe();
|
.Subscribe();
|
||||||
|
|
||||||
@@ -656,28 +653,20 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
applicationLibraryThread.Start();
|
applicationLibraryThread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
|
private void ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded)
|
||||||
{
|
{
|
||||||
var msg = "";
|
string msg = numDlcAdded > 0 && numUpdatesAdded > 0
|
||||||
|
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded)
|
||||||
|
: numDlcAdded > 0
|
||||||
|
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded)
|
||||||
|
: numUpdatesAdded > 0
|
||||||
|
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded)
|
||||||
|
: null;
|
||||||
|
|
||||||
if (numDlcAdded > 0 && numUpdatesAdded > 0)
|
if (msg is null) return;
|
||||||
{
|
|
||||||
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded);
|
|
||||||
}
|
|
||||||
else if (numDlcAdded > 0)
|
|
||||||
{
|
|
||||||
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded);
|
|
||||||
}
|
|
||||||
else if (numUpdatesAdded > 0)
|
|
||||||
{
|
|
||||||
msg = string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
|
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
|
||||||
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);
|
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);
|
||||||
|
@@ -14,7 +14,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
|
public SettingsWindow(VirtualFileSystem virtualFileSystem, ContentManager contentManager)
|
||||||
{
|
{
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.Settings]}";
|
Title = App.FormatTitle(LocaleKeys.Settings);
|
||||||
|
|
||||||
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager);
|
ViewModel = new SettingsViewModel(virtualFileSystem, contentManager);
|
||||||
DataContext = ViewModel;
|
DataContext = ViewModel;
|
||||||
|
@@ -17,9 +17,9 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
public StyleableWindow()
|
public StyleableWindow()
|
||||||
{
|
{
|
||||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||||
TransparencyLevelHint = new[] { WindowTransparencyLevel.None };
|
TransparencyLevelHint = [WindowTransparencyLevel.None];
|
||||||
|
|
||||||
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState)).GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png");
|
using Stream stream = Assembly.GetAssembly(typeof(ConfigurationState))!.GetManifestResourceStream("Ryujinx.UI.Common.Resources.Logo_Ryujinx.png")!;
|
||||||
|
|
||||||
Icon = new WindowIcon(stream);
|
Icon = new WindowIcon(stream);
|
||||||
stream.Position = 0;
|
stream.Position = 0;
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using FluentAvalonia.UI.Controls;
|
using FluentAvalonia.UI.Controls;
|
||||||
@@ -64,21 +62,14 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
private void OpenLocation(object sender, RoutedEventArgs e)
|
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Button button)
|
if (sender is Button { DataContext: TitleUpdateModel model })
|
||||||
{
|
|
||||||
if (button.DataContext is TitleUpdateModel model)
|
|
||||||
{
|
|
||||||
OpenHelper.LocateFile(model.Path);
|
OpenHelper.LocateFile(model.Path);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveUpdate(object sender, RoutedEventArgs e)
|
private void RemoveUpdate(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (sender is Button button)
|
if (sender is Button { DataContext: TitleUpdateModel model })
|
||||||
{
|
ViewModel.RemoveUpdate(model);
|
||||||
ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveAll(object sender, RoutedEventArgs e)
|
private void RemoveAll(object sender, RoutedEventArgs e)
|
||||||
|
Reference in New Issue
Block a user