Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
f449895e6d | |||
410be95ab6 | |||
cff9046fc7 | |||
86fd0643c2 | |||
43a83a401e | |||
f0e27a23a5 | |||
e68650237d | |||
1faff14e73 | |||
784cf9d594 | |||
64263c5218 | |||
065c4e520d | |||
139a930407 | |||
719dc97bbd |
@ -4,5 +4,7 @@
|
|||||||
{
|
{
|
||||||
IJitMemoryBlock Allocate(ulong size);
|
IJitMemoryBlock Allocate(ulong size);
|
||||||
IJitMemoryBlock Reserve(ulong size);
|
IJitMemoryBlock Reserve(ulong size);
|
||||||
|
|
||||||
|
ulong GetPageSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,8 @@ namespace ARMeilleure.Signal
|
|||||||
|
|
||||||
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
private const uint EXCEPTION_ACCESS_VIOLATION = 0xc0000005;
|
||||||
|
|
||||||
private static ulong _pageSize = GetPageSize();
|
private static ulong _pageSize;
|
||||||
private static ulong _pageMask = _pageSize - 1;
|
private static ulong _pageMask;
|
||||||
|
|
||||||
private static IntPtr _handlerConfig;
|
private static IntPtr _handlerConfig;
|
||||||
private static IntPtr _signalHandlerPtr;
|
private static IntPtr _signalHandlerPtr;
|
||||||
@ -81,19 +81,6 @@ namespace ARMeilleure.Signal
|
|||||||
private static readonly object _lock = new object();
|
private static readonly object _lock = new object();
|
||||||
private static bool _initialized;
|
private static bool _initialized;
|
||||||
|
|
||||||
private static ulong GetPageSize()
|
|
||||||
{
|
|
||||||
// TODO: This needs to be based on the current memory manager configuration.
|
|
||||||
if (OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
|
||||||
{
|
|
||||||
return 1UL << 14;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 1UL << 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static NativeSignalHandler()
|
static NativeSignalHandler()
|
||||||
{
|
{
|
||||||
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
|
_handlerConfig = Marshal.AllocHGlobal(Unsafe.SizeOf<SignalHandlerConfig>());
|
||||||
@ -102,12 +89,12 @@ namespace ARMeilleure.Signal
|
|||||||
config = new SignalHandlerConfig();
|
config = new SignalHandlerConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitializeJitCache(IJitMemoryAllocator allocator)
|
public static void Initialize(IJitMemoryAllocator allocator)
|
||||||
{
|
{
|
||||||
JitCache.Initialize(allocator);
|
JitCache.Initialize(allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitializeSignalHandler(Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
|
||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
|
|
||||||
@ -115,6 +102,9 @@ namespace ARMeilleure.Signal
|
|||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
|
|
||||||
|
_pageSize = pageSize;
|
||||||
|
_pageMask = pageSize - 1;
|
||||||
|
|
||||||
ref SignalHandlerConfig config = ref GetConfigRef();
|
ref SignalHandlerConfig config = ref GetConfigRef();
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||||
|
@ -81,7 +81,7 @@ namespace ARMeilleure.Translation
|
|||||||
|
|
||||||
if (memory.Type.IsHostMapped())
|
if (memory.Type.IsHostMapped())
|
||||||
{
|
{
|
||||||
NativeSignalHandler.InitializeSignalHandler();
|
NativeSignalHandler.InitializeSignalHandler(allocator.GetPageSize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,9 +12,9 @@ using Ryujinx.Audio.Integration;
|
|||||||
using Ryujinx.Ava.Common;
|
using Ryujinx.Ava.Common;
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.Input;
|
using Ryujinx.Ava.Input;
|
||||||
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.Ava.UI.Renderer;
|
||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
@ -44,7 +44,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.Versioning;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
@ -66,8 +65,8 @@ namespace Ryujinx.Ava
|
|||||||
private const float VolumeDelta = 0.05f;
|
private const float VolumeDelta = 0.05f;
|
||||||
|
|
||||||
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
private static readonly Cursor InvisibleCursor = new(StandardCursorType.None);
|
||||||
private readonly IntPtr InvisibleCursorWin;
|
private readonly IntPtr InvisibleCursorWin;
|
||||||
private readonly IntPtr DefaultCursorWin;
|
private readonly IntPtr DefaultCursorWin;
|
||||||
|
|
||||||
private readonly long _ticksPerFrame;
|
private readonly long _ticksPerFrame;
|
||||||
private readonly Stopwatch _chrono;
|
private readonly Stopwatch _chrono;
|
||||||
@ -80,6 +79,7 @@ namespace Ryujinx.Ava
|
|||||||
private readonly MainWindowViewModel _viewModel;
|
private readonly MainWindowViewModel _viewModel;
|
||||||
private readonly IKeyboard _keyboardInterface;
|
private readonly IKeyboard _keyboardInterface;
|
||||||
private readonly TopLevel _topLevel;
|
private readonly TopLevel _topLevel;
|
||||||
|
public RendererHost _rendererHost;
|
||||||
|
|
||||||
private readonly GraphicsDebugLevel _glLogLevel;
|
private readonly GraphicsDebugLevel _glLogLevel;
|
||||||
private float _newVolume;
|
private float _newVolume;
|
||||||
@ -105,7 +105,6 @@ namespace Ryujinx.Ava
|
|||||||
public event EventHandler AppExit;
|
public event EventHandler AppExit;
|
||||||
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
public event EventHandler<StatusUpdatedEventArgs> StatusUpdatedEvent;
|
||||||
|
|
||||||
public RendererHost Renderer { get; }
|
|
||||||
public VirtualFileSystem VirtualFileSystem { get; }
|
public VirtualFileSystem VirtualFileSystem { get; }
|
||||||
public ContentManager ContentManager { get; }
|
public ContentManager ContentManager { get; }
|
||||||
public NpadManager NpadManager { get; }
|
public NpadManager NpadManager { get; }
|
||||||
@ -117,7 +116,6 @@ namespace Ryujinx.Ava
|
|||||||
public string ApplicationPath { get; private set; }
|
public string ApplicationPath { get; private set; }
|
||||||
public bool ScreenshotRequested { get; set; }
|
public bool ScreenshotRequested { get; set; }
|
||||||
|
|
||||||
|
|
||||||
public AppHost(
|
public AppHost(
|
||||||
RendererHost renderer,
|
RendererHost renderer,
|
||||||
InputManager inputManager,
|
InputManager inputManager,
|
||||||
@ -144,11 +142,12 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
NpadManager = _inputManager.CreateNpadManager();
|
NpadManager = _inputManager.CreateNpadManager();
|
||||||
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
||||||
Renderer = renderer;
|
|
||||||
ApplicationPath = applicationPath;
|
ApplicationPath = applicationPath;
|
||||||
VirtualFileSystem = virtualFileSystem;
|
VirtualFileSystem = virtualFileSystem;
|
||||||
ContentManager = contentManager;
|
ContentManager = contentManager;
|
||||||
|
|
||||||
|
_rendererHost = renderer;
|
||||||
|
|
||||||
_chrono = new Stopwatch();
|
_chrono = new Stopwatch();
|
||||||
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
_ticksPerFrame = Stopwatch.Frequency / TargetFps;
|
||||||
|
|
||||||
@ -183,10 +182,10 @@ namespace Ryujinx.Ava
|
|||||||
{
|
{
|
||||||
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
_lastCursorMoveTime = Stopwatch.GetTimestamp();
|
||||||
|
|
||||||
if ((Renderer.Content as EmbeddedWindow).TransformedBounds != null)
|
if (_rendererHost.EmbeddedWindow.TransformedBounds != null)
|
||||||
{
|
{
|
||||||
var point = e.GetCurrentPoint(window).Position;
|
var point = e.GetCurrentPoint(window).Position;
|
||||||
var bounds = (Renderer.Content as EmbeddedWindow).TransformedBounds.Value.Clip;
|
var bounds = _rendererHost.EmbeddedWindow.TransformedBounds.Value.Clip;
|
||||||
|
|
||||||
_isCursorInRenderer = point.X >= bounds.X &&
|
_isCursorInRenderer = point.X >= bounds.X &&
|
||||||
point.X <= bounds.Width + bounds.X &&
|
point.X <= bounds.Width + bounds.X &&
|
||||||
@ -318,7 +317,7 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_viewModel.SetUIProgressHandlers(Device);
|
_viewModel.SetUIProgressHandlers(Device);
|
||||||
|
|
||||||
Renderer.SizeChanged += Window_SizeChanged;
|
_rendererHost.SizeChanged += Window_SizeChanged;
|
||||||
|
|
||||||
_isActive = true;
|
_isActive = true;
|
||||||
|
|
||||||
@ -430,11 +429,11 @@ namespace Ryujinx.Ava
|
|||||||
_windowsMultimediaTimerResolution = null;
|
_windowsMultimediaTimerResolution = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer?.MakeCurrent();
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent();
|
||||||
|
|
||||||
Device.DisposeGpu();
|
Device.DisposeGpu();
|
||||||
|
|
||||||
Renderer?.MakeCurrent(null);
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
private void HideCursorState_Changed(object sender, ReactiveEventArgs<bool> state)
|
||||||
@ -635,11 +634,12 @@ namespace Ryujinx.Ava
|
|||||||
// Initialize Renderer.
|
// Initialize Renderer.
|
||||||
IRenderer renderer;
|
IRenderer renderer;
|
||||||
|
|
||||||
if (Renderer.IsVulkan)
|
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
|
||||||
{
|
{
|
||||||
string preferredGpu = ConfigurationState.Instance.Graphics.PreferredGpu.Value;
|
renderer = new VulkanRenderer(
|
||||||
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowVulkan).CreateSurface,
|
||||||
renderer = new VulkanRenderer(Renderer.CreateVulkanSurface, VulkanHelper.GetRequiredInstanceExtensions, preferredGpu);
|
VulkanHelper.GetRequiredInstanceExtensions,
|
||||||
|
ConfigurationState.Instance.Graphics.PreferredGpu.Value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -787,14 +787,12 @@ namespace Ryujinx.Ava
|
|||||||
|
|
||||||
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
_renderer.ScreenCaptured += Renderer_ScreenCaptured;
|
||||||
|
|
||||||
(_renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Renderer.GetContext()));
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.InitializeBackgroundContext(_renderer);
|
||||||
|
|
||||||
Renderer.MakeCurrent();
|
|
||||||
|
|
||||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||||
|
|
||||||
Width = (int)Renderer.Bounds.Width;
|
Width = (int)_rendererHost.Bounds.Width;
|
||||||
Height = (int)Renderer.Bounds.Height;
|
Height = (int)_rendererHost.Bounds.Height;
|
||||||
|
|
||||||
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
|
_renderer.Window.SetSize((int)(Width * _topLevel.PlatformImpl.RenderScaling), (int)(Height * _topLevel.PlatformImpl.RenderScaling));
|
||||||
|
|
||||||
@ -827,7 +825,7 @@ namespace Ryujinx.Ava
|
|||||||
_viewModel.SwitchToRenderer(false);
|
_viewModel.SwitchToRenderer(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
Device.PresentFrame(() => Renderer?.SwapBuffers());
|
Device.PresentFrame(() => (_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.SwapBuffers());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ticks >= _ticksPerFrame)
|
if (_ticks >= _ticksPerFrame)
|
||||||
@ -837,7 +835,7 @@ namespace Ryujinx.Ava
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Renderer?.MakeCurrent(null);
|
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateStatus()
|
public void UpdateStatus()
|
||||||
@ -853,7 +851,7 @@ namespace Ryujinx.Ava
|
|||||||
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
StatusUpdatedEvent?.Invoke(this, new StatusUpdatedEventArgs(
|
||||||
Device.EnableDeviceVsync,
|
Device.EnableDeviceVsync,
|
||||||
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
|
LocaleManager.Instance[LocaleKeys.VolumeShort] + $": {(int)(Device.GetVolume() * 100)}%",
|
||||||
Renderer.IsVulkan ? "Vulkan" : "OpenGL",
|
ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan ? "Vulkan" : "OpenGL",
|
||||||
dockedMode,
|
dockedMode,
|
||||||
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
ConfigurationState.Instance.Graphics.AspectRatio.Value.ToText(),
|
||||||
LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
LocaleManager.Instance[LocaleKeys.Game] + $": {Device.Statistics.GetGameFrameRate():00.00} FPS ({Device.Statistics.GetGameFrameTime():00.00} ms)",
|
||||||
|
@ -524,7 +524,7 @@
|
|||||||
"UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!",
|
"UserErrorUndefinedDescription": "An undefined error occured! This shouldn't happen, please contact a dev!",
|
||||||
"OpenSetupGuideMessage": "Open the Setup Guide",
|
"OpenSetupGuideMessage": "Open the Setup Guide",
|
||||||
"NoUpdate": "No Update",
|
"NoUpdate": "No Update",
|
||||||
"TitleUpdateVersionLabel": "Version {0} - {1}",
|
"TitleUpdateVersionLabel": "Version {0}",
|
||||||
"RyujinxInfo": "Ryujinx - Info",
|
"RyujinxInfo": "Ryujinx - Info",
|
||||||
"RyujinxConfirm": "Ryujinx - Confirmation",
|
"RyujinxConfirm": "Ryujinx - Confirmation",
|
||||||
"FileDialogAllTypes": "All types",
|
"FileDialogAllTypes": "All types",
|
||||||
@ -585,7 +585,7 @@
|
|||||||
"UserProfilesSetProfileImage": "Set Profile Image",
|
"UserProfilesSetProfileImage": "Set Profile Image",
|
||||||
"UserProfileEmptyNameError": "Name is required",
|
"UserProfileEmptyNameError": "Name is required",
|
||||||
"UserProfileNoImageError": "Profile image must be set",
|
"UserProfileNoImageError": "Profile image must be set",
|
||||||
"GameUpdateWindowHeading": "{0} Update(s) available for {1} ({2})",
|
"GameUpdateWindowHeading": "Manage Updates for {0} ({1})",
|
||||||
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
"SettingsTabHotkeysResScaleUpHotkey": "Increase resolution:",
|
||||||
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
"SettingsTabHotkeysResScaleDownHotkey": "Decrease resolution:",
|
||||||
"UserProfilesName": "Name:",
|
"UserProfilesName": "Name:",
|
||||||
|
@ -136,7 +136,7 @@ namespace Ryujinx.Ava.UI.Applet
|
|||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
_hiddenTextBox.Clear();
|
_hiddenTextBox.Clear();
|
||||||
_parent.ViewModel.RendererControl.Focus();
|
_parent.ViewModel.RendererHostControl.Focus();
|
||||||
|
|
||||||
_parent = null;
|
_parent = null;
|
||||||
});
|
});
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
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:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
||||||
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:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
|
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
mc:Ignorable="d"
|
Focusable="True"
|
||||||
Focusable="True">
|
mc:Ignorable="d">
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
<helpers:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||||
@ -130,7 +130,8 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="10" />
|
<ColumnDefinition Width="10" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="150" />
|
||||||
|
<ColumnDefinition Width="100" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<Image
|
<Image
|
||||||
Grid.RowSpan="3"
|
Grid.RowSpan="3"
|
||||||
@ -141,30 +142,54 @@
|
|||||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||||
<StackPanel
|
<Border
|
||||||
Grid.Column="2"
|
Grid.Column="2"
|
||||||
|
Margin="0,0,5,0"
|
||||||
|
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
||||||
|
BorderThickness="0,0,1,0">
|
||||||
|
<StackPanel
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Orientation="Vertical"
|
||||||
|
Spacing="5">
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Text="{Binding TitleName}"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding Developer}"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Text="{Binding Version}"
|
||||||
|
TextAlignment="Left"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="3"
|
||||||
|
Margin="10,0,0,0"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
Spacing="5" >
|
Spacing="5">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding TitleName}"
|
Text="{Binding TitleId}"
|
||||||
TextAlignment="Left"
|
TextAlignment="Left"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Text="{Binding Developer}"
|
Text="{Binding FileExtension}"
|
||||||
TextAlignment="Left"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
<TextBlock
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
Text="{Binding Version}"
|
|
||||||
TextAlignment="Left"
|
TextAlignment="Left"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Column="3"
|
Grid.Column="4"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
VerticalAlignment="Top"
|
VerticalAlignment="Top"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Markup.Xaml;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
|
||||||
using Ryujinx.Common.Configuration;
|
|
||||||
using Silk.NET.Vulkan;
|
|
||||||
using SPB.Graphics.OpenGL;
|
|
||||||
using SPB.Windowing;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
|
||||||
{
|
|
||||||
public partial class RendererHost : UserControl, IDisposable
|
|
||||||
{
|
|
||||||
private readonly GraphicsDebugLevel _graphicsDebugLevel;
|
|
||||||
private EmbeddedWindow _currentWindow;
|
|
||||||
|
|
||||||
public bool IsVulkan { get; private set; }
|
|
||||||
|
|
||||||
public RendererHost(GraphicsDebugLevel graphicsDebugLevel)
|
|
||||||
{
|
|
||||||
_graphicsDebugLevel = graphicsDebugLevel;
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public RendererHost()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CreateOpenGL()
|
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
|
|
||||||
_currentWindow = new OpenGLEmbeddedWindow(3, 3, _graphicsDebugLevel);
|
|
||||||
Initialize();
|
|
||||||
|
|
||||||
IsVulkan = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Initialize()
|
|
||||||
{
|
|
||||||
_currentWindow.WindowCreated += CurrentWindow_WindowCreated;
|
|
||||||
_currentWindow.SizeChanged += CurrentWindow_SizeChanged;
|
|
||||||
Content = _currentWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CreateVulkan()
|
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
|
|
||||||
_currentWindow = new VulkanEmbeddedWindow();
|
|
||||||
Initialize();
|
|
||||||
|
|
||||||
IsVulkan = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OpenGLContextBase GetContext()
|
|
||||||
{
|
|
||||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
|
||||||
{
|
|
||||||
return openGlEmbeddedWindow.Context;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnDetachedFromVisualTree(e);
|
|
||||||
|
|
||||||
Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CurrentWindow_SizeChanged(object sender, Size e)
|
|
||||||
{
|
|
||||||
SizeChanged?.Invoke(sender, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
|
|
||||||
{
|
|
||||||
RendererInitialized?.Invoke(this, EventArgs.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MakeCurrent()
|
|
||||||
{
|
|
||||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
|
||||||
{
|
|
||||||
openGlEmbeddedWindow.MakeCurrent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void MakeCurrent(SwappableNativeWindowBase window)
|
|
||||||
{
|
|
||||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
|
||||||
{
|
|
||||||
openGlEmbeddedWindow.MakeCurrent(window);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SwapBuffers()
|
|
||||||
{
|
|
||||||
if (_currentWindow is OpenGLEmbeddedWindow openGlEmbeddedWindow)
|
|
||||||
{
|
|
||||||
openGlEmbeddedWindow.SwapBuffers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public event EventHandler<EventArgs> RendererInitialized;
|
|
||||||
public event Action<object, Size> SizeChanged;
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (_currentWindow != null)
|
|
||||||
{
|
|
||||||
_currentWindow.WindowCreated -= CurrentWindow_WindowCreated;
|
|
||||||
_currentWindow.SizeChanged -= CurrentWindow_SizeChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SurfaceKHR CreateVulkanSurface(Instance instance, Vk api)
|
|
||||||
{
|
|
||||||
return (_currentWindow is VulkanEmbeddedWindow vulkanEmbeddedWindow)
|
|
||||||
? vulkanEmbeddedWindow.CreateSurface(instance)
|
|
||||||
: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using SPB.Graphics;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
[SupportedOSPlatform("linux")]
|
|
||||||
internal class AvaloniaGlxContext : SPB.Platform.GLX.GLXOpenGLContext
|
|
||||||
{
|
|
||||||
public AvaloniaGlxContext(IntPtr handle)
|
|
||||||
: base(FramebufferFormat.Default, 0, 0, 0, false, null)
|
|
||||||
{
|
|
||||||
ContextHandle = handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
using SPB.Graphics;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
internal class AvaloniaWglContext : SPB.Platform.WGL.WGLOpenGLContext
|
|
||||||
{
|
|
||||||
public AvaloniaWglContext(IntPtr handle)
|
|
||||||
: base(FramebufferFormat.Default, 0, 0, 0, false, null)
|
|
||||||
{
|
|
||||||
ContextHandle = handle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,235 +0,0 @@
|
|||||||
using Avalonia;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
using Avalonia.Input;
|
|
||||||
using Avalonia.Platform;
|
|
||||||
using SPB.Graphics;
|
|
||||||
using SPB.Platform;
|
|
||||||
using SPB.Platform.GLX;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
public class EmbeddedWindow : NativeControlHost
|
|
||||||
{
|
|
||||||
private WindowProc _wndProcDelegate;
|
|
||||||
private string _className;
|
|
||||||
|
|
||||||
protected GLXWindow X11Window { get; set; }
|
|
||||||
protected IntPtr WindowHandle { get; set; }
|
|
||||||
protected IntPtr X11Display { get; set; }
|
|
||||||
protected IntPtr NsView { get; set; }
|
|
||||||
protected IntPtr MetalLayer { get; set; }
|
|
||||||
|
|
||||||
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
|
||||||
|
|
||||||
public event EventHandler<IntPtr> WindowCreated;
|
|
||||||
public event EventHandler<Size> SizeChanged;
|
|
||||||
|
|
||||||
protected virtual void OnWindowDestroyed() { }
|
|
||||||
protected virtual void OnWindowDestroying()
|
|
||||||
{
|
|
||||||
WindowHandle = IntPtr.Zero;
|
|
||||||
X11Display = IntPtr.Zero;
|
|
||||||
NsView = IntPtr.Zero;
|
|
||||||
MetalLayer = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EmbeddedWindow()
|
|
||||||
{
|
|
||||||
var stateObserverable = this.GetObservable(BoundsProperty);
|
|
||||||
|
|
||||||
stateObserverable.Subscribe(StateChanged);
|
|
||||||
|
|
||||||
Initialized += NativeEmbeddedWindow_Initialized;
|
|
||||||
}
|
|
||||||
|
|
||||||
public virtual void OnWindowCreated() { }
|
|
||||||
|
|
||||||
private void NativeEmbeddedWindow_Initialized(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
OnWindowCreated();
|
|
||||||
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
WindowCreated?.Invoke(this, WindowHandle);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void StateChanged(Rect rect)
|
|
||||||
{
|
|
||||||
SizeChanged?.Invoke(this, rect.Size);
|
|
||||||
_updateBoundsCallback?.Invoke(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
return CreateLinux(parent);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
return CreateWin32(parent);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
return CreateMacOs(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.CreateNativeControlCore(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
|
||||||
{
|
|
||||||
OnWindowDestroying();
|
|
||||||
|
|
||||||
if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
DestroyLinux();
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
DestroyWin32(control);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
DestroyMacOS();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
base.DestroyNativeControlCore(control);
|
|
||||||
}
|
|
||||||
|
|
||||||
OnWindowDestroyed();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
|
||||||
protected virtual IPlatformHandle CreateLinux(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
|
||||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
|
||||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
|
||||||
|
|
||||||
return new PlatformHandle(WindowHandle, "X11");
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
IPlatformHandle CreateWin32(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
_className = "NativeWindow-" + Guid.NewGuid();
|
|
||||||
_wndProcDelegate = WndProc;
|
|
||||||
var wndClassEx = new WNDCLASSEX
|
|
||||||
{
|
|
||||||
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
|
||||||
hInstance = GetModuleHandle(null),
|
|
||||||
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
|
||||||
style = ClassStyles.CS_OWNDC,
|
|
||||||
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
|
||||||
hCursor = CreateArrowCursor()
|
|
||||||
};
|
|
||||||
|
|
||||||
var atom = RegisterClassEx(ref wndClassEx);
|
|
||||||
|
|
||||||
var handle = CreateWindowEx(
|
|
||||||
0,
|
|
||||||
_className,
|
|
||||||
"NativeWindow",
|
|
||||||
WindowStyles.WS_CHILD,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
640,
|
|
||||||
480,
|
|
||||||
parent.Handle,
|
|
||||||
IntPtr.Zero,
|
|
||||||
IntPtr.Zero,
|
|
||||||
IntPtr.Zero);
|
|
||||||
|
|
||||||
WindowHandle = handle;
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
|
||||||
|
|
||||||
return new PlatformHandle(WindowHandle, "HWND");
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
IntPtr WndProc(IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
|
||||||
{
|
|
||||||
var point = new Point((long)lParam & 0xFFFF, ((long)lParam >> 16) & 0xFFFF);
|
|
||||||
var root = VisualRoot as Window;
|
|
||||||
bool isLeft = false;
|
|
||||||
switch (msg)
|
|
||||||
{
|
|
||||||
case WindowsMessages.LBUTTONDOWN:
|
|
||||||
case WindowsMessages.RBUTTONDOWN:
|
|
||||||
isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
|
||||||
this.RaiseEvent(new PointerPressedEventArgs(
|
|
||||||
this,
|
|
||||||
new Pointer(0, PointerType.Mouse, true),
|
|
||||||
root,
|
|
||||||
this.TranslatePoint(point, root).Value,
|
|
||||||
(ulong)Environment.TickCount64,
|
|
||||||
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed),
|
|
||||||
KeyModifiers.None));
|
|
||||||
break;
|
|
||||||
case WindowsMessages.LBUTTONUP:
|
|
||||||
case WindowsMessages.RBUTTONUP:
|
|
||||||
isLeft = msg == WindowsMessages.LBUTTONUP;
|
|
||||||
this.RaiseEvent(new PointerReleasedEventArgs(
|
|
||||||
this,
|
|
||||||
new Pointer(0, PointerType.Mouse, true),
|
|
||||||
root,
|
|
||||||
this.TranslatePoint(point, root).Value,
|
|
||||||
(ulong)Environment.TickCount64,
|
|
||||||
new PointerPointProperties(isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased),
|
|
||||||
KeyModifiers.None,
|
|
||||||
isLeft ? MouseButton.Left : MouseButton.Right));
|
|
||||||
break;
|
|
||||||
case WindowsMessages.MOUSEMOVE:
|
|
||||||
this.RaiseEvent(new PointerEventArgs(
|
|
||||||
PointerMovedEvent,
|
|
||||||
this,
|
|
||||||
new Pointer(0, PointerType.Mouse, true),
|
|
||||||
root,
|
|
||||||
this.TranslatePoint(point, root).Value,
|
|
||||||
(ulong)Environment.TickCount64,
|
|
||||||
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
|
||||||
KeyModifiers.None));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefWindowProc(hWnd, msg, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
|
||||||
IPlatformHandle CreateMacOs(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
|
||||||
|
|
||||||
NsView = nsView;
|
|
||||||
|
|
||||||
return new PlatformHandle(nsView, "NSView");
|
|
||||||
}
|
|
||||||
|
|
||||||
void DestroyLinux()
|
|
||||||
{
|
|
||||||
X11Window?.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("windows")]
|
|
||||||
void DestroyWin32(IPlatformHandle handle)
|
|
||||||
{
|
|
||||||
DestroyWindow(handle.Handle);
|
|
||||||
UnregisterClass(_className, GetModuleHandle(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
[SupportedOSPlatform("macos")]
|
|
||||||
void DestroyMacOS()
|
|
||||||
{
|
|
||||||
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
using Avalonia.OpenGL;
|
|
||||||
using SPB.Graphics.OpenGL;
|
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
internal static class IGlContextExtension
|
|
||||||
{
|
|
||||||
public static OpenGLContextBase AsOpenGLContextBase(this IGlContext context)
|
|
||||||
{
|
|
||||||
var handle = (IntPtr)context.GetType().GetProperty("Handle").GetValue(context);
|
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
return new AvaloniaWglContext(handle);
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
return new AvaloniaGlxContext(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
using Avalonia.Platform;
|
|
||||||
using Silk.NET.Vulkan;
|
|
||||||
using SPB.Graphics.Vulkan;
|
|
||||||
using SPB.Platform.GLX;
|
|
||||||
using SPB.Platform.Metal;
|
|
||||||
using SPB.Platform.Win32;
|
|
||||||
using SPB.Platform.X11;
|
|
||||||
using SPB.Windowing;
|
|
||||||
using System;
|
|
||||||
using System.Runtime.Versioning;
|
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
|
||||||
{
|
|
||||||
public class VulkanEmbeddedWindow : EmbeddedWindow
|
|
||||||
{
|
|
||||||
private NativeWindowBase _window;
|
|
||||||
|
|
||||||
[SupportedOSPlatform("linux")]
|
|
||||||
protected override IPlatformHandle CreateLinux(IPlatformHandle parent)
|
|
||||||
{
|
|
||||||
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(parent.Handle));
|
|
||||||
WindowHandle = X11Window.WindowHandle.RawHandle;
|
|
||||||
X11Display = X11Window.DisplayHandle.RawHandle;
|
|
||||||
|
|
||||||
X11Window.Hide();
|
|
||||||
|
|
||||||
return new PlatformHandle(WindowHandle, "X11");
|
|
||||||
}
|
|
||||||
|
|
||||||
public SurfaceKHR CreateSurface(Instance instance)
|
|
||||||
{
|
|
||||||
if (OperatingSystem.IsWindows())
|
|
||||||
{
|
|
||||||
_window = new SimpleWin32Window(new NativeHandle(WindowHandle));
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsLinux())
|
|
||||||
{
|
|
||||||
_window = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
|
||||||
}
|
|
||||||
else if (OperatingSystem.IsMacOS())
|
|
||||||
{
|
|
||||||
_window = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new PlatformNotSupportedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, _window));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,23 +3,17 @@ using Ryujinx.Ava.Common.Locale;
|
|||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
internal class TitleUpdateModel
|
public class TitleUpdateModel
|
||||||
{
|
{
|
||||||
public bool IsEnabled { get; set; }
|
|
||||||
public bool IsNoUpdate { get; }
|
|
||||||
public ApplicationControlProperty Control { get; }
|
public ApplicationControlProperty Control { get; }
|
||||||
public string Path { get; }
|
public string Path { get; }
|
||||||
|
|
||||||
public string Label => IsNoUpdate
|
public string Label => string.Format(LocaleManager.Instance[LocaleKeys.TitleUpdateVersionLabel], Control.DisplayVersionString.ToString());
|
||||||
? LocaleManager.Instance[LocaleKeys.NoUpdate]
|
|
||||||
: string.Format(LocaleManager.Instance[LocaleKeys.TitleUpdateVersionLabel], Control.DisplayVersionString.ToString(),
|
|
||||||
Path);
|
|
||||||
|
|
||||||
public TitleUpdateModel(ApplicationControlProperty control, string path, bool isNoUpdate = false)
|
public TitleUpdateModel(ApplicationControlProperty control, string path)
|
||||||
{
|
{
|
||||||
Control = control;
|
Control = control;
|
||||||
Path = path;
|
Path = path;
|
||||||
IsNoUpdate = isNoUpdate;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
259
Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
Normal file
259
Ryujinx.Ava/UI/Renderer/EmbeddedWindow.cs
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using SPB.Graphics;
|
||||||
|
using SPB.Platform;
|
||||||
|
using SPB.Platform.GLX;
|
||||||
|
using SPB.Platform.X11;
|
||||||
|
using SPB.Windowing;
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static Ryujinx.Ava.UI.Helpers.Win32NativeInterop;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
|
{
|
||||||
|
public class EmbeddedWindow : NativeControlHost
|
||||||
|
{
|
||||||
|
private WindowProc _wndProcDelegate;
|
||||||
|
private string _className;
|
||||||
|
|
||||||
|
protected GLXWindow X11Window { get; set; }
|
||||||
|
|
||||||
|
protected IntPtr WindowHandle { get; set; }
|
||||||
|
protected IntPtr X11Display { get; set; }
|
||||||
|
protected IntPtr NsView { get; set; }
|
||||||
|
protected IntPtr MetalLayer { get; set; }
|
||||||
|
|
||||||
|
private UpdateBoundsCallbackDelegate _updateBoundsCallback;
|
||||||
|
|
||||||
|
public event EventHandler<IntPtr> WindowCreated;
|
||||||
|
public event EventHandler<Size> SizeChanged;
|
||||||
|
|
||||||
|
public EmbeddedWindow()
|
||||||
|
{
|
||||||
|
this.GetObservable(BoundsProperty).Subscribe(StateChanged);
|
||||||
|
|
||||||
|
Initialized += OnNativeEmbeddedWindowCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnWindowCreated() { }
|
||||||
|
|
||||||
|
protected virtual void OnWindowDestroyed() { }
|
||||||
|
|
||||||
|
protected virtual void OnWindowDestroying()
|
||||||
|
{
|
||||||
|
WindowHandle = IntPtr.Zero;
|
||||||
|
X11Display = IntPtr.Zero;
|
||||||
|
NsView = IntPtr.Zero;
|
||||||
|
MetalLayer = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNativeEmbeddedWindowCreated(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
OnWindowCreated();
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
WindowCreated?.Invoke(this, WindowHandle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StateChanged(Rect rect)
|
||||||
|
{
|
||||||
|
SizeChanged?.Invoke(this, rect.Size);
|
||||||
|
_updateBoundsCallback?.Invoke(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IPlatformHandle CreateNativeControlCore(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
return CreateLinux(control);
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
return CreateWin32(control);
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
return CreateMacOS();
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateNativeControlCore(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DestroyNativeControlCore(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
OnWindowDestroying();
|
||||||
|
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
DestroyLinux();
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
DestroyWin32(control);
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
DestroyMacOS();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.DestroyNativeControlCore(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnWindowDestroyed();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
private IPlatformHandle CreateLinux(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.Vulkan)
|
||||||
|
{
|
||||||
|
X11Window = new GLXWindow(new NativeHandle(X11.DefaultDisplay), new NativeHandle(control.Handle));
|
||||||
|
X11Window.Hide();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
X11Window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100) as GLXWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowHandle = X11Window.WindowHandle.RawHandle;
|
||||||
|
X11Display = X11Window.DisplayHandle.RawHandle;
|
||||||
|
|
||||||
|
return new PlatformHandle(WindowHandle, "X11");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
IPlatformHandle CreateWin32(IPlatformHandle control)
|
||||||
|
{
|
||||||
|
_className = "NativeWindow-" + Guid.NewGuid();
|
||||||
|
|
||||||
|
_wndProcDelegate = delegate (IntPtr hWnd, WindowsMessages msg, IntPtr wParam, IntPtr lParam)
|
||||||
|
{
|
||||||
|
if (VisualRoot != null)
|
||||||
|
{
|
||||||
|
Point rootVisualPosition = this.TranslatePoint(new Point((long)lParam & 0xFFFF, (long)lParam >> 16 & 0xFFFF), VisualRoot).Value;
|
||||||
|
Pointer pointer = new(0, PointerType.Mouse, true);
|
||||||
|
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case WindowsMessages.LBUTTONDOWN:
|
||||||
|
case WindowsMessages.RBUTTONDOWN:
|
||||||
|
{
|
||||||
|
bool isLeft = msg == WindowsMessages.LBUTTONDOWN;
|
||||||
|
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||||
|
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonPressed : PointerUpdateKind.RightButtonPressed);
|
||||||
|
|
||||||
|
var evnt = new PointerPressedEventArgs(
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
properties,
|
||||||
|
KeyModifiers.None);
|
||||||
|
|
||||||
|
RaiseEvent(evnt);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WindowsMessages.LBUTTONUP:
|
||||||
|
case WindowsMessages.RBUTTONUP:
|
||||||
|
{
|
||||||
|
bool isLeft = msg == WindowsMessages.LBUTTONUP;
|
||||||
|
RawInputModifiers pointerPointModifier = isLeft ? RawInputModifiers.LeftMouseButton : RawInputModifiers.RightMouseButton;
|
||||||
|
PointerPointProperties properties = new(pointerPointModifier, isLeft ? PointerUpdateKind.LeftButtonReleased : PointerUpdateKind.RightButtonReleased);
|
||||||
|
|
||||||
|
var evnt = new PointerReleasedEventArgs(
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
properties,
|
||||||
|
KeyModifiers.None,
|
||||||
|
isLeft ? MouseButton.Left : MouseButton.Right);
|
||||||
|
|
||||||
|
RaiseEvent(evnt);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case WindowsMessages.MOUSEMOVE:
|
||||||
|
{
|
||||||
|
var evnt = new PointerEventArgs(
|
||||||
|
PointerMovedEvent,
|
||||||
|
this,
|
||||||
|
pointer,
|
||||||
|
VisualRoot,
|
||||||
|
rootVisualPosition,
|
||||||
|
(ulong)Environment.TickCount64,
|
||||||
|
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.Other),
|
||||||
|
KeyModifiers.None);
|
||||||
|
|
||||||
|
RaiseEvent(evnt);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefWindowProc(hWnd, msg, wParam, lParam);
|
||||||
|
};
|
||||||
|
|
||||||
|
WNDCLASSEX wndClassEx = new()
|
||||||
|
{
|
||||||
|
cbSize = Marshal.SizeOf<WNDCLASSEX>(),
|
||||||
|
hInstance = GetModuleHandle(null),
|
||||||
|
lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate),
|
||||||
|
style = ClassStyles.CS_OWNDC,
|
||||||
|
lpszClassName = Marshal.StringToHGlobalUni(_className),
|
||||||
|
hCursor = CreateArrowCursor()
|
||||||
|
};
|
||||||
|
|
||||||
|
RegisterClassEx(ref wndClassEx);
|
||||||
|
|
||||||
|
WindowHandle = CreateWindowEx(0, _className, "NativeWindow", WindowStyles.WS_CHILD, 0, 0, 640, 480, control.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(wndClassEx.lpszClassName);
|
||||||
|
|
||||||
|
return new PlatformHandle(WindowHandle, "HWND");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
IPlatformHandle CreateMacOS()
|
||||||
|
{
|
||||||
|
MetalLayer = MetalHelper.GetMetalLayer(out IntPtr nsView, out _updateBoundsCallback);
|
||||||
|
|
||||||
|
NsView = nsView;
|
||||||
|
|
||||||
|
return new PlatformHandle(nsView, "NSView");
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("Linux")]
|
||||||
|
void DestroyLinux()
|
||||||
|
{
|
||||||
|
X11Window?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
void DestroyWin32(IPlatformHandle handle)
|
||||||
|
{
|
||||||
|
DestroyWindow(handle.Handle);
|
||||||
|
UnregisterClass(_className, GetModuleHandle(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
void DestroyMacOS()
|
||||||
|
{
|
||||||
|
MetalHelper.DestroyMetalLayer(NsView, MetalLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
using OpenTK.Graphics.OpenGL;
|
using OpenTK.Graphics.OpenGL;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.OpenGL;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using SPB.Graphics;
|
using SPB.Graphics;
|
||||||
using SPB.Graphics.OpenGL;
|
using SPB.Graphics.OpenGL;
|
||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
@ -7,26 +10,20 @@ using SPB.Platform.WGL;
|
|||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
{
|
{
|
||||||
public class OpenGLEmbeddedWindow : EmbeddedWindow
|
public class EmbeddedWindowOpenGL : EmbeddedWindow
|
||||||
{
|
{
|
||||||
private readonly int _major;
|
|
||||||
private readonly int _minor;
|
|
||||||
private readonly GraphicsDebugLevel _graphicsDebugLevel;
|
|
||||||
private SwappableNativeWindowBase _window;
|
private SwappableNativeWindowBase _window;
|
||||||
|
|
||||||
public OpenGLContextBase Context { get; set; }
|
public OpenGLContextBase Context { get; set; }
|
||||||
|
|
||||||
public OpenGLEmbeddedWindow(int major, int minor, GraphicsDebugLevel graphicsDebugLevel)
|
public EmbeddedWindowOpenGL() { }
|
||||||
{
|
|
||||||
_major = major;
|
|
||||||
_minor = minor;
|
|
||||||
_graphicsDebugLevel = graphicsDebugLevel;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnWindowDestroying()
|
protected override void OnWindowDestroying()
|
||||||
{
|
{
|
||||||
Context.Dispose();
|
Context.Dispose();
|
||||||
|
|
||||||
base.OnWindowDestroying();
|
base.OnWindowDestroying();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,19 +45,20 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var flags = OpenGLContextFlags.Compat;
|
var flags = OpenGLContextFlags.Compat;
|
||||||
if (_graphicsDebugLevel != GraphicsDebugLevel.None)
|
if (ConfigurationState.Instance.Logger.GraphicsDebugLevel != GraphicsDebugLevel.None)
|
||||||
{
|
{
|
||||||
flags |= OpenGLContextFlags.Debug;
|
flags |= OpenGLContextFlags.Debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
Context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, _major, _minor, flags);
|
var graphicsMode = Environment.OSVersion.Platform == PlatformID.Unix ? new FramebufferFormat(new ColorFormat(8, 8, 8, 0), 16, 0, ColorFormat.Zero, 0, 2, false) : FramebufferFormat.Default;
|
||||||
|
|
||||||
|
Context = PlatformHelper.CreateOpenGLContext(graphicsMode, 3, 3, flags);
|
||||||
|
|
||||||
Context.Initialize(_window);
|
Context.Initialize(_window);
|
||||||
Context.MakeCurrent(_window);
|
Context.MakeCurrent(_window);
|
||||||
|
|
||||||
var bindingsContext = new OpenToolkitBindingsContext(Context.GetProcAddress);
|
GL.LoadBindings(new OpenTKBindingsContext(Context.GetProcAddress));
|
||||||
|
|
||||||
GL.LoadBindings(bindingsContext);
|
|
||||||
Context.MakeCurrent(null);
|
Context.MakeCurrent(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,7 +74,14 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
public void SwapBuffers()
|
public void SwapBuffers()
|
||||||
{
|
{
|
||||||
_window.SwapBuffers();
|
_window?.SwapBuffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeBackgroundContext(IRenderer renderer)
|
||||||
|
{
|
||||||
|
(renderer as OpenGLRenderer)?.InitializeBackgroundContext(SPBOpenGLContext.CreateBackgroundContext(Context));
|
||||||
|
|
||||||
|
MakeCurrent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
42
Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs
Normal file
42
Ryujinx.Ava/UI/Renderer/EmbeddedWindowVulkan.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using SPB.Graphics.Vulkan;
|
||||||
|
using SPB.Platform.Metal;
|
||||||
|
using SPB.Platform.Win32;
|
||||||
|
using SPB.Platform.X11;
|
||||||
|
using SPB.Windowing;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
|
{
|
||||||
|
public class EmbeddedWindowVulkan : EmbeddedWindow
|
||||||
|
{
|
||||||
|
public SurfaceKHR CreateSurface(Instance instance)
|
||||||
|
{
|
||||||
|
NativeWindowBase nativeWindowBase;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
nativeWindowBase = new SimpleWin32Window(new NativeHandle(WindowHandle));
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
nativeWindowBase = new SimpleX11Window(new NativeHandle(X11Display), new NativeHandle(WindowHandle));
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
nativeWindowBase = new SimpleMetalWindow(new NativeHandle(NsView), new NativeHandle(MetalLayer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new PlatformNotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SurfaceKHR((ulong?)VulkanHelper.CreateWindowSurface(instance.Handle, nativeWindowBase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SurfaceKHR CreateSurface(Instance instance, Vk api)
|
||||||
|
{
|
||||||
|
return CreateSurface(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,13 @@
|
|||||||
using OpenTK;
|
using OpenTK;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
{
|
{
|
||||||
internal class OpenToolkitBindingsContext : IBindingsContext
|
internal class OpenTKBindingsContext : IBindingsContext
|
||||||
{
|
{
|
||||||
private readonly Func<string, IntPtr> _getProcAddress;
|
private readonly Func<string, IntPtr> _getProcAddress;
|
||||||
|
|
||||||
public OpenToolkitBindingsContext(Func<string, IntPtr> getProcAddress)
|
public OpenTKBindingsContext(Func<string, IntPtr> getProcAddress)
|
||||||
{
|
{
|
||||||
_getProcAddress = getProcAddress;
|
_getProcAddress = getProcAddress;
|
||||||
}
|
}
|
@ -6,6 +6,6 @@
|
|||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
x:Class="Ryujinx.Ava.UI.Controls.RendererHost"
|
x:Class="Ryujinx.Ava.UI.Renderer.RendererHost"
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
</UserControl>
|
</UserControl>
|
68
Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
Normal file
68
Ryujinx.Ava/UI/Renderer/RendererHost.axaml.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
|
{
|
||||||
|
public partial class RendererHost : UserControl, IDisposable
|
||||||
|
{
|
||||||
|
public readonly EmbeddedWindow EmbeddedWindow;
|
||||||
|
|
||||||
|
public event EventHandler<EventArgs> WindowCreated;
|
||||||
|
public event Action<object, Size> SizeChanged;
|
||||||
|
|
||||||
|
public RendererHost()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
|
||||||
|
{
|
||||||
|
EmbeddedWindow = new EmbeddedWindowOpenGL();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EmbeddedWindow = new EmbeddedWindowVulkan();
|
||||||
|
}
|
||||||
|
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Initialize()
|
||||||
|
{
|
||||||
|
EmbeddedWindow.WindowCreated += CurrentWindow_WindowCreated;
|
||||||
|
EmbeddedWindow.SizeChanged += CurrentWindow_SizeChanged;
|
||||||
|
|
||||||
|
Content = EmbeddedWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (EmbeddedWindow != null)
|
||||||
|
{
|
||||||
|
EmbeddedWindow.WindowCreated -= CurrentWindow_WindowCreated;
|
||||||
|
EmbeddedWindow.SizeChanged -= CurrentWindow_SizeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CurrentWindow_SizeChanged(object sender, Size e)
|
||||||
|
{
|
||||||
|
SizeChanged?.Invoke(sender, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CurrentWindow_WindowCreated(object sender, IntPtr e)
|
||||||
|
{
|
||||||
|
WindowCreated?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,17 +5,17 @@ using SPB.Graphics.OpenGL;
|
|||||||
using SPB.Platform;
|
using SPB.Platform;
|
||||||
using SPB.Windowing;
|
using SPB.Windowing;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Helpers
|
namespace Ryujinx.Ava.UI.Renderer
|
||||||
{
|
{
|
||||||
class SPBOpenGLContext : IOpenGLContext
|
class SPBOpenGLContext : IOpenGLContext
|
||||||
{
|
{
|
||||||
private OpenGLContextBase _context;
|
private readonly OpenGLContextBase _context;
|
||||||
private NativeWindowBase _window;
|
private readonly NativeWindowBase _window;
|
||||||
|
|
||||||
private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window)
|
private SPBOpenGLContext(OpenGLContextBase context, NativeWindowBase window)
|
||||||
{
|
{
|
||||||
_context = context;
|
_context = context;
|
||||||
_window = window;
|
_window = window;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@ -32,12 +32,12 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
public static SPBOpenGLContext CreateBackgroundContext(OpenGLContextBase sharedContext)
|
||||||
{
|
{
|
||||||
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
OpenGLContextBase context = PlatformHelper.CreateOpenGLContext(FramebufferFormat.Default, 3, 3, OpenGLContextFlags.Compat, true, sharedContext);
|
||||||
NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
NativeWindowBase window = PlatformHelper.CreateOpenGLWindow(FramebufferFormat.Default, 0, 0, 100, 100);
|
||||||
|
|
||||||
context.Initialize(window);
|
context.Initialize(window);
|
||||||
context.MakeCurrent(window);
|
context.MakeCurrent(window);
|
||||||
|
|
||||||
GL.LoadBindings(new OpenToolkitBindingsContext(context.GetProcAddress));
|
GL.LoadBindings(new OpenTKBindingsContext(context.GetProcAddress));
|
||||||
|
|
||||||
context.MakeCurrent(null);
|
context.MakeCurrent(null);
|
||||||
|
|
@ -13,6 +13,7 @@ using Ryujinx.Ava.Input;
|
|||||||
using Ryujinx.Ava.UI.Controls;
|
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.Ava.UI.Renderer;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
@ -870,7 +871,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
public Action<bool> SwitchToGameControl { get; private set; }
|
public Action<bool> SwitchToGameControl { get; private set; }
|
||||||
public Action<Control> SetMainContent { get; private set; }
|
public Action<Control> SetMainContent { get; private set; }
|
||||||
public TopLevel TopLevel { get; private set; }
|
public TopLevel TopLevel { get; private set; }
|
||||||
public RendererHost RendererControl { get; private set; }
|
public RendererHost RendererHostControl { get; private set; }
|
||||||
public bool IsClosing { get; set; }
|
public bool IsClosing { get; set; }
|
||||||
public LibHacHorizonManager LibHacHorizonManager { get; internal set; }
|
public LibHacHorizonManager LibHacHorizonManager { get; internal set; }
|
||||||
public IHostUiHandler UiHandler { get; internal set; }
|
public IHostUiHandler UiHandler { get; internal set; }
|
||||||
@ -1144,7 +1145,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
private void InitializeGame()
|
private void InitializeGame()
|
||||||
{
|
{
|
||||||
RendererControl.RendererInitialized += GlRenderer_Created;
|
RendererHostControl.WindowCreated += RendererHost_Created;
|
||||||
|
|
||||||
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
AppHost.StatusUpdatedEvent += Update_StatusBar;
|
||||||
AppHost.AppExit += AppHost_AppExit;
|
AppHost.AppExit += AppHost_AppExit;
|
||||||
@ -1203,7 +1204,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GlRenderer_Created(object sender, EventArgs e)
|
private void RendererHost_Created(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
ShowLoading(false);
|
ShowLoading(false);
|
||||||
|
|
||||||
@ -1601,13 +1602,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public async void OpenTitleUpdateManager()
|
public async void OpenTitleUpdateManager()
|
||||||
{
|
{
|
||||||
ApplicationData selection = SelectedApplication;
|
if (SelectedApplication != null)
|
||||||
if (selection != null)
|
|
||||||
{
|
{
|
||||||
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
await TitleUpdateWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
|
||||||
{
|
|
||||||
await new TitleUpdateWindow(VirtualFileSystem, ulong.Parse(selection.TitleId, NumberStyles.HexNumber), selection.TitleName).ShowDialog(desktop.MainWindow);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1735,18 +1732,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
PrepareLoadScreen();
|
PrepareLoadScreen();
|
||||||
|
|
||||||
RendererControl = new RendererHost(ConfigurationState.Instance.Logger.GraphicsDebugLevel);
|
RendererHostControl = new RendererHost();
|
||||||
if (ConfigurationState.Instance.Graphics.GraphicsBackend.Value == GraphicsBackend.OpenGl)
|
|
||||||
{
|
|
||||||
RendererControl.CreateOpenGL();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RendererControl.CreateVulkan();
|
|
||||||
}
|
|
||||||
|
|
||||||
AppHost = new AppHost(
|
AppHost = new AppHost(
|
||||||
RendererControl,
|
RendererHostControl,
|
||||||
InputManager,
|
InputManager,
|
||||||
path,
|
path,
|
||||||
VirtualFileSystem,
|
VirtualFileSystem,
|
||||||
@ -1787,9 +1776,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
SwitchToGameControl(startFullscreen);
|
SwitchToGameControl(startFullscreen);
|
||||||
|
|
||||||
SetMainContent(RendererControl);
|
SetMainContent(RendererHostControl);
|
||||||
|
|
||||||
RendererControl.Focus();
|
RendererHostControl.Focus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1857,8 +1846,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
HandleRelaunch();
|
HandleRelaunch();
|
||||||
});
|
});
|
||||||
|
|
||||||
RendererControl.RendererInitialized -= GlRenderer_Created;
|
RendererHostControl.WindowCreated -= RendererHost_Created;
|
||||||
RendererControl = null;
|
RendererHostControl = null;
|
||||||
|
|
||||||
SelectedIcon = null;
|
SelectedIcon = null;
|
||||||
|
|
||||||
|
226
Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
Normal file
226
Ryujinx.Ava/UI/ViewModels/TitleUpdateViewModel.cs
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Ns;
|
||||||
|
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 Ryujinx.HLE.HOS;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using SpanHelpers = LibHac.Common.SpanHelpers;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.ViewModels;
|
||||||
|
|
||||||
|
public class TitleUpdateViewModel : BaseModel
|
||||||
|
{
|
||||||
|
public TitleUpdateMetadata _titleUpdateWindowData;
|
||||||
|
public readonly string _titleUpdateJsonPath;
|
||||||
|
private VirtualFileSystem _virtualFileSystem { get; }
|
||||||
|
private ulong _titleId { get; }
|
||||||
|
private string _titleName { get; }
|
||||||
|
|
||||||
|
private AvaloniaList<TitleUpdateModel> _titleUpdates = new();
|
||||||
|
private AvaloniaList<object> _views = new();
|
||||||
|
private object _selectedUpdate;
|
||||||
|
|
||||||
|
public AvaloniaList<TitleUpdateModel> TitleUpdates
|
||||||
|
{
|
||||||
|
get => _titleUpdates;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_titleUpdates = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<object> Views
|
||||||
|
{
|
||||||
|
get => _views;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_views = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public object SelectedUpdate
|
||||||
|
{
|
||||||
|
get => _selectedUpdate;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedUpdate = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TitleUpdateViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
|
{
|
||||||
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
|
_titleId = titleId;
|
||||||
|
_titleName = titleName;
|
||||||
|
|
||||||
|
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Failed to deserialize title update data for {_titleId} at {_titleUpdateJsonPath}");
|
||||||
|
|
||||||
|
_titleUpdateWindowData = new TitleUpdateMetadata
|
||||||
|
{
|
||||||
|
Selected = "",
|
||||||
|
Paths = new List<string>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadUpdates()
|
||||||
|
{
|
||||||
|
foreach (string path in _titleUpdateWindowData.Paths)
|
||||||
|
{
|
||||||
|
AddUpdate(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TitleUpdateModel selected = TitleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected, null);
|
||||||
|
|
||||||
|
SelectedUpdate = selected;
|
||||||
|
|
||||||
|
SortUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SortUpdates()
|
||||||
|
{
|
||||||
|
var list = TitleUpdates.ToList();
|
||||||
|
|
||||||
|
list.Sort((first, second) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
Views.Clear();
|
||||||
|
Views.Add(new BaseModel());
|
||||||
|
Views.AddRange(list);
|
||||||
|
|
||||||
|
if (SelectedUpdate == null)
|
||||||
|
{
|
||||||
|
SelectedUpdate = Views[0];
|
||||||
|
}
|
||||||
|
else if (!TitleUpdates.Contains(SelectedUpdate))
|
||||||
|
{
|
||||||
|
if (Views.Count > 1)
|
||||||
|
{
|
||||||
|
SelectedUpdate = Views[1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedUpdate = Views[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddUpdate(string path)
|
||||||
|
{
|
||||||
|
if (File.Exists(path) && TitleUpdates.All(x => x.Path != path))
|
||||||
|
{
|
||||||
|
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
||||||
|
|
||||||
|
if (controlNca != null && patchNca != null)
|
||||||
|
{
|
||||||
|
ApplicationControlProperty controlData = new();
|
||||||
|
|
||||||
|
using UniqueRef<IFile> nacpFile = new();
|
||||||
|
|
||||||
|
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||||
|
|
||||||
|
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveUpdate(TitleUpdateModel update)
|
||||||
|
{
|
||||||
|
TitleUpdates.Remove(update);
|
||||||
|
|
||||||
|
SortUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Add()
|
||||||
|
{
|
||||||
|
OpenFileDialog dialog = new()
|
||||||
|
{
|
||||||
|
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
AddUpdate(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SortUpdates();
|
||||||
|
}
|
||||||
|
}
|
@ -107,6 +107,7 @@
|
|||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
<ListBox
|
<ListBox
|
||||||
Name="SaveList"
|
Name="SaveList"
|
||||||
|
VirtualizationMode="None"
|
||||||
Items="{Binding Views}"
|
Items="{Binding Views}"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch">
|
VerticalAlignment="Stretch">
|
||||||
@ -116,6 +117,9 @@
|
|||||||
<Setter Property="Margin" Value="5" />
|
<Setter Property="Margin" Value="5" />
|
||||||
<Setter Property="CornerRadius" Value="4" />
|
<Setter Property="CornerRadius" Value="4" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="ListBoxItem:selected /template/ Border#SelectionIndicator">
|
||||||
|
<Setter Property="IsVisible" Value="False" />
|
||||||
|
</Style>
|
||||||
</ListBox.Styles>
|
</ListBox.Styles>
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate x:DataType="models:SaveModel">
|
<DataTemplate x:DataType="models:SaveModel">
|
||||||
|
@ -1,115 +1,135 @@
|
|||||||
<window:StyleableWindow
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow"
|
x:Class="Ryujinx.Ava.UI.Windows.TitleUpdateWindow"
|
||||||
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="600"
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
Height="400"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
MinWidth="600"
|
Width="500"
|
||||||
MinHeight="400"
|
Height="300"
|
||||||
MaxWidth="600"
|
|
||||||
MaxHeight="400"
|
|
||||||
SizeToContent="Height"
|
|
||||||
WindowStartupLocation="CenterOwner"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:TitleUpdateViewModel"
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
<Grid Margin="15">
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock
|
|
||||||
Name="Heading"
|
|
||||||
Grid.Row="1"
|
|
||||||
MaxWidth="500"
|
|
||||||
Margin="20,15,20,20"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
LineHeight="18"
|
|
||||||
TextAlignment="Center"
|
|
||||||
TextWrapping="Wrap" />
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="2"
|
Grid.Row="0"
|
||||||
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">
|
VirtualizationMode="None"
|
||||||
<ItemsControl
|
Background="Transparent"
|
||||||
Margin="10"
|
SelectedItem="{Binding SelectedUpdate, Mode=TwoWay}"
|
||||||
HorizontalAlignment="Stretch"
|
Items="{Binding Views}">
|
||||||
VerticalAlignment="Stretch"
|
<ListBox.DataTemplates>
|
||||||
Items="{Binding _titleUpdates}">
|
<DataTemplate
|
||||||
<ItemsControl.ItemTemplate>
|
DataType="models:TitleUpdateModel">
|
||||||
<DataTemplate>
|
<Panel Margin="10">
|
||||||
<RadioButton
|
<TextBlock
|
||||||
Padding="8,0"
|
HorizontalAlignment="Left"
|
||||||
VerticalContentAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
GroupName="Update"
|
TextWrapping="Wrap"
|
||||||
IsChecked="{Binding IsEnabled, Mode=TwoWay}">
|
Text="{Binding Label}" />
|
||||||
<Label
|
<StackPanel
|
||||||
Margin="0"
|
Spacing="10"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<Button
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Content="{Binding Label}"
|
HorizontalAlignment="Right"
|
||||||
FontSize="12" />
|
Padding="10"
|
||||||
</RadioButton>
|
MinWidth="0"
|
||||||
</DataTemplate>
|
MinHeight="0"
|
||||||
</ItemsControl.ItemTemplate>
|
Click="OpenLocation">
|
||||||
</ItemsControl>
|
<ui:SymbolIcon
|
||||||
</ScrollViewer>
|
Symbol="OpenFolder"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Padding="10"
|
||||||
|
MinWidth="0"
|
||||||
|
MinHeight="0"
|
||||||
|
Click="RemoveUpdate">
|
||||||
|
<ui:SymbolIcon
|
||||||
|
Symbol="Cancel"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Panel>
|
||||||
|
</DataTemplate>
|
||||||
|
<DataTemplate
|
||||||
|
DataType="viewModels:BaseModel">
|
||||||
|
<Panel
|
||||||
|
Height="33"
|
||||||
|
Margin="10">
|
||||||
|
<TextBlock
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Text="{locale:Locale NoUpdate}" />
|
||||||
|
</Panel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.DataTemplates>
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
</ListBox>
|
||||||
</Border>
|
</Border>
|
||||||
<DockPanel
|
<Panel
|
||||||
Grid.Row="3"
|
Grid.Row="1"
|
||||||
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"
|
Command="{ReflectionBinding Add}">
|
||||||
Command="{Binding 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"
|
Click="RemoveAll">
|
||||||
Command="{Binding 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"
|
Click="Save">
|
||||||
Command="{Binding Save}">
|
|
||||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
Name="CancelButton"
|
Name="CancelButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Click="Close">
|
||||||
Command="{Binding Close}">
|
|
||||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||||
</Button>
|
</Button>
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</Panel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableWindow>
|
</UserControl>
|
@ -1,271 +1,116 @@
|
|||||||
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.Ns;
|
|
||||||
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.Common.Utilities;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Path = System.IO.Path;
|
using System.Threading.Tasks;
|
||||||
using SpanHelpers = LibHac.Common.SpanHelpers;
|
using Button = Avalonia.Controls.Button;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
public partial class TitleUpdateWindow : StyleableWindow
|
public partial class TitleUpdateWindow : UserControl
|
||||||
{
|
{
|
||||||
private readonly string _titleUpdateJsonPath;
|
public TitleUpdateViewModel ViewModel;
|
||||||
private TitleUpdateMetadata _titleUpdateWindowData;
|
|
||||||
|
|
||||||
private VirtualFileSystem _virtualFileSystem { get; }
|
|
||||||
private AvaloniaList<TitleUpdateModel> _titleUpdates { get; set; }
|
|
||||||
|
|
||||||
private ulong _titleId { get; }
|
|
||||||
private string _titleName { get; }
|
|
||||||
|
|
||||||
public TitleUpdateWindow()
|
public TitleUpdateWindow()
|
||||||
{
|
{
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.UpdateWindowTitle]} - {_titleName} ({_titleId:X16})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public TitleUpdateWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
DataContext = ViewModel = new TitleUpdateViewModel(virtualFileSystem, titleId, titleName);
|
||||||
_titleUpdates = new AvaloniaList<TitleUpdateModel>();
|
|
||||||
|
|
||||||
_titleId = titleId;
|
|
||||||
_titleName = titleName;
|
|
||||||
|
|
||||||
_titleUpdateJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "updates.json");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData = JsonHelper.DeserializeFromFile<TitleUpdateMetadata>(_titleUpdateJsonPath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_titleUpdateWindowData = new TitleUpdateMetadata
|
|
||||||
{
|
|
||||||
Selected = "",
|
|
||||||
Paths = new List<string>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
DataContext = this;
|
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.UpdateWindowTitle]} - {_titleName} ({_titleId:X16})";
|
|
||||||
|
|
||||||
LoadUpdates();
|
|
||||||
PrintHeading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrintHeading()
|
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
Heading.Text = string.Format(LocaleManager.Instance[LocaleKeys.GameUpdateWindowHeading], _titleUpdates.Count - 1, _titleName, _titleId.ToString("X16"));
|
ContentDialog contentDialog = new()
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadUpdates()
|
|
||||||
{
|
|
||||||
_titleUpdates.Add(new TitleUpdateModel(default, string.Empty, true));
|
|
||||||
|
|
||||||
foreach (string path in _titleUpdateWindowData.Paths)
|
|
||||||
{
|
{
|
||||||
AddUpdate(path);
|
PrimaryButtonText = "",
|
||||||
}
|
SecondaryButtonText = "",
|
||||||
|
CloseButtonText = "",
|
||||||
if (_titleUpdateWindowData.Selected == "")
|
Content = new TitleUpdateWindow(virtualFileSystem, titleId, titleName),
|
||||||
{
|
Title = string.Format(LocaleManager.Instance[LocaleKeys.GameUpdateWindowHeading], titleName, titleId.ToString("X16"))
|
||||||
_titleUpdates[0].IsEnabled = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TitleUpdateModel selected = _titleUpdates.FirstOrDefault(x => x.Path == _titleUpdateWindowData.Selected);
|
|
||||||
List<TitleUpdateModel> enabled = _titleUpdates.Where(x => x.IsEnabled).ToList();
|
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in enabled)
|
|
||||||
{
|
|
||||||
update.IsEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected != null)
|
|
||||||
{
|
|
||||||
selected.IsEnabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddUpdate(string path)
|
|
||||||
{
|
|
||||||
if (File.Exists(path) && !_titleUpdates.Any(x => x.Path == path))
|
|
||||||
{
|
|
||||||
using FileStream file = new(path, FileMode.Open, FileAccess.Read);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
(Nca patchNca, Nca controlNca) = ApplicationLoader.GetGameUpdateDataFromPartition(_virtualFileSystem, new PartitionFileSystem(file.AsStorage()), _titleId.ToString("x16"), 0);
|
|
||||||
|
|
||||||
if (controlNca != null && patchNca != null)
|
|
||||||
{
|
|
||||||
ApplicationControlProperty controlData = new();
|
|
||||||
|
|
||||||
using UniqueRef<IFile> nacpFile = new();
|
|
||||||
|
|
||||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
|
||||||
|
|
||||||
_titleUpdates.Add(new TitleUpdateModel(controlData, path));
|
|
||||||
|
|
||||||
foreach (var update in _titleUpdates)
|
|
||||||
{
|
|
||||||
update.IsEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_titleUpdates.Last().IsEnabled = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUpdateAddUpdateErrorMessage]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.Post(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogDlcLoadNcaErrorMessage], ex.Message, path));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveUpdates(bool removeSelectedOnly = false)
|
|
||||||
{
|
|
||||||
if (removeSelectedOnly)
|
|
||||||
{
|
|
||||||
_titleUpdates.RemoveAll(_titleUpdates.Where(x => x.IsEnabled && !x.IsNoUpdate).ToList());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_titleUpdates.RemoveAll(_titleUpdates.Where(x => !x.IsNoUpdate).ToList());
|
|
||||||
}
|
|
||||||
|
|
||||||
_titleUpdates.FirstOrDefault(x => x.IsNoUpdate).IsEnabled = true;
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
PrintHeading();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveSelected()
|
|
||||||
{
|
|
||||||
RemoveUpdates(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAll()
|
|
||||||
{
|
|
||||||
RemoveUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Add()
|
|
||||||
{
|
|
||||||
OpenFileDialog dialog = new()
|
|
||||||
{
|
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectUpdateDialogTitle],
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
AddUpdate(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SortUpdates();
|
|
||||||
PrintHeading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SortUpdates()
|
private void Close(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var list = _titleUpdates.ToList();
|
((ContentDialog)Parent).Hide();
|
||||||
|
|
||||||
list.Sort((first, second) =>
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(first.Control.DisplayVersionString.ToString()))
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (string.IsNullOrEmpty(second.Control.DisplayVersionString.ToString()))
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Version.Parse(first.Control.DisplayVersionString.ToString()).CompareTo(Version.Parse(second.Control.DisplayVersionString.ToString())) * -1;
|
|
||||||
});
|
|
||||||
|
|
||||||
_titleUpdates.Clear();
|
|
||||||
_titleUpdates.AddRange(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Clear();
|
ViewModel._titleUpdateWindowData.Paths.Clear();
|
||||||
|
|
||||||
_titleUpdateWindowData.Selected = "";
|
ViewModel._titleUpdateWindowData.Selected = "";
|
||||||
|
|
||||||
foreach (TitleUpdateModel update in _titleUpdates)
|
foreach (TitleUpdateModel update in ViewModel.TitleUpdates)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Paths.Add(update.Path);
|
ViewModel._titleUpdateWindowData.Paths.Add(update.Path);
|
||||||
|
|
||||||
if (update.IsEnabled)
|
if (update == ViewModel.SelectedUpdate)
|
||||||
{
|
{
|
||||||
_titleUpdateWindowData.Selected = update.Path;
|
ViewModel._titleUpdateWindowData.Selected = update.Path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream titleUpdateJsonStream = File.Create(_titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
|
using (FileStream titleUpdateJsonStream = File.Create(ViewModel._titleUpdateJsonPath, 4096, FileOptions.WriteThrough))
|
||||||
{
|
{
|
||||||
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_titleUpdateWindowData, true)));
|
titleUpdateJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(ViewModel._titleUpdateWindowData, true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Owner is MainWindow window)
|
if (VisualRoot is MainWindow window)
|
||||||
{
|
{
|
||||||
window.ViewModel.LoadApplications();
|
window.ViewModel.LoadApplications();
|
||||||
}
|
}
|
||||||
|
|
||||||
Close();
|
((ContentDialog)Parent).Hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button)
|
||||||
|
{
|
||||||
|
if (button.DataContext is TitleUpdateModel model)
|
||||||
|
{
|
||||||
|
OpenHelper.LocateFile(model.Path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveUpdate(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button)
|
||||||
|
{
|
||||||
|
ViewModel.RemoveUpdate((TitleUpdateModel)button.DataContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAll(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel.TitleUpdates.Clear();
|
||||||
|
|
||||||
|
ViewModel.SortUpdates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
470
Ryujinx.Cpu/AddressSpace.cs
Normal file
470
Ryujinx.Cpu/AddressSpace.cs
Normal file
@ -0,0 +1,470 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Collections;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu
|
||||||
|
{
|
||||||
|
class AddressSpace : IDisposable
|
||||||
|
{
|
||||||
|
private const ulong PageSize = 0x1000;
|
||||||
|
|
||||||
|
private const int DefaultBlockAlignment = 1 << 20;
|
||||||
|
|
||||||
|
private enum MappingType : byte
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Private,
|
||||||
|
Shared
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Mapping : IntrusiveRedBlackTreeNode<Mapping>, IComparable<Mapping>
|
||||||
|
{
|
||||||
|
public ulong Address { get; private set; }
|
||||||
|
public ulong Size { get; private set; }
|
||||||
|
public ulong EndAddress => Address + Size;
|
||||||
|
public MappingType Type { get; private set; }
|
||||||
|
|
||||||
|
public Mapping(ulong address, ulong size, MappingType type)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Size = size;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Mapping Split(ulong splitAddress)
|
||||||
|
{
|
||||||
|
ulong leftSize = splitAddress - Address;
|
||||||
|
ulong rightSize = EndAddress - splitAddress;
|
||||||
|
|
||||||
|
Mapping left = new Mapping(Address, leftSize, Type);
|
||||||
|
|
||||||
|
Address = splitAddress;
|
||||||
|
Size = rightSize;
|
||||||
|
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateState(MappingType newType)
|
||||||
|
{
|
||||||
|
Type = newType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Extend(ulong sizeDelta)
|
||||||
|
{
|
||||||
|
Size += sizeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Mapping other)
|
||||||
|
{
|
||||||
|
if (Address < other.Address)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (Address <= other.EndAddress - 1UL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PrivateMapping : IntrusiveRedBlackTreeNode<PrivateMapping>, IComparable<PrivateMapping>
|
||||||
|
{
|
||||||
|
public ulong Address { get; private set; }
|
||||||
|
public ulong Size { get; private set; }
|
||||||
|
public ulong EndAddress => Address + Size;
|
||||||
|
public PrivateMemoryAllocation PrivateAllocation { get; private set; }
|
||||||
|
|
||||||
|
public PrivateMapping(ulong address, ulong size, PrivateMemoryAllocation privateAllocation)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Size = size;
|
||||||
|
PrivateAllocation = privateAllocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateMapping Split(ulong splitAddress)
|
||||||
|
{
|
||||||
|
ulong leftSize = splitAddress - Address;
|
||||||
|
ulong rightSize = EndAddress - splitAddress;
|
||||||
|
|
||||||
|
(var leftAllocation, PrivateAllocation) = PrivateAllocation.Split(leftSize);
|
||||||
|
|
||||||
|
PrivateMapping left = new PrivateMapping(Address, leftSize, leftAllocation);
|
||||||
|
|
||||||
|
Address = splitAddress;
|
||||||
|
Size = rightSize;
|
||||||
|
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Map(MemoryBlock baseBlock, MemoryBlock mirrorBlock, PrivateMemoryAllocation newAllocation)
|
||||||
|
{
|
||||||
|
baseBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
|
||||||
|
mirrorBlock.MapView(newAllocation.Memory, newAllocation.Offset, Address, Size);
|
||||||
|
PrivateAllocation = newAllocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unmap(MemoryBlock baseBlock, MemoryBlock mirrorBlock)
|
||||||
|
{
|
||||||
|
if (PrivateAllocation.IsValid)
|
||||||
|
{
|
||||||
|
baseBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
|
||||||
|
mirrorBlock.UnmapView(PrivateAllocation.Memory, Address, Size);
|
||||||
|
PrivateAllocation.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
PrivateAllocation = default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Extend(ulong sizeDelta)
|
||||||
|
{
|
||||||
|
Size += sizeDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(PrivateMapping other)
|
||||||
|
{
|
||||||
|
if (Address < other.Address)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (Address <= other.EndAddress - 1UL)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly MemoryBlock _backingMemory;
|
||||||
|
private readonly PrivateMemoryAllocator _privateMemoryAllocator;
|
||||||
|
private readonly IntrusiveRedBlackTree<Mapping> _mappingTree;
|
||||||
|
private readonly IntrusiveRedBlackTree<PrivateMapping> _privateTree;
|
||||||
|
|
||||||
|
private readonly object _treeLock;
|
||||||
|
|
||||||
|
private readonly bool _supports4KBPages;
|
||||||
|
|
||||||
|
public MemoryBlock Base { get; }
|
||||||
|
public MemoryBlock Mirror { get; }
|
||||||
|
|
||||||
|
public AddressSpace(MemoryBlock backingMemory, ulong asSize, bool supports4KBPages)
|
||||||
|
{
|
||||||
|
if (!supports4KBPages)
|
||||||
|
{
|
||||||
|
_privateMemoryAllocator = new PrivateMemoryAllocator(DefaultBlockAlignment, MemoryAllocationFlags.Mirrorable | MemoryAllocationFlags.NoMap);
|
||||||
|
_mappingTree = new IntrusiveRedBlackTree<Mapping>();
|
||||||
|
_privateTree = new IntrusiveRedBlackTree<PrivateMapping>();
|
||||||
|
_treeLock = new object();
|
||||||
|
|
||||||
|
_mappingTree.Add(new Mapping(0UL, asSize, MappingType.None));
|
||||||
|
_privateTree.Add(new PrivateMapping(0UL, asSize, default));
|
||||||
|
}
|
||||||
|
|
||||||
|
_backingMemory = backingMemory;
|
||||||
|
_supports4KBPages = supports4KBPages;
|
||||||
|
|
||||||
|
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
||||||
|
|
||||||
|
Base = new MemoryBlock(asSize, asFlags);
|
||||||
|
Mirror = new MemoryBlock(asSize, asFlags);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
|
{
|
||||||
|
if (_supports4KBPages)
|
||||||
|
{
|
||||||
|
Base.MapView(_backingMemory, pa, va, size);
|
||||||
|
Mirror.MapView(_backingMemory, pa, va, size);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_treeLock)
|
||||||
|
{
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
bool isAligned = ((va | pa | size) & (alignment - 1)) == 0;
|
||||||
|
|
||||||
|
if (flags.HasFlag(MemoryMapFlags.Private) && !isAligned)
|
||||||
|
{
|
||||||
|
Update(va, pa, size, MappingType.Private);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The update method assumes that shared mappings are already aligned.
|
||||||
|
|
||||||
|
if (!flags.HasFlag(MemoryMapFlags.Private))
|
||||||
|
{
|
||||||
|
if ((va & (alignment - 1)) != (pa & (alignment - 1)))
|
||||||
|
{
|
||||||
|
throw new InvalidMemoryRegionException($"Virtual address 0x{va:X} and physical address 0x{pa:X} are misaligned and can't be aligned.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
va = BitUtils.AlignDown(va, alignment);
|
||||||
|
pa = BitUtils.AlignDown(pa, alignment);
|
||||||
|
size = BitUtils.AlignUp(endAddress, alignment) - va;
|
||||||
|
}
|
||||||
|
|
||||||
|
Update(va, pa, size, MappingType.Shared);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unmap(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (_supports4KBPages)
|
||||||
|
{
|
||||||
|
Base.UnmapView(_backingMemory, va, size);
|
||||||
|
Mirror.UnmapView(_backingMemory, va, size);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_treeLock)
|
||||||
|
{
|
||||||
|
Update(va, 0UL, size, MappingType.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update(ulong va, ulong pa, ulong size, MappingType type)
|
||||||
|
{
|
||||||
|
Mapping map = _mappingTree.GetNode(new Mapping(va, 1UL, MappingType.None));
|
||||||
|
|
||||||
|
Update(map, va, pa, size, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mapping Update(Mapping map, ulong va, ulong pa, ulong size, MappingType type)
|
||||||
|
{
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
|
||||||
|
for (; map != null; map = map.Successor)
|
||||||
|
{
|
||||||
|
if (map.Address < va)
|
||||||
|
{
|
||||||
|
_mappingTree.Add(map.Split(va));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress > endAddress)
|
||||||
|
{
|
||||||
|
Mapping newMap = map.Split(endAddress);
|
||||||
|
_mappingTree.Add(newMap);
|
||||||
|
map = newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MappingType.None:
|
||||||
|
if (map.Type == MappingType.Shared)
|
||||||
|
{
|
||||||
|
ulong startOffset = map.Address - va;
|
||||||
|
ulong mapVa = va + startOffset;
|
||||||
|
ulong mapSize = Math.Min(size - startOffset, map.Size);
|
||||||
|
ulong mapEndAddress = mapVa + mapSize;
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
|
||||||
|
mapVa = BitUtils.AlignDown(mapVa, alignment);
|
||||||
|
mapEndAddress = BitUtils.AlignUp(mapEndAddress, alignment);
|
||||||
|
|
||||||
|
mapSize = mapEndAddress - mapVa;
|
||||||
|
|
||||||
|
Base.UnmapView(_backingMemory, mapVa, mapSize);
|
||||||
|
Mirror.UnmapView(_backingMemory, mapVa, mapSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnmapPrivate(va, size);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MappingType.Private:
|
||||||
|
if (map.Type == MappingType.Shared)
|
||||||
|
{
|
||||||
|
throw new InvalidMemoryRegionException($"Private mapping request at 0x{va:X} with size 0x{size:X} overlaps shared mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MapPrivate(va, size);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MappingType.Shared:
|
||||||
|
if (map.Type != MappingType.None)
|
||||||
|
{
|
||||||
|
throw new InvalidMemoryRegionException($"Shared mapping request at 0x{va:X} with size 0x{size:X} overlaps mapping at 0x{map.Address:X} with size 0x{map.Size:X}.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ulong startOffset = map.Address - va;
|
||||||
|
ulong mapPa = pa + startOffset;
|
||||||
|
ulong mapVa = va + startOffset;
|
||||||
|
ulong mapSize = Math.Min(size - startOffset, map.Size);
|
||||||
|
|
||||||
|
Base.MapView(_backingMemory, mapPa, mapVa, mapSize);
|
||||||
|
Mirror.MapView(_backingMemory, mapPa, mapVa, mapSize);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.UpdateState(type);
|
||||||
|
map = TryCoalesce(map);
|
||||||
|
|
||||||
|
if (map.EndAddress >= endAddress)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mapping TryCoalesce(Mapping map)
|
||||||
|
{
|
||||||
|
Mapping previousMap = map.Predecessor;
|
||||||
|
Mapping nextMap = map.Successor;
|
||||||
|
|
||||||
|
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||||
|
{
|
||||||
|
previousMap.Extend(map.Size);
|
||||||
|
_mappingTree.Remove(map);
|
||||||
|
map = previousMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||||
|
{
|
||||||
|
map.Extend(nextMap.Size);
|
||||||
|
_mappingTree.Remove(nextMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanCoalesce(Mapping left, Mapping right)
|
||||||
|
{
|
||||||
|
return left.Type == right.Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MapPrivate(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
|
||||||
|
// Expand the range outwards based on page size to ensure that at least the requested region is mapped.
|
||||||
|
ulong vaAligned = BitUtils.AlignDown(va, alignment);
|
||||||
|
ulong endAddressAligned = BitUtils.AlignUp(endAddress, alignment);
|
||||||
|
|
||||||
|
ulong sizeAligned = endAddressAligned - vaAligned;
|
||||||
|
|
||||||
|
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
|
||||||
|
|
||||||
|
for (; map != null; map = map.Successor)
|
||||||
|
{
|
||||||
|
if (!map.PrivateAllocation.IsValid)
|
||||||
|
{
|
||||||
|
if (map.Address < vaAligned)
|
||||||
|
{
|
||||||
|
_privateTree.Add(map.Split(vaAligned));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress > endAddressAligned)
|
||||||
|
{
|
||||||
|
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||||
|
_privateTree.Add(newMap);
|
||||||
|
map = newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.Map(Base, Mirror, _privateMemoryAllocator.Allocate(map.Size, MemoryBlock.GetPageSize()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress >= endAddressAligned)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnmapPrivate(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
ulong endAddress = va + size;
|
||||||
|
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
|
||||||
|
// Shrink the range inwards based on page size to ensure we won't unmap memory that might be still in use.
|
||||||
|
ulong vaAligned = BitUtils.AlignUp(va, alignment);
|
||||||
|
ulong endAddressAligned = BitUtils.AlignDown(endAddress, alignment);
|
||||||
|
|
||||||
|
if (endAddressAligned <= vaAligned)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong alignedSize = endAddressAligned - vaAligned;
|
||||||
|
|
||||||
|
PrivateMapping map = _privateTree.GetNode(new PrivateMapping(va, 1UL, default));
|
||||||
|
|
||||||
|
for (; map != null; map = map.Successor)
|
||||||
|
{
|
||||||
|
if (map.PrivateAllocation.IsValid)
|
||||||
|
{
|
||||||
|
if (map.Address < vaAligned)
|
||||||
|
{
|
||||||
|
_privateTree.Add(map.Split(vaAligned));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress > endAddressAligned)
|
||||||
|
{
|
||||||
|
PrivateMapping newMap = map.Split(endAddressAligned);
|
||||||
|
_privateTree.Add(newMap);
|
||||||
|
map = newMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
map.Unmap(Base, Mirror);
|
||||||
|
map = TryCoalesce(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (map.EndAddress >= endAddressAligned)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrivateMapping TryCoalesce(PrivateMapping map)
|
||||||
|
{
|
||||||
|
PrivateMapping previousMap = map.Predecessor;
|
||||||
|
PrivateMapping nextMap = map.Successor;
|
||||||
|
|
||||||
|
if (previousMap != null && CanCoalesce(previousMap, map))
|
||||||
|
{
|
||||||
|
previousMap.Extend(map.Size);
|
||||||
|
_privateTree.Remove(map);
|
||||||
|
map = previousMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextMap != null && CanCoalesce(map, nextMap))
|
||||||
|
{
|
||||||
|
map.Extend(nextMap.Size);
|
||||||
|
_privateTree.Remove(nextMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CanCoalesce(PrivateMapping left, PrivateMapping right)
|
||||||
|
{
|
||||||
|
return !left.PrivateAllocation.IsValid && !right.PrivateAllocation.IsValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_privateMemoryAllocator?.Dispose();
|
||||||
|
Base.Dispose();
|
||||||
|
Mirror.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,5 +7,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
public IJitMemoryBlock Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None);
|
public IJitMemoryBlock Allocate(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.None);
|
||||||
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Jit);
|
public IJitMemoryBlock Reserve(ulong size) => new JitMemoryBlock(size, MemoryAllocationFlags.Reserve | MemoryAllocationFlags.Jit);
|
||||||
|
|
||||||
|
public ulong GetPageSize() => MemoryBlock.GetPageSize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,9 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
private readonly MemoryBlock _backingMemory;
|
private readonly MemoryBlock _backingMemory;
|
||||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Supports4KBPages => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Address space width in bits.
|
/// Address space width in bits.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -76,7 +79,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Map(ulong va, ulong pa, ulong size)
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
{
|
{
|
||||||
AssertValidAddressAndSize(va, size);
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
@ -91,9 +94,16 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
pa += PageSize;
|
pa += PageSize;
|
||||||
remainingSize -= PageSize;
|
remainingSize -= PageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Tracking.Map(oVa, size);
|
Tracking.Map(oVa, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Unmap(ulong va, ulong size)
|
public void Unmap(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -378,6 +388,32 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<HostMemoryRange>();
|
||||||
|
}
|
||||||
|
|
||||||
|
var guestRegions = GetPhysicalRegionsImpl(va, size);
|
||||||
|
if (guestRegions == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var regions = new HostMemoryRange[guestRegions.Count];
|
||||||
|
|
||||||
|
for (int i = 0; i < regions.Length; i++)
|
||||||
|
{
|
||||||
|
var guestRegion = guestRegions[i];
|
||||||
|
IntPtr pointer = _backingMemory.GetPointer(guestRegion.Address, guestRegion.Size);
|
||||||
|
regions[i] = new HostMemoryRange((nuint)(ulong)pointer, guestRegion.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -386,6 +422,11 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
return Enumerable.Empty<MemoryRange>();
|
return Enumerable.Empty<MemoryRange>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return GetPhysicalRegionsImpl(va, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<MemoryRange> GetPhysicalRegionsImpl(ulong va, ulong size)
|
||||||
|
{
|
||||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
|
@ -5,6 +5,7 @@ using Ryujinx.Memory.Range;
|
|||||||
using Ryujinx.Memory.Tracking;
|
using Ryujinx.Memory.Tracking;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
@ -37,20 +38,21 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
private readonly InvalidAccessHandler _invalidAccessHandler;
|
private readonly InvalidAccessHandler _invalidAccessHandler;
|
||||||
private readonly bool _unsafeMode;
|
private readonly bool _unsafeMode;
|
||||||
|
|
||||||
private readonly MemoryBlock _addressSpace;
|
private readonly AddressSpace _addressSpace;
|
||||||
private readonly MemoryBlock _addressSpaceMirror;
|
|
||||||
private readonly ulong _addressSpaceSize;
|
private readonly ulong _addressSpaceSize;
|
||||||
|
|
||||||
private readonly MemoryBlock _backingMemory;
|
|
||||||
private readonly PageTable<ulong> _pageTable;
|
private readonly PageTable<ulong> _pageTable;
|
||||||
|
|
||||||
private readonly MemoryEhMeilleure _memoryEh;
|
private readonly MemoryEhMeilleure _memoryEh;
|
||||||
|
|
||||||
private readonly ulong[] _pageBitmap;
|
private readonly ulong[] _pageBitmap;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
|
||||||
|
|
||||||
public int AddressSpaceBits { get; }
|
public int AddressSpaceBits { get; }
|
||||||
|
|
||||||
public IntPtr PageTablePointer => _addressSpace.Pointer;
|
public IntPtr PageTablePointer => _addressSpace.Base.Pointer;
|
||||||
|
|
||||||
public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostMappedUnsafe : MemoryManagerType.HostMapped;
|
public MemoryManagerType Type => _unsafeMode ? MemoryManagerType.HostMappedUnsafe : MemoryManagerType.HostMapped;
|
||||||
|
|
||||||
@ -67,7 +69,6 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
|
||||||
public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
public MemoryManagerHostMapped(MemoryBlock backingMemory, ulong addressSpaceSize, bool unsafeMode, InvalidAccessHandler invalidAccessHandler = null)
|
||||||
{
|
{
|
||||||
_backingMemory = backingMemory;
|
|
||||||
_pageTable = new PageTable<ulong>();
|
_pageTable = new PageTable<ulong>();
|
||||||
_invalidAccessHandler = invalidAccessHandler;
|
_invalidAccessHandler = invalidAccessHandler;
|
||||||
_unsafeMode = unsafeMode;
|
_unsafeMode = unsafeMode;
|
||||||
@ -86,13 +87,10 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
|
|
||||||
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
|
||||||
|
|
||||||
MemoryAllocationFlags asFlags = MemoryAllocationFlags.Reserve | MemoryAllocationFlags.ViewCompatible;
|
_addressSpace = new AddressSpace(backingMemory, asSize, Supports4KBPages);
|
||||||
|
|
||||||
_addressSpace = new MemoryBlock(asSize, asFlags);
|
Tracking = new MemoryTracking(this, (int)MemoryBlock.GetPageSize(), invalidAccessHandler);
|
||||||
_addressSpaceMirror = new MemoryBlock(asSize, asFlags);
|
_memoryEh = new MemoryEhMeilleure(_addressSpace.Base, _addressSpace.Mirror, Tracking);
|
||||||
|
|
||||||
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
|
|
||||||
_memoryEh = new MemoryEhMeilleure(_addressSpace, _addressSpaceMirror, Tracking);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -145,18 +143,23 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Map(ulong va, ulong pa, ulong size)
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
{
|
{
|
||||||
AssertValidAddressAndSize(va, size);
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
_addressSpace.MapView(_backingMemory, pa, va, size);
|
_addressSpace.Map(va, pa, size, flags);
|
||||||
_addressSpaceMirror.MapView(_backingMemory, pa, va, size);
|
|
||||||
AddMapping(va, size);
|
AddMapping(va, size);
|
||||||
PtMap(va, pa, size);
|
PtMap(va, pa, size);
|
||||||
|
|
||||||
Tracking.Map(va, size);
|
Tracking.Map(va, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Unmap(ulong va, ulong size)
|
public void Unmap(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -167,8 +170,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
|
|
||||||
RemoveMapping(va, size);
|
RemoveMapping(va, size);
|
||||||
PtUnmap(va, size);
|
PtUnmap(va, size);
|
||||||
_addressSpace.UnmapView(_backingMemory, va, size);
|
_addressSpace.Unmap(va, size);
|
||||||
_addressSpaceMirror.UnmapView(_backingMemory, va, size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PtMap(ulong va, ulong pa, ulong size)
|
private void PtMap(ulong va, ulong pa, ulong size)
|
||||||
@ -201,7 +203,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
AssertMapped(va, (ulong)Unsafe.SizeOf<T>());
|
AssertMapped(va, (ulong)Unsafe.SizeOf<T>());
|
||||||
|
|
||||||
return _addressSpaceMirror.Read<T>(va);
|
return _addressSpace.Mirror.Read<T>(va);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -241,7 +243,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
AssertMapped(va, (ulong)data.Length);
|
AssertMapped(va, (ulong)data.Length);
|
||||||
|
|
||||||
_addressSpaceMirror.Read(va, data);
|
_addressSpace.Mirror.Read(va, data);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -260,7 +262,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), write: true);
|
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), write: true);
|
||||||
|
|
||||||
_addressSpaceMirror.Write(va, value);
|
_addressSpace.Mirror.Write(va, value);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -278,7 +280,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
SignalMemoryTracking(va, (ulong)data.Length, write: true);
|
SignalMemoryTracking(va, (ulong)data.Length, write: true);
|
||||||
|
|
||||||
_addressSpaceMirror.Write(va, data);
|
_addressSpace.Mirror.Write(va, data);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -296,7 +298,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
AssertMapped(va, (ulong)data.Length);
|
AssertMapped(va, (ulong)data.Length);
|
||||||
|
|
||||||
_addressSpaceMirror.Write(va, data);
|
_addressSpace.Mirror.Write(va, data);
|
||||||
}
|
}
|
||||||
catch (InvalidMemoryRegionException)
|
catch (InvalidMemoryRegionException)
|
||||||
{
|
{
|
||||||
@ -314,7 +316,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
SignalMemoryTracking(va, (ulong)data.Length, false);
|
SignalMemoryTracking(va, (ulong)data.Length, false);
|
||||||
|
|
||||||
Span<byte> target = _addressSpaceMirror.GetSpan(va, data.Length);
|
Span<byte> target = _addressSpace.Mirror.GetSpan(va, data.Length);
|
||||||
bool changed = !data.SequenceEqual(target);
|
bool changed = !data.SequenceEqual(target);
|
||||||
|
|
||||||
if (changed)
|
if (changed)
|
||||||
@ -347,7 +349,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
AssertMapped(va, (ulong)size);
|
AssertMapped(va, (ulong)size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _addressSpaceMirror.GetSpan(va, size);
|
return _addressSpace.Mirror.GetSpan(va, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -362,7 +364,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
AssertMapped(va, (ulong)size);
|
AssertMapped(va, (ulong)size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _addressSpaceMirror.GetWritableRegion(va, size);
|
return _addressSpace.Mirror.GetWritableRegion(va, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -370,7 +372,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
{
|
{
|
||||||
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
|
||||||
|
|
||||||
return ref _addressSpaceMirror.GetRef<T>(va);
|
return ref _addressSpace.Mirror.GetRef<T>(va);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -454,6 +456,14 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
|
return Enumerable.Repeat(new HostMemoryRange((nuint)(ulong)_addressSpace.Mirror.GetPointer(va, size), size), 1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -692,7 +702,7 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
_ => MemoryPermission.None
|
_ => MemoryPermission.None
|
||||||
};
|
};
|
||||||
|
|
||||||
_addressSpace.Reprotect(va, size, protection, false);
|
_addressSpace.Base.Reprotect(va, size, protection, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -799,7 +809,6 @@ namespace Ryujinx.Cpu.Jit
|
|||||||
protected override void Destroy()
|
protected override void Destroy()
|
||||||
{
|
{
|
||||||
_addressSpace.Dispose();
|
_addressSpace.Dispose();
|
||||||
_addressSpaceMirror.Dispose();
|
|
||||||
_memoryEh.Dispose();
|
_memoryEh.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
41
Ryujinx.Cpu/PrivateMemoryAllocation.cs
Normal file
41
Ryujinx.Cpu/PrivateMemoryAllocation.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu
|
||||||
|
{
|
||||||
|
struct PrivateMemoryAllocation : IDisposable
|
||||||
|
{
|
||||||
|
private readonly PrivateMemoryAllocator _owner;
|
||||||
|
private readonly PrivateMemoryAllocator.Block _block;
|
||||||
|
|
||||||
|
public bool IsValid => _owner != null;
|
||||||
|
public MemoryBlock Memory => _block?.Memory;
|
||||||
|
public ulong Offset { get; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
public PrivateMemoryAllocation(
|
||||||
|
PrivateMemoryAllocator owner,
|
||||||
|
PrivateMemoryAllocator.Block block,
|
||||||
|
ulong offset,
|
||||||
|
ulong size)
|
||||||
|
{
|
||||||
|
_owner = owner;
|
||||||
|
_block = block;
|
||||||
|
Offset = offset;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (PrivateMemoryAllocation, PrivateMemoryAllocation) Split(ulong splitOffset)
|
||||||
|
{
|
||||||
|
PrivateMemoryAllocation left = new PrivateMemoryAllocation(_owner, _block, Offset, splitOffset);
|
||||||
|
PrivateMemoryAllocation right = new PrivateMemoryAllocation(_owner, _block, Offset + splitOffset, Size - splitOffset);
|
||||||
|
|
||||||
|
return (left, right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_owner.Free(_block, Offset, Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
268
Ryujinx.Cpu/PrivateMemoryAllocator.cs
Normal file
268
Ryujinx.Cpu/PrivateMemoryAllocator.cs
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace Ryujinx.Cpu
|
||||||
|
{
|
||||||
|
class PrivateMemoryAllocator : PrivateMemoryAllocatorImpl<PrivateMemoryAllocator.Block>
|
||||||
|
{
|
||||||
|
public const ulong InvalidOffset = ulong.MaxValue;
|
||||||
|
|
||||||
|
public class Block : IComparable<Block>
|
||||||
|
{
|
||||||
|
public MemoryBlock Memory { get; private set; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
private struct Range : IComparable<Range>
|
||||||
|
{
|
||||||
|
public ulong Offset { get; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
public Range(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
Offset = offset;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Range other)
|
||||||
|
{
|
||||||
|
return Offset.CompareTo(other.Offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<Range> _freeRanges;
|
||||||
|
|
||||||
|
public Block(MemoryBlock memory, ulong size)
|
||||||
|
{
|
||||||
|
Memory = memory;
|
||||||
|
Size = size;
|
||||||
|
_freeRanges = new List<Range>
|
||||||
|
{
|
||||||
|
new Range(0, size)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong Allocate(ulong size, ulong alignment)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _freeRanges.Count; i++)
|
||||||
|
{
|
||||||
|
var range = _freeRanges[i];
|
||||||
|
|
||||||
|
ulong alignedOffset = BitUtils.AlignUp(range.Offset, alignment);
|
||||||
|
ulong sizeDelta = alignedOffset - range.Offset;
|
||||||
|
ulong usableSize = range.Size - sizeDelta;
|
||||||
|
|
||||||
|
if (sizeDelta < range.Size && usableSize >= size)
|
||||||
|
{
|
||||||
|
_freeRanges.RemoveAt(i);
|
||||||
|
|
||||||
|
if (sizeDelta != 0)
|
||||||
|
{
|
||||||
|
InsertFreeRange(range.Offset, sizeDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong endOffset = range.Offset + range.Size;
|
||||||
|
ulong remainingSize = endOffset - (alignedOffset + size);
|
||||||
|
if (remainingSize != 0)
|
||||||
|
{
|
||||||
|
InsertFreeRange(endOffset - remainingSize, remainingSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return alignedOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvalidOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
InsertFreeRangeComingled(offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertFreeRange(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
var range = new Range(offset, size);
|
||||||
|
int index = _freeRanges.BinarySearch(range);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
|
||||||
|
_freeRanges.Insert(index, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertFreeRangeComingled(ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
ulong endOffset = offset + size;
|
||||||
|
var range = new Range(offset, size);
|
||||||
|
int index = _freeRanges.BinarySearch(range);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < _freeRanges.Count && _freeRanges[index].Offset == endOffset)
|
||||||
|
{
|
||||||
|
endOffset = _freeRanges[index].Offset + _freeRanges[index].Size;
|
||||||
|
_freeRanges.RemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index > 0 && _freeRanges[index - 1].Offset + _freeRanges[index - 1].Size == offset)
|
||||||
|
{
|
||||||
|
offset = _freeRanges[index - 1].Offset;
|
||||||
|
_freeRanges.RemoveAt(--index);
|
||||||
|
}
|
||||||
|
|
||||||
|
range = new Range(offset, endOffset - offset);
|
||||||
|
|
||||||
|
_freeRanges.Insert(index, range);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsTotallyFree()
|
||||||
|
{
|
||||||
|
if (_freeRanges.Count == 1 && _freeRanges[0].Size == Size)
|
||||||
|
{
|
||||||
|
Debug.Assert(_freeRanges[0].Offset == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Block other)
|
||||||
|
{
|
||||||
|
return Size.CompareTo(other.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Destroy()
|
||||||
|
{
|
||||||
|
Memory.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateMemoryAllocator(int blockAlignment, MemoryAllocationFlags allocationFlags) : base(blockAlignment, allocationFlags)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrivateMemoryAllocation Allocate(ulong size, ulong alignment)
|
||||||
|
{
|
||||||
|
var allocation = Allocate(size, alignment, CreateBlock);
|
||||||
|
|
||||||
|
return new PrivateMemoryAllocation(this, allocation.Block, allocation.Offset, allocation.Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Block CreateBlock(MemoryBlock memory, ulong size)
|
||||||
|
{
|
||||||
|
return new Block(memory, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrivateMemoryAllocatorImpl<T> : IDisposable where T : PrivateMemoryAllocator.Block
|
||||||
|
{
|
||||||
|
private const ulong InvalidOffset = ulong.MaxValue;
|
||||||
|
|
||||||
|
public struct Allocation
|
||||||
|
{
|
||||||
|
public T Block { get; }
|
||||||
|
public ulong Offset { get; }
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
public Allocation(T block, ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
Block = block;
|
||||||
|
Offset = offset;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<T> _blocks;
|
||||||
|
|
||||||
|
private readonly int _blockAlignment;
|
||||||
|
private readonly MemoryAllocationFlags _allocationFlags;
|
||||||
|
|
||||||
|
public PrivateMemoryAllocatorImpl(int blockAlignment, MemoryAllocationFlags allocationFlags)
|
||||||
|
{
|
||||||
|
_blocks = new List<T>();
|
||||||
|
_blockAlignment = blockAlignment;
|
||||||
|
_allocationFlags = allocationFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Allocation Allocate(ulong size, ulong alignment, Func<MemoryBlock, ulong, T> createBlock)
|
||||||
|
{
|
||||||
|
// Ensure we have a sane alignment value.
|
||||||
|
if ((ulong)(int)alignment != alignment || (int)alignment <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _blocks.Count; i++)
|
||||||
|
{
|
||||||
|
var block = _blocks[i];
|
||||||
|
|
||||||
|
if (block.Size >= size)
|
||||||
|
{
|
||||||
|
ulong offset = block.Allocate(size, alignment);
|
||||||
|
if (offset != InvalidOffset)
|
||||||
|
{
|
||||||
|
return new Allocation(block, offset, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment);
|
||||||
|
|
||||||
|
var memory = new MemoryBlock(blockAlignedSize, _allocationFlags);
|
||||||
|
var newBlock = createBlock(memory, blockAlignedSize);
|
||||||
|
|
||||||
|
InsertBlock(newBlock);
|
||||||
|
|
||||||
|
ulong newBlockOffset = newBlock.Allocate(size, alignment);
|
||||||
|
Debug.Assert(newBlockOffset != InvalidOffset);
|
||||||
|
|
||||||
|
return new Allocation(newBlock, newBlockOffset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Free(PrivateMemoryAllocator.Block block, ulong offset, ulong size)
|
||||||
|
{
|
||||||
|
block.Free(offset, size);
|
||||||
|
|
||||||
|
if (block.IsTotallyFree())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _blocks.Count; i++)
|
||||||
|
{
|
||||||
|
if (_blocks[i] == block)
|
||||||
|
{
|
||||||
|
_blocks.RemoveAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block.Destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InsertBlock(T block)
|
||||||
|
{
|
||||||
|
int index = _blocks.BinarySearch(block);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
index = ~index;
|
||||||
|
}
|
||||||
|
|
||||||
|
_blocks.Insert(index, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _blocks.Count; i++)
|
||||||
|
{
|
||||||
|
_blocks[i].Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
_blocks.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,28 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An entry on the short duration texture cache.
|
||||||
|
/// </summary>
|
||||||
|
class ShortTextureCacheEntry
|
||||||
|
{
|
||||||
|
public readonly TextureDescriptor Descriptor;
|
||||||
|
public readonly int InvalidatedSequence;
|
||||||
|
public readonly Texture Texture;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a new entry on the short duration texture cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">Last descriptor that referenced the texture</param>
|
||||||
|
/// <param name="texture">The texture</param>
|
||||||
|
public ShortTextureCacheEntry(TextureDescriptor descriptor, Texture texture)
|
||||||
|
{
|
||||||
|
Descriptor = descriptor;
|
||||||
|
InvalidatedSequence = texture.InvalidatedSequence;
|
||||||
|
Texture = texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A texture cache that automatically removes older textures that are not used for some time.
|
/// A texture cache that automatically removes older textures that are not used for some time.
|
||||||
/// The cache works with a rotated list with a fixed size. When new textures are added, the
|
/// The cache works with a rotated list with a fixed size. When new textures are added, the
|
||||||
@ -16,6 +38,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
private readonly LinkedList<Texture> _textures;
|
private readonly LinkedList<Texture> _textures;
|
||||||
private readonly ConcurrentQueue<Texture> _deferredRemovals;
|
private readonly ConcurrentQueue<Texture> _deferredRemovals;
|
||||||
|
|
||||||
|
private HashSet<ShortTextureCacheEntry> _shortCacheBuilder;
|
||||||
|
private HashSet<ShortTextureCacheEntry> _shortCache;
|
||||||
|
|
||||||
|
private Dictionary<TextureDescriptor, ShortTextureCacheEntry> _shortCacheLookup;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the automatic deletion cache.
|
/// Creates a new instance of the automatic deletion cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -23,6 +50,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
_textures = new LinkedList<Texture>();
|
_textures = new LinkedList<Texture>();
|
||||||
_deferredRemovals = new ConcurrentQueue<Texture>();
|
_deferredRemovals = new ConcurrentQueue<Texture>();
|
||||||
|
|
||||||
|
_shortCacheBuilder = new HashSet<ShortTextureCacheEntry>();
|
||||||
|
_shortCache = new HashSet<ShortTextureCacheEntry>();
|
||||||
|
|
||||||
|
_shortCacheLookup = new Dictionary<TextureDescriptor, ShortTextureCacheEntry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -130,6 +162,85 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_deferredRemovals.Enqueue(texture);
|
_deferredRemovals.Enqueue(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to find a texture on the short duration cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The texture descriptor</param>
|
||||||
|
/// <returns>The texture if found, null otherwise</returns>
|
||||||
|
public Texture FindShortCache(in TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
if (_shortCacheLookup.Count > 0 && _shortCacheLookup.TryGetValue(descriptor, out var entry))
|
||||||
|
{
|
||||||
|
if (entry.InvalidatedSequence == entry.Texture.InvalidatedSequence)
|
||||||
|
{
|
||||||
|
return entry.Texture;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_shortCacheLookup.Remove(descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a texture from the short duration cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to remove from the short cache</param>
|
||||||
|
public void RemoveShortCache(Texture texture)
|
||||||
|
{
|
||||||
|
bool removed = _shortCache.Remove(texture.ShortCacheEntry);
|
||||||
|
removed |= _shortCacheBuilder.Remove(texture.ShortCacheEntry);
|
||||||
|
|
||||||
|
if (removed)
|
||||||
|
{
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
|
||||||
|
_shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
|
||||||
|
texture.ShortCacheEntry = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a texture to the short duration cache.
|
||||||
|
/// It starts in the builder set, and it is moved into the deletion set on next process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to add to the short cache</param>
|
||||||
|
/// <param name="descriptor">Last used texture descriptor</param>
|
||||||
|
public void AddShortCache(Texture texture, ref TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
var entry = new ShortTextureCacheEntry(descriptor, texture);
|
||||||
|
|
||||||
|
_shortCacheBuilder.Add(entry);
|
||||||
|
_shortCacheLookup.Add(entry.Descriptor, entry);
|
||||||
|
|
||||||
|
texture.ShortCacheEntry = entry;
|
||||||
|
|
||||||
|
texture.IncrementReferenceCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete textures from the short duration cache.
|
||||||
|
/// Moves the builder set to be deleted on next process.
|
||||||
|
/// </summary>
|
||||||
|
public void ProcessShortCache()
|
||||||
|
{
|
||||||
|
HashSet<ShortTextureCacheEntry> toRemove = _shortCache;
|
||||||
|
|
||||||
|
foreach (var entry in toRemove)
|
||||||
|
{
|
||||||
|
entry.Texture.DecrementReferenceCount();
|
||||||
|
|
||||||
|
_shortCacheLookup.Remove(entry.Descriptor);
|
||||||
|
entry.Texture.ShortCacheEntry = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
toRemove.Clear();
|
||||||
|
_shortCache = _shortCacheBuilder;
|
||||||
|
_shortCacheBuilder = toRemove;
|
||||||
|
}
|
||||||
|
|
||||||
public IEnumerator<Texture> GetEnumerator()
|
public IEnumerator<Texture> GetEnumerator()
|
||||||
{
|
{
|
||||||
return _textures.GetEnumerator();
|
return _textures.GetEnumerator();
|
||||||
|
@ -91,7 +91,17 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <returns>A reference to the descriptor</returns>
|
/// <returns>A reference to the descriptor</returns>
|
||||||
public ref readonly T2 GetDescriptorRef(int id)
|
public ref readonly T2 GetDescriptorRef(int id)
|
||||||
{
|
{
|
||||||
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(Address + (ulong)id * DescriptorSize, DescriptorSize))[0];
|
return ref GetDescriptorRefAddress(Address + (ulong)id * DescriptorSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a reference to the descriptor for a given address.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Address of the descriptor</param>
|
||||||
|
/// <returns>A reference to the descriptor</returns>
|
||||||
|
public ref readonly T2 GetDescriptorRefAddress(ulong address)
|
||||||
|
{
|
||||||
|
return ref MemoryMarshal.Cast<byte, T2>(PhysicalMemory.GetSpan(address, DescriptorSize))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -138,6 +138,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
public LinkedListNode<Texture> CacheNode { get; set; }
|
public LinkedListNode<Texture> CacheNode { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Entry for this texture in the short duration cache, if present.
|
||||||
|
/// </summary>
|
||||||
|
public ShortTextureCacheEntry ShortCacheEntry { get; set; }
|
||||||
|
|
||||||
/// Physical memory ranges where the texture data is located.
|
/// Physical memory ranges where the texture data is located.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public MultiRange Range { get; private set; }
|
public MultiRange Range { get; private set; }
|
||||||
@ -1555,6 +1559,20 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
|
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
|
||||||
}
|
}
|
||||||
_referenceCount++;
|
_referenceCount++;
|
||||||
|
|
||||||
|
if (ShortCacheEntry != null)
|
||||||
|
{
|
||||||
|
_physicalMemory.TextureCache.RemoveShortCache(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the texture has one reference left, and will delete on reference decrement.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if there is one reference remaining, false otherwise</returns>
|
||||||
|
public bool HasOneReference()
|
||||||
|
{
|
||||||
|
return _referenceCount == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -1624,6 +1642,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_poolOwners.Clear();
|
_poolOwners.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ShortCacheEntry != null && _context.IsGpuThread())
|
||||||
|
{
|
||||||
|
// If this is called from another thread (unmapped), the short cache will
|
||||||
|
// have to remove this texture on a future tick.
|
||||||
|
|
||||||
|
_physicalMemory.TextureCache.RemoveShortCache(this);
|
||||||
|
}
|
||||||
|
|
||||||
InvalidatedSequence++;
|
InvalidatedSequence++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -894,6 +894,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempt to find a texture on the short duration cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="descriptor">The texture descriptor</param>
|
||||||
|
/// <returns>The texture if found, null otherwise</returns>
|
||||||
|
public Texture FindShortCache(in TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
return _cache.FindShortCache(descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null.
|
/// Tries to find an existing texture matching the given buffer copy destination. If none is found, returns null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -1178,6 +1188,33 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_cache.RemoveDeferred(texture);
|
_cache.RemoveDeferred(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a texture to the short duration cache. This typically keeps it alive for two ticks.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to add to the short cache</param>
|
||||||
|
/// <param name="descriptor">Last used texture descriptor</param>
|
||||||
|
public void AddShortCache(Texture texture, ref TextureDescriptor descriptor)
|
||||||
|
{
|
||||||
|
_cache.AddShortCache(texture, ref descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a texture from the short duration cache.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to remove from the short cache</param>
|
||||||
|
public void RemoveShortCache(Texture texture)
|
||||||
|
{
|
||||||
|
_cache.RemoveShortCache(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ticks periodic elements of the texture cache.
|
||||||
|
/// </summary>
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
_cache.ProcessShortCache();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes all textures and samplers in the cache.
|
/// Disposes all textures and samplers in the cache.
|
||||||
/// It's an error to use the texture cache after disposal.
|
/// It's an error to use the texture cache after disposal.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.Intrinsics;
|
using System.Runtime.Intrinsics;
|
||||||
|
|
||||||
@ -6,7 +7,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maxwell texture descriptor, as stored on the GPU texture pool memory region.
|
/// Maxwell texture descriptor, as stored on the GPU texture pool memory region.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
struct TextureDescriptor : ITextureDescriptor
|
struct TextureDescriptor : ITextureDescriptor, IEquatable<TextureDescriptor>
|
||||||
{
|
{
|
||||||
#pragma warning disable CS0649
|
#pragma warning disable CS0649
|
||||||
public uint Word0;
|
public uint Word0;
|
||||||
@ -249,5 +250,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).Equals(Unsafe.As<TextureDescriptor, Vector256<byte>>(ref other));
|
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).Equals(Unsafe.As<TextureDescriptor, Vector256<byte>>(ref other));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if two descriptors are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The descriptor to compare against</param>
|
||||||
|
/// <returns>True if they are equal, false otherwise</returns>
|
||||||
|
public bool Equals(TextureDescriptor other)
|
||||||
|
{
|
||||||
|
return Equals(ref other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a hash code for this descriptor.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The hash code for this descriptor.</returns>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Unsafe.As<TextureDescriptor, Vector256<byte>>(ref this).GetHashCode();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1420,6 +1420,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="size">The size of the flushing memory access</param>
|
/// <param name="size">The size of the flushing memory access</param>
|
||||||
public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
|
public void FlushAction(TextureGroupHandle handle, ulong address, ulong size)
|
||||||
{
|
{
|
||||||
|
// If the page size is larger than 4KB, we will have a lot of false positives for flushing.
|
||||||
|
// Let's avoid flushing textures that are unlikely to be read from CPU to improve performance
|
||||||
|
// on those platforms.
|
||||||
|
if (!_physicalMemory.Supports4KBPages && !Storage.Info.IsLinear && !_context.IsGpuThread())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
// There is a small gap here where the action is removed but _actionRegistered is still 1.
|
||||||
// In this case it will skip registering the action, but here we are already handling it,
|
// In this case it will skip registering the action, but here we are already handling it,
|
||||||
// so there shouldn't be any issue as it's the same handler for all actions.
|
// so there shouldn't be any issue as it's the same handler for all actions.
|
||||||
|
@ -52,16 +52,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
texture = PhysicalMemory.TextureCache.FindShortCache(descriptor);
|
||||||
|
|
||||||
ProcessDereferenceQueue();
|
|
||||||
|
|
||||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
|
||||||
|
|
||||||
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
return ref descriptor;
|
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||||
|
|
||||||
|
ProcessDereferenceQueue();
|
||||||
|
|
||||||
|
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||||
|
|
||||||
|
// If this happens, then the texture address is invalid, we can't add it to the cache.
|
||||||
|
if (texture == null)
|
||||||
|
{
|
||||||
|
return ref descriptor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
texture.IncrementReferenceCount(this, id);
|
texture.IncrementReferenceCount(this, id);
|
||||||
@ -208,15 +213,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (texture != null)
|
if (texture != null)
|
||||||
{
|
{
|
||||||
TextureDescriptor descriptor = PhysicalMemory.Read<TextureDescriptor>(address);
|
ref TextureDescriptor cachedDescriptor = ref DescriptorCache[id];
|
||||||
|
ref readonly TextureDescriptor descriptor = ref GetDescriptorRefAddress(address);
|
||||||
|
|
||||||
// If the descriptors are the same, the texture is the same,
|
// If the descriptors are the same, the texture is the same,
|
||||||
// we don't need to remove as it was not modified. Just continue.
|
// we don't need to remove as it was not modified. Just continue.
|
||||||
if (descriptor.Equals(ref DescriptorCache[id]))
|
if (descriptor.Equals(ref cachedDescriptor))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (texture.HasOneReference())
|
||||||
|
{
|
||||||
|
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
texture.DecrementReferenceCount(this, id);
|
texture.DecrementReferenceCount(this, id);
|
||||||
|
|
||||||
Items[id] = null;
|
Items[id] = null;
|
||||||
|
@ -470,19 +470,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address < Address)
|
ulong maxAddress = Math.Max(address, Address);
|
||||||
|
ulong minEndAddress = Math.Min(address + size, Address + Size);
|
||||||
|
|
||||||
|
if (maxAddress >= minEndAddress)
|
||||||
{
|
{
|
||||||
address = Address;
|
// Access doesn't overlap.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong maxSize = Address + Size - address;
|
ForceDirty(maxAddress, minEndAddress - maxAddress);
|
||||||
|
|
||||||
if (size > maxSize)
|
|
||||||
{
|
|
||||||
size = maxSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
ForceDirty(address, size);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
private IVirtualMemoryManagerTracked _cpuMemory;
|
private IVirtualMemoryManagerTracked _cpuMemory;
|
||||||
private int _referenceCount;
|
private int _referenceCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whenever the memory manager supports 4KB pages.
|
||||||
|
/// </summary>
|
||||||
|
public bool Supports4KBPages => _cpuMemory.Supports4KBPages;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// In-memory shader cache.
|
/// In-memory shader cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -204,6 +204,8 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
|
|
||||||
Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range);
|
Texture texture = pt.Cache.FindOrCreateTexture(null, TextureSearchFlags.WithUpscale, pt.Info, 0, null, pt.Range);
|
||||||
|
|
||||||
|
pt.Cache.Tick();
|
||||||
|
|
||||||
texture.SynchronizeMemory();
|
texture.SynchronizeMemory();
|
||||||
|
|
||||||
ImageCrop crop = pt.Crop;
|
ImageCrop crop = pt.Crop;
|
||||||
|
@ -79,21 +79,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
|
var sampleCountFlags = ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)info.Samples);
|
||||||
|
|
||||||
var usage = DefaultUsageFlags;
|
var usage = GetImageUsageFromFormat(info.Format);
|
||||||
|
|
||||||
if (info.Format.IsDepthOrStencil())
|
|
||||||
{
|
|
||||||
usage |= ImageUsageFlags.DepthStencilAttachmentBit;
|
|
||||||
}
|
|
||||||
else if (info.Format.IsRtColorCompatible())
|
|
||||||
{
|
|
||||||
usage |= ImageUsageFlags.ColorAttachmentBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.Format.IsImageCompatible())
|
|
||||||
{
|
|
||||||
usage |= ImageUsageFlags.StorageBit;
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags = ImageCreateFlags.CreateMutableFormatBit;
|
var flags = ImageCreateFlags.CreateMutableFormatBit;
|
||||||
|
|
||||||
@ -306,6 +292,27 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static ImageUsageFlags GetImageUsageFromFormat(GAL.Format format)
|
||||||
|
{
|
||||||
|
var usage = DefaultUsageFlags;
|
||||||
|
|
||||||
|
if (format.IsDepthOrStencil())
|
||||||
|
{
|
||||||
|
usage |= ImageUsageFlags.DepthStencilAttachmentBit;
|
||||||
|
}
|
||||||
|
else if (format.IsRtColorCompatible())
|
||||||
|
{
|
||||||
|
usage |= ImageUsageFlags.ColorAttachmentBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format.IsImageCompatible())
|
||||||
|
{
|
||||||
|
usage |= ImageUsageFlags.StorageBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return usage;
|
||||||
|
}
|
||||||
|
|
||||||
public static SampleCountFlags ConvertToSampleCountFlags(SampleCountFlags supportedSampleCounts, uint samples)
|
public static SampleCountFlags ConvertToSampleCountFlags(SampleCountFlags supportedSampleCounts, uint samples)
|
||||||
{
|
{
|
||||||
if (samples == 0 || samples > (uint)SampleCountFlags.Count64Bit)
|
if (samples == 0 || samples > (uint)SampleCountFlags.Count64Bit)
|
||||||
|
@ -54,6 +54,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
gd.Textures.Add(this);
|
gd.Textures.Add(this);
|
||||||
|
|
||||||
var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
|
var format = _gd.FormatCapabilities.ConvertToVkFormat(info.Format);
|
||||||
|
var usage = TextureStorage.GetImageUsageFromFormat(info.Format);
|
||||||
var levels = (uint)info.Levels;
|
var levels = (uint)info.Levels;
|
||||||
var layers = (uint)info.GetLayers();
|
var layers = (uint)info.GetLayers();
|
||||||
|
|
||||||
@ -94,7 +95,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers);
|
var subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, layers);
|
||||||
var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers);
|
var subresourceRangeDepth = new ImageSubresourceRange(aspectFlagsDepth, (uint)firstLevel, levels, (uint)firstLayer, layers);
|
||||||
|
|
||||||
unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags = 0)
|
unsafe Auto<DisposableImageView> CreateImageView(ComponentMapping cm, ImageSubresourceRange sr, ImageViewType viewType, ImageUsageFlags usageFlags)
|
||||||
{
|
{
|
||||||
var usage = new ImageViewUsageCreateInfo()
|
var usage = new ImageViewUsageCreateInfo()
|
||||||
{
|
{
|
||||||
@ -110,14 +111,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Format = format,
|
Format = format,
|
||||||
Components = cm,
|
Components = cm,
|
||||||
SubresourceRange = sr,
|
SubresourceRange = sr,
|
||||||
PNext = usageFlags == 0 ? null : &usage
|
PNext = &usage
|
||||||
};
|
};
|
||||||
|
|
||||||
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
|
gd.Api.CreateImageView(device, imageCreateInfo, null, out var imageView).ThrowOnError();
|
||||||
return new Auto<DisposableImageView>(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage());
|
return new Auto<DisposableImageView>(new DisposableImageView(gd.Api, device, imageView), null, storage.GetImage());
|
||||||
}
|
}
|
||||||
|
|
||||||
_imageView = CreateImageView(componentMapping, subresourceRange, type);
|
_imageView = CreateImageView(componentMapping, subresourceRange, type, ImageUsageFlags.SampledBit);
|
||||||
|
|
||||||
// Framebuffer attachments and storage images requires a identity component mapping.
|
// Framebuffer attachments and storage images requires a identity component mapping.
|
||||||
var identityComponentMapping = new ComponentMapping(
|
var identityComponentMapping = new ComponentMapping(
|
||||||
@ -126,7 +127,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ComponentSwizzle.B,
|
ComponentSwizzle.B,
|
||||||
ComponentSwizzle.A);
|
ComponentSwizzle.A);
|
||||||
|
|
||||||
_imageViewIdentity = CreateImageView(identityComponentMapping, subresourceRangeDepth, type);
|
_imageViewIdentity = CreateImageView(identityComponentMapping, subresourceRangeDepth, type, usage);
|
||||||
|
|
||||||
// Framebuffer attachments also require 3D textures to be bound as 2D array.
|
// Framebuffer attachments also require 3D textures to be bound as 2D array.
|
||||||
if (info.Target == Target.Texture3D)
|
if (info.Target == Target.Texture3D)
|
||||||
@ -144,7 +145,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
|
subresourceRange = new ImageSubresourceRange(aspectFlags, (uint)firstLevel, levels, (uint)firstLayer, (uint)info.Depth);
|
||||||
|
|
||||||
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray);
|
_imageView2dArray = CreateImageView(identityComponentMapping, subresourceRange, ImageViewType.Type2DArray, usage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ using Ryujinx.Common.Logging;
|
|||||||
using Ryujinx.HLE.HOS;
|
using Ryujinx.HLE.HOS;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Text;
|
using System.Buffers.Text;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@ -35,7 +36,8 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
public EmulatedGameCard GameCard { get; private set; }
|
public EmulatedGameCard GameCard { get; private set; }
|
||||||
public EmulatedSdCard SdCard { get; private set; }
|
public EmulatedSdCard SdCard { get; private set; }
|
||||||
public ModLoader ModLoader { get; private set; }
|
public ModLoader ModLoader { get; private set; }
|
||||||
public Stream RomFs { get; private set; }
|
|
||||||
|
private readonly ConcurrentDictionary<ulong, Stream> _romFsByPid;
|
||||||
|
|
||||||
private static bool _isInitialized = false;
|
private static bool _isInitialized = false;
|
||||||
|
|
||||||
@ -55,17 +57,34 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
ReloadKeySet();
|
ReloadKeySet();
|
||||||
ModLoader = new ModLoader(); // Should only be created once
|
ModLoader = new ModLoader(); // Should only be created once
|
||||||
|
_romFsByPid = new ConcurrentDictionary<ulong, Stream>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadRomFs(string fileName)
|
public void LoadRomFs(ulong pid, string fileName)
|
||||||
{
|
{
|
||||||
RomFs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
var romfsStream = new FileStream(fileName, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
_romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) =>
|
||||||
|
{
|
||||||
|
oldStream.Close();
|
||||||
|
|
||||||
|
return romfsStream;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRomFs(Stream romfsStream)
|
public void SetRomFs(ulong pid, Stream romfsStream)
|
||||||
{
|
{
|
||||||
RomFs?.Close();
|
_romFsByPid.AddOrUpdate(pid, romfsStream, (pid, oldStream) =>
|
||||||
RomFs = romfsStream;
|
{
|
||||||
|
oldStream.Close();
|
||||||
|
|
||||||
|
return romfsStream;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Stream GetRomFs(ulong pid)
|
||||||
|
{
|
||||||
|
return _romFsByPid[pid];
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetFullPath(string basePath, string fileName)
|
public string GetFullPath(string basePath, string fileName)
|
||||||
@ -583,7 +602,12 @@ namespace Ryujinx.HLE.FileSystem
|
|||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
RomFs?.Dispose();
|
foreach (var stream in _romFsByPid.Values)
|
||||||
|
{
|
||||||
|
stream.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
_romFsByPid.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -76,11 +76,6 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
public void LoadCart(string exeFsDir, string romFsFile = null)
|
public void LoadCart(string exeFsDir, string romFsFile = null)
|
||||||
{
|
{
|
||||||
if (romFsFile != null)
|
|
||||||
{
|
|
||||||
_device.Configuration.VirtualFileSystem.LoadRomFs(romFsFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
|
LocalFileSystem codeFs = new LocalFileSystem(exeFsDir);
|
||||||
|
|
||||||
MetaLoader metaData = ReadNpdm(codeFs);
|
MetaLoader metaData = ReadNpdm(codeFs);
|
||||||
@ -95,7 +90,12 @@ namespace Ryujinx.HLE.HOS
|
|||||||
EnsureSaveData(new ApplicationId(TitleId));
|
EnsureSaveData(new ApplicationId(TitleId));
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadExeFs(codeFs, string.Empty, metaData);
|
ulong pid = LoadExeFs(codeFs, string.Empty, metaData);
|
||||||
|
|
||||||
|
if (romFsFile != null)
|
||||||
|
{
|
||||||
|
_device.Configuration.VirtualFileSystem.LoadRomFs(pid, romFsFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
|
public static (Nca main, Nca patch, Nca control) GetGameData(VirtualFileSystem fileSystem, PartitionFileSystem pfs, int programIndex)
|
||||||
@ -491,6 +491,8 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
_displayVersion = displayVersion;
|
_displayVersion = displayVersion;
|
||||||
|
|
||||||
|
ulong pid = LoadExeFs(codeFs, displayVersion, metaData);
|
||||||
|
|
||||||
if (dataStorage == null)
|
if (dataStorage == null)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
|
Logger.Warning?.Print(LogClass.Loader, "No RomFS found in NCA");
|
||||||
@ -499,7 +501,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
{
|
{
|
||||||
IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
|
IStorage newStorage = _device.Configuration.VirtualFileSystem.ModLoader.ApplyRomFsMods(TitleId, dataStorage);
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.SetRomFs(newStorage.AsStream(FileAccess.Read));
|
_device.Configuration.VirtualFileSystem.SetRomFs(pid, newStorage.AsStream(FileAccess.Read));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't create save data for system programs.
|
// Don't create save data for system programs.
|
||||||
@ -510,8 +512,6 @@ namespace Ryujinx.HLE.HOS
|
|||||||
EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
|
EnsureSaveData(new ApplicationId(TitleId & ~0xFul));
|
||||||
}
|
}
|
||||||
|
|
||||||
LoadExeFs(codeFs, displayVersion, metaData);
|
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
|
Logger.Info?.Print(LogClass.Loader, $"Application Loaded: {TitleName} v{DisplayVersion} [{TitleIdText}] [{(TitleIs64Bit ? "64-bit" : "32-bit")}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -579,7 +579,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false)
|
private ulong LoadExeFs(IFileSystem codeFs, string displayVersion, MetaLoader metaData = null, bool isHomebrew = false)
|
||||||
{
|
{
|
||||||
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
|
if (_device.Configuration.VirtualFileSystem.ModLoader.ReplaceExefsPartition(TitleId, ref codeFs))
|
||||||
{
|
{
|
||||||
@ -654,6 +654,8 @@ namespace Ryujinx.HLE.HOS
|
|||||||
DiskCacheLoadState = result.DiskCacheLoadState;
|
DiskCacheLoadState = result.DiskCacheLoadState;
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
||||||
|
|
||||||
|
return result.ProcessId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadProgram(string filePath)
|
public void LoadProgram(string filePath)
|
||||||
@ -665,6 +667,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
bool isNro = Path.GetExtension(filePath).ToLower() == ".nro";
|
||||||
|
|
||||||
IExecutable executable;
|
IExecutable executable;
|
||||||
|
Stream romfsStream = null;
|
||||||
|
|
||||||
if (isNro)
|
if (isNro)
|
||||||
{
|
{
|
||||||
@ -697,7 +700,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
if (romfsSize != 0)
|
if (romfsSize != 0)
|
||||||
{
|
{
|
||||||
_device.Configuration.VirtualFileSystem.SetRomFs(new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset));
|
romfsStream = new HomebrewRomFsStream(input, obj.FileSize + (long)romfsOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nacpSize != 0)
|
if (nacpSize != 0)
|
||||||
@ -758,6 +761,11 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable);
|
ProgramLoadResult result = ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, programInfo, executables: executable);
|
||||||
|
|
||||||
|
if (romfsStream != null)
|
||||||
|
{
|
||||||
|
_device.Configuration.VirtualFileSystem.SetRomFs(result.ProcessId, romfsStream);
|
||||||
|
}
|
||||||
|
|
||||||
DiskCacheLoadState = result.DiskCacheLoadState;
|
DiskCacheLoadState = result.DiskCacheLoadState;
|
||||||
|
|
||||||
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
_device.Configuration.VirtualFileSystem.ModLoader.LoadCheats(TitleId, result.TamperInfo, _device.TamperMachine);
|
||||||
|
@ -84,7 +84,7 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||||||
KernelConstants.UserSlabHeapItemSize,
|
KernelConstants.UserSlabHeapItemSize,
|
||||||
KernelConstants.UserSlabHeapSize);
|
KernelConstants.UserSlabHeapSize);
|
||||||
|
|
||||||
memory.Commit(KernelConstants.UserSlabHeapBase - DramMemoryMap.DramBase, KernelConstants.UserSlabHeapSize);
|
CommitMemory(KernelConstants.UserSlabHeapBase - DramMemoryMap.DramBase, KernelConstants.UserSlabHeapSize);
|
||||||
|
|
||||||
CriticalSection = new KCriticalSection(this);
|
CriticalSection = new KCriticalSection(this);
|
||||||
Schedulers = new KScheduler[KScheduler.CpuCoresCount];
|
Schedulers = new KScheduler[KScheduler.CpuCoresCount];
|
||||||
@ -119,6 +119,17 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||||||
new Thread(PreemptionThreadStart) { Name = "HLE.PreemptionThread" }.Start();
|
new Thread(PreemptionThreadStart) { Name = "HLE.PreemptionThread" }.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CommitMemory(ulong address, ulong size)
|
||||||
|
{
|
||||||
|
ulong alignment = MemoryBlock.GetPageSize();
|
||||||
|
ulong endAddress = address + size;
|
||||||
|
|
||||||
|
address &= ~(alignment - 1);
|
||||||
|
endAddress = (endAddress + (alignment - 1)) & ~(alignment - 1);
|
||||||
|
|
||||||
|
Memory.Commit(address, endAddress - address);
|
||||||
|
}
|
||||||
|
|
||||||
public ulong NewThreadUid()
|
public ulong NewThreadUid()
|
||||||
{
|
{
|
||||||
return Interlocked.Increment(ref _threadUid) - 1;
|
return Interlocked.Increment(ref _threadUid) - 1;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
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;
|
||||||
@ -71,4 +70,4 @@ namespace Ryujinx.HLE.HOS.Kernel
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -64,7 +64,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
if (address != 0)
|
if (address != 0)
|
||||||
{
|
{
|
||||||
IncrementPagesReferenceCount(address, pagesCount);
|
IncrementPagesReferenceCount(address, pagesCount);
|
||||||
context.Memory.Commit(address - DramMemoryMap.DramBase, pagesCount * KPageTableBase.PageSize);
|
context.CommitMemory(address - DramMemoryMap.DramBase, pagesCount * KPageTableBase.PageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using Ryujinx.Memory;
|
using Ryujinx.Memory;
|
||||||
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
@ -9,11 +11,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
{
|
{
|
||||||
private readonly IVirtualMemoryManager _cpuMemory;
|
private readonly IVirtualMemoryManager _cpuMemory;
|
||||||
|
|
||||||
|
protected override bool Supports4KBPages => _cpuMemory.Supports4KBPages;
|
||||||
|
|
||||||
public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context)
|
public KPageTable(KernelContext context, IVirtualMemoryManager cpuMemory) : base(context)
|
||||||
{
|
{
|
||||||
_cpuMemory = cpuMemory;
|
_cpuMemory = cpuMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
return _cpuMemory.GetHostRegions(va, size);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override void GetPhysicalRegions(ulong va, ulong size, KPageList pageList)
|
protected override void GetPhysicalRegions(ulong va, ulong size, KPageList pageList)
|
||||||
{
|
{
|
||||||
@ -43,7 +53,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = MapPages(dst, pageList, newDstPermission, false, 0);
|
result = MapPages(dst, pageList, newDstPermission, MemoryMapFlags.Private, false, 0);
|
||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
{
|
{
|
||||||
@ -81,7 +91,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
{
|
{
|
||||||
Result mapResult = MapPages(dst, dstPageList, oldDstPermission, false, 0);
|
Result mapResult = MapPages(dst, dstPageList, oldDstPermission, MemoryMapFlags.Private, false, 0);
|
||||||
Debug.Assert(mapResult == Result.Success);
|
Debug.Assert(mapResult == Result.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,13 +99,20 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Result MapPages(ulong dstVa, ulong pagesCount, ulong srcPa, KMemoryPermission permission, bool shouldFillPages, byte fillValue)
|
protected override Result MapPages(
|
||||||
|
ulong dstVa,
|
||||||
|
ulong pagesCount,
|
||||||
|
ulong srcPa,
|
||||||
|
KMemoryPermission permission,
|
||||||
|
MemoryMapFlags flags,
|
||||||
|
bool shouldFillPages,
|
||||||
|
byte fillValue)
|
||||||
{
|
{
|
||||||
ulong size = pagesCount * PageSize;
|
ulong size = pagesCount * PageSize;
|
||||||
|
|
||||||
Context.Memory.Commit(srcPa - DramMemoryMap.DramBase, size);
|
Context.CommitMemory(srcPa - DramMemoryMap.DramBase, size);
|
||||||
|
|
||||||
_cpuMemory.Map(dstVa, srcPa - DramMemoryMap.DramBase, size);
|
_cpuMemory.Map(dstVa, srcPa - DramMemoryMap.DramBase, size, flags);
|
||||||
|
|
||||||
if (DramMemoryMap.IsHeapPhysicalAddress(srcPa))
|
if (DramMemoryMap.IsHeapPhysicalAddress(srcPa))
|
||||||
{
|
{
|
||||||
@ -111,7 +128,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Result MapPages(ulong address, KPageList pageList, KMemoryPermission permission, bool shouldFillPages, byte fillValue)
|
protected override Result MapPages(
|
||||||
|
ulong address,
|
||||||
|
KPageList pageList,
|
||||||
|
KMemoryPermission permission,
|
||||||
|
MemoryMapFlags flags,
|
||||||
|
bool shouldFillPages,
|
||||||
|
byte fillValue)
|
||||||
{
|
{
|
||||||
using var scopedPageList = new KScopedPageList(Context.MemoryManager, pageList);
|
using var scopedPageList = new KScopedPageList(Context.MemoryManager, pageList);
|
||||||
|
|
||||||
@ -122,9 +145,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
ulong addr = pageNode.Address - DramMemoryMap.DramBase;
|
ulong addr = pageNode.Address - DramMemoryMap.DramBase;
|
||||||
ulong size = pageNode.PagesCount * PageSize;
|
ulong size = pageNode.PagesCount * PageSize;
|
||||||
|
|
||||||
Context.Memory.Commit(addr, size);
|
Context.CommitMemory(addr, size);
|
||||||
|
|
||||||
_cpuMemory.Map(currentVa, addr, size);
|
_cpuMemory.Map(currentVa, addr, size, flags);
|
||||||
|
|
||||||
if (shouldFillPages)
|
if (shouldFillPages)
|
||||||
{
|
{
|
||||||
@ -139,6 +162,21 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
return Result.Success;
|
return Result.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
protected override Result MapForeign(IEnumerable<HostMemoryRange> regions, ulong va, ulong size)
|
||||||
|
{
|
||||||
|
ulong offset = 0;
|
||||||
|
|
||||||
|
foreach (var region in regions)
|
||||||
|
{
|
||||||
|
_cpuMemory.MapForeign(va + offset, region.Address, region.Size);
|
||||||
|
|
||||||
|
offset += region.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected override Result Unmap(ulong address, ulong pagesCount)
|
protected override Result Unmap(ulong address, ulong pagesCount)
|
||||||
{
|
{
|
||||||
@ -188,4 +226,4 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
_cpuMemory.Write(va, data);
|
_cpuMemory.Write(va, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,8 @@
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
using Ryujinx.Memory.Range;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -29,6 +31,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
private const int MaxBlocksNeededForInsertion = 2;
|
private const int MaxBlocksNeededForInsertion = 2;
|
||||||
|
|
||||||
protected readonly KernelContext Context;
|
protected readonly KernelContext Context;
|
||||||
|
protected virtual bool Supports4KBPages => true;
|
||||||
|
|
||||||
public ulong AddrSpaceStart { get; private set; }
|
public ulong AddrSpaceStart { get; private set; }
|
||||||
public ulong AddrSpaceEnd { get; private set; }
|
public ulong AddrSpaceEnd { get; private set; }
|
||||||
@ -366,7 +369,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
return KernelResult.OutOfResource;
|
return KernelResult.OutOfResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
Result result = MapPages(address, pageList, permission);
|
Result result = MapPages(address, pageList, permission, MemoryMapFlags.None);
|
||||||
|
|
||||||
if (result == Result.Success)
|
if (result == Result.Success)
|
||||||
{
|
{
|
||||||
@ -502,7 +505,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
|
|
||||||
if (paIsValid)
|
if (paIsValid)
|
||||||
{
|
{
|
||||||
result = MapPages(address, pagesCount, srcPa, permission);
|
result = MapPages(address, pagesCount, srcPa, permission, MemoryMapFlags.Private);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -565,7 +568,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
|
|
||||||
using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
|
using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
|
||||||
|
|
||||||
return MapPages(address, pageList, permission);
|
return MapPages(address, pageList, permission, MemoryMapFlags.Private);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result MapProcessCodeMemory(ulong dst, ulong src, ulong size)
|
public Result MapProcessCodeMemory(ulong dst, ulong src, ulong size)
|
||||||
@ -746,7 +749,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
return KernelResult.InvalidMemState;
|
return KernelResult.InvalidMemState;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = MapPages(_currentHeapAddr, pageList, KMemoryPermission.ReadAndWrite, true, (byte)_heapFillValue);
|
result = MapPages(_currentHeapAddr, pageList, KMemoryPermission.ReadAndWrite, MemoryMapFlags.Private, true, (byte)_heapFillValue);
|
||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
{
|
{
|
||||||
@ -1334,7 +1337,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
|
|
||||||
ulong currentPagesCount = Math.Min(srcPaPages, dstVaPages);
|
ulong currentPagesCount = Math.Min(srcPaPages, dstVaPages);
|
||||||
|
|
||||||
MapPages(dstVa, currentPagesCount, srcPa, KMemoryPermission.ReadAndWrite);
|
MapPages(dstVa, currentPagesCount, srcPa, KMemoryPermission.ReadAndWrite, MemoryMapFlags.Private);
|
||||||
|
|
||||||
dstVa += currentPagesCount * PageSize;
|
dstVa += currentPagesCount * PageSize;
|
||||||
srcPa += currentPagesCount * PageSize;
|
srcPa += currentPagesCount * PageSize;
|
||||||
@ -1878,7 +1881,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
Context.Memory.Fill(GetDramAddressFromPa(firstPageFillAddress), unusedSizeAfter, (byte)_ipcFillValue);
|
Context.Memory.Fill(GetDramAddressFromPa(firstPageFillAddress), unusedSizeAfter, (byte)_ipcFillValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
Result result = MapPages(currentVa, 1, dstFirstPagePa, permission);
|
Result result = MapPages(currentVa, 1, dstFirstPagePa, permission, MemoryMapFlags.Private);
|
||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
{
|
{
|
||||||
@ -1894,10 +1897,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
{
|
{
|
||||||
ulong alignedSize = endAddrTruncated - addressRounded;
|
ulong alignedSize = endAddrTruncated - addressRounded;
|
||||||
|
|
||||||
KPageList pageList = new KPageList();
|
Result result;
|
||||||
srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList);
|
|
||||||
|
|
||||||
Result result = MapPages(currentVa, pageList, permission);
|
if (srcPageTable.Supports4KBPages)
|
||||||
|
{
|
||||||
|
KPageList pageList = new KPageList();
|
||||||
|
srcPageTable.GetPhysicalRegions(addressRounded, alignedSize, pageList);
|
||||||
|
|
||||||
|
result = MapPages(currentVa, pageList, permission, MemoryMapFlags.None);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = MapForeign(srcPageTable.GetHostRegions(addressRounded, alignedSize), currentVa, alignedSize);
|
||||||
|
}
|
||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
{
|
{
|
||||||
@ -1932,7 +1944,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
|
|
||||||
Context.Memory.Fill(GetDramAddressFromPa(lastPageFillAddr), unusedSizeAfter, (byte)_ipcFillValue);
|
Context.Memory.Fill(GetDramAddressFromPa(lastPageFillAddr), unusedSizeAfter, (byte)_ipcFillValue);
|
||||||
|
|
||||||
Result result = MapPages(currentVa, 1, dstLastPagePa, permission);
|
Result result = MapPages(currentVa, 1, dstLastPagePa, permission, MemoryMapFlags.Private);
|
||||||
|
|
||||||
if (result != Result.Success)
|
if (result != Result.Success)
|
||||||
{
|
{
|
||||||
@ -2884,6 +2896,16 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
return StackRegionStart > address || address + size - 1 > StackRegionEnd - 1;
|
return StackRegionStart > address || address + size - 1 > StackRegionEnd - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the host regions that make up the given virtual address region.
|
||||||
|
/// If any part of the virtual region is unmapped, null is returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address of the range</param>
|
||||||
|
/// <param name="size">Size of the range</param>
|
||||||
|
/// <returns>The host regions</returns>
|
||||||
|
/// <exception cref="Ryujinx.Memory.InvalidMemoryRegionException">Throw for unhandled invalid or unmapped memory accesses</exception>
|
||||||
|
protected abstract IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the physical regions that make up the given virtual address region.
|
/// Gets the physical regions that make up the given virtual address region.
|
||||||
/// If any part of the virtual region is unmapped, null is returned.
|
/// If any part of the virtual region is unmapped, null is returned.
|
||||||
@ -2936,10 +2958,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
/// <param name="pagesCount">Number of pages to map</param>
|
/// <param name="pagesCount">Number of pages to map</param>
|
||||||
/// <param name="srcPa">Physical address where the pages should be mapped. May be ignored if aliasing is not supported</param>
|
/// <param name="srcPa">Physical address where the pages should be mapped. May be ignored if aliasing is not supported</param>
|
||||||
/// <param name="permission">Permission of the region to be mapped</param>
|
/// <param name="permission">Permission of the region to be mapped</param>
|
||||||
|
/// <param name="flags">Flags controlling the memory map operation</param>
|
||||||
/// <param name="shouldFillPages">Indicate if the pages should be filled with the <paramref name="fillValue"/> value</param>
|
/// <param name="shouldFillPages">Indicate if the pages should be filled with the <paramref name="fillValue"/> value</param>
|
||||||
/// <param name="fillValue">The value used to fill pages when <paramref name="shouldFillPages"/> is set to true</param>
|
/// <param name="fillValue">The value used to fill pages when <paramref name="shouldFillPages"/> is set to true</param>
|
||||||
/// <returns>Result of the mapping operation</returns>
|
/// <returns>Result of the mapping operation</returns>
|
||||||
protected abstract Result MapPages(ulong dstVa, ulong pagesCount, ulong srcPa, KMemoryPermission permission, bool shouldFillPages = false, byte fillValue = 0);
|
protected abstract Result MapPages(
|
||||||
|
ulong dstVa,
|
||||||
|
ulong pagesCount,
|
||||||
|
ulong srcPa,
|
||||||
|
KMemoryPermission permission,
|
||||||
|
MemoryMapFlags flags,
|
||||||
|
bool shouldFillPages = false,
|
||||||
|
byte fillValue = 0);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a region of memory into the specified physical memory region.
|
/// Maps a region of memory into the specified physical memory region.
|
||||||
@ -2947,10 +2977,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
/// <param name="address">Destination virtual address that should be mapped</param>
|
/// <param name="address">Destination virtual address that should be mapped</param>
|
||||||
/// <param name="pageList">List of physical memory pages where the pages should be mapped. May be ignored if aliasing is not supported</param>
|
/// <param name="pageList">List of physical memory pages where the pages should be mapped. May be ignored if aliasing is not supported</param>
|
||||||
/// <param name="permission">Permission of the region to be mapped</param>
|
/// <param name="permission">Permission of the region to be mapped</param>
|
||||||
|
/// <param name="flags">Flags controlling the memory map operation</param>
|
||||||
/// <param name="shouldFillPages">Indicate if the pages should be filled with the <paramref name="fillValue"/> value</param>
|
/// <param name="shouldFillPages">Indicate if the pages should be filled with the <paramref name="fillValue"/> value</param>
|
||||||
/// <param name="fillValue">The value used to fill pages when <paramref name="shouldFillPages"/> is set to true</param>
|
/// <param name="fillValue">The value used to fill pages when <paramref name="shouldFillPages"/> is set to true</param>
|
||||||
/// <returns>Result of the mapping operation</returns>
|
/// <returns>Result of the mapping operation</returns>
|
||||||
protected abstract Result MapPages(ulong address, KPageList pageList, KMemoryPermission permission, bool shouldFillPages = false, byte fillValue = 0);
|
protected abstract Result MapPages(
|
||||||
|
ulong address,
|
||||||
|
KPageList pageList,
|
||||||
|
KMemoryPermission permission,
|
||||||
|
MemoryMapFlags flags,
|
||||||
|
bool shouldFillPages = false,
|
||||||
|
byte fillValue = 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps pages into an arbitrary host memory location.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="regions">Host regions to be mapped into the specified virtual memory region</param>
|
||||||
|
/// <param name="va">Destination virtual address of the range on this page table</param>
|
||||||
|
/// <param name="size">Size of the range</param>
|
||||||
|
/// <returns>Result of the mapping operation</returns>
|
||||||
|
protected abstract Result MapForeign(IEnumerable<HostMemoryRange> regions, ulong va, ulong size);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unmaps a region of memory that was previously mapped with one of the page mapping methods.
|
/// Unmaps a region of memory that was previously mapped with one of the page mapping methods.
|
||||||
|
@ -2,6 +2,7 @@ using Ryujinx.Common;
|
|||||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Memory;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||||
{
|
{
|
||||||
@ -48,7 +49,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
return KernelResult.InvalidPermission;
|
return KernelResult.InvalidPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission);
|
// On platforms with page size > 4 KB, this can fail due to the address not being page aligned,
|
||||||
|
// we can return an error to force the application to retry with a different address.
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return memoryManager.MapPages(address, _pageList, MemoryState.SharedMemory, permission);
|
||||||
|
}
|
||||||
|
catch (InvalidMemoryRegionException)
|
||||||
|
{
|
||||||
|
return KernelResult.InvalidMemState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Result UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process)
|
public Result UnmapFromProcess(KPageTableBase memoryManager, ulong address, ulong size, KProcess process)
|
||||||
|
@ -18,7 +18,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
|||||||
{
|
{
|
||||||
ulong address = pageNode.Address - DramMemoryMap.DramBase;
|
ulong address = pageNode.Address - DramMemoryMap.DramBase;
|
||||||
ulong size = pageNode.PagesCount * KPageTableBase.PageSize;
|
ulong size = pageNode.PagesCount * KPageTableBase.PageSize;
|
||||||
context.Memory.Commit(address, size);
|
context.CommitMemory(address, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,17 +41,19 @@ namespace Ryujinx.HLE.HOS
|
|||||||
|
|
||||||
struct ProgramLoadResult
|
struct ProgramLoadResult
|
||||||
{
|
{
|
||||||
public static ProgramLoadResult Failed => new ProgramLoadResult(false, null, null);
|
public static ProgramLoadResult Failed => new ProgramLoadResult(false, null, null, 0);
|
||||||
|
|
||||||
public readonly bool Success;
|
public readonly bool Success;
|
||||||
public readonly ProcessTamperInfo TamperInfo;
|
public readonly ProcessTamperInfo TamperInfo;
|
||||||
public readonly IDiskCacheLoadState DiskCacheLoadState;
|
public readonly IDiskCacheLoadState DiskCacheLoadState;
|
||||||
|
public readonly ulong ProcessId;
|
||||||
|
|
||||||
public ProgramLoadResult(bool success, ProcessTamperInfo tamperInfo, IDiskCacheLoadState diskCacheLoadState)
|
public ProgramLoadResult(bool success, ProcessTamperInfo tamperInfo, IDiskCacheLoadState diskCacheLoadState, ulong pid)
|
||||||
{
|
{
|
||||||
Success = success;
|
Success = success;
|
||||||
TamperInfo = tamperInfo;
|
TamperInfo = tamperInfo;
|
||||||
DiskCacheLoadState = diskCacheLoadState;
|
DiskCacheLoadState = diskCacheLoadState;
|
||||||
|
ProcessId = pid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,7 +368,7 @@ namespace Ryujinx.HLE.HOS
|
|||||||
process.MemoryManager.AliasRegionStart,
|
process.MemoryManager.AliasRegionStart,
|
||||||
process.MemoryManager.CodeRegionStart);
|
process.MemoryManager.CodeRegionStart);
|
||||||
|
|
||||||
return new ProgramLoadResult(true, tamperInfo, processContextFactory.DiskCacheLoadState);
|
return new ProgramLoadResult(true, tamperInfo, processContextFactory.DiskCacheLoadState, process.Pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
|
private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
|
||||||
|
@ -27,6 +27,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
|||||||
class IFileSystemProxy : DisposableIpcService
|
class IFileSystemProxy : DisposableIpcService
|
||||||
{
|
{
|
||||||
private SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> _baseFileSystemProxy;
|
private SharedRef<LibHac.FsSrv.Sf.IFileSystemProxy> _baseFileSystemProxy;
|
||||||
|
private ulong _pid;
|
||||||
|
|
||||||
public IFileSystemProxy(ServiceCtx context) : base(context.Device.System.FsServer)
|
public IFileSystemProxy(ServiceCtx context) : base(context.Device.System.FsServer)
|
||||||
{
|
{
|
||||||
@ -38,6 +39,8 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
|||||||
// SetCurrentProcess(u64, pid)
|
// SetCurrentProcess(u64, pid)
|
||||||
public ResultCode SetCurrentProcess(ServiceCtx context)
|
public ResultCode SetCurrentProcess(ServiceCtx context)
|
||||||
{
|
{
|
||||||
|
_pid = context.Request.HandleDesc.PId;
|
||||||
|
|
||||||
return ResultCode.Success;
|
return ResultCode.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -702,7 +705,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
|||||||
// OpenDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> dataStorage
|
// OpenDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage> dataStorage
|
||||||
public ResultCode OpenDataStorageByCurrentProcess(ServiceCtx context)
|
public ResultCode OpenDataStorageByCurrentProcess(ServiceCtx context)
|
||||||
{
|
{
|
||||||
var storage = context.Device.FileSystem.RomFs.AsStorage(true);
|
var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true);
|
||||||
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage);
|
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage);
|
||||||
using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref()));
|
using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref()));
|
||||||
|
|
||||||
@ -791,7 +794,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
|||||||
// OpenPatchDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage>
|
// OpenPatchDataStorageByCurrentProcess() -> object<nn::fssrv::sf::IStorage>
|
||||||
public ResultCode OpenPatchDataStorageByCurrentProcess(ServiceCtx context)
|
public ResultCode OpenPatchDataStorageByCurrentProcess(ServiceCtx context)
|
||||||
{
|
{
|
||||||
var storage = context.Device.FileSystem.RomFs.AsStorage(true);
|
var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true);
|
||||||
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage);
|
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage);
|
||||||
using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref()));
|
using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref()));
|
||||||
|
|
||||||
@ -811,7 +814,7 @@ namespace Ryujinx.HLE.HOS.Services.Fs
|
|||||||
throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex}).");
|
throw new NotImplementedException($"Accessing storage from other programs is not supported (program index = {programIndex}).");
|
||||||
}
|
}
|
||||||
|
|
||||||
var storage = context.Device.FileSystem.RomFs.AsStorage(true);
|
var storage = context.Device.FileSystem.GetRomFs(_pid).AsStorage(true);
|
||||||
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage);
|
using var sharedStorage = new SharedRef<LibHac.Fs.IStorage>(storage);
|
||||||
using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref()));
|
using var sfStorage = new SharedRef<IStorage>(new StorageInterfaceAdapter(ref sharedStorage.Ref()));
|
||||||
|
|
||||||
|
@ -10,6 +10,24 @@ namespace Ryujinx.HLE.HOS.Services.Pm
|
|||||||
{
|
{
|
||||||
public IDebugMonitorInterface(ServiceCtx context) { }
|
public IDebugMonitorInterface(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandHipc(4)]
|
||||||
|
// GetProgramId() -> sf::Out<ncm::ProgramId> out_process_id
|
||||||
|
public ResultCode GetApplicationProcessId(ServiceCtx context)
|
||||||
|
{
|
||||||
|
// TODO: Not correct as it shouldn't be directly using kernel objects here
|
||||||
|
foreach (KProcess process in context.Device.System.KernelContext.Processes.Values)
|
||||||
|
{
|
||||||
|
if (process.IsApplication)
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(process.Pid);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultCode.ProcessNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
[CommandHipc(65000)]
|
[CommandHipc(65000)]
|
||||||
// AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status
|
// AtmosphereGetProcessInfo(os::ProcessId process_id) -> sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status
|
||||||
public ResultCode GetProcessInfo(ServiceCtx context)
|
public ResultCode GetProcessInfo(ServiceCtx context)
|
||||||
|
@ -1,8 +1,28 @@
|
|||||||
namespace Ryujinx.HLE.HOS.Services.Pm
|
using Ryujinx.HLE.HOS.Kernel;
|
||||||
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
|
|
||||||
|
namespace Ryujinx.HLE.HOS.Services.Pm
|
||||||
{
|
{
|
||||||
[Service("pm:info")]
|
[Service("pm:info")]
|
||||||
class IInformationInterface : IpcService
|
class IInformationInterface : IpcService
|
||||||
{
|
{
|
||||||
public IInformationInterface(ServiceCtx context) { }
|
public IInformationInterface(ServiceCtx context) { }
|
||||||
|
|
||||||
|
[CommandHipc(0)]
|
||||||
|
// GetProgramId(os::ProcessId process_id) -> sf::Out<ncm::ProgramId> out
|
||||||
|
public ResultCode GetProgramId(ServiceCtx context)
|
||||||
|
{
|
||||||
|
ulong pid = context.RequestData.ReadUInt64();
|
||||||
|
|
||||||
|
// TODO: Not correct as it shouldn't be directly using kernel objects here
|
||||||
|
if (context.Device.System.KernelContext.Processes.TryGetValue(pid, out KProcess process))
|
||||||
|
{
|
||||||
|
context.ResponseData.Write(process.TitleId);
|
||||||
|
|
||||||
|
return ResultCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultCode.ProcessNotFound;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
17
Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs
Normal file
17
Ryujinx.HLE/HOS/Services/Pm/ResultCode.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Ryujinx.HLE.HOS.Services.Pm
|
||||||
|
{
|
||||||
|
enum ResultCode
|
||||||
|
{
|
||||||
|
ModuleId = 15,
|
||||||
|
ErrorCodeShift = 9,
|
||||||
|
|
||||||
|
Success = 0,
|
||||||
|
|
||||||
|
ProcessNotFound = (1 << ErrorCodeShift) | ModuleId,
|
||||||
|
AlreadyStarted = (2 << ErrorCodeShift) | ModuleId,
|
||||||
|
NotTerminated = (3 << ErrorCodeShift) | ModuleId,
|
||||||
|
DebugHookInUse = (4 << ErrorCodeShift) | ModuleId,
|
||||||
|
ApplicationRunning = (5 << ErrorCodeShift) | ModuleId,
|
||||||
|
InvalidSize = (6 << ErrorCodeShift) | ModuleId,
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ namespace Ryujinx.Memory.Tests
|
|||||||
{
|
{
|
||||||
public class MockVirtualMemoryManager : IVirtualMemoryManager
|
public class MockVirtualMemoryManager : IVirtualMemoryManager
|
||||||
{
|
{
|
||||||
|
public bool Supports4KBPages => true;
|
||||||
|
|
||||||
public bool NoMappings = false;
|
public bool NoMappings = false;
|
||||||
|
|
||||||
public event Action<ulong, ulong, MemoryPermission> OnProtect;
|
public event Action<ulong, ulong, MemoryPermission> OnProtect;
|
||||||
@ -14,7 +16,12 @@ namespace Ryujinx.Memory.Tests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Map(ulong va, ulong pa, ulong size)
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MapForeign(ulong va, nuint hostAddress, ulong size)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
@ -64,6 +71,11 @@ namespace Ryujinx.Memory.Tests
|
|||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IEnumerable<HostMemoryRange> IVirtualMemoryManager.GetHostRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerable<MemoryRange> IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size)
|
IEnumerable<MemoryRange> IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
return NoMappings ? new MemoryRange[0] : new MemoryRange[] { new MemoryRange(va, size) };
|
return NoMappings ? new MemoryRange[0] : new MemoryRange[] { new MemoryRange(va, size) };
|
||||||
|
@ -13,9 +13,12 @@ namespace Ryujinx.Memory
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock
|
public sealed class AddressSpaceManager : IVirtualMemoryManager, IWritableBlock
|
||||||
{
|
{
|
||||||
public const int PageBits = PageTable<ulong>.PageBits;
|
public const int PageBits = PageTable<nuint>.PageBits;
|
||||||
public const int PageSize = PageTable<ulong>.PageSize;
|
public const int PageSize = PageTable<nuint>.PageSize;
|
||||||
public const int PageMask = PageTable<ulong>.PageMask;
|
public const int PageMask = PageTable<nuint>.PageMask;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public bool Supports4KBPages => true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Address space width in bits.
|
/// Address space width in bits.
|
||||||
@ -25,7 +28,7 @@ namespace Ryujinx.Memory
|
|||||||
private readonly ulong _addressSpaceSize;
|
private readonly ulong _addressSpaceSize;
|
||||||
|
|
||||||
private readonly MemoryBlock _backingMemory;
|
private readonly MemoryBlock _backingMemory;
|
||||||
private readonly PageTable<ulong> _pageTable;
|
private readonly PageTable<nuint> _pageTable;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the memory manager.
|
/// Creates a new instance of the memory manager.
|
||||||
@ -46,17 +49,17 @@ namespace Ryujinx.Memory
|
|||||||
AddressSpaceBits = asBits;
|
AddressSpaceBits = asBits;
|
||||||
_addressSpaceSize = asSize;
|
_addressSpaceSize = asSize;
|
||||||
_backingMemory = backingMemory;
|
_backingMemory = backingMemory;
|
||||||
_pageTable = new PageTable<ulong>();
|
_pageTable = new PageTable<nuint>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Map(ulong va, ulong pa, ulong size)
|
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
|
||||||
{
|
{
|
||||||
AssertValidAddressAndSize(va, size);
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
while (size != 0)
|
while (size != 0)
|
||||||
{
|
{
|
||||||
_pageTable.Map(va, pa);
|
_pageTable.Map(va, (nuint)(ulong)_backingMemory.GetPointer(pa, PageSize));
|
||||||
|
|
||||||
va += PageSize;
|
va += PageSize;
|
||||||
pa += PageSize;
|
pa += PageSize;
|
||||||
@ -64,6 +67,21 @@ namespace Ryujinx.Memory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void MapForeign(ulong va, nuint hostPointer, ulong size)
|
||||||
|
{
|
||||||
|
AssertValidAddressAndSize(va, size);
|
||||||
|
|
||||||
|
while (size != 0)
|
||||||
|
{
|
||||||
|
_pageTable.Map(va, hostPointer);
|
||||||
|
|
||||||
|
va += PageSize;
|
||||||
|
hostPointer += PageSize;
|
||||||
|
size -= PageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Unmap(ulong va, ulong size)
|
public void Unmap(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -108,7 +126,7 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
if (IsContiguousAndMapped(va, data.Length))
|
if (IsContiguousAndMapped(va, data.Length))
|
||||||
{
|
{
|
||||||
data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length));
|
data.CopyTo(GetHostSpanContiguous(va, data.Length));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -116,22 +134,18 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
if ((va & PageMask) != 0)
|
if ((va & PageMask) != 0)
|
||||||
{
|
{
|
||||||
ulong pa = GetPhysicalAddressInternal(va);
|
|
||||||
|
|
||||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||||
|
|
||||||
data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
data.Slice(0, size).CopyTo(GetHostSpanContiguous(va, size));
|
||||||
|
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; offset < data.Length; offset += size)
|
for (; offset < data.Length; offset += size)
|
||||||
{
|
{
|
||||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
|
||||||
|
|
||||||
size = Math.Min(data.Length - offset, PageSize);
|
size = Math.Min(data.Length - offset, PageSize);
|
||||||
|
|
||||||
data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size));
|
data.Slice(offset, size).CopyTo(GetHostSpanContiguous(va + (ulong)offset, size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +168,7 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
if (IsContiguousAndMapped(va, size))
|
if (IsContiguousAndMapped(va, size))
|
||||||
{
|
{
|
||||||
return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size);
|
return GetHostSpanContiguous(va, size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -176,7 +190,7 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
if (IsContiguousAndMapped(va, size))
|
if (IsContiguousAndMapped(va, size))
|
||||||
{
|
{
|
||||||
return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size));
|
return new WritableRegion(null, va, new NativeMemoryManager<byte>((byte*)GetHostAddress(va), size).Memory);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -189,14 +203,14 @@ namespace Ryujinx.Memory
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public ref T GetRef<T>(ulong va) where T : unmanaged
|
public unsafe ref T GetRef<T>(ulong va) where T : unmanaged
|
||||||
{
|
{
|
||||||
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
if (!IsContiguous(va, Unsafe.SizeOf<T>()))
|
||||||
{
|
{
|
||||||
ThrowMemoryNotContiguous();
|
ThrowMemoryNotContiguous();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ref _backingMemory.GetRef<T>(GetPhysicalAddressInternal(va));
|
return ref *(T*)GetHostAddress(va);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -210,7 +224,7 @@ namespace Ryujinx.Memory
|
|||||||
return (int)(vaSpan / PageSize);
|
return (int)(vaSpan / PageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException();
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
|
private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va);
|
||||||
@ -232,7 +246,7 @@ namespace Ryujinx.Memory
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize))
|
if (GetHostAddress(va) + PageSize != GetHostAddress(va + PageSize))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -243,6 +257,17 @@ namespace Ryujinx.Memory
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
|
||||||
|
{
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<HostMemoryRange>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetHostRegionsImpl(va, size);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
|
||||||
{
|
{
|
||||||
@ -251,6 +276,39 @@ namespace Ryujinx.Memory
|
|||||||
return Enumerable.Empty<MemoryRange>();
|
return Enumerable.Empty<MemoryRange>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hostRegions = GetHostRegionsImpl(va, size);
|
||||||
|
if (hostRegions == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var regions = new MemoryRange[hostRegions.Count];
|
||||||
|
|
||||||
|
ulong backingStart = (ulong)_backingMemory.Pointer;
|
||||||
|
ulong backingEnd = backingStart + _backingMemory.Size;
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < regions.Length; i++)
|
||||||
|
{
|
||||||
|
var hostRegion = hostRegions[i];
|
||||||
|
|
||||||
|
if ((ulong)hostRegion.Address >= backingStart && (ulong)hostRegion.Address < backingEnd)
|
||||||
|
{
|
||||||
|
regions[count++] = new MemoryRange((ulong)hostRegion.Address - backingStart, hostRegion.Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count != regions.Length)
|
||||||
|
{
|
||||||
|
return new ArraySegment<MemoryRange>(regions, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
return regions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<HostMemoryRange> GetHostRegionsImpl(ulong va, ulong size)
|
||||||
|
{
|
||||||
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@ -258,9 +316,9 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
int pages = GetPagesCount(va, (uint)size, out va);
|
int pages = GetPagesCount(va, (uint)size, out va);
|
||||||
|
|
||||||
var regions = new List<MemoryRange>();
|
var regions = new List<HostMemoryRange>();
|
||||||
|
|
||||||
ulong regionStart = GetPhysicalAddressInternal(va);
|
nuint regionStart = GetHostAddress(va);
|
||||||
ulong regionSize = PageSize;
|
ulong regionSize = PageSize;
|
||||||
|
|
||||||
for (int page = 0; page < pages - 1; page++)
|
for (int page = 0; page < pages - 1; page++)
|
||||||
@ -270,12 +328,12 @@ namespace Ryujinx.Memory
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong newPa = GetPhysicalAddressInternal(va + PageSize);
|
nuint newHostAddress = GetHostAddress(va + PageSize);
|
||||||
|
|
||||||
if (GetPhysicalAddressInternal(va) + PageSize != newPa)
|
if (GetHostAddress(va) + PageSize != newHostAddress)
|
||||||
{
|
{
|
||||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
regions.Add(new HostMemoryRange(regionStart, regionSize));
|
||||||
regionStart = newPa;
|
regionStart = newHostAddress;
|
||||||
regionSize = 0;
|
regionSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +341,7 @@ namespace Ryujinx.Memory
|
|||||||
regionSize += PageSize;
|
regionSize += PageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
regions.Add(new MemoryRange(regionStart, regionSize));
|
regions.Add(new HostMemoryRange(regionStart, regionSize));
|
||||||
|
|
||||||
return regions;
|
return regions;
|
||||||
}
|
}
|
||||||
@ -301,22 +359,18 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
if ((va & PageMask) != 0)
|
if ((va & PageMask) != 0)
|
||||||
{
|
{
|
||||||
ulong pa = GetPhysicalAddressInternal(va);
|
|
||||||
|
|
||||||
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
size = Math.Min(data.Length, PageSize - (int)(va & PageMask));
|
||||||
|
|
||||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size));
|
GetHostSpanContiguous(va, size).CopyTo(data.Slice(0, size));
|
||||||
|
|
||||||
offset += size;
|
offset += size;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (; offset < data.Length; offset += size)
|
for (; offset < data.Length; offset += size)
|
||||||
{
|
{
|
||||||
ulong pa = GetPhysicalAddressInternal(va + (ulong)offset);
|
|
||||||
|
|
||||||
size = Math.Min(data.Length - offset, PageSize);
|
size = Math.Min(data.Length - offset, PageSize);
|
||||||
|
|
||||||
_backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size));
|
GetHostSpanContiguous(va + (ulong)offset, size).CopyTo(data.Slice(offset, size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,22 +445,23 @@ namespace Ryujinx.Memory
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ulong GetPhysicalAddressInternal(ulong va)
|
private unsafe Span<byte> GetHostSpanContiguous(ulong va, int size)
|
||||||
{
|
{
|
||||||
return _pageTable.Read(va) + (va & PageMask);
|
return new Span<byte>((void*)GetHostAddress(va), size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private nuint GetHostAddress(ulong va)
|
||||||
/// Reprotect a region of virtual memory for tracking. Sets software protection bits.
|
{
|
||||||
/// </summary>
|
return _pageTable.Read(va) + (nuint)(va & PageMask);
|
||||||
/// <param name="va">Virtual address base</param>
|
}
|
||||||
/// <param name="size">Size of the region to protect</param>
|
|
||||||
/// <param name="protection">Memory protection to set</param>
|
/// <inheritdoc/>
|
||||||
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
|
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
|
||||||
{
|
{
|
||||||
// Only the ARM Memory Manager has tracking for now.
|
// Only the ARM Memory Manager has tracking for now.
|
||||||
|
@ -6,6 +6,12 @@ namespace Ryujinx.Memory
|
|||||||
{
|
{
|
||||||
public interface IVirtualMemoryManager
|
public interface IVirtualMemoryManager
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whenever the memory manager supports aliasing pages at 4KB granularity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if 4KB pages are supported by the memory manager, false otherwise</returns>
|
||||||
|
bool Supports4KBPages { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Maps a virtual memory range into a physical memory range.
|
/// Maps a virtual memory range into a physical memory range.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -15,7 +21,20 @@ namespace Ryujinx.Memory
|
|||||||
/// <param name="va">Virtual memory address</param>
|
/// <param name="va">Virtual memory address</param>
|
||||||
/// <param name="pa">Physical memory address where the region should be mapped to</param>
|
/// <param name="pa">Physical memory address where the region should be mapped to</param>
|
||||||
/// <param name="size">Size to be mapped</param>
|
/// <param name="size">Size to be mapped</param>
|
||||||
void Map(ulong va, ulong pa, ulong size);
|
/// <param name="flags">Flags controlling memory mapping</param>
|
||||||
|
void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a virtual memory range into an arbitrary host memory range.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Addresses and size must be page aligned.
|
||||||
|
/// Not all memory managers supports this feature.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="va">Virtual memory address</param>
|
||||||
|
/// <param name="hostPointer">Host pointer where the virtual region should be mapped</param>
|
||||||
|
/// <param name="size">Size to be mapped</param>
|
||||||
|
void MapForeign(ulong va, nuint hostPointer, ulong size);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Unmaps a previously mapped range of virtual memory.
|
/// Unmaps a previously mapped range of virtual memory.
|
||||||
@ -115,6 +134,15 @@ namespace Ryujinx.Memory
|
|||||||
/// <exception cref="MemoryNotContiguousException">Throw if the specified memory region is not contiguous in physical memory</exception>
|
/// <exception cref="MemoryNotContiguousException">Throw if the specified memory region is not contiguous in physical memory</exception>
|
||||||
ref T GetRef<T>(ulong va) where T : unmanaged;
|
ref T GetRef<T>(ulong va) where T : unmanaged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the host regions that make up the given virtual address region.
|
||||||
|
/// If any part of the virtual region is unmapped, null is returned.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="va">Virtual address of the range</param>
|
||||||
|
/// <param name="size">Size of the range</param>
|
||||||
|
/// <returns>Array of host regions</returns>
|
||||||
|
IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the physical regions that make up the given virtual address region.
|
/// Gets the physical regions that make up the given virtual address region.
|
||||||
/// If any part of the virtual region is unmapped, null is returned.
|
/// If any part of the virtual region is unmapped, null is returned.
|
||||||
|
@ -440,4 +440,4 @@ namespace Ryujinx.Memory
|
|||||||
|
|
||||||
private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
|
private static void ThrowInvalidMemoryRegionException() => throw new InvalidMemoryRegionException();
|
||||||
}
|
}
|
||||||
}
|
}
|
23
Ryujinx.Memory/MemoryMapFlags.cs
Normal file
23
Ryujinx.Memory/MemoryMapFlags.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Flags that indicate how the host memory should be mapped.
|
||||||
|
/// </summary>
|
||||||
|
[Flags]
|
||||||
|
public enum MemoryMapFlags
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// No mapping flags.
|
||||||
|
/// </summary>
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the implementation is free to ignore the specified backing memory offset
|
||||||
|
/// and allocate its own private storage for the mapping.
|
||||||
|
/// This allows some mappings that would otherwise fail due to host platform restrictions to succeed.
|
||||||
|
/// </summary>
|
||||||
|
Private = 1 << 0
|
||||||
|
}
|
||||||
|
}
|
71
Ryujinx.Memory/Range/HostMemoryRange.cs
Normal file
71
Ryujinx.Memory/Range/HostMemoryRange.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Memory.Range
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Range of memory composed of an address and size.
|
||||||
|
/// </summary>
|
||||||
|
public struct HostMemoryRange : IEquatable<HostMemoryRange>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty memory range, with a null address and zero size.
|
||||||
|
/// </summary>
|
||||||
|
public static HostMemoryRange Empty => new HostMemoryRange(0, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Start address of the range.
|
||||||
|
/// </summary>
|
||||||
|
public nuint Address { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Size of the range in bytes.
|
||||||
|
/// </summary>
|
||||||
|
public ulong Size { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Address where the range ends (exclusive).
|
||||||
|
/// </summary>
|
||||||
|
public nuint EndAddress => Address + (nuint)Size;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new memory range with the specified address and size.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="address">Start address</param>
|
||||||
|
/// <param name="size">Size in bytes</param>
|
||||||
|
public HostMemoryRange(nuint address, ulong size)
|
||||||
|
{
|
||||||
|
Address = address;
|
||||||
|
Size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the range overlaps with another.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The other range to check for overlap</param>
|
||||||
|
/// <returns>True if the ranges overlap, false otherwise</returns>
|
||||||
|
public bool OverlapsWith(HostMemoryRange other)
|
||||||
|
{
|
||||||
|
nuint thisAddress = Address;
|
||||||
|
nuint thisEndAddress = EndAddress;
|
||||||
|
nuint otherAddress = other.Address;
|
||||||
|
nuint otherEndAddress = other.EndAddress;
|
||||||
|
|
||||||
|
return thisAddress < otherEndAddress && otherAddress < thisEndAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
return obj is HostMemoryRange other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(HostMemoryRange other)
|
||||||
|
{
|
||||||
|
return Address == other.Address && Size == other.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Address, Size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -139,8 +139,6 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
/// <returns>The memory tracking handle</returns>
|
/// <returns>The memory tracking handle</returns>
|
||||||
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
||||||
{
|
{
|
||||||
(address, size) = PageAlign(address, size);
|
|
||||||
|
|
||||||
return new MultiRegionHandle(this, address, size, handles, granularity);
|
return new MultiRegionHandle(this, address, size, handles, granularity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,11 +164,11 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
/// <returns>The memory tracking handle</returns>
|
/// <returns>The memory tracking handle</returns>
|
||||||
public RegionHandle BeginTracking(ulong address, ulong size)
|
public RegionHandle BeginTracking(ulong address, ulong size)
|
||||||
{
|
{
|
||||||
(address, size) = PageAlign(address, size);
|
var (paAddress, paSize) = PageAlign(address, size);
|
||||||
|
|
||||||
lock (TrackingLock)
|
lock (TrackingLock)
|
||||||
{
|
{
|
||||||
RegionHandle handle = new RegionHandle(this, address, size, _memoryManager.IsRangeMapped(address, size));
|
RegionHandle handle = new RegionHandle(this, paAddress, paSize, address, size, _memoryManager.IsRangeMapped(address, size));
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
@ -186,11 +184,11 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
/// <returns>The memory tracking handle</returns>
|
/// <returns>The memory tracking handle</returns>
|
||||||
internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit)
|
internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit)
|
||||||
{
|
{
|
||||||
(address, size) = PageAlign(address, size);
|
var (paAddress, paSize) = PageAlign(address, size);
|
||||||
|
|
||||||
lock (TrackingLock)
|
lock (TrackingLock)
|
||||||
{
|
{
|
||||||
RegionHandle handle = new RegionHandle(this, address, size, bitmap, bit, _memoryManager.IsRangeMapped(address, size));
|
RegionHandle handle = new RegionHandle(this, paAddress, paSize, address, size, bitmap, bit, _memoryManager.IsRangeMapped(address, size));
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
|
|
||||||
internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
|
||||||
{
|
{
|
||||||
_handles = new RegionHandle[size / granularity];
|
_handles = new RegionHandle[(size + granularity - 1) / granularity];
|
||||||
Granularity = granularity;
|
Granularity = granularity;
|
||||||
|
|
||||||
_dirtyBitmap = new ConcurrentBitmap(_handles.Length, true);
|
_dirtyBitmap = new ConcurrentBitmap(_handles.Length, true);
|
||||||
@ -50,7 +50,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
|
|
||||||
foreach (RegionHandle handle in handles)
|
foreach (RegionHandle handle in handles)
|
||||||
{
|
{
|
||||||
int startIndex = (int)((handle.Address - address) / granularity);
|
int startIndex = (int)((handle.RealAddress - address) / granularity);
|
||||||
|
|
||||||
// Fill any gap left before this handle.
|
// Fill any gap left before this handle.
|
||||||
while (i < startIndex)
|
while (i < startIndex)
|
||||||
@ -72,7 +72,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int endIndex = (int)((handle.EndAddress - address) / granularity);
|
int endIndex = (int)((handle.RealEndAddress - address) / granularity);
|
||||||
|
|
||||||
while (i < endIndex)
|
while (i < endIndex)
|
||||||
{
|
{
|
||||||
@ -171,12 +171,13 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
modifiedAction(rgStart, rgSize);
|
modifiedAction(rgStart, rgSize);
|
||||||
rgSize = 0;
|
rgSize = 0;
|
||||||
}
|
}
|
||||||
rgStart = handle.Address;
|
|
||||||
|
rgStart = handle.RealAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handle.Dirty)
|
if (handle.Dirty)
|
||||||
{
|
{
|
||||||
rgSize += handle.Size;
|
rgSize += handle.RealSize;
|
||||||
handle.Reprotect();
|
handle.Reprotect();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,7 +192,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
int startHandle = (int)((address - Address) / Granularity);
|
int startHandle = (int)((address - Address) / Granularity);
|
||||||
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
|
||||||
|
|
||||||
ulong rgStart = _handles[startHandle].Address;
|
ulong rgStart = Address + (ulong)startHandle * Granularity;
|
||||||
|
|
||||||
if (startHandle == lastHandle)
|
if (startHandle == lastHandle)
|
||||||
{
|
{
|
||||||
@ -200,7 +201,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
if (handle.Dirty)
|
if (handle.Dirty)
|
||||||
{
|
{
|
||||||
handle.Reprotect();
|
handle.Reprotect();
|
||||||
modifiedAction(rgStart, handle.Size);
|
modifiedAction(rgStart, handle.RealSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@ -273,10 +274,10 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
modifiedAction(rgStart, rgSize);
|
modifiedAction(rgStart, rgSize);
|
||||||
rgSize = 0;
|
rgSize = 0;
|
||||||
}
|
}
|
||||||
rgStart = handle.Address;
|
rgStart = handle.RealAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
rgSize += handle.Size;
|
rgSize += handle.RealSize;
|
||||||
handle.Reprotect(false, (checkMasks[index] & bitValue) == 0);
|
handle.Reprotect(false, (checkMasks[index] & bitValue) == 0);
|
||||||
|
|
||||||
checkMasks[index] &= ~bitValue;
|
checkMasks[index] &= ~bitValue;
|
||||||
@ -320,7 +321,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
{
|
{
|
||||||
handle.Reprotect();
|
handle.Reprotect();
|
||||||
|
|
||||||
modifiedAction(rgStart, handle.Size);
|
modifiedAction(rgStart, handle.RealSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,10 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
public ulong Size { get; }
|
public ulong Size { get; }
|
||||||
public ulong EndAddress { get; }
|
public ulong EndAddress { get; }
|
||||||
|
|
||||||
|
public ulong RealAddress { get; }
|
||||||
|
public ulong RealSize { get; }
|
||||||
|
public ulong RealEndAddress { get; }
|
||||||
|
|
||||||
internal IMultiRegionHandle Parent { get; set; }
|
internal IMultiRegionHandle Parent { get; set; }
|
||||||
|
|
||||||
private event Action _onDirty;
|
private event Action _onDirty;
|
||||||
@ -89,10 +93,12 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
/// <param name="tracking">Tracking object for the target memory block</param>
|
/// <param name="tracking">Tracking object for the target memory block</param>
|
||||||
/// <param name="address">Virtual address of the region to track</param>
|
/// <param name="address">Virtual address of the region to track</param>
|
||||||
/// <param name="size">Size of the region to track</param>
|
/// <param name="size">Size of the region to track</param>
|
||||||
|
/// <param name="realAddress">The real, unaligned address of the handle</param>
|
||||||
|
/// <param name="realSize">The real, unaligned size of the handle</param>
|
||||||
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
|
/// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
|
||||||
/// <param name="bit">The bit index representing the dirty flag for this handle</param>
|
/// <param name="bit">The bit index representing the dirty flag for this handle</param>
|
||||||
/// <param name="mapped">True if the region handle starts mapped</param>
|
/// <param name="mapped">True if the region handle starts mapped</param>
|
||||||
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ConcurrentBitmap bitmap, int bit, bool mapped = true)
|
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, ConcurrentBitmap bitmap, int bit, bool mapped = true)
|
||||||
{
|
{
|
||||||
Bitmap = bitmap;
|
Bitmap = bitmap;
|
||||||
DirtyBit = bit;
|
DirtyBit = bit;
|
||||||
@ -104,6 +110,10 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
Size = size;
|
Size = size;
|
||||||
EndAddress = address + size;
|
EndAddress = address + size;
|
||||||
|
|
||||||
|
RealAddress = realAddress;
|
||||||
|
RealSize = realSize;
|
||||||
|
RealEndAddress = realAddress + realSize;
|
||||||
|
|
||||||
_tracking = tracking;
|
_tracking = tracking;
|
||||||
_regions = tracking.GetVirtualRegionsForHandle(address, size);
|
_regions = tracking.GetVirtualRegionsForHandle(address, size);
|
||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
@ -119,16 +129,23 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
/// <param name="tracking">Tracking object for the target memory block</param>
|
/// <param name="tracking">Tracking object for the target memory block</param>
|
||||||
/// <param name="address">Virtual address of the region to track</param>
|
/// <param name="address">Virtual address of the region to track</param>
|
||||||
/// <param name="size">Size of the region to track</param>
|
/// <param name="size">Size of the region to track</param>
|
||||||
|
/// <param name="realAddress">The real, unaligned address of the handle</param>
|
||||||
|
/// <param name="realSize">The real, unaligned size of the handle</param>
|
||||||
/// <param name="mapped">True if the region handle starts mapped</param>
|
/// <param name="mapped">True if the region handle starts mapped</param>
|
||||||
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
|
internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, bool mapped = true)
|
||||||
{
|
{
|
||||||
Bitmap = new ConcurrentBitmap(1, mapped);
|
Bitmap = new ConcurrentBitmap(1, mapped);
|
||||||
|
|
||||||
Unmapped = !mapped;
|
Unmapped = !mapped;
|
||||||
|
|
||||||
Address = address;
|
Address = address;
|
||||||
Size = size;
|
Size = size;
|
||||||
EndAddress = address + size;
|
EndAddress = address + size;
|
||||||
|
|
||||||
|
RealAddress = realAddress;
|
||||||
|
RealSize = realSize;
|
||||||
|
RealEndAddress = realAddress + realSize;
|
||||||
|
|
||||||
_tracking = tracking;
|
_tracking = tracking;
|
||||||
_regions = tracking.GetVirtualRegionsForHandle(address, size);
|
_regions = tracking.GetVirtualRegionsForHandle(address, size);
|
||||||
foreach (var region in _regions)
|
foreach (var region in _regions)
|
||||||
@ -199,6 +216,10 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
|
|
||||||
if (_preAction != null)
|
if (_preAction != null)
|
||||||
{
|
{
|
||||||
|
// Limit the range to within this handle.
|
||||||
|
ulong maxAddress = Math.Max(address, RealAddress);
|
||||||
|
ulong minEndAddress = Math.Min(address + size, RealAddress + RealSize);
|
||||||
|
|
||||||
// Copy the handles list in case it changes when we're out of the lock.
|
// Copy the handles list in case it changes when we're out of the lock.
|
||||||
if (handleIterable is List<RegionHandle>)
|
if (handleIterable is List<RegionHandle>)
|
||||||
{
|
{
|
||||||
@ -212,7 +233,7 @@ namespace Ryujinx.Memory.Tracking
|
|||||||
{
|
{
|
||||||
lock (_preActionLock)
|
lock (_preActionLock)
|
||||||
{
|
{
|
||||||
_preAction?.Invoke(address, size);
|
_preAction?.Invoke(maxAddress, minEndAddress - maxAddress);
|
||||||
|
|
||||||
// The action is removed after it returns, to ensure that the null check above succeeds when
|
// The action is removed after it returns, to ensure that the null check above succeeds when
|
||||||
// it's still in progress rather than continuing and possibly missing a required data flush.
|
// it's still in progress rather than continuing and possibly missing a required data flush.
|
||||||
|
@ -53,7 +53,7 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
_ram = new MemoryBlock(Size * 2);
|
_ram = new MemoryBlock(Size * 2);
|
||||||
_memory = new MemoryManager(_ram, 1ul << 16);
|
_memory = new MemoryManager(_ram, 1ul << 16);
|
||||||
_memory.IncrementReferenceCount();
|
_memory.IncrementReferenceCount();
|
||||||
_memory.Map(CodeBaseAddress, 0, Size * 2);
|
_memory.Map(CodeBaseAddress, 0, Size * 2, MemoryMapFlags.Private);
|
||||||
|
|
||||||
_context = CpuContext.CreateExecutionContext();
|
_context = CpuContext.CreateExecutionContext();
|
||||||
Translator.IsReadyForTranslation.Set();
|
Translator.IsReadyForTranslation.Set();
|
||||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Tests.Cpu
|
|||||||
_ram = new MemoryBlock(Size * 2);
|
_ram = new MemoryBlock(Size * 2);
|
||||||
_memory = new MemoryManager(_ram, 1ul << 16);
|
_memory = new MemoryManager(_ram, 1ul << 16);
|
||||||
_memory.IncrementReferenceCount();
|
_memory.IncrementReferenceCount();
|
||||||
_memory.Map(CodeBaseAddress, 0, Size * 2);
|
_memory.Map(CodeBaseAddress, 0, Size * 2, MemoryMapFlags.Private);
|
||||||
|
|
||||||
_context = CpuContext.CreateExecutionContext();
|
_context = CpuContext.CreateExecutionContext();
|
||||||
_context.IsAarch32 = true;
|
_context.IsAarch32 = true;
|
||||||
|
@ -38,9 +38,9 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
private readonly byte[] _nroIcon;
|
private readonly byte[] _nroIcon;
|
||||||
private readonly byte[] _nsoIcon;
|
private readonly byte[] _nsoIcon;
|
||||||
|
|
||||||
private VirtualFileSystem _virtualFileSystem;
|
private readonly VirtualFileSystem _virtualFileSystem;
|
||||||
private Language _desiredTitleLanguage;
|
private Language _desiredTitleLanguage;
|
||||||
private CancellationTokenSource _cancellationToken;
|
private CancellationTokenSource _cancellationToken;
|
||||||
|
|
||||||
public ApplicationLibrary(VirtualFileSystem virtualFileSystem)
|
public ApplicationLibrary(VirtualFileSystem virtualFileSystem)
|
||||||
{
|
{
|
||||||
@ -53,7 +53,7 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
_nsoIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NSO.png");
|
_nsoIcon = GetResourceBytes("Ryujinx.Ui.Common.Resources.Icon_NSO.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] GetResourceBytes(string resourceName)
|
private static byte[] GetResourceBytes(string resourceName)
|
||||||
{
|
{
|
||||||
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
|
Stream resourceStream = Assembly.GetCallingAssembly().GetManifestResourceStream(resourceName);
|
||||||
byte[] resourceByteArray = new byte[resourceStream.Length];
|
byte[] resourceByteArray = new byte[resourceStream.Length];
|
||||||
@ -68,9 +68,9 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
_cancellationToken?.Cancel();
|
_cancellationToken?.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
|
public static void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
|
||||||
{
|
{
|
||||||
using var controlFile = new UniqueRef<IFile>();
|
using UniqueRef<IFile> controlFile = new();
|
||||||
|
|
||||||
controlFs.OpenFile(ref controlFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
controlFs.OpenFile(ref controlFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
|
controlFile.Get.Read(out _, 0, outProperty, ReadOption.None).ThrowIfFailure();
|
||||||
@ -86,7 +86,7 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
_cancellationToken = new CancellationTokenSource();
|
_cancellationToken = new CancellationTokenSource();
|
||||||
|
|
||||||
// Builds the applications list with paths to found applications
|
// Builds the applications list with paths to found applications
|
||||||
List<string> applications = new List<string>();
|
List<string> applications = new();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -143,251 +143,246 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
string version = "0";
|
string version = "0";
|
||||||
byte[] applicationIcon = null;
|
byte[] applicationIcon = null;
|
||||||
|
|
||||||
BlitStruct<ApplicationControlProperty> controlHolder = new BlitStruct<ApplicationControlProperty>(1);
|
BlitStruct<ApplicationControlProperty> controlHolder = new(1);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string extension = Path.GetExtension(applicationPath).ToLower();
|
string extension = Path.GetExtension(applicationPath).ToLower();
|
||||||
|
|
||||||
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
||||||
{
|
{
|
||||||
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
try
|
||||||
{
|
{
|
||||||
try
|
PartitionFileSystem pfs;
|
||||||
|
|
||||||
|
bool isExeFs = false;
|
||||||
|
|
||||||
|
if (extension == ".xci")
|
||||||
{
|
{
|
||||||
PartitionFileSystem pfs;
|
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
||||||
|
|
||||||
bool isExeFs = false;
|
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pfs = new PartitionFileSystem(file.AsStorage());
|
||||||
|
|
||||||
if (extension == ".xci")
|
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
|
||||||
|
bool hasMainNca = false;
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
|
||||||
{
|
{
|
||||||
Xci xci = new Xci(_virtualFileSystem.KeySet, file.AsStorage());
|
if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
|
||||||
|
|
||||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pfs = new PartitionFileSystem(file.AsStorage());
|
|
||||||
|
|
||||||
// If the NSP doesn't have a main NCA, decrement the number of applications found and then continue to the next application.
|
|
||||||
bool hasMainNca = false;
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
|
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(fileEntry.FullPath).ToLower() == ".nca")
|
using UniqueRef<IFile> ncaFile = new();
|
||||||
|
|
||||||
|
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||||
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
|
// Some main NCAs don't have a data partition, so check if the partition exists before opening it
|
||||||
|
if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
|
||||||
{
|
{
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
hasMainNca = true;
|
||||||
|
|
||||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
break;
|
||||||
|
|
||||||
Nca nca = new Nca(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
|
||||||
|
|
||||||
// Some main NCAs don't have a data partition, so check if the partition exists before opening it
|
|
||||||
if (nca.Header.ContentType == NcaContentType.Program && !(nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
|
|
||||||
{
|
|
||||||
hasMainNca = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
|
|
||||||
{
|
|
||||||
isExeFs = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
|
||||||
if (!hasMainNca && !isExeFs)
|
|
||||||
{
|
{
|
||||||
numApplicationsFound--;
|
isExeFs = true;
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isExeFs)
|
if (!hasMainNca && !isExeFs)
|
||||||
{
|
|
||||||
applicationIcon = _nspIcon;
|
|
||||||
|
|
||||||
using var npdmFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
Result result = pfs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
|
|
||||||
|
|
||||||
if (ResultFs.PathNotFound.Includes(result))
|
|
||||||
{
|
|
||||||
Npdm npdm = new Npdm(npdmFile.Get.AsStream());
|
|
||||||
|
|
||||||
titleName = npdm.TitleName;
|
|
||||||
titleId = npdm.Aci0.TitleId.ToString("x16");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId);
|
|
||||||
|
|
||||||
// Check if there is an update available.
|
|
||||||
if (IsUpdateApplied(titleId, out IFileSystem updatedControlFs))
|
|
||||||
{
|
|
||||||
// Replace the original ControlFs by the updated one.
|
|
||||||
controlFs = updatedControlFs;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadControlData(controlFs, controlHolder.ByteSpan);
|
|
||||||
|
|
||||||
GetGameInformation(ref controlHolder.Value, out titleName, out _, out developer, out version);
|
|
||||||
|
|
||||||
// Read the icon from the ControlFS and store it as a byte array
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var icon = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
icon.Get.AsStream().CopyTo(stream);
|
|
||||||
applicationIcon = stream.ToArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (HorizonResultException)
|
|
||||||
{
|
|
||||||
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
|
||||||
{
|
|
||||||
if (entry.Name == "control.nacp")
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var icon = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
icon.Get.AsStream().CopyTo(stream);
|
|
||||||
applicationIcon = stream.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationIcon != null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationIcon == null)
|
|
||||||
{
|
|
||||||
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (MissingKeyException exception)
|
|
||||||
{
|
|
||||||
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
|
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
|
||||||
}
|
|
||||||
catch (InvalidDataException)
|
|
||||||
{
|
|
||||||
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
|
|
||||||
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
|
|
||||||
|
|
||||||
numApplicationsFound--;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (extension == ".nro")
|
|
||||||
{
|
|
||||||
BinaryReader reader = new BinaryReader(file);
|
|
||||||
|
|
||||||
byte[] Read(long position, int size)
|
|
||||||
{
|
|
||||||
file.Seek(position, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
return reader.ReadBytes(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
file.Seek(24, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
int assetOffset = reader.ReadInt32();
|
|
||||||
|
|
||||||
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
|
||||||
{
|
|
||||||
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
|
||||||
|
|
||||||
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
|
||||||
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
|
||||||
|
|
||||||
ulong nacpOffset = reader.ReadUInt64();
|
|
||||||
ulong nacpSize = reader.ReadUInt64();
|
|
||||||
|
|
||||||
// Reads and stores game icon as byte array
|
|
||||||
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
|
||||||
|
|
||||||
// Read the NACP data
|
|
||||||
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
|
|
||||||
|
|
||||||
GetGameInformation(ref controlHolder.Value, out titleName, out titleId, out developer, out version);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applicationIcon = _nroIcon;
|
|
||||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
|
||||||
|
|
||||||
numApplicationsFound--;
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (extension == ".nca")
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Nca nca = new Nca(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage());
|
|
||||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
|
||||||
|
|
||||||
if (nca.Header.ContentType != NcaContentType.Program || (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
|
|
||||||
{
|
{
|
||||||
numApplicationsFound--;
|
numApplicationsFound--;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InvalidDataException)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}");
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
|
||||||
|
|
||||||
|
if (isExeFs)
|
||||||
|
{
|
||||||
|
applicationIcon = _nspIcon;
|
||||||
|
|
||||||
|
using UniqueRef<IFile> npdmFile = new();
|
||||||
|
|
||||||
|
Result result = pfs.OpenFile(ref npdmFile.Ref(), "/main.npdm".ToU8Span(), OpenMode.Read);
|
||||||
|
|
||||||
|
if (ResultFs.PathNotFound.Includes(result))
|
||||||
|
{
|
||||||
|
Npdm npdm = new(npdmFile.Get.AsStream());
|
||||||
|
|
||||||
|
titleName = npdm.TitleName;
|
||||||
|
titleId = npdm.Aci0.TitleId.ToString("x16");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId);
|
||||||
|
|
||||||
|
// Check if there is an update available.
|
||||||
|
if (IsUpdateApplied(titleId, out IFileSystem updatedControlFs))
|
||||||
|
{
|
||||||
|
// Replace the original ControlFs by the updated one.
|
||||||
|
controlFs = updatedControlFs;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadControlData(controlFs, controlHolder.ByteSpan);
|
||||||
|
|
||||||
|
GetGameInformation(ref controlHolder.Value, out titleName, out _, out developer, out version);
|
||||||
|
|
||||||
|
// Read the icon from the ControlFS and store it as a byte array
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using UniqueRef<IFile> icon = new();
|
||||||
|
|
||||||
|
controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
icon.Get.AsStream().CopyTo(stream);
|
||||||
|
applicationIcon = stream.ToArray();
|
||||||
|
}
|
||||||
|
catch (HorizonResultException)
|
||||||
|
{
|
||||||
|
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
||||||
|
{
|
||||||
|
if (entry.Name == "control.nacp")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var icon = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
icon.Get.AsStream().CopyTo(stream);
|
||||||
|
applicationIcon = stream.ToArray();
|
||||||
|
|
||||||
|
|
||||||
|
if (applicationIcon != null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (MissingKeyException exception)
|
||||||
|
{
|
||||||
|
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Your key set is missing a key with the name: {exception.Name}");
|
||||||
|
}
|
||||||
|
catch (InvalidDataException)
|
||||||
|
{
|
||||||
|
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {applicationPath}");
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
|
||||||
|
|
||||||
|
numApplicationsFound--;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (extension == ".nro")
|
||||||
|
{
|
||||||
|
BinaryReader reader = new(file);
|
||||||
|
|
||||||
|
byte[] Read(long position, int size)
|
||||||
|
{
|
||||||
|
file.Seek(position, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
return reader.ReadBytes(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
file.Seek(24, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
int assetOffset = reader.ReadInt32();
|
||||||
|
|
||||||
|
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
||||||
|
{
|
||||||
|
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
||||||
|
|
||||||
|
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
||||||
|
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||||
|
|
||||||
|
ulong nacpOffset = reader.ReadUInt64();
|
||||||
|
ulong nacpSize = reader.ReadUInt64();
|
||||||
|
|
||||||
|
// Reads and stores game icon as byte array
|
||||||
|
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
||||||
|
|
||||||
|
// Read the NACP data
|
||||||
|
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
|
||||||
|
|
||||||
|
GetGameInformation(ref controlHolder.Value, out titleName, out titleId, out developer, out version);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationIcon = _nroIcon;
|
||||||
|
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||||
|
|
||||||
|
numApplicationsFound--;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (extension == ".nca")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Nca nca = new(_virtualFileSystem.KeySet, new FileStream(applicationPath, FileMode.Open, FileAccess.Read).AsStorage());
|
||||||
|
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||||
|
|
||||||
|
if (nca.Header.ContentType != NcaContentType.Program || (nca.SectionExists(NcaSectionType.Data) && nca.Header.GetFsHeader(dataIndex).IsPatchSection()))
|
||||||
|
{
|
||||||
numApplicationsFound--;
|
numApplicationsFound--;
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationIcon = _ncaIcon;
|
|
||||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
|
||||||
}
|
}
|
||||||
// If its an NSO we just set defaults
|
catch (InvalidDataException)
|
||||||
else if (extension == ".nso")
|
|
||||||
{
|
{
|
||||||
applicationIcon = _nsoIcon;
|
Logger.Warning?.Print(LogClass.Application, $"The NCA header content type check has failed. This is usually because the header key is incorrect or missing. Errored File: {applicationPath}");
|
||||||
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||||
|
|
||||||
|
numApplicationsFound--;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationIcon = _ncaIcon;
|
||||||
|
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||||
|
}
|
||||||
|
// If its an NSO we just set defaults
|
||||||
|
else if (extension == ".nso")
|
||||||
|
{
|
||||||
|
applicationIcon = _nsoIcon;
|
||||||
|
titleName = Path.GetFileNameWithoutExtension(applicationPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (IOException exception)
|
catch (IOException exception)
|
||||||
@ -404,14 +399,21 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
appMetadata.Title = titleName;
|
appMetadata.Title = titleName;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (appMetadata.LastPlayed != "Never" && !DateTime.TryParse(appMetadata.LastPlayed, out _))
|
if (appMetadata.LastPlayed != "Never")
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)");
|
if (!DateTime.TryParse(appMetadata.LastPlayed, out _))
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"Last played datetime \"{appMetadata.LastPlayed}\" is invalid for current system culture, skipping (did current culture change?)");
|
||||||
|
|
||||||
appMetadata.LastPlayed = "Never";
|
appMetadata.LastPlayed = "Never";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
appMetadata.LastPlayed = appMetadata.LastPlayed[..^3];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationData data = new ApplicationData
|
ApplicationData data = new()
|
||||||
{
|
{
|
||||||
Favorite = appMetadata.Favorite,
|
Favorite = appMetadata.Favorite,
|
||||||
Icon = applicationIcon,
|
Icon = applicationIcon,
|
||||||
@ -419,7 +421,7 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
TitleId = titleId,
|
TitleId = titleId,
|
||||||
Developer = developer,
|
Developer = developer,
|
||||||
Version = version,
|
Version = version,
|
||||||
TimePlayed = ConvertSecondsToReadableString(appMetadata.TimePlayed),
|
TimePlayed = ConvertSecondsToFormattedString(appMetadata.TimePlayed),
|
||||||
TimePlayedNum = appMetadata.TimePlayed,
|
TimePlayedNum = appMetadata.TimePlayed,
|
||||||
LastPlayed = appMetadata.LastPlayed,
|
LastPlayed = appMetadata.LastPlayed,
|
||||||
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1),
|
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0, 1),
|
||||||
@ -488,10 +490,9 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
|
|
||||||
appMetadata = new ApplicationMetadata();
|
appMetadata = new ApplicationMetadata();
|
||||||
|
|
||||||
using (FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough))
|
using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough);
|
||||||
{
|
|
||||||
JsonHelper.Serialize(stream, appMetadata, true);
|
JsonHelper.Serialize(stream, appMetadata, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -509,10 +510,9 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
{
|
{
|
||||||
modifyFunction(appMetadata);
|
modifyFunction(appMetadata);
|
||||||
|
|
||||||
using (FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough))
|
using FileStream stream = File.Create(metadataFile, 4096, FileOptions.WriteThrough);
|
||||||
{
|
|
||||||
JsonHelper.Serialize(stream, appMetadata, true);
|
JsonHelper.Serialize(stream, appMetadata, true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return appMetadata;
|
return appMetadata;
|
||||||
@ -529,192 +529,177 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
{
|
{
|
||||||
string extension = Path.GetExtension(applicationPath).ToLower();
|
string extension = Path.GetExtension(applicationPath).ToLower();
|
||||||
|
|
||||||
using (FileStream file = new FileStream(applicationPath, FileMode.Open, FileAccess.Read))
|
using FileStream file = new(applicationPath, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
||||||
{
|
{
|
||||||
if (extension == ".nsp" || extension == ".pfs0" || extension == ".xci")
|
try
|
||||||
{
|
{
|
||||||
try
|
PartitionFileSystem pfs;
|
||||||
|
|
||||||
|
bool isExeFs = false;
|
||||||
|
|
||||||
|
if (extension == ".xci")
|
||||||
{
|
{
|
||||||
PartitionFileSystem pfs;
|
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
||||||
|
|
||||||
bool isExeFs = false;
|
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pfs = new PartitionFileSystem(file.AsStorage());
|
||||||
|
|
||||||
if (extension == ".xci")
|
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
|
||||||
{
|
{
|
||||||
Xci xci = new(_virtualFileSystem.KeySet, file.AsStorage());
|
if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
|
||||||
|
|
||||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pfs = new PartitionFileSystem(file.AsStorage());
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*"))
|
|
||||||
{
|
{
|
||||||
if (Path.GetFileNameWithoutExtension(fileEntry.FullPath) == "main")
|
isExeFs = true;
|
||||||
{
|
|
||||||
isExeFs = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isExeFs)
|
if (isExeFs)
|
||||||
|
{
|
||||||
|
applicationIcon = _nspIcon;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Store the ControlFS in variable called controlFs
|
||||||
|
GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out _);
|
||||||
|
|
||||||
|
// Read the icon from the ControlFS and store it as a byte array
|
||||||
|
try
|
||||||
{
|
{
|
||||||
applicationIcon = _nspIcon;
|
using var icon = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
icon.Get.AsStream().CopyTo(stream);
|
||||||
|
applicationIcon = stream.ToArray();
|
||||||
}
|
}
|
||||||
else
|
catch (HorizonResultException)
|
||||||
{
|
{
|
||||||
// Store the ControlFS in variable called controlFs
|
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
||||||
GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out _);
|
|
||||||
|
|
||||||
// Read the icon from the ControlFS and store it as a byte array
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
|
if (entry.Name == "control.nacp")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
using var icon = new UniqueRef<IFile>();
|
using var icon = new UniqueRef<IFile>();
|
||||||
|
|
||||||
controlFs.OpenFile(ref icon.Ref(), $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = new())
|
||||||
{
|
{
|
||||||
icon.Get.AsStream().CopyTo(stream);
|
icon.Get.AsStream().CopyTo(stream);
|
||||||
applicationIcon = stream.ToArray();
|
applicationIcon = stream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (HorizonResultException)
|
if (applicationIcon != null)
|
||||||
{
|
|
||||||
foreach (DirectoryEntryEx entry in controlFs.EnumerateEntries("/", "*"))
|
|
||||||
{
|
{
|
||||||
if (entry.Name == "control.nacp")
|
break;
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
using var icon = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
controlFs.OpenFile(ref icon.Ref(), entry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
|
||||||
{
|
|
||||||
icon.Get.AsStream().CopyTo(stream);
|
|
||||||
applicationIcon = stream.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationIcon != null)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applicationIcon == null)
|
|
||||||
{
|
|
||||||
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applicationIcon ??= extension == ".xci" ? _xciIcon : _nspIcon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (MissingKeyException)
|
|
||||||
{
|
|
||||||
applicationIcon = extension == ".xci"
|
|
||||||
? _xciIcon
|
|
||||||
: _nspIcon;
|
|
||||||
}
|
|
||||||
catch (InvalidDataException)
|
|
||||||
{
|
|
||||||
applicationIcon = extension == ".xci"
|
|
||||||
? _xciIcon
|
|
||||||
: _nspIcon;
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application,
|
|
||||||
$"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (extension == ".nro")
|
catch (MissingKeyException)
|
||||||
{
|
{
|
||||||
BinaryReader reader = new(file);
|
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
|
||||||
|
|
||||||
byte[] Read(long position, int size)
|
|
||||||
{
|
|
||||||
file.Seek(position, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
return reader.ReadBytes(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
file.Seek(24, SeekOrigin.Begin);
|
|
||||||
|
|
||||||
int assetOffset = reader.ReadInt32();
|
|
||||||
|
|
||||||
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
|
||||||
{
|
|
||||||
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
|
||||||
|
|
||||||
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
|
||||||
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
|
||||||
|
|
||||||
// Reads and stores game icon as byte array
|
|
||||||
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
applicationIcon = _nroIcon;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Logger.Warning?.Print(LogClass.Application,
|
|
||||||
$"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (extension == ".nca")
|
catch (InvalidDataException)
|
||||||
{
|
{
|
||||||
applicationIcon = _ncaIcon;
|
applicationIcon = extension == ".xci" ? _xciIcon : _nspIcon;
|
||||||
}
|
}
|
||||||
// If its an NSO we just set defaults
|
catch (Exception exception)
|
||||||
else if (extension == ".nso")
|
|
||||||
{
|
{
|
||||||
applicationIcon = _nsoIcon;
|
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. File: '{applicationPath}' Error: {exception}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (extension == ".nro")
|
||||||
|
{
|
||||||
|
BinaryReader reader = new(file);
|
||||||
|
|
||||||
|
byte[] Read(long position, int size)
|
||||||
|
{
|
||||||
|
file.Seek(position, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
return reader.ReadBytes(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
file.Seek(24, SeekOrigin.Begin);
|
||||||
|
|
||||||
|
int assetOffset = reader.ReadInt32();
|
||||||
|
|
||||||
|
if (Encoding.ASCII.GetString(Read(assetOffset, 4)) == "ASET")
|
||||||
|
{
|
||||||
|
byte[] iconSectionInfo = Read(assetOffset + 8, 0x10);
|
||||||
|
|
||||||
|
long iconOffset = BitConverter.ToInt64(iconSectionInfo, 0);
|
||||||
|
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||||
|
|
||||||
|
// Reads and stores game icon as byte array
|
||||||
|
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationIcon = _nroIcon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.Warning?.Print(LogClass.Application, $"The file encountered was not of a valid type. Errored File: {applicationPath}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (extension == ".nca")
|
||||||
|
{
|
||||||
|
applicationIcon = _ncaIcon;
|
||||||
|
}
|
||||||
|
// If its an NSO we just set defaults
|
||||||
|
else if (extension == ".nso")
|
||||||
|
{
|
||||||
|
applicationIcon = _nsoIcon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(Exception)
|
catch(Exception)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application,
|
Logger.Warning?.Print(LogClass.Application, $"Could not retrieve a valid icon for the app. Default icon will be used. Errored File: {applicationPath}");
|
||||||
$"Could not retrieve a valid icon for the app. Default icon will be used. Errored File: {applicationPath}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return applicationIcon ?? _ncaIcon;
|
return applicationIcon ?? _ncaIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ConvertSecondsToReadableString(double seconds)
|
private static string ConvertSecondsToFormattedString(double seconds)
|
||||||
{
|
{
|
||||||
const int secondsPerMinute = 60;
|
System.TimeSpan time = System.TimeSpan.FromSeconds(seconds);
|
||||||
const int secondsPerHour = secondsPerMinute * 60;
|
|
||||||
const int secondsPerDay = secondsPerHour * 24;
|
|
||||||
|
|
||||||
string readableString;
|
string timeString;
|
||||||
|
if (time.Days != 0)
|
||||||
if (seconds < secondsPerMinute)
|
|
||||||
{
|
{
|
||||||
readableString = $"{seconds} seconds";
|
timeString = $"{time.Days}d {time.Hours:D2}h {time.Minutes:D2}m";
|
||||||
}
|
}
|
||||||
else if (seconds < secondsPerHour)
|
else if (time.Hours != 0)
|
||||||
{
|
{
|
||||||
readableString = $"{Math.Round(seconds / secondsPerMinute, 0, MidpointRounding.AwayFromZero)} minutes";
|
timeString = $"{time.Hours:D2}h {time.Minutes:D2}m";
|
||||||
}
|
}
|
||||||
else if (seconds < secondsPerDay)
|
else if (time.Minutes != 0)
|
||||||
{
|
{
|
||||||
readableString = $"{Math.Round(seconds / secondsPerHour, 1, MidpointRounding.AwayFromZero)} hours";
|
timeString = $"{time.Minutes:D2}m";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
readableString = $"{Math.Round(seconds / secondsPerDay, 1, MidpointRounding.AwayFromZero)} days";
|
timeString = "Never";
|
||||||
}
|
}
|
||||||
|
|
||||||
return readableString;
|
return timeString;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
|
private void GetGameInformation(ref ApplicationControlProperty controlData, out string titleName, out string titleId, out string publisher, out string version)
|
||||||
@ -797,8 +782,7 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
}
|
}
|
||||||
catch (InvalidDataException)
|
catch (InvalidDataException)
|
||||||
{
|
{
|
||||||
Logger.Warning?.Print(LogClass.Application,
|
Logger.Warning?.Print(LogClass.Application, $"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
|
||||||
$"The header key is incorrect or missing and therefore the NCA header content type check has failed. Errored File: {updatePath}");
|
|
||||||
}
|
}
|
||||||
catch (MissingKeyException exception)
|
catch (MissingKeyException exception)
|
||||||
{
|
{
|
||||||
|
@ -617,7 +617,7 @@ namespace Ryujinx.Ui.Common.Configuration
|
|||||||
Graphics.ResScaleCustom.Value = 1.0f;
|
Graphics.ResScaleCustom.Value = 1.0f;
|
||||||
Graphics.MaxAnisotropy.Value = -1.0f;
|
Graphics.MaxAnisotropy.Value = -1.0f;
|
||||||
Graphics.AspectRatio.Value = AspectRatio.Fixed16x9;
|
Graphics.AspectRatio.Value = AspectRatio.Fixed16x9;
|
||||||
Graphics.GraphicsBackend.Value = GraphicsBackend.OpenGl;
|
Graphics.GraphicsBackend.Value = OperatingSystem.IsMacOS() ? GraphicsBackend.Vulkan : GraphicsBackend.OpenGl;
|
||||||
Graphics.PreferredGpu.Value = "";
|
Graphics.PreferredGpu.Value = "";
|
||||||
Graphics.ShadersDumpPath.Value = "";
|
Graphics.ShadersDumpPath.Value = "";
|
||||||
Logger.EnableDebug.Value = false;
|
Logger.EnableDebug.Value = false;
|
||||||
|
@ -1,19 +1,75 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Ui.Common.Helper
|
namespace Ryujinx.Ui.Common.Helper
|
||||||
{
|
{
|
||||||
public static class OpenHelper
|
public static partial class OpenHelper
|
||||||
{
|
{
|
||||||
|
[LibraryImport("shell32.dll", SetLastError = true)]
|
||||||
|
public static partial int SHOpenFolderAndSelectItems(IntPtr pidlFolder, uint cidl, IntPtr apidl, uint dwFlags);
|
||||||
|
|
||||||
|
[LibraryImport("shell32.dll", SetLastError = true)]
|
||||||
|
public static partial void ILFree(IntPtr pidlList);
|
||||||
|
|
||||||
|
[LibraryImport("shell32.dll", SetLastError = true)]
|
||||||
|
public static partial IntPtr ILCreateFromPathW([MarshalAs(UnmanagedType.LPWStr)] string pszPath);
|
||||||
|
|
||||||
public static void OpenFolder(string path)
|
public static void OpenFolder(string path)
|
||||||
{
|
{
|
||||||
Process.Start(new ProcessStartInfo
|
if (Directory.Exists(path))
|
||||||
{
|
{
|
||||||
FileName = path,
|
Process.Start(new ProcessStartInfo
|
||||||
UseShellExecute = true,
|
{
|
||||||
Verb = "open"
|
FileName = path,
|
||||||
});
|
UseShellExecute = true,
|
||||||
|
Verb = "open"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Notice.Print(LogClass.Application, $"Directory \"{path}\" doesn't exist!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void LocateFile(string path)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
IntPtr pidlList = ILCreateFromPathW(path);
|
||||||
|
if (pidlList != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Marshal.ThrowExceptionForHR(SHOpenFolderAndSelectItems(pidlList, 0, IntPtr.Zero, 0));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ILFree(pidlList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
Process.Start("open", $"-R \"{path}\"");
|
||||||
|
}
|
||||||
|
else if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
Process.Start("dbus-send", $"--session --print-reply --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:\"file://{path}\" string:\"\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OpenFolder(Path.GetDirectoryName(path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Notice.Print(LogClass.Application, $"File \"{path}\" doesn't exist!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenUrl(string url)
|
public static void OpenUrl(string url)
|
||||||
|
@ -7,65 +7,33 @@ namespace Ryujinx.Ui.Helper
|
|||||||
{
|
{
|
||||||
public static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
public static int TimePlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||||
{
|
{
|
||||||
string aValue = model.GetValue(a, 5).ToString();
|
static string ReverseFormat(string time)
|
||||||
string bValue = model.GetValue(b, 5).ToString();
|
{
|
||||||
float aFloat;
|
if (time == "Never")
|
||||||
float bFloat;
|
{
|
||||||
|
return "00";
|
||||||
|
}
|
||||||
|
|
||||||
if (aValue.Length > 7 && aValue[^7..] == "minutes")
|
var numbers = time.Split(new char[] { 'd', 'h', 'm' });
|
||||||
{
|
|
||||||
aValue = aValue.Replace("minutes", "");
|
time = time.Replace(" ", "").Replace("d", ".").Replace("h", ":").Replace("m", "");
|
||||||
aFloat = (float.Parse(aValue) * 60);
|
|
||||||
}
|
if (numbers.Length == 2)
|
||||||
else if (aValue.Length > 5 && aValue[^5..] == "hours")
|
{
|
||||||
{
|
return $"00.00:{time}";
|
||||||
aValue = aValue.Replace("hours", "");
|
}
|
||||||
aFloat = (float.Parse(aValue) * 3600);
|
else if (numbers.Length == 3)
|
||||||
}
|
{
|
||||||
else if (aValue.Length > 4 && aValue[^4..] == "days")
|
return $"00.{time}";
|
||||||
{
|
}
|
||||||
aValue = aValue.Replace("days", "");
|
|
||||||
aFloat = (float.Parse(aValue) * 86400);
|
return time;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
aValue = aValue.Replace("seconds", "");
|
|
||||||
aFloat = float.Parse(aValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bValue.Length > 7 && bValue[^7..] == "minutes")
|
string aValue = ReverseFormat(model.GetValue(a, 5).ToString());
|
||||||
{
|
string bValue = ReverseFormat(model.GetValue(b, 5).ToString());
|
||||||
bValue = bValue.Replace("minutes", "");
|
|
||||||
bFloat = (float.Parse(bValue) * 60);
|
|
||||||
}
|
|
||||||
else if (bValue.Length > 5 && bValue[^5..] == "hours")
|
|
||||||
{
|
|
||||||
bValue = bValue.Replace("hours", "");
|
|
||||||
bFloat = (float.Parse(bValue) * 3600);
|
|
||||||
}
|
|
||||||
else if (bValue.Length > 4 && bValue[^4..] == "days")
|
|
||||||
{
|
|
||||||
bValue = bValue.Replace("days", "");
|
|
||||||
bFloat = (float.Parse(bValue) * 86400);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
bValue = bValue[0..^8];
|
|
||||||
bFloat = float.Parse(bValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aFloat > bFloat)
|
return TimeSpan.Compare(TimeSpan.Parse(aValue), TimeSpan.Parse(bValue));
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
else if (bFloat > aFloat)
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
public static int LastPlayedSort(ITreeModel model, TreeIter a, TreeIter b)
|
||||||
@ -123,4 +91,4 @@ namespace Ryujinx.Ui.Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user