Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
0b58f46266 | |||
aa96dcb1be | |||
82a638230e | |||
d11fe26aa3 | |||
dcf10561b9 | |||
cdc8fed64f | |||
388446c255 | |||
29e192f241 | |||
5b3662b793 | |||
1329c47ea4 | |||
6bce46621c | |||
e6e5838916 | |||
51065d9129 | |||
6228331fd1 | |||
98e7c33630 | |||
5c3cfb84c0 | |||
55557525b1 | |||
7e6342e44d | |||
c3555cb5d6 | |||
815819767c | |||
623604c391 | |||
617c5700ca | |||
7b62f7475e | |||
841dd56f4c | |||
a16d582a10 | |||
9ef0be477b | |||
c14ce4d2a5 | |||
171b46ef49 | |||
56fe2ff535 | |||
b1f8f868f6 | |||
d773d5152e |
@ -233,6 +233,29 @@ dotnet_naming_style.IPascalCase.required_suffix =
|
||||
dotnet_naming_style.IPascalCase.word_separator =
|
||||
dotnet_naming_style.IPascalCase.capitalization = pascal_case
|
||||
|
||||
# TODO:
|
||||
# .NET 8 migration (new warnings are caused by the NET 8 C# compiler and analyzer)
|
||||
# The following info messages might need to be fixed in the source code instead of hiding the actual message
|
||||
# Without the following lines, dotnet format would fail
|
||||
# Disable "Collection initialization can be simplified"
|
||||
dotnet_diagnostic.IDE0028.severity = none
|
||||
dotnet_diagnostic.IDE0300.severity = none
|
||||
dotnet_diagnostic.IDE0301.severity = none
|
||||
dotnet_diagnostic.IDE0302.severity = none
|
||||
dotnet_diagnostic.IDE0305.severity = none
|
||||
# Disable "'new' expression can be simplified"
|
||||
dotnet_diagnostic.IDE0090.severity = none
|
||||
# Disable "Use primary constructor"
|
||||
dotnet_diagnostic.IDE0290.severity = none
|
||||
# Disable "Member '' does not access instance data and can be marked as static"
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
# Disable "Change type of field '' from '' to '' for improved performance"
|
||||
dotnet_diagnostic.CA1859.severity = none
|
||||
# Disable "Prefer 'static readonly' fields over constant array arguments if the called method is called repeatedly and is not mutating the passed array"
|
||||
dotnet_diagnostic.CA1861.severity = none
|
||||
# Disable "Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison, but keep in mind that this might cause subtle changes in behavior, so make sure to conduct thorough testing after applying the suggestion, or if culturally sensitive comparison is not required, consider using 'StringComparison.OrdinalIgnoreCase'"
|
||||
dotnet_diagnostic.CA1862.severity = none
|
||||
|
||||
[src/Ryujinx.HLE/HOS/Services/**.cs]
|
||||
# Disable "mark members as static" rule for services
|
||||
dotnet_diagnostic.CA1822.severity = none
|
||||
|
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@ -30,7 +30,7 @@ jobs:
|
||||
|
||||
- os: windows-latest
|
||||
OS_NAME: Windows x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win10-x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
|
||||
fail-fast: false
|
||||
@ -155,4 +155,4 @@ jobs:
|
||||
with:
|
||||
name: sdl2-ryujinx-headless-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-macos_universal
|
||||
path: "publish_headless/*.tar.gz"
|
||||
if: github.event_name == 'pull_request'
|
||||
if: github.event_name == 'pull_request'
|
||||
|
4
.github/workflows/flatpak.yml
vendored
4
.github/workflows/flatpak.yml
vendored
@ -49,7 +49,9 @@ jobs:
|
||||
run: python -m pip install PyYAML lxml
|
||||
|
||||
- name: Restore Nuget packages
|
||||
run: dotnet restore Ryujinx/${{ env.RYUJINX_PROJECT_FILE }}
|
||||
# With .NET 8.0.100, Microsoft.NET.ILLink.Tasks isn't restored by default and only seems to appears when publishing.
|
||||
# So we just publish to grab the dependencies
|
||||
run: dotnet publish -c Release -r linux-x64 Ryujinx/${{ env.RYUJINX_PROJECT_FILE }} --self-contained
|
||||
|
||||
- name: Generate nuget_sources.json
|
||||
shell: python
|
||||
|
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@ -25,7 +25,7 @@ env:
|
||||
jobs:
|
||||
tag:
|
||||
name: Create tag
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
@ -59,7 +59,7 @@ jobs:
|
||||
|
||||
- os: windows-latest
|
||||
OS_NAME: Windows x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win10-x64
|
||||
DOTNET_RUNTIME_IDENTIFIER: win-x64
|
||||
RELEASE_ZIP_OS_NAME: win_x64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -156,11 +156,11 @@ jobs:
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Setup LLVM 14
|
||||
- name: Setup LLVM 15
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 14
|
||||
sudo ./llvm.sh 15
|
||||
|
||||
- name: Install rcodesign
|
||||
run: |
|
||||
@ -215,4 +215,4 @@ jobs:
|
||||
needs: release
|
||||
with:
|
||||
ryujinx_version: "1.1.${{ github.run_number }}"
|
||||
secrets: inherit
|
||||
secrets: inherit
|
||||
|
@ -18,18 +18,19 @@
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.3.0-beta.4" />
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.19.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NetCoreServer" Version="7.0.0" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Audio.OpenAL" Version="4.8.1" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.8.1" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
@ -44,10 +45,10 @@
|
||||
<PackageVersion Include="SixLabors.ImageSharp" Version="1.0.4" />
|
||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.2" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="8.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="7.0.3" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="8.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="8.0.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -68,7 +68,7 @@ The latest automatic build for Windows, macOS, and Linux can be found on the [Of
|
||||
If you wish to build the emulator yourself, follow these steps:
|
||||
|
||||
### Step 1
|
||||
Install the X64 version of [.NET 7.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/7.0).
|
||||
Install the X64 version of [.NET 8.0 (or higher) SDK](https://dotnet.microsoft.com/download/dotnet/8.0).
|
||||
|
||||
### 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.
|
||||
@ -141,4 +141,5 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
|
||||
|
||||
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
||||
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
||||
- [ldn_mitm](https://github.com/spacemeowx2/ldn_mitm) is used for one of our available multiplayer modes.
|
||||
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
||||
|
@ -710,4 +710,4 @@
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
</details>
|
||||
</details>
|
||||
|
@ -43,7 +43,7 @@
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.games</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>11.0</string>
|
||||
<string>12.0</string>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
@ -155,4 +155,4 @@
|
||||
<string>200000</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
</plist>
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "7.0.200",
|
||||
"version": "8.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -38,7 +38,9 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
block = new Block(blkAddress);
|
||||
|
||||
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) || opsCount > instructionLimit || !memory.IsMapped(blkAddress))
|
||||
if ((dMode != DecoderMode.MultipleBlocks && visited.Count >= 1) ||
|
||||
opsCount > instructionLimit ||
|
||||
(visited.Count > 0 && !memory.IsMapped(blkAddress)))
|
||||
{
|
||||
block.Exit = true;
|
||||
block.EndAddress = blkAddress;
|
||||
|
@ -117,12 +117,11 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
int funcOffset = (int)(pointer.ToInt64() - _jitRegion.Pointer.ToInt64());
|
||||
|
||||
bool result = TryFind(funcOffset, out CacheEntry entry);
|
||||
Debug.Assert(result);
|
||||
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
|
||||
Remove(funcOffset);
|
||||
if (TryFind(funcOffset, out CacheEntry entry, out int entryIndex) && entry.Offset == funcOffset)
|
||||
{
|
||||
_cacheAllocator.Free(funcOffset, AlignCodeSize(entry.Size));
|
||||
_cacheEntries.RemoveAt(entryIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,22 +180,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
_cacheEntries.Insert(index, entry);
|
||||
}
|
||||
|
||||
private static void Remove(int offset)
|
||||
{
|
||||
int index = _cacheEntries.BinarySearch(new CacheEntry(offset, 0, default));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index - 1;
|
||||
}
|
||||
|
||||
if (index >= 0)
|
||||
{
|
||||
_cacheEntries.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryFind(int offset, out CacheEntry entry)
|
||||
public static bool TryFind(int offset, out CacheEntry entry, out int entryIndex)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
@ -210,11 +194,13 @@ namespace ARMeilleure.Translation.Cache
|
||||
if (index >= 0)
|
||||
{
|
||||
entry = _cacheEntries[index];
|
||||
entryIndex = index;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
entry = default;
|
||||
entryIndex = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
int offset = (int)((long)controlPc - context.ToInt64());
|
||||
|
||||
if (!JitCache.TryFind(offset, out CacheEntry funcEntry))
|
||||
if (!JitCache.TryFind(offset, out CacheEntry funcEntry, out _))
|
||||
{
|
||||
return null; // Not found.
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OpenTK.OpenAL" />
|
||||
<PackageReference Include="OpenTK.Audio.OpenAL" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RuntimeIdentifiers>win10-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -15,11 +15,11 @@
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dll</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dylib</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win10-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.so</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
|
@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Utils
|
||||
throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null);
|
||||
}
|
||||
|
||||
MemoryMarshal.Write(backingMemory.Span[..size], ref data);
|
||||
MemoryMarshal.Write(backingMemory.Span[..size], in data);
|
||||
|
||||
backingMemory = backingMemory[size..];
|
||||
}
|
||||
@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Renderer.Utils
|
||||
throw new ArgumentOutOfRangeException(nameof(backingMemory), backingMemory.Length, null);
|
||||
}
|
||||
|
||||
MemoryMarshal.Write(backingMemory[..size], ref data);
|
||||
MemoryMarshal.Write(backingMemory[..size], in data);
|
||||
|
||||
backingMemory = backingMemory[size..];
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -190,6 +190,7 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
||||
ConfigurationState.Instance.Graphics.EnableColorSpacePassthrough.Event += UpdateColorSpacePassthrough;
|
||||
|
||||
ConfigurationState.Instance.System.EnableInternetAccess.Event += UpdateEnableInternetAccessState;
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
||||
ConfigurationState.Instance.Multiplayer.Mode.Event += UpdateMultiplayerModeState;
|
||||
|
||||
@ -408,6 +409,11 @@ namespace Ryujinx.Ava
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateEnableInternetAccessState(object sender, ReactiveEventArgs<bool> e)
|
||||
{
|
||||
Device.Configuration.EnableInternetAccess = e.NewValue;
|
||||
}
|
||||
|
||||
private void UpdateLanInterfaceIdState(object sender, ReactiveEventArgs<string> e)
|
||||
{
|
||||
Device.Configuration.MultiplayerLanInterfaceId = e.NewValue;
|
||||
@ -710,7 +716,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
ApplicationLibrary.LoadAndSaveMetaData(Device.Processes.ActiveApplication.ProgramIdText, appMetadata =>
|
||||
{
|
||||
appMetadata.LastPlayed = DateTime.UtcNow;
|
||||
appMetadata.UpdatePreGame();
|
||||
});
|
||||
|
||||
return true;
|
||||
|
@ -14,7 +14,7 @@
|
||||
"MenuBarFileOpenEmuFolder": "Open Ryujinx Folder",
|
||||
"MenuBarFileOpenLogsFolder": "Open Logs Folder",
|
||||
"MenuBarFileExit": "_Exit",
|
||||
"MenuBarOptions": "Options",
|
||||
"MenuBarOptions": "_Options",
|
||||
"MenuBarOptionsToggleFullscreen": "Toggle Fullscreen",
|
||||
"MenuBarOptionsStartGamesInFullscreen": "Start Games in Fullscreen Mode",
|
||||
"MenuBarOptionsStopEmulation": "Stop Emulation",
|
||||
@ -30,7 +30,7 @@
|
||||
"MenuBarToolsManageFileTypes": "Manage file types",
|
||||
"MenuBarToolsInstallFileTypes": "Install file types",
|
||||
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
|
||||
"MenuBarHelp": "Help",
|
||||
"MenuBarHelp": "_Help",
|
||||
"MenuBarHelpCheckForUpdates": "Check for Updates",
|
||||
"MenuBarHelpAbout": "About",
|
||||
"MenuSearch": "Search...",
|
||||
@ -650,7 +650,7 @@
|
||||
"UserEditorTitle": "Edit User",
|
||||
"UserEditorTitleCreate": "Create User",
|
||||
"SettingsTabNetworkInterface": "Network Interface:",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN/LDN features",
|
||||
"NetworkInterfaceDefault": "Default",
|
||||
"PackagingShaders": "Packaging Shaders",
|
||||
"AboutChangelogButton": "View Changelog on GitHub",
|
||||
|
@ -173,7 +173,7 @@ namespace Ryujinx.Ava.Common
|
||||
string extension = Path.GetExtension(titleFilePath).ToLower();
|
||||
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
IFileSystem pfs;
|
||||
|
||||
if (extension == ".xci")
|
||||
{
|
||||
@ -181,7 +181,9 @@ namespace Ryujinx.Ava.Common
|
||||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
var pfsTemp = new PartitionFileSystem();
|
||||
pfsTemp.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
pfs = pfsTemp;
|
||||
}
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
|
@ -6,13 +6,13 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Common.GraphicsDriver;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.SystemInfo;
|
||||
using Ryujinx.Common.SystemInterop;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using Ryujinx.Ui.Common.SystemInfo;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win10-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RuntimeIdentifiers>win-x64;osx-x64;linux-x64</RuntimeIdentifiers>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Version>1.0.0-dirty</Version>
|
||||
@ -25,6 +25,16 @@
|
||||
<TrimMode>partial</TrimMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
FluentAvalonia, used in the Avalonia UI, requires a workaround for the json serializer used internally when using .NET 8+ System.Text.Json.
|
||||
See:
|
||||
https://github.com/amwx/FluentAvalonia/issues/481
|
||||
https://devblogs.microsoft.com/dotnet/system-text-json-in-dotnet-8/
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<JsonSerializerIsReflectionEnabledByDefault>true</JsonSerializerIsReflectionEnabledByDefault>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" />
|
||||
<PackageReference Include="Avalonia.Desktop" />
|
||||
@ -40,7 +50,7 @@
|
||||
<PackageReference Include="OpenTK.Core" />
|
||||
<PackageReference Include="Ryujinx.Audio.OpenAL.Dependencies" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64' AND '$(RuntimeIdentifier)' != 'osx-arm64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Nvdec.Dependencies" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'" />
|
||||
<PackageReference Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win-x64'" />
|
||||
<PackageReference Include="Silk.NET.Vulkan" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||
|
@ -12,6 +12,11 @@
|
||||
Click="ToggleFavorite_Click"
|
||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
|
||||
<MenuItem
|
||||
Click="CreateApplicationShortcut_Click"
|
||||
Header="{locale:Locale GameListContextMenuCreateShortcut}"
|
||||
IsEnabled="{Binding CreateShortcutEnabled}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Click="OpenUserSaveDirectory_Click"
|
||||
@ -82,9 +87,4 @@
|
||||
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
Click="CreateApplicationShortcut_Click"
|
||||
Header="{locale:Locale GameListContextMenuCreateShortcut}"
|
||||
IsEnabled="{Binding CreateShortcutEnabled}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
|
||||
</MenuFlyout>
|
||||
|
@ -126,17 +126,17 @@
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TimePlayed}"
|
||||
Text="{Binding TimePlayedString}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding LastPlayed, Converter={helpers:NullableDateTimeConverter}}"
|
||||
Text="{Binding LastPlayedString, Converter={helpers:LocalizedNeverConverter}}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding FileSize}"
|
||||
Text="{Binding FileSizeString}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
|
43
src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
Normal file
43
src/Ryujinx.Ava/UI/Helpers/LocalizedNeverConverter.cs
Normal file
@ -0,0 +1,43 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// This <see cref="IValueConverter"/> makes sure that the string "Never" that's returned by <see cref="ValueFormatUtils.FormatDateTime"/> is properly localized in the Avalonia UI.
|
||||
/// After the Avalonia UI has been made the default and the GTK UI is removed, <see cref="ValueFormatUtils"/> should be updated to directly return a localized string.
|
||||
/// </summary>
|
||||
internal class LocalizedNeverConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
private static readonly LocalizedNeverConverter _instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not string valStr)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
if (valStr == "Never")
|
||||
{
|
||||
return LocaleManager.Instance[LocaleKeys.Never];
|
||||
}
|
||||
|
||||
return valStr;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
internal class NullableDateTimeConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
private static readonly NullableDateTimeConverter _instance = new();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return LocaleManager.Instance[LocaleKeys.Never];
|
||||
}
|
||||
|
||||
if (value is DateTime dateTime)
|
||||
{
|
||||
return dateTime.ToLocalTime().ToString(culture);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider)
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
}
|
@ -86,7 +86,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
public static partial IntPtr SetCursor(IntPtr handle);
|
||||
|
||||
[LibraryImport("user32.dll")]
|
||||
public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, byte[] pvAndPlane, byte[] pvXorPlane);
|
||||
public static partial IntPtr CreateCursor(IntPtr hInst, int xHotSpot, int yHotSpot, int nWidth, int nHeight, [In] byte[] pvAndPlane, [In] byte[] pvXorPlane);
|
||||
|
||||
[LibraryImport("user32.dll", SetLastError = true, EntryPoint = "RegisterClassExW")]
|
||||
public static partial ushort RegisterClassEx(ref WndClassEx param);
|
||||
|
@ -13,20 +13,19 @@ namespace Ryujinx.Ava.UI.Models.Generic
|
||||
|
||||
public int Compare(ApplicationData x, ApplicationData y)
|
||||
{
|
||||
var aValue = x.LastPlayed;
|
||||
var bValue = y.LastPlayed;
|
||||
DateTime aValue = DateTime.UnixEpoch, bValue = DateTime.UnixEpoch;
|
||||
|
||||
if (!aValue.HasValue)
|
||||
if (x?.LastPlayed != null)
|
||||
{
|
||||
aValue = DateTime.UnixEpoch;
|
||||
aValue = x.LastPlayed.Value;
|
||||
}
|
||||
|
||||
if (!bValue.HasValue)
|
||||
if (y?.LastPlayed != null)
|
||||
{
|
||||
bValue = DateTime.UnixEpoch;
|
||||
bValue = y.LastPlayed.Value;
|
||||
}
|
||||
|
||||
return (IsAscending ? 1 : -1) * DateTime.Compare(bValue.Value, aValue.Value);
|
||||
return (IsAscending ? 1 : -1) * DateTime.Compare(aValue, bValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
Normal file
31
src/Ryujinx.Ava/UI/Models/Generic/TimePlayedSortComparer.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Models.Generic
|
||||
{
|
||||
internal class TimePlayedSortComparer : IComparer<ApplicationData>
|
||||
{
|
||||
public TimePlayedSortComparer() { }
|
||||
public TimePlayedSortComparer(bool isAscending) { IsAscending = isAscending; }
|
||||
|
||||
public bool IsAscending { get; }
|
||||
|
||||
public int Compare(ApplicationData x, ApplicationData y)
|
||||
{
|
||||
TimeSpan aValue = TimeSpan.Zero, bValue = TimeSpan.Zero;
|
||||
|
||||
if (x?.TimePlayed != null)
|
||||
{
|
||||
aValue = x.TimePlayed;
|
||||
}
|
||||
|
||||
if (y?.TimePlayed != null)
|
||||
{
|
||||
bValue = y.TimePlayed;
|
||||
}
|
||||
|
||||
return (IsAscending ? 1 : -1) * TimeSpan.Compare(aValue, bValue);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@ -38,26 +38,7 @@ namespace Ryujinx.Ava.UI.Models
|
||||
|
||||
public bool SizeAvailable { get; set; }
|
||||
|
||||
public string SizeString => GetSizeString();
|
||||
|
||||
private string GetSizeString()
|
||||
{
|
||||
const int Scale = 1024;
|
||||
string[] orders = { "GiB", "MiB", "KiB" };
|
||||
long max = (long)Math.Pow(Scale, orders.Length);
|
||||
|
||||
foreach (string order in orders)
|
||||
{
|
||||
if (Size > max)
|
||||
{
|
||||
return $"{decimal.Divide(Size, max):##.##} {order}";
|
||||
}
|
||||
|
||||
max /= Scale;
|
||||
}
|
||||
|
||||
return "0 KiB";
|
||||
}
|
||||
public string SizeString => ValueFormatUtils.FormatFileSize(Size);
|
||||
|
||||
public SaveModel(SaveDataInfo info)
|
||||
{
|
||||
|
@ -126,7 +126,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
|
||||
@ -232,7 +233,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
using FileStream containerFile = File.OpenRead(path);
|
||||
|
||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||
PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(containerFile.AsStorage()).ThrowIfFailure();
|
||||
bool containsDownloadableContent = false;
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
|
@ -930,21 +930,20 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
return SortMode switch
|
||||
{
|
||||
#pragma warning disable IDE0055 // Disable formatting
|
||||
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
|
||||
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
||||
ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending),
|
||||
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSizeBytes)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSizeBytes),
|
||||
ApplicationSort.TotalTimePlayed => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TimePlayedNum)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TimePlayedNum),
|
||||
ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.TitleName)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.TitleName),
|
||||
ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending),
|
||||
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
|
||||
ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSize)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize),
|
||||
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
||||
ApplicationSort.Favorite => !IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Favorite)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Favorite),
|
||||
ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Developer),
|
||||
ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension),
|
||||
ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path)
|
||||
: SortExpressionComparer<ApplicationData>.Descending(app => app.Path),
|
||||
_ => null,
|
||||
#pragma warning restore IDE0055
|
||||
};
|
||||
@ -1549,13 +1548,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata =>
|
||||
{
|
||||
if (appMetadata.LastPlayed.HasValue)
|
||||
{
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
appMetadata.LastPlayed = DateTime.UtcNow;
|
||||
appMetadata.UpdatePostGame();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -170,7 +170,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
try
|
||||
{
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, new PartitionFileSystem(file.AsStorage()), TitleId.ToString("x16"), 0);
|
||||
var pfs = new PartitionFileSystem();
|
||||
pfs.Initialize(file.AsStorage()).ThrowIfFailure();
|
||||
(Nca patchNca, Nca controlNca) = ApplicationLibrary.GetGameUpdateDataFromPartition(VirtualFileSystem, pfs, TitleId.ToString("x16"), 0);
|
||||
|
||||
if (controlNca != null && patchNca != null)
|
||||
{
|
||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Common.Configuration
|
||||
string appDataPath;
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Library", "Application Support");
|
||||
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -3,5 +3,6 @@
|
||||
public enum MultiplayerMode
|
||||
{
|
||||
Disabled,
|
||||
LdnMitm,
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
@ -74,5 +74,10 @@ namespace Ryujinx.Common.Utilities
|
||||
{
|
||||
return ConvertIpv4Address(IPAddress.Parse(ipAddress));
|
||||
}
|
||||
|
||||
public static IPAddress ConvertUint(uint ipAddress)
|
||||
{
|
||||
return new IPAddress(new byte[] { (byte)((ipAddress >> 24) & 0xFF), (byte)((ipAddress >> 16) & 0xFF), (byte)((ipAddress >> 8) & 0xFF), (byte)(ipAddress & 0xFF) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
62
src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs
Normal file
62
src/Ryujinx.Cpu/AppleHv/HvCodePatcher.cs
Normal file
@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
|
||||
namespace Ryujinx.Cpu.AppleHv
|
||||
{
|
||||
static class HvCodePatcher
|
||||
{
|
||||
private const uint XMask = 0x3f808000u;
|
||||
private const uint XValue = 0x8000000u;
|
||||
|
||||
private const uint ZrIndex = 31u;
|
||||
|
||||
public static void RewriteUnorderedExclusiveInstructions(Span<byte> code)
|
||||
{
|
||||
Span<uint> codeUint = MemoryMarshal.Cast<byte, uint>(code);
|
||||
Span<Vector128<uint>> codeVector = MemoryMarshal.Cast<byte, Vector128<uint>>(code);
|
||||
|
||||
Vector128<uint> mask = Vector128.Create(XMask);
|
||||
Vector128<uint> value = Vector128.Create(XValue);
|
||||
|
||||
for (int index = 0; index < codeVector.Length; index++)
|
||||
{
|
||||
Vector128<uint> v = codeVector[index];
|
||||
|
||||
if (Vector128.EqualsAny(Vector128.BitwiseAnd(v, mask), value))
|
||||
{
|
||||
int baseIndex = index * 4;
|
||||
|
||||
for (int instIndex = baseIndex; instIndex < baseIndex + 4; instIndex++)
|
||||
{
|
||||
ref uint inst = ref codeUint[instIndex];
|
||||
|
||||
if ((inst & XMask) != XValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isPair = (inst & (1u << 21)) != 0;
|
||||
bool isLoad = (inst & (1u << 22)) != 0;
|
||||
|
||||
uint rt2 = (inst >> 10) & 0x1fu;
|
||||
uint rs = (inst >> 16) & 0x1fu;
|
||||
|
||||
if (isLoad && rs != ZrIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isPair && rt2 != ZrIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Set the ordered flag.
|
||||
inst |= 1u << 15;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -128,21 +128,6 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0051 // Remove unused private member
|
||||
/// <summary>
|
||||
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
|
||||
/// </summary>
|
||||
/// <param name="va">Virtual address of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
private void AssertMapped(ulong va, ulong size)
|
||||
{
|
||||
if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
|
||||
{
|
||||
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
|
||||
}
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||
{
|
||||
@ -736,6 +721,24 @@ namespace Ryujinx.Cpu.AppleHv
|
||||
return (int)(vaSpan / PageSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
if (protection.HasFlag(MemoryPermission.Execute))
|
||||
{
|
||||
// Some applications use unordered exclusive memory access instructions
|
||||
// where it is not valid to do so, leading to memory re-ordering that
|
||||
// makes the code behave incorrectly on some CPUs.
|
||||
// To work around this, we force all such accesses to be ordered.
|
||||
|
||||
using WritableRegion writableRegion = GetWritableRegion(va, (int)size);
|
||||
|
||||
HvCodePatcher.RewriteUnorderedExclusiveInstructions(writableRegion.Memory.Span);
|
||||
}
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
|
@ -575,24 +575,17 @@ namespace Ryujinx.Cpu.Jit
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable IDE0051 // Remove unused private member
|
||||
private ulong GetPhysicalAddress(ulong va)
|
||||
{
|
||||
// We return -1L if the virtual address is invalid or unmapped.
|
||||
if (!ValidateAddress(va) || !IsMapped(va))
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
return GetPhysicalAddressInternal(va);
|
||||
}
|
||||
#pragma warning restore IDE0051
|
||||
|
||||
private ulong GetPhysicalAddressInternal(ulong va)
|
||||
{
|
||||
return PteToPa(_pageTable.Read<ulong>((va / PageSize) * PteSize) & ~(0xffffUL << 48)) + (va & PageMask);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
@ -698,9 +691,5 @@ namespace Ryujinx.Cpu.Jit
|
||||
/// Disposes of resources used by the memory manager.
|
||||
/// </summary>
|
||||
protected override void Destroy() => _pageTable.Dispose();
|
||||
|
||||
#pragma warning disable IDE0051 // Remove unused private member
|
||||
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
|
||||
#pragma warning restore IDE0051
|
||||
}
|
||||
}
|
||||
|
@ -615,6 +615,12 @@ namespace Ryujinx.Cpu.Jit
|
||||
return (int)(vaSpan / PageSize);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Reprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
|
@ -211,6 +211,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
int xCount = (int)_state.State.LineLengthIn;
|
||||
int yCount = (int)_state.State.LineCount;
|
||||
|
||||
_channel.TextureManager.RefreshModifiedTextures();
|
||||
_3dEngine.CreatePendingSyncs();
|
||||
_3dEngine.FlushUboDirty();
|
||||
|
||||
@ -279,7 +280,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
|
||||
bool completeSource = IsTextureCopyComplete(src, srcLinear, srcBpp, srcStride, xCount, yCount);
|
||||
bool completeDest = IsTextureCopyComplete(dst, dstLinear, dstBpp, dstStride, xCount, yCount);
|
||||
|
||||
if (completeSource && completeDest)
|
||||
// Try to set the texture data directly,
|
||||
// but only if we are doing a complete copy,
|
||||
// and not for block linear to linear copies, since those are typically accessed from the CPU.
|
||||
|
||||
if (completeSource && completeDest && !(dstLinear && !srcLinear))
|
||||
{
|
||||
var target = memoryManager.Physical.TextureCache.FindTexture(
|
||||
memoryManager,
|
||||
|
@ -149,6 +149,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// </summary>
|
||||
public bool HadPoolOwner { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Physical memory ranges where the texture data is located.
|
||||
/// </summary>
|
||||
public MultiRange Range { get; private set; }
|
||||
@ -1700,7 +1701,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if (Group.Storage == this)
|
||||
{
|
||||
Group.Unmapped();
|
||||
|
||||
Group.ClearModified(unmapRange);
|
||||
}
|
||||
}
|
||||
|
@ -107,8 +107,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Any texture that has been unmapped at any point or is partially unmapped
|
||||
// should update their pool references after the remap completes.
|
||||
|
||||
MultiRange unmapped = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
|
||||
|
||||
foreach (var texture in _partiallyMappedTextures)
|
||||
{
|
||||
texture.UpdatePoolMappings();
|
||||
@ -735,9 +733,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
if (overlap.IsView)
|
||||
{
|
||||
overlapCompatibility = overlapCompatibility == TextureViewCompatibility.FormatAlias ?
|
||||
TextureViewCompatibility.Incompatible :
|
||||
TextureViewCompatibility.CopyOnly;
|
||||
overlapCompatibility = TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -815,7 +811,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
Texture overlap = _textureOverlaps[index];
|
||||
OverlapInfo oInfo = _overlapInfo[index];
|
||||
|
||||
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible || oInfo.Compatibility == TextureViewCompatibility.FormatAlias)
|
||||
if (oInfo.Compatibility <= TextureViewCompatibility.LayoutIncompatible)
|
||||
{
|
||||
if (!overlap.IsView && texture.DataOverlaps(overlap, oInfo.Compatibility))
|
||||
{
|
||||
|
@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
// D32F and R32F texture have the same representation internally,
|
||||
// however the R32F format is used to sample from depth textures.
|
||||
if (lhs.FormatInfo.Format == Format.D32Float && rhs.FormatInfo.Format == Format.R32Float && (forSampler || depthAlias))
|
||||
if (IsValidDepthAsColorAlias(lhs.FormatInfo.Format, rhs.FormatInfo.Format) && (forSampler || depthAlias))
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
@ -239,14 +239,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
|
||||
if (lhs.FormatInfo.Format == Format.D16Unorm && rhs.FormatInfo.Format == Format.R16Unorm)
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
|
||||
if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
|
||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
else if ((lhs.FormatInfo.Format == Format.D24UnormS8Uint ||
|
||||
lhs.FormatInfo.Format == Format.S8UintD24Unorm) && rhs.FormatInfo.Format == Format.B8G8R8A8Unorm)
|
||||
{
|
||||
return TextureMatchQuality.FormatAlias;
|
||||
}
|
||||
@ -632,12 +626,27 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (lhsFormat.Format.IsDepthOrStencil() || rhsFormat.Format.IsDepthOrStencil())
|
||||
{
|
||||
return FormatMatches(lhs, rhs, flags.HasFlag(TextureSearchFlags.ForSampler), flags.HasFlag(TextureSearchFlags.DepthAlias)) switch
|
||||
bool forSampler = flags.HasFlag(TextureSearchFlags.ForSampler);
|
||||
bool depthAlias = flags.HasFlag(TextureSearchFlags.DepthAlias);
|
||||
|
||||
TextureMatchQuality matchQuality = FormatMatches(lhs, rhs, forSampler, depthAlias);
|
||||
|
||||
if (matchQuality == TextureMatchQuality.Perfect)
|
||||
{
|
||||
TextureMatchQuality.Perfect => TextureViewCompatibility.Full,
|
||||
TextureMatchQuality.FormatAlias => TextureViewCompatibility.FormatAlias,
|
||||
_ => TextureViewCompatibility.Incompatible,
|
||||
};
|
||||
return TextureViewCompatibility.Full;
|
||||
}
|
||||
else if (matchQuality == TextureMatchQuality.FormatAlias)
|
||||
{
|
||||
return TextureViewCompatibility.FormatAlias;
|
||||
}
|
||||
else if (IsValidColorAsDepthAlias(lhsFormat.Format, rhsFormat.Format) || IsValidDepthAsColorAlias(lhsFormat.Format, rhsFormat.Format))
|
||||
{
|
||||
return TextureViewCompatibility.CopyOnly;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsFormatHostIncompatible(lhs, caps) || IsFormatHostIncompatible(rhs, caps))
|
||||
@ -666,6 +675,30 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return TextureViewCompatibility.Incompatible;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if it's valid to alias a color format as a depth format.
|
||||
/// </summary>
|
||||
/// <param name="lhsFormat">Source format to be checked</param>
|
||||
/// <param name="rhsFormat">Target format to be checked</param>
|
||||
/// <returns>True if it's valid to alias the formats</returns>
|
||||
private static bool IsValidColorAsDepthAlias(Format lhsFormat, Format rhsFormat)
|
||||
{
|
||||
return (lhsFormat == Format.R32Float && rhsFormat == Format.D32Float) ||
|
||||
(lhsFormat == Format.R16Unorm && rhsFormat == Format.D16Unorm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if it's valid to alias a depth format as a color format.
|
||||
/// </summary>
|
||||
/// <param name="lhsFormat">Source format to be checked</param>
|
||||
/// <param name="rhsFormat">Target format to be checked</param>
|
||||
/// <returns>True if it's valid to alias the formats</returns>
|
||||
private static bool IsValidDepthAsColorAlias(Format lhsFormat, Format rhsFormat)
|
||||
{
|
||||
return (lhsFormat == Format.D32Float && rhsFormat == Format.R32Float) ||
|
||||
(lhsFormat == Format.D16Unorm && rhsFormat == Format.R16Unorm);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if aliasing of two formats that would normally be considered incompatible be allowed,
|
||||
/// using copy dependencies.
|
||||
|
@ -992,26 +992,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action to perform when a memory tracking handle is flipped to dirty.
|
||||
/// This notifies overlapping textures that the memory needs to be synchronized.
|
||||
/// </summary>
|
||||
/// <param name="groupHandle">The handle that a dirty flag was set on</param>
|
||||
private void DirtyAction(TextureGroupHandle groupHandle)
|
||||
{
|
||||
// Notify all textures that belong to this handle.
|
||||
|
||||
Storage.SignalGroupDirty();
|
||||
|
||||
lock (groupHandle.Overlaps)
|
||||
{
|
||||
foreach (Texture overlap in groupHandle.Overlaps)
|
||||
{
|
||||
overlap.SignalGroupDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a CpuRegionHandle for a given address and size range in CPU VA.
|
||||
/// </summary>
|
||||
@ -1083,11 +1063,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
views,
|
||||
result.ToArray());
|
||||
|
||||
foreach (RegionHandle handle in result)
|
||||
{
|
||||
handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
|
||||
}
|
||||
|
||||
return groupHandle;
|
||||
}
|
||||
|
||||
@ -1359,11 +1334,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
var groupHandle = new TextureGroupHandle(this, 0, Storage.Size, _views, 0, 0, 0, _allOffsets.Length, cpuRegionHandles);
|
||||
|
||||
foreach (RegionHandle handle in cpuRegionHandles)
|
||||
{
|
||||
handle.RegisterDirtyEvent(() => DirtyAction(groupHandle));
|
||||
}
|
||||
|
||||
handles = new TextureGroupHandle[] { groupHandle };
|
||||
}
|
||||
else
|
||||
@ -1619,6 +1589,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if ((ignore == null || !handle.HasDependencyTo(ignore)) && handle.Modified)
|
||||
{
|
||||
handle.Modified = false;
|
||||
handle.DeferredCopy = null;
|
||||
Storage.SignalModifiedDirty();
|
||||
|
||||
lock (handle.Overlaps)
|
||||
@ -1659,6 +1630,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return;
|
||||
}
|
||||
|
||||
// If size is zero, we have nothing to flush.
|
||||
if (size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
||||
// In this case it will skip registering the action, but here we are already handling it,
|
||||
// so there shouldn't be any issue as it's the same handler for all actions.
|
||||
|
@ -152,6 +152,32 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// Linear textures are presumed to be used for readback initially.
|
||||
_flushBalance = FlushBalanceThreshold + FlushBalanceIncrement;
|
||||
}
|
||||
|
||||
foreach (RegionHandle handle in handles)
|
||||
{
|
||||
handle.RegisterDirtyEvent(DirtyAction);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action to perform when a memory tracking handle is flipped to dirty.
|
||||
/// This notifies overlapping textures that the memory needs to be synchronized.
|
||||
/// </summary>
|
||||
private void DirtyAction()
|
||||
{
|
||||
// Notify all textures that belong to this handle.
|
||||
|
||||
_group.Storage.SignalGroupDirty();
|
||||
|
||||
lock (Overlaps)
|
||||
{
|
||||
foreach (Texture overlap in Overlaps)
|
||||
{
|
||||
overlap.SignalGroupDirty();
|
||||
}
|
||||
}
|
||||
|
||||
DeferredCopy = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -449,7 +475,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
public void DeferCopy(TextureGroupHandle copyFrom)
|
||||
{
|
||||
Modified = false;
|
||||
|
||||
DeferredCopy = copyFrom;
|
||||
|
||||
_group.Storage.SignalGroupDirty();
|
||||
@ -506,7 +531,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
existing.Other.Handle.CreateCopyDependency(this);
|
||||
|
||||
if (copyToOther)
|
||||
if (copyToOther && Modified)
|
||||
{
|
||||
existing.Other.Handle.DeferCopy(this);
|
||||
}
|
||||
@ -550,10 +575,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
if (fromHandle != null)
|
||||
{
|
||||
// Only copy if the copy texture is still modified.
|
||||
// It will be set as unmodified if new data is written from CPU, as the data previously in the texture will flush.
|
||||
// DeferredCopy will be set to null if new data is written from CPU (see the DirtyAction method).
|
||||
// It will also set as unmodified if a copy is deferred to it.
|
||||
|
||||
shouldCopy = fromHandle.Modified;
|
||||
shouldCopy = true;
|
||||
|
||||
if (fromHandle._bindCount == 0)
|
||||
{
|
||||
|
@ -20,8 +20,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
private readonly Texture[] _rtColors;
|
||||
private readonly ITexture[] _rtHostColors;
|
||||
private readonly bool[] _rtColorsBound;
|
||||
private Texture _rtDepthStencil;
|
||||
private ITexture _rtHostDs;
|
||||
private bool _rtDsBound;
|
||||
|
||||
public int ClipRegionWidth { get; private set; }
|
||||
public int ClipRegionHeight { get; private set; }
|
||||
@ -51,6 +53,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
_rtColors = new Texture[Constants.TotalRenderTargets];
|
||||
_rtHostColors = new ITexture[Constants.TotalRenderTargets];
|
||||
_rtColorsBound = new bool[Constants.TotalRenderTargets];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -154,7 +157,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (_rtColors[index] != color)
|
||||
{
|
||||
_rtColors[index]?.SignalModifying(false);
|
||||
if (_rtColorsBound[index])
|
||||
{
|
||||
_rtColors[index]?.SignalModifying(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rtColorsBound[index] = true;
|
||||
}
|
||||
|
||||
if (color != null)
|
||||
{
|
||||
@ -180,7 +190,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (_rtDepthStencil != depthStencil)
|
||||
{
|
||||
_rtDepthStencil?.SignalModifying(false);
|
||||
if (_rtDsBound)
|
||||
{
|
||||
_rtDepthStencil?.SignalModifying(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_rtDsBound = true;
|
||||
}
|
||||
|
||||
if (depthStencil != null)
|
||||
{
|
||||
@ -413,21 +430,45 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
bool anyChanged = false;
|
||||
|
||||
if (_rtHostDs != _rtDepthStencil?.HostTexture)
|
||||
{
|
||||
_rtHostDs = _rtDepthStencil?.HostTexture;
|
||||
Texture dsTexture = _rtDepthStencil;
|
||||
ITexture hostDsTexture = null;
|
||||
|
||||
if (dsTexture != null)
|
||||
{
|
||||
hostDsTexture = dsTexture.HostTexture;
|
||||
|
||||
if (!_rtDsBound)
|
||||
{
|
||||
dsTexture.SignalModifying(true);
|
||||
_rtDsBound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_rtHostDs != hostDsTexture)
|
||||
{
|
||||
_rtHostDs = hostDsTexture;
|
||||
anyChanged = true;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _rtColors.Length; index++)
|
||||
{
|
||||
ITexture hostTexture = _rtColors[index]?.HostTexture;
|
||||
Texture texture = _rtColors[index];
|
||||
ITexture hostTexture = null;
|
||||
|
||||
if (texture != null)
|
||||
{
|
||||
hostTexture = texture.HostTexture;
|
||||
|
||||
if (!_rtColorsBound[index])
|
||||
{
|
||||
texture.SignalModifying(true);
|
||||
_rtColorsBound[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_rtHostColors[index] != hostTexture)
|
||||
{
|
||||
_rtHostColors[index] = hostTexture;
|
||||
|
||||
anyChanged = true;
|
||||
}
|
||||
}
|
||||
@ -452,6 +493,31 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks all currently bound render target textures as modified, and also makes them be set as modified again on next use.
|
||||
/// </summary>
|
||||
public void RefreshModifiedTextures()
|
||||
{
|
||||
Texture dsTexture = _rtDepthStencil;
|
||||
|
||||
if (dsTexture != null && _rtDsBound)
|
||||
{
|
||||
dsTexture.SignalModifying(false);
|
||||
_rtDsBound = false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < _rtColors.Length; index++)
|
||||
{
|
||||
Texture texture = _rtColors[index];
|
||||
|
||||
if (texture != null && _rtColorsBound[index])
|
||||
{
|
||||
texture.SignalModifying(false);
|
||||
_rtColorsBound[index] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Forces the texture and sampler pools to be re-loaded from the cache on next use.
|
||||
/// </summary>
|
||||
@ -488,11 +554,19 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
for (int i = 0; i < _rtColors.Length; i++)
|
||||
{
|
||||
_rtColors[i]?.DecrementReferenceCount();
|
||||
if (_rtColorsBound[i])
|
||||
{
|
||||
_rtColors[i]?.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
_rtColors[i] = null;
|
||||
}
|
||||
|
||||
_rtDepthStencil?.DecrementReferenceCount();
|
||||
if (_rtDsBound)
|
||||
{
|
||||
_rtDepthStencil?.DecrementReferenceCount();
|
||||
}
|
||||
|
||||
_rtDepthStencil = null;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 5791;
|
||||
private const uint CodeGenVersion = 5957;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
|
@ -37,10 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||
/// <returns>The incremented value of the syncpoint</returns>
|
||||
public uint IncrementSyncpoint(uint id)
|
||||
{
|
||||
if (id >= MaxHardwareSyncpoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
|
||||
|
||||
return _syncpoints[id].Increment();
|
||||
}
|
||||
@ -53,10 +50,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||
/// <returns>The value of the syncpoint</returns>
|
||||
public uint GetSyncpointValue(uint id)
|
||||
{
|
||||
if (id >= MaxHardwareSyncpoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
|
||||
|
||||
return _syncpoints[id].Value;
|
||||
}
|
||||
@ -72,10 +66,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||
/// <returns>The created SyncpointWaiterHandle object or null if already past threshold</returns>
|
||||
public SyncpointWaiterHandle RegisterCallbackOnSyncpoint(uint id, uint threshold, Action<SyncpointWaiterHandle> callback)
|
||||
{
|
||||
if (id >= MaxHardwareSyncpoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
|
||||
|
||||
return _syncpoints[id].RegisterCallback(threshold, callback);
|
||||
}
|
||||
@ -88,10 +79,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||
/// <exception cref="System.ArgumentOutOfRangeException">Thrown when id >= MaxHardwareSyncpoints</exception>
|
||||
public void UnregisterCallback(uint id, SyncpointWaiterHandle waiterInformation)
|
||||
{
|
||||
if (id >= MaxHardwareSyncpoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
|
||||
|
||||
_syncpoints[id].UnregisterCallback(waiterInformation);
|
||||
}
|
||||
@ -107,10 +95,7 @@ namespace Ryujinx.Graphics.Gpu.Synchronization
|
||||
/// <returns>True if timed out</returns>
|
||||
public bool WaitOnSyncpoint(uint id, uint threshold, TimeSpan timeout)
|
||||
{
|
||||
if (id >= MaxHardwareSyncpoints)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(id));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(id, (uint)MaxHardwareSyncpoints);
|
||||
|
||||
// TODO: Remove this when GPU channel scheduling will be implemented.
|
||||
if (timeout == Timeout.InfiniteTimeSpan)
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -367,7 +367,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
return to;
|
||||
}
|
||||
|
||||
private TextureView PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
|
||||
public void PboCopy(TextureView from, TextureView to, int srcLayer, int dstLayer, int srcLevel, int dstLevel, int width, int height)
|
||||
{
|
||||
int dstWidth = width;
|
||||
int dstHeight = height;
|
||||
@ -445,8 +445,6 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
}
|
||||
|
||||
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
private void EnsurePbo(TextureView view)
|
||||
|
@ -140,6 +140,28 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
|
||||
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, 0, firstLayer, 0, firstLevel, layers, levels);
|
||||
}
|
||||
else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
|
||||
{
|
||||
int layers = Math.Min(Info.GetLayers(), destinationView.Info.GetLayers() - firstLayer);
|
||||
int levels = Math.Min(Info.Levels, destinationView.Info.Levels - firstLevel);
|
||||
|
||||
for (int level = 0; level < levels; level++)
|
||||
{
|
||||
int srcWidth = Math.Max(1, Width >> level);
|
||||
int srcHeight = Math.Max(1, Height >> level);
|
||||
|
||||
int dstWidth = Math.Max(1, destinationView.Width >> (firstLevel + level));
|
||||
int dstHeight = Math.Max(1, destinationView.Height >> (firstLevel + level));
|
||||
|
||||
int minWidth = Math.Min(srcWidth, dstWidth);
|
||||
int minHeight = Math.Min(srcHeight, dstHeight);
|
||||
|
||||
for (int layer = 0; layer < layers; layer++)
|
||||
{
|
||||
_renderer.TextureCopy.PboCopy(this, destinationView, 0, firstLayer + layer, 0, firstLevel + level, minWidth, minHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
|
||||
@ -169,6 +191,13 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
||||
{
|
||||
_renderer.TextureCopyIncompatible.CopyIncompatibleFormats(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||
}
|
||||
else if (destinationView.Format.IsDepthOrStencil() != Format.IsDepthOrStencil())
|
||||
{
|
||||
int minWidth = Math.Min(Width, destinationView.Width);
|
||||
int minHeight = Math.Min(Height, destinationView.Height);
|
||||
|
||||
_renderer.TextureCopy.PboCopy(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, minWidth, minHeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -69,10 +69,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||
|
||||
public Operand GetDest(int index)
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfNotEqual(index, 0);
|
||||
|
||||
return _dest;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -55,7 +55,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bindlessHandle.AsgOp is not Operation handleCombineOp)
|
||||
if (!TryGetOperation(bindlessHandle.AsgOp, out Operation handleCombineOp))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@ -199,9 +199,64 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetOperation(INode asgOp, out Operation outOperation)
|
||||
{
|
||||
if (asgOp is PhiNode phi)
|
||||
{
|
||||
// If we have a phi, let's check if all inputs are effectively the same value.
|
||||
// If so, we can "see through" the phi and pick any of the inputs (since they are all the same).
|
||||
|
||||
Operand firstSrc = phi.GetSource(0);
|
||||
|
||||
for (int index = 1; index < phi.SourcesCount; index++)
|
||||
{
|
||||
if (!IsSameOperand(firstSrc, phi.GetSource(index)))
|
||||
{
|
||||
outOperation = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
asgOp = firstSrc.AsgOp;
|
||||
}
|
||||
|
||||
if (asgOp is Operation operation)
|
||||
{
|
||||
outOperation = operation;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
outOperation = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsSameOperand(Operand x, Operand y)
|
||||
{
|
||||
if (x.Type == y.Type && x.Type == OperandType.LocalVariable)
|
||||
{
|
||||
return x.AsgOp is Operation xOp &&
|
||||
y.AsgOp is Operation yOp &&
|
||||
xOp.Inst == Instruction.BitwiseOr &&
|
||||
yOp.Inst == Instruction.BitwiseOr &&
|
||||
AreBothEqualConstantBuffers(xOp.GetSource(0), yOp.GetSource(0)) &&
|
||||
AreBothEqualConstantBuffers(xOp.GetSource(1), yOp.GetSource(1));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool AreBothEqualConstantBuffers(Operand x, Operand y)
|
||||
{
|
||||
return x.Type == y.Type && x.Value == y.Value && x.Type == OperandType.ConstantBuffer;
|
||||
}
|
||||
|
||||
private static Operand GetSourceForMaskedHandle(Operation asgOp, uint mask)
|
||||
{
|
||||
// Assume it was already checked that the operation is bitwise AND.
|
||||
|
||||
Operand src0 = asgOp.GetSource(0);
|
||||
Operand src1 = asgOp.GetSource(1);
|
||||
|
||||
@ -210,6 +265,7 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||
// We can't check if the mask matches here as both operands are from a constant buffer.
|
||||
// Be optimistic and assume it matches. Avoid constant buffer 1 as official drivers
|
||||
// uses this one to store compiler constants.
|
||||
|
||||
return src0.GetCbufSlot() == 1 ? src1 : src0;
|
||||
}
|
||||
else if (src0.Type == OperandType.ConstantBuffer && src1.Type == OperandType.Constant)
|
||||
|
@ -1,6 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
@ -102,11 +103,11 @@ namespace Ryujinx.Graphics.Texture.Utils
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static RgbaColor32 operator <<(RgbaColor32 x, int shift)
|
||||
public static RgbaColor32 operator <<(RgbaColor32 x, [ConstantExpected] byte shift)
|
||||
{
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
return new RgbaColor32(Sse2.ShiftLeftLogical(x._color, (byte)shift));
|
||||
return new RgbaColor32(Sse2.ShiftLeftLogical(x._color, shift));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -115,11 +116,11 @@ namespace Ryujinx.Graphics.Texture.Utils
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static RgbaColor32 operator >>(RgbaColor32 x, int shift)
|
||||
public static RgbaColor32 operator >>(RgbaColor32 x, [ConstantExpected] byte shift)
|
||||
{
|
||||
if (Sse2.IsSupported)
|
||||
{
|
||||
return new RgbaColor32(Sse2.ShiftRightLogical(x._color, (byte)shift));
|
||||
return new RgbaColor32(Sse2.ShiftRightLogical(x._color, shift));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -189,7 +189,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
1,
|
||||
new ReadOnlySpan<MemoryBarrier>(memoryBarrier),
|
||||
new ReadOnlySpan<MemoryBarrier>(in memoryBarrier),
|
||||
0,
|
||||
ReadOnlySpan<BufferMemoryBarrier>.Empty,
|
||||
0,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
|
@ -211,6 +211,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
|
||||
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, 0, firstLayer, 0, firstLevel, layers, levels);
|
||||
}
|
||||
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
|
||||
{
|
||||
int layers = Math.Min(Info.GetLayers(), dst.Info.GetLayers() - firstLayer);
|
||||
int levels = Math.Min(Info.Levels, dst.Info.Levels - firstLevel);
|
||||
|
||||
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, 0, firstLayer, 0, FirstLevel, layers, levels);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextureCopy.Copy(
|
||||
@ -260,6 +267,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
_gd.HelperShader.CopyIncompatibleFormats(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||
}
|
||||
else if (src.Info.Format.IsDepthOrStencil() != dst.Info.Format.IsDepthOrStencil())
|
||||
{
|
||||
_gd.HelperShader.CopyColor(_gd, cbs, src, dst, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
TextureCopy.Copy(
|
||||
|
@ -33,9 +33,5 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public VulkanException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
protected VulkanException(SerializationInfo info, StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,6 @@ namespace Ryujinx.HLE.Exceptions
|
||||
Request = context.Request;
|
||||
}
|
||||
|
||||
protected ServiceNotImplementedException(SerializationInfo info, StreamingContext context) : base(info, context) { }
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
|
@ -198,7 +198,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read);
|
||||
fs.OpenFile(ref ncaFile.Ref, ncaPath.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
var nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||
if (nca.Header.ContentType != NcaContentType.Meta)
|
||||
{
|
||||
@ -210,7 +210,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
using var pfs0 = nca.OpenFileSystem(0, integrityCheckLevel);
|
||||
using var cnmtFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read);
|
||||
pfs0.OpenFile(ref cnmtFile.Ref, pfs0.EnumerateEntries().Single().FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
var cnmt = new Cnmt(cnmtFile.Get.AsStream());
|
||||
if (cnmt.Type != ContentMetaType.AddOnContent || (cnmt.TitleId & 0xFFFFFFFFFFFFE000) != aocBaseId)
|
||||
@ -220,7 +220,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
string ncaId = Convert.ToHexString(cnmt.ContentEntries[0].NcaId).ToLower();
|
||||
|
||||
AddAocItem(cnmt.TitleId, containerPath, $"{ncaId}.nca", true);
|
||||
AddAocItem(cnmt.TitleId, containerPath, $"/{ncaId}.nca", true);
|
||||
}
|
||||
}
|
||||
|
||||
@ -238,7 +238,8 @@ namespace Ryujinx.HLE.FileSystem
|
||||
if (!mergedToContainer)
|
||||
{
|
||||
using FileStream fileStream = File.OpenRead(containerPath);
|
||||
using PartitionFileSystem partitionFileSystem = new(fileStream.AsStorage());
|
||||
using PartitionFileSystem partitionFileSystem = new();
|
||||
partitionFileSystem.Initialize(fileStream.AsStorage()).ThrowIfFailure();
|
||||
|
||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||
}
|
||||
@ -259,17 +260,17 @@ namespace Ryujinx.HLE.FileSystem
|
||||
{
|
||||
var file = new FileStream(aoc.ContainerPath, FileMode.Open, FileAccess.Read);
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
PartitionFileSystem pfs;
|
||||
|
||||
switch (Path.GetExtension(aoc.ContainerPath))
|
||||
{
|
||||
case ".xci":
|
||||
pfs = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
||||
var xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage()).OpenPartition(XciPartitionType.Secure);
|
||||
xci.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
break;
|
||||
case ".nsp":
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read);
|
||||
var pfs = new PartitionFileSystem();
|
||||
pfs.Initialize(file.AsStorage());
|
||||
pfs.OpenFile(ref ncaFile.Ref, aoc.NcaPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
break;
|
||||
default:
|
||||
return false; // Print error?
|
||||
@ -419,10 +420,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
if (locationList != null)
|
||||
{
|
||||
if (locationList.Contains(entry))
|
||||
{
|
||||
locationList.Remove(entry);
|
||||
}
|
||||
locationList.Remove(entry);
|
||||
|
||||
locationList.AddLast(entry);
|
||||
}
|
||||
@ -606,11 +604,11 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
if (filesystem.FileExists($"{path}/00"))
|
||||
{
|
||||
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode);
|
||||
filesystem.OpenFile(ref file.Ref, $"{path}/00".ToU8Span(), mode).ThrowIfFailure();
|
||||
}
|
||||
else
|
||||
{
|
||||
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode);
|
||||
filesystem.OpenFile(ref file.Ref, path.ToU8Span(), mode).ThrowIfFailure();
|
||||
}
|
||||
|
||||
return file.Release();
|
||||
|
@ -7,6 +7,7 @@ using LibHac.Fs.Shim;
|
||||
using LibHac.FsSrv;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Sdmmc;
|
||||
using LibHac.Spl;
|
||||
using LibHac.Tools.Es;
|
||||
using LibHac.Tools.Fs;
|
||||
@ -32,7 +33,7 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
public KeySet KeySet { get; private set; }
|
||||
public EmulatedGameCard GameCard { get; private set; }
|
||||
public EmulatedSdCard SdCard { get; private set; }
|
||||
public SdmmcApi SdCard { get; private set; }
|
||||
public ModLoader ModLoader { get; private set; }
|
||||
|
||||
private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid;
|
||||
@ -198,15 +199,15 @@ namespace Ryujinx.HLE.FileSystem
|
||||
fsServerObjects.FsCreators.EncryptedFileSystemCreator = new EncryptedFileSystemCreator();
|
||||
|
||||
GameCard = fsServerObjects.GameCard;
|
||||
SdCard = fsServerObjects.SdCard;
|
||||
SdCard = fsServerObjects.Sdmmc;
|
||||
|
||||
SdCard.SetSdCardInsertionStatus(true);
|
||||
SdCard.SetSdCardInserted(true);
|
||||
|
||||
var fsServerConfig = new FileSystemServerConfig
|
||||
{
|
||||
DeviceOperator = fsServerObjects.DeviceOperator,
|
||||
ExternalKeySet = KeySet.ExternalKeySet,
|
||||
FsCreators = fsServerObjects.FsCreators,
|
||||
StorageDeviceManagerFactory = fsServerObjects.StorageDeviceManagerFactory,
|
||||
RandomGenerator = randomGenerator,
|
||||
};
|
||||
|
||||
@ -263,7 +264,16 @@ namespace Ryujinx.HLE.FileSystem
|
||||
|
||||
if (result.IsSuccess())
|
||||
{
|
||||
Ticket ticket = new(ticketFile.Get.AsStream());
|
||||
// When reading a file from a Sha256PartitionFileSystem, you can't start a read in the middle
|
||||
// of the hashed portion (usually the first 0x200 bytes) of the file and end the read after
|
||||
// the end of the hashed portion, so we read the ticket file using a single read.
|
||||
byte[] ticketData = new byte[0x2C0];
|
||||
result = ticketFile.Get.Read(out long bytesRead, 0, ticketData);
|
||||
|
||||
if (result.IsFailure() || bytesRead != ticketData.Length)
|
||||
continue;
|
||||
|
||||
Ticket ticket = new(new MemoryStream(ticketData));
|
||||
var titleKey = ticket.GetTitleKey(KeySet);
|
||||
|
||||
if (titleKey != null)
|
||||
|
@ -101,7 +101,7 @@ namespace Ryujinx.HLE
|
||||
/// <summary>
|
||||
/// Control if the guest application should be told that there is a Internet connection available.
|
||||
/// </summary>
|
||||
internal readonly bool EnableInternetAccess;
|
||||
public bool EnableInternetAccess { internal get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Control LibHac's integrity check level.
|
||||
|
46
src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
Normal file
46
src/Ryujinx.HLE/HOS/Kernel/Memory/KMemoryPermission.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
[Flags]
|
||||
enum KMemoryPermission : uint
|
||||
{
|
||||
None = 0,
|
||||
UserMask = Read | Write | Execute,
|
||||
Mask = uint.MaxValue,
|
||||
|
||||
Read = 1 << 0,
|
||||
Write = 1 << 1,
|
||||
Execute = 1 << 2,
|
||||
DontCare = 1 << 28,
|
||||
|
||||
ReadAndWrite = Read | Write,
|
||||
ReadAndExecute = Read | Execute,
|
||||
}
|
||||
|
||||
static class KMemoryPermissionExtensions
|
||||
{
|
||||
public static MemoryPermission Convert(this KMemoryPermission permission)
|
||||
{
|
||||
MemoryPermission output = MemoryPermission.None;
|
||||
|
||||
if (permission.HasFlag(KMemoryPermission.Read))
|
||||
{
|
||||
output = MemoryPermission.Read;
|
||||
}
|
||||
|
||||
if (permission.HasFlag(KMemoryPermission.Write))
|
||||
{
|
||||
output |= MemoryPermission.Write;
|
||||
}
|
||||
|
||||
if (permission.HasFlag(KMemoryPermission.Execute))
|
||||
{
|
||||
output |= MemoryPermission.Execute;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
}
|
@ -203,15 +203,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
/// <inheritdoc/>
|
||||
protected override Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||
{
|
||||
// TODO.
|
||||
_cpuMemory.Reprotect(address, pagesCount * PageSize, permission.Convert());
|
||||
|
||||
return Result.Success;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||
protected override Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission)
|
||||
{
|
||||
// TODO.
|
||||
return Result.Success;
|
||||
// TODO: Flush JIT cache.
|
||||
|
||||
return Reprotect(address, pagesCount, permission);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -1255,7 +1255,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
if ((oldPermission & KMemoryPermission.Execute) != 0)
|
||||
{
|
||||
result = ReprotectWithAttributes(address, pagesCount, permission);
|
||||
result = ReprotectAndFlush(address, pagesCount, permission);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -3036,13 +3036,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
protected abstract Result Reprotect(ulong address, ulong pagesCount, KMemoryPermission permission);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the permissions of a given virtual memory region.
|
||||
/// Changes the permissions of a given virtual memory region, while also flushing the cache.
|
||||
/// </summary>
|
||||
/// <param name="address">Virtual address of the region to have the permission changes</param>
|
||||
/// <param name="pagesCount">Number of pages to have their permissions changed</param>
|
||||
/// <param name="permission">New permission</param>
|
||||
/// <returns>Result of the permission change operation</returns>
|
||||
protected abstract Result ReprotectWithAttributes(ulong address, ulong pagesCount, KMemoryPermission permission);
|
||||
protected abstract Result ReprotectAndFlush(ulong address, ulong pagesCount, KMemoryPermission permission);
|
||||
|
||||
/// <summary>
|
||||
/// Alerts the memory tracking that a given region has been read from or written to.
|
||||
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
[Flags]
|
||||
enum KMemoryPermission : uint
|
||||
{
|
||||
None = 0,
|
||||
UserMask = Read | Write | Execute,
|
||||
Mask = uint.MaxValue,
|
||||
|
||||
Read = 1 << 0,
|
||||
Write = 1 << 1,
|
||||
Execute = 1 << 2,
|
||||
DontCare = 1 << 28,
|
||||
|
||||
ReadAndWrite = Read | Write,
|
||||
ReadAndExecute = Read | Execute,
|
||||
}
|
||||
}
|
@ -533,7 +533,9 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
Logger.Info?.Print(LogClass.ModLoader, "Using replacement ExeFS partition");
|
||||
|
||||
exefs = new PartitionFileSystem(mods.ExefsContainers[0].Path.OpenRead().AsStorage());
|
||||
var pfs = new PartitionFileSystem();
|
||||
pfs.Initialize(mods.ExefsContainers[0].Path.OpenRead().AsStorage()).ThrowIfFailure();
|
||||
exefs = pfs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -26,7 +26,9 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
try
|
||||
{
|
||||
LocalStorage storage = new(pfsPath, FileAccess.Read, FileMode.Open);
|
||||
using SharedRef<LibHac.Fs.Fsa.IFileSystem> nsp = new(new PartitionFileSystem(storage));
|
||||
var pfs = new PartitionFileSystem();
|
||||
using SharedRef<LibHac.Fs.Fsa.IFileSystem> nsp = new(pfs);
|
||||
pfs.Initialize(storage).ThrowIfFailure();
|
||||
|
||||
ImportTitleKeysFromNsp(nsp.Get, context.Device.System.KeySet);
|
||||
|
||||
@ -90,7 +92,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
|
||||
try
|
||||
{
|
||||
PartitionFileSystem nsp = new(pfsFile.AsStorage());
|
||||
PartitionFileSystem nsp = new();
|
||||
nsp.Initialize(pfsFile.AsStorage()).ThrowIfFailure();
|
||||
|
||||
ImportTitleKeysFromNsp(nsp, context.Device.System.KeySet);
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
using LibHac;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using Path = LibHac.FsSrv.Sf.Path;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
@ -202,6 +203,16 @@ namespace Ryujinx.HLE.HOS.Services.Fs.FileSystemProxy
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
[CommandCmif(16)]
|
||||
public ResultCode GetFileSystemAttribute(ServiceCtx context)
|
||||
{
|
||||
Result result = _fileSystem.Get.GetFileSystemAttribute(out FileSystemAttribute attribute);
|
||||
|
||||
context.ResponseData.Write(SpanHelpers.AsReadOnlyByteSpan(in attribute));
|
||||
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
if (isDisposing)
|
||||
|
@ -1380,7 +1380,10 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
||||
[CommandCmif(1016)]
|
||||
public ResultCode FlushAccessLogOnSdCard(ServiceCtx context)
|
||||
{
|
||||
return (ResultCode)_baseFileSystemProxy.Get.FlushAccessLogOnSdCard().Value;
|
||||
// Logging the access log to the SD card isn't implemented, meaning this function will be a no-op since
|
||||
// there's nothing to flush. Return success until it's implemented.
|
||||
// return (ResultCode)_baseFileSystemProxy.Get.FlushAccessLogOnSdCard().Value;
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
[CommandCmif(1017)]
|
||||
|
12
src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs
Normal file
12
src/Ryujinx.HLE/HOS/Services/Ldn/LdnConst.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn
|
||||
{
|
||||
static class LdnConst
|
||||
{
|
||||
public const int SsidLengthMax = 0x20;
|
||||
public const int AdvertiseDataSizeMax = 0x180;
|
||||
public const int UserNameBytesMax = 0x20;
|
||||
public const int NodeCountMax = 8;
|
||||
public const int StationCountMax = NodeCountMax - 1;
|
||||
public const int PassphraseLengthMax = 0x40;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
@ -48,7 +48,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
{
|
||||
result[i].Reserved = new Array7<byte>();
|
||||
|
||||
if (i < 8)
|
||||
if (i < LdnConst.NodeCountMax)
|
||||
{
|
||||
result[i].State = array[i].State;
|
||||
array[i].State = NodeLatestUpdateFlags.None;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.Types
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
@ -30,7 +29,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
_parent.NetworkClient.NetworkChange -= NetworkChanged;
|
||||
}
|
||||
|
||||
private void NetworkChanged(object sender, RyuLdn.NetworkChangeEventArgs e)
|
||||
private void NetworkChanged(object sender, NetworkChangeEventArgs e)
|
||||
{
|
||||
LatestUpdates.CalculateLatestUpdate(NetworkInfo.Ldn.Nodes, e.Info.Ldn.Nodes);
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
interface INetworkClient : IDisposable
|
||||
{
|
||||
bool NeedsRealId { get; }
|
||||
|
||||
event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||
|
||||
void DisconnectNetwork();
|
@ -8,7 +8,7 @@ using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Ipc;
|
||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm;
|
||||
using Ryujinx.Horizon.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
@ -395,7 +395,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1)
|
||||
if (scanFilter.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
|
||||
{
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
@ -546,7 +546,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
context.RequestData.BaseStream.Seek(4, SeekOrigin.Current); // Alignment?
|
||||
NetworkConfig networkConfig = context.RequestData.ReadStruct<NetworkConfig>();
|
||||
|
||||
if (networkConfig.IntentId.LocalCommunicationId == -1)
|
||||
if (networkConfig.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
|
||||
{
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
@ -555,7 +555,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
|
||||
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkConfig.IntentId.LocalCommunicationId);
|
||||
if (!isLocalCommunicationIdValid)
|
||||
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
||||
{
|
||||
return ResultCode.InvalidObject;
|
||||
}
|
||||
@ -568,13 +568,13 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
networkConfig.Channel = CheckDevelopmentChannel(networkConfig.Channel);
|
||||
securityConfig.SecurityMode = CheckDevelopmentSecurityMode(securityConfig.SecurityMode);
|
||||
|
||||
if (networkConfig.NodeCountMax <= 8)
|
||||
if (networkConfig.NodeCountMax <= LdnConst.NodeCountMax)
|
||||
{
|
||||
if ((((ulong)networkConfig.LocalCommunicationVersion) & 0x80000000) == 0)
|
||||
{
|
||||
if (securityConfig.SecurityMode <= SecurityMode.Retail)
|
||||
{
|
||||
if (securityConfig.Passphrase.Length <= 0x40)
|
||||
if (securityConfig.Passphrase.Length <= LdnConst.PassphraseLengthMax)
|
||||
{
|
||||
if (_state == NetworkState.AccessPoint)
|
||||
{
|
||||
@ -678,7 +678,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
return _nifmResultCode;
|
||||
}
|
||||
|
||||
if (bufferSize == 0 || bufferSize > 0x180)
|
||||
if (bufferSize == 0 || bufferSize > LdnConst.AdvertiseDataSizeMax)
|
||||
{
|
||||
return ResultCode.InvalidArgument;
|
||||
}
|
||||
@ -848,10 +848,10 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
|
||||
context.Memory.Read(bufferPosition, networkInfoBytes);
|
||||
|
||||
networkInfo = MemoryMarshal.Cast<byte, NetworkInfo>(networkInfoBytes)[0];
|
||||
networkInfo = MemoryMarshal.Read<NetworkInfo>(networkInfoBytes);
|
||||
}
|
||||
|
||||
if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1)
|
||||
if (networkInfo.NetworkId.IntentId.LocalCommunicationId == -1 && NetworkClient.NeedsRealId)
|
||||
{
|
||||
// TODO: Call nn::arp::GetApplicationControlProperty here when implemented.
|
||||
ApplicationControlProperty controlProperty = context.Device.Processes.ActiveApplication.ApplicationControlProperties;
|
||||
@ -860,7 +860,7 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
}
|
||||
|
||||
bool isLocalCommunicationIdValid = CheckLocalCommunicationIdPermission(context, (ulong)networkInfo.NetworkId.IntentId.LocalCommunicationId);
|
||||
if (!isLocalCommunicationIdValid)
|
||||
if (!isLocalCommunicationIdValid && NetworkClient.NeedsRealId)
|
||||
{
|
||||
return ResultCode.InvalidObject;
|
||||
}
|
||||
@ -1061,10 +1061,16 @@ namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
if (System.Net.NetworkInformation.NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
MultiplayerMode mode = context.Device.Configuration.MultiplayerMode;
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initializing with multiplayer mode: {mode}");
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case MultiplayerMode.LdnMitm:
|
||||
NetworkClient = new LdnMitmClient(context.Device.Configuration);
|
||||
break;
|
||||
case MultiplayerMode.Disabled:
|
||||
NetworkClient = new DisabledLdnClient();
|
||||
NetworkClient = new LdnDisabledClient();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Network.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.RyuLdn
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator
|
||||
{
|
||||
class DisabledLdnClient : INetworkClient
|
||||
class LdnDisabledClient : INetworkClient
|
||||
{
|
||||
public bool NeedsRealId => true;
|
||||
|
||||
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||
|
||||
public NetworkError Connect(ConnectRequest request)
|
@ -0,0 +1,611 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
|
||||
{
|
||||
internal class LanDiscovery : IDisposable
|
||||
{
|
||||
private const int DefaultPort = 11452;
|
||||
private const ushort CommonChannel = 6;
|
||||
private const byte CommonLinkLevel = 3;
|
||||
private const byte CommonNetworkType = 2;
|
||||
|
||||
private const int FailureTimeout = 4000;
|
||||
|
||||
private readonly LdnMitmClient _parent;
|
||||
private readonly LanProtocol _protocol;
|
||||
private bool _initialized;
|
||||
private readonly Ssid _fakeSsid;
|
||||
private ILdnTcpSocket _tcp;
|
||||
private LdnProxyUdpServer _udp, _udp2;
|
||||
private readonly List<LdnProxyTcpSession> _stations = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
private readonly AutoResetEvent _apConnected = new(false);
|
||||
|
||||
internal readonly IPAddress LocalAddr;
|
||||
internal readonly IPAddress LocalBroadcastAddr;
|
||||
internal NetworkInfo NetworkInfo;
|
||||
|
||||
public bool IsHost => _tcp is LdnProxyTcpServer;
|
||||
|
||||
private readonly Random _random = new();
|
||||
|
||||
// NOTE: Credit to https://stackoverflow.com/a/39338188
|
||||
private static IPAddress GetBroadcastAddress(IPAddress address, IPAddress mask)
|
||||
{
|
||||
uint ipAddress = BitConverter.ToUInt32(address.GetAddressBytes(), 0);
|
||||
uint ipMaskV4 = BitConverter.ToUInt32(mask.GetAddressBytes(), 0);
|
||||
uint broadCastIpAddress = ipAddress | ~ipMaskV4;
|
||||
|
||||
return new IPAddress(BitConverter.GetBytes(broadCastIpAddress));
|
||||
}
|
||||
|
||||
private static NetworkInfo GetEmptyNetworkInfo()
|
||||
{
|
||||
NetworkInfo networkInfo = new()
|
||||
{
|
||||
NetworkId = new NetworkId
|
||||
{
|
||||
SessionId = new Array16<byte>(),
|
||||
},
|
||||
Common = new CommonNetworkInfo
|
||||
{
|
||||
MacAddress = new Array6<byte>(),
|
||||
Ssid = new Ssid
|
||||
{
|
||||
Name = new Array33<byte>(),
|
||||
},
|
||||
},
|
||||
Ldn = new LdnNetworkInfo
|
||||
{
|
||||
NodeCountMax = LdnConst.NodeCountMax,
|
||||
SecurityParameter = new Array16<byte>(),
|
||||
Nodes = new Array8<NodeInfo>(),
|
||||
AdvertiseData = new Array384<byte>(),
|
||||
Reserved4 = new Array140<byte>(),
|
||||
},
|
||||
};
|
||||
|
||||
for (int i = 0; i < LdnConst.NodeCountMax; i++)
|
||||
{
|
||||
networkInfo.Ldn.Nodes[i] = new NodeInfo
|
||||
{
|
||||
MacAddress = new Array6<byte>(),
|
||||
UserName = new Array33<byte>(),
|
||||
Reserved2 = new Array16<byte>(),
|
||||
};
|
||||
}
|
||||
|
||||
return networkInfo;
|
||||
}
|
||||
|
||||
public LanDiscovery(LdnMitmClient parent, IPAddress ipAddress, IPAddress ipv4Mask)
|
||||
{
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Initialize LanDiscovery using IP: {ipAddress}");
|
||||
|
||||
_parent = parent;
|
||||
LocalAddr = ipAddress;
|
||||
LocalBroadcastAddr = GetBroadcastAddress(ipAddress, ipv4Mask);
|
||||
|
||||
_fakeSsid = new Ssid
|
||||
{
|
||||
Length = LdnConst.SsidLengthMax,
|
||||
};
|
||||
_random.NextBytes(_fakeSsid.Name.AsSpan()[..32]);
|
||||
|
||||
_protocol = new LanProtocol(this);
|
||||
_protocol.Accept += OnConnect;
|
||||
_protocol.SyncNetwork += OnSyncNetwork;
|
||||
_protocol.DisconnectStation += DisconnectStation;
|
||||
|
||||
NetworkInfo = GetEmptyNetworkInfo();
|
||||
|
||||
ResetStations();
|
||||
|
||||
if (!InitUdp())
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "LanDiscovery Initialize: InitUdp failed.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
protected void OnSyncNetwork(NetworkInfo info)
|
||||
{
|
||||
bool updated = false;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (!NetworkInfo.Equals(info))
|
||||
{
|
||||
NetworkInfo = info;
|
||||
updated = true;
|
||||
|
||||
Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"Host IP: {NetworkHelpers.ConvertUint(info.Ldn.Nodes[0].Ipv4Address)}");
|
||||
}
|
||||
}
|
||||
|
||||
if (updated)
|
||||
{
|
||||
_parent.InvokeNetworkChange(info, true);
|
||||
}
|
||||
|
||||
_apConnected.Set();
|
||||
}
|
||||
|
||||
protected void OnConnect(LdnProxyTcpSession station)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
station.NodeId = LocateEmptyNode();
|
||||
|
||||
if (_stations.Count > LdnConst.StationCountMax || station.NodeId == -1)
|
||||
{
|
||||
station.Disconnect();
|
||||
station.Dispose();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_stations.Add(station);
|
||||
|
||||
UpdateNodes();
|
||||
}
|
||||
}
|
||||
|
||||
public void DisconnectStation(LdnProxyTcpSession station)
|
||||
{
|
||||
if (!station.IsDisposed)
|
||||
{
|
||||
if (station.IsConnected)
|
||||
{
|
||||
station.Disconnect();
|
||||
}
|
||||
|
||||
station.Dispose();
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_stations.Remove(station))
|
||||
{
|
||||
NetworkInfo.Ldn.Nodes[station.NodeId] = new NodeInfo()
|
||||
{
|
||||
MacAddress = new Array6<byte>(),
|
||||
UserName = new Array33<byte>(),
|
||||
Reserved2 = new Array16<byte>(),
|
||||
};
|
||||
|
||||
UpdateNodes();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool SetAdvertiseData(byte[] data)
|
||||
{
|
||||
if (data.Length > LdnConst.AdvertiseDataSizeMax)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "AdvertiseData exceeds size limit.");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
data.CopyTo(NetworkInfo.Ldn.AdvertiseData.AsSpan());
|
||||
NetworkInfo.Ldn.AdvertiseDataSize = (ushort)data.Length;
|
||||
|
||||
// NOTE: Otherwise this results in SessionKeepFailed or MasterDisconnected
|
||||
lock (_lock)
|
||||
{
|
||||
if (NetworkInfo.Ldn.Nodes[0].IsConnected == 1)
|
||||
{
|
||||
UpdateNodes(true);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void InitNetworkInfo()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
NetworkInfo.Common.MacAddress = GetFakeMac();
|
||||
NetworkInfo.Common.Channel = CommonChannel;
|
||||
NetworkInfo.Common.LinkLevel = CommonLinkLevel;
|
||||
NetworkInfo.Common.NetworkType = CommonNetworkType;
|
||||
NetworkInfo.Common.Ssid = _fakeSsid;
|
||||
|
||||
NetworkInfo.Ldn.Nodes = new Array8<NodeInfo>();
|
||||
|
||||
for (int i = 0; i < LdnConst.NodeCountMax; i++)
|
||||
{
|
||||
NetworkInfo.Ldn.Nodes[i].NodeId = (byte)i;
|
||||
NetworkInfo.Ldn.Nodes[i].IsConnected = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Array6<byte> GetFakeMac(IPAddress address = null)
|
||||
{
|
||||
address ??= LocalAddr;
|
||||
|
||||
byte[] ip = address.GetAddressBytes();
|
||||
|
||||
var macAddress = new Array6<byte>();
|
||||
new byte[] { 0x02, 0x00, ip[0], ip[1], ip[2], ip[3] }.CopyTo(macAddress.AsSpan());
|
||||
|
||||
return macAddress;
|
||||
}
|
||||
|
||||
public bool InitTcp(bool listening, IPAddress address = null, int port = DefaultPort)
|
||||
{
|
||||
Logger.Debug?.PrintMsg(LogClass.ServiceLdn, $"LanDiscovery InitTcp: IP: {address}, listening: {listening}");
|
||||
|
||||
if (_tcp != null)
|
||||
{
|
||||
_tcp.DisconnectAndStop();
|
||||
_tcp.Dispose();
|
||||
_tcp = null;
|
||||
}
|
||||
|
||||
ILdnTcpSocket tcpSocket;
|
||||
|
||||
if (listening)
|
||||
{
|
||||
try
|
||||
{
|
||||
address ??= LocalAddr;
|
||||
|
||||
tcpSocket = new LdnProxyTcpServer(_protocol, address, port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyTcpServer: {ex}");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!tcpSocket.Start())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (address == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
tcpSocket = new LdnProxyTcpClient(_protocol, address, port);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyTcpClient: {ex}");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_tcp = tcpSocket;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool InitUdp()
|
||||
{
|
||||
_udp?.Stop();
|
||||
_udp2?.Stop();
|
||||
|
||||
try
|
||||
{
|
||||
// NOTE: Linux won't receive any broadcast packets if the socket is not bound to the broadcast address.
|
||||
// Windows only works if bound to localhost or the local address.
|
||||
// See this discussion: https://stackoverflow.com/questions/13666789/receiving-udp-broadcast-packets-on-linux
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
_udp2 = new LdnProxyUdpServer(_protocol, LocalBroadcastAddr, DefaultPort);
|
||||
}
|
||||
|
||||
_udp = new LdnProxyUdpServer(_protocol, LocalAddr, DefaultPort);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to create LdnProxyUdpServer: {ex}");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public NetworkInfo[] Scan(ushort channel, ScanFilter filter)
|
||||
{
|
||||
_udp.ClearScanResults();
|
||||
|
||||
if (_protocol.SendBroadcast(_udp, LanPacketType.Scan, DefaultPort) < 0)
|
||||
{
|
||||
return Array.Empty<NetworkInfo>();
|
||||
}
|
||||
|
||||
List<NetworkInfo> outNetworkInfo = new();
|
||||
|
||||
foreach (KeyValuePair<ulong, NetworkInfo> item in _udp.GetScanResults())
|
||||
{
|
||||
bool copy = true;
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.LocalCommunicationId))
|
||||
{
|
||||
copy &= filter.NetworkId.IntentId.LocalCommunicationId == item.Value.NetworkId.IntentId.LocalCommunicationId;
|
||||
}
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.SessionId))
|
||||
{
|
||||
copy &= filter.NetworkId.SessionId.AsSpan().SequenceEqual(item.Value.NetworkId.SessionId.AsSpan());
|
||||
}
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.NetworkType))
|
||||
{
|
||||
copy &= filter.NetworkType == (NetworkType)item.Value.Common.NetworkType;
|
||||
}
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.Ssid))
|
||||
{
|
||||
Span<byte> gameSsid = item.Value.Common.Ssid.Name.AsSpan()[item.Value.Common.Ssid.Length..];
|
||||
Span<byte> scanSsid = filter.Ssid.Name.AsSpan()[filter.Ssid.Length..];
|
||||
copy &= gameSsid.SequenceEqual(scanSsid);
|
||||
}
|
||||
|
||||
if (filter.Flag.HasFlag(ScanFilterFlag.SceneId))
|
||||
{
|
||||
copy &= filter.NetworkId.IntentId.SceneId == item.Value.NetworkId.IntentId.SceneId;
|
||||
}
|
||||
|
||||
if (copy)
|
||||
{
|
||||
if (item.Value.Ldn.Nodes[0].UserName[0] != 0)
|
||||
{
|
||||
outNetworkInfo.Add(item.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, "LanDiscovery Scan: Got empty Username. There might be a timing issue somewhere...");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return outNetworkInfo.ToArray();
|
||||
}
|
||||
|
||||
protected void ResetStations()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
foreach (LdnProxyTcpSession station in _stations)
|
||||
{
|
||||
station.Disconnect();
|
||||
station.Dispose();
|
||||
}
|
||||
|
||||
_stations.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private int LocateEmptyNode()
|
||||
{
|
||||
Array8<NodeInfo> nodes = NetworkInfo.Ldn.Nodes;
|
||||
|
||||
for (int i = 1; i < nodes.Length; i++)
|
||||
{
|
||||
if (nodes[i].IsConnected == 0)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
protected void UpdateNodes(bool forceUpdate = false)
|
||||
{
|
||||
int countConnected = 1;
|
||||
|
||||
foreach (LdnProxyTcpSession station in _stations.Where(station => station.IsConnected))
|
||||
{
|
||||
countConnected++;
|
||||
|
||||
station.OverrideInfo();
|
||||
|
||||
// NOTE: This is not part of the original implementation.
|
||||
NetworkInfo.Ldn.Nodes[station.NodeId] = station.NodeInfo;
|
||||
}
|
||||
|
||||
byte nodeCount = (byte)countConnected;
|
||||
|
||||
bool networkInfoChanged = forceUpdate || NetworkInfo.Ldn.NodeCount != nodeCount;
|
||||
|
||||
NetworkInfo.Ldn.NodeCount = nodeCount;
|
||||
|
||||
foreach (LdnProxyTcpSession station in _stations)
|
||||
{
|
||||
if (station.IsConnected)
|
||||
{
|
||||
if (_protocol.SendPacket(station, LanPacketType.SyncNetwork, SpanHelpers.AsSpan<NetworkInfo, byte>(ref NetworkInfo).ToArray()) < 0)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Failed to send {LanPacketType.SyncNetwork} to station {station.NodeId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (networkInfoChanged)
|
||||
{
|
||||
_parent.InvokeNetworkChange(NetworkInfo, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected NodeInfo GetNodeInfo(NodeInfo node, UserConfig userConfig, ushort localCommunicationVersion)
|
||||
{
|
||||
uint ipAddress = NetworkHelpers.ConvertIpv4Address(LocalAddr);
|
||||
|
||||
node.MacAddress = GetFakeMac();
|
||||
node.IsConnected = 1;
|
||||
node.UserName = userConfig.UserName;
|
||||
node.LocalCommunicationVersion = localCommunicationVersion;
|
||||
node.Ipv4Address = ipAddress;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
public bool CreateNetwork(SecurityConfig securityConfig, UserConfig userConfig, NetworkConfig networkConfig)
|
||||
{
|
||||
if (!InitTcp(true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InitNetworkInfo();
|
||||
|
||||
NetworkInfo.Ldn.NodeCountMax = networkConfig.NodeCountMax;
|
||||
NetworkInfo.Ldn.SecurityMode = (ushort)securityConfig.SecurityMode;
|
||||
|
||||
NetworkInfo.Common.Channel = networkConfig.Channel == 0 ? (ushort)6 : networkConfig.Channel;
|
||||
|
||||
NetworkInfo.NetworkId.SessionId = new Array16<byte>();
|
||||
_random.NextBytes(NetworkInfo.NetworkId.SessionId.AsSpan());
|
||||
NetworkInfo.NetworkId.IntentId = networkConfig.IntentId;
|
||||
|
||||
NetworkInfo.Ldn.Nodes[0] = GetNodeInfo(NetworkInfo.Ldn.Nodes[0], userConfig, networkConfig.LocalCommunicationVersion);
|
||||
NetworkInfo.Ldn.Nodes[0].IsConnected = 1;
|
||||
NetworkInfo.Ldn.NodeCount++;
|
||||
|
||||
_parent.InvokeNetworkChange(NetworkInfo, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DestroyNetwork()
|
||||
{
|
||||
if (_tcp != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcp.DisconnectAndStop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_tcp.Dispose();
|
||||
_tcp = null;
|
||||
}
|
||||
}
|
||||
|
||||
ResetStations();
|
||||
}
|
||||
|
||||
public NetworkError Connect(NetworkInfo networkInfo, UserConfig userConfig, uint localCommunicationVersion)
|
||||
{
|
||||
_apConnected.Reset();
|
||||
|
||||
if (networkInfo.Ldn.NodeCount == 0)
|
||||
{
|
||||
return NetworkError.Unknown;
|
||||
}
|
||||
|
||||
IPAddress address = NetworkHelpers.ConvertUint(networkInfo.Ldn.Nodes[0].Ipv4Address);
|
||||
|
||||
Logger.Info?.PrintMsg(LogClass.ServiceLdn, $"Connecting to host: {address}");
|
||||
|
||||
if (!InitTcp(false, address))
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Could not initialize TCPClient");
|
||||
|
||||
return NetworkError.ConnectNotFound;
|
||||
}
|
||||
|
||||
if (!_tcp.Connect())
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Failed to connect.");
|
||||
|
||||
return NetworkError.ConnectFailure;
|
||||
}
|
||||
|
||||
NodeInfo myNode = GetNodeInfo(new NodeInfo(), userConfig, (ushort)localCommunicationVersion);
|
||||
if (_protocol.SendPacket(_tcp, LanPacketType.Connect, SpanHelpers.AsSpan<NodeInfo, byte>(ref myNode).ToArray()) < 0)
|
||||
{
|
||||
return NetworkError.Unknown;
|
||||
}
|
||||
|
||||
return _apConnected.WaitOne(FailureTimeout) ? NetworkError.None : NetworkError.ConnectTimeout;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
DisconnectAndStop();
|
||||
ResetStations();
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
_protocol.Accept -= OnConnect;
|
||||
_protocol.SyncNetwork -= OnSyncNetwork;
|
||||
_protocol.DisconnectStation -= DisconnectStation;
|
||||
}
|
||||
|
||||
public void DisconnectAndStop()
|
||||
{
|
||||
if (_udp != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_udp.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_udp.Dispose();
|
||||
_udp = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_udp2 != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_udp2.Stop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_udp2.Dispose();
|
||||
_udp2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tcp != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_tcp.DisconnectAndStop();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_tcp.Dispose();
|
||||
_tcp = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Types;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
|
||||
{
|
||||
internal class LanProtocol
|
||||
{
|
||||
private const uint LanMagic = 0x11451400;
|
||||
|
||||
public const int BufferSize = 2048;
|
||||
public const int TcpTxBufferSize = 0x800;
|
||||
public const int TcpRxBufferSize = 0x1000;
|
||||
public const int TxBufferSizeMax = 0x2000;
|
||||
public const int RxBufferSizeMax = 0x2000;
|
||||
|
||||
private readonly int _headerSize = Marshal.SizeOf<LanPacketHeader>();
|
||||
|
||||
private readonly LanDiscovery _discovery;
|
||||
|
||||
public event Action<LdnProxyTcpSession> Accept;
|
||||
public event Action<EndPoint, LanPacketType, byte[]> Scan;
|
||||
public event Action<NetworkInfo> ScanResponse;
|
||||
public event Action<NetworkInfo> SyncNetwork;
|
||||
public event Action<NodeInfo, EndPoint> Connect;
|
||||
public event Action<LdnProxyTcpSession> DisconnectStation;
|
||||
|
||||
public LanProtocol(LanDiscovery parent)
|
||||
{
|
||||
_discovery = parent;
|
||||
}
|
||||
|
||||
public void InvokeAccept(LdnProxyTcpSession session)
|
||||
{
|
||||
Accept?.Invoke(session);
|
||||
}
|
||||
|
||||
public void InvokeDisconnectStation(LdnProxyTcpSession session)
|
||||
{
|
||||
DisconnectStation?.Invoke(session);
|
||||
}
|
||||
|
||||
private void DecodeAndHandle(LanPacketHeader header, byte[] data, EndPoint endPoint = null)
|
||||
{
|
||||
switch (header.Type)
|
||||
{
|
||||
case LanPacketType.Scan:
|
||||
// UDP
|
||||
if (_discovery.IsHost)
|
||||
{
|
||||
Scan?.Invoke(endPoint, LanPacketType.ScanResponse, SpanHelpers.AsSpan<NetworkInfo, byte>(ref _discovery.NetworkInfo).ToArray());
|
||||
}
|
||||
break;
|
||||
case LanPacketType.ScanResponse:
|
||||
// UDP
|
||||
ScanResponse?.Invoke(MemoryMarshal.Cast<byte, NetworkInfo>(data)[0]);
|
||||
break;
|
||||
case LanPacketType.SyncNetwork:
|
||||
// TCP
|
||||
SyncNetwork?.Invoke(MemoryMarshal.Cast<byte, NetworkInfo>(data)[0]);
|
||||
break;
|
||||
case LanPacketType.Connect:
|
||||
// TCP Session / Station
|
||||
Connect?.Invoke(MemoryMarshal.Cast<byte, NodeInfo>(data)[0], endPoint);
|
||||
break;
|
||||
default:
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decode error: Unhandled type {header.Type}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Read(scoped ref byte[] buffer, scoped ref int bufferEnd, byte[] data, int offset, int size, EndPoint endPoint = null)
|
||||
{
|
||||
if (endPoint != null && _discovery.LocalAddr.Equals(((IPEndPoint)endPoint).Address))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
while (index < size)
|
||||
{
|
||||
if (bufferEnd < _headerSize)
|
||||
{
|
||||
int copyable2 = Math.Min(size - index, Math.Min(size, _headerSize - bufferEnd));
|
||||
|
||||
Array.Copy(data, index + offset, buffer, bufferEnd, copyable2);
|
||||
|
||||
index += copyable2;
|
||||
bufferEnd += copyable2;
|
||||
}
|
||||
|
||||
if (bufferEnd >= _headerSize)
|
||||
{
|
||||
LanPacketHeader header = MemoryMarshal.Cast<byte, LanPacketHeader>(buffer)[0];
|
||||
if (header.Magic != LanMagic)
|
||||
{
|
||||
bufferEnd = 0;
|
||||
|
||||
Logger.Warning?.PrintMsg(LogClass.ServiceLdn, $"Invalid magic number in received packet. [magic: {header.Magic}] [EP: {endPoint}]");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int totalSize = _headerSize + header.Length;
|
||||
if (totalSize > BufferSize)
|
||||
{
|
||||
bufferEnd = 0;
|
||||
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Max packet size {BufferSize} exceeded.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int copyable = Math.Min(size - index, Math.Min(size, totalSize - bufferEnd));
|
||||
|
||||
Array.Copy(data, index + offset, buffer, bufferEnd, copyable);
|
||||
|
||||
index += copyable;
|
||||
bufferEnd += copyable;
|
||||
|
||||
if (totalSize == bufferEnd)
|
||||
{
|
||||
byte[] ldnData = new byte[totalSize - _headerSize];
|
||||
Array.Copy(buffer, _headerSize, ldnData, 0, ldnData.Length);
|
||||
|
||||
if (header.Compressed == 1)
|
||||
{
|
||||
if (Decompress(ldnData, out byte[] decompressedLdnData) != 0)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error:\n {header}, {_headerSize}\n {ldnData}, {ldnData.Length}");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (decompressedLdnData.Length != header.DecompressLength)
|
||||
{
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error: length does not match. ({decompressedLdnData.Length} != {header.DecompressLength})");
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, $"Decompress error data: '{string.Join("", decompressedLdnData.Select(x => (int)x).ToArray())}'");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ldnData = decompressedLdnData;
|
||||
}
|
||||
|
||||
DecodeAndHandle(header, ldnData, endPoint);
|
||||
|
||||
bufferEnd = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int SendBroadcast(ILdnSocket s, LanPacketType type, int port)
|
||||
{
|
||||
return SendPacket(s, type, Array.Empty<byte>(), new IPEndPoint(_discovery.LocalBroadcastAddr, port));
|
||||
}
|
||||
|
||||
public int SendPacket(ILdnSocket s, LanPacketType type, byte[] data, EndPoint endPoint = null)
|
||||
{
|
||||
byte[] buf = PreparePacket(type, data);
|
||||
|
||||
return s.SendPacketAsync(endPoint, buf) ? 0 : -1;
|
||||
}
|
||||
|
||||
public int SendPacket(LdnProxyTcpSession s, LanPacketType type, byte[] data)
|
||||
{
|
||||
byte[] buf = PreparePacket(type, data);
|
||||
|
||||
return s.SendAsync(buf) ? 0 : -1;
|
||||
}
|
||||
|
||||
private LanPacketHeader PrepareHeader(LanPacketHeader header, LanPacketType type)
|
||||
{
|
||||
header.Magic = LanMagic;
|
||||
header.Type = type;
|
||||
header.Compressed = 0;
|
||||
header.Length = 0;
|
||||
header.DecompressLength = 0;
|
||||
header.Reserved = new Array2<byte>();
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private byte[] PreparePacket(LanPacketType type, byte[] data)
|
||||
{
|
||||
LanPacketHeader header = PrepareHeader(new LanPacketHeader(), type);
|
||||
header.Length = (ushort)data.Length;
|
||||
|
||||
byte[] buf;
|
||||
if (data.Length > 0)
|
||||
{
|
||||
if (Compress(data, out byte[] compressed) == 0)
|
||||
{
|
||||
header.DecompressLength = header.Length;
|
||||
header.Length = (ushort)compressed.Length;
|
||||
header.Compressed = 1;
|
||||
|
||||
buf = new byte[compressed.Length + _headerSize];
|
||||
|
||||
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
|
||||
compressed.CopyTo(buf, _headerSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = new byte[data.Length + _headerSize];
|
||||
|
||||
Logger.Error?.PrintMsg(LogClass.ServiceLdn, "Compressing packet data failed.");
|
||||
|
||||
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
|
||||
data.CopyTo(buf, _headerSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
buf = new byte[_headerSize];
|
||||
SpanHelpers.AsSpan<LanPacketHeader, byte>(ref header).ToArray().CopyTo(buf, 0);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
private int Compress(byte[] input, out byte[] output)
|
||||
{
|
||||
List<byte> outputList = new();
|
||||
int i = 0;
|
||||
int maxCount = 0xFF;
|
||||
|
||||
while (i < input.Length)
|
||||
{
|
||||
byte inputByte = input[i++];
|
||||
int count = 0;
|
||||
|
||||
if (inputByte == 0)
|
||||
{
|
||||
while (i < input.Length && input[i] == 0 && count < maxCount)
|
||||
{
|
||||
count += 1;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (inputByte == 0)
|
||||
{
|
||||
outputList.Add(0);
|
||||
|
||||
if (outputList.Count == BufferSize)
|
||||
{
|
||||
output = null;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
outputList.Add((byte)count);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputList.Add(inputByte);
|
||||
}
|
||||
}
|
||||
|
||||
output = outputList.ToArray();
|
||||
|
||||
return i == input.Length ? 0 : -1;
|
||||
}
|
||||
|
||||
private int Decompress(byte[] input, out byte[] output)
|
||||
{
|
||||
List<byte> outputList = new();
|
||||
int i = 0;
|
||||
|
||||
while (i < input.Length && outputList.Count < BufferSize)
|
||||
{
|
||||
byte inputByte = input[i++];
|
||||
|
||||
outputList.Add(inputByte);
|
||||
|
||||
if (inputByte == 0)
|
||||
{
|
||||
if (i == input.Length)
|
||||
{
|
||||
output = null;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int count = input[i++];
|
||||
|
||||
for (int j = 0; j < count; j++)
|
||||
{
|
||||
if (outputList.Count == BufferSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
outputList.Add(inputByte);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output = outputList.ToArray();
|
||||
|
||||
return i == input.Length ? 0 : -1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,104 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.Types;
|
||||
using System;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm
|
||||
{
|
||||
/// <summary>
|
||||
/// Client implementation for <a href="https://github.com/spacemeowx2/ldn_mitm">ldn_mitm</a>
|
||||
/// </summary>
|
||||
internal class LdnMitmClient : INetworkClient
|
||||
{
|
||||
public bool NeedsRealId => false;
|
||||
|
||||
public event EventHandler<NetworkChangeEventArgs> NetworkChange;
|
||||
|
||||
private readonly LanDiscovery _lanDiscovery;
|
||||
|
||||
public LdnMitmClient(HLEConfiguration config)
|
||||
{
|
||||
UnicastIPAddressInformation localIpInterface = NetworkHelpers.GetLocalInterface(config.MultiplayerLanInterfaceId).Item2;
|
||||
|
||||
_lanDiscovery = new LanDiscovery(this, localIpInterface.Address, localIpInterface.IPv4Mask);
|
||||
}
|
||||
|
||||
internal void InvokeNetworkChange(NetworkInfo info, bool connected, DisconnectReason reason = DisconnectReason.None)
|
||||
{
|
||||
NetworkChange?.Invoke(this, new NetworkChangeEventArgs(info, connected: connected, disconnectReason: reason));
|
||||
}
|
||||
|
||||
public NetworkError Connect(ConnectRequest request)
|
||||
{
|
||||
return _lanDiscovery.Connect(request.NetworkInfo, request.UserConfig, request.LocalCommunicationVersion);
|
||||
}
|
||||
|
||||
public NetworkError ConnectPrivate(ConnectPrivateRequest request)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient ConnectPrivate");
|
||||
|
||||
return NetworkError.None;
|
||||
}
|
||||
|
||||
public bool CreateNetwork(CreateAccessPointRequest request, byte[] advertiseData)
|
||||
{
|
||||
return _lanDiscovery.CreateNetwork(request.SecurityConfig, request.UserConfig, request.NetworkConfig);
|
||||
}
|
||||
|
||||
public bool CreateNetworkPrivate(CreateAccessPointPrivateRequest request, byte[] advertiseData)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient CreateNetworkPrivate");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DisconnectAndStop()
|
||||
{
|
||||
_lanDiscovery.DisconnectAndStop();
|
||||
}
|
||||
|
||||
public void DisconnectNetwork()
|
||||
{
|
||||
_lanDiscovery.DestroyNetwork();
|
||||
}
|
||||
|
||||
public ResultCode Reject(DisconnectReason disconnectReason, uint nodeId)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient Reject");
|
||||
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
public NetworkInfo[] Scan(ushort channel, ScanFilter scanFilter)
|
||||
{
|
||||
return _lanDiscovery.Scan(channel, scanFilter);
|
||||
}
|
||||
|
||||
public void SetAdvertiseData(byte[] data)
|
||||
{
|
||||
_lanDiscovery.SetAdvertiseData(data);
|
||||
}
|
||||
|
||||
public void SetGameVersion(byte[] versionString)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetGameVersion");
|
||||
}
|
||||
|
||||
public void SetStationAcceptPolicy(AcceptPolicy acceptPolicy)
|
||||
{
|
||||
// NOTE: This method is not implemented in ldn_mitm
|
||||
Logger.Stub?.PrintMsg(LogClass.ServiceLdn, "LdnMitmClient SetStationAcceptPolicy");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_lanDiscovery.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Ldn.UserServiceCreator.LdnMitm.Proxy
|
||||
{
|
||||
internal interface ILdnSocket : IDisposable
|
||||
{
|
||||
bool SendPacketAsync(EndPoint endpoint, byte[] buffer);
|
||||
bool Start();
|
||||
bool Stop();
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user