Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
5131b71437 | ||
|
7870423671 | ||
|
b72916fbc1 | ||
|
da073fce61 | ||
|
1fc90e57d2 | ||
|
eafcc314a9 | ||
|
6e9bd4de13 | ||
|
05a41b31bc |
@@ -1,6 +1,7 @@
|
|||||||
using ARMeilleure.CodeGen.Linking;
|
using ARMeilleure.CodeGen.Linking;
|
||||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -59,7 +60,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||||
{
|
{
|
||||||
_stream = new MemoryStream();
|
_stream = MemoryStreamManager.Shared.GetStream();
|
||||||
|
|
||||||
AllocResult = allocResult;
|
AllocResult = allocResult;
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.CodeGen.Linking;
|
using ARMeilleure.CodeGen.Linking;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -1285,7 +1286,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
|
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
|
||||||
_stream.Seek(0, SeekOrigin.Begin);
|
_stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
using var codeStream = new MemoryStream();
|
using var codeStream = MemoryStreamManager.Shared.GetStream();
|
||||||
var assembler = new Assembler(codeStream, HasRelocs);
|
var assembler = new Assembler(codeStream, HasRelocs);
|
||||||
|
|
||||||
bool hasRelocs = HasRelocs;
|
bool hasRelocs = HasRelocs;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
|
|
||||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||||
{
|
{
|
||||||
_stream = new MemoryStream();
|
_stream = MemoryStreamManager.Shared.GetStream();
|
||||||
_blockLabels = new Operand[blocksCount];
|
_blockLabels = new Operand[blocksCount];
|
||||||
|
|
||||||
AllocResult = allocResult;
|
AllocResult = allocResult;
|
||||||
|
@@ -6,6 +6,7 @@ using ARMeilleure.Memory;
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -150,10 +151,10 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
private void InitializeCarriers()
|
private void InitializeCarriers()
|
||||||
{
|
{
|
||||||
_infosStream = new MemoryStream();
|
_infosStream = MemoryStreamManager.Shared.GetStream();
|
||||||
_codesList = new List<byte[]>();
|
_codesList = new List<byte[]>();
|
||||||
_relocsStream = new MemoryStream();
|
_relocsStream = MemoryStreamManager.Shared.GetStream();
|
||||||
_unwindInfosStream = new MemoryStream();
|
_unwindInfosStream = MemoryStreamManager.Shared.GetStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeCarriers()
|
private void DisposeCarriers()
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@@ -182,7 +183,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||||
|
|
||||||
@@ -274,7 +275,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
outerHeader.SetHeaderHash();
|
outerHeader.SetHeaderHash();
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||||
|
|
||||||
|
@@ -22,6 +22,7 @@
|
|||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||||
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
@@ -46,7 +47,7 @@
|
|||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
|
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-f7c841d" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -583,10 +583,10 @@
|
|||||||
"SelectUpdateDialogTitle": "Select update files",
|
"SelectUpdateDialogTitle": "Select update files",
|
||||||
"UserProfileWindowTitle": "User Profiles Manager",
|
"UserProfileWindowTitle": "User Profiles Manager",
|
||||||
"CheatWindowTitle": "Cheats Manager",
|
"CheatWindowTitle": "Cheats Manager",
|
||||||
"DlcWindowTitle": "Downloadable Content Manager",
|
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
|
||||||
"UpdateWindowTitle": "Title Update Manager",
|
"UpdateWindowTitle": "Title Update Manager",
|
||||||
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
||||||
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
|
"DlcWindowHeading": "{0} Downloadable Content(s)",
|
||||||
"UserProfilesEditProfile": "Edit Selected",
|
"UserProfilesEditProfile": "Edit Selected",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
|
||||||
<Exec Command="codesign --entitlements $(ProjectDir)..\distribution\macos\entitlements.xml -f --deep -s $(SigningCertificate) $(TargetDir)$(TargetName)" />
|
<Exec Command="codesign --entitlements '$(ProjectDir)..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,8 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
public string ContainerPath { get; }
|
public string ContainerPath { get; }
|
||||||
public string FullPath { get; }
|
public string FullPath { get; }
|
||||||
|
|
||||||
|
public string FileName => Path.GetFileName(ContainerPath);
|
||||||
|
|
||||||
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
||||||
{
|
{
|
||||||
TitleId = titleId;
|
TitleId = titleId;
|
||||||
|
340
Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
Normal file
340
Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using DynamicData;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
{
|
||||||
|
public class DownloadableContentManagerViewModel : BaseModel
|
||||||
|
{
|
||||||
|
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
||||||
|
private readonly string _downloadableContentJsonPath;
|
||||||
|
|
||||||
|
private VirtualFileSystem _virtualFileSystem;
|
||||||
|
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
|
||||||
|
private AvaloniaList<DownloadableContentModel> _views = new();
|
||||||
|
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
||||||
|
|
||||||
|
private string _search;
|
||||||
|
private ulong _titleId;
|
||||||
|
private string _titleName;
|
||||||
|
|
||||||
|
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
||||||
|
{
|
||||||
|
get => _downloadableContents;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_downloadableContents = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<DownloadableContentModel> Views
|
||||||
|
{
|
||||||
|
get => _views;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_views = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<DownloadableContentModel> SelectedDownloadableContents
|
||||||
|
{
|
||||||
|
get => _selectedDownloadableContents;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedDownloadableContents = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Search
|
||||||
|
{
|
||||||
|
get => _search;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_search = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UpdateCount
|
||||||
|
{
|
||||||
|
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
|
{
|
||||||
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
|
_titleId = titleId;
|
||||||
|
_titleName = titleName;
|
||||||
|
|
||||||
|
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize.");
|
||||||
|
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadDownloadableContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadDownloadableContents()
|
||||||
|
{
|
||||||
|
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
|
||||||
|
{
|
||||||
|
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||||
|
{
|
||||||
|
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||||
|
|
||||||
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||||
|
{
|
||||||
|
using UniqueRef<IFile> ncaFile = new();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
|
||||||
|
if (nca != null)
|
||||||
|
{
|
||||||
|
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
|
||||||
|
downloadableContentContainer.ContainerPath,
|
||||||
|
downloadableContentNca.FullPath,
|
||||||
|
downloadableContentNca.Enabled);
|
||||||
|
|
||||||
|
DownloadableContents.Add(content);
|
||||||
|
|
||||||
|
if (content.Enabled)
|
||||||
|
{
|
||||||
|
SelectedDownloadableContents.Add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Save the list again to remove leftovers.
|
||||||
|
Save();
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort()
|
||||||
|
{
|
||||||
|
DownloadableContents.AsObservableChangeSet()
|
||||||
|
.Filter(Filter)
|
||||||
|
.Bind(out var view).AsObservableList();
|
||||||
|
|
||||||
|
_views.Clear();
|
||||||
|
_views.AddRange(view);
|
||||||
|
OnPropertyChanged(nameof(Views));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Filter(object arg)
|
||||||
|
{
|
||||||
|
if (arg is DownloadableContentModel content)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleId.ToLower().Contains(_search.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadNcaErrorMessage], ex.Message, containerPath));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Add()
|
||||||
|
{
|
||||||
|
OpenFileDialog dialog = new OpenFileDialog()
|
||||||
|
{
|
||||||
|
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
||||||
|
AllowMultiple = true
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.Filters.Add(new FileDialogFilter
|
||||||
|
{
|
||||||
|
Name = "NSP",
|
||||||
|
Extensions = { "nsp" }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||||
|
|
||||||
|
if (files != null)
|
||||||
|
{
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
await AddDownloadableContent(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddDownloadableContent(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||||
|
bool containsDownloadableContent = false;
|
||||||
|
|
||||||
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
||||||
|
if (nca == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
|
{
|
||||||
|
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true);
|
||||||
|
DownloadableContents.Add(content);
|
||||||
|
SelectedDownloadableContents.Add(content);
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
|
|
||||||
|
containsDownloadableContent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!containsDownloadableContent)
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(DownloadableContentModel model)
|
||||||
|
{
|
||||||
|
DownloadableContents.Remove(model);
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAll()
|
||||||
|
{
|
||||||
|
DownloadableContents.Clear();
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableAll()
|
||||||
|
{
|
||||||
|
SelectedDownloadableContents = new(DownloadableContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableAll()
|
||||||
|
{
|
||||||
|
SelectedDownloadableContents.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList.Clear();
|
||||||
|
|
||||||
|
DownloadableContentContainer container = default;
|
||||||
|
|
||||||
|
foreach (DownloadableContentModel downloadableContent in DownloadableContents)
|
||||||
|
{
|
||||||
|
if (container.ContainerPath != downloadableContent.ContainerPath)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList.Add(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
container = new DownloadableContentContainer
|
||||||
|
{
|
||||||
|
ContainerPath = downloadableContent.ContainerPath,
|
||||||
|
DownloadableContentNcaList = new List<DownloadableContentNca>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
container.DownloadableContentNcaList.Add(new DownloadableContentNca
|
||||||
|
{
|
||||||
|
Enabled = downloadableContent.Enabled,
|
||||||
|
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
|
||||||
|
FullPath = downloadableContent.FullPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList.Add(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
|
||||||
|
{
|
||||||
|
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -1564,7 +1564,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (SelectedApplication != null)
|
if (SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
|
await DownloadableContentManagerWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,172 +1,194 @@
|
|||||||
<window:StyleableWindow
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow"
|
x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
Width="800"
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
Height="500"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
MinWidth="800"
|
Width="500"
|
||||||
MinHeight="500"
|
Height="380"
|
||||||
MaxWidth="800"
|
|
||||||
MaxHeight="500"
|
|
||||||
SizeToContent="Height"
|
|
||||||
WindowStartupLocation="CenterOwner"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:DownloadableContentManagerViewModel"
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
<Grid Name="DownloadableContentGrid" Margin="15">
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock
|
<Panel
|
||||||
Name="Heading"
|
Margin="0 0 0 10"
|
||||||
Grid.Row="1"
|
Grid.Row="0">
|
||||||
MaxWidth="500"
|
<Grid>
|
||||||
Margin="20,15,20,20"
|
<Grid.ColumnDefinitions>
|
||||||
HorizontalAlignment="Center"
|
<ColumnDefinition Width="Auto" />
|
||||||
VerticalAlignment="Center"
|
<ColumnDefinition Width="Auto" />
|
||||||
LineHeight="18"
|
<ColumnDefinition Width="*" />
|
||||||
TextAlignment="Center"
|
</Grid.ColumnDefinitions>
|
||||||
TextWrapping="Wrap" />
|
<TextBlock
|
||||||
<DockPanel
|
Grid.Column="0"
|
||||||
Grid.Row="2"
|
Text="{Binding UpdateCount}" />
|
||||||
Margin="0"
|
<StackPanel
|
||||||
HorizontalAlignment="Left">
|
Margin="10 0"
|
||||||
<Button
|
Grid.Column="1"
|
||||||
Name="EnableAllButton"
|
Orientation="Horizontal">
|
||||||
MinWidth="90"
|
<Button
|
||||||
Margin="5"
|
Name="EnableAllButton"
|
||||||
Command="{Binding EnableAll}">
|
MinWidth="90"
|
||||||
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
|
Margin="5"
|
||||||
</Button>
|
Command="{ReflectionBinding EnableAll}">
|
||||||
<Button
|
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
|
||||||
Name="DisableAllButton"
|
</Button>
|
||||||
MinWidth="90"
|
<Button
|
||||||
Margin="5"
|
Name="DisableAllButton"
|
||||||
Command="{Binding DisableAll}">
|
MinWidth="90"
|
||||||
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
|
Margin="5"
|
||||||
</Button>
|
Command="{ReflectionBinding DisableAll}">
|
||||||
</DockPanel>
|
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBox
|
||||||
|
Grid.Column="2"
|
||||||
|
MinHeight="27"
|
||||||
|
MaxHeight="27"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Watermark="{locale:Locale Search}"
|
||||||
|
Text="{Binding Search}" />
|
||||||
|
</Grid>
|
||||||
|
</Panel>
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="3"
|
Grid.Row="1"
|
||||||
Margin="5"
|
Margin="0 0 0 24"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
BorderBrush="Gray"
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
BorderThickness="1">
|
BorderThickness="1"
|
||||||
<ScrollViewer
|
CornerRadius="5"
|
||||||
VerticalAlignment="Stretch"
|
Padding="2.5">
|
||||||
HorizontalScrollBarVisibility="Auto"
|
<ListBox
|
||||||
VerticalScrollBarVisibility="Auto">
|
AutoScrollToSelectedItem="False"
|
||||||
<DataGrid
|
VirtualizationMode="None"
|
||||||
Name="DlcDataGrid"
|
SelectionMode="Multiple, Toggle"
|
||||||
MinHeight="200"
|
Background="Transparent"
|
||||||
HorizontalAlignment="Stretch"
|
SelectionChanged="OnSelectionChanged"
|
||||||
VerticalAlignment="Stretch"
|
SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}"
|
||||||
CanUserReorderColumns="False"
|
Items="{Binding Views}">
|
||||||
CanUserResizeColumns="True"
|
<ListBox.DataTemplates>
|
||||||
CanUserSortColumns="True"
|
<DataTemplate
|
||||||
HorizontalScrollBarVisibility="Auto"
|
DataType="models:DownloadableContentModel">
|
||||||
Items="{Binding _downloadableContents}"
|
<Panel Margin="10">
|
||||||
SelectionMode="Extended"
|
<Grid>
|
||||||
VerticalScrollBarVisibility="Auto">
|
<Grid.ColumnDefinitions>
|
||||||
<DataGrid.Styles>
|
<ColumnDefinition Width="*" />
|
||||||
<Styles>
|
<ColumnDefinition Width="Auto" />
|
||||||
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
|
</Grid.ColumnDefinitions>
|
||||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
<Grid
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
Grid.Column="0">
|
||||||
</Style>
|
<Grid.ColumnDefinitions>
|
||||||
</Styles>
|
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||||
<Styles>
|
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||||
<Style Selector="DataGridCell:nth-child(1)">
|
</Grid.ColumnDefinitions>
|
||||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
<TextBlock
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
Grid.Column="0"
|
||||||
</Style>
|
HorizontalAlignment="Left"
|
||||||
</Styles>
|
VerticalAlignment="Center"
|
||||||
</DataGrid.Styles>
|
MaxLines="2"
|
||||||
<DataGrid.Columns>
|
TextWrapping="Wrap"
|
||||||
<DataGridTemplateColumn Width="90">
|
TextTrimming="CharacterEllipsis"
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
Text="{Binding FileName}" />
|
||||||
<DataTemplate>
|
<TextBlock
|
||||||
<CheckBox
|
Grid.Column="1"
|
||||||
Width="50"
|
Margin="10 0"
|
||||||
MinWidth="40"
|
HorizontalAlignment="Left"
|
||||||
HorizontalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsChecked="{Binding Enabled}" />
|
Text="{Binding TitleId}" />
|
||||||
</DataTemplate>
|
</Grid>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
<StackPanel
|
||||||
<DataGridTemplateColumn.Header>
|
Grid.Column="1"
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
|
Spacing="10"
|
||||||
</DataGridTemplateColumn.Header>
|
Orientation="Horizontal"
|
||||||
</DataGridTemplateColumn>
|
HorizontalAlignment="Right">
|
||||||
<DataGridTextColumn Width="140" Binding="{Binding TitleId}">
|
<Button
|
||||||
<DataGridTextColumn.Header>
|
VerticalAlignment="Center"
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
|
HorizontalAlignment="Right"
|
||||||
</DataGridTextColumn.Header>
|
Padding="10"
|
||||||
</DataGridTextColumn>
|
MinWidth="0"
|
||||||
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
|
MinHeight="0"
|
||||||
<DataGridTextColumn.Header>
|
Click="OpenLocation">
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
|
<ui:SymbolIcon
|
||||||
</DataGridTextColumn.Header>
|
Symbol="OpenFolder"
|
||||||
</DataGridTextColumn>
|
HorizontalAlignment="Center"
|
||||||
<DataGridTextColumn Binding="{Binding ContainerPath}">
|
VerticalAlignment="Center" />
|
||||||
<DataGridTextColumn.Header>
|
</Button>
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
|
<Button
|
||||||
</DataGridTextColumn.Header>
|
VerticalAlignment="Center"
|
||||||
</DataGridTextColumn>
|
HorizontalAlignment="Right"
|
||||||
</DataGrid.Columns>
|
Padding="10"
|
||||||
</DataGrid>
|
MinWidth="0"
|
||||||
</ScrollViewer>
|
MinHeight="0"
|
||||||
|
Click="RemoveDLC">
|
||||||
|
<ui:SymbolIcon
|
||||||
|
Symbol="Cancel"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Panel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.DataTemplates>
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
</ListBox>
|
||||||
</Border>
|
</Border>
|
||||||
<DockPanel
|
<Panel
|
||||||
Grid.Row="4"
|
Grid.Row="2"
|
||||||
Margin="0"
|
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
<Button
|
<Button
|
||||||
Name="AddButton"
|
Name="AddButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Command="{Binding Add}">
|
Command="{ReflectionBinding Add}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
Name="RemoveButton"
|
|
||||||
MinWidth="90"
|
|
||||||
Margin="5"
|
|
||||||
Command="{Binding RemoveSelected}">
|
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
Name="RemoveAllButton"
|
Name="RemoveAllButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Command="{Binding RemoveAll}">
|
Command="{ReflectionBinding RemoveAll}">
|
||||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||||
</Button>
|
</Button>
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
<Button
|
<Button
|
||||||
Name="SaveButton"
|
Name="SaveButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Command="{Binding SaveAndClose}">
|
Click="SaveAndClose">
|
||||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
Name="CancelButton"
|
Name="CancelButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Command="{Binding Close}">
|
Click="Close">
|
||||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||||
</Button>
|
</Button>
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</Panel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableWindow>
|
</UserControl>
|
@@ -1,314 +1,115 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Interactivity;
|
||||||
using LibHac.Common;
|
using Avalonia.Styling;
|
||||||
using LibHac.Fs;
|
using FluentAvalonia.UI.Controls;
|
||||||
using LibHac.Fs.Fsa;
|
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Tools.Fs;
|
|
||||||
using LibHac.Tools.FsSystem;
|
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using System;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Path = System.IO.Path;
|
using Button = Avalonia.Controls.Button;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
public partial class DownloadableContentManagerWindow : StyleableWindow
|
public partial class DownloadableContentManagerWindow : UserControl
|
||||||
{
|
{
|
||||||
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
public DownloadableContentManagerViewModel ViewModel;
|
||||||
private readonly string _downloadableContentJsonPath;
|
|
||||||
|
|
||||||
private VirtualFileSystem _virtualFileSystem { get; }
|
|
||||||
private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
|
|
||||||
|
|
||||||
private ulong _titleId { get; }
|
|
||||||
private string _titleName { get; }
|
|
||||||
|
|
||||||
public DownloadableContentManagerWindow()
|
public DownloadableContentManagerWindow()
|
||||||
{
|
{
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.DlcWindowTitle]} - {_titleName} ({_titleId:X16})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId, titleName);
|
||||||
_downloadableContents = new AvaloniaList<DownloadableContentModel>();
|
|
||||||
|
|
||||||
_titleId = titleId;
|
|
||||||
_titleName = titleName;
|
|
||||||
|
|
||||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
DataContext = this;
|
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
RemoveButton.IsEnabled = false;
|
|
||||||
|
|
||||||
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
|
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.DlcWindowTitle]} - {_titleName} ({_titleId:X16})";
|
|
||||||
|
|
||||||
LoadDownloadableContents();
|
|
||||||
PrintHeading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
|
ContentDialog contentDialog = new()
|
||||||
}
|
|
||||||
|
|
||||||
private void PrintHeading()
|
|
||||||
{
|
|
||||||
Heading.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DlcWindowHeading, _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadDownloadableContents()
|
|
||||||
{
|
|
||||||
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
|
|
||||||
{
|
{
|
||||||
if (File.Exists(downloadableContentContainer.ContainerPath))
|
PrimaryButtonText = "",
|
||||||
{
|
SecondaryButtonText = "",
|
||||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
CloseButtonText = "",
|
||||||
|
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId, titleName),
|
||||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16"))
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
|
||||||
|
|
||||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
|
||||||
{
|
|
||||||
using UniqueRef<IFile> ncaFile = new();
|
|
||||||
|
|
||||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
|
|
||||||
if (nca != null)
|
|
||||||
{
|
|
||||||
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
|
|
||||||
downloadableContentContainer.ContainerPath,
|
|
||||||
downloadableContentNca.FullPath,
|
|
||||||
downloadableContentNca.Enabled));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Save the list again to remove leftovers.
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, containerPath));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task AddDownloadableContent(string path)
|
|
||||||
{
|
|
||||||
if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(path);
|
|
||||||
|
|
||||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
|
||||||
bool containsDownloadableContent = false;
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
|
||||||
if (nca == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
|
||||||
{
|
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
|
|
||||||
|
|
||||||
containsDownloadableContent = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!containsDownloadableContent)
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveDownloadableContents(bool removeSelectedOnly = false)
|
|
||||||
{
|
|
||||||
if (removeSelectedOnly)
|
|
||||||
{
|
|
||||||
AvaloniaList<DownloadableContentModel> removedItems = new();
|
|
||||||
|
|
||||||
foreach (var item in DlcDataGrid.SelectedItems)
|
|
||||||
{
|
|
||||||
removedItems.Add(item as DownloadableContentModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
DlcDataGrid.SelectedItems.Clear();
|
|
||||||
|
|
||||||
foreach (var item in removedItems)
|
|
||||||
{
|
|
||||||
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_downloadableContents.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintHeading();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveSelected()
|
|
||||||
{
|
|
||||||
RemoveDownloadableContents(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAll()
|
|
||||||
{
|
|
||||||
RemoveDownloadableContents();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnableAll()
|
|
||||||
{
|
|
||||||
foreach(var item in _downloadableContents)
|
|
||||||
{
|
|
||||||
item.Enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisableAll()
|
|
||||||
{
|
|
||||||
foreach (var item in _downloadableContents)
|
|
||||||
{
|
|
||||||
item.Enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Add()
|
|
||||||
{
|
|
||||||
OpenFileDialog dialog = new OpenFileDialog()
|
|
||||||
{
|
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
|
||||||
AllowMultiple = true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
{
|
bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false));
|
||||||
Name = "NSP",
|
|
||||||
Extensions = { "nsp" }
|
|
||||||
});
|
|
||||||
|
|
||||||
string[] files = await dialog.ShowAsync(this);
|
contentDialog.Styles.Add(bottomBorder);
|
||||||
|
|
||||||
if (files != null)
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
{
|
|
||||||
foreach (string file in files)
|
|
||||||
{
|
|
||||||
await AddDownloadableContent(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintHeading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs)
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList.Clear();
|
ViewModel.Save();
|
||||||
|
((ContentDialog)Parent).Hide();
|
||||||
|
}
|
||||||
|
|
||||||
DownloadableContentContainer container = default;
|
private void Close(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
((ContentDialog)Parent).Hide();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (DownloadableContentModel downloadableContent in _downloadableContents)
|
private void RemoveDLC(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button)
|
||||||
{
|
{
|
||||||
if (container.ContainerPath != downloadableContent.ContainerPath)
|
if (button.DataContext is DownloadableContentModel model)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
ViewModel.Remove(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button)
|
||||||
|
{
|
||||||
|
if (button.DataContext is DownloadableContentModel model)
|
||||||
|
{
|
||||||
|
OpenHelper.LocateFile(model.ContainerPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
foreach (var content in e.AddedItems)
|
||||||
|
{
|
||||||
|
if (content is DownloadableContentModel model)
|
||||||
|
{
|
||||||
|
var index = ViewModel.DownloadableContents.IndexOf(model);
|
||||||
|
|
||||||
|
if (index != -1)
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList.Add(container);
|
ViewModel.DownloadableContents[index].Enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
container = new DownloadableContentContainer
|
|
||||||
{
|
|
||||||
ContainerPath = downloadableContent.ContainerPath,
|
|
||||||
DownloadableContentNcaList = new List<DownloadableContentNca>()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
container.DownloadableContentNcaList.Add(new DownloadableContentNca
|
foreach (var content in e.RemovedItems)
|
||||||
|
{
|
||||||
|
if (content is DownloadableContentModel model)
|
||||||
{
|
{
|
||||||
Enabled = downloadableContent.Enabled,
|
var index = ViewModel.DownloadableContents.IndexOf(model);
|
||||||
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
|
|
||||||
FullPath = downloadableContent.FullPath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
if (index != -1)
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList.Add(container);
|
ViewModel.DownloadableContents[index].Enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
|
|
||||||
{
|
|
||||||
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveAndClose()
|
|
||||||
{
|
|
||||||
Save();
|
|
||||||
Close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -12,19 +12,5 @@ namespace Ryujinx.Common
|
|||||||
{
|
{
|
||||||
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
|
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
|
||||||
where T : unmanaged
|
|
||||||
{
|
|
||||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
|
||||||
|
|
||||||
writer.Write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Write(this BinaryWriter writer, UInt128 value)
|
|
||||||
{
|
|
||||||
writer.Write((ulong)value);
|
|
||||||
writer.Write((ulong)(value >> 64));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
28
Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common
|
||||||
|
{
|
||||||
|
public static class BinaryWriterExtensions
|
||||||
|
{
|
||||||
|
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||||
|
|
||||||
|
writer.Write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Write(this BinaryWriter writer, UInt128 value)
|
||||||
|
{
|
||||||
|
writer.Write((ulong)value);
|
||||||
|
writer.Write((ulong)(value >> 64));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Write(this BinaryWriter writer, MemoryStream stream)
|
||||||
|
{
|
||||||
|
stream.CopyTo(writer.BaseStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
138
Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common
|
||||||
|
{
|
||||||
|
public static class StreamExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
|
||||||
|
///
|
||||||
|
/// This default implementation converts each buffer value to a stack-allocated
|
||||||
|
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="buffer">The buffer of values to be written</param>
|
||||||
|
public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
|
||||||
|
{
|
||||||
|
if (buffer.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
|
||||||
|
stream.Write(byteBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Span<byte> byteBuffer = stackalloc byte[sizeof(int)];
|
||||||
|
|
||||||
|
foreach (int value in buffer)
|
||||||
|
{
|
||||||
|
BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
|
||||||
|
stream.Write(byteBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a four-byte signed integer to this stream. The current position
|
||||||
|
/// of the stream is advanced by four.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public static void Write(this Stream stream, int value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(int)];
|
||||||
|
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an eight-byte signed integer to this stream. The current position
|
||||||
|
/// of the stream is advanced by eight.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public static void Write(this Stream stream, long value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
||||||
|
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
// Writes a four-byte unsigned integer to this stream. The current position
|
||||||
|
// of the stream is advanced by four.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public static void Write(this Stream stream, uint value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an eight-byte unsigned integer to this stream. The current
|
||||||
|
/// position of the stream is advanced by eight.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public static void Write(this Stream stream, ulong value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the contents of source to stream by calling source.CopyTo(stream).
|
||||||
|
/// Provides consistency with other Stream.Write methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="source">The stream to be read from</param>
|
||||||
|
public static void Write(this Stream stream, Stream source)
|
||||||
|
{
|
||||||
|
source.CopyTo(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a sequence of bytes to the Stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to.</param>
|
||||||
|
/// <param name="value">The byte to be written</param>
|
||||||
|
/// <param name="count">The number of times the value should be written</param>
|
||||||
|
public static void WriteByte(this Stream stream, byte value, int count)
|
||||||
|
{
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int BlockSize = 16;
|
||||||
|
|
||||||
|
int blockCount = count / BlockSize;
|
||||||
|
if (blockCount > 0)
|
||||||
|
{
|
||||||
|
Span<byte> span = stackalloc byte[BlockSize];
|
||||||
|
span.Fill(value);
|
||||||
|
for (int x = 0; x < blockCount; x++)
|
||||||
|
{
|
||||||
|
stream.Write(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int nonBlockBytes = count % BlockSize;
|
||||||
|
for (int x = 0; x < nonBlockBytes; x++)
|
||||||
|
{
|
||||||
|
stream.WriteByte(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
99
Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using Microsoft.IO;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Memory
|
||||||
|
{
|
||||||
|
public static class MemoryStreamManager
|
||||||
|
{
|
||||||
|
private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
|
||||||
|
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
|
||||||
|
/// and b) return them as <c>RecyclableMemoryStream</c> so we don't have to cast.
|
||||||
|
/// </summary>
|
||||||
|
public static class Shared
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>MemoryStream</c> object with no tag and a default initial capacity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream()
|
||||||
|
=> new RecyclableMemoryStream(_shared);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
|
||||||
|
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||||
|
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream(byte[] buffer)
|
||||||
|
=> GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>MemoryStream</c> object with the given tag and with contents copied from the provided
|
||||||
|
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||||
|
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream(ReadOnlySpan<byte> buffer)
|
||||||
|
=> GetStream(Guid.NewGuid(), null, buffer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||||
|
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||||
|
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||||
|
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||||
|
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan<byte> buffer)
|
||||||
|
{
|
||||||
|
RecyclableMemoryStream stream = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
|
||||||
|
stream.Write(buffer);
|
||||||
|
stream.Position = 0;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
stream?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||||
|
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The new stream's position is set to the beginning of the stream when returned</remarks>
|
||||||
|
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||||
|
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||||
|
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||||
|
/// <param name="offset">The offset from the start of the buffer to copy from</param>
|
||||||
|
/// <param name="count">The number of bytes to copy from the buffer</param>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
RecyclableMemoryStream stream = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stream = new RecyclableMemoryStream(_shared, id, tag, count);
|
||||||
|
stream.Write(buffer, offset, count);
|
||||||
|
stream.Position = 0;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
stream?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
|
||||||
<PackageReference Include="MsgPack.Cli" />
|
<PackageReference Include="MsgPack.Cli" />
|
||||||
<PackageReference Include="System.Management" />
|
<PackageReference Include="System.Management" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -38,12 +40,7 @@ namespace Ryujinx.Common
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var mem = new MemoryStream())
|
return StreamUtils.StreamToBytes(stream);
|
||||||
{
|
|
||||||
stream.CopyTo(mem);
|
|
||||||
|
|
||||||
return mem.ToArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +53,7 @@ namespace Ryujinx.Common
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var mem = new MemoryStream())
|
return await StreamUtils.StreamToBytesAsync(stream);
|
||||||
{
|
|
||||||
await stream.CopyToAsync(mem);
|
|
||||||
|
|
||||||
return mem.ToArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
using System.IO;
|
using Microsoft.IO;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Utilities
|
namespace Ryujinx.Common.Utilities
|
||||||
{
|
{
|
||||||
@@ -6,12 +10,22 @@ namespace Ryujinx.Common.Utilities
|
|||||||
{
|
{
|
||||||
public static byte[] StreamToBytes(Stream input)
|
public static byte[] StreamToBytes(Stream input)
|
||||||
{
|
{
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
input.CopyTo(stream);
|
input.CopyTo(stream);
|
||||||
|
|
||||||
return stream.ToArray();
|
return stream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
|
{
|
||||||
|
await input.CopyToAsync(stream, cancellationToken);
|
||||||
|
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,6 @@
|
|||||||
using Ryujinx.Memory;
|
using Microsoft.IO;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -40,7 +42,7 @@ namespace Ryujinx.Cpu
|
|||||||
|
|
||||||
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
|
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
|
||||||
{
|
{
|
||||||
using (MemoryStream ms = new MemoryStream())
|
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
for (long offs = 0; offs < maxSize || maxSize == -1; offs++)
|
for (long offs = 0; offs < maxSize || maxSize == -1; offs++)
|
||||||
{
|
{
|
||||||
@@ -54,7 +56,7 @@ namespace Ryujinx.Cpu
|
|||||||
ms.WriteByte(value);
|
ms.WriteByte(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Encoding.ASCII.GetString(ms.ToArray());
|
return Encoding.ASCII.GetString(ms.GetReadOnlySequence());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,5 +28,10 @@ namespace Ryujinx.Cpu.Tracking
|
|||||||
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
|
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
|
||||||
|
|
||||||
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
|
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
|
||||||
|
|
||||||
|
public bool RangeEquals(CpuRegionHandle other)
|
||||||
|
{
|
||||||
|
return _impl.RealAddress == other._impl.RealAddress && _impl.RealSize == other._impl.RealSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,5 +20,15 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
{
|
{
|
||||||
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
|
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool HasDepthOrLayers(this Target target)
|
||||||
|
{
|
||||||
|
return target == Target.Texture3D ||
|
||||||
|
target == Target.Texture1DArray ||
|
||||||
|
target == Target.Texture2DArray ||
|
||||||
|
target == Target.Texture2DMultisampleArray ||
|
||||||
|
target == Target.Cubemap ||
|
||||||
|
target == Target.CubemapArray;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -152,21 +152,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
ulong ticks = _context.GetTimestamp();
|
ulong ticks = _context.GetTimestamp();
|
||||||
|
|
||||||
float divisor = type switch
|
|
||||||
{
|
|
||||||
ReportCounterType.SamplesPassed => _channel.TextureManager.RenderTargetScale * _channel.TextureManager.RenderTargetScale,
|
|
||||||
_ => 1f
|
|
||||||
};
|
|
||||||
|
|
||||||
ICounterEvent counter = null;
|
ICounterEvent counter = null;
|
||||||
|
|
||||||
void resultHandler(object evt, ulong result)
|
void resultHandler(object evt, ulong result)
|
||||||
{
|
{
|
||||||
if (divisor != 1f)
|
|
||||||
{
|
|
||||||
result = (ulong)MathF.Ceiling(result / divisor);
|
|
||||||
}
|
|
||||||
|
|
||||||
CounterData counterData = new CounterData
|
CounterData counterData = new CounterData
|
||||||
{
|
{
|
||||||
Counter = result,
|
Counter = result,
|
||||||
|
@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
public TexturePool Pool;
|
public TexturePool Pool;
|
||||||
public int ID;
|
public int ID;
|
||||||
|
public ulong GpuAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GpuContext _context;
|
private GpuContext _context;
|
||||||
@@ -162,6 +163,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsView => _viewStorage != this;
|
public bool IsView => _viewStorage != this;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not this texture has views.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasViews => _views.Count > 0;
|
||||||
|
|
||||||
private int _referenceCount;
|
private int _referenceCount;
|
||||||
private List<TexturePoolOwner> _poolOwners;
|
private List<TexturePoolOwner> _poolOwners;
|
||||||
|
|
||||||
@@ -354,7 +360,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
texture._viewStorage = this;
|
texture._viewStorage = this;
|
||||||
|
|
||||||
Group.UpdateViews(_views);
|
Group.UpdateViews(_views, texture);
|
||||||
|
|
||||||
if (texture.Group != null && texture.Group != Group)
|
if (texture.Group != null && texture.Group != Group)
|
||||||
{
|
{
|
||||||
@@ -378,11 +384,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
_views.Remove(texture);
|
_views.Remove(texture);
|
||||||
|
|
||||||
|
Group.RemoveView(texture);
|
||||||
|
|
||||||
texture._viewStorage = texture;
|
texture._viewStorage = texture;
|
||||||
|
|
||||||
DecrementReferenceCount();
|
DecrementReferenceCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the texture's physical memory range. This forces tracking to regenerate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="range">New physical memory range backing the texture</param>
|
||||||
|
public void ReplaceRange(MultiRange range)
|
||||||
|
{
|
||||||
|
Range = range;
|
||||||
|
|
||||||
|
Group.RangeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a copy dependency to a texture that is view compatible with this one.
|
/// Create a copy dependency to a texture that is view compatible with this one.
|
||||||
/// When either texture is modified, the texture data will be copied to the other to keep them in sync.
|
/// When either texture is modified, the texture data will be copied to the other to keep them in sync.
|
||||||
@@ -715,6 +734,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
height = Math.Max(height >> level, 1);
|
height = Math.Max(height >> level, 1);
|
||||||
depth = Math.Max(depth >> level, 1);
|
depth = Math.Max(depth >> level, 1);
|
||||||
|
|
||||||
|
int sliceDepth = single ? 1 : depth;
|
||||||
|
|
||||||
SpanOrArray<byte> result;
|
SpanOrArray<byte> result;
|
||||||
|
|
||||||
if (Info.IsLinear)
|
if (Info.IsLinear)
|
||||||
@@ -735,7 +756,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth,
|
depth,
|
||||||
single ? 1 : depth,
|
sliceDepth,
|
||||||
levels,
|
levels,
|
||||||
layers,
|
layers,
|
||||||
Info.FormatInfo.BlockWidth,
|
Info.FormatInfo.BlockWidth,
|
||||||
@@ -759,7 +780,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
Info.FormatInfo.BlockHeight,
|
Info.FormatInfo.BlockHeight,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth,
|
sliceDepth,
|
||||||
levels,
|
levels,
|
||||||
layers,
|
layers,
|
||||||
out byte[] decoded))
|
out byte[] decoded))
|
||||||
@@ -771,7 +792,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (GraphicsConfig.EnableTextureRecompression)
|
if (GraphicsConfig.EnableTextureRecompression)
|
||||||
{
|
{
|
||||||
decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
|
decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = decoded;
|
result = decoded;
|
||||||
@@ -782,15 +803,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
case Format.Etc2RgbaSrgb:
|
case Format.Etc2RgbaSrgb:
|
||||||
case Format.Etc2RgbaUnorm:
|
case Format.Etc2RgbaUnorm:
|
||||||
result = ETC2Decoder.DecodeRgba(result, width, height, depth, levels, layers);
|
result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Etc2RgbPtaSrgb:
|
case Format.Etc2RgbPtaSrgb:
|
||||||
case Format.Etc2RgbPtaUnorm:
|
case Format.Etc2RgbPtaUnorm:
|
||||||
result = ETC2Decoder.DecodePta(result, width, height, depth, levels, layers);
|
result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Etc2RgbSrgb:
|
case Format.Etc2RgbSrgb:
|
||||||
case Format.Etc2RgbUnorm:
|
case Format.Etc2RgbUnorm:
|
||||||
result = ETC2Decoder.DecodeRgb(result, width, height, depth, levels, layers);
|
result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -800,31 +821,31 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
case Format.Bc1RgbaSrgb:
|
case Format.Bc1RgbaSrgb:
|
||||||
case Format.Bc1RgbaUnorm:
|
case Format.Bc1RgbaUnorm:
|
||||||
result = BCnDecoder.DecodeBC1(result, width, height, depth, levels, layers);
|
result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Bc2Srgb:
|
case Format.Bc2Srgb:
|
||||||
case Format.Bc2Unorm:
|
case Format.Bc2Unorm:
|
||||||
result = BCnDecoder.DecodeBC2(result, width, height, depth, levels, layers);
|
result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Bc3Srgb:
|
case Format.Bc3Srgb:
|
||||||
case Format.Bc3Unorm:
|
case Format.Bc3Unorm:
|
||||||
result = BCnDecoder.DecodeBC3(result, width, height, depth, levels, layers);
|
result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Bc4Snorm:
|
case Format.Bc4Snorm:
|
||||||
case Format.Bc4Unorm:
|
case Format.Bc4Unorm:
|
||||||
result = BCnDecoder.DecodeBC4(result, width, height, depth, levels, layers, Format == Format.Bc4Snorm);
|
result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
|
||||||
break;
|
break;
|
||||||
case Format.Bc5Snorm:
|
case Format.Bc5Snorm:
|
||||||
case Format.Bc5Unorm:
|
case Format.Bc5Unorm:
|
||||||
result = BCnDecoder.DecodeBC5(result, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
|
||||||
break;
|
break;
|
||||||
case Format.Bc6HSfloat:
|
case Format.Bc6HSfloat:
|
||||||
case Format.Bc6HUfloat:
|
case Format.Bc6HUfloat:
|
||||||
result = BCnDecoder.DecodeBC6(result, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
|
result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
|
||||||
break;
|
break;
|
||||||
case Format.Bc7Srgb:
|
case Format.Bc7Srgb:
|
||||||
case Format.Bc7Unorm:
|
case Format.Bc7Unorm:
|
||||||
result = BCnDecoder.DecodeBC7(result, width, height, depth, levels, layers);
|
result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1484,11 +1505,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pool">The texture pool this texture has been added to</param>
|
/// <param name="pool">The texture pool this texture has been added to</param>
|
||||||
/// <param name="id">The ID of the reference to this texture in the pool</param>
|
/// <param name="id">The ID of the reference to this texture in the pool</param>
|
||||||
public void IncrementReferenceCount(TexturePool pool, int id)
|
/// <param name="gpuVa">GPU VA of the pool reference</param>
|
||||||
|
public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
|
||||||
{
|
{
|
||||||
lock (_poolOwners)
|
lock (_poolOwners)
|
||||||
{
|
{
|
||||||
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
|
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
|
||||||
}
|
}
|
||||||
_referenceCount++;
|
_referenceCount++;
|
||||||
|
|
||||||
@@ -1585,6 +1607,36 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
InvalidatedSequence++;
|
InvalidatedSequence++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue updating texture mappings on the pool. Happens from another thread.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdatePoolMappings()
|
||||||
|
{
|
||||||
|
lock (_poolOwners)
|
||||||
|
{
|
||||||
|
ulong address = 0;
|
||||||
|
|
||||||
|
foreach (var owner in _poolOwners)
|
||||||
|
{
|
||||||
|
if (address == 0 || address == owner.GpuAddress)
|
||||||
|
{
|
||||||
|
address = owner.GpuAddress;
|
||||||
|
|
||||||
|
owner.Pool.QueueUpdateMapping(this, owner.ID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If there is a different GPU VA mapping, prefer the first and delete the others.
|
||||||
|
owner.Pool.ForceRemove(this, owner.ID, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_poolOwners.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidatedSequence++;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete the texture if it is not used anymore.
|
/// Delete the texture if it is not used anymore.
|
||||||
/// The texture is considered unused when the reference count is zero,
|
/// The texture is considered unused when the reference count is zero,
|
||||||
@@ -1636,7 +1688,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
Group.ClearModified(unmapRange);
|
Group.ClearModified(unmapRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveFromPools(true);
|
UpdatePoolMappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -194,6 +194,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_cache.Lift(texture);
|
_cache.Lift(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to update a texture's physical memory range.
|
||||||
|
/// Returns false if there is an existing texture that matches with the updated range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to update</param>
|
||||||
|
/// <param name="range">New physical memory range</param>
|
||||||
|
/// <returns>True if the mapping was updated, false otherwise</returns>
|
||||||
|
public bool UpdateMapping(Texture texture, MultiRange range)
|
||||||
|
{
|
||||||
|
// There cannot be an existing texture compatible with this mapping in the texture cache already.
|
||||||
|
int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
|
||||||
|
|
||||||
|
for (int i = 0; i < overlapCount; i++)
|
||||||
|
{
|
||||||
|
var other = _textureOverlaps[i];
|
||||||
|
|
||||||
|
if (texture != other &&
|
||||||
|
(texture.IsViewCompatible(other.Info, other.Range, true, other.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible ||
|
||||||
|
other.IsViewCompatible(texture.Info, texture.Range, true, texture.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_textures.Remove(texture);
|
||||||
|
|
||||||
|
texture.ReplaceRange(range);
|
||||||
|
|
||||||
|
_textures.Add(texture);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to find an existing texture, or create a new one if not found.
|
/// Tries to find an existing texture, or create a new one if not found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class TextureGroup : IDisposable
|
class TextureGroup : IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures.
|
||||||
|
/// </summary>
|
||||||
|
private const int GranularLayerThreshold = 8;
|
||||||
|
|
||||||
private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
|
private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -116,7 +121,29 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_allOffsets = size.AllOffsets;
|
_allOffsets = size.AllOffsets;
|
||||||
_sliceSizes = size.SliceSizes;
|
_sliceSizes = size.SliceSizes;
|
||||||
|
|
||||||
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
|
if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
|
||||||
|
{
|
||||||
|
_hasLayerViews = true;
|
||||||
|
_hasMipViews = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
|
||||||
|
|
||||||
|
// If the texture is partially mapped, fully subdivide handles immediately.
|
||||||
|
|
||||||
|
MultiRange range = Storage.Range;
|
||||||
|
for (int i = 0; i < range.Count; i++)
|
||||||
|
{
|
||||||
|
if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
_hasLayerViews = true;
|
||||||
|
_hasMipViews = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RecalculateHandleRegions();
|
RecalculateHandleRegions();
|
||||||
}
|
}
|
||||||
@@ -249,7 +276,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
bool anyModified = false;
|
bool anyModified = false;
|
||||||
bool anyUnmapped = false;
|
bool anyNotDirty = false;
|
||||||
|
|
||||||
for (int i = 0; i < regionCount; i++)
|
for (int i = 0; i < regionCount; i++)
|
||||||
{
|
{
|
||||||
@@ -294,20 +321,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
dirty |= handleDirty;
|
dirty |= handleDirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
anyUnmapped |= handleUnmapped;
|
|
||||||
|
|
||||||
if (group.NeedsCopy)
|
if (group.NeedsCopy)
|
||||||
{
|
{
|
||||||
// The texture we copied from is still being written to. Copy from it again the next time this texture is used.
|
// The texture we copied from is still being written to. Copy from it again the next time this texture is used.
|
||||||
texture.SignalGroupDirty();
|
texture.SignalGroupDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadNeeded[baseHandle + i] = handleDirty && !handleUnmapped;
|
bool loadNeeded = handleDirty && !handleUnmapped;
|
||||||
|
|
||||||
|
anyNotDirty |= !loadNeeded;
|
||||||
|
_loadNeeded[baseHandle + i] = loadNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirty)
|
if (dirty)
|
||||||
{
|
{
|
||||||
if (anyUnmapped || (_handles.Length > 1 && (anyModified || split)))
|
if (anyNotDirty || (_handles.Length > 1 && (anyModified || split)))
|
||||||
{
|
{
|
||||||
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
|
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
|
||||||
|
|
||||||
@@ -331,24 +359,56 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="regionCount">The number of handles to synchronize</param>
|
/// <param name="regionCount">The number of handles to synchronize</param>
|
||||||
private void SynchronizePartial(int baseHandle, int regionCount)
|
private void SynchronizePartial(int baseHandle, int regionCount)
|
||||||
{
|
{
|
||||||
|
int spanEndIndex = -1;
|
||||||
|
int spanBase = 0;
|
||||||
|
ReadOnlySpan<byte> dataSpan = ReadOnlySpan<byte>.Empty;
|
||||||
|
|
||||||
for (int i = 0; i < regionCount; i++)
|
for (int i = 0; i < regionCount; i++)
|
||||||
{
|
{
|
||||||
if (_loadNeeded[baseHandle + i])
|
if (_loadNeeded[baseHandle + i])
|
||||||
{
|
{
|
||||||
var info = GetHandleInformation(baseHandle + i);
|
var info = GetHandleInformation(baseHandle + i);
|
||||||
|
|
||||||
|
// Ensure the data for this handle is loaded in the span.
|
||||||
|
if (spanEndIndex <= i - 1)
|
||||||
|
{
|
||||||
|
spanEndIndex = i;
|
||||||
|
|
||||||
|
if (_is3D)
|
||||||
|
{
|
||||||
|
// Look ahead to see how many handles need to be loaded.
|
||||||
|
for (int j = i + 1; j < regionCount; j++)
|
||||||
|
{
|
||||||
|
if (_loadNeeded[baseHandle + j])
|
||||||
|
{
|
||||||
|
spanEndIndex = j;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var endInfo = spanEndIndex == i ? info : GetHandleInformation(baseHandle + spanEndIndex);
|
||||||
|
|
||||||
|
spanBase = _allOffsets[info.Index];
|
||||||
|
int spanLast = _allOffsets[endInfo.Index + endInfo.Layers * endInfo.Levels - 1];
|
||||||
|
int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size);
|
||||||
|
int size = endOffset - spanBase;
|
||||||
|
|
||||||
|
dataSpan = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)spanBase, (ulong)size));
|
||||||
|
}
|
||||||
|
|
||||||
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
|
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
|
||||||
for (int layer = 0; layer < info.Layers; layer++)
|
for (int layer = 0; layer < info.Layers; layer++)
|
||||||
{
|
{
|
||||||
for (int level = 0; level < info.Levels; level++)
|
for (int level = 0; level < info.Levels; level++)
|
||||||
{
|
{
|
||||||
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
|
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
|
||||||
|
|
||||||
int offset = _allOffsets[offsetIndex];
|
int offset = _allOffsets[offsetIndex];
|
||||||
int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
|
|
||||||
int size = endOffset - offset;
|
|
||||||
|
|
||||||
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
|
ReadOnlySpan<byte> data = dataSpan.Slice(offset - spanBase);
|
||||||
|
|
||||||
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
|
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
|
||||||
|
|
||||||
@@ -865,8 +925,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <returns>A TextureGroupHandle covering the given views</returns>
|
/// <returns>A TextureGroupHandle covering the given views</returns>
|
||||||
private TextureGroupHandle GenerateHandles(int viewStart, int views)
|
private TextureGroupHandle GenerateHandles(int viewStart, int views)
|
||||||
{
|
{
|
||||||
|
int viewEnd = viewStart + views - 1;
|
||||||
|
(_, int lastLevel) = GetLayerLevelForView(viewEnd);
|
||||||
|
|
||||||
int offset = _allOffsets[viewStart];
|
int offset = _allOffsets[viewStart];
|
||||||
int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
|
int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
|
||||||
int size = endOffset - offset;
|
int size = endOffset - offset;
|
||||||
|
|
||||||
var result = new List<CpuRegionHandle>();
|
var result = new List<CpuRegionHandle>();
|
||||||
@@ -926,7 +989,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// Update the views in this texture group, rebuilding the memory tracking if required.
|
/// Update the views in this texture group, rebuilding the memory tracking if required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="views">The views list of the storage texture</param>
|
/// <param name="views">The views list of the storage texture</param>
|
||||||
public void UpdateViews(List<Texture> views)
|
/// <param name="texture">The texture that has been added, if that is the only change, otherwise null</param>
|
||||||
|
public void UpdateViews(List<Texture> views, Texture texture)
|
||||||
{
|
{
|
||||||
// This is saved to calculate overlapping views for each handle.
|
// This is saved to calculate overlapping views for each handle.
|
||||||
_views = views;
|
_views = views;
|
||||||
@@ -964,17 +1028,44 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (!regionsRebuilt)
|
if (!regionsRebuilt)
|
||||||
{
|
{
|
||||||
// Must update the overlapping views on all handles, but only if they were not just recreated.
|
if (texture != null)
|
||||||
|
|
||||||
foreach (TextureGroupHandle handle in _handles)
|
|
||||||
{
|
{
|
||||||
handle.RecalculateOverlaps(this, views);
|
int offset = FindOffset(texture);
|
||||||
|
|
||||||
|
foreach (TextureGroupHandle handle in _handles)
|
||||||
|
{
|
||||||
|
handle.AddOverlap(offset, texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Must update the overlapping views on all handles, but only if they were not just recreated.
|
||||||
|
|
||||||
|
foreach (TextureGroupHandle handle in _handles)
|
||||||
|
{
|
||||||
|
handle.RecalculateOverlaps(this, views);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalAllDirty();
|
SignalAllDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a view from the group, removing it from all overlap lists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="view">View to remove from the group</param>
|
||||||
|
public void RemoveView(Texture view)
|
||||||
|
{
|
||||||
|
int offset = FindOffset(view);
|
||||||
|
|
||||||
|
foreach (TextureGroupHandle handle in _handles)
|
||||||
|
{
|
||||||
|
handle.RemoveOverlap(offset, view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inherit handle state from an old set of handles, such as modified and dirty flags.
|
/// Inherit handle state from an old set of handles, such as modified and dirty flags.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1057,7 +1148,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// The dirty flags from the previous handles will be kept.
|
/// The dirty flags from the previous handles will be kept.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handles">The handles to replace the current handles with</param>
|
/// <param name="handles">The handles to replace the current handles with</param>
|
||||||
private void ReplaceHandles(TextureGroupHandle[] handles)
|
/// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
|
||||||
|
private void ReplaceHandles(TextureGroupHandle[] handles, bool rangeChanged)
|
||||||
{
|
{
|
||||||
if (_handles != null)
|
if (_handles != null)
|
||||||
{
|
{
|
||||||
@@ -1065,9 +1157,50 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
foreach (TextureGroupHandle groupHandle in handles)
|
foreach (TextureGroupHandle groupHandle in handles)
|
||||||
{
|
{
|
||||||
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
if (rangeChanged)
|
||||||
{
|
{
|
||||||
handle.Reprotect();
|
// When the storage range changes, this becomes a little different.
|
||||||
|
// If a range does not match one in the original, treat it as modified.
|
||||||
|
// It has been newly mapped and its data must be synchronized.
|
||||||
|
|
||||||
|
if (groupHandle.Handles.Length == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var oldGroup in _handles)
|
||||||
|
{
|
||||||
|
if (!groupHandle.OverlapsWith(oldGroup.Offset, oldGroup.Size))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
||||||
|
{
|
||||||
|
bool hasMatch = false;
|
||||||
|
|
||||||
|
foreach (var oldHandle in oldGroup.Handles)
|
||||||
|
{
|
||||||
|
if (oldHandle.RangeEquals(handle))
|
||||||
|
{
|
||||||
|
hasMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMatch)
|
||||||
|
{
|
||||||
|
handle.Reprotect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
||||||
|
{
|
||||||
|
handle.Reprotect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1089,7 +1222,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
|
/// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void RecalculateHandleRegions()
|
/// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
|
||||||
|
private void RecalculateHandleRegions(bool rangeChanged = false)
|
||||||
{
|
{
|
||||||
TextureGroupHandle[] handles;
|
TextureGroupHandle[] handles;
|
||||||
|
|
||||||
@@ -1171,7 +1305,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplaceHandles(handles);
|
ReplaceHandles(handles, rangeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Regenerates handles when the storage range has been remapped.
|
||||||
|
/// This forces the regions to be fully subdivided.
|
||||||
|
/// </summary>
|
||||||
|
public void RangeChanged()
|
||||||
|
{
|
||||||
|
_hasLayerViews = true;
|
||||||
|
_hasMipViews = true;
|
||||||
|
|
||||||
|
RecalculateHandleRegions(true);
|
||||||
|
|
||||||
|
SignalAllDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -159,6 +159,42 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a single texture view as an overlap if its range overlaps.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset of the view in the group</param>
|
||||||
|
/// <param name="view">The texture to add as an overlap</param>
|
||||||
|
public void AddOverlap(int offset, Texture view)
|
||||||
|
{
|
||||||
|
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
|
||||||
|
|
||||||
|
if (OverlapsWith(offset, (int)view.Size))
|
||||||
|
{
|
||||||
|
lock (Overlaps)
|
||||||
|
{
|
||||||
|
Overlaps.Add(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a single texture view as an overlap if its range overlaps.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset of the view in the group</param>
|
||||||
|
/// <param name="view">The texture to add as an overlap</param>
|
||||||
|
public void RemoveOverlap(int offset, Texture view)
|
||||||
|
{
|
||||||
|
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
|
||||||
|
|
||||||
|
if (OverlapsWith(offset, (int)view.Size))
|
||||||
|
{
|
||||||
|
lock (Overlaps)
|
||||||
|
{
|
||||||
|
Overlaps.Remove(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
|
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Texture;
|
using Ryujinx.Graphics.Texture;
|
||||||
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
@@ -12,8 +15,63 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
|
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A request to dereference a texture from a pool.
|
||||||
|
/// </summary>
|
||||||
|
private struct DereferenceRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the dereference is due to a mapping change or not.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool IsRemapped;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The texture being dereferenced.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Texture Texture;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the pool entry this reference belonged to.
|
||||||
|
/// </summary>
|
||||||
|
public readonly int ID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a dereference request for a texture with a specific pool ID, and remapped flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param>
|
||||||
|
/// <param name="texture">The texture being dereferenced</param>
|
||||||
|
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
|
||||||
|
private DereferenceRequest(bool isRemapped, Texture texture, int id)
|
||||||
|
{
|
||||||
|
IsRemapped = isRemapped;
|
||||||
|
Texture = texture;
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a dereference request for a texture removal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">The texture being removed</param>
|
||||||
|
/// <returns>A texture removal dereference request</returns>
|
||||||
|
public static DereferenceRequest Remove(Texture texture)
|
||||||
|
{
|
||||||
|
return new DereferenceRequest(false, texture, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a dereference request for a texture remapping with a specific pool ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">The texture being remapped</param>
|
||||||
|
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
|
||||||
|
/// <returns>A remap dereference request</returns>
|
||||||
|
public static DereferenceRequest Remap(Texture texture, int id)
|
||||||
|
{
|
||||||
|
return new DereferenceRequest(true, texture, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly GpuChannel _channel;
|
private readonly GpuChannel _channel;
|
||||||
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
|
private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>();
|
||||||
private TextureDescriptor _defaultDescriptor;
|
private TextureDescriptor _defaultDescriptor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,7 +116,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||||
|
|
||||||
ProcessDereferenceQueue();
|
// The dereference queue can put our texture back on the cache.
|
||||||
|
if ((texture = ProcessDereferenceQueue(id)) != null)
|
||||||
|
{
|
||||||
|
return ref descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||||
|
|
||||||
@@ -69,10 +131,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
texture.IncrementReferenceCount(this, id);
|
|
||||||
|
|
||||||
Items[id] = texture;
|
Items[id] = texture;
|
||||||
|
|
||||||
|
texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
|
||||||
|
|
||||||
DescriptorCache[id] = descriptor;
|
DescriptorCache[id] = descriptor;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -155,11 +217,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
|
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
|
||||||
public void ForceRemove(Texture texture, int id, bool deferred)
|
public void ForceRemove(Texture texture, int id, bool deferred)
|
||||||
{
|
{
|
||||||
Items[id] = null;
|
var previous = Interlocked.Exchange(ref Items[id], null);
|
||||||
|
|
||||||
if (deferred)
|
if (deferred)
|
||||||
{
|
{
|
||||||
_dereferenceQueue.Enqueue(texture);
|
if (previous != null)
|
||||||
|
{
|
||||||
|
_dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -167,16 +232,91 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues a request to update a texture's mapping.
|
||||||
|
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture with potential mapping change</param>
|
||||||
|
/// <param name="id">ID in cache of texture with potential mapping change</param>
|
||||||
|
public void QueueUpdateMapping(Texture texture, int id)
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref Items[id], null) == texture)
|
||||||
|
{
|
||||||
|
_dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process the dereference queue, decrementing the reference count for each texture in it.
|
/// Process the dereference queue, decrementing the reference count for each texture in it.
|
||||||
/// This is used to ensure that texture disposal happens on the render thread.
|
/// This is used to ensure that texture disposal happens on the render thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ProcessDereferenceQueue()
|
/// <param name="id">The ID of the entry that triggered this method</param>
|
||||||
|
/// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns>
|
||||||
|
private Texture ProcessDereferenceQueue(int id = -1)
|
||||||
{
|
{
|
||||||
while (_dereferenceQueue.TryDequeue(out Texture toRemove))
|
while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
|
||||||
{
|
{
|
||||||
toRemove.DecrementReferenceCount();
|
Texture texture = request.Texture;
|
||||||
|
|
||||||
|
// Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
|
||||||
|
// TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
|
||||||
|
|
||||||
|
if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
|
||||||
|
{
|
||||||
|
// Has the mapping for this texture changed?
|
||||||
|
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
|
||||||
|
|
||||||
|
ulong address = descriptor.UnpackAddress();
|
||||||
|
|
||||||
|
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
||||||
|
|
||||||
|
// If the texture is not mapped at all, delete its reference.
|
||||||
|
|
||||||
|
if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Items[request.ID] = texture;
|
||||||
|
|
||||||
|
// Create a new pool reference, as the last one was removed on unmap.
|
||||||
|
|
||||||
|
texture.IncrementReferenceCount(this, request.ID, address);
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
|
||||||
|
// Refetch the range. Changes since the last check could have been lost
|
||||||
|
// as the cache entry was not restored (required to queue mapping change).
|
||||||
|
|
||||||
|
range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
||||||
|
|
||||||
|
if (!range.Equals(texture.Range))
|
||||||
|
{
|
||||||
|
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
|
||||||
|
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
|
||||||
|
{
|
||||||
|
// Texture could not be remapped due to a collision, just delete it.
|
||||||
|
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
|
||||||
|
{
|
||||||
|
// If this is null, a request was already queued to decrement reference.
|
||||||
|
texture.DecrementReferenceCount(this, request.ID);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.ID == id)
|
||||||
|
{
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -213,9 +353,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
texture.DecrementReferenceCount(this, id);
|
if (Interlocked.Exchange(ref Items[id], null) != null)
|
||||||
|
{
|
||||||
Items[id] = null;
|
texture.DecrementReferenceCount(this, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
@@ -11,16 +13,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
{
|
{
|
||||||
public static byte[] Pack(ShaderSource[] sources)
|
public static byte[] Pack(ShaderSource[] sources)
|
||||||
{
|
{
|
||||||
using MemoryStream output = new MemoryStream();
|
using MemoryStream output = MemoryStreamManager.Shared.GetStream();
|
||||||
using BinaryWriter writer = new BinaryWriter(output);
|
|
||||||
|
|
||||||
writer.Write(sources.Length);
|
output.Write(sources.Length);
|
||||||
|
|
||||||
for (int i = 0; i < sources.Length; i++)
|
foreach (ShaderSource source in sources)
|
||||||
{
|
{
|
||||||
writer.Write((int)sources[i].Stage);
|
output.Write((int)source.Stage);
|
||||||
writer.Write(sources[i].BinaryCode.Length);
|
output.Write(source.BinaryCode.Length);
|
||||||
writer.Write(sources[i].BinaryCode);
|
output.Write(source.BinaryCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.ToArray();
|
return output.ToArray();
|
||||||
|
@@ -178,7 +178,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pipeline.Initialize(this);
|
_pipeline.Initialize(this);
|
||||||
_counters.Initialize();
|
_counters.Initialize(_pipeline);
|
||||||
|
|
||||||
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
|
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
|
||||||
// This call is expected to fail if we're running with a core profile,
|
// This call is expected to fail if we're running with a core profile,
|
||||||
|
@@ -773,6 +773,16 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
_tfEnabled = false;
|
_tfEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double GetCounterDivisor(CounterType type)
|
||||||
|
{
|
||||||
|
if (type == CounterType.SamplesPassed)
|
||||||
|
{
|
||||||
|
return _renderScale[0].X * _renderScale[0].X;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||||
{
|
{
|
||||||
if (!enable)
|
if (!enable)
|
||||||
|
@@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
{
|
{
|
||||||
private const int MaxQueryRetries = 5000;
|
private const int MaxQueryRetries = 5000;
|
||||||
private const long DefaultValue = -1;
|
private const long DefaultValue = -1;
|
||||||
|
private const ulong HighMask = 0xFFFFFFFF00000000;
|
||||||
|
|
||||||
public int Query { get; }
|
public int Query { get; }
|
||||||
|
|
||||||
@@ -63,11 +64,17 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool WaitingForValue(long data)
|
||||||
|
{
|
||||||
|
return data == DefaultValue ||
|
||||||
|
((ulong)data & HighMask) == (unchecked((ulong)DefaultValue) & HighMask);
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetResult(out long result)
|
public bool TryGetResult(out long result)
|
||||||
{
|
{
|
||||||
result = Marshal.ReadInt64(_bufferMap);
|
result = Marshal.ReadInt64(_bufferMap);
|
||||||
|
|
||||||
return result != DefaultValue;
|
return WaitingForValue(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long AwaitResult(AutoResetEvent wakeSignal = null)
|
public long AwaitResult(AutoResetEvent wakeSignal = null)
|
||||||
@@ -76,7 +83,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
|
|
||||||
if (wakeSignal == null)
|
if (wakeSignal == null)
|
||||||
{
|
{
|
||||||
while (data == DefaultValue)
|
while (WaitingForValue(data))
|
||||||
{
|
{
|
||||||
data = Marshal.ReadInt64(_bufferMap);
|
data = Marshal.ReadInt64(_bufferMap);
|
||||||
}
|
}
|
||||||
@@ -84,10 +91,10 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int iterations = 0;
|
int iterations = 0;
|
||||||
while (data == DefaultValue && iterations++ < MaxQueryRetries)
|
while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
|
||||||
{
|
{
|
||||||
data = Marshal.ReadInt64(_bufferMap);
|
data = Marshal.ReadInt64(_bufferMap);
|
||||||
if (data == DefaultValue)
|
if (WaitingForValue(data))
|
||||||
{
|
{
|
||||||
wakeSignal.WaitOne(1);
|
wakeSignal.WaitOne(1);
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,8 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
public CounterType Type { get; }
|
public CounterType Type { get; }
|
||||||
public bool Disposed { get; private set; }
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
|
private readonly Pipeline _pipeline;
|
||||||
|
|
||||||
private Queue<CounterQueueEvent> _events = new Queue<CounterQueueEvent>();
|
private Queue<CounterQueueEvent> _events = new Queue<CounterQueueEvent>();
|
||||||
private CounterQueueEvent _current;
|
private CounterQueueEvent _current;
|
||||||
|
|
||||||
@@ -28,10 +30,12 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
|
|
||||||
private Thread _consumerThread;
|
private Thread _consumerThread;
|
||||||
|
|
||||||
internal CounterQueue(CounterType type)
|
internal CounterQueue(Pipeline pipeline, CounterType type)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
|
|
||||||
|
_pipeline = pipeline;
|
||||||
|
|
||||||
QueryTarget glType = GetTarget(Type);
|
QueryTarget glType = GetTarget(Type);
|
||||||
|
|
||||||
_queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize);
|
_queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize);
|
||||||
@@ -119,7 +123,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
_current.ReserveForHostAccess();
|
_current.ReserveForHostAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
_current.Complete(draws > 0);
|
_current.Complete(draws > 0, _pipeline.GetCounterDivisor(Type));
|
||||||
_events.Enqueue(_current);
|
_events.Enqueue(_current);
|
||||||
|
|
||||||
_current.OnResult += resultHandler;
|
_current.OnResult += resultHandler;
|
||||||
|
@@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
|
|
||||||
private object _lock = new object();
|
private object _lock = new object();
|
||||||
private ulong _result = ulong.MaxValue;
|
private ulong _result = ulong.MaxValue;
|
||||||
|
private double _divisor = 1f;
|
||||||
|
|
||||||
public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex)
|
public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex)
|
||||||
{
|
{
|
||||||
@@ -45,9 +46,11 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
ClearCounter = true;
|
ClearCounter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Complete(bool withResult)
|
internal void Complete(bool withResult, double divisor)
|
||||||
{
|
{
|
||||||
_counter.End(withResult);
|
_counter.End(withResult);
|
||||||
|
|
||||||
|
_divisor = divisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
|
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
|
||||||
@@ -78,7 +81,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result += (ulong)queryResult;
|
result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor);
|
||||||
|
|
||||||
_result = result;
|
_result = result;
|
||||||
|
|
||||||
|
@@ -14,12 +14,12 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
_counterQueues = new CounterQueue[count];
|
_counterQueues = new CounterQueue[count];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize(Pipeline pipeline)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < _counterQueues.Length; index++)
|
for (int index = 0; index < _counterQueues.Length; index++)
|
||||||
{
|
{
|
||||||
CounterType type = (CounterType)index;
|
CounterType type = (CounterType)index;
|
||||||
_counterQueues[index] = new CounterQueue(type);
|
_counterQueues[index] = new CounterQueue(pipeline, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
int outSize = GetTextureSize(
|
int outSize = GetTextureSize(
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth,
|
sliceDepth,
|
||||||
levels,
|
levels,
|
||||||
layers,
|
layers,
|
||||||
blockWidth,
|
blockWidth,
|
||||||
|
@@ -684,6 +684,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_tfEnabled = false;
|
_tfEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double GetCounterDivisor(CounterType type)
|
||||||
|
{
|
||||||
|
if (type == CounterType.SamplesPassed)
|
||||||
|
{
|
||||||
|
return _renderScale[0].X * _renderScale[0].X;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsCommandBufferActive(CommandBuffer cb)
|
public bool IsCommandBufferActive(CommandBuffer cb)
|
||||||
{
|
{
|
||||||
return CommandBuffer.Handle == cb.Handle;
|
return CommandBuffer.Handle == cb.Handle;
|
||||||
|
@@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
private const int MaxQueryRetries = 5000;
|
private const int MaxQueryRetries = 5000;
|
||||||
private const long DefaultValue = -1;
|
private const long DefaultValue = -1;
|
||||||
private const long DefaultValueInt = 0xFFFFFFFF;
|
private const long DefaultValueInt = 0xFFFFFFFF;
|
||||||
|
private const ulong HighMask = 0xFFFFFFFF00000000;
|
||||||
|
|
||||||
private readonly Vk _api;
|
private readonly Vk _api;
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
@@ -125,6 +126,12 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool WaitingForValue(long data)
|
||||||
|
{
|
||||||
|
return data == _defaultValue ||
|
||||||
|
(!_result32Bit && ((ulong)data & HighMask) == ((ulong)_defaultValue & HighMask));
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetResult(out long result)
|
public bool TryGetResult(out long result)
|
||||||
{
|
{
|
||||||
result = Marshal.ReadInt64(_bufferMap);
|
result = Marshal.ReadInt64(_bufferMap);
|
||||||
@@ -138,7 +145,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
|
|
||||||
if (wakeSignal == null)
|
if (wakeSignal == null)
|
||||||
{
|
{
|
||||||
while (data == _defaultValue)
|
while (WaitingForValue(data))
|
||||||
{
|
{
|
||||||
data = Marshal.ReadInt64(_bufferMap);
|
data = Marshal.ReadInt64(_bufferMap);
|
||||||
}
|
}
|
||||||
@@ -146,10 +153,10 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int iterations = 0;
|
int iterations = 0;
|
||||||
while (data == _defaultValue && iterations++ < MaxQueryRetries)
|
while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
|
||||||
{
|
{
|
||||||
data = Marshal.ReadInt64(_bufferMap);
|
data = Marshal.ReadInt64(_bufferMap);
|
||||||
if (data == _defaultValue)
|
if (WaitingForValue(data))
|
||||||
{
|
{
|
||||||
wakeSignal.WaitOne(1);
|
wakeSignal.WaitOne(1);
|
||||||
}
|
}
|
||||||
|
@@ -148,7 +148,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
_current.ReserveForHostAccess();
|
_current.ReserveForHostAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
_current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten);
|
_current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten, _pipeline.GetCounterDivisor(Type));
|
||||||
_events.Enqueue(_current);
|
_events.Enqueue(_current);
|
||||||
|
|
||||||
_current.OnResult += resultHandler;
|
_current.OnResult += resultHandler;
|
||||||
|
@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
|
|
||||||
private object _lock = new object();
|
private object _lock = new object();
|
||||||
private ulong _result = ulong.MaxValue;
|
private ulong _result = ulong.MaxValue;
|
||||||
|
private double _divisor = 1f;
|
||||||
|
|
||||||
public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex)
|
public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex)
|
||||||
{
|
{
|
||||||
@@ -52,9 +53,11 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
ClearCounter = true;
|
ClearCounter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Complete(bool withResult)
|
internal void Complete(bool withResult, double divisor)
|
||||||
{
|
{
|
||||||
_counter.End(withResult);
|
_counter.End(withResult);
|
||||||
|
|
||||||
|
_divisor = divisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
|
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
|
||||||
@@ -85,7 +88,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result += (ulong)queryResult;
|
result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor);
|
||||||
|
|
||||||
_result = result;
|
_result = result;
|
||||||
|
|
||||||
|
@@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem;
|
|||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
using LibHac.Tools.Ncm;
|
using LibHac.Tools.Ncm;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.HOS.Services.Ssl;
|
using Ryujinx.HLE.HOS.Services.Ssl;
|
||||||
@@ -637,12 +638,12 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
|
|
||||||
private Stream GetZipStream(ZipArchiveEntry entry)
|
private Stream GetZipStream(ZipArchiveEntry entry)
|
||||||
{
|
{
|
||||||
MemoryStream dest = new MemoryStream();
|
MemoryStream dest = MemoryStreamManager.Shared.GetStream();
|
||||||
|
|
||||||
Stream src = entry.Open();
|
using (Stream src = entry.Open())
|
||||||
|
{
|
||||||
src.CopyTo(dest);
|
src.CopyTo(dest);
|
||||||
src.Dispose();
|
}
|
||||||
|
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -70,7 +71,7 @@ namespace Ryujinx.HLE.HOS.Applets.Browser
|
|||||||
|
|
||||||
private byte[] BuildResponseOld(WebCommonReturnValue result)
|
private byte[] BuildResponseOld(WebCommonReturnValue result)
|
||||||
{
|
{
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
{
|
{
|
||||||
writer.WriteStruct(result);
|
writer.WriteStruct(result);
|
||||||
@@ -80,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Applets.Browser
|
|||||||
}
|
}
|
||||||
private byte[] BuildResponseNew(List<BrowserOutput> outputArguments)
|
private byte[] BuildResponseNew(List<BrowserOutput> outputArguments)
|
||||||
{
|
{
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
{
|
{
|
||||||
writer.WriteStruct(new WebArgHeader
|
writer.WriteStruct(new WebArgHeader
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid;
|
using Ryujinx.HLE.HOS.Services.Hid;
|
||||||
using Ryujinx.HLE.HOS.Services.Hid.Types;
|
using Ryujinx.HLE.HOS.Services.Hid.Types;
|
||||||
@@ -123,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
|
|
||||||
private byte[] BuildResponse(ControllerSupportResultInfo result)
|
private byte[] BuildResponse(ControllerSupportResultInfo result)
|
||||||
{
|
{
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
{
|
{
|
||||||
writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf<ControllerSupportResultInfo>())));
|
writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf<ControllerSupportResultInfo>())));
|
||||||
@@ -134,7 +135,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
|
|
||||||
private byte[] BuildResponse()
|
private byte[] BuildResponse()
|
||||||
{
|
{
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
{
|
{
|
||||||
writer.Write((ulong)ResultCode.Success);
|
writer.Write((ulong)ResultCode.Success);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -43,7 +44,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
{
|
{
|
||||||
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
|
UserProfile currentUser = _system.AccountManager.LastOpenedUser;
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
{
|
{
|
||||||
writer.Write((ulong)PlayerSelectResult.Success);
|
writer.Write((ulong)PlayerSelectResult.Success);
|
||||||
|
@@ -338,7 +338,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
|
ProcessCreationInfo creationInfo = new ProcessCreationInfo("Service", 1, 0, 0x8000000, 1, flags, 0, 0);
|
||||||
|
|
||||||
int[] defaultCapabilities = new int[]
|
uint[] defaultCapabilities = new uint[]
|
||||||
{
|
{
|
||||||
0x030363F7,
|
0x030363F7,
|
||||||
0x1FFFFFCF,
|
0x1FFFFFCF,
|
||||||
@@ -552,4 +552,4 @@ namespace Ryujinx.HLE.HOS
|
|||||||
IsPaused = pause;
|
IsPaused = pause;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,3 +1,6 @@
|
|||||||
|
using Microsoft.IO;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
@@ -18,20 +21,27 @@ namespace Ryujinx.HLE.HOS.Ipc
|
|||||||
|
|
||||||
HasPId = (word & 1) != 0;
|
HasPId = (word & 1) != 0;
|
||||||
|
|
||||||
ToCopy = new int[(word >> 1) & 0xf];
|
|
||||||
ToMove = new int[(word >> 5) & 0xf];
|
|
||||||
|
|
||||||
PId = HasPId ? reader.ReadUInt64() : 0;
|
PId = HasPId ? reader.ReadUInt64() : 0;
|
||||||
|
|
||||||
for (int index = 0; index < ToCopy.Length; index++)
|
int toCopySize = (word >> 1) & 0xf;
|
||||||
|
int[] toCopy = toCopySize == 0 ? Array.Empty<int>() : new int[toCopySize];
|
||||||
|
|
||||||
|
for (int index = 0; index < toCopy.Length; index++)
|
||||||
{
|
{
|
||||||
ToCopy[index] = reader.ReadInt32();
|
toCopy[index] = reader.ReadInt32();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int index = 0; index < ToMove.Length; index++)
|
ToCopy = toCopy;
|
||||||
|
|
||||||
|
int toMoveSize = (word >> 5) & 0xf;
|
||||||
|
int[] toMove = toMoveSize == 0 ? Array.Empty<int>() : new int[toMoveSize];
|
||||||
|
|
||||||
|
for (int index = 0; index < toMove.Length; index++)
|
||||||
{
|
{
|
||||||
ToMove[index] = reader.ReadInt32();
|
toMove[index] = reader.ReadInt32();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ToMove = toMove;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IpcHandleDesc(int[] copy, int[] move)
|
public IpcHandleDesc(int[] copy, int[] move)
|
||||||
@@ -57,36 +67,27 @@ namespace Ryujinx.HLE.HOS.Ipc
|
|||||||
return new IpcHandleDesc(Array.Empty<int>(), handles);
|
return new IpcHandleDesc(Array.Empty<int>(), handles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetBytes()
|
public RecyclableMemoryStream GetStream()
|
||||||
{
|
{
|
||||||
using (MemoryStream ms = new MemoryStream())
|
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
|
||||||
|
|
||||||
|
int word = HasPId ? 1 : 0;
|
||||||
|
|
||||||
|
word |= (ToCopy.Length & 0xf) << 1;
|
||||||
|
word |= (ToMove.Length & 0xf) << 5;
|
||||||
|
|
||||||
|
ms.Write(word);
|
||||||
|
|
||||||
|
if (HasPId)
|
||||||
{
|
{
|
||||||
BinaryWriter writer = new BinaryWriter(ms);
|
ms.Write(PId);
|
||||||
|
|
||||||
int word = HasPId ? 1 : 0;
|
|
||||||
|
|
||||||
word |= (ToCopy.Length & 0xf) << 1;
|
|
||||||
word |= (ToMove.Length & 0xf) << 5;
|
|
||||||
|
|
||||||
writer.Write(word);
|
|
||||||
|
|
||||||
if (HasPId)
|
|
||||||
{
|
|
||||||
writer.Write(PId);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (int handle in ToCopy)
|
|
||||||
{
|
|
||||||
writer.Write(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (int handle in ToMove)
|
|
||||||
{
|
|
||||||
writer.Write(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ms.Write(ToCopy);
|
||||||
|
ms.Write(ToMove);
|
||||||
|
|
||||||
|
ms.Position = 0;
|
||||||
|
return ms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,8 @@
|
|||||||
|
using Microsoft.IO;
|
||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -32,9 +36,9 @@ namespace Ryujinx.HLE.HOS.Ipc
|
|||||||
ObjectIds = new List<int>();
|
ObjectIds = new List<int>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IpcMessage(byte[] data, long cmdPtr) : this()
|
public IpcMessage(ReadOnlySpan<byte> data, long cmdPtr) : this()
|
||||||
{
|
{
|
||||||
using (MemoryStream ms = new MemoryStream(data))
|
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data))
|
||||||
{
|
{
|
||||||
BinaryReader reader = new BinaryReader(ms);
|
BinaryReader reader = new BinaryReader(ms);
|
||||||
|
|
||||||
@@ -114,124 +118,119 @@ namespace Ryujinx.HLE.HOS.Ipc
|
|||||||
|
|
||||||
for (int index = 0; index < recvListCount; index++)
|
for (int index = 0; index < recvListCount; index++)
|
||||||
{
|
{
|
||||||
RecvListBuff.Add(new IpcRecvListBuffDesc(reader));
|
RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetBytes(long cmdPtr, ulong recvListAddr)
|
public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr)
|
||||||
{
|
{
|
||||||
using (MemoryStream ms = new MemoryStream())
|
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
|
||||||
|
|
||||||
|
int word0;
|
||||||
|
int word1;
|
||||||
|
|
||||||
|
word0 = (int)Type;
|
||||||
|
word0 |= (PtrBuff.Count & 0xf) << 16;
|
||||||
|
word0 |= (SendBuff.Count & 0xf) << 20;
|
||||||
|
word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
||||||
|
word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
||||||
|
|
||||||
|
using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream();
|
||||||
|
|
||||||
|
int dataLength = RawData?.Length ?? 0;
|
||||||
|
|
||||||
|
dataLength = (dataLength + 3) & ~3;
|
||||||
|
|
||||||
|
int rawLength = dataLength;
|
||||||
|
|
||||||
|
int pad0 = (int)GetPadSize16(cmdPtr + 8 + (handleDataStream?.Length ?? 0) + PtrBuff.Count * 8);
|
||||||
|
|
||||||
|
// Apparently, padding after Raw Data is 16 bytes, however when there is
|
||||||
|
// padding before Raw Data too, we need to subtract the size of this padding.
|
||||||
|
// This is the weirdest padding I've seen so far...
|
||||||
|
int pad1 = 0x10 - pad0;
|
||||||
|
|
||||||
|
dataLength = (dataLength + pad0 + pad1) / 4;
|
||||||
|
|
||||||
|
word1 = (dataLength & 0x3ff) | (2 << 10);
|
||||||
|
|
||||||
|
if (HandleDesc != null)
|
||||||
{
|
{
|
||||||
BinaryWriter writer = new BinaryWriter(ms);
|
word1 |= 1 << 31;
|
||||||
|
|
||||||
int word0;
|
|
||||||
int word1;
|
|
||||||
|
|
||||||
word0 = (int)Type;
|
|
||||||
word0 |= (PtrBuff.Count & 0xf) << 16;
|
|
||||||
word0 |= (SendBuff.Count & 0xf) << 20;
|
|
||||||
word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
|
||||||
word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
|
||||||
|
|
||||||
byte[] handleData = Array.Empty<byte>();
|
|
||||||
|
|
||||||
if (HandleDesc != null)
|
|
||||||
{
|
|
||||||
handleData = HandleDesc.GetBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
int dataLength = RawData?.Length ?? 0;
|
|
||||||
|
|
||||||
dataLength = (dataLength + 3) & ~3;
|
|
||||||
|
|
||||||
int rawLength = dataLength;
|
|
||||||
|
|
||||||
int pad0 = (int)GetPadSize16(cmdPtr + 8 + handleData.Length + PtrBuff.Count * 8);
|
|
||||||
|
|
||||||
// Apparently, padding after Raw Data is 16 bytes, however when there is
|
|
||||||
// padding before Raw Data too, we need to subtract the size of this padding.
|
|
||||||
// This is the weirdest padding I've seen so far...
|
|
||||||
int pad1 = 0x10 - pad0;
|
|
||||||
|
|
||||||
dataLength = (dataLength + pad0 + pad1) / 4;
|
|
||||||
|
|
||||||
word1 = (dataLength & 0x3ff) | (2 << 10);
|
|
||||||
|
|
||||||
if (HandleDesc != null)
|
|
||||||
{
|
|
||||||
word1 |= 1 << 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Write(word0);
|
|
||||||
writer.Write(word1);
|
|
||||||
writer.Write(handleData);
|
|
||||||
|
|
||||||
for (int index = 0; index < PtrBuff.Count; index++)
|
|
||||||
{
|
|
||||||
writer.Write(PtrBuff[index].GetWord0());
|
|
||||||
writer.Write(PtrBuff[index].GetWord1());
|
|
||||||
}
|
|
||||||
|
|
||||||
ms.Seek(pad0, SeekOrigin.Current);
|
|
||||||
|
|
||||||
if (RawData != null)
|
|
||||||
{
|
|
||||||
writer.Write(RawData);
|
|
||||||
ms.Seek(rawLength - RawData.Length, SeekOrigin.Current);
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Write(new byte[pad1]);
|
|
||||||
writer.Write(recvListAddr);
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ms.Write(word0);
|
||||||
|
ms.Write(word1);
|
||||||
|
|
||||||
|
if (handleDataStream != null)
|
||||||
|
{
|
||||||
|
ms.Write(handleDataStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (IpcPtrBuffDesc ptrBuffDesc in PtrBuff)
|
||||||
|
{
|
||||||
|
ms.Write(ptrBuffDesc.GetWord0());
|
||||||
|
ms.Write(ptrBuffDesc.GetWord1());
|
||||||
|
}
|
||||||
|
|
||||||
|
ms.WriteByte(0, pad0);
|
||||||
|
|
||||||
|
if (RawData != null)
|
||||||
|
{
|
||||||
|
ms.Write(RawData);
|
||||||
|
ms.WriteByte(0, rawLength - RawData.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
ms.WriteByte(0, pad1);
|
||||||
|
|
||||||
|
ms.Write(recvListAddr);
|
||||||
|
|
||||||
|
ms.Position = 0;
|
||||||
|
|
||||||
|
return ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetBytesTipc()
|
public RecyclableMemoryStream GetStreamTipc()
|
||||||
{
|
{
|
||||||
Debug.Assert(PtrBuff.Count == 0);
|
Debug.Assert(PtrBuff.Count == 0);
|
||||||
|
|
||||||
using (MemoryStream ms = new MemoryStream())
|
RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream();
|
||||||
|
|
||||||
|
int word0;
|
||||||
|
int word1;
|
||||||
|
|
||||||
|
word0 = (int)Type;
|
||||||
|
word0 |= (SendBuff.Count & 0xf) << 20;
|
||||||
|
word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
||||||
|
word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
||||||
|
|
||||||
|
using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream();
|
||||||
|
|
||||||
|
int dataLength = RawData?.Length ?? 0;
|
||||||
|
|
||||||
|
dataLength = ((dataLength + 3) & ~3) / 4;
|
||||||
|
|
||||||
|
word1 = (dataLength & 0x3ff);
|
||||||
|
|
||||||
|
if (HandleDesc != null)
|
||||||
{
|
{
|
||||||
BinaryWriter writer = new BinaryWriter(ms);
|
word1 |= 1 << 31;
|
||||||
|
|
||||||
int word0;
|
|
||||||
int word1;
|
|
||||||
|
|
||||||
word0 = (int)Type;
|
|
||||||
word0 |= (SendBuff.Count & 0xf) << 20;
|
|
||||||
word0 |= (ReceiveBuff.Count & 0xf) << 24;
|
|
||||||
word0 |= (ExchangeBuff.Count & 0xf) << 28;
|
|
||||||
|
|
||||||
byte[] handleData = Array.Empty<byte>();
|
|
||||||
|
|
||||||
if (HandleDesc != null)
|
|
||||||
{
|
|
||||||
handleData = HandleDesc.GetBytes();
|
|
||||||
}
|
|
||||||
|
|
||||||
int dataLength = RawData?.Length ?? 0;
|
|
||||||
|
|
||||||
dataLength = ((dataLength + 3) & ~3) / 4;
|
|
||||||
|
|
||||||
word1 = (dataLength & 0x3ff);
|
|
||||||
|
|
||||||
if (HandleDesc != null)
|
|
||||||
{
|
|
||||||
word1 |= 1 << 31;
|
|
||||||
}
|
|
||||||
|
|
||||||
writer.Write(word0);
|
|
||||||
writer.Write(word1);
|
|
||||||
writer.Write(handleData);
|
|
||||||
|
|
||||||
if (RawData != null)
|
|
||||||
{
|
|
||||||
writer.Write(RawData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ms.ToArray();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ms.Write(word0);
|
||||||
|
ms.Write(word1);
|
||||||
|
|
||||||
|
if (handleDataStream != null)
|
||||||
|
{
|
||||||
|
ms.Write(handleDataStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RawData != null)
|
||||||
|
{
|
||||||
|
ms.Write(RawData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
private long GetPadSize16(long position)
|
private long GetPadSize16(long position)
|
||||||
|
@@ -13,13 +13,11 @@ namespace Ryujinx.HLE.HOS.Ipc
|
|||||||
Size = size;
|
Size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IpcRecvListBuffDesc(BinaryReader reader)
|
public IpcRecvListBuffDesc(ulong packedValue)
|
||||||
{
|
{
|
||||||
ulong value = reader.ReadUInt64();
|
Position = packedValue & 0xffffffffffff;
|
||||||
|
|
||||||
Position = value & 0xffffffffffff;
|
Size = (ushort)(packedValue >> 48);
|
||||||
|
|
||||||
Size = (ushort)(value >> 48);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
|
|
||||||
public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint)
|
public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint)
|
||||||
{
|
{
|
||||||
Object = schedulerObj;
|
Object = schedulerObj;
|
||||||
TimePoint = timePoint;
|
TimePoint = timePoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
private bool _keepRunning;
|
private bool _keepRunning;
|
||||||
private long _enforceWakeupFromSpinWait;
|
private long _enforceWakeupFromSpinWait;
|
||||||
|
|
||||||
|
private const long NanosecondsPerSecond = 1000000000L;
|
||||||
|
private const long NanosecondsPerMillisecond = 1000000L;
|
||||||
|
|
||||||
public KTimeManager(KernelContext context)
|
public KTimeManager(KernelContext context)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
@@ -55,7 +58,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
{
|
{
|
||||||
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
|
_waitingObjects.Add(new WaitingObject(schedulerObj, timePoint));
|
||||||
|
|
||||||
if (timeout < 1000000)
|
if (timeout < NanosecondsPerMillisecond)
|
||||||
{
|
{
|
||||||
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
|
Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1);
|
||||||
}
|
}
|
||||||
@@ -142,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
private WaitingObject GetNextWaitingObject()
|
private WaitingObject GetNextWaitingObject()
|
||||||
{
|
{
|
||||||
WaitingObject selected = null;
|
WaitingObject selected = null;
|
||||||
|
|
||||||
long lowestTimePoint = long.MaxValue;
|
long lowestTimePoint = long.MaxValue;
|
||||||
|
|
||||||
for (int index = _waitingObjects.Count - 1; index >= 0; index--)
|
for (int index = _waitingObjects.Count - 1; index >= 0; index--)
|
||||||
@@ -161,7 +164,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
|
|
||||||
public static long ConvertNanosecondsToMilliseconds(long time)
|
public static long ConvertNanosecondsToMilliseconds(long time)
|
||||||
{
|
{
|
||||||
time /= 1000000;
|
time /= NanosecondsPerMillisecond;
|
||||||
|
|
||||||
if ((ulong)time > int.MaxValue)
|
if ((ulong)time > int.MaxValue)
|
||||||
{
|
{
|
||||||
@@ -173,18 +176,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
|
|||||||
|
|
||||||
public static long ConvertMillisecondsToNanoseconds(long time)
|
public static long ConvertMillisecondsToNanoseconds(long time)
|
||||||
{
|
{
|
||||||
return time * 1000000;
|
return time * NanosecondsPerMillisecond;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long ConvertNanosecondsToHostTicks(long ns)
|
public static long ConvertNanosecondsToHostTicks(long ns)
|
||||||
{
|
{
|
||||||
long nsDiv = ns / 1000000000;
|
long nsDiv = ns / NanosecondsPerSecond;
|
||||||
long nsMod = ns % 1000000000;
|
long nsMod = ns % NanosecondsPerSecond;
|
||||||
long tickDiv = PerformanceCounter.TicksPerSecond / 1000000000;
|
long tickDiv = PerformanceCounter.TicksPerSecond / NanosecondsPerSecond;
|
||||||
long tickMod = PerformanceCounter.TicksPerSecond % 1000000000;
|
long tickMod = PerformanceCounter.TicksPerSecond % NanosecondsPerSecond;
|
||||||
|
|
||||||
long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / 1000000000;
|
long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / NanosecondsPerSecond;
|
||||||
return (nsDiv * tickDiv) * 1000000000 + nsDiv * tickMod + nsMod * tickDiv + baseTicks;
|
return (nsDiv * tickDiv) * NanosecondsPerSecond + nsDiv * tickMod + nsMod * tickDiv + baseTicks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long ConvertGuestTicksToNanoseconds(long ticks)
|
public static long ConvertGuestTicksToNanoseconds(long ticks)
|
||||||
|
@@ -7,6 +7,8 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||||||
public const int InitialKipId = 1;
|
public const int InitialKipId = 1;
|
||||||
public const int InitialProcessId = 0x51;
|
public const int InitialProcessId = 0x51;
|
||||||
|
|
||||||
|
public const int SupervisorCallCount = 0xC0;
|
||||||
|
|
||||||
public const int MemoryBlockAllocatorSize = 0x2710;
|
public const int MemoryBlockAllocatorSize = 0x2710;
|
||||||
|
|
||||||
public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase;
|
public const ulong UserSlabHeapBase = DramMemoryMap.SlabHeapBase;
|
||||||
@@ -15,4 +17,4 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||||||
|
|
||||||
public const ulong CounterFrequency = 19200000;
|
public const ulong CounterFrequency = 19200000;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||||||
public static Result StartInitialProcess(
|
public static Result StartInitialProcess(
|
||||||
KernelContext context,
|
KernelContext context,
|
||||||
ProcessCreationInfo creationInfo,
|
ProcessCreationInfo creationInfo,
|
||||||
ReadOnlySpan<int> capabilities,
|
ReadOnlySpan<uint> capabilities,
|
||||||
int mainThreadPriority,
|
int mainThreadPriority,
|
||||||
ThreadStart customThreadStart)
|
ThreadStart customThreadStart)
|
||||||
{
|
{
|
||||||
|
22
Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
Normal file
22
Ryujinx.HLE/HOS/Kernel/Process/CapabilityExtensions.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
|
{
|
||||||
|
static class CapabilityExtensions
|
||||||
|
{
|
||||||
|
public static CapabilityType GetCapabilityType(this uint cap)
|
||||||
|
{
|
||||||
|
return (CapabilityType)(((cap + 1) & ~cap) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint GetFlag(this CapabilityType type)
|
||||||
|
{
|
||||||
|
return (uint)type + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static uint GetId(this CapabilityType type)
|
||||||
|
{
|
||||||
|
return (uint)BitOperations.TrailingZeroCount(type.GetFlag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs
Normal file
19
Ryujinx.HLE/HOS/Kernel/Process/CapabilityType.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
|
{
|
||||||
|
enum CapabilityType : uint
|
||||||
|
{
|
||||||
|
CorePriority = (1u << 3) - 1,
|
||||||
|
SyscallMask = (1u << 4) - 1,
|
||||||
|
MapRange = (1u << 6) - 1,
|
||||||
|
MapIoPage = (1u << 7) - 1,
|
||||||
|
MapRegion = (1u << 10) - 1,
|
||||||
|
InterruptPair = (1u << 11) - 1,
|
||||||
|
ProgramType = (1u << 13) - 1,
|
||||||
|
KernelVersion = (1u << 14) - 1,
|
||||||
|
HandleTable = (1u << 15) - 1,
|
||||||
|
DebugFlags = (1u << 16) - 1,
|
||||||
|
|
||||||
|
Invalid = 0u,
|
||||||
|
Padding = ~0u
|
||||||
|
}
|
||||||
|
}
|
@@ -19,7 +19,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
private int _activeSlotsCount;
|
private int _activeSlotsCount;
|
||||||
|
|
||||||
private int _size;
|
private uint _size;
|
||||||
|
|
||||||
private ushort _idCounter;
|
private ushort _idCounter;
|
||||||
|
|
||||||
@@ -28,9 +28,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result Initialize(int size)
|
public Result Initialize(uint size)
|
||||||
{
|
{
|
||||||
if ((uint)size > 1024)
|
if (size > 1024)
|
||||||
{
|
{
|
||||||
return KernelResult.OutOfMemory;
|
return KernelResult.OutOfMemory;
|
||||||
}
|
}
|
||||||
|
@@ -16,11 +16,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
{
|
{
|
||||||
class KProcess : KSynchronizationObject
|
class KProcess : KSynchronizationObject
|
||||||
{
|
{
|
||||||
public const int KernelVersionMajor = 10;
|
public const uint KernelVersionMajor = 10;
|
||||||
public const int KernelVersionMinor = 4;
|
public const uint KernelVersionMinor = 4;
|
||||||
public const int KernelVersionRevision = 0;
|
public const uint KernelVersionRevision = 0;
|
||||||
|
|
||||||
public const int KernelVersionPacked =
|
public const uint KernelVersionPacked =
|
||||||
(KernelVersionMajor << 19) |
|
(KernelVersionMajor << 19) |
|
||||||
(KernelVersionMinor << 15) |
|
(KernelVersionMinor << 15) |
|
||||||
(KernelVersionRevision << 0);
|
(KernelVersionRevision << 0);
|
||||||
@@ -119,7 +119,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
public Result InitializeKip(
|
public Result InitializeKip(
|
||||||
ProcessCreationInfo creationInfo,
|
ProcessCreationInfo creationInfo,
|
||||||
ReadOnlySpan<int> capabilities,
|
ReadOnlySpan<uint> capabilities,
|
||||||
KPageList pageList,
|
KPageList pageList,
|
||||||
KResourceLimit resourceLimit,
|
KResourceLimit resourceLimit,
|
||||||
MemoryRegion memRegion,
|
MemoryRegion memRegion,
|
||||||
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
public Result Initialize(
|
public Result Initialize(
|
||||||
ProcessCreationInfo creationInfo,
|
ProcessCreationInfo creationInfo,
|
||||||
ReadOnlySpan<int> capabilities,
|
ReadOnlySpan<uint> capabilities,
|
||||||
KResourceLimit resourceLimit,
|
KResourceLimit resourceLimit,
|
||||||
MemoryRegion memRegion,
|
MemoryRegion memRegion,
|
||||||
IProcessContextFactory contextFactory,
|
IProcessContextFactory contextFactory,
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
@@ -9,48 +8,49 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
{
|
{
|
||||||
class KProcessCapabilities
|
class KProcessCapabilities
|
||||||
{
|
{
|
||||||
public byte[] SvcAccessMask { get; private set; }
|
public byte[] SvcAccessMask { get; }
|
||||||
public byte[] IrqAccessMask { get; private set; }
|
public byte[] IrqAccessMask { get; }
|
||||||
|
|
||||||
public ulong AllowedCpuCoresMask { get; private set; }
|
public ulong AllowedCpuCoresMask { get; private set; }
|
||||||
public ulong AllowedThreadPriosMask { get; private set; }
|
public ulong AllowedThreadPriosMask { get; private set; }
|
||||||
|
|
||||||
public int DebuggingFlags { get; private set; }
|
public uint DebuggingFlags { get; private set; }
|
||||||
public int HandleTableSize { get; private set; }
|
public uint HandleTableSize { get; private set; }
|
||||||
public int KernelReleaseVersion { get; private set; }
|
public uint KernelReleaseVersion { get; private set; }
|
||||||
public int ApplicationType { get; private set; }
|
public uint ApplicationType { get; private set; }
|
||||||
|
|
||||||
public KProcessCapabilities()
|
public KProcessCapabilities()
|
||||||
{
|
{
|
||||||
SvcAccessMask = new byte[0x10];
|
// length / number of bits of the underlying type
|
||||||
|
SvcAccessMask = new byte[KernelConstants.SupervisorCallCount / 8];
|
||||||
IrqAccessMask = new byte[0x80];
|
IrqAccessMask = new byte[0x80];
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result InitializeForKernel(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager)
|
public Result InitializeForKernel(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
|
||||||
{
|
{
|
||||||
AllowedCpuCoresMask = 0xf;
|
AllowedCpuCoresMask = 0xf;
|
||||||
AllowedThreadPriosMask = ulong.MaxValue;
|
AllowedThreadPriosMask = ulong.MaxValue;
|
||||||
DebuggingFlags &= ~3;
|
DebuggingFlags &= ~3u;
|
||||||
KernelReleaseVersion = KProcess.KernelVersionPacked;
|
KernelReleaseVersion = KProcess.KernelVersionPacked;
|
||||||
|
|
||||||
return Parse(capabilities, memoryManager);
|
return Parse(capabilities, memoryManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result InitializeForUser(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager)
|
public Result InitializeForUser(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
|
||||||
{
|
{
|
||||||
return Parse(capabilities, memoryManager);
|
return Parse(capabilities, memoryManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result Parse(ReadOnlySpan<int> capabilities, KPageTableBase memoryManager)
|
private Result Parse(ReadOnlySpan<uint> capabilities, KPageTableBase memoryManager)
|
||||||
{
|
{
|
||||||
int mask0 = 0;
|
int mask0 = 0;
|
||||||
int mask1 = 0;
|
int mask1 = 0;
|
||||||
|
|
||||||
for (int index = 0; index < capabilities.Length; index++)
|
for (int index = 0; index < capabilities.Length; index++)
|
||||||
{
|
{
|
||||||
int cap = capabilities[index];
|
uint cap = capabilities[index];
|
||||||
|
|
||||||
if (((cap + 1) & ~cap) != 0x40)
|
if (cap.GetCapabilityType() != CapabilityType.MapRange)
|
||||||
{
|
{
|
||||||
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
|
Result result = ParseCapability(cap, ref mask0, ref mask1, memoryManager);
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
return KernelResult.InvalidCombination;
|
return KernelResult.InvalidCombination;
|
||||||
}
|
}
|
||||||
|
|
||||||
int prevCap = cap;
|
uint prevCap = cap;
|
||||||
|
|
||||||
cap = capabilities[++index];
|
cap = capabilities[++index];
|
||||||
|
|
||||||
@@ -85,8 +85,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
return KernelResult.InvalidSize;
|
return KernelResult.InvalidSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
long address = ((long)(uint)prevCap << 5) & 0xffffff000;
|
long address = ((long)prevCap << 5) & 0xffffff000;
|
||||||
long size = ((long)(uint)cap << 5) & 0xfffff000;
|
long size = ((long)cap << 5) & 0xfffff000;
|
||||||
|
|
||||||
if (((ulong)(address + size - 1) >> 36) != 0)
|
if (((ulong)(address + size - 1) >> 36) != 0)
|
||||||
{
|
{
|
||||||
@@ -118,20 +118,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Result ParseCapability(int cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
|
private Result ParseCapability(uint cap, ref int mask0, ref int mask1, KPageTableBase memoryManager)
|
||||||
{
|
{
|
||||||
int code = (cap + 1) & ~cap;
|
CapabilityType code = cap.GetCapabilityType();
|
||||||
|
|
||||||
if (code == 1)
|
if (code == CapabilityType.Invalid)
|
||||||
{
|
{
|
||||||
return KernelResult.InvalidCapability;
|
return KernelResult.InvalidCapability;
|
||||||
}
|
}
|
||||||
else if (code == 0)
|
else if (code == CapabilityType.Padding)
|
||||||
{
|
{
|
||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int codeMask = 1 << (32 - BitOperations.LeadingZeroCount((uint)code + 1));
|
int codeMask = 1 << (32 - BitOperations.LeadingZeroCount(code.GetFlag() + 1));
|
||||||
|
|
||||||
// Check if the property was already set.
|
// Check if the property was already set.
|
||||||
if (((mask0 & codeMask) & 0x1e008) != 0)
|
if (((mask0 & codeMask) & 0x1e008) != 0)
|
||||||
@@ -143,23 +143,23 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
switch (code)
|
switch (code)
|
||||||
{
|
{
|
||||||
case 8:
|
case CapabilityType.CorePriority:
|
||||||
{
|
{
|
||||||
if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0)
|
if (AllowedCpuCoresMask != 0 || AllowedThreadPriosMask != 0)
|
||||||
{
|
{
|
||||||
return KernelResult.InvalidCapability;
|
return KernelResult.InvalidCapability;
|
||||||
}
|
}
|
||||||
|
|
||||||
int lowestCpuCore = (cap >> 16) & 0xff;
|
uint lowestCpuCore = (cap >> 16) & 0xff;
|
||||||
int highestCpuCore = (cap >> 24) & 0xff;
|
uint highestCpuCore = (cap >> 24) & 0xff;
|
||||||
|
|
||||||
if (lowestCpuCore > highestCpuCore)
|
if (lowestCpuCore > highestCpuCore)
|
||||||
{
|
{
|
||||||
return KernelResult.InvalidCombination;
|
return KernelResult.InvalidCombination;
|
||||||
}
|
}
|
||||||
|
|
||||||
int highestThreadPrio = (cap >> 4) & 0x3f;
|
uint highestThreadPrio = (cap >> 4) & 0x3f;
|
||||||
int lowestThreadPrio = (cap >> 10) & 0x3f;
|
uint lowestThreadPrio = (cap >> 10) & 0x3f;
|
||||||
|
|
||||||
if (lowestThreadPrio > highestThreadPrio)
|
if (lowestThreadPrio > highestThreadPrio)
|
||||||
{
|
{
|
||||||
@@ -177,9 +177,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x10:
|
case CapabilityType.SyscallMask:
|
||||||
{
|
{
|
||||||
int slot = (cap >> 29) & 7;
|
int slot = ((int)cap >> 29) & 7;
|
||||||
|
|
||||||
int svcSlotMask = 1 << slot;
|
int svcSlotMask = 1 << slot;
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
mask1 |= svcSlotMask;
|
mask1 |= svcSlotMask;
|
||||||
|
|
||||||
int svcMask = (cap >> 5) & 0xffffff;
|
uint svcMask = (cap >> 5) & 0xffffff;
|
||||||
|
|
||||||
int baseSvc = slot * 24;
|
int baseSvc = slot * 24;
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
|
|
||||||
int svcId = baseSvc + index;
|
int svcId = baseSvc + index;
|
||||||
|
|
||||||
if (svcId > 0x7f)
|
if (svcId >= KernelConstants.SupervisorCallCount)
|
||||||
{
|
{
|
||||||
return KernelResult.MaximumExceeded;
|
return KernelResult.MaximumExceeded;
|
||||||
}
|
}
|
||||||
@@ -214,20 +214,27 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x80:
|
case CapabilityType.MapIoPage:
|
||||||
{
|
{
|
||||||
long address = ((long)(uint)cap << 4) & 0xffffff000;
|
long address = ((long)cap << 4) & 0xffffff000;
|
||||||
|
|
||||||
memoryManager.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite);
|
memoryManager.MapIoMemory(address, KPageTableBase.PageSize, KMemoryPermission.ReadAndWrite);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x800:
|
case CapabilityType.MapRegion:
|
||||||
|
{
|
||||||
|
// TODO: Implement capabilities for MapRegion
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case CapabilityType.InterruptPair:
|
||||||
{
|
{
|
||||||
// TODO: GIC distributor check.
|
// TODO: GIC distributor check.
|
||||||
int irq0 = (cap >> 12) & 0x3ff;
|
int irq0 = ((int)cap >> 12) & 0x3ff;
|
||||||
int irq1 = (cap >> 22) & 0x3ff;
|
int irq1 = ((int)cap >> 22) & 0x3ff;
|
||||||
|
|
||||||
if (irq0 != 0x3ff)
|
if (irq0 != 0x3ff)
|
||||||
{
|
{
|
||||||
@@ -242,11 +249,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x2000:
|
case CapabilityType.ProgramType:
|
||||||
{
|
{
|
||||||
int applicationType = cap >> 14;
|
uint applicationType = (cap >> 14);
|
||||||
|
|
||||||
if ((uint)applicationType > 7)
|
if (applicationType > 7)
|
||||||
{
|
{
|
||||||
return KernelResult.ReservedValue;
|
return KernelResult.ReservedValue;
|
||||||
}
|
}
|
||||||
@@ -256,7 +263,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x4000:
|
case CapabilityType.KernelVersion:
|
||||||
{
|
{
|
||||||
// Note: This check is bugged on kernel too, we are just replicating the bug here.
|
// Note: This check is bugged on kernel too, we are just replicating the bug here.
|
||||||
if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000)
|
if ((KernelReleaseVersion >> 17) != 0 || cap < 0x80000)
|
||||||
@@ -269,11 +276,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x8000:
|
case CapabilityType.HandleTable:
|
||||||
{
|
{
|
||||||
int handleTableSize = cap >> 26;
|
uint handleTableSize = cap >> 26;
|
||||||
|
|
||||||
if ((uint)handleTableSize > 0x3ff)
|
if (handleTableSize > 0x3ff)
|
||||||
{
|
{
|
||||||
return KernelResult.ReservedValue;
|
return KernelResult.ReservedValue;
|
||||||
}
|
}
|
||||||
@@ -283,16 +290,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 0x10000:
|
case CapabilityType.DebugFlags:
|
||||||
{
|
{
|
||||||
int debuggingFlags = cap >> 19;
|
uint debuggingFlags = cap >> 19;
|
||||||
|
|
||||||
if ((uint)debuggingFlags > 3)
|
if (debuggingFlags > 3)
|
||||||
{
|
{
|
||||||
return KernelResult.ReservedValue;
|
return KernelResult.ReservedValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
DebuggingFlags &= ~3;
|
DebuggingFlags &= ~3u;
|
||||||
DebuggingFlags |= debuggingFlags;
|
DebuggingFlags |= debuggingFlags;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -304,18 +311,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong GetMaskFromMinMax(int min, int max)
|
private static ulong GetMaskFromMinMax(uint min, uint max)
|
||||||
{
|
{
|
||||||
int range = max - min + 1;
|
uint range = max - min + 1;
|
||||||
|
|
||||||
if (range == 64)
|
if (range == 64)
|
||||||
{
|
{
|
||||||
return ulong.MaxValue;
|
return ulong.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong mask = (1UL << range) - 1;
|
ulong mask = (1UL << (int)range) - 1;
|
||||||
|
|
||||||
return mask << min;
|
return mask << (int)min;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,5 +1,8 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Kernel.Process
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||||
{
|
{
|
||||||
|
[Flags]
|
||||||
enum ProcessCreationFlags
|
enum ProcessCreationFlags
|
||||||
{
|
{
|
||||||
Is64Bit = 1 << 0,
|
Is64Bit = 1 << 0,
|
||||||
|
@@ -54,7 +54,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
|||||||
public Result CreateProcess(
|
public Result CreateProcess(
|
||||||
out int handle,
|
out int handle,
|
||||||
ProcessCreationInfo info,
|
ProcessCreationInfo info,
|
||||||
ReadOnlySpan<int> capabilities,
|
ReadOnlySpan<uint> capabilities,
|
||||||
IProcessContextFactory contextFactory,
|
IProcessContextFactory contextFactory,
|
||||||
ThreadStart customThreadStart = null)
|
ThreadStart customThreadStart = null)
|
||||||
{
|
{
|
||||||
@@ -553,7 +553,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
|||||||
|
|
||||||
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
KProcess currentProcess = KernelStatic.GetCurrentProcess();
|
||||||
|
|
||||||
KSynchronizationObject[] syncObjs = new KSynchronizationObject[handles.Length];
|
KSynchronizationObject[] syncObjs = handles.Length == 0 ? Array.Empty<KSynchronizationObject>() : new KSynchronizationObject[handles.Length];
|
||||||
|
|
||||||
for (int index = 0; index < handles.Length; index++)
|
for (int index = 0; index < handles.Length; index++)
|
||||||
{
|
{
|
||||||
@@ -3002,4 +3002,4 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
|||||||
return (address & 3) != 0;
|
return (address & 3) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -5,11 +5,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
{
|
{
|
||||||
class KPriorityQueue
|
class KPriorityQueue
|
||||||
{
|
{
|
||||||
private LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
|
private readonly LinkedList<KThread>[][] _scheduledThreadsPerPrioPerCore;
|
||||||
private LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
|
private readonly LinkedList<KThread>[][] _suggestedThreadsPerPrioPerCore;
|
||||||
|
|
||||||
private long[] _scheduledPrioritiesPerCore;
|
private readonly long[] _scheduledPrioritiesPerCore;
|
||||||
private long[] _suggestedPrioritiesPerCore;
|
private readonly long[] _suggestedPrioritiesPerCore;
|
||||||
|
|
||||||
public KPriorityQueue()
|
public KPriorityQueue()
|
||||||
{
|
{
|
||||||
@@ -32,43 +32,134 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
_suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
|
_suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount];
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<KThread> SuggestedThreads(int core)
|
public readonly ref struct KThreadEnumerable
|
||||||
{
|
{
|
||||||
return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core);
|
readonly LinkedList<KThread>[][] _listPerPrioPerCore;
|
||||||
}
|
readonly long[] _prios;
|
||||||
|
readonly int _core;
|
||||||
|
|
||||||
public IEnumerable<KThread> ScheduledThreads(int core)
|
public KThreadEnumerable(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
|
||||||
{
|
|
||||||
return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<KThread> Iterate(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
|
|
||||||
{
|
|
||||||
long prioMask = prios[core];
|
|
||||||
|
|
||||||
int prio = BitOperations.TrailingZeroCount(prioMask);
|
|
||||||
|
|
||||||
prioMask &= ~(1L << prio);
|
|
||||||
|
|
||||||
while (prio < KScheduler.PrioritiesCount)
|
|
||||||
{
|
{
|
||||||
LinkedList<KThread> list = listPerPrioPerCore[prio][core];
|
_listPerPrioPerCore = listPerPrioPerCore;
|
||||||
|
_prios = prios;
|
||||||
|
_core = core;
|
||||||
|
}
|
||||||
|
|
||||||
LinkedListNode<KThread> node = list.First;
|
public Enumerator GetEnumerator()
|
||||||
|
{
|
||||||
|
return new Enumerator(_listPerPrioPerCore, _prios, _core);
|
||||||
|
}
|
||||||
|
|
||||||
while (node != null)
|
public ref struct Enumerator
|
||||||
|
{
|
||||||
|
private readonly LinkedList<KThread>[][] _listPerPrioPerCore;
|
||||||
|
private readonly int _core;
|
||||||
|
private long _prioMask;
|
||||||
|
private int _prio;
|
||||||
|
private LinkedList<KThread> _list;
|
||||||
|
private LinkedListNode<KThread> _node;
|
||||||
|
|
||||||
|
public Enumerator(LinkedList<KThread>[][] listPerPrioPerCore, long[] prios, int core)
|
||||||
{
|
{
|
||||||
yield return node.Value;
|
_listPerPrioPerCore = listPerPrioPerCore;
|
||||||
|
_core = core;
|
||||||
node = node.Next;
|
_prioMask = prios[core];
|
||||||
|
_prio = BitOperations.TrailingZeroCount(_prioMask);
|
||||||
|
_prioMask &= ~(1L << _prio);
|
||||||
}
|
}
|
||||||
|
|
||||||
prio = BitOperations.TrailingZeroCount(prioMask);
|
public KThread Current => _node?.Value;
|
||||||
|
|
||||||
prioMask &= ~(1L << prio);
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
_node = _node?.Next;
|
||||||
|
|
||||||
|
if (_node == null)
|
||||||
|
{
|
||||||
|
if (!MoveNextListAndFirstNode())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return _node != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool MoveNextListAndFirstNode()
|
||||||
|
{
|
||||||
|
if (_prio < KScheduler.PrioritiesCount)
|
||||||
|
{
|
||||||
|
_list = _listPerPrioPerCore[_prio][_core];
|
||||||
|
|
||||||
|
_node = _list.First;
|
||||||
|
|
||||||
|
_prio = BitOperations.TrailingZeroCount(_prioMask);
|
||||||
|
|
||||||
|
_prioMask &= ~(1L << _prio);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_list = null;
|
||||||
|
_node = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public KThreadEnumerable ScheduledThreads(int core)
|
||||||
|
{
|
||||||
|
return new KThreadEnumerable(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KThreadEnumerable SuggestedThreads(int core)
|
||||||
|
{
|
||||||
|
return new KThreadEnumerable(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KThread ScheduledThreadsFirstOrDefault(int core)
|
||||||
|
{
|
||||||
|
return ScheduledThreadsElementAtOrDefault(core, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KThread ScheduledThreadsElementAtOrDefault(int core, int index)
|
||||||
|
{
|
||||||
|
int currentIndex = 0;
|
||||||
|
foreach (var scheduledThread in ScheduledThreads(core))
|
||||||
|
{
|
||||||
|
if (currentIndex == index)
|
||||||
|
{
|
||||||
|
return scheduledThread;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public KThread ScheduledThreadsWithDynamicPriorityFirstOrDefault(int core, int dynamicPriority)
|
||||||
|
{
|
||||||
|
foreach (var scheduledThread in ScheduledThreads(core))
|
||||||
|
{
|
||||||
|
if (scheduledThread.DynamicPriority == dynamicPriority)
|
||||||
|
{
|
||||||
|
return scheduledThread;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasScheduledThreads(int core)
|
||||||
|
{
|
||||||
|
return ScheduledThreadsFirstOrDefault(core) != null;
|
||||||
|
}
|
||||||
|
|
||||||
public void TransferToCore(int prio, int dstCore, KThread thread)
|
public void TransferToCore(int prio, int dstCore, KThread thread)
|
||||||
{
|
{
|
||||||
int srcCore = thread.ActiveCore;
|
int srcCore = thread.ActiveCore;
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@@ -17,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 };
|
private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 };
|
||||||
|
|
||||||
|
private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount];
|
||||||
|
|
||||||
private readonly KernelContext _context;
|
private readonly KernelContext _context;
|
||||||
private readonly int _coreId;
|
private readonly int _coreId;
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
for (int core = 0; core < CpuCoresCount; core++)
|
for (int core = 0; core < CpuCoresCount; core++)
|
||||||
{
|
{
|
||||||
KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault();
|
KThread thread = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core);
|
||||||
|
|
||||||
if (thread != null &&
|
if (thread != null &&
|
||||||
thread.Owner != null &&
|
thread.Owner != null &&
|
||||||
@@ -115,12 +115,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
{
|
{
|
||||||
// If the core is not idle (there's already a thread running on it),
|
// If the core is not idle (there's already a thread running on it),
|
||||||
// then we don't need to attempt load balancing.
|
// then we don't need to attempt load balancing.
|
||||||
if (context.PriorityQueue.ScheduledThreads(core).Any())
|
if (context.PriorityQueue.HasScheduledThreads(core))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] srcCoresHighestPrioThreads = new int[CpuCoresCount];
|
Array.Fill(_srcCoresHighestPrioThreads, 0);
|
||||||
|
|
||||||
int srcCoresHighestPrioThreadsCount = 0;
|
int srcCoresHighestPrioThreadsCount = 0;
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore;
|
_srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not yet selected candidate found.
|
// Not yet selected candidate found.
|
||||||
@@ -158,9 +158,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
// (the first one that doesn't make the source core idle if moved).
|
// (the first one that doesn't make the source core idle if moved).
|
||||||
for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++)
|
for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++)
|
||||||
{
|
{
|
||||||
int srcCore = srcCoresHighestPrioThreads[index];
|
int srcCore = _srcCoresHighestPrioThreads[index];
|
||||||
|
|
||||||
KThread src = context.PriorityQueue.ScheduledThreads(srcCore).ElementAtOrDefault(1);
|
KThread src = context.PriorityQueue.ScheduledThreadsElementAtOrDefault(srcCore, 1);
|
||||||
|
|
||||||
if (src != null)
|
if (src != null)
|
||||||
{
|
{
|
||||||
@@ -422,9 +422,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
private static void RotateScheduledQueue(KernelContext context, int core, int prio)
|
private static void RotateScheduledQueue(KernelContext context, int core, int prio)
|
||||||
{
|
{
|
||||||
IEnumerable<KThread> scheduledThreads = context.PriorityQueue.ScheduledThreads(core);
|
KThread selectedThread = context.PriorityQueue.ScheduledThreadsWithDynamicPriorityFirstOrDefault(core, prio);
|
||||||
|
|
||||||
KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio);
|
|
||||||
KThread nextThread = null;
|
KThread nextThread = null;
|
||||||
|
|
||||||
// Yield priority queue.
|
// Yield priority queue.
|
||||||
@@ -433,14 +431,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread);
|
nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<KThread> SuitableCandidates()
|
static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread selectedThread, KThread nextThread, Predicate< KThread> predicate)
|
||||||
{
|
{
|
||||||
foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
|
foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
|
||||||
{
|
{
|
||||||
int suggestedCore = suggested.ActiveCore;
|
int suggestedCore = suggested.ActiveCore;
|
||||||
if (suggestedCore >= 0)
|
if (suggestedCore >= 0)
|
||||||
{
|
{
|
||||||
KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault();
|
KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore);
|
||||||
|
|
||||||
if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2))
|
if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2))
|
||||||
{
|
{
|
||||||
@@ -453,14 +451,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
nextThread == null ||
|
nextThread == null ||
|
||||||
nextThread.LastScheduledTime >= suggested.LastScheduledTime)
|
nextThread.LastScheduledTime >= suggested.LastScheduledTime)
|
||||||
{
|
{
|
||||||
yield return suggested;
|
if (predicate(suggested))
|
||||||
|
{
|
||||||
|
return suggested;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select candidate threads that could run on this core.
|
// Select candidate threads that could run on this core.
|
||||||
// Only take into account threads that are not yet selected.
|
// Only take into account threads that are not yet selected.
|
||||||
KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio);
|
KThread dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority == prio);
|
||||||
|
|
||||||
if (dst != null)
|
if (dst != null)
|
||||||
{
|
{
|
||||||
@@ -469,11 +472,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
// If the priority of the currently selected thread is lower or same as the preemption priority,
|
// If the priority of the currently selected thread is lower or same as the preemption priority,
|
||||||
// then try to migrate a thread with lower priority.
|
// then try to migrate a thread with lower priority.
|
||||||
KThread bestCandidate = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault();
|
KThread bestCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core);
|
||||||
|
|
||||||
if (bestCandidate != null && bestCandidate.DynamicPriority >= prio)
|
if (bestCandidate != null && bestCandidate.DynamicPriority >= prio)
|
||||||
{
|
{
|
||||||
dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority < bestCandidate.DynamicPriority);
|
dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority < bestCandidate.DynamicPriority);
|
||||||
|
|
||||||
if (dst != null)
|
if (dst != null)
|
||||||
{
|
{
|
||||||
@@ -534,7 +537,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
// Move current thread to the end of the queue.
|
// Move current thread to the end of the queue.
|
||||||
KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread);
|
KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread);
|
||||||
|
|
||||||
IEnumerable<KThread> SuitableCandidates()
|
static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread nextThread, int lessThanOrEqualPriority)
|
||||||
{
|
{
|
||||||
foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
|
foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core))
|
||||||
{
|
{
|
||||||
@@ -554,12 +557,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
if (suggested.LastScheduledTime <= nextThread.LastScheduledTime ||
|
if (suggested.LastScheduledTime <= nextThread.LastScheduledTime ||
|
||||||
suggested.DynamicPriority < nextThread.DynamicPriority)
|
suggested.DynamicPriority < nextThread.DynamicPriority)
|
||||||
{
|
{
|
||||||
yield return suggested;
|
if (suggested.DynamicPriority <= lessThanOrEqualPriority)
|
||||||
|
{
|
||||||
|
return suggested;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio);
|
KThread dst = FirstSuitableCandidateOrDefault(context, core, nextThread, prio);
|
||||||
|
|
||||||
if (dst != null)
|
if (dst != null)
|
||||||
{
|
{
|
||||||
@@ -596,7 +604,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
|
|
||||||
context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread);
|
context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread);
|
||||||
|
|
||||||
if (!context.PriorityQueue.ScheduledThreads(core).Any())
|
if (!context.PriorityQueue.HasScheduledThreads(core))
|
||||||
{
|
{
|
||||||
KThread selectedThread = null;
|
KThread selectedThread = null;
|
||||||
|
|
||||||
@@ -609,7 +617,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
KThread firstCandidate = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault();
|
KThread firstCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore);
|
||||||
|
|
||||||
if (firstCandidate == suggested)
|
if (firstCandidate == suggested)
|
||||||
{
|
{
|
||||||
|
@@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LinkedListNode<KThread>[] syncNodes = new LinkedListNode<KThread>[syncObjs.Length];
|
LinkedListNode<KThread>[] syncNodes = syncObjs.Length == 0 ? Array.Empty<LinkedListNode<KThread>>() : new LinkedListNode<KThread>[syncObjs.Length];
|
||||||
|
|
||||||
for (int index = 0; index < syncObjs.Length; index++)
|
for (int index = 0; index < syncObjs.Length; index++)
|
||||||
{
|
{
|
||||||
|
@@ -80,7 +80,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL;
|
ulong codeBaseAddress = kip.Is64BitAddressSpace ? 0x8000000UL : 0x200000UL;
|
||||||
|
|
||||||
ulong codeAddress = codeBaseAddress + (ulong)kip.TextOffset;
|
ulong codeAddress = codeBaseAddress + kip.TextOffset;
|
||||||
|
|
||||||
ProcessCreationFlags flags = 0;
|
ProcessCreationFlags flags = 0;
|
||||||
|
|
||||||
@@ -231,13 +231,13 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
nsoSize = BitUtils.AlignUp<uint>(nsoSize, KPageTableBase.PageSize);
|
nsoSize = BitUtils.AlignUp<uint>(nsoSize, KPageTableBase.PageSize);
|
||||||
|
|
||||||
nsoBase[index] = codeStart + (ulong)codeSize;
|
nsoBase[index] = codeStart + codeSize;
|
||||||
|
|
||||||
codeSize += nsoSize;
|
codeSize += nsoSize;
|
||||||
|
|
||||||
if (arguments != null && argsSize == 0)
|
if (arguments != null && argsSize == 0)
|
||||||
{
|
{
|
||||||
argsStart = (ulong)codeSize;
|
argsStart = codeSize;
|
||||||
|
|
||||||
argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize);
|
argsSize = (uint)BitUtils.AlignDown(arguments.Length * 2 + ArgsTotalSize - 1, KPageTableBase.PageSize);
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
result = process.Initialize(
|
result = process.Initialize(
|
||||||
creationInfo,
|
creationInfo,
|
||||||
MemoryMarshal.Cast<byte, int>(npdm.KernelCapabilityData).ToArray(),
|
MemoryMarshal.Cast<byte, uint>(npdm.KernelCapabilityData),
|
||||||
resourceLimit,
|
resourceLimit,
|
||||||
memoryRegion,
|
memoryRegion,
|
||||||
processContextFactory);
|
processContextFactory);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
|
namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
|
||||||
@@ -10,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage
|
|||||||
public static byte[] MakeLaunchParams(UserProfile userProfile)
|
public static byte[] MakeLaunchParams(UserProfile userProfile)
|
||||||
{
|
{
|
||||||
// Size needs to be at least 0x88 bytes otherwise application errors.
|
// Size needs to be at least 0x88 bytes otherwise application errors.
|
||||||
using (MemoryStream ms = new MemoryStream())
|
using (MemoryStream ms = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
BinaryWriter writer = new BinaryWriter(ms);
|
BinaryWriter writer = new BinaryWriter(ms);
|
||||||
|
|
||||||
|
@@ -5,6 +5,7 @@ using LibHac.FsSystem;
|
|||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.Exceptions;
|
using Ryujinx.HLE.Exceptions;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
@@ -160,7 +161,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl
|
|||||||
static uint KXor(uint data) => data ^ FontKey;
|
static uint KXor(uint data) => data ^ FontKey;
|
||||||
|
|
||||||
using (BinaryReader reader = new BinaryReader(bfttfStream))
|
using (BinaryReader reader = new BinaryReader(bfttfStream))
|
||||||
using (MemoryStream ttfStream = new MemoryStream())
|
using (MemoryStream ttfStream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (BinaryWriter output = new BinaryWriter(ttfStream))
|
using (BinaryWriter output = new BinaryWriter(ttfStream))
|
||||||
{
|
{
|
||||||
if (KXor(reader.ReadUInt32()) != BFTTFMagic)
|
if (KXor(reader.ReadUInt32()) != BFTTFMagic)
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS.Ipc;
|
using Ryujinx.HLE.HOS.Ipc;
|
||||||
using Ryujinx.HLE.HOS.Kernel;
|
using Ryujinx.HLE.HOS.Kernel;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Ipc;
|
using Ryujinx.HLE.HOS.Kernel.Ipc;
|
||||||
@@ -5,6 +7,7 @@ using Ryujinx.HLE.HOS.Kernel.Process;
|
|||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -19,7 +22,7 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
// not large enough.
|
// not large enough.
|
||||||
private const int PointerBufferSize = 0x8000;
|
private const int PointerBufferSize = 0x8000;
|
||||||
|
|
||||||
private readonly static int[] DefaultCapabilities = new int[]
|
private readonly static uint[] DefaultCapabilities = new uint[]
|
||||||
{
|
{
|
||||||
0x030363F7,
|
0x030363F7,
|
||||||
0x1FFFFFCF,
|
0x1FFFFFCF,
|
||||||
@@ -37,14 +40,27 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
private readonly Dictionary<int, IpcService> _sessions = new Dictionary<int, IpcService>();
|
private readonly Dictionary<int, IpcService> _sessions = new Dictionary<int, IpcService>();
|
||||||
private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>();
|
private readonly Dictionary<int, Func<IpcService>> _ports = new Dictionary<int, Func<IpcService>>();
|
||||||
|
|
||||||
|
private readonly MemoryStream _requestDataStream;
|
||||||
|
private readonly BinaryReader _requestDataReader;
|
||||||
|
|
||||||
|
private readonly MemoryStream _responseDataStream;
|
||||||
|
private readonly BinaryWriter _responseDataWriter;
|
||||||
|
|
||||||
public ManualResetEvent InitDone { get; }
|
public ManualResetEvent InitDone { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public Func<IpcService> SmObjectFactory { get; }
|
public Func<IpcService> SmObjectFactory { get; }
|
||||||
|
|
||||||
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
|
public ServerBase(KernelContext context, string name, Func<IpcService> smObjectFactory = null)
|
||||||
{
|
{
|
||||||
InitDone = new ManualResetEvent(false);
|
|
||||||
_context = context;
|
_context = context;
|
||||||
|
|
||||||
|
_requestDataStream = MemoryStreamManager.Shared.GetStream();
|
||||||
|
_requestDataReader = new BinaryReader(_requestDataStream);
|
||||||
|
|
||||||
|
_responseDataStream = MemoryStreamManager.Shared.GetStream();
|
||||||
|
_responseDataWriter = new BinaryWriter(_responseDataStream);
|
||||||
|
|
||||||
|
InitDone = new ManualResetEvent(false);
|
||||||
Name = name;
|
Name = name;
|
||||||
SmObjectFactory = smObjectFactory;
|
SmObjectFactory = smObjectFactory;
|
||||||
|
|
||||||
@@ -110,15 +126,15 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
int[] portHandles = _portHandles.ToArray();
|
int handleCount = _portHandles.Count + _sessionHandles.Count;
|
||||||
int[] sessionHandles = _sessionHandles.ToArray();
|
|
||||||
int[] handles = new int[portHandles.Length + sessionHandles.Length];
|
|
||||||
|
|
||||||
portHandles.CopyTo(handles, 0);
|
int[] handles = ArrayPool<int>.Shared.Rent(handleCount);
|
||||||
sessionHandles.CopyTo(handles, portHandles.Length);
|
|
||||||
|
_portHandles.CopyTo(handles, 0);
|
||||||
|
_sessionHandles.CopyTo(handles, _portHandles.Count);
|
||||||
|
|
||||||
// We still need a timeout here to allow the service to pick up and listen new sessions...
|
// We still need a timeout here to allow the service to pick up and listen new sessions...
|
||||||
var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles, replyTargetHandle, 1000000L);
|
var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L);
|
||||||
|
|
||||||
thread.HandlePostSyscall();
|
thread.HandlePostSyscall();
|
||||||
|
|
||||||
@@ -129,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
|
|
||||||
replyTargetHandle = 0;
|
replyTargetHandle = 0;
|
||||||
|
|
||||||
if (rc == Result.Success && signaledIndex >= portHandles.Length)
|
if (rc == Result.Success && signaledIndex >= _portHandles.Count)
|
||||||
{
|
{
|
||||||
// We got a IPC request, process it, pass to the appropriate service if needed.
|
// We got a IPC request, process it, pass to the appropriate service if needed.
|
||||||
int signaledHandle = handles[signaledIndex];
|
int signaledHandle = handles[signaledIndex];
|
||||||
@@ -156,6 +172,8 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
_selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10);
|
||||||
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
|
_selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrayPool<int>.Shared.Return(handles);
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispose();
|
Dispose();
|
||||||
@@ -166,13 +184,9 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
KProcess process = KernelStatic.GetCurrentProcess();
|
KProcess process = KernelStatic.GetCurrentProcess();
|
||||||
KThread thread = KernelStatic.GetCurrentThread();
|
KThread thread = KernelStatic.GetCurrentThread();
|
||||||
ulong messagePtr = thread.TlsAddress;
|
ulong messagePtr = thread.TlsAddress;
|
||||||
ulong messageSize = 0x100;
|
|
||||||
|
|
||||||
byte[] reqData = new byte[messageSize];
|
IpcMessage request = ReadRequest(process, messagePtr);
|
||||||
|
|
||||||
process.CpuMemory.Read(messagePtr, reqData);
|
|
||||||
|
|
||||||
IpcMessage request = new IpcMessage(reqData, (long)messagePtr);
|
|
||||||
IpcMessage response = new IpcMessage();
|
IpcMessage response = new IpcMessage();
|
||||||
|
|
||||||
ulong tempAddr = recvListAddr;
|
ulong tempAddr = recvListAddr;
|
||||||
@@ -202,158 +216,157 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
bool shouldReply = true;
|
bool shouldReply = true;
|
||||||
bool isTipcCommunication = false;
|
bool isTipcCommunication = false;
|
||||||
|
|
||||||
using (MemoryStream raw = new MemoryStream(request.RawData))
|
_requestDataStream.SetLength(0);
|
||||||
|
_requestDataStream.Write(request.RawData);
|
||||||
|
_requestDataStream.Position = 0;
|
||||||
|
|
||||||
|
if (request.Type == IpcMessageType.HipcRequest ||
|
||||||
|
request.Type == IpcMessageType.HipcRequestWithContext)
|
||||||
{
|
{
|
||||||
BinaryReader reqReader = new BinaryReader(raw);
|
response.Type = IpcMessageType.HipcResponse;
|
||||||
|
|
||||||
if (request.Type == IpcMessageType.HipcRequest ||
|
_responseDataStream.SetLength(0);
|
||||||
request.Type == IpcMessageType.HipcRequestWithContext)
|
|
||||||
{
|
|
||||||
response.Type = IpcMessageType.HipcResponse;
|
|
||||||
|
|
||||||
using (MemoryStream resMs = new MemoryStream())
|
ServiceCtx context = new ServiceCtx(
|
||||||
{
|
_context.Device,
|
||||||
BinaryWriter resWriter = new BinaryWriter(resMs);
|
process,
|
||||||
|
process.CpuMemory,
|
||||||
|
thread,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
_requestDataReader,
|
||||||
|
_responseDataWriter);
|
||||||
|
|
||||||
ServiceCtx context = new ServiceCtx(
|
_sessions[serverSessionHandle].CallHipcMethod(context);
|
||||||
_context.Device,
|
|
||||||
process,
|
|
||||||
process.CpuMemory,
|
|
||||||
thread,
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
reqReader,
|
|
||||||
resWriter);
|
|
||||||
|
|
||||||
_sessions[serverSessionHandle].CallHipcMethod(context);
|
response.RawData = _responseDataStream.ToArray();
|
||||||
|
|
||||||
response.RawData = resMs.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (request.Type == IpcMessageType.HipcControl ||
|
|
||||||
request.Type == IpcMessageType.HipcControlWithContext)
|
|
||||||
{
|
|
||||||
uint magic = (uint)reqReader.ReadUInt64();
|
|
||||||
uint cmdId = (uint)reqReader.ReadUInt64();
|
|
||||||
|
|
||||||
switch (cmdId)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
request = FillResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain());
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
request = FillResponse(response, 0, PointerBufferSize);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// TODO: Whats the difference between IpcDuplicateSession/Ex?
|
|
||||||
case 2:
|
|
||||||
case 4:
|
|
||||||
int unknown = reqReader.ReadInt32();
|
|
||||||
|
|
||||||
_context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0);
|
|
||||||
|
|
||||||
AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]);
|
|
||||||
|
|
||||||
response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle);
|
|
||||||
|
|
||||||
request = FillResponse(response, 0);
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: throw new NotImplementedException(cmdId.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (request.Type == IpcMessageType.HipcCloseSession || request.Type == IpcMessageType.TipcCloseSession)
|
|
||||||
{
|
|
||||||
_context.Syscall.CloseHandle(serverSessionHandle);
|
|
||||||
_sessionHandles.Remove(serverSessionHandle);
|
|
||||||
IpcService service = _sessions[serverSessionHandle];
|
|
||||||
if (service is IDisposable disposableObj)
|
|
||||||
{
|
|
||||||
disposableObj.Dispose();
|
|
||||||
}
|
|
||||||
_sessions.Remove(serverSessionHandle);
|
|
||||||
shouldReply = false;
|
|
||||||
}
|
|
||||||
// If the type is past 0xF, we are using TIPC
|
|
||||||
else if (request.Type > IpcMessageType.TipcCloseSession)
|
|
||||||
{
|
|
||||||
isTipcCommunication = true;
|
|
||||||
|
|
||||||
// Response type is always the same as request on TIPC.
|
|
||||||
response.Type = request.Type;
|
|
||||||
|
|
||||||
using (MemoryStream resMs = new MemoryStream())
|
|
||||||
{
|
|
||||||
BinaryWriter resWriter = new BinaryWriter(resMs);
|
|
||||||
|
|
||||||
ServiceCtx context = new ServiceCtx(
|
|
||||||
_context.Device,
|
|
||||||
process,
|
|
||||||
process.CpuMemory,
|
|
||||||
thread,
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
reqReader,
|
|
||||||
resWriter);
|
|
||||||
|
|
||||||
_sessions[serverSessionHandle].CallTipcMethod(context);
|
|
||||||
|
|
||||||
response.RawData = resMs.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
process.CpuMemory.Write(messagePtr, response.GetBytesTipc());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new NotImplementedException(request.Type.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isTipcCommunication)
|
|
||||||
{
|
|
||||||
process.CpuMemory.Write(messagePtr, response.GetBytes((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return shouldReply;
|
|
||||||
}
|
}
|
||||||
|
else if (request.Type == IpcMessageType.HipcControl ||
|
||||||
|
request.Type == IpcMessageType.HipcControlWithContext)
|
||||||
|
{
|
||||||
|
uint magic = (uint)_requestDataReader.ReadUInt64();
|
||||||
|
uint cmdId = (uint)_requestDataReader.ReadUInt64();
|
||||||
|
|
||||||
|
switch (cmdId)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
FillHipcResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
FillHipcResponse(response, 0, PointerBufferSize);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// TODO: Whats the difference between IpcDuplicateSession/Ex?
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
int unknown = _requestDataReader.ReadInt32();
|
||||||
|
|
||||||
|
_context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0);
|
||||||
|
|
||||||
|
AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]);
|
||||||
|
|
||||||
|
response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle);
|
||||||
|
|
||||||
|
FillHipcResponse(response, 0);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: throw new NotImplementedException(cmdId.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (request.Type == IpcMessageType.HipcCloseSession || request.Type == IpcMessageType.TipcCloseSession)
|
||||||
|
{
|
||||||
|
_context.Syscall.CloseHandle(serverSessionHandle);
|
||||||
|
_sessionHandles.Remove(serverSessionHandle);
|
||||||
|
IpcService service = _sessions[serverSessionHandle];
|
||||||
|
(service as IDisposable)?.Dispose();
|
||||||
|
_sessions.Remove(serverSessionHandle);
|
||||||
|
shouldReply = false;
|
||||||
|
}
|
||||||
|
// If the type is past 0xF, we are using TIPC
|
||||||
|
else if (request.Type > IpcMessageType.TipcCloseSession)
|
||||||
|
{
|
||||||
|
isTipcCommunication = true;
|
||||||
|
|
||||||
|
// Response type is always the same as request on TIPC.
|
||||||
|
response.Type = request.Type;
|
||||||
|
|
||||||
|
_responseDataStream.SetLength(0);
|
||||||
|
|
||||||
|
ServiceCtx context = new ServiceCtx(
|
||||||
|
_context.Device,
|
||||||
|
process,
|
||||||
|
process.CpuMemory,
|
||||||
|
thread,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
_requestDataReader,
|
||||||
|
_responseDataWriter);
|
||||||
|
|
||||||
|
_sessions[serverSessionHandle].CallTipcMethod(context);
|
||||||
|
|
||||||
|
response.RawData = _responseDataStream.ToArray();
|
||||||
|
|
||||||
|
using var responseStream = response.GetStreamTipc();
|
||||||
|
process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new NotImplementedException(request.Type.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isTipcCommunication)
|
||||||
|
{
|
||||||
|
using var responseStream = response.GetStream((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48));
|
||||||
|
process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence());
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldReply;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IpcMessage FillResponse(IpcMessage response, long result, params int[] values)
|
private static IpcMessage ReadRequest(KProcess process, ulong messagePtr)
|
||||||
{
|
{
|
||||||
using (MemoryStream ms = new MemoryStream())
|
const int messageSize = 0x100;
|
||||||
{
|
|
||||||
BinaryWriter writer = new BinaryWriter(ms);
|
|
||||||
|
|
||||||
foreach (int value in values)
|
byte[] reqData = ArrayPool<byte>.Shared.Rent(messageSize);
|
||||||
{
|
|
||||||
writer.Write(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return FillResponse(response, result, ms.ToArray());
|
Span<byte> reqDataSpan = reqData.AsSpan(0, messageSize);
|
||||||
}
|
reqDataSpan.Clear();
|
||||||
|
|
||||||
|
process.CpuMemory.Read(messagePtr, reqDataSpan);
|
||||||
|
|
||||||
|
IpcMessage request = new IpcMessage(reqDataSpan, (long)messagePtr);
|
||||||
|
|
||||||
|
ArrayPool<byte>.Shared.Return(reqData);
|
||||||
|
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IpcMessage FillResponse(IpcMessage response, long result, byte[] data = null)
|
private void FillHipcResponse(IpcMessage response, long result)
|
||||||
|
{
|
||||||
|
FillHipcResponse(response, result, ReadOnlySpan<byte>.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FillHipcResponse(IpcMessage response, long result, int value)
|
||||||
|
{
|
||||||
|
Span<byte> span = stackalloc byte[sizeof(int)];
|
||||||
|
BinaryPrimitives.WriteInt32LittleEndian(span, value);
|
||||||
|
FillHipcResponse(response, result, span);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
response.Type = IpcMessageType.HipcResponse;
|
response.Type = IpcMessageType.HipcResponse;
|
||||||
|
|
||||||
using (MemoryStream ms = new MemoryStream())
|
_responseDataStream.SetLength(0);
|
||||||
{
|
|
||||||
BinaryWriter writer = new BinaryWriter(ms);
|
|
||||||
|
|
||||||
writer.Write(IpcMagic.Sfco);
|
_responseDataStream.Write(IpcMagic.Sfco);
|
||||||
writer.Write(result);
|
_responseDataStream.Write(result);
|
||||||
|
|
||||||
if (data != null)
|
_responseDataStream.Write(data);
|
||||||
{
|
|
||||||
writer.Write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
response.RawData = ms.ToArray();
|
response.RawData = _responseDataStream.ToArray();
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
@@ -372,6 +385,11 @@ namespace Ryujinx.HLE.HOS.Services
|
|||||||
|
|
||||||
_sessions.Clear();
|
_sessions.Clear();
|
||||||
|
|
||||||
|
_requestDataReader.Dispose();
|
||||||
|
_requestDataStream.Dispose();
|
||||||
|
_responseDataWriter.Dispose();
|
||||||
|
_responseDataStream.Dispose();
|
||||||
|
|
||||||
InitDone.Dispose();
|
InitDone.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ namespace Ryujinx.HLE.Loaders.Executables
|
|||||||
public uint DataSize { get; }
|
public uint DataSize { get; }
|
||||||
public uint BssSize { get; }
|
public uint BssSize { get; }
|
||||||
|
|
||||||
public int[] Capabilities { get; }
|
public uint[] Capabilities { get; }
|
||||||
public bool UsesSecureMemory { get; }
|
public bool UsesSecureMemory { get; }
|
||||||
public bool Is64BitAddressSpace { get; }
|
public bool Is64BitAddressSpace { get; }
|
||||||
public bool Is64Bit { get; }
|
public bool Is64Bit { get; }
|
||||||
@@ -57,11 +57,11 @@ namespace Ryujinx.HLE.Loaders.Executables
|
|||||||
Version = reader.Version;
|
Version = reader.Version;
|
||||||
Name = reader.Name.ToString();
|
Name = reader.Name.ToString();
|
||||||
|
|
||||||
Capabilities = new int[32];
|
Capabilities = new uint[32];
|
||||||
|
|
||||||
for (int index = 0; index < Capabilities.Length; index++)
|
for (int index = 0; index < Capabilities.Length; index++)
|
||||||
{
|
{
|
||||||
Capabilities[index] = (int)reader.Capabilities[index];
|
Capabilities[index] = reader.Capabilities[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.GetSegmentSize(KipReader.SegmentType.Data, out int uncompressedSize).ThrowIfFailure();
|
reader.GetSegmentSize(KipReader.SegmentType.Data, out int uncompressedSize).ThrowIfFailure();
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
using LibHac.Common;
|
using LibHac.Common;
|
||||||
|
using Microsoft.IO;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
@@ -77,7 +79,7 @@ namespace Ryujinx.HLE.Utilities
|
|||||||
ulong position = context.Request.PtrBuff[index].Position;
|
ulong position = context.Request.PtrBuff[index].Position;
|
||||||
ulong size = context.Request.PtrBuff[index].Size;
|
ulong size = context.Request.PtrBuff[index].Size;
|
||||||
|
|
||||||
using (MemoryStream ms = new MemoryStream())
|
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
while (size-- > 0)
|
while (size-- > 0)
|
||||||
{
|
{
|
||||||
@@ -91,7 +93,7 @@ namespace Ryujinx.HLE.Utilities
|
|||||||
ms.WriteByte(value);
|
ms.WriteByte(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Encoding.UTF8.GetString(ms.ToArray());
|
return Encoding.UTF8.GetString(ms.GetReadOnlySequence());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +112,7 @@ namespace Ryujinx.HLE.Utilities
|
|||||||
ulong position = context.Request.SendBuff[index].Position;
|
ulong position = context.Request.SendBuff[index].Position;
|
||||||
ulong size = context.Request.SendBuff[index].Size;
|
ulong size = context.Request.SendBuff[index].Size;
|
||||||
|
|
||||||
using (MemoryStream ms = new MemoryStream())
|
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
while (size-- > 0)
|
while (size-- > 0)
|
||||||
{
|
{
|
||||||
@@ -124,7 +126,7 @@ namespace Ryujinx.HLE.Utilities
|
|||||||
ms.WriteByte(value);
|
ms.WriteByte(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Encoding.UTF8.GetString(ms.ToArray());
|
return Encoding.UTF8.GetString(ms.GetReadOnlySequence());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,6 +3,7 @@ using Ryujinx.Common.Configuration.Hid;
|
|||||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||||
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
using Ryujinx.Common.Configuration.Hid.Controller.Motion;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Input.HLE;
|
using Ryujinx.Input.HLE;
|
||||||
using Ryujinx.Input.Motion.CemuHook.Protocol;
|
using Ryujinx.Input.Motion.CemuHook.Protocol;
|
||||||
using System;
|
using System;
|
||||||
@@ -381,7 +382,7 @@ namespace Ryujinx.Input.Motion.CemuHook
|
|||||||
|
|
||||||
Header header = GenerateHeader(clientId);
|
Header header = GenerateHeader(clientId);
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
{
|
{
|
||||||
writer.WriteStruct(header);
|
writer.WriteStruct(header);
|
||||||
@@ -421,7 +422,7 @@ namespace Ryujinx.Input.Motion.CemuHook
|
|||||||
|
|
||||||
Header header = GenerateHeader(clientId);
|
Header header = GenerateHeader(clientId);
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (BinaryWriter writer = new BinaryWriter(stream))
|
using (BinaryWriter writer = new BinaryWriter(stream))
|
||||||
{
|
{
|
||||||
writer.WriteStruct(header);
|
writer.WriteStruct(header);
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using Ryujinx.Memory.Range;
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Memory
|
namespace Ryujinx.Memory
|
||||||
@@ -77,6 +78,21 @@ namespace Ryujinx.Memory
|
|||||||
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||||
void Write(ulong va, ReadOnlySpan<byte> data);
|
void Write(ulong va, ReadOnlySpan<byte> data);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes data to CPU mapped memory, with write tracking.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address to write the data into</param>
|
||||||
|
/// <param name="data">Data to be written</param>
|
||||||
|
/// <exception cref="InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||||
|
public void Write(ulong va, ReadOnlySequence<byte> data)
|
||||||
|
{
|
||||||
|
foreach (ReadOnlyMemory<byte> segment in data)
|
||||||
|
{
|
||||||
|
Write(va, segment.Span);
|
||||||
|
va += (ulong)segment.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes data to the application process, returning false if the data was not changed.
|
/// Writes data to the application process, returning false if the data was not changed.
|
||||||
/// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
|
/// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date.
|
||||||
|
@@ -8,6 +8,8 @@ namespace Ryujinx.Memory.Range
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly struct MultiRange : IEquatable<MultiRange>
|
public readonly struct MultiRange : IEquatable<MultiRange>
|
||||||
{
|
{
|
||||||
|
private const ulong InvalidAddress = ulong.MaxValue;
|
||||||
|
|
||||||
private readonly MemoryRange _singleRange;
|
private readonly MemoryRange _singleRange;
|
||||||
private readonly MemoryRange[] _ranges;
|
private readonly MemoryRange[] _ranges;
|
||||||
|
|
||||||
@@ -107,7 +109,16 @@ namespace Ryujinx.Memory.Range
|
|||||||
else if (offset < range.Size)
|
else if (offset < range.Size)
|
||||||
{
|
{
|
||||||
ulong sliceSize = Math.Min(size, range.Size - offset);
|
ulong sliceSize = Math.Min(size, range.Size - offset);
|
||||||
ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
|
|
||||||
|
if (range.Address == InvalidAddress)
|
||||||
|
{
|
||||||
|
ranges.Add(new MemoryRange(range.Address, sliceSize));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
|
||||||
|
}
|
||||||
|
|
||||||
size -= sliceSize;
|
size -= sliceSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -6,6 +6,7 @@ using LibHac.FsSystem;
|
|||||||
using LibHac.Ncm;
|
using LibHac.Ncm;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using SixLabors.ImageSharp;
|
using SixLabors.ImageSharp;
|
||||||
@@ -136,8 +137,8 @@ namespace Ryujinx.Ui.Windows
|
|||||||
|
|
||||||
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
using (MemoryStream streamPng = new MemoryStream())
|
using (MemoryStream streamPng = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
file.Get.AsStream().CopyTo(stream);
|
file.Get.AsStream().CopyTo(stream);
|
||||||
|
|
||||||
@@ -169,7 +170,7 @@ namespace Ryujinx.Ui.Windows
|
|||||||
|
|
||||||
private byte[] ProcessImage(byte[] data)
|
private byte[] ProcessImage(byte[] data)
|
||||||
{
|
{
|
||||||
using (MemoryStream streamJpg = new MemoryStream())
|
using (MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
Image avatarImage = Image.Load(data, new PngDecoder());
|
Image avatarImage = Image.Load(data, new PngDecoder());
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Gtk;
|
using Gtk;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
@@ -181,7 +182,7 @@ namespace Ryujinx.Ui.Windows
|
|||||||
{
|
{
|
||||||
image.Mutate(x => x.Resize(256, 256));
|
image.Mutate(x => x.Resize(256, 256));
|
||||||
|
|
||||||
using (MemoryStream streamJpg = new MemoryStream())
|
using (MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
image.SaveAsJpeg(streamJpg);
|
image.SaveAsJpeg(streamJpg);
|
||||||
|
|
||||||
|
@@ -6,31 +6,31 @@ PUBLISH_DIRECTORY=$1
|
|||||||
OUTPUT_DIRECTORY=$2
|
OUTPUT_DIRECTORY=$2
|
||||||
ENTITLEMENTS_FILE_PATH=$3
|
ENTITLEMENTS_FILE_PATH=$3
|
||||||
|
|
||||||
APP_BUNDLE_DIRECTORY=$OUTPUT_DIRECTORY/Ryujinx.app
|
APP_BUNDLE_DIRECTORY="$OUTPUT_DIRECTORY/Ryujinx.app"
|
||||||
|
|
||||||
rm -rf $APP_BUNDLE_DIRECTORY
|
rm -rf "$APP_BUNDLE_DIRECTORY"
|
||||||
mkdir -p $APP_BUNDLE_DIRECTORY/Contents
|
mkdir -p "$APP_BUNDLE_DIRECTORY/Contents"
|
||||||
mkdir $APP_BUNDLE_DIRECTORY/Contents/Frameworks
|
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
|
||||||
mkdir $APP_BUNDLE_DIRECTORY/Contents/MacOS
|
mkdir "$APP_BUNDLE_DIRECTORY/Contents/MacOS"
|
||||||
mkdir $APP_BUNDLE_DIRECTORY/Contents/Resources
|
mkdir "$APP_BUNDLE_DIRECTORY/Contents/Resources"
|
||||||
|
|
||||||
# Copy executables first
|
# Copy executables first
|
||||||
cp $PUBLISH_DIRECTORY/Ryujinx.Ava $APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx
|
cp "$PUBLISH_DIRECTORY/Ryujinx.Ava" "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||||
chmod u+x $APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx
|
chmod u+x "$APP_BUNDLE_DIRECTORY/Contents/MacOS/Ryujinx"
|
||||||
|
|
||||||
# Then all libraries
|
# Then all libraries
|
||||||
cp $PUBLISH_DIRECTORY/*.dylib $APP_BUNDLE_DIRECTORY/Contents/Frameworks
|
cp "$PUBLISH_DIRECTORY"/*.dylib "$APP_BUNDLE_DIRECTORY/Contents/Frameworks"
|
||||||
|
|
||||||
# Then resources
|
# Then resources
|
||||||
cp Info.plist $APP_BUNDLE_DIRECTORY/Contents
|
cp Info.plist "$APP_BUNDLE_DIRECTORY/Contents"
|
||||||
cp Ryujinx.icns $APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns
|
cp Ryujinx.icns "$APP_BUNDLE_DIRECTORY/Contents/Resources/Ryujinx.icns"
|
||||||
cp updater.sh $APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh
|
cp updater.sh "$APP_BUNDLE_DIRECTORY/Contents/Resources/updater.sh"
|
||||||
cp -r $PUBLISH_DIRECTORY/THIRDPARTY.md $APP_BUNDLE_DIRECTORY/Contents/Resources
|
cp -r "$PUBLISH_DIRECTORY/THIRDPARTY.md" "$APP_BUNDLE_DIRECTORY/Contents/Resources"
|
||||||
|
|
||||||
echo -n "APPL????" > $APP_BUNDLE_DIRECTORY/Contents/PkgInfo
|
echo -n "APPL????" > "$APP_BUNDLE_DIRECTORY/Contents/PkgInfo"
|
||||||
|
|
||||||
# Fixup libraries and executable
|
# Fixup libraries and executable
|
||||||
python3 bundle_fix_up.py $APP_BUNDLE_DIRECTORY MacOS/Ryujinx
|
python3 bundle_fix_up.py "$APP_BUNDLE_DIRECTORY" MacOS/Ryujinx
|
||||||
|
|
||||||
# Now sign it
|
# Now sign it
|
||||||
if ! [ -x "$(command -v codesign)" ];
|
if ! [ -x "$(command -v codesign)" ];
|
||||||
@@ -44,9 +44,9 @@ then
|
|||||||
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
|
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
|
||||||
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
|
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
|
||||||
echo "Usign rcodesign for ad-hoc signing"
|
echo "Usign rcodesign for ad-hoc signing"
|
||||||
rcodesign sign --entitlements-xml-path $ENTITLEMENTS_FILE_PATH $APP_BUNDLE_DIRECTORY
|
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$APP_BUNDLE_DIRECTORY"
|
||||||
else
|
else
|
||||||
echo "Usign codesign for ad-hoc signing"
|
echo "Usign codesign for ad-hoc signing"
|
||||||
codesign --entitlements $ENTITLEMENTS_FILE_PATH -f --deep -s - $APP_BUNDLE_DIRECTORY
|
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$APP_BUNDLE_DIRECTORY"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@@ -7,54 +7,54 @@ if [ "$#" -ne 6 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p $1
|
mkdir -p "$1"
|
||||||
mkdir -p $2
|
mkdir -p "$2"
|
||||||
mkdir -p $3
|
mkdir -p "$3"
|
||||||
|
|
||||||
BASE_DIR=$(readlink -f $1)
|
BASE_DIR=$(readlink -f "$1")
|
||||||
TEMP_DIRECTORY=$(readlink -f $2)
|
TEMP_DIRECTORY=$(readlink -f "$2")
|
||||||
OUTPUT_DIRECTORY=$(readlink -f $3)
|
OUTPUT_DIRECTORY=$(readlink -f "$3")
|
||||||
ENTITLEMENTS_FILE_PATH=$(readlink -f $4)
|
ENTITLEMENTS_FILE_PATH=$(readlink -f "$4")
|
||||||
VERSION=$5
|
VERSION=$5
|
||||||
SOURCE_REVISION_ID=$6
|
SOURCE_REVISION_ID=$6
|
||||||
|
|
||||||
RELEASE_TAR_FILE_NAME=Ryujinx-$VERSION-macos_universal.app.tar
|
RELEASE_TAR_FILE_NAME=Ryujinx-$VERSION-macos_universal.app.tar
|
||||||
ARM64_APP_BUNDLE=$TEMP_DIRECTORY/output_arm64/Ryujinx.app
|
ARM64_APP_BUNDLE="$TEMP_DIRECTORY/output_arm64/Ryujinx.app"
|
||||||
X64_APP_BUNDLE=$TEMP_DIRECTORY/output_x64/Ryujinx.app
|
X64_APP_BUNDLE="$TEMP_DIRECTORY/output_x64/Ryujinx.app"
|
||||||
UNIVERSAL_APP_BUNDLE=$OUTPUT_DIRECTORY/Ryujinx.app
|
UNIVERSAL_APP_BUNDLE="$OUTPUT_DIRECTORY/Ryujinx.app"
|
||||||
EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
|
EXECUTABLE_SUB_PATH=Contents/MacOS/Ryujinx
|
||||||
|
|
||||||
rm -rf $TEMP_DIRECTORY
|
rm -rf "$TEMP_DIRECTORY"
|
||||||
mkdir -p $TEMP_DIRECTORY
|
mkdir -p "$TEMP_DIRECTORY"
|
||||||
|
|
||||||
DOTNET_COMMON_ARGS="-p:DebugType=embedded -p:Version=$VERSION -p:SourceRevisionId=$SOURCE_REVISION_ID --self-contained true"
|
DOTNET_COMMON_ARGS="-p:DebugType=embedded -p:Version=$VERSION -p:SourceRevisionId=$SOURCE_REVISION_ID --self-contained true"
|
||||||
|
|
||||||
dotnet restore
|
dotnet restore
|
||||||
dotnet build -c Release Ryujinx.Ava
|
dotnet build -c Release Ryujinx.Ava
|
||||||
dotnet publish -c Release -r osx-arm64 -o $TEMP_DIRECTORY/publish_arm64 $DOTNET_COMMON_ARGS Ryujinx.Ava
|
dotnet publish -c Release -r osx-arm64 -o "$TEMP_DIRECTORY/publish_arm64" $DOTNET_COMMON_ARGS Ryujinx.Ava
|
||||||
dotnet publish -c Release -r osx-x64 -o $TEMP_DIRECTORY/publish_x64 $DOTNET_COMMON_ARGS Ryujinx.Ava
|
dotnet publish -c Release -r osx-x64 -o "$TEMP_DIRECTORY/publish_x64" $DOTNET_COMMON_ARGS Ryujinx.Ava
|
||||||
|
|
||||||
# Get ride of the support library for ARMeilleur for x64 (that's only for arm64)
|
# Get ride of the support library for ARMeilleur for x64 (that's only for arm64)
|
||||||
rm -rf $TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib
|
rm -rf "$TEMP_DIRECTORY/publish_x64/libarmeilleure-jitsupport.dylib"
|
||||||
|
|
||||||
# Get ride of libsoundio from arm64 builds as we don't have a arm64 variant
|
# Get ride of libsoundio from arm64 builds as we don't have a arm64 variant
|
||||||
# TODO: remove this once done
|
# TODO: remove this once done
|
||||||
rm -rf $TEMP_DIRECTORY/publish_arm64/libsoundio.dylib
|
rm -rf "$TEMP_DIRECTORY/publish_arm64/libsoundio.dylib"
|
||||||
|
|
||||||
pushd $BASE_DIR/distribution/macos
|
pushd "$BASE_DIR/distribution/macos"
|
||||||
./create_app_bundle.sh $TEMP_DIRECTORY/publish_x64 $TEMP_DIRECTORY/output_x64 $ENTITLEMENTS_FILE_PATH
|
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_x64" "$TEMP_DIRECTORY/output_x64" "$ENTITLEMENTS_FILE_PATH"
|
||||||
./create_app_bundle.sh $TEMP_DIRECTORY/publish_arm64 $TEMP_DIRECTORY/output_arm64 $ENTITLEMENTS_FILE_PATH
|
./create_app_bundle.sh "$TEMP_DIRECTORY/publish_arm64" "$TEMP_DIRECTORY/output_arm64" "$ENTITLEMENTS_FILE_PATH"
|
||||||
popd
|
popd
|
||||||
|
|
||||||
rm -rf $UNIVERSAL_APP_BUNDLE
|
rm -rf "$UNIVERSAL_APP_BUNDLE"
|
||||||
mkdir -p $OUTPUT_DIRECTORY
|
mkdir -p "$OUTPUT_DIRECTORY"
|
||||||
|
|
||||||
# Let's copy one of the two different app bundle and remove the executable
|
# Let's copy one of the two different app bundle and remove the executable
|
||||||
cp -R $ARM64_APP_BUNDLE $UNIVERSAL_APP_BUNDLE
|
cp -R "$ARM64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE"
|
||||||
rm $UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH
|
rm "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH"
|
||||||
|
|
||||||
# Make it libraries universal
|
# Make it libraries universal
|
||||||
python3 $BASE_DIR/distribution/macos/construct_universal_dylib.py $ARM64_APP_BUNDLE $X64_APP_BUNDLE $UNIVERSAL_APP_BUNDLE "**/*.dylib"
|
python3 "$BASE_DIR/distribution/macos/construct_universal_dylib.py" "$ARM64_APP_BUNDLE" "$X64_APP_BUNDLE" "$UNIVERSAL_APP_BUNDLE" "**/*.dylib"
|
||||||
|
|
||||||
if ! [ -x "$(command -v lipo)" ];
|
if ! [ -x "$(command -v lipo)" ];
|
||||||
then
|
then
|
||||||
@@ -69,12 +69,12 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Make it the executable universal
|
# Make it the executable universal
|
||||||
$LIPO $ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH $X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH -output $UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH -create
|
$LIPO "$ARM64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" "$X64_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -output "$UNIVERSAL_APP_BUNDLE/$EXECUTABLE_SUB_PATH" -create
|
||||||
|
|
||||||
# Patch up the Info.plist to have appropriate version
|
# Patch up the Info.plist to have appropriate version
|
||||||
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" $UNIVERSAL_APP_BUNDLE/Contents/Info.plist
|
sed -r -i.bck "s/\%\%RYUJINX_BUILD_VERSION\%\%/$VERSION/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
|
||||||
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" $UNIVERSAL_APP_BUNDLE/Contents/Info.plist
|
sed -r -i.bck "s/\%\%RYUJINX_BUILD_GIT_HASH\%\%/$SOURCE_REVISION_ID/g;" "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist"
|
||||||
rm $UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck
|
rm "$UNIVERSAL_APP_BUNDLE/Contents/Info.plist.bck"
|
||||||
|
|
||||||
# Now sign it
|
# Now sign it
|
||||||
if ! [ -x "$(command -v codesign)" ];
|
if ! [ -x "$(command -v codesign)" ];
|
||||||
@@ -88,16 +88,16 @@ then
|
|||||||
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
|
# NOTE: Currently require https://github.com/indygreg/apple-platform-rs/pull/44 to work on other OSes.
|
||||||
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
|
# cargo install --git "https://github.com/marysaka/apple-platform-rs" --branch "fix/adhoc-app-bundle" apple-codesign --bin "rcodesign"
|
||||||
echo "Usign rcodesign for ad-hoc signing"
|
echo "Usign rcodesign for ad-hoc signing"
|
||||||
rcodesign sign --entitlements-xml-path $ENTITLEMENTS_FILE_PATH $UNIVERSAL_APP_BUNDLE
|
rcodesign sign --entitlements-xml-path "$ENTITLEMENTS_FILE_PATH" "$UNIVERSAL_APP_BUNDLE"
|
||||||
else
|
else
|
||||||
echo "Usign codesign for ad-hoc signing"
|
echo "Usign codesign for ad-hoc signing"
|
||||||
codesign --entitlements $ENTITLEMENTS_FILE_PATH -f --deep -s - $UNIVERSAL_APP_BUNDLE
|
codesign --entitlements "$ENTITLEMENTS_FILE_PATH" -f --deep -s - "$UNIVERSAL_APP_BUNDLE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Creating archive"
|
echo "Creating archive"
|
||||||
pushd $OUTPUT_DIRECTORY
|
pushd "$OUTPUT_DIRECTORY"
|
||||||
tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf $RELEASE_TAR_FILE_NAME Ryujinx.app 1> /dev/null
|
tar --exclude "Ryujinx.app/Contents/MacOS/Ryujinx" -cvf $RELEASE_TAR_FILE_NAME Ryujinx.app 1> /dev/null
|
||||||
python3 $BASE_DIR/distribution/misc/add_tar_exec.py $RELEASE_TAR_FILE_NAME "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
|
python3 "$BASE_DIR/distribution/misc/add_tar_exec.py" $RELEASE_TAR_FILE_NAME "Ryujinx.app/Contents/MacOS/Ryujinx" "Ryujinx.app/Contents/MacOS/Ryujinx"
|
||||||
gzip -9 < $RELEASE_TAR_FILE_NAME > $RELEASE_TAR_FILE_NAME.gz
|
gzip -9 < $RELEASE_TAR_FILE_NAME > $RELEASE_TAR_FILE_NAME.gz
|
||||||
rm $RELEASE_TAR_FILE_NAME
|
rm $RELEASE_TAR_FILE_NAME
|
||||||
popd
|
popd
|
||||||
|
Reference in New Issue
Block a user