Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
2382717600 | |||
30ee70a9bc | |||
232b1012b0 | |||
e747f5cd83 | |||
8aff17a93c | |||
f2a41b7a1c | |||
c881cd2d14 | |||
68f9091870 |
@ -1,9 +1,9 @@
|
||||
<Application
|
||||
x:Class="Ryujinx.Ava.App"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:sty="using:FluentAvalonia.Styling"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sty="using:FluentAvalonia.Styling">
|
||||
<Application.Styles>
|
||||
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False"/>
|
||||
<sty:FluentAvaloniaTheme UseSystemThemeOnWindows="False" />
|
||||
</Application.Styles>
|
||||
</Application>
|
@ -2,7 +2,6 @@ using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using FluentAvalonia.Styling;
|
||||
using Ryujinx.Ava.Ui.Windows;
|
||||
using Ryujinx.Common;
|
||||
@ -13,7 +12,7 @@ using System.IO;
|
||||
|
||||
namespace Ryujinx.Ava
|
||||
{
|
||||
public class App : Avalonia.Application
|
||||
public class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
@ -46,7 +45,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
private void ShowRestartDialog()
|
||||
{
|
||||
// TODO. Implement Restart Dialog when SettingsWindow is implemented.
|
||||
// TODO: Implement Restart Dialog when SettingsWindow is implemented.
|
||||
}
|
||||
|
||||
private void ThemeChanged_Event(object sender, ReactiveEventArgs<string> e)
|
||||
|
@ -57,7 +57,7 @@ namespace Ryujinx.Ava
|
||||
private static readonly Cursor InvisibleCursor = new Cursor(StandardCursorType.None);
|
||||
|
||||
private readonly AccountManager _accountManager;
|
||||
private UserChannelPersistence _userChannelPersistence;
|
||||
private readonly UserChannelPersistence _userChannelPersistence;
|
||||
|
||||
private readonly InputManager _inputManager;
|
||||
|
||||
@ -82,7 +82,6 @@ namespace Ryujinx.Ava
|
||||
private bool _dialogShown;
|
||||
|
||||
private WindowsMultimediaTimerResolution _windowsMultimediaTimerResolution;
|
||||
private KeyboardStateSnapshot _lastKeyboardSnapshot;
|
||||
|
||||
private readonly CancellationTokenSource _gpuCancellationTokenSource;
|
||||
|
||||
@ -126,7 +125,6 @@ namespace Ryujinx.Ava
|
||||
_glLogLevel = ConfigurationState.Instance.Logger.GraphicsDebugLevel;
|
||||
_inputManager.SetMouseDriver(new AvaloniaMouseDriver(renderer));
|
||||
_keyboardInterface = (IKeyboard)_inputManager.KeyboardDriver.GetGamepad("0");
|
||||
_lastKeyboardSnapshot = _keyboardInterface.GetKeyboardStateSnapshot();
|
||||
|
||||
NpadManager = _inputManager.CreateNpadManager();
|
||||
TouchScreenManager = _inputManager.CreateTouchScreenManager();
|
||||
@ -722,9 +720,7 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value
|
||||
? HLE.MemoryConfiguration.MemoryConfiguration6GB
|
||||
: HLE.MemoryConfiguration.MemoryConfiguration4GB;
|
||||
var memoryConfiguration = ConfigurationState.Instance.System.ExpandRam.Value ? HLE.MemoryConfiguration.MemoryConfiguration6GB : HLE.MemoryConfiguration.MemoryConfiguration4GB;
|
||||
|
||||
IntegrityCheckLevel fsIntegrityCheckLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks ? IntegrityCheckLevel.ErrorOnInvalid : IntegrityCheckLevel.None;
|
||||
|
||||
@ -898,7 +894,7 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleScreenState(KeyboardStateSnapshot keyboard, KeyboardStateSnapshot lastKeyboard)
|
||||
private void HandleScreenState()
|
||||
{
|
||||
if (ConfigurationState.Instance.Hid.EnableMouse)
|
||||
{
|
||||
@ -935,19 +931,12 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
KeyboardStateSnapshot keyboard = _keyboardInterface.GetKeyboardStateSnapshot();
|
||||
HandleScreenState();
|
||||
|
||||
HandleScreenState(keyboard, _lastKeyboardSnapshot);
|
||||
|
||||
if (keyboard.IsPressed(Key.Delete))
|
||||
if (_keyboardInterface.GetKeyboardStateSnapshot().IsPressed(Key.Delete) && _parent.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
if (_parent.WindowState != WindowState.FullScreen)
|
||||
{
|
||||
Ptc.Continue();
|
||||
}
|
||||
Ptc.Continue();
|
||||
}
|
||||
|
||||
_lastKeyboardSnapshot = keyboard;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20" Height="2000">
|
||||
<Border Height="2000" Padding="20">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="Code Font Family" />
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
@ -27,8 +25,12 @@
|
||||
Name="btnRem"
|
||||
HorizontalAlignment="Right"
|
||||
Content="Add" />
|
||||
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello"
|
||||
UseFloatingWatermark="True" />
|
||||
<TextBox
|
||||
Width="100"
|
||||
VerticalAlignment="Center"
|
||||
Text="Rrrrr"
|
||||
UseFloatingWatermark="True"
|
||||
Watermark="Hello" />
|
||||
<CheckBox>Test Check</CheckBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
@ -1,9 +1,7 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<StyleInclude Source="avares://Ryujinx.Ava/Assets/Styles/Styles.xaml" />
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20" Height="2000">
|
||||
<Border Height="2000" Padding="20">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="Code Font Family" />
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
@ -27,8 +25,12 @@
|
||||
Name="btnRem"
|
||||
HorizontalAlignment="Right"
|
||||
Content="Add" />
|
||||
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello"
|
||||
UseFloatingWatermark="True" />
|
||||
<TextBox
|
||||
Width="100"
|
||||
VerticalAlignment="Center"
|
||||
Text="Rrrrr"
|
||||
UseFloatingWatermark="True"
|
||||
Watermark="Hello" />
|
||||
<CheckBox>Test Check</CheckBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Styles
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:sys="clr-namespace:System;assembly=netstandard">
|
||||
xmlns:sys="clr-namespace:System;assembly=netstandard"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia">
|
||||
<Design.PreviewWith>
|
||||
<Border Padding="20" Height="2000">
|
||||
<Border Height="2000" Padding="20">
|
||||
<StackPanel Spacing="5">
|
||||
<TextBlock Text="Code Font Family" />
|
||||
<Grid RowDefinitions="*,Auto">
|
||||
@ -22,15 +22,19 @@
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ToggleButton
|
||||
Name="btnAdd"
|
||||
HorizontalAlignment="Right"
|
||||
Height="28"
|
||||
HorizontalAlignment="Right"
|
||||
Content="Addy" />
|
||||
<Button
|
||||
Name="btnRem"
|
||||
HorizontalAlignment="Right"
|
||||
Content="Add" />
|
||||
<TextBox Width="100" VerticalAlignment="Center" Text="Rrrrr" Watermark="Hello"
|
||||
UseFloatingWatermark="True" />
|
||||
<TextBox
|
||||
Width="100"
|
||||
VerticalAlignment="Center"
|
||||
Text="Rrrrr"
|
||||
UseFloatingWatermark="True"
|
||||
Watermark="Hello" />
|
||||
<CheckBox>Test Check</CheckBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
@ -62,13 +66,10 @@
|
||||
<Style Selector="Image.huge">
|
||||
<Setter Property="Width" Value="120" />
|
||||
</Style>
|
||||
<Style Selector="RadioButton">
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
</Style>
|
||||
<Style Selector="#TitleBarHost > Image">
|
||||
<Style Selector="#TitleBarHost > Image">
|
||||
<Setter Property="Margin" Value="10" />
|
||||
</Style>
|
||||
<Style Selector="#TitleBarHost > Label">
|
||||
<Style Selector="#TitleBarHost > Label">
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
</Style>
|
||||
@ -225,12 +226,12 @@
|
||||
<StaticResource x:Key="ListViewItemBackgroundPointerOver" ResourceKey="SystemAccentColorDark2" />
|
||||
<StaticResource x:Key="ListViewItemBackgroundSelectedPressed" ResourceKey="ThemeAccentColorBrush" />
|
||||
<StaticResource x:Key="ListViewItemBackgroundSelectedPointerOver" ResourceKey="SystemAccentColorDark2" />
|
||||
<SolidColorBrush x:Key="DataGridGridLinesBrush"
|
||||
Color="{DynamicResource SystemBaseMediumLowColor}"
|
||||
Opacity="0.4" />
|
||||
<SolidColorBrush
|
||||
x:Key="DataGridGridLinesBrush"
|
||||
Opacity="0.4"
|
||||
Color="{DynamicResource SystemBaseMediumLowColor}" />
|
||||
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush" Color="{DynamicResource DataGridSelectionColor}" />
|
||||
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush"
|
||||
Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
||||
<SolidColorBrush x:Key="MenuFlyoutPresenterBorderBrush" Color="{DynamicResource MenuFlyoutPresenterBorderColor}" />
|
||||
<SolidColorBrush x:Key="ThemeAccentColorBrush" Color="{DynamicResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="ListBoxBackground" Color="{DynamicResource ThemeContentBackgroundColor}" />
|
||||
<SolidColorBrush x:Key="ThemeForegroundBrush" Color="{DynamicResource ThemeForegroundColor}" />
|
||||
@ -241,7 +242,6 @@
|
||||
<SolidColorBrush x:Key="SplitButtonBackgroundCheckedDisabled" Color="#00E81123" />
|
||||
<Thickness x:Key="PageMargin">40 0 40 0</Thickness>
|
||||
<Thickness x:Key="Margin">0 5 0 5</Thickness>
|
||||
<Thickness x:Key="TextMargin">0 4 0 0</Thickness>
|
||||
<Thickness x:Key="MenuItemPadding">5 0 5 0</Thickness>
|
||||
<Color x:Key="MenuFlyoutPresenterBorderColor">#00000000</Color>
|
||||
<Color x:Key="SystemAccentColor">#FF00C3E3</Color>
|
||||
|
@ -1,17 +1,21 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
Width="450"
|
||||
Height="340"
|
||||
Title="{locale:Locale ErrorWindowTitle}">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20">
|
||||
<Window
|
||||
x:Class="Ryujinx.Ava.Ui.Applet.ErrorAppletWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="{locale:Locale ErrorWindowTitle}"
|
||||
Width="450"
|
||||
Height="340"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
@ -21,11 +25,28 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Row="1" Grid.RowSpan="2" Margin="5, 10, 20 , 10" Grid.Column="0"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80" MinWidth="50" />
|
||||
<TextBlock Grid.Row="1" Margin="10" Grid.Column="1" VerticalAlignment="Stretch" TextWrapping="Wrap"
|
||||
Text="{Binding Message}" />
|
||||
<StackPanel Name="ButtonStack" Margin="10" Spacing="10" Grid.Row="2" Grid.Column="1"
|
||||
HorizontalAlignment="Right" Orientation="Horizontal" />
|
||||
<Image
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Grid.Column="0"
|
||||
Height="80"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="10"
|
||||
VerticalAlignment="Stretch"
|
||||
Text="{Binding Message}"
|
||||
TextWrapping="Wrap" />
|
||||
<StackPanel
|
||||
Name="ButtonStack"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10" />
|
||||
</Grid>
|
||||
</Window>
|
@ -1,12 +1,16 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog"
|
||||
Width="400">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20">
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.SwkbdAppletDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Width="400"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@ -18,15 +22,43 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Row="1" VerticalAlignment="Center" Grid.RowSpan="5" Margin="5, 10, 20 , 10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="80"
|
||||
MinWidth="50" />
|
||||
<TextBlock Grid.Row="1" Margin="5" Grid.Column="1" Text="{Binding MainText}" TextWrapping="Wrap" />
|
||||
<TextBlock Grid.Row="2" Margin="5" Grid.Column="1" Text="{Binding SecondaryText}" TextWrapping="Wrap" />
|
||||
<TextBox Name="Input" KeyUp="Message_KeyUp" UseFloatingWatermark="True" TextInput="Message_TextInput"
|
||||
Text="{Binding Message}" Grid.Row="2"
|
||||
Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" TextWrapping="Wrap" />
|
||||
<TextBlock Name="Error" Margin="5" Grid.Row="4" Grid.Column="1" HorizontalAlignment="Stretch"
|
||||
TextWrapping="Wrap" />
|
||||
<Image
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="5"
|
||||
Height="80"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
VerticalAlignment="Center"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="5"
|
||||
Text="{Binding MainText}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="5"
|
||||
Text="{Binding SecondaryText}"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Name="Input"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
KeyUp="Message_KeyUp"
|
||||
Text="{Binding Message}"
|
||||
TextInput="Message_TextInput"
|
||||
TextWrapping="Wrap"
|
||||
UseFloatingWatermark="True" />
|
||||
<TextBlock
|
||||
Name="Error"
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextWrapping="Wrap" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,188 +1,219 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.GameGridView">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
<MenuItem
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.GameGridView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
<MenuItem
|
||||
Command="{Binding ToggleFavorite}"
|
||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuToggleFavoriteToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenUserSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserSaveDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserSaveDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenDeviceSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserDeviceDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserDeviceDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenBcatSaveDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenUserBcatDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenUserBcatDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Command="{Binding OpenTitleUpdateManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageTitleUpdates}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageTitleUpdatesToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenDlcManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageDlc}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageDlcToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenCheatManager}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuManageCheatToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenModsDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenModsDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenSdModsDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
|
||||
<MenuItem
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
|
||||
<MenuItem
|
||||
Command="{Binding PurgePtcCache}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementPurgePptc}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgePptcToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding PurgeShaderCache}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCache}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementPurgeShaderCacheToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenPtcDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenPptcDirectoryToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding OpenShaderCacheDirectory}"
|
||||
Header="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectory}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuCacheManagementOpenShaderCacheDirectoryToolTip}" />
|
||||
</MenuItem>
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
|
||||
<MenuItem
|
||||
</MenuItem>
|
||||
<MenuItem Header="{locale:Locale GameListContextMenuExtractData}">
|
||||
<MenuItem
|
||||
Command="{Binding ExtractExeFs}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataExeFS}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataExeFSToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding ExtractRomFs}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataRomFS}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataRomFSToolTip}" />
|
||||
<MenuItem
|
||||
<MenuItem
|
||||
Command="{Binding ExtractLogo}"
|
||||
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
SelectionChanged="GameList_SelectionChanged"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
VerticalAlignment="Stretch"
|
||||
Items="{Binding AppsObservableList}">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" JustifyContent="Center"
|
||||
AlignContent="FlexStart" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="MaxWidth" Value="0"/>
|
||||
<Setter Property="Opacity" Value="0.0"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="50%">
|
||||
<Setter Property="MaxWidth" Value="1000"/>
|
||||
<Setter Property="Opacity" Value="0.3"/>
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="MaxWidth" Value="1000"/>
|
||||
<Setter Property="Opacity" Value="1.0"/>
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.Styles>
|
||||
<Style Selector="ui|SymbolIcon.small.icon">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.normal.icon">
|
||||
<Setter Property="FontSize" Value="19" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.large.icon">
|
||||
<Setter Property="FontSize" Value="23" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.huge.icon">
|
||||
<Setter Property="FontSize" Value="26" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<Border
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
</MenuItem>
|
||||
</MenuFlyout>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox
|
||||
Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
Items="{Binding AppsObservableList}"
|
||||
SelectionChanged="GameList_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<flex:FlexPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
AlignContent="FlexStart"
|
||||
JustifyContent="Center" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="Margin" Value="5" />
|
||||
<Setter Property="CornerRadius" Value="5" />
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColorDark3}" />
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="MaxWidth" Value="0" />
|
||||
<Setter Property="Opacity" Value="0.0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="50%">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="MaxWidth" Value="1000" />
|
||||
<Setter Property="Opacity" Value="1.0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.Styles>
|
||||
<Style Selector="ui|SymbolIcon.small.icon">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.normal.icon">
|
||||
<Setter Property="FontSize" Value="19" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.large.icon">
|
||||
<Setter Property="FontSize" Value="23" />
|
||||
</Style>
|
||||
<Style Selector="ui|SymbolIcon.huge.icon">
|
||||
<Setter Property="FontSize" Value="26" />
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<Border
|
||||
Margin="0"
|
||||
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}"
|
||||
HorizontalAlignment="Stretch"
|
||||
Padding="{Binding $parent[UserControl].DataContext.GridItemPadding}" CornerRadius="5"
|
||||
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True">
|
||||
<Grid Margin="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image HorizontalAlignment="Stretch" VerticalAlignment="Top" Margin="0" Grid.Row="0"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}"
|
||||
Height="50" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
Margin="5" Grid.Row="1">
|
||||
<TextBlock Text="{Binding TitleName}" TextAlignment="Center" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Foreground="Yellow" Symbol="StarFilled"
|
||||
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
<ui:SymbolIcon Classes.icon="true" Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Foreground="Black" Symbol="Star"
|
||||
IsVisible="{Binding Favorite}" Margin="5" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
VerticalAlignment="Stretch"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid Margin="0">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Height="50"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding $parent[UserControl].DataContext.ShowNames}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TitleName}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ui:SymbolIcon
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.icon="true"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Foreground="Yellow"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<ui:SymbolIcon
|
||||
Margin="5"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Classes.icon="true"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Foreground="Black"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="Star" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,13 +1,16 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.GameListView">
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.GameListView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:flex="clr-namespace:Avalonia.Flexbox;assembly=Avalonia.Flexbox"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<controls:BitmapArrayValueConverter x:Key="ByteImage" />
|
||||
<MenuFlyout x:Key="GameContextMenu" Opened="MenuBase_OnMenuOpened">
|
||||
@ -88,18 +91,23 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ListBox Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
SelectionChanged="GameList_SelectionChanged"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
VerticalAlignment="Stretch"
|
||||
Name="GameListBox"
|
||||
Items="{Binding AppsObservableList}">
|
||||
<ListBox
|
||||
Name="GameListBox"
|
||||
Grid.Row="0"
|
||||
Padding="8"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ContextFlyout="{StaticResource GameContextMenu}"
|
||||
DoubleTapped="GameList_DoubleTapped"
|
||||
Items="{Binding AppsObservableList}"
|
||||
SelectionChanged="GameList_SelectionChanged">
|
||||
<ListBox.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Orientation="Vertical" Spacing="2" />
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="2" />
|
||||
</ItemsPanelTemplate>
|
||||
</ListBox.ItemsPanel>
|
||||
<ListBox.Styles>
|
||||
@ -112,16 +120,16 @@
|
||||
<Style.Animations>
|
||||
<Animation Duration="0:0:0.7">
|
||||
<KeyFrame Cue="0%">
|
||||
<Setter Property="MaxHeight" Value="0"/>
|
||||
<Setter Property="Opacity" Value="0.0"/>
|
||||
<Setter Property="MaxHeight" Value="0" />
|
||||
<Setter Property="Opacity" Value="0.0" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="50%">
|
||||
<Setter Property="MaxHeight" Value="1000"/>
|
||||
<Setter Property="Opacity" Value="0.3"/>
|
||||
<Setter Property="MaxHeight" Value="1000" />
|
||||
<Setter Property="Opacity" Value="0.3" />
|
||||
</KeyFrame>
|
||||
<KeyFrame Cue="100%">
|
||||
<Setter Property="MaxHeight" Value="1000"/>
|
||||
<Setter Property="Opacity" Value="1.0"/>
|
||||
<Setter Property="MaxHeight" Value="1000" />
|
||||
<Setter Property="Opacity" Value="1.0" />
|
||||
</KeyFrame>
|
||||
</Animation>
|
||||
</Style.Animations>
|
||||
@ -130,54 +138,96 @@
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Border HorizontalAlignment="Stretch"
|
||||
Padding="10" CornerRadius="5"
|
||||
VerticalAlignment="Stretch" Margin="0" ClipToBounds="True">
|
||||
<Grid >
|
||||
<Border
|
||||
Margin="0"
|
||||
Padding="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="10"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="10" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<Image
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
<Image
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Margin="0"
|
||||
Classes.huge="{Binding $parent[UserControl].DataContext.IsGridHuge}"
|
||||
Grid.RowSpan="3" Grid.Column="0" Margin="0"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel Orientation="Vertical" Spacing="5" VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Grid.Column="2">
|
||||
<TextBlock Text="{Binding TitleName}" TextAlignment="Left" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding Developer}" TextAlignment="Left" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding Version}" TextAlignment="Left" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical" Spacing="5" VerticalAlignment="Top" HorizontalAlignment="Right"
|
||||
Grid.Column="3">
|
||||
<TextBlock Text="{Binding TimePlayed}" TextAlignment="Right" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding LastPlayed}" TextAlignment="Right" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
<TextBlock Text="{Binding FileSize}" TextAlignment="Right" TextWrapping="Wrap"
|
||||
HorizontalAlignment="Stretch" />
|
||||
</StackPanel>
|
||||
<ui:SymbolIcon Grid.Row="0" Grid.Column="0" FontSize="20"
|
||||
Foreground="Yellow"
|
||||
Symbol="StarFilled"
|
||||
IsVisible="{Binding Favorite}" Margin="-5, -5, 0, 0" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
<ui:SymbolIcon Grid.Row="0" Grid.Column="0" FontSize="20"
|
||||
Foreground="Black"
|
||||
Symbol="Star"
|
||||
IsVisible="{Binding Favorite}" Margin="-5, -5, 0, 0" VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left" />
|
||||
Classes.large="{Binding $parent[UserControl].DataContext.IsGridLarge}"
|
||||
Classes.normal="{Binding $parent[UserControl].DataContext.IsGridMedium}"
|
||||
Classes.small="{Binding $parent[UserControl].DataContext.IsGridSmall}"
|
||||
Source="{Binding Icon, Converter={StaticResource ByteImage}}" />
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
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>
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Orientation="Vertical"
|
||||
Spacing="5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding TimePlayed}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding LastPlayed}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Stretch"
|
||||
Text="{Binding FileSize}"
|
||||
TextAlignment="Right"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
<ui:SymbolIcon
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="20"
|
||||
Foreground="Yellow"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="StarFilled" />
|
||||
<ui:SymbolIcon
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
Margin="-5,-5,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Top"
|
||||
FontSize="20"
|
||||
Foreground="Black"
|
||||
IsVisible="{Binding Favorite}"
|
||||
Symbol="Star" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
@ -1,18 +1,31 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.InputDialog">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Center" Margin="5,10,5, 5">
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.InputDialog"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="5,10,5,5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock HorizontalAlignment="Center" Text="{Binding Message}" />
|
||||
<TextBox MaxLength="{Binding MaxLength}" Grid.Row="1" Margin="10" Width="300" HorizontalAlignment="Center"
|
||||
Text="{Binding Input, Mode=TwoWay}" />
|
||||
<TextBlock Grid.Row="2" Margin="5, 5, 5, 10" HorizontalAlignment="Center" Text="{Binding SubMessage}" />
|
||||
<TextBox
|
||||
Grid.Row="1"
|
||||
Width="300"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLength="{Binding MaxLength}"
|
||||
Text="{Binding Input, Mode=TwoWay}" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="5,5,5,10"
|
||||
HorizontalAlignment="Center"
|
||||
Text="{Binding SubMessage}" />
|
||||
</Grid>
|
||||
</UserControl>
|
@ -1,14 +1,18 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.UpdateWaitWindow"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
SizeToContent="WidthAndHeight"
|
||||
Title="Ryujinx - Waiting">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="20">
|
||||
<Window
|
||||
x:Class="Ryujinx.Ava.Ui.Controls.UpdateWaitWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx - Waiting"
|
||||
SizeToContent="WidthAndHeight"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@ -17,12 +21,22 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Image Grid.Row="1" Margin="5, 10, 20 , 10" Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common"
|
||||
Height="70"
|
||||
MinWidth="50" />
|
||||
<StackPanel Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" Orientation="Vertical">
|
||||
<TextBlock Margin="5" Name="PrimaryText" />
|
||||
<TextBlock VerticalAlignment="Center" Name="SecondaryText" Margin="5" />
|
||||
<Image
|
||||
Grid.Row="1"
|
||||
Height="70"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Vertical">
|
||||
<TextBlock Name="PrimaryText" Margin="5" />
|
||||
<TextBlock
|
||||
Name="SecondaryText"
|
||||
Margin="5"
|
||||
VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Window>
|
@ -1,28 +1,41 @@
|
||||
<window:StyleableWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
CanResize="False"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Width="850" MinHeight="550" Height="550"
|
||||
SizeToContent="Width"
|
||||
MinWidth="500"
|
||||
Title="Ryujinx - About">
|
||||
<Grid Margin="15" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.AboutWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx - About"
|
||||
Width="850"
|
||||
Height="550"
|
||||
MinWidth="500"
|
||||
MinHeight="550"
|
||||
d:DesignHeight="350"
|
||||
d:DesignWidth="400"
|
||||
CanResize="False"
|
||||
SizeToContent="Width"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="15"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Row="1" Margin="20" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="0">
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
@ -40,93 +53,168 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Margin="5, 10, 20 , 10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" Height="110" MinWidth="50" />
|
||||
<TextBlock FontSize="35" TextAlignment="Center" Grid.Row="0" Grid.Column="1" Text="Ryujinx"
|
||||
Margin="0,20,0,0" />
|
||||
<TextBlock FontSize="16" TextAlignment="Center" Grid.Row="1" Grid.Column="1" Text="(REE-YOU-JINX)"
|
||||
Margin="0,0,0,0" />
|
||||
<Button Grid.Column="1" Background="Transparent" HorizontalAlignment="Center" Margin="0" Grid.Row="2"
|
||||
Tag="https://www.ryujinx.org/"
|
||||
Click="Button_OnClick">
|
||||
<TextBlock ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}"
|
||||
TextAlignment="Center" TextDecorations="Underline" Text="www.ryujinx.org" />
|
||||
<Image
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="0"
|
||||
Height="110"
|
||||
MinWidth="50"
|
||||
Margin="5,10,20,10"
|
||||
Source="resm:Ryujinx.Ui.Common.Resources.Logo_Ryujinx.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Margin="0,20,0,0"
|
||||
FontSize="35"
|
||||
Text="Ryujinx"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,0"
|
||||
FontSize="16"
|
||||
Text="(REE-YOU-JINX)"
|
||||
TextAlignment="Center" />
|
||||
<Button
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://www.ryujinx.org/">
|
||||
<TextBlock
|
||||
Text="www.ryujinx.org"
|
||||
TextAlignment="Center"
|
||||
TextDecorations="Underline"
|
||||
ToolTip.Tip="{locale:Locale AboutUrlTooltipMessage}" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<TextBlock TextAlignment="Center" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
Text="{Binding Version}" Grid.Row="1" />
|
||||
<TextBlock Grid.Row="2" TextAlignment="Center" HorizontalAlignment="Center" Margin="20"
|
||||
Text="{locale:Locale AboutDisclaimerMessage}"
|
||||
MaxLines="2" />
|
||||
<TextBlock Grid.Row="3" TextAlignment="Center" HorizontalAlignment="Center" Margin="20"
|
||||
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
|
||||
Name="AmiiboLabel"
|
||||
PointerPressed="AmiiboLabel_OnPointerPressed"
|
||||
MaxLines="2" />
|
||||
<StackPanel Spacing="10" Orientation="Horizontal" Grid.Row="4" HorizontalAlignment="Center">
|
||||
<StackPanel Orientation="Vertical"
|
||||
ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
|
||||
<Button Height="65" Background="Transparent" Tag="https://www.patreon.com/ryujinx"
|
||||
Click="Button_OnClick">
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Version}"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLines="2"
|
||||
Text="{locale:Locale AboutDisclaimerMessage}"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Name="AmiiboLabel"
|
||||
Grid.Row="3"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Center"
|
||||
MaxLines="2"
|
||||
PointerPressed="AmiiboLabel_OnPointerPressed"
|
||||
Text="{locale:Locale AboutAmiiboDisclaimerMessage}"
|
||||
TextAlignment="Center" />
|
||||
<StackPanel
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Center"
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutPatreonUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://www.patreon.com/ryujinx">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Patreon.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Patreon" HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Patreon" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical"
|
||||
ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
|
||||
<Button Height="65" Background="Transparent" Tag="https://github.com/Ryujinx/Ryujinx"
|
||||
Click="Button_OnClick">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutGithubUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_GitHub.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="GitHub" HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="GitHub" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical"
|
||||
ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
|
||||
<Button Height="65" Background="Transparent" Tag="https://discordapp.com/invite/N2FmfVc"
|
||||
Click="Button_OnClick">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutDiscordUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://discordapp.com/invite/N2FmfVc">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Discord.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Discord" HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Discord" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Vertical"
|
||||
ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
|
||||
<Button Height="65" Background="Transparent" Tag="https://twitter.com/RyujinxEmu"
|
||||
Click="Button_OnClick">
|
||||
<StackPanel Orientation="Vertical" ToolTip.Tip="{locale:Locale AboutTwitterUrlTooltipMessage}">
|
||||
<Button
|
||||
Height="65"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://twitter.com/RyujinxEmu">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Image Source="resm:Ryujinx.Ui.Common.Resources.Logo_Twitter.png?assembly=Ryujinx.Ui.Common" />
|
||||
<TextBlock Grid.Row="1" Margin="0,5,0,0" Text="Twitter" HorizontalAlignment="Center" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="0,5,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
Text="Twitter" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Grid.Row="1" Grid.Column="1" VerticalAlignment="Stretch" Margin="5" Width="2" BorderBrush="White"
|
||||
BorderThickness="1,0,0,0">
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Width="2"
|
||||
Margin="5"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="1,0,0,0">
|
||||
<Separator Width="0" />
|
||||
</Border>
|
||||
<Grid Grid.Row="1" Margin="20" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Column="2">
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@ -136,27 +224,58 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="{locale:Locale AboutRyujinxAboutTitle}" FontWeight="Bold" TextDecorations="Underline" />
|
||||
<TextBlock LineHeight="20" Grid.Row="1" Margin="20,5,5,5"
|
||||
Text="{locale:Locale AboutRyujinxAboutContent}" />
|
||||
<TextBlock Grid.Row="2" Margin="0,10,0,0" Text="{locale:Locale AboutRyujinxMaintainersTitle}"
|
||||
FontWeight="Bold"
|
||||
TextDecorations="Underline" />
|
||||
<TextBlock LineHeight="20" Grid.Row="3" Margin="20,5,5,5"
|
||||
Text="{Binding Developers}" />
|
||||
<Button Background="Transparent" HorizontalAlignment="Right" Grid.Row="4"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a" Click="Button_OnClick">
|
||||
<TextBlock ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}"
|
||||
TextAlignment="Right" TextDecorations="Underline"
|
||||
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}" />
|
||||
<TextBlock
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxAboutTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Margin="20,5,5,5"
|
||||
LineHeight="20"
|
||||
Text="{locale:Locale AboutRyujinxAboutContent}" />
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Margin="0,10,0,0"
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxMaintainersTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<TextBlock
|
||||
Grid.Row="3"
|
||||
Margin="20,5,5,5"
|
||||
LineHeight="20"
|
||||
Text="{Binding Developers}" />
|
||||
<Button
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Right"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx/graphs/contributors?type=a">
|
||||
<TextBlock
|
||||
Text="{locale:Locale AboutRyujinxContributorsButtonHeader}"
|
||||
TextAlignment="Right"
|
||||
TextDecorations="Underline"
|
||||
ToolTip.Tip="{locale:Locale AboutRyujinxMaintainersContentTooltipMessage}" />
|
||||
</Button>
|
||||
<TextBlock Grid.Row="5" Margin="0,0,0,0" Text="{locale:Locale AboutRyujinxSupprtersTitle}"
|
||||
FontWeight="Bold"
|
||||
TextDecorations="Underline" />
|
||||
<Border Width="460" Grid.Row="6" VerticalAlignment="Stretch" Height="200" BorderThickness="1" Margin="20,5"
|
||||
BorderBrush="White" Padding="5">
|
||||
<TextBlock TextWrapping="Wrap" VerticalAlignment="Top" Name="SupportersTextBlock"
|
||||
Text="{Binding Supporters}" />
|
||||
<TextBlock
|
||||
Grid.Row="5"
|
||||
Margin="0,0,0,0"
|
||||
FontWeight="Bold"
|
||||
Text="{locale:Locale AboutRyujinxSupprtersTitle}"
|
||||
TextDecorations="Underline" />
|
||||
<Border
|
||||
Grid.Row="6"
|
||||
Width="460"
|
||||
Height="200"
|
||||
Margin="20,5"
|
||||
Padding="5"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="1">
|
||||
<TextBlock
|
||||
Name="SupportersTextBlock"
|
||||
VerticalAlignment="Top"
|
||||
Text="{Binding Supporters}"
|
||||
TextWrapping="Wrap" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
@ -1,25 +1,25 @@
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.MainWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:controls="clr-namespace:Ryujinx.Ava.Ui.Controls"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="clr-namespace:Ryujinx.Ava.Ui.Models"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.Ui.ViewModels"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx"
|
||||
Height="785"
|
||||
Width="1280"
|
||||
d:DesignHeight="720"
|
||||
d:DesignWidth="1280"
|
||||
Height="785"
|
||||
MinWidth="1024"
|
||||
MinHeight="680"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
d:DesignHeight="720"
|
||||
d:DesignWidth="1280"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:MainWindowViewModel"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
mc:Ignorable="d">
|
||||
<Window.Styles>
|
||||
<Style Selector="TitleBar:fullscreen">
|
||||
@ -38,23 +38,28 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<controls:OffscreenTextBox Name="HiddenTextBox" Grid.Row="0" />
|
||||
<ContentControl Grid.Row="1"
|
||||
Focusable="False"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False">
|
||||
<ui:ContentDialog Name="ContentDialog"
|
||||
KeyboardNavigation.IsTabStop="False"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
IsVisible="True" />
|
||||
<ContentControl
|
||||
Grid.Row="1"
|
||||
Focusable="False"
|
||||
IsVisible="False"
|
||||
KeyboardNavigation.IsTabStop="False">
|
||||
<ui:ContentDialog
|
||||
Name="ContentDialog"
|
||||
IsPrimaryButtonEnabled="True"
|
||||
IsSecondaryButtonEnabled="True"
|
||||
IsVisible="True"
|
||||
KeyboardNavigation.IsTabStop="False" />
|
||||
</ContentControl>
|
||||
<StackPanel IsVisible="False" Grid.Row="0">
|
||||
<StackPanel Grid.Row="0" IsVisible="False">
|
||||
<controls:HotKeyControl Name="FullscreenHotKey" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<controls:HotKeyControl Name="FullscreenHotKey2" Command="{ReflectionBinding ToggleFullscreen}" />
|
||||
<controls:HotKeyControl Name="DockToggleHotKey" Command="{ReflectionBinding ToggleDockMode}" />
|
||||
<controls:HotKeyControl Name="ExitHotKey" Command="{ReflectionBinding ExitCurrentState}" />
|
||||
</StackPanel>
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1">
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
@ -73,47 +78,51 @@
|
||||
<DockPanel HorizontalAlignment="Stretch">
|
||||
<Menu
|
||||
Name="Menu"
|
||||
Margin="0"
|
||||
Height="35"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Left">
|
||||
<Menu.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<DockPanel HorizontalAlignment="Stretch" Margin="0" />
|
||||
<DockPanel Margin="0" HorizontalAlignment="Stretch" />
|
||||
</ItemsPanelTemplate>
|
||||
</Menu.ItemsPanel>
|
||||
<MenuItem
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarFile}">
|
||||
<MenuItem IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
Command="{ReflectionBinding OpenFile}"
|
||||
Header="{locale:Locale MenuBarFileOpenFromFile}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
|
||||
<MenuItem IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
Command="{ReflectionBinding OpenFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenUnpacked}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
|
||||
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}"
|
||||
IsEnabled="{Binding IsAppletMenuActive}">
|
||||
<MenuItem Command="{ReflectionBinding OpenMiiApplet}" Header="Mii Edit Applet"
|
||||
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarFile}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenFile}"
|
||||
Header="{locale:Locale MenuBarFileOpenFromFile}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFileTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenUnpacked}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale LoadApplicationFolderTooltip}" />
|
||||
<MenuItem Header="{locale:Locale MenuBarFileOpenApplet}" IsEnabled="{Binding IsAppletMenuActive}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenMiiApplet}"
|
||||
Header="Mii Edit Applet"
|
||||
ToolTip.Tip="{locale:Locale MenuBarFileOpenAppletOpenMiiAppletToolTip}" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{ReflectionBinding OpenRyujinxFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
|
||||
<MenuItem Command="{ReflectionBinding OpenLogsFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenRyujinxFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenEmuFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxFolderTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenLogsFolder}"
|
||||
Header="{locale:Locale MenuBarFileOpenLogsFolder}"
|
||||
ToolTip.Tip="{locale:Locale OpenRyujinxLogsTooltip}" />
|
||||
<Separator />
|
||||
<MenuItem Command="{ReflectionBinding CloseWindow}"
|
||||
Header="{locale:Locale MenuBarFileExit}"
|
||||
ToolTip.Tip="{locale:Locale ExitTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding CloseWindow}"
|
||||
Header="{locale:Locale MenuBarFileExit}"
|
||||
ToolTip.Tip="{locale:Locale ExitTooltip}" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarOptions}">
|
||||
<MenuItem Command="{ReflectionBinding ToggleFullscreen}"
|
||||
Header="{locale:Locale MenuBarOptionsToggleFullscreen}" InputGesture="F11" />
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarOptions}">
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ToggleFullscreen}"
|
||||
Header="{locale:Locale MenuBarOptionsToggleFullscreen}"
|
||||
InputGesture="F11" />
|
||||
<MenuItem Header="{locale:Locale MenuBarOptionsStartGamesInFullscreen}">
|
||||
<MenuItem.Icon>
|
||||
<CheckBox IsChecked="{Binding StartGamesInFullscreen, Mode=TwoWay}" />
|
||||
@ -126,60 +135,82 @@
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Header="{locale:Locale MenuBarOptionsChangeLanguage}">
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="en_US"
|
||||
Header="American English" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="pt_BR"
|
||||
Header="Brazilian Portuguese" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="es_ES"
|
||||
Header="Castilian Spanish" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="fr_FR"
|
||||
Header="French" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="de_DE"
|
||||
Header="German" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="el_GR"
|
||||
Header="Greek" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="it_IT"
|
||||
Header="Italian" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ko_KR"
|
||||
Header="Korean" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="ru_RU"
|
||||
Header="Russian" />
|
||||
<MenuItem Command="{ReflectionBinding ChangeLanguage}" CommandParameter="tr_TR"
|
||||
Header="Turkish" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="en_US"
|
||||
Header="American English" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="pt_BR"
|
||||
Header="Brazilian Portuguese" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="es_ES"
|
||||
Header="Castilian Spanish" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="fr_FR"
|
||||
Header="French" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="de_DE"
|
||||
Header="German" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="el_GR"
|
||||
Header="Greek" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="it_IT"
|
||||
Header="Italian" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ko_KR"
|
||||
Header="Korean" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="ru_RU"
|
||||
Header="Russian" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ChangeLanguage}"
|
||||
CommandParameter="tr_TR"
|
||||
Header="Turkish" />
|
||||
</MenuItem>
|
||||
<Separator />
|
||||
<MenuItem Command="{ReflectionBinding OpenSettings}"
|
||||
Header="{locale:Locale MenuBarOptionsSettings}"
|
||||
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
|
||||
<MenuItem Command="{ReflectionBinding ManageProfiles}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
|
||||
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenSettings}"
|
||||
Header="{locale:Locale MenuBarOptionsSettings}"
|
||||
ToolTip.Tip="{locale:Locale OpenSettingsTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding ManageProfiles}"
|
||||
Header="{locale:Locale MenuBarOptionsManageUserProfiles}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}"
|
||||
ToolTip.Tip="{locale:Locale OpenProfileManagerTooltip}" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
Name="ActionsMenuItem"
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarActions}"
|
||||
Name="ActionsMenuItem"
|
||||
IsEnabled="{Binding IsGameRunning}">
|
||||
<MenuItem
|
||||
Click="PauseEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsPauseEmulation}"
|
||||
InputGesture="{Binding PauseKey}"
|
||||
IsEnabled="{Binding !IsPaused}"
|
||||
IsVisible="{Binding !IsPaused}"
|
||||
InputGesture="{Binding PauseKey}" />
|
||||
IsVisible="{Binding !IsPaused}" />
|
||||
<MenuItem
|
||||
Click="ResumeEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsResumeEmulation}"
|
||||
InputGesture="{Binding PauseKey}"
|
||||
IsEnabled="{Binding IsPaused}"
|
||||
IsVisible="{Binding IsPaused}"
|
||||
InputGesture="{Binding PauseKey}" />
|
||||
IsVisible="{Binding IsPaused}" />
|
||||
<MenuItem
|
||||
Click="StopEmulation_Click"
|
||||
Header="{locale:Locale MenuBarOptionsStopEmulation}"
|
||||
ToolTip.Tip="{locale:Locale StopEmulationTooltip}"
|
||||
IsEnabled="{Binding IsGameRunning}" InputGesture="Escape" />
|
||||
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}"
|
||||
Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
|
||||
InputGesture="Escape"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
ToolTip.Tip="{locale:Locale StopEmulationTooltip}" />
|
||||
<MenuItem Command="{ReflectionBinding SimulateWakeUpMessage}" Header="{locale:Locale MenuBarOptionsSimulateWakeUpMessage}" />
|
||||
<Separator />
|
||||
<MenuItem
|
||||
Name="ScanAmiiboMenuItem"
|
||||
@ -187,41 +218,38 @@
|
||||
Command="{ReflectionBinding OpenAmiiboWindow}"
|
||||
Header="{locale:Locale MenuBarActionsScanAmiibo}"
|
||||
IsEnabled="{Binding IsAmiiboRequested}" />
|
||||
<MenuItem Command="{ReflectionBinding TakeScreenshot}"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
|
||||
InputGesture="{Binding ScreenshotKey}" />
|
||||
<MenuItem Command="{ReflectionBinding HideUi}"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
Header="{locale:Locale MenuBarFileToolsHideUi}"
|
||||
InputGesture="{Binding ShowUiKey}" />
|
||||
<MenuItem Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
|
||||
IsEnabled="{Binding IsGameRunning}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding TakeScreenshot}"
|
||||
Header="{locale:Locale MenuBarFileToolsTakeScreenshot}"
|
||||
InputGesture="{Binding ScreenshotKey}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding HideUi}"
|
||||
Header="{locale:Locale MenuBarFileToolsHideUi}"
|
||||
InputGesture="{Binding ShowUiKey}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenCheatManagerForCurrentApp}"
|
||||
Header="{locale:Locale GameListContextMenuManageCheat}"
|
||||
IsEnabled="{Binding IsGameRunning}" />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarTools}">
|
||||
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}"
|
||||
IsEnabled="{Binding EnableNonGameRunningControls}">
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}"
|
||||
Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}"
|
||||
Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarTools}">
|
||||
<MenuItem Header="{locale:Locale MenuBarToolsInstallFirmware}" IsEnabled="{Binding EnableNonGameRunningControls}">
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
|
||||
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
|
||||
</MenuItem>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
VerticalAlignment="Center"
|
||||
Header="{locale:Locale MenuBarHelp}">
|
||||
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
|
||||
<MenuItem
|
||||
Name="UpdateMenuItem"
|
||||
Command="{ReflectionBinding CheckForUpdates}"
|
||||
Header="{locale:Locale MenuBarHelpCheckForUpdates}"
|
||||
ToolTip.Tip="{locale:Locale CheckUpdatesTooltip}" />
|
||||
<Separator />
|
||||
<MenuItem Command="{ReflectionBinding OpenAboutWindow}"
|
||||
Header="{locale:Locale MenuBarHelpAbout}"
|
||||
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
|
||||
<MenuItem
|
||||
Command="{ReflectionBinding OpenAboutWindow}"
|
||||
Header="{locale:Locale MenuBarHelpAbout}"
|
||||
ToolTip.Tip="{locale:Locale OpenAboutTooltip}" />
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</DockPanel>
|
||||
@ -230,152 +258,213 @@
|
||||
Name="Content"
|
||||
Grid.Row="1"
|
||||
Padding="0"
|
||||
IsVisible="{Binding ShowContent}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
||||
BorderThickness="0,0,0,0"
|
||||
DockPanel.Dock="Top">
|
||||
DockPanel.Dock="Top"
|
||||
IsVisible="{Binding ShowContent}">
|
||||
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<DockPanel Grid.Row="0" HorizontalAlignment="Stretch" Margin="0,0,0,5">
|
||||
<DockPanel
|
||||
Grid.Row="0"
|
||||
Margin="0,0,0,5"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Button
|
||||
IsEnabled="{Binding IsGrid}" VerticalAlignment="Stretch" MinWidth="40" Width="40"
|
||||
Margin="5,2,0,2" Command="{ReflectionBinding SetListMode}">
|
||||
<ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0"
|
||||
Glyph="{controls:GlyphValueConverter List}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
Width="40"
|
||||
MinWidth="40"
|
||||
Margin="5,2,0,2"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{ReflectionBinding SetListMode}"
|
||||
IsEnabled="{Binding IsGrid}">
|
||||
<ui:FontIcon
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
Glyph="{controls:GlyphValueConverter List}" />
|
||||
</Button>
|
||||
<Button
|
||||
IsEnabled="{Binding IsList}" VerticalAlignment="Stretch" MinWidth="40" Width="40"
|
||||
Margin="5,2,5,2" Command="{ReflectionBinding SetGridMode}">
|
||||
<ui:FontIcon FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0"
|
||||
Glyph="{controls:GlyphValueConverter Grid}"
|
||||
HorizontalAlignment="Stretch" />
|
||||
Width="40"
|
||||
MinWidth="40"
|
||||
Margin="5,2,5,2"
|
||||
VerticalAlignment="Stretch"
|
||||
Command="{ReflectionBinding SetGridMode}"
|
||||
IsEnabled="{Binding IsList}">
|
||||
<ui:FontIcon
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
FontFamily="avares://FluentAvalonia/Fonts#Symbols"
|
||||
Glyph="{controls:GlyphValueConverter Grid}" />
|
||||
</Button>
|
||||
<TextBlock Text="{locale:Locale IconSize}"
|
||||
VerticalAlignment="Center" Margin="10,0"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
|
||||
<Slider Width="150" Margin="5,-10,5 ,0" Height="35"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
|
||||
VerticalAlignment="Center" Minimum="1" Maximum="4" IsSnapToTickEnabled="True"
|
||||
TickFrequency="1" Value="{Binding GridSizeScale}" />
|
||||
<CheckBox Margin="0" IsChecked="{Binding ShowNames, Mode=TwoWay}" VerticalAlignment="Center"
|
||||
IsVisible="{Binding IsGrid}">
|
||||
<TextBlock Text="{locale:Locale CommonShowNames}" Margin="5,3,0,0" />
|
||||
<TextBlock
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale IconSize}"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}" />
|
||||
<Slider
|
||||
Width="150"
|
||||
Height="35"
|
||||
Margin="5,-10,5,0"
|
||||
VerticalAlignment="Center"
|
||||
IsSnapToTickEnabled="True"
|
||||
Maximum="4"
|
||||
Minimum="1"
|
||||
TickFrequency="1"
|
||||
ToolTip.Tip="{locale:Locale IconSizeTooltip}"
|
||||
Value="{Binding GridSizeScale}" />
|
||||
<CheckBox
|
||||
Margin="0"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding ShowNames, Mode=TwoWay}"
|
||||
IsVisible="{Binding IsGrid}">
|
||||
<TextBlock Margin="5,3,0,0" Text="{locale:Locale CommonShowNames}" />
|
||||
</CheckBox>
|
||||
<TextBox
|
||||
Name="SearchBox"
|
||||
DockPanel.Dock="Right"
|
||||
VerticalAlignment="Center"
|
||||
MinWidth="200"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Right"
|
||||
KeyUp="SearchBox_OnKeyUp"
|
||||
Text="{Binding SearchText}"
|
||||
Watermark="{locale:Locale MenuSearch}" />
|
||||
<ui:DropDownButton DockPanel.Dock="Right"
|
||||
HorizontalAlignment="Right" Width="150" VerticalAlignment="Center"
|
||||
Content="{Binding SortName}">
|
||||
<ui:DropDownButton
|
||||
Width="150"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Content="{Binding SortName}"
|
||||
DockPanel.Dock="Right">
|
||||
<ui:DropDownButton.Flyout>
|
||||
<Flyout Placement="Bottom">
|
||||
<StackPanel Orientation="Vertical" HorizontalAlignment="Stretch" Margin="0">
|
||||
<StackPanel
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<StackPanel>
|
||||
<RadioButton Tag="Favorite"
|
||||
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
|
||||
GroupName="Sort"
|
||||
Content="{locale:Locale CommonFavorite}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="Title" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderApplication}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="Developer" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderDeveloper}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="TotalTimePlayed" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderTimePlayed}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="LastPlayed" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderLastPlayed}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="FileType" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderFileExtension}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="FileSize" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderFileSize}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton Tag="Path" GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
|
||||
Content="{locale:Locale GameListHeaderPath}"
|
||||
Checked="Sort_Checked" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale CommonFavorite}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByFavorite, Mode=OneTime}"
|
||||
Tag="Favorite" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderApplication}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTitle, Mode=OneTime}"
|
||||
Tag="Title" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderDeveloper}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByDeveloper, Mode=OneTime}"
|
||||
Tag="Developer" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderTimePlayed}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByTimePlayed, Mode=OneTime}"
|
||||
Tag="TotalTimePlayed" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderLastPlayed}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByLastPlayed, Mode=OneTime}"
|
||||
Tag="LastPlayed" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderFileExtension}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByType, Mode=OneTime}"
|
||||
Tag="FileType" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderFileSize}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedBySize, Mode=OneTime}"
|
||||
Tag="FileSize" />
|
||||
<RadioButton
|
||||
Checked="Sort_Checked"
|
||||
Content="{locale:Locale GameListHeaderPath}"
|
||||
GroupName="Sort"
|
||||
IsChecked="{Binding IsSortedByPath, Mode=OneTime}"
|
||||
Tag="Path" />
|
||||
</StackPanel>
|
||||
<Border HorizontalAlignment="Stretch" Margin="5" Height="2" BorderBrush="White"
|
||||
Width="60" BorderThickness="0,1,0,0">
|
||||
<Separator HorizontalAlignment="Stretch" Height="0" />
|
||||
<Border
|
||||
Width="60"
|
||||
Height="2"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
BorderBrush="White"
|
||||
BorderThickness="0,1,0,0">
|
||||
<Separator Height="0" HorizontalAlignment="Stretch" />
|
||||
</Border>
|
||||
<RadioButton Tag="Ascending" IsChecked="{Binding IsAscending, Mode=OneTime}"
|
||||
GroupName="Order"
|
||||
Content="{locale:Locale OrderAscending}" Checked="Order_Checked" />
|
||||
<RadioButton Tag="Descending" GroupName="Order"
|
||||
IsChecked="{Binding !IsAscending, Mode=OneTime}"
|
||||
Content="{locale:Locale OrderDescending}" Checked="Order_Checked" />
|
||||
<RadioButton
|
||||
Checked="Order_Checked"
|
||||
Content="{locale:Locale OrderAscending}"
|
||||
GroupName="Order"
|
||||
IsChecked="{Binding IsAscending, Mode=OneTime}"
|
||||
Tag="Ascending" />
|
||||
<RadioButton
|
||||
Checked="Order_Checked"
|
||||
Content="{locale:Locale OrderDescending}"
|
||||
GroupName="Order"
|
||||
IsChecked="{Binding !IsAscending, Mode=OneTime}"
|
||||
Tag="Descending" />
|
||||
</StackPanel>
|
||||
</Flyout>
|
||||
</ui:DropDownButton.Flyout>
|
||||
</ui:DropDownButton>
|
||||
<TextBlock DockPanel.Dock="Right" HorizontalAlignment="Right"
|
||||
Text="{locale:Locale CommonSort}" VerticalAlignment="Center" Margin="10,0" />
|
||||
<TextBlock
|
||||
Margin="10,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Right"
|
||||
Text="{locale:Locale CommonSort}" />
|
||||
</DockPanel>
|
||||
<controls:GameListView
|
||||
x:Name="GameList"
|
||||
Grid.Row="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
IsVisible="{Binding IsList}" />
|
||||
<controls:GameGridView
|
||||
x:Name="GameGrid"
|
||||
Grid.Row="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
IsVisible="{Binding IsGrid}" />
|
||||
</Grid>
|
||||
</ContentControl>
|
||||
<Grid Grid.Row="1"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
ZIndex="1000"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
ZIndex="1000">
|
||||
<Grid
|
||||
HorizontalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Margin="40"
|
||||
VerticalAlignment="Center">
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
Grid.Column="0"
|
||||
Grid.RowSpan="2"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Grid.Column="0"
|
||||
Width="256"
|
||||
Height="256"
|
||||
Margin="10"
|
||||
@ -383,62 +472,64 @@
|
||||
BorderBrush="Black"
|
||||
BorderThickness="2"
|
||||
BoxShadow="4 4 32 8 #40000000"
|
||||
CornerRadius="3">
|
||||
CornerRadius="3"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Image
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Width="256"
|
||||
Height="256"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Source="{Binding SelectedIcon, Converter={StaticResource ByteImage}}" />
|
||||
</Border>
|
||||
<Grid Grid.Column="1"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center">
|
||||
<Grid
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Grid.Row="0"
|
||||
Margin="10"
|
||||
FontSize="30"
|
||||
FontWeight="Bold"
|
||||
TextWrapping="Wrap"
|
||||
Text="{Binding LoadHeading}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Text="{Binding LoadHeading}"
|
||||
TextAlignment="Left"
|
||||
TextWrapping="Wrap" />
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
CornerRadius="5"
|
||||
ClipToBounds="True"
|
||||
BorderBrush="{Binding ProgressBarBackgroundColor}"
|
||||
Margin="10"
|
||||
Padding="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="10"
|
||||
BorderThickness="1">
|
||||
BorderBrush="{Binding ProgressBarBackgroundColor}"
|
||||
BorderThickness="1"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5"
|
||||
IsVisible="{Binding ShowLoadProgress}">
|
||||
<ProgressBar
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Height="10"
|
||||
MinWidth="500"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
CornerRadius="5"
|
||||
ClipToBounds="True"
|
||||
MinWidth="500"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{Binding ProgressBarBackgroundColor}"
|
||||
ClipToBounds="True"
|
||||
CornerRadius="5"
|
||||
Foreground="{Binding ProgressBarForegroundColor}"
|
||||
IsIndeterminate="{Binding IsLoadingIndeterminate}"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Maximum="{Binding ProgressMaximum}"
|
||||
Minimum="0"
|
||||
IsIndeterminate="{Binding IsLoadingIndeterminate}"
|
||||
Value="{Binding ProgressValue}" />
|
||||
</Border>
|
||||
<TextBlock
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Grid.Row="2"
|
||||
Margin="10"
|
||||
FontSize="18"
|
||||
IsVisible="{Binding ShowLoadProgress}"
|
||||
Text="{Binding CacheLoadStatus}"
|
||||
TextAlignment="Left" />
|
||||
</Grid>
|
||||
@ -450,8 +541,8 @@
|
||||
Height="30"
|
||||
Margin="0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
VerticalAlignment="Bottom"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
DockPanel.Dock="Bottom"
|
||||
IsVisible="{Binding ShowMenuAndStatusBar}">
|
||||
<Grid.ColumnDefinitions>
|
||||
@ -460,8 +551,11 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Grid.Column="0" IsVisible="{Binding EnableNonGameRunningControls}"
|
||||
VerticalAlignment="Center" Margin="10,0">
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding EnableNonGameRunningControls}">
|
||||
<Grid Margin="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@ -476,7 +570,10 @@
|
||||
VerticalAlignment="Center"
|
||||
Background="Transparent"
|
||||
Command="{ReflectionBinding LoadApplications}">
|
||||
<ui:SymbolIcon Symbol="Refresh" Height="100" Width="50" />
|
||||
<ui:SymbolIcon
|
||||
Width="50"
|
||||
Height="100"
|
||||
Symbol="Refresh" />
|
||||
</Button>
|
||||
<TextBlock
|
||||
Name="LoadStatus"
|
||||
@ -489,11 +586,11 @@
|
||||
Name="LoadProgressBar"
|
||||
Grid.Column="2"
|
||||
Height="6"
|
||||
Maximum="{Binding StatusBarProgressMaximum}"
|
||||
Value="{Binding StatusBarProgressValue}"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{DynamicResource HighlightColor}"
|
||||
IsVisible="{Binding EnableNonGameRunningControls}" />
|
||||
IsVisible="{Binding EnableNonGameRunningControls}"
|
||||
Maximum="{Binding StatusBarProgressMaximum}"
|
||||
Value="{Binding StatusBarProgressValue}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
@ -505,132 +602,137 @@
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Name="VsyncStatus"
|
||||
Margin="0,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{Binding VsyncColor}"
|
||||
PointerReleased="VsyncStatus_PointerReleased"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Margin="0,0,5,0"
|
||||
PointerReleased="VsyncStatus_PointerReleased"
|
||||
Text="VSync"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
Name="DockedStatus"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
PointerReleased="DockedStatus_PointerReleased"
|
||||
Text="{Binding DockedStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
Name="AspectRatioStatus"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Margin="5,0,5,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
PointerReleased="AspectRatioStatus_PointerReleased"
|
||||
Text="{Binding AspectRatioStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<ui:ToggleSplitButton
|
||||
Name="VolumeStatus"
|
||||
Margin="-2,0,-3,0"
|
||||
Padding="5,0,0,5"
|
||||
Name="VolumeStatus"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderBrush="{DynamicResource ThemeContentBackgroundColor}"
|
||||
Background="{DynamicResource ThemeContentBackgroundColor}"
|
||||
BorderBrush="{DynamicResource ThemeContentBackgroundColor}"
|
||||
Content="{Binding VolumeStatusText}"
|
||||
IsChecked="{Binding VolumeMuted}"
|
||||
Content="{Binding VolumeStatusText}">
|
||||
IsVisible="{Binding !ShowLoadProgress}">
|
||||
<ui:ToggleSplitButton.Flyout>
|
||||
<Flyout Placement="Bottom" ShowMode="TransientWithDismissOnPointerMoveAway">
|
||||
<Grid Margin="0">
|
||||
<Slider Value="{Binding Volume}"
|
||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||
Minimum="0"
|
||||
Maximum="1"
|
||||
TickFrequency="0.05"
|
||||
IsSnapToTickEnabled="True"
|
||||
Padding="0"
|
||||
Margin="0"
|
||||
SmallChange="0.01"
|
||||
LargeChange="0.05"
|
||||
Width="150" />
|
||||
<Slider
|
||||
Width="150"
|
||||
Margin="0"
|
||||
Padding="0"
|
||||
IsSnapToTickEnabled="True"
|
||||
LargeChange="0.05"
|
||||
Maximum="1"
|
||||
Minimum="0"
|
||||
SmallChange="0.01"
|
||||
TickFrequency="0.05"
|
||||
ToolTip.Tip="{locale:Locale AudioVolumeTooltip}"
|
||||
Value="{Binding Volume}" />
|
||||
</Grid>
|
||||
</Flyout>
|
||||
</ui:ToggleSplitButton.Flyout>
|
||||
</ui:ToggleSplitButton>
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding GameStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Margin="2,0"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding FifoStatusText}"
|
||||
TextAlignment="Left" />
|
||||
<Border
|
||||
Width="2"
|
||||
Margin="2,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
BorderThickness="1"
|
||||
Height="12"
|
||||
BorderBrush="Gray" />
|
||||
Margin="2,0"
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
IsVisible="{Binding !ShowLoadProgress}" />
|
||||
<TextBlock
|
||||
Margin="5,0,5,0"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding !ShowLoadProgress}"
|
||||
Text="{Binding GpuStatusText}"
|
||||
TextAlignment="Left" />
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center" IsVisible="{Binding ShowFirmwareStatus}" Grid.Column="3"
|
||||
Orientation="Horizontal" Margin="10, 0">
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
Margin="10,0"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding ShowFirmwareStatus}"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Name="FirmwareStatus"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Margin="0"
|
||||
Text="{locale:Locale StatusBarSystemVersion}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
@ -1,18 +1,26 @@
|
||||
<window:StyleableWindow xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="350"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
Width="500" MinHeight="500" Height="500"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
MinWidth="500"
|
||||
Title="Ryujinx Updater">
|
||||
<Grid Margin="20" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
|
||||
<window:StyleableWindow
|
||||
x:Class="Ryujinx.Ava.Ui.Windows.UpdaterWindow"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.Ui.Windows"
|
||||
Title="Ryujinx Updater"
|
||||
Width="500"
|
||||
Height="500"
|
||||
MinWidth="500"
|
||||
MinHeight="500"
|
||||
d:DesignHeight="350"
|
||||
d:DesignWidth="400"
|
||||
CanResize="False"
|
||||
SizeToContent="Height"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
mc:Ignorable="d">
|
||||
<Grid
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
@ -20,17 +28,38 @@
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Grid.Row="1" HorizontalAlignment="Stretch" TextAlignment="Center" Height="20" Name="MainText" />
|
||||
<TextBlock Height="20" HorizontalAlignment="Stretch" TextAlignment="Center" Name="SecondaryText" Grid.Row="2" />
|
||||
<ProgressBar IsVisible="False" HorizontalAlignment="Stretch" Name="ProgressBar" Maximum="100" Minimum="0"
|
||||
Margin="20" Grid.Row="3" />
|
||||
<StackPanel IsVisible="False" Name="ButtonBox" Orientation="Horizontal" Spacing="20" Grid.Row="4"
|
||||
HorizontalAlignment="Right">
|
||||
<Button Command="{Binding YesPressed}" MinWidth="50">
|
||||
<TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogYes}" />
|
||||
<TextBlock
|
||||
Name="MainText"
|
||||
Grid.Row="1"
|
||||
Height="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextAlignment="Center" />
|
||||
<TextBlock
|
||||
Name="SecondaryText"
|
||||
Grid.Row="2"
|
||||
Height="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
TextAlignment="Center" />
|
||||
<ProgressBar
|
||||
Name="ProgressBar"
|
||||
Grid.Row="3"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsVisible="False"
|
||||
Maximum="100"
|
||||
Minimum="0" />
|
||||
<StackPanel
|
||||
Name="ButtonBox"
|
||||
Grid.Row="4"
|
||||
HorizontalAlignment="Right"
|
||||
IsVisible="False"
|
||||
Orientation="Horizontal"
|
||||
Spacing="20">
|
||||
<Button MinWidth="50" Command="{Binding YesPressed}">
|
||||
<TextBlock Text="{locale:Locale InputDialogYes}" TextAlignment="Center" />
|
||||
</Button>
|
||||
<Button Command="{Binding NoPressed}" MinWidth="50">
|
||||
<TextBlock TextAlignment="Center" Text="{locale:Locale InputDialogNo}" />
|
||||
<Button MinWidth="50" Command="{Binding NoPressed}">
|
||||
<TextBlock Text="{locale:Locale InputDialogNo}" TextAlignment="Center" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
12
Ryujinx.Common/Memory/Box.cs
Normal file
12
Ryujinx.Common/Memory/Box.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Common.Memory
|
||||
{
|
||||
public class Box<T> where T : unmanaged
|
||||
{
|
||||
public T Data;
|
||||
|
||||
public Box()
|
||||
{
|
||||
Data = new T();
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,17 @@ namespace Ryujinx.Common
|
||||
|
||||
public static long AlignUp(long value, int size)
|
||||
{
|
||||
return (value + (size - 1)) & -(long)size;
|
||||
return AlignUp(value, (long)size);
|
||||
}
|
||||
|
||||
public static ulong AlignUp(ulong value, ulong size)
|
||||
{
|
||||
return (ulong)AlignUp((long)value, (long)size);
|
||||
}
|
||||
|
||||
public static long AlignUp(long value, long size)
|
||||
{
|
||||
return (value + (size - 1)) & -size;
|
||||
}
|
||||
|
||||
public static uint AlignDown(uint value, int size)
|
||||
@ -42,7 +52,17 @@ namespace Ryujinx.Common
|
||||
|
||||
public static long AlignDown(long value, int size)
|
||||
{
|
||||
return value & -(long)size;
|
||||
return AlignDown(value, (long)size);
|
||||
}
|
||||
|
||||
public static ulong AlignDown(ulong value, ulong size)
|
||||
{
|
||||
return (ulong)AlignDown((long)value, (long)size);
|
||||
}
|
||||
|
||||
public static long AlignDown(long value, long size)
|
||||
{
|
||||
return value & -size;
|
||||
}
|
||||
|
||||
public static int DivRoundUp(int value, int dividend)
|
||||
|
@ -101,6 +101,16 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// <returns>The GPU resource with the given ID</returns>
|
||||
public abstract T1 Get(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given ID is valid and inside the range of the pool.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the descriptor. This is effectively a zero-based index</param>
|
||||
/// <returns>True if the specified ID is valid, false otherwise</returns>
|
||||
public bool IsValidId(int id)
|
||||
{
|
||||
return (uint)id <= MaximumId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Synchronizes host memory with guest memory.
|
||||
/// This causes invalidation of pool entries,
|
||||
|
@ -35,7 +35,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
private readonly TextureBindingInfo[][] _textureBindings;
|
||||
private readonly TextureBindingInfo[][] _imageBindings;
|
||||
|
||||
private struct TextureStatePerStage
|
||||
private struct TextureState
|
||||
{
|
||||
public ITexture Texture;
|
||||
public ISampler Sampler;
|
||||
@ -45,10 +45,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
public int InvalidatedSequence;
|
||||
public Texture CachedTexture;
|
||||
public Sampler CachedSampler;
|
||||
public int ScaleIndex;
|
||||
public TextureUsageFlags UsageFlags;
|
||||
}
|
||||
|
||||
private TextureStatePerStage[] _textureState;
|
||||
private TextureStatePerStage[] _imageState;
|
||||
private TextureState[] _textureState;
|
||||
private TextureState[] _imageState;
|
||||
|
||||
private int[] _textureBindingsCount;
|
||||
private int[] _imageBindingsCount;
|
||||
@ -83,8 +85,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
_textureBindings = new TextureBindingInfo[stages][];
|
||||
_imageBindings = new TextureBindingInfo[stages][];
|
||||
|
||||
_textureState = new TextureStatePerStage[InitialTextureStateSize];
|
||||
_imageState = new TextureStatePerStage[InitialImageStateSize];
|
||||
_textureState = new TextureState[InitialTextureStateSize];
|
||||
_imageState = new TextureState[InitialImageStateSize];
|
||||
|
||||
_textureBindingsCount = new int[stages];
|
||||
_imageBindingsCount = new int[stages];
|
||||
@ -230,18 +232,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
/// Updates the texture scale for a given texture or image.
|
||||
/// </summary>
|
||||
/// <param name="texture">Start GPU virtual address of the pool</param>
|
||||
/// <param name="binding">The related texture binding</param>
|
||||
/// <param name="usageFlags">The related texture usage flags</param>
|
||||
/// <param name="index">The texture/image binding index</param>
|
||||
/// <param name="stage">The active shader stage</param>
|
||||
/// <returns>True if the given texture has become blacklisted, indicating that its host texture may have changed.</returns>
|
||||
private bool UpdateScale(Texture texture, TextureBindingInfo binding, int index, ShaderStage stage)
|
||||
private bool UpdateScale(Texture texture, TextureUsageFlags usageFlags, int index, ShaderStage stage)
|
||||
{
|
||||
float result = 1f;
|
||||
bool changed = false;
|
||||
|
||||
if ((binding.Flags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
|
||||
if ((usageFlags & TextureUsageFlags.NeedsScaleValue) != 0 && texture != null)
|
||||
{
|
||||
if ((binding.Flags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
||||
if ((usageFlags & TextureUsageFlags.ResScaleUnsupported) != 0)
|
||||
{
|
||||
changed = texture.ScaleMode != TextureScaleMode.Blacklisted;
|
||||
texture.BlacklistScale();
|
||||
@ -469,6 +471,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
for (int index = 0; index < textureCount; index++)
|
||||
{
|
||||
TextureBindingInfo bindingInfo = _textureBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
||||
@ -487,7 +490,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
samplerId = TextureHandle.UnpackSamplerId(packedId);
|
||||
}
|
||||
|
||||
ref TextureStatePerStage state = ref _textureState[bindingInfo.Binding];
|
||||
ref TextureState state = ref _textureState[bindingInfo.Binding];
|
||||
|
||||
if (!poolModified &&
|
||||
state.TextureHandle == textureId &&
|
||||
@ -499,6 +502,18 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
// The texture is already bound.
|
||||
state.CachedTexture.SynchronizeMemory();
|
||||
|
||||
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, index, stage))
|
||||
{
|
||||
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
state.Texture = hostTextureRebind;
|
||||
state.ScaleIndex = index;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTextureRebind);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -522,12 +537,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
{
|
||||
if (state.Texture != hostTexture)
|
||||
{
|
||||
if (UpdateScale(texture, bindingInfo, index, stage))
|
||||
if (UpdateScale(texture, usageFlags, index, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
state.Texture = hostTexture;
|
||||
state.ScaleIndex = index;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
_context.Renderer.Pipeline.SetTexture(bindingInfo.Binding, hostTexture);
|
||||
}
|
||||
@ -589,6 +606,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
for (int index = 0; index < imageCount; index++)
|
||||
{
|
||||
TextureBindingInfo bindingInfo = _imageBindings[stageIndex][index];
|
||||
TextureUsageFlags usageFlags = bindingInfo.Flags;
|
||||
int scaleIndex = baseScaleIndex + index;
|
||||
|
||||
(int textureBufferIndex, int samplerBufferIndex) = TextureHandle.UnpackSlots(bindingInfo.CbufSlot, _textureBufferIndex);
|
||||
|
||||
@ -597,7 +616,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
int packedId = TextureHandle.ReadPackedId(bindingInfo.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
ref TextureStatePerStage state = ref _imageState[bindingInfo.Binding];
|
||||
ref TextureState state = ref _imageState[bindingInfo.Binding];
|
||||
|
||||
bool isStore = bindingInfo.Flags.HasFlag(TextureUsageFlags.ImageStore);
|
||||
|
||||
@ -606,12 +625,28 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
state.CachedTexture != null &&
|
||||
state.CachedTexture.InvalidatedSequence == state.InvalidatedSequence)
|
||||
{
|
||||
Texture cachedTexture = state.CachedTexture;
|
||||
|
||||
// The texture is already bound.
|
||||
state.CachedTexture.SynchronizeMemory();
|
||||
cachedTexture.SynchronizeMemory();
|
||||
|
||||
if (isStore)
|
||||
{
|
||||
state.CachedTexture?.SignalModified();
|
||||
cachedTexture?.SignalModified();
|
||||
}
|
||||
|
||||
if ((state.ScaleIndex != index || state.UsageFlags != usageFlags) &&
|
||||
UpdateScale(state.CachedTexture, usageFlags, scaleIndex, stage))
|
||||
{
|
||||
ITexture hostTextureRebind = state.CachedTexture.GetTargetTexture(bindingInfo.Target);
|
||||
|
||||
Format format = bindingInfo.Format == 0 ? cachedTexture.Format : bindingInfo.Format;
|
||||
|
||||
state.Texture = hostTextureRebind;
|
||||
state.ScaleIndex = scaleIndex;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
_context.Renderer.Pipeline.SetImage(bindingInfo.Binding, hostTextureRebind, format);
|
||||
}
|
||||
|
||||
continue;
|
||||
@ -649,12 +684,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
if (state.Texture != hostTexture)
|
||||
{
|
||||
if (UpdateScale(texture, bindingInfo, baseScaleIndex + index, stage))
|
||||
if (UpdateScale(texture, usageFlags, scaleIndex, stage))
|
||||
{
|
||||
hostTexture = texture?.GetTargetTexture(bindingInfo.Target);
|
||||
}
|
||||
|
||||
state.Texture = hostTexture;
|
||||
state.ScaleIndex = scaleIndex;
|
||||
state.UsageFlags = usageFlags;
|
||||
|
||||
Format format = bindingInfo.Format;
|
||||
|
||||
@ -701,7 +738,22 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
|
||||
TexturePool texturePool = _texturePoolCache.FindOrCreate(_channel, poolAddress, maximumId);
|
||||
|
||||
return texturePool.GetDescriptor(textureId);
|
||||
TextureDescriptor descriptor;
|
||||
|
||||
if (texturePool.IsValidId(textureId))
|
||||
{
|
||||
descriptor = texturePool.GetDescriptor(textureId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the ID is not valid, we just return a default descriptor with the most common state.
|
||||
// Since this is used for shader specialization, doing so might avoid the need for recompilations.
|
||||
descriptor = new TextureDescriptor();
|
||||
descriptor.Word4 |= (uint)TextureTarget.Texture2D << 23;
|
||||
descriptor.Word5 |= 1u << 31; // Coords normalized.
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -241,25 +241,6 @@ namespace Ryujinx.Graphics.Gpu.Image
|
||||
return (TextureMsaaMode)((Word7 >> 8) & 0xf);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the equivalent of this TextureDescriptor for the shader cache.
|
||||
/// </summary>
|
||||
/// <returns>The equivalent of this TextureDescriptor for the shader cache.</returns>
|
||||
public GuestTextureDescriptor ToCache()
|
||||
{
|
||||
GuestTextureDescriptor result = new GuestTextureDescriptor
|
||||
{
|
||||
Handle = uint.MaxValue,
|
||||
Format = UnpackFormat(),
|
||||
Target = UnpackTextureTarget(),
|
||||
IsSrgb = UnpackSrgb(),
|
||||
IsTextureCoordNormalized = UnpackTextureCoordNormalized(),
|
||||
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if two descriptors are equal.
|
||||
/// </summary>
|
||||
|
@ -579,14 +579,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
textureKey.StageIndex);
|
||||
|
||||
int packedId = TextureHandle.ReadPackedId(textureKey.Handle, cachedTextureBuffer, cachedSamplerBuffer);
|
||||
|
||||
int textureId = TextureHandle.UnpackTextureId(packedId);
|
||||
|
||||
ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId);
|
||||
|
||||
if (!MatchesTexture(kv.Value, descriptor))
|
||||
if (pool.IsValidId(textureId))
|
||||
{
|
||||
return false;
|
||||
ref readonly Image.TextureDescriptor descriptor = ref pool.GetDescriptorRef(textureId);
|
||||
|
||||
if (!MatchesTexture(kv.Value, descriptor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,87 +0,0 @@
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KMemoryRegionBlock
|
||||
{
|
||||
public long[][] Masks;
|
||||
|
||||
public ulong FreeCount;
|
||||
public int MaxLevel;
|
||||
public ulong StartAligned;
|
||||
public ulong SizeInBlocksTruncated;
|
||||
public ulong SizeInBlocksRounded;
|
||||
public int Order;
|
||||
public int NextOrder;
|
||||
|
||||
public bool TryCoalesce(int index, int count)
|
||||
{
|
||||
long mask = ((1L << count) - 1) << (index & 63);
|
||||
|
||||
index /= 64;
|
||||
|
||||
if (count >= 64)
|
||||
{
|
||||
int remaining = count;
|
||||
int tempIdx = index;
|
||||
|
||||
do
|
||||
{
|
||||
if (Masks[MaxLevel - 1][tempIdx++] != -1L)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
remaining -= 64;
|
||||
}
|
||||
while (remaining != 0);
|
||||
|
||||
remaining = count;
|
||||
tempIdx = index;
|
||||
|
||||
do
|
||||
{
|
||||
Masks[MaxLevel - 1][tempIdx] = 0;
|
||||
|
||||
ClearMaskBit(MaxLevel - 2, tempIdx++);
|
||||
|
||||
remaining -= 64;
|
||||
}
|
||||
while (remaining != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
long value = Masks[MaxLevel - 1][index];
|
||||
|
||||
if ((mask & ~value) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value &= ~mask;
|
||||
|
||||
Masks[MaxLevel - 1][index] = value;
|
||||
|
||||
if (value == 0)
|
||||
{
|
||||
ClearMaskBit(MaxLevel - 2, index);
|
||||
}
|
||||
}
|
||||
|
||||
FreeCount -= (ulong)count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClearMaskBit(int startLevel, int index)
|
||||
{
|
||||
for (int level = startLevel; level >= 0; level--, index /= 64)
|
||||
{
|
||||
Masks[level][index / 64] &= ~(1L << (index & 63));
|
||||
|
||||
if (Masks[level][index / 64] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,102 +1,42 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.HLE.HOS.Kernel.Common;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KMemoryRegionManager
|
||||
{
|
||||
private static readonly int[] BlockOrders = new int[] { 12, 16, 21, 22, 25, 29, 30 };
|
||||
private readonly KPageHeap _pageHeap;
|
||||
|
||||
public ulong Address { get; private set; }
|
||||
public ulong EndAddr { get; private set; }
|
||||
public ulong Size { get; private set; }
|
||||
|
||||
private int _blockOrdersCount;
|
||||
|
||||
private readonly KMemoryRegionBlock[] _blocks;
|
||||
public ulong Address { get; }
|
||||
public ulong Size { get; }
|
||||
public ulong EndAddr => Address + Size;
|
||||
|
||||
private readonly ushort[] _pageReferenceCounts;
|
||||
|
||||
public KMemoryRegionManager(ulong address, ulong size, ulong endAddr)
|
||||
{
|
||||
_blocks = new KMemoryRegionBlock[BlockOrders.Length];
|
||||
|
||||
Address = address;
|
||||
Size = size;
|
||||
EndAddr = endAddr;
|
||||
|
||||
_blockOrdersCount = BlockOrders.Length;
|
||||
|
||||
for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++)
|
||||
{
|
||||
_blocks[blockIndex] = new KMemoryRegionBlock();
|
||||
|
||||
_blocks[blockIndex].Order = BlockOrders[blockIndex];
|
||||
|
||||
int nextOrder = blockIndex == _blockOrdersCount - 1 ? 0 : BlockOrders[blockIndex + 1];
|
||||
|
||||
_blocks[blockIndex].NextOrder = nextOrder;
|
||||
|
||||
int currBlockSize = 1 << BlockOrders[blockIndex];
|
||||
int nextBlockSize = currBlockSize;
|
||||
|
||||
if (nextOrder != 0)
|
||||
{
|
||||
nextBlockSize = 1 << nextOrder;
|
||||
}
|
||||
|
||||
ulong startAligned = BitUtils.AlignDown(address, nextBlockSize);
|
||||
ulong endAddrAligned = BitUtils.AlignDown(endAddr, currBlockSize);
|
||||
|
||||
ulong sizeInBlocksTruncated = (endAddrAligned - startAligned) >> BlockOrders[blockIndex];
|
||||
|
||||
ulong endAddrRounded = BitUtils.AlignUp(address + size, nextBlockSize);
|
||||
|
||||
ulong sizeInBlocksRounded = (endAddrRounded - startAligned) >> BlockOrders[blockIndex];
|
||||
|
||||
_blocks[blockIndex].StartAligned = startAligned;
|
||||
_blocks[blockIndex].SizeInBlocksTruncated = sizeInBlocksTruncated;
|
||||
_blocks[blockIndex].SizeInBlocksRounded = sizeInBlocksRounded;
|
||||
|
||||
ulong currSizeInBlocks = sizeInBlocksRounded;
|
||||
|
||||
int maxLevel = 0;
|
||||
|
||||
do
|
||||
{
|
||||
maxLevel++;
|
||||
}
|
||||
while ((currSizeInBlocks /= 64) != 0);
|
||||
|
||||
_blocks[blockIndex].MaxLevel = maxLevel;
|
||||
|
||||
_blocks[blockIndex].Masks = new long[maxLevel][];
|
||||
|
||||
currSizeInBlocks = sizeInBlocksRounded;
|
||||
|
||||
for (int level = maxLevel - 1; level >= 0; level--)
|
||||
{
|
||||
currSizeInBlocks = (currSizeInBlocks + 63) / 64;
|
||||
|
||||
_blocks[blockIndex].Masks[level] = new long[currSizeInBlocks];
|
||||
}
|
||||
}
|
||||
Size = size;
|
||||
|
||||
_pageReferenceCounts = new ushort[size / KPageTableBase.PageSize];
|
||||
|
||||
if (size != 0)
|
||||
{
|
||||
FreePages(address, size / KPageTableBase.PageSize);
|
||||
}
|
||||
_pageHeap = new KPageHeap(address, size);
|
||||
_pageHeap.Free(address, size / KPageTableBase.PageSize);
|
||||
_pageHeap.UpdateUsedSize();
|
||||
}
|
||||
|
||||
public KernelResult AllocatePages(ulong pagesCount, bool backwards, out KPageList pageList)
|
||||
public KernelResult AllocatePages(out KPageList pageList, ulong pagesCount)
|
||||
{
|
||||
lock (_blocks)
|
||||
if (pagesCount == 0)
|
||||
{
|
||||
KernelResult result = AllocatePagesImpl(pagesCount, backwards, out pageList);
|
||||
pageList = new KPageList();
|
||||
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
lock (_pageHeap)
|
||||
{
|
||||
KernelResult result = AllocatePagesImpl(out pageList, pagesCount, false);
|
||||
|
||||
if (result == KernelResult.Success)
|
||||
{
|
||||
@ -112,9 +52,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
public ulong AllocatePagesContiguous(KernelContext context, ulong pagesCount, bool backwards)
|
||||
{
|
||||
lock (_blocks)
|
||||
if (pagesCount == 0)
|
||||
{
|
||||
ulong address = AllocatePagesContiguousImpl(pagesCount, backwards);
|
||||
return 0;
|
||||
}
|
||||
|
||||
lock (_pageHeap)
|
||||
{
|
||||
ulong address = AllocatePagesContiguousImpl(pagesCount, 1, backwards);
|
||||
|
||||
if (address != 0)
|
||||
{
|
||||
@ -126,373 +71,110 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
}
|
||||
}
|
||||
|
||||
private KernelResult AllocatePagesImpl(ulong pagesCount, bool backwards, out KPageList pageList)
|
||||
private KernelResult AllocatePagesImpl(out KPageList pageList, ulong pagesCount, bool random)
|
||||
{
|
||||
pageList = new KPageList();
|
||||
|
||||
if (_blockOrdersCount > 0)
|
||||
{
|
||||
if (GetFreePagesImpl() < pagesCount)
|
||||
{
|
||||
return KernelResult.OutOfMemory;
|
||||
}
|
||||
}
|
||||
else if (pagesCount != 0)
|
||||
int heapIndex = KPageHeap.GetBlockIndex(pagesCount);
|
||||
|
||||
if (heapIndex < 0)
|
||||
{
|
||||
return KernelResult.OutOfMemory;
|
||||
}
|
||||
|
||||
for (int blockIndex = _blockOrdersCount - 1; blockIndex >= 0; blockIndex--)
|
||||
for (int index = heapIndex; index >= 0; index--)
|
||||
{
|
||||
KMemoryRegionBlock block = _blocks[blockIndex];
|
||||
ulong pagesPerAlloc = KPageHeap.GetBlockPagesCount(index);
|
||||
|
||||
ulong bestFitBlockSize = 1UL << block.Order;
|
||||
|
||||
ulong blockPagesCount = bestFitBlockSize / KPageTableBase.PageSize;
|
||||
|
||||
// Check if this is the best fit for this page size.
|
||||
// If so, try allocating as much requested pages as possible.
|
||||
while (blockPagesCount <= pagesCount)
|
||||
while (pagesCount >= pagesPerAlloc)
|
||||
{
|
||||
ulong address = AllocatePagesForOrder(blockIndex, backwards, bestFitBlockSize);
|
||||
ulong allocatedBlock = _pageHeap.AllocateBlock(index, random);
|
||||
|
||||
// The address being zero means that no free space was found on that order,
|
||||
// just give up and try with the next one.
|
||||
if (address == 0)
|
||||
if (allocatedBlock == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Add new allocated page(s) to the pages list.
|
||||
// If an error occurs, then free all allocated pages and fail.
|
||||
KernelResult result = pageList.AddRange(address, blockPagesCount);
|
||||
KernelResult result = pageList.AddRange(allocatedBlock, pagesPerAlloc);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
FreePages(address, blockPagesCount);
|
||||
|
||||
foreach (KPageNode pageNode in pageList)
|
||||
{
|
||||
FreePages(pageNode.Address, pageNode.PagesCount);
|
||||
}
|
||||
FreePages(pageList);
|
||||
_pageHeap.Free(allocatedBlock, pagesPerAlloc);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pagesCount -= blockPagesCount;
|
||||
pagesCount -= pagesPerAlloc;
|
||||
}
|
||||
}
|
||||
|
||||
// Success case, all requested pages were allocated successfully.
|
||||
if (pagesCount == 0)
|
||||
if (pagesCount != 0)
|
||||
{
|
||||
return KernelResult.Success;
|
||||
FreePages(pageList);
|
||||
|
||||
return KernelResult.OutOfMemory;
|
||||
}
|
||||
|
||||
// Error case, free allocated pages and return out of memory.
|
||||
foreach (KPageNode pageNode in pageList)
|
||||
{
|
||||
FreePages(pageNode.Address, pageNode.PagesCount);
|
||||
}
|
||||
|
||||
pageList = null;
|
||||
|
||||
return KernelResult.OutOfMemory;
|
||||
return KernelResult.Success;
|
||||
}
|
||||
|
||||
private ulong AllocatePagesContiguousImpl(ulong pagesCount, bool backwards)
|
||||
private ulong AllocatePagesContiguousImpl(ulong pagesCount, ulong alignPages, bool random)
|
||||
{
|
||||
if (pagesCount == 0 || _blocks.Length < 1)
|
||||
int heapIndex = KPageHeap.GetAlignedBlockIndex(pagesCount, alignPages);
|
||||
|
||||
ulong allocatedBlock = _pageHeap.AllocateBlock(heapIndex, random);
|
||||
|
||||
if (allocatedBlock == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blockIndex = 0;
|
||||
ulong allocatedPages = KPageHeap.GetBlockPagesCount(heapIndex);
|
||||
|
||||
while ((1UL << _blocks[blockIndex].Order) / KPageTableBase.PageSize < pagesCount)
|
||||
if (allocatedPages > pagesCount)
|
||||
{
|
||||
if (++blockIndex >= _blocks.Length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
_pageHeap.Free(allocatedBlock + pagesCount * KPageTableBase.PageSize, allocatedPages - pagesCount);
|
||||
}
|
||||
|
||||
ulong tightestFitBlockSize = 1UL << _blocks[blockIndex].Order;
|
||||
|
||||
ulong address = AllocatePagesForOrder(blockIndex, backwards, tightestFitBlockSize);
|
||||
|
||||
ulong requiredSize = pagesCount * KPageTableBase.PageSize;
|
||||
|
||||
if (address != 0 && tightestFitBlockSize > requiredSize)
|
||||
{
|
||||
FreePages(address + requiredSize, (tightestFitBlockSize - requiredSize) / KPageTableBase.PageSize);
|
||||
}
|
||||
|
||||
return address;
|
||||
return allocatedBlock;
|
||||
}
|
||||
|
||||
private ulong AllocatePagesForOrder(int blockIndex, bool backwards, ulong bestFitBlockSize)
|
||||
public void FreePage(ulong address)
|
||||
{
|
||||
ulong address = 0;
|
||||
|
||||
KMemoryRegionBlock block = null;
|
||||
|
||||
for (int currBlockIndex = blockIndex;
|
||||
currBlockIndex < _blockOrdersCount && address == 0;
|
||||
currBlockIndex++)
|
||||
lock (_pageHeap)
|
||||
{
|
||||
block = _blocks[currBlockIndex];
|
||||
|
||||
int index = 0;
|
||||
|
||||
bool zeroMask = false;
|
||||
|
||||
for (int level = 0; level < block.MaxLevel; level++)
|
||||
{
|
||||
long mask = block.Masks[level][index];
|
||||
|
||||
if (mask == 0)
|
||||
{
|
||||
zeroMask = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (backwards)
|
||||
{
|
||||
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
|
||||
}
|
||||
}
|
||||
|
||||
if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
block.FreeCount--;
|
||||
|
||||
int tempIdx = index;
|
||||
|
||||
for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
|
||||
{
|
||||
block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
|
||||
|
||||
if (block.Masks[level][tempIdx / 64] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
address = block.StartAligned + ((ulong)index << block.Order);
|
||||
_pageHeap.Free(address, 1);
|
||||
}
|
||||
|
||||
for (int currBlockIndex = blockIndex;
|
||||
currBlockIndex < _blockOrdersCount && address == 0;
|
||||
currBlockIndex++)
|
||||
{
|
||||
block = _blocks[currBlockIndex];
|
||||
|
||||
int index = 0;
|
||||
|
||||
bool zeroMask = false;
|
||||
|
||||
for (int level = 0; level < block.MaxLevel; level++)
|
||||
{
|
||||
long mask = block.Masks[level][index];
|
||||
|
||||
if (mask == 0)
|
||||
{
|
||||
zeroMask = true;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (backwards)
|
||||
{
|
||||
index = index * 64 + BitOperations.LeadingZeroCount((ulong)BitUtils.ReverseBits64(mask));
|
||||
}
|
||||
else
|
||||
{
|
||||
index = (index * 64 + 63) - BitOperations.LeadingZeroCount((ulong)mask);
|
||||
}
|
||||
}
|
||||
|
||||
if (block.SizeInBlocksTruncated <= (ulong)index || zeroMask)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
block.FreeCount--;
|
||||
|
||||
int tempIdx = index;
|
||||
|
||||
for (int level = block.MaxLevel - 1; level >= 0; level--, tempIdx /= 64)
|
||||
{
|
||||
block.Masks[level][tempIdx / 64] &= ~(1L << (tempIdx & 63));
|
||||
|
||||
if (block.Masks[level][tempIdx / 64] != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
address = block.StartAligned + ((ulong)index << block.Order);
|
||||
}
|
||||
|
||||
if (address != 0)
|
||||
{
|
||||
// If we are using a larger order than best fit, then we should
|
||||
// split it into smaller blocks.
|
||||
ulong firstFreeBlockSize = 1UL << block.Order;
|
||||
|
||||
if (firstFreeBlockSize > bestFitBlockSize)
|
||||
{
|
||||
FreePages(address + bestFitBlockSize, (firstFreeBlockSize - bestFitBlockSize) / KPageTableBase.PageSize);
|
||||
}
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
private void FreePages(ulong address, ulong pagesCount)
|
||||
public void FreePages(KPageList pageList)
|
||||
{
|
||||
lock (_blocks)
|
||||
lock (_pageHeap)
|
||||
{
|
||||
ulong endAddr = address + pagesCount * KPageTableBase.PageSize;
|
||||
|
||||
int blockIndex = _blockOrdersCount - 1;
|
||||
|
||||
ulong addressRounded = 0;
|
||||
ulong endAddrTruncated = 0;
|
||||
|
||||
for (; blockIndex >= 0; blockIndex--)
|
||||
foreach (KPageNode pageNode in pageList)
|
||||
{
|
||||
KMemoryRegionBlock allocInfo = _blocks[blockIndex];
|
||||
|
||||
int blockSize = 1 << allocInfo.Order;
|
||||
|
||||
addressRounded = BitUtils.AlignUp (address, blockSize);
|
||||
endAddrTruncated = BitUtils.AlignDown(endAddr, blockSize);
|
||||
|
||||
if (addressRounded < endAddrTruncated)
|
||||
{
|
||||
break;
|
||||
}
|
||||
_pageHeap.Free(pageNode.Address, pageNode.PagesCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FreeRegion(ulong currAddress)
|
||||
{
|
||||
for (int currBlockIndex = blockIndex;
|
||||
currBlockIndex < _blockOrdersCount && currAddress != 0;
|
||||
currBlockIndex++)
|
||||
{
|
||||
KMemoryRegionBlock block = _blocks[currBlockIndex];
|
||||
|
||||
block.FreeCount++;
|
||||
|
||||
ulong freedBlocks = (currAddress - block.StartAligned) >> block.Order;
|
||||
|
||||
int index = (int)freedBlocks;
|
||||
|
||||
for (int level = block.MaxLevel - 1; level >= 0; level--, index /= 64)
|
||||
{
|
||||
long mask = block.Masks[level][index / 64];
|
||||
|
||||
block.Masks[level][index / 64] = mask | (1L << (index & 63));
|
||||
|
||||
if (mask != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int blockSizeDelta = 1 << (block.NextOrder - block.Order);
|
||||
|
||||
int freedBlocksTruncated = BitUtils.AlignDown((int)freedBlocks, blockSizeDelta);
|
||||
|
||||
if (!block.TryCoalesce(freedBlocksTruncated, blockSizeDelta))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
currAddress = block.StartAligned + ((ulong)freedBlocksTruncated << block.Order);
|
||||
}
|
||||
}
|
||||
|
||||
// Free inside aligned region.
|
||||
ulong baseAddress = addressRounded;
|
||||
|
||||
while (baseAddress < endAddrTruncated)
|
||||
{
|
||||
ulong blockSize = 1UL << _blocks[blockIndex].Order;
|
||||
|
||||
FreeRegion(baseAddress);
|
||||
|
||||
baseAddress += blockSize;
|
||||
}
|
||||
|
||||
int nextBlockIndex = blockIndex - 1;
|
||||
|
||||
// Free region between Address and aligned region start.
|
||||
baseAddress = addressRounded;
|
||||
|
||||
for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--)
|
||||
{
|
||||
ulong blockSize = 1UL << _blocks[blockIndex].Order;
|
||||
|
||||
while (baseAddress - blockSize >= address)
|
||||
{
|
||||
baseAddress -= blockSize;
|
||||
|
||||
FreeRegion(baseAddress);
|
||||
}
|
||||
}
|
||||
|
||||
// Free region between aligned region end and End Address.
|
||||
baseAddress = endAddrTruncated;
|
||||
|
||||
for (blockIndex = nextBlockIndex; blockIndex >= 0; blockIndex--)
|
||||
{
|
||||
ulong blockSize = 1UL << _blocks[blockIndex].Order;
|
||||
|
||||
while (baseAddress + blockSize <= endAddr)
|
||||
{
|
||||
FreeRegion(baseAddress);
|
||||
|
||||
baseAddress += blockSize;
|
||||
}
|
||||
}
|
||||
public void FreePages(ulong address, ulong pagesCount)
|
||||
{
|
||||
lock (_pageHeap)
|
||||
{
|
||||
_pageHeap.Free(address, pagesCount);
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetFreePages()
|
||||
{
|
||||
lock (_blocks)
|
||||
lock (_pageHeap)
|
||||
{
|
||||
return GetFreePagesImpl();
|
||||
return _pageHeap.GetFreePagesCount();
|
||||
}
|
||||
}
|
||||
|
||||
private ulong GetFreePagesImpl()
|
||||
{
|
||||
ulong availablePages = 0;
|
||||
|
||||
for (int blockIndex = 0; blockIndex < _blockOrdersCount; blockIndex++)
|
||||
{
|
||||
KMemoryRegionBlock block = _blocks[blockIndex];
|
||||
|
||||
ulong blockPagesCount = (1UL << block.Order) / KPageTableBase.PageSize;
|
||||
|
||||
availablePages += blockPagesCount * block.FreeCount;
|
||||
}
|
||||
|
||||
return availablePages;
|
||||
}
|
||||
|
||||
public void IncrementPagesReferenceCount(ulong address, ulong pagesCount)
|
||||
{
|
||||
ulong index = GetPageOffset(address);
|
||||
|
298
Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs
Normal file
298
Ryujinx.HLE/HOS/Kernel/Memory/KPageBitmap.cs
Normal file
@ -0,0 +1,298 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KPageBitmap
|
||||
{
|
||||
private struct RandomNumberGenerator
|
||||
{
|
||||
private uint _entropy;
|
||||
private uint _bitsAvailable;
|
||||
|
||||
private void RefreshEntropy()
|
||||
{
|
||||
_entropy = 0;
|
||||
_bitsAvailable = sizeof(uint) * 8;
|
||||
}
|
||||
|
||||
private bool GenerateRandomBit()
|
||||
{
|
||||
if (_bitsAvailable == 0)
|
||||
{
|
||||
RefreshEntropy();
|
||||
}
|
||||
|
||||
bool bit = (_entropy & 1) != 0;
|
||||
|
||||
_entropy >>= 1;
|
||||
_bitsAvailable--;
|
||||
|
||||
return bit;
|
||||
}
|
||||
|
||||
public int SelectRandomBit(ulong bitmap)
|
||||
{
|
||||
int selected = 0;
|
||||
|
||||
int bitsCount = UInt64BitSize / 2;
|
||||
ulong mask = (1UL << bitsCount) - 1;
|
||||
|
||||
while (bitsCount != 0)
|
||||
{
|
||||
ulong low = bitmap & mask;
|
||||
ulong high = (bitmap >> bitsCount) & mask;
|
||||
|
||||
bool chooseLow;
|
||||
|
||||
if (high == 0)
|
||||
{
|
||||
chooseLow = true;
|
||||
}
|
||||
else if (low == 0)
|
||||
{
|
||||
chooseLow = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
chooseLow = GenerateRandomBit();
|
||||
}
|
||||
|
||||
if (chooseLow)
|
||||
{
|
||||
bitmap = low;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitmap = high;
|
||||
selected += bitsCount;
|
||||
}
|
||||
|
||||
bitsCount /= 2;
|
||||
mask >>= bitsCount;
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
}
|
||||
|
||||
private const int UInt64BitSize = sizeof(ulong) * 8;
|
||||
private const int MaxDepth = 4;
|
||||
|
||||
private readonly RandomNumberGenerator _rng;
|
||||
private readonly ArraySegment<ulong>[] _bitStorages;
|
||||
private int _usedDepths;
|
||||
|
||||
public int BitsCount { get; private set; }
|
||||
|
||||
public int HighestDepthIndex => _usedDepths - 1;
|
||||
|
||||
public KPageBitmap()
|
||||
{
|
||||
_rng = new RandomNumberGenerator();
|
||||
_bitStorages = new ArraySegment<ulong>[MaxDepth];
|
||||
}
|
||||
|
||||
public ArraySegment<ulong> Initialize(ArraySegment<ulong> storage, ulong size)
|
||||
{
|
||||
_usedDepths = GetRequiredDepth(size);
|
||||
|
||||
for (int depth = HighestDepthIndex; depth >= 0; depth--)
|
||||
{
|
||||
_bitStorages[depth] = storage;
|
||||
size = BitUtils.DivRoundUp(size, UInt64BitSize);
|
||||
storage = storage.Slice((int)size);
|
||||
}
|
||||
|
||||
return storage;
|
||||
}
|
||||
|
||||
public ulong FindFreeBlock(bool random)
|
||||
{
|
||||
ulong offset = 0;
|
||||
int depth = 0;
|
||||
|
||||
if (random)
|
||||
{
|
||||
do
|
||||
{
|
||||
ulong v = _bitStorages[depth][(int)offset];
|
||||
|
||||
if (v == 0)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
offset = offset * UInt64BitSize + (ulong)_rng.SelectRandomBit(v);
|
||||
}
|
||||
while (++depth < _usedDepths);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
{
|
||||
ulong v = _bitStorages[depth][(int)offset];
|
||||
|
||||
if (v == 0)
|
||||
{
|
||||
return ulong.MaxValue;
|
||||
}
|
||||
|
||||
offset = offset * UInt64BitSize + (ulong)BitOperations.TrailingZeroCount(v);
|
||||
}
|
||||
while (++depth < _usedDepths);
|
||||
}
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void SetBit(ulong offset)
|
||||
{
|
||||
SetBit(HighestDepthIndex, offset);
|
||||
BitsCount++;
|
||||
}
|
||||
|
||||
public void ClearBit(ulong offset)
|
||||
{
|
||||
ClearBit(HighestDepthIndex, offset);
|
||||
BitsCount--;
|
||||
}
|
||||
|
||||
public bool ClearRange(ulong offset, int count)
|
||||
{
|
||||
int depth = HighestDepthIndex;
|
||||
var bits = _bitStorages[depth];
|
||||
|
||||
int bitInd = (int)(offset / UInt64BitSize);
|
||||
|
||||
if (count < UInt64BitSize)
|
||||
{
|
||||
int shift = (int)(offset % UInt64BitSize);
|
||||
|
||||
ulong mask = ((1UL << count) - 1) << shift;
|
||||
|
||||
ulong v = bits[bitInd];
|
||||
|
||||
if ((v & mask) != mask)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
v &= ~mask;
|
||||
bits[bitInd] = v;
|
||||
|
||||
if (v == 0)
|
||||
{
|
||||
ClearBit(depth - 1, (ulong)bitInd);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int remaining = count;
|
||||
int i = 0;
|
||||
|
||||
do
|
||||
{
|
||||
if (bits[bitInd + i++] != ulong.MaxValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
remaining -= UInt64BitSize;
|
||||
}
|
||||
while (remaining > 0);
|
||||
|
||||
remaining = count;
|
||||
i = 0;
|
||||
|
||||
do
|
||||
{
|
||||
bits[bitInd + i] = 0;
|
||||
ClearBit(depth - 1, (ulong)(bitInd + i));
|
||||
i++;
|
||||
remaining -= UInt64BitSize;
|
||||
}
|
||||
while (remaining > 0);
|
||||
}
|
||||
|
||||
BitsCount -= count;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetBit(int depth, ulong offset)
|
||||
{
|
||||
while (depth >= 0)
|
||||
{
|
||||
int ind = (int)(offset / UInt64BitSize);
|
||||
int which = (int)(offset % UInt64BitSize);
|
||||
|
||||
ulong mask = 1UL << which;
|
||||
|
||||
ulong v = _bitStorages[depth][ind];
|
||||
|
||||
_bitStorages[depth][ind] = v | mask;
|
||||
|
||||
if (v != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
offset = (ulong)ind;
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearBit(int depth, ulong offset)
|
||||
{
|
||||
while (depth >= 0)
|
||||
{
|
||||
int ind = (int)(offset / UInt64BitSize);
|
||||
int which = (int)(offset % UInt64BitSize);
|
||||
|
||||
ulong mask = 1UL << which;
|
||||
|
||||
ulong v = _bitStorages[depth][ind];
|
||||
|
||||
v &= ~mask;
|
||||
|
||||
_bitStorages[depth][ind] = v;
|
||||
|
||||
if (v != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
offset = (ulong)ind;
|
||||
depth--;
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetRequiredDepth(ulong regionSize)
|
||||
{
|
||||
int depth = 0;
|
||||
|
||||
do
|
||||
{
|
||||
regionSize /= UInt64BitSize;
|
||||
depth++;
|
||||
}
|
||||
while (regionSize != 0);
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
public static int CalculateManagementOverheadSize(ulong regionSize)
|
||||
{
|
||||
int overheadBits = 0;
|
||||
|
||||
for (int depth = GetRequiredDepth(regionSize) - 1; depth >= 0; depth--)
|
||||
{
|
||||
regionSize = BitUtils.DivRoundUp(regionSize, UInt64BitSize);
|
||||
overheadBits += (int)regionSize;
|
||||
}
|
||||
|
||||
return overheadBits * sizeof(ulong);
|
||||
}
|
||||
}
|
||||
}
|
283
Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs
Normal file
283
Ryujinx.HLE/HOS/Kernel/Memory/KPageHeap.cs
Normal file
@ -0,0 +1,283 @@
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
class KPageHeap
|
||||
{
|
||||
private class Block
|
||||
{
|
||||
private KPageBitmap _bitmap = new KPageBitmap();
|
||||
private ulong _heapAddress;
|
||||
private ulong _endOffset;
|
||||
|
||||
public int Shift { get; private set; }
|
||||
public int NextShift { get; private set; }
|
||||
public ulong Size => 1UL << Shift;
|
||||
public int PagesCount => (int)(Size / KPageTableBase.PageSize);
|
||||
public int FreeBlocksCount => _bitmap.BitsCount;
|
||||
public int FreePagesCount => FreeBlocksCount * PagesCount;
|
||||
|
||||
public ArraySegment<ulong> Initialize(ulong address, ulong size, int blockShift, int nextBlockShift, ArraySegment<ulong> bitStorage)
|
||||
{
|
||||
Shift = blockShift;
|
||||
NextShift = nextBlockShift;
|
||||
|
||||
ulong endAddress = address + size;
|
||||
|
||||
ulong align = nextBlockShift != 0
|
||||
? 1UL << nextBlockShift
|
||||
: 1UL << blockShift;
|
||||
|
||||
address = BitUtils.AlignDown(address, align);
|
||||
endAddress = BitUtils.AlignUp (endAddress, align);
|
||||
|
||||
_heapAddress = address;
|
||||
_endOffset = (endAddress - address) / (1UL << blockShift);
|
||||
|
||||
return _bitmap.Initialize(bitStorage, _endOffset);
|
||||
}
|
||||
|
||||
public ulong PushBlock(ulong address)
|
||||
{
|
||||
ulong offset = (address - _heapAddress) >> Shift;
|
||||
|
||||
_bitmap.SetBit(offset);
|
||||
|
||||
if (NextShift != 0)
|
||||
{
|
||||
int diff = 1 << (NextShift - Shift);
|
||||
|
||||
offset = BitUtils.AlignDown(offset, diff);
|
||||
|
||||
if (_bitmap.ClearRange(offset, diff))
|
||||
{
|
||||
return _heapAddress + (offset << Shift);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public ulong PopBlock(bool random)
|
||||
{
|
||||
long sOffset = (long)_bitmap.FindFreeBlock(random);
|
||||
|
||||
if (sOffset < 0L)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
ulong offset = (ulong)sOffset;
|
||||
|
||||
_bitmap.ClearBit(offset);
|
||||
|
||||
return _heapAddress + (offset << Shift);
|
||||
}
|
||||
|
||||
public static int CalculateManagementOverheadSize(ulong regionSize, int currBlockShift, int nextBlockShift)
|
||||
{
|
||||
ulong currBlockSize = 1UL << currBlockShift;
|
||||
ulong nextBlockSize = 1UL << nextBlockShift;
|
||||
ulong align = nextBlockShift != 0 ? nextBlockSize : currBlockSize;
|
||||
return KPageBitmap.CalculateManagementOverheadSize((align * 2 + BitUtils.AlignUp(regionSize, align)) / currBlockSize);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly int[] _memoryBlockPageShifts = new int[] { 12, 16, 21, 22, 25, 29, 30 };
|
||||
|
||||
private readonly ulong _heapAddress;
|
||||
private readonly ulong _heapSize;
|
||||
private ulong _usedSize;
|
||||
private readonly int _blocksCount;
|
||||
private readonly Block[] _blocks;
|
||||
|
||||
public KPageHeap(ulong address, ulong size) : this(address, size, _memoryBlockPageShifts)
|
||||
{
|
||||
}
|
||||
|
||||
public KPageHeap(ulong address, ulong size, int[] blockShifts)
|
||||
{
|
||||
_heapAddress = address;
|
||||
_heapSize = size;
|
||||
_blocksCount = blockShifts.Length;
|
||||
_blocks = new Block[_memoryBlockPageShifts.Length];
|
||||
|
||||
var currBitmapStorage = new ArraySegment<ulong>(new ulong[CalculateManagementOverheadSize(size, blockShifts)]);
|
||||
|
||||
for (int i = 0; i < blockShifts.Length; i++)
|
||||
{
|
||||
int currBlockShift = blockShifts[i];
|
||||
int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
|
||||
|
||||
_blocks[i] = new Block();
|
||||
|
||||
currBitmapStorage = _blocks[i].Initialize(address, size, currBlockShift, nextBlockShift, currBitmapStorage);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateUsedSize()
|
||||
{
|
||||
_usedSize = _heapSize - (GetFreePagesCount() * KPageTableBase.PageSize);
|
||||
}
|
||||
|
||||
public ulong GetFreePagesCount()
|
||||
{
|
||||
ulong freeCount = 0;
|
||||
|
||||
for (int i = 0; i < _blocksCount; i++)
|
||||
{
|
||||
freeCount += (ulong)_blocks[i].FreePagesCount;
|
||||
}
|
||||
|
||||
return freeCount;
|
||||
}
|
||||
|
||||
public ulong AllocateBlock(int index, bool random)
|
||||
{
|
||||
ulong neededSize = _blocks[index].Size;
|
||||
|
||||
for (int i = index; i < _blocksCount; i++)
|
||||
{
|
||||
ulong address = _blocks[i].PopBlock(random);
|
||||
|
||||
if (address != 0)
|
||||
{
|
||||
ulong allocatedSize = _blocks[i].Size;
|
||||
|
||||
if (allocatedSize > neededSize)
|
||||
{
|
||||
Free(address + neededSize, (allocatedSize - neededSize) / KPageTableBase.PageSize);
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void FreeBlock(ulong block, int index)
|
||||
{
|
||||
do
|
||||
{
|
||||
block = _blocks[index++].PushBlock(block);
|
||||
}
|
||||
while (block != 0);
|
||||
}
|
||||
|
||||
public void Free(ulong address, ulong pagesCount)
|
||||
{
|
||||
if (pagesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int bigIndex = _blocksCount - 1;
|
||||
|
||||
ulong start = address;
|
||||
ulong end = address + pagesCount * KPageTableBase.PageSize;
|
||||
ulong beforeStart = start;
|
||||
ulong beforeEnd = start;
|
||||
ulong afterStart = end;
|
||||
ulong afterEnd = end;
|
||||
|
||||
while (bigIndex >= 0)
|
||||
{
|
||||
ulong blockSize = _blocks[bigIndex].Size;
|
||||
|
||||
ulong bigStart = BitUtils.AlignUp (start, blockSize);
|
||||
ulong bigEnd = BitUtils.AlignDown(end, blockSize);
|
||||
|
||||
if (bigStart < bigEnd)
|
||||
{
|
||||
for (ulong block = bigStart; block < bigEnd; block += blockSize)
|
||||
{
|
||||
FreeBlock(block, bigIndex);
|
||||
}
|
||||
|
||||
beforeEnd = bigStart;
|
||||
afterStart = bigEnd;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
bigIndex--;
|
||||
}
|
||||
|
||||
for (int i = bigIndex - 1; i >= 0; i--)
|
||||
{
|
||||
ulong blockSize = _blocks[i].Size;
|
||||
|
||||
while (beforeStart + blockSize <= beforeEnd)
|
||||
{
|
||||
beforeEnd -= blockSize;
|
||||
FreeBlock(beforeEnd, i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = bigIndex - 1; i >= 0; i--)
|
||||
{
|
||||
ulong blockSize = _blocks[i].Size;
|
||||
|
||||
while (afterStart + blockSize <= afterEnd)
|
||||
{
|
||||
FreeBlock(afterStart, i);
|
||||
afterStart += blockSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int GetAlignedBlockIndex(ulong pagesCount, ulong alignPages)
|
||||
{
|
||||
ulong targetPages = Math.Max(pagesCount, alignPages);
|
||||
|
||||
for (int i = 0; i < _memoryBlockPageShifts.Length; i++)
|
||||
{
|
||||
if (targetPages <= GetBlockPagesCount(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static int GetBlockIndex(ulong pagesCount)
|
||||
{
|
||||
for (int i = _memoryBlockPageShifts.Length - 1; i >= 0; i--)
|
||||
{
|
||||
if (pagesCount >= GetBlockPagesCount(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static ulong GetBlockSize(int index)
|
||||
{
|
||||
return 1UL << _memoryBlockPageShifts[index];
|
||||
}
|
||||
|
||||
public static ulong GetBlockPagesCount(int index)
|
||||
{
|
||||
return GetBlockSize(index) / KPageTableBase.PageSize;
|
||||
}
|
||||
|
||||
private static int CalculateManagementOverheadSize(ulong regionSize, int[] blockShifts)
|
||||
{
|
||||
int overheadSize = 0;
|
||||
|
||||
for (int i = 0; i < blockShifts.Length; i++)
|
||||
{
|
||||
int currBlockShift = blockShifts[i];
|
||||
int nextBlockShift = i != blockShifts.Length - 1 ? blockShifts[i + 1] : 0;
|
||||
overheadSize += Block.CalculateManagementOverheadSize(regionSize, currBlockShift, nextBlockShift);
|
||||
}
|
||||
|
||||
return BitUtils.AlignUp(overheadSize, KPageTableBase.PageSize);
|
||||
}
|
||||
}
|
||||
}
|
@ -555,7 +555,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
{
|
||||
KMemoryRegionManager region = GetMemoryRegionManager();
|
||||
|
||||
KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList);
|
||||
KernelResult result = region.AllocatePages(out KPageList pageList, pagesCount);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
@ -712,7 +712,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
KMemoryRegionManager region = GetMemoryRegionManager();
|
||||
|
||||
KernelResult result = region.AllocatePages(pagesCount, _aslrDisabled, out KPageList pageList);
|
||||
KernelResult result = region.AllocatePages(out KPageList pageList, pagesCount);
|
||||
|
||||
using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
|
||||
|
||||
@ -1276,7 +1276,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
|
||||
|
||||
KMemoryRegionManager region = GetMemoryRegionManager();
|
||||
|
||||
KernelResult result = region.AllocatePages(remainingPages, _aslrDisabled, out KPageList pageList);
|
||||
KernelResult result = region.AllocatePages(out KPageList pageList, remainingPages);
|
||||
|
||||
using var _ = new OnScopeExit(() => pageList.DecrementPagesReferenceCount(Context.MemoryManager));
|
||||
|
||||
|
@ -966,6 +966,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
SignalExitToDebugExited();
|
||||
SignalExit();
|
||||
}
|
||||
|
||||
KernelStatic.GetCurrentThread().Exit();
|
||||
}
|
||||
|
||||
private void UnpauseAndTerminateAllThreadsExcept(KThread currentThread)
|
||||
@ -981,7 +983,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
|
||||
|
||||
foreach (KThread thread in _threads)
|
||||
{
|
||||
if ((thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
|
||||
if (thread != currentThread && (thread.SchedFlags & ThreadSchedState.LowMask) != ThreadSchedState.TerminationPending)
|
||||
{
|
||||
thread.PrepareForTermination();
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
KMemoryRegionManager region = context.MemoryManager.MemoryRegions[(int)memoryRegion];
|
||||
|
||||
KernelResult result = region.AllocatePages((ulong)codePagesCount, false, out KPageList pageList);
|
||||
KernelResult result = region.AllocatePages(out KPageList pageList, (ulong)codePagesCount);
|
||||
|
||||
if (result != KernelResult.Success)
|
||||
{
|
||||
|
@ -2,7 +2,10 @@ using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
||||
@ -100,15 +103,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
||||
|
||||
string locationName = StringUtils.ReadInlinedAsciiString(context.RequestData, 0x24);
|
||||
|
||||
ResultCode resultCode = _timeZoneContentManager.LoadTimeZoneRule(out TimeZoneRule rules, locationName);
|
||||
|
||||
// Write TimeZoneRule if success
|
||||
if (resultCode == ResultCode.Success)
|
||||
using (WritableRegion region = context.Memory.GetWritableRegion(bufferPosition, Unsafe.SizeOf<TimeZoneRule>()))
|
||||
{
|
||||
MemoryHelper.Write(context.Memory, bufferPosition, rules);
|
||||
}
|
||||
ref TimeZoneRule rules = ref MemoryMarshal.Cast<byte, TimeZoneRule>(region.Memory.Span)[0];
|
||||
|
||||
return resultCode;
|
||||
return _timeZoneContentManager.LoadTimeZoneRule(ref rules, locationName);
|
||||
}
|
||||
}
|
||||
|
||||
[CommandHipc(100)]
|
||||
|
@ -4,9 +4,12 @@ using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
||||
{
|
||||
@ -165,11 +168,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
||||
|
||||
using (MemoryStream timeZoneBinaryStream = new MemoryStream(temp))
|
||||
{
|
||||
result = _timeZoneManager.ParseTimeZoneRuleBinary(out TimeZoneRule timeZoneRule, timeZoneBinaryStream);
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
using (WritableRegion region = context.Memory.GetWritableRegion(timeZoneRuleBufferPosition, Unsafe.SizeOf<TimeZoneRule>()))
|
||||
{
|
||||
MemoryHelper.Write(context.Memory, timeZoneRuleBufferPosition, timeZoneRule);
|
||||
ref TimeZoneRule rule = ref MemoryMarshal.Cast<byte, TimeZoneRule>(region.Memory.Span)[0];
|
||||
|
||||
result = _timeZoneManager.ParseTimeZoneRuleBinary(ref rule, timeZoneBinaryStream);
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,9 +202,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, bufferPosition);
|
||||
ReadOnlySpan<TimeZoneRule> rules = MemoryMarshal.Cast<byte, TimeZoneRule>(context.Memory.GetSpan(bufferPosition, (int)bufferSize));
|
||||
|
||||
ResultCode resultCode = _timeZoneManager.ToCalendarTime(rules, posixTime, out CalendarInfo calendar);
|
||||
ResultCode resultCode = _timeZoneManager.ToCalendarTime(in rules[0], posixTime, out CalendarInfo calendar);
|
||||
|
||||
if (resultCode == 0)
|
||||
{
|
||||
@ -244,9 +247,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.StaticService
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
TimeZoneRule rules = MemoryHelper.Read<TimeZoneRule>(context.Memory, inBufferPosition);
|
||||
ReadOnlySpan<TimeZoneRule> rules = MemoryMarshal.Cast<byte, TimeZoneRule>(context.Memory.GetSpan(inBufferPosition, (int)inBufferSize));
|
||||
|
||||
ResultCode resultCode = _timeZoneManager.ToPosixTime(rules, calendarTime, out long posixTime);
|
||||
ResultCode resultCode = _timeZoneManager.ToPosixTime(in rules[0], calendarTime, out long posixTime);
|
||||
|
||||
if (resultCode == ResultCode.Success)
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
@ -38,7 +39,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
new int[] { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
|
||||
};
|
||||
|
||||
private const string TimeZoneDefaultRule = ",M4.1.0,M10.5.0";
|
||||
private static readonly byte[] TimeZoneDefaultRule = Encoding.ASCII.GetBytes(",M4.1.0,M10.5.0");
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 0x4, Size = 0x10)]
|
||||
private struct CalendarTimeInternal
|
||||
@ -133,7 +134,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return (t1 - t0) == SecondsPerRepeat;
|
||||
}
|
||||
|
||||
private static bool TimeTypeEquals(TimeZoneRule outRules, byte aIndex, byte bIndex)
|
||||
private static bool TimeTypeEquals(in TimeZoneRule outRules, byte aIndex, byte bIndex)
|
||||
{
|
||||
if (aIndex < 0 || aIndex >= outRules.TypeCount || bIndex < 0 || bIndex >= outRules.TypeCount)
|
||||
{
|
||||
@ -150,7 +151,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
StringUtils.CompareCStr(outRules.Chars[a.AbbreviationListIndex..], outRules.Chars[b.AbbreviationListIndex..]) == 0;
|
||||
}
|
||||
|
||||
private static int GetQZName(ReadOnlySpan<char> name, int namePosition, char delimiter)
|
||||
private static int GetQZName(ReadOnlySpan<byte> name, int namePosition, char delimiter)
|
||||
{
|
||||
int i = namePosition;
|
||||
|
||||
@ -162,13 +163,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return i;
|
||||
}
|
||||
|
||||
private static int GetTZName(char[] name, int namePosition)
|
||||
private static int GetTZName(ReadOnlySpan<byte> name, int namePosition)
|
||||
{
|
||||
int i = namePosition;
|
||||
|
||||
char c;
|
||||
|
||||
while ((c = name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+')
|
||||
while ((c = (char)name[i]) != '\0' && !char.IsDigit(c) && c != ',' && c != '-' && c != '+')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
@ -176,7 +177,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return i;
|
||||
}
|
||||
|
||||
private static bool GetNum(char[] name, ref int namePosition, out int num, int min, int max)
|
||||
private static bool GetNum(ReadOnlySpan<byte> name, ref int namePosition, out int num, int min, int max)
|
||||
{
|
||||
num = 0;
|
||||
|
||||
@ -185,7 +186,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return false;
|
||||
}
|
||||
|
||||
char c = name[namePosition];
|
||||
char c = (char)name[namePosition];
|
||||
|
||||
if (!char.IsDigit(c))
|
||||
{
|
||||
@ -205,7 +206,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return false;
|
||||
}
|
||||
|
||||
c = name[namePosition];
|
||||
c = (char)name[namePosition];
|
||||
}
|
||||
while (char.IsDigit(c));
|
||||
|
||||
@ -217,7 +218,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool GetSeconds(char[] name, ref int namePosition, out int seconds)
|
||||
private static bool GetSeconds(ReadOnlySpan<byte> name, ref int namePosition, out int seconds)
|
||||
{
|
||||
seconds = 0;
|
||||
|
||||
@ -266,7 +267,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool GetOffset(char[] name, ref int namePosition, ref int offset)
|
||||
private static bool GetOffset(ReadOnlySpan<byte> name, ref int namePosition, ref int offset)
|
||||
{
|
||||
bool isNegative = false;
|
||||
|
||||
@ -304,7 +305,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool GetRule(char[] name, ref int namePosition, out Rule rule)
|
||||
private static bool GetRule(ReadOnlySpan<byte> name, ref int namePosition, out Rule rule)
|
||||
{
|
||||
rule = new Rule();
|
||||
|
||||
@ -347,7 +348,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
|
||||
isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerWekk - 1);
|
||||
}
|
||||
else if (char.IsDigit(name[namePosition]))
|
||||
else if (char.IsDigit((char)name[namePosition]))
|
||||
{
|
||||
rule.Type = RuleType.DayOfYear;
|
||||
isValid = GetNum(name, ref namePosition, out rule.Day, 0, DaysPerLYear - 1);
|
||||
@ -385,19 +386,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static bool ParsePosixName(ReadOnlySpan<char> name, out TimeZoneRule outRules, bool lastDitch)
|
||||
private static bool ParsePosixName(ReadOnlySpan<byte> name, ref TimeZoneRule outRules, bool lastDitch)
|
||||
{
|
||||
outRules = new TimeZoneRule
|
||||
{
|
||||
Ats = new long[TzMaxTimes],
|
||||
Types = new byte[TzMaxTimes],
|
||||
Ttis = new TimeTypeInfo[TzMaxTypes],
|
||||
Chars = new char[TzCharsArraySize]
|
||||
};
|
||||
outRules = new TimeZoneRule();
|
||||
|
||||
int stdLen;
|
||||
|
||||
ReadOnlySpan<char> stdName = name;
|
||||
ReadOnlySpan<byte> stdName = name;
|
||||
int namePosition = 0;
|
||||
int stdOffset = 0;
|
||||
|
||||
@ -428,7 +423,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
}
|
||||
else
|
||||
{
|
||||
namePosition = GetTZName(name.ToArray(), namePosition);
|
||||
namePosition = GetTZName(name, namePosition);
|
||||
stdLen = namePosition;
|
||||
}
|
||||
|
||||
@ -449,7 +444,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
int destLen = 0;
|
||||
int dstOffset = 0;
|
||||
|
||||
ReadOnlySpan<char> destName = name.Slice(namePosition);
|
||||
ReadOnlySpan<byte> destName = name.Slice(namePosition);
|
||||
|
||||
if (TzCharsArraySize < charCount)
|
||||
{
|
||||
@ -476,7 +471,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
else
|
||||
{
|
||||
destName = name.Slice(namePosition);
|
||||
namePosition = GetTZName(name.ToArray(), namePosition);
|
||||
namePosition = GetTZName(name, namePosition);
|
||||
destLen = namePosition;
|
||||
}
|
||||
|
||||
@ -507,7 +502,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
|
||||
if (name[namePosition] == '\0')
|
||||
{
|
||||
name = TimeZoneDefaultRule.ToCharArray();
|
||||
name = TimeZoneDefaultRule;
|
||||
namePosition = 0;
|
||||
}
|
||||
|
||||
@ -515,7 +510,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
namePosition++;
|
||||
|
||||
bool IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule start);
|
||||
bool IsRuleValid = GetRule(name, ref namePosition, out Rule start);
|
||||
if (!IsRuleValid)
|
||||
{
|
||||
return false;
|
||||
@ -526,7 +521,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return false;
|
||||
}
|
||||
|
||||
IsRuleValid = GetRule(name.ToArray(), ref namePosition, out Rule end);
|
||||
IsRuleValid = GetRule(name, ref namePosition, out Rule end);
|
||||
if (!IsRuleValid)
|
||||
{
|
||||
return false;
|
||||
@ -738,7 +733,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
}
|
||||
|
||||
charsPosition += stdLen;
|
||||
outRules.Chars[charsPosition++] = '\0';
|
||||
outRules.Chars[charsPosition++] = 0;
|
||||
|
||||
if (destLen != 0)
|
||||
{
|
||||
@ -746,7 +741,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
outRules.Chars[charsPosition + i] = destName[i];
|
||||
}
|
||||
outRules.Chars[charsPosition + destLen] = '\0';
|
||||
outRules.Chars[charsPosition + destLen] = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -882,20 +877,14 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool ParsePosixName(string name, out TimeZoneRule outRules)
|
||||
internal static bool ParsePosixName(string name, ref TimeZoneRule outRules)
|
||||
{
|
||||
return ParsePosixName(name.ToCharArray(), out outRules, false);
|
||||
return ParsePosixName(Encoding.ASCII.GetBytes(name), ref outRules, false);
|
||||
}
|
||||
|
||||
internal static bool ParseTimeZoneBinary(out TimeZoneRule outRules, Stream inputData)
|
||||
internal static bool ParseTimeZoneBinary(ref TimeZoneRule outRules, Stream inputData)
|
||||
{
|
||||
outRules = new TimeZoneRule
|
||||
{
|
||||
Ats = new long[TzMaxTimes],
|
||||
Types = new byte[TzMaxTimes],
|
||||
Ttis = new TimeTypeInfo[TzMaxTypes],
|
||||
Chars = new char[TzCharsArraySize]
|
||||
};
|
||||
outRules = new TimeZoneRule();
|
||||
|
||||
BinaryReader reader = new BinaryReader(inputData);
|
||||
|
||||
@ -1020,10 +1009,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
outRules.Ttis[i] = ttis;
|
||||
}
|
||||
|
||||
Encoding.ASCII.GetChars(p[..outRules.CharCount].ToArray()).CopyTo(outRules.Chars.AsSpan());
|
||||
p[..outRules.CharCount].CopyTo(outRules.Chars);
|
||||
|
||||
p = p[outRules.CharCount..];
|
||||
outRules.Chars[outRules.CharCount] = '\0';
|
||||
outRules.Chars[outRules.CharCount] = 0;
|
||||
|
||||
for (int i = 0; i < outRules.TypeCount; i++)
|
||||
{
|
||||
@ -1077,27 +1066,30 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
char[] tempName = new char[TzNameMax + 1];
|
||||
byte[] tempName = new byte[TzNameMax + 1];
|
||||
Array.Copy(workBuffer, position, tempName, 0, nRead);
|
||||
|
||||
if (nRead > 2 && tempName[0] == '\n' && tempName[nRead - 1] == '\n' && outRules.TypeCount + 2 <= TzMaxTypes)
|
||||
{
|
||||
tempName[nRead - 1] = '\0';
|
||||
tempName[nRead - 1] = 0;
|
||||
|
||||
char[] name = new char[TzNameMax];
|
||||
byte[] name = new byte[TzNameMax];
|
||||
Array.Copy(tempName, 1, name, 0, nRead - 1);
|
||||
|
||||
if (ParsePosixName(name, out TimeZoneRule tempRules, false))
|
||||
Box<TimeZoneRule> tempRulesBox = new Box<TimeZoneRule>();
|
||||
ref TimeZoneRule tempRules = ref tempRulesBox.Data;
|
||||
|
||||
if (ParsePosixName(name, ref tempRulesBox.Data, false))
|
||||
{
|
||||
int abbreviationCount = 0;
|
||||
charCount = outRules.CharCount;
|
||||
|
||||
Span<char> chars = outRules.Chars;
|
||||
Span<byte> chars = outRules.Chars;
|
||||
|
||||
for (int i = 0; i < tempRules.TypeCount; i++)
|
||||
{
|
||||
ReadOnlySpan<char> tempChars = tempRules.Chars;
|
||||
ReadOnlySpan<char> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
|
||||
ReadOnlySpan<byte> tempChars = tempRules.Chars;
|
||||
ReadOnlySpan<byte> tempAbbreviation = tempChars[tempRules.Ttis[i].AbbreviationListIndex..];
|
||||
|
||||
int j;
|
||||
|
||||
@ -1175,7 +1167,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
for (int i = 1; i < outRules.TimeCount; i++)
|
||||
{
|
||||
if (TimeTypeEquals(outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0]))
|
||||
if (TimeTypeEquals(in outRules, outRules.Types[i], outRules.Types[0]) && DifferByRepeat(outRules.Ats[i], outRules.Ats[0]))
|
||||
{
|
||||
outRules.GoBack = true;
|
||||
break;
|
||||
@ -1184,7 +1176,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
|
||||
for (int i = outRules.TimeCount - 2; i >= 0; i--)
|
||||
{
|
||||
if (TimeTypeEquals(outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i]))
|
||||
if (TimeTypeEquals(in outRules, outRules.Types[outRules.TimeCount - 1], outRules.Types[i]) && DifferByRepeat(outRules.Ats[outRules.TimeCount - 1], outRules.Ats[i]))
|
||||
{
|
||||
outRules.GoAhead = true;
|
||||
break;
|
||||
@ -1259,10 +1251,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
long remainingSeconds = time % SecondsPerDay;
|
||||
|
||||
calendarTime = new CalendarTimeInternal();
|
||||
calendarAdditionalInfo = new CalendarAdditionalInfo()
|
||||
{
|
||||
TimezoneName = new char[8]
|
||||
};
|
||||
calendarAdditionalInfo = new CalendarAdditionalInfo();
|
||||
|
||||
while (timeDays < 0 || timeDays >= YearLengths[IsLeap((int)year)])
|
||||
{
|
||||
@ -1353,13 +1342,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static ResultCode ToCalendarTimeInternal(TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
|
||||
private static ResultCode ToCalendarTimeInternal(in TimeZoneRule rules, long time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo)
|
||||
{
|
||||
calendarTime = new CalendarTimeInternal();
|
||||
calendarAdditionalInfo = new CalendarAdditionalInfo()
|
||||
{
|
||||
TimezoneName = new char[8]
|
||||
};
|
||||
calendarAdditionalInfo = new CalendarAdditionalInfo();
|
||||
|
||||
ResultCode result;
|
||||
|
||||
@ -1398,7 +1384,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return ResultCode.TimeNotFound;
|
||||
}
|
||||
|
||||
result = ToCalendarTimeInternal(rules, newTime, out calendarTime, out calendarAdditionalInfo);
|
||||
result = ToCalendarTimeInternal(in rules, newTime, out calendarTime, out calendarAdditionalInfo);
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
@ -1450,17 +1436,17 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
calendarAdditionalInfo.IsDaySavingTime = rules.Ttis[ttiIndex].IsDaySavingTime;
|
||||
|
||||
ReadOnlySpan<char> timeZoneAbbreviation = rules.Chars.AsSpan()[rules.Ttis[ttiIndex].AbbreviationListIndex..];
|
||||
ReadOnlySpan<byte> timeZoneAbbreviation = rules.Chars[rules.Ttis[ttiIndex].AbbreviationListIndex..];
|
||||
|
||||
int timeZoneSize = Math.Min(StringUtils.LengthCstr(timeZoneAbbreviation), 8);
|
||||
|
||||
timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.AsSpan());
|
||||
timeZoneAbbreviation[..timeZoneSize].CopyTo(calendarAdditionalInfo.TimezoneName.ToSpan());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static ResultCode ToPosixTimeInternal(TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime)
|
||||
private static ResultCode ToPosixTimeInternal(in TimeZoneRule rules, CalendarTimeInternal calendarTime, out long posixTime)
|
||||
{
|
||||
posixTime = 0;
|
||||
|
||||
@ -1604,7 +1590,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
|
||||
int direction;
|
||||
|
||||
ResultCode result = ToCalendarTimeInternal(rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _);
|
||||
ResultCode result = ToCalendarTimeInternal(in rules, pivot, out CalendarTimeInternal candidateCalendarTime, out _);
|
||||
if (result != 0)
|
||||
{
|
||||
if (pivot > 0)
|
||||
@ -1675,9 +1661,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return ResultCode.Success;
|
||||
}
|
||||
|
||||
internal static ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
|
||||
internal static ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar)
|
||||
{
|
||||
ResultCode result = ToCalendarTimeInternal(rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo);
|
||||
ResultCode result = ToCalendarTimeInternal(in rules, time, out CalendarTimeInternal calendarTime, out CalendarAdditionalInfo calendarAdditionalInfo);
|
||||
|
||||
calendar = new CalendarInfo()
|
||||
{
|
||||
@ -1697,7 +1683,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return result;
|
||||
}
|
||||
|
||||
internal static ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
|
||||
internal static ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
|
||||
{
|
||||
CalendarTimeInternal calendarTimeInternal = new CalendarTimeInternal()
|
||||
{
|
||||
@ -1710,7 +1696,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
Second = calendarTime.Second
|
||||
};
|
||||
|
||||
return ToPosixTimeInternal(rules, calendarTimeInternal, out posixTime);
|
||||
return ToPosixTimeInternal(in rules, calendarTimeInternal, out posixTime);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
|
||||
using TimeZoneRuleBox = Ryujinx.Common.Memory.Box<Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule>;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
@ -149,7 +149,11 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
continue;
|
||||
}
|
||||
|
||||
TimeZone.ParseTimeZoneBinary(out TimeZoneRule tzRule, tzif.Get.AsStream());
|
||||
TimeZoneRuleBox tzRuleBox = new TimeZoneRuleBox();
|
||||
ref TimeZoneRule tzRule = ref tzRuleBox.Data;
|
||||
|
||||
TimeZone.ParseTimeZoneBinary(ref tzRule, tzif.Get.AsStream());
|
||||
|
||||
|
||||
TimeTypeInfo ttInfo;
|
||||
if (tzRule.TimeCount > 0) // Find the current transition period
|
||||
@ -174,10 +178,10 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
continue;
|
||||
}
|
||||
|
||||
var abbrStart = tzRule.Chars.AsSpan(ttInfo.AbbreviationListIndex);
|
||||
int abbrEnd = abbrStart.IndexOf('\0');
|
||||
var abbrStart = tzRule.Chars[ttInfo.AbbreviationListIndex..];
|
||||
int abbrEnd = abbrStart.IndexOf((byte)0);
|
||||
|
||||
outList.Add((ttInfo.GmtOffset, locName, abbrStart.Slice(0, abbrEnd).ToString()));
|
||||
outList.Add((ttInfo.GmtOffset, locName, abbrStart[..abbrEnd].ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,15 +280,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return (ResultCode)result.Value;
|
||||
}
|
||||
|
||||
internal ResultCode LoadTimeZoneRule(out TimeZoneRule outRules, string locationName)
|
||||
internal ResultCode LoadTimeZoneRule(ref TimeZoneRule rules, string locationName)
|
||||
{
|
||||
outRules = new TimeZoneRule
|
||||
{
|
||||
Ats = new long[TzMaxTimes],
|
||||
Types = new byte[TzMaxTimes],
|
||||
Ttis = new TimeTypeInfo[TzMaxTypes],
|
||||
Chars = new char[TzCharsArraySize]
|
||||
};
|
||||
rules = default;
|
||||
|
||||
if (!HasTimeZoneBinaryTitle())
|
||||
{
|
||||
@ -295,7 +293,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
|
||||
if (result == ResultCode.Success)
|
||||
{
|
||||
result = Manager.ParseTimeZoneRuleBinary(out outRules, timeZoneBinaryStream);
|
||||
result = Manager.ParseTimeZoneRuleBinary(ref rules, timeZoneBinaryStream);
|
||||
|
||||
ncaFile.Dispose();
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System.IO;
|
||||
using static Ryujinx.HLE.HOS.Services.Time.TimeZone.TimeZoneRule;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
class TimeZoneManager
|
||||
{
|
||||
private bool _isInitialized;
|
||||
private TimeZoneRule _myRules;
|
||||
private Box<TimeZoneRule> _myRules;
|
||||
private string _deviceLocationName;
|
||||
private UInt128 _timeZoneRuleVersion;
|
||||
private uint _totalLocationNameCount;
|
||||
@ -21,15 +21,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
_deviceLocationName = "UTC";
|
||||
_timeZoneRuleVersion = new UInt128();
|
||||
_lock = new object();
|
||||
|
||||
// Empty rules
|
||||
_myRules = new TimeZoneRule
|
||||
{
|
||||
Ats = new long[TzMaxTimes],
|
||||
Types = new byte[TzMaxTimes],
|
||||
Ttis = new TimeTypeInfo[TzMaxTypes],
|
||||
Chars = new char[TzCharsArraySize]
|
||||
};
|
||||
_myRules = new Box<TimeZoneRule>();
|
||||
|
||||
_timeZoneUpdateTimePoint = SteadyClockTimePoint.GetRandom();
|
||||
}
|
||||
@ -78,7 +70,9 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out TimeZoneRule rules, timeZoneBinaryStream);
|
||||
Box<TimeZoneRule> rules = new Box<TimeZoneRule>();
|
||||
|
||||
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref rules.Data, timeZoneBinaryStream);
|
||||
|
||||
if (timeZoneConversionSuccess)
|
||||
{
|
||||
@ -154,13 +148,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return result;
|
||||
}
|
||||
|
||||
public ResultCode ParseTimeZoneRuleBinary(out TimeZoneRule outRules, Stream timeZoneBinaryStream)
|
||||
public ResultCode ParseTimeZoneRuleBinary(ref TimeZoneRule outRules, Stream timeZoneBinaryStream)
|
||||
{
|
||||
ResultCode result = ResultCode.Success;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(out outRules, timeZoneBinaryStream);
|
||||
bool timeZoneConversionSuccess = TimeZone.ParseTimeZoneBinary(ref outRules, timeZoneBinaryStream);
|
||||
|
||||
if (!timeZoneConversionSuccess)
|
||||
{
|
||||
@ -208,7 +202,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
result = ToCalendarTime(_myRules, time, out calendar);
|
||||
result = ToCalendarTime(in _myRules.Data, time, out calendar);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -220,13 +214,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return result;
|
||||
}
|
||||
|
||||
public ResultCode ToCalendarTime(TimeZoneRule rules, long time, out CalendarInfo calendar)
|
||||
public ResultCode ToCalendarTime(in TimeZoneRule rules, long time, out CalendarInfo calendar)
|
||||
{
|
||||
ResultCode result;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
result = TimeZone.ToCalendarTime(rules, time, out calendar);
|
||||
result = TimeZone.ToCalendarTime(in rules, time, out calendar);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -240,7 +234,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
result = ToPosixTime(_myRules, calendarTime, out posixTime);
|
||||
result = ToPosixTime(in _myRules.Data, calendarTime, out posixTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -252,13 +246,13 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
return result;
|
||||
}
|
||||
|
||||
public ResultCode ToPosixTime(TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
|
||||
public ResultCode ToPosixTime(in TimeZoneRule rules, CalendarTime calendarTime, out long posixTime)
|
||||
{
|
||||
ResultCode result;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
result = TimeZone.ToPosixTime(rules, calendarTime, out posixTime);
|
||||
result = TimeZone.ToPosixTime(in rules, calendarTime, out posixTime);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
@ -8,14 +9,12 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
public uint DayOfWeek;
|
||||
public uint DayOfYear;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
|
||||
public char[] TimezoneName;
|
||||
public Array8<byte> TimezoneName;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsDaySavingTime;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public char[] Padding;
|
||||
public Array3<byte> Padding;
|
||||
|
||||
public int GmtOffset;
|
||||
}
|
||||
|
@ -1,17 +1,19 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 4)]
|
||||
struct TimeTypeInfo
|
||||
[StructLayout(LayoutKind.Sequential, Size = Size, Pack = 4)]
|
||||
public struct TimeTypeInfo
|
||||
{
|
||||
public const int Size = 0x10;
|
||||
|
||||
public int GmtOffset;
|
||||
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsDaySavingTime;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
|
||||
public char[] Padding1;
|
||||
public Array3<byte> Padding1;
|
||||
|
||||
public int AbbreviationListIndex;
|
||||
|
||||
@ -21,7 +23,6 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool IsGMT;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
|
||||
public char[] Padding2;
|
||||
public ushort Padding2;
|
||||
}
|
||||
}
|
@ -1,16 +1,18 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 0x4000, CharSet = CharSet.Ansi)]
|
||||
struct TimeZoneRule
|
||||
public struct TimeZoneRule
|
||||
{
|
||||
public const int TzMaxTypes = 128;
|
||||
public const int TzMaxChars = 50;
|
||||
public const int TzMaxLeaps = 50;
|
||||
public const int TzMaxTimes = 1000;
|
||||
public const int TzNameMax = 255;
|
||||
public const int TzCharsArraySize = 2 * (TzNameMax + 1);
|
||||
public const int TzMaxTypes = 128;
|
||||
public const int TzMaxChars = 50;
|
||||
public const int TzMaxLeaps = 50;
|
||||
public const int TzMaxTimes = 1000;
|
||||
public const int TzNameMax = 255;
|
||||
public const int TzCharsArraySize = 2 * (TzNameMax + 1);
|
||||
|
||||
public int TimeCount;
|
||||
public int TypeCount;
|
||||
@ -22,17 +24,32 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
|
||||
[MarshalAs(UnmanagedType.I1)]
|
||||
public bool GoAhead;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)]
|
||||
public long[] Ats;
|
||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(long) * TzMaxTimes)]
|
||||
private struct AtsStorageStruct { }
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTimes)]
|
||||
public byte[] Types;
|
||||
private AtsStorageStruct _ats;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzMaxTypes)]
|
||||
public TimeTypeInfo[] Ttis;
|
||||
public Span<long> Ats => SpanHelpers.AsSpan<AtsStorageStruct, long>(ref _ats);
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = TzCharsArraySize)]
|
||||
public char[] Chars;
|
||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzMaxTimes)]
|
||||
private struct TypesStorageStruct { }
|
||||
|
||||
private TypesStorageStruct _types;
|
||||
|
||||
public Span<byte> Types => SpanHelpers.AsByteSpan(ref _types);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = TimeTypeInfo.Size * TzMaxTypes)]
|
||||
private struct TimeTypeInfoStorageStruct { }
|
||||
|
||||
private TimeTypeInfoStorageStruct _ttis;
|
||||
|
||||
public Span<TimeTypeInfo> Ttis => SpanHelpers.AsSpan<TimeTypeInfoStorageStruct, TimeTypeInfo>(ref _ttis);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(byte) * TzCharsArraySize)]
|
||||
private struct CharsStorageStruct { }
|
||||
|
||||
private CharsStorageStruct _chars;
|
||||
public Span<byte> Chars => SpanHelpers.AsByteSpan(ref _chars);
|
||||
|
||||
public int DefaultType;
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ namespace Ryujinx.HLE.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
public static int CompareCStr(ReadOnlySpan<char> s1, ReadOnlySpan<char> s2)
|
||||
public static int CompareCStr(ReadOnlySpan<byte> s1, ReadOnlySpan<byte> s2)
|
||||
{
|
||||
int s1Index = 0;
|
||||
int s2Index = 0;
|
||||
@ -142,11 +142,11 @@ namespace Ryujinx.HLE.Utilities
|
||||
return s2[s2Index] - s1[s1Index];
|
||||
}
|
||||
|
||||
public static int LengthCstr(ReadOnlySpan<char> s)
|
||||
public static int LengthCstr(ReadOnlySpan<byte> s)
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
while (s[i] != '\0')
|
||||
while (s[i] != 0)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
|
@ -79,6 +79,13 @@ namespace Ryujinx.Input.SDL2
|
||||
return;
|
||||
}
|
||||
|
||||
// Sometimes a JoyStick connected event fires after the app starts even though it was connected before
|
||||
// so it is rejected to avoid doubling the entries.
|
||||
if (_gamepadsIds.Contains(id))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gamepadsInstanceIdsMapping.TryAdd(joystickInstanceId, id))
|
||||
{
|
||||
_gamepadsIds.Add(id);
|
||||
|
@ -227,6 +227,8 @@ namespace Ryujinx.Memory.Tracking
|
||||
// Look up the virtual region using the region list.
|
||||
// Signal up the chain to relevant handles.
|
||||
|
||||
bool shouldThrow = false;
|
||||
|
||||
lock (TrackingLock)
|
||||
{
|
||||
ref var overlaps = ref ThreadStaticArray<VirtualRegion>.Get();
|
||||
@ -235,32 +237,41 @@ namespace Ryujinx.Memory.Tracking
|
||||
|
||||
if (count == 0 && !precise)
|
||||
{
|
||||
if (!_memoryManager.IsMapped(address))
|
||||
if (_memoryManager.IsMapped(address))
|
||||
{
|
||||
_invalidAccessHandler?.Invoke(address);
|
||||
|
||||
// We can't continue - it's impossible to remove protection from the page.
|
||||
// Even if the access handler wants us to continue, we wouldn't be able to.
|
||||
throw new InvalidMemoryRegionException();
|
||||
}
|
||||
|
||||
_memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
|
||||
return false; // We can't handle this - it's probably a real invalid access.
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
VirtualRegion region = overlaps[i];
|
||||
|
||||
if (precise)
|
||||
{
|
||||
region.SignalPrecise(address, size, write);
|
||||
_memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
|
||||
return false; // We can't handle this - it's probably a real invalid access.
|
||||
}
|
||||
else
|
||||
{
|
||||
region.Signal(address, size, write);
|
||||
shouldThrow = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
VirtualRegion region = overlaps[i];
|
||||
|
||||
if (precise)
|
||||
{
|
||||
region.SignalPrecise(address, size, write);
|
||||
}
|
||||
else
|
||||
{
|
||||
region.Signal(address, size, write);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldThrow)
|
||||
{
|
||||
_invalidAccessHandler?.Invoke(address);
|
||||
|
||||
// We can't continue - it's impossible to remove protection from the page.
|
||||
// Even if the access handler wants us to continue, we wouldn't be able to.
|
||||
throw new InvalidMemoryRegionException();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -24,6 +24,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.HLE\Ryujinx.HLE.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Tests.Unicorn\Ryujinx.Tests.Unicorn.csproj" />
|
||||
<ProjectReference Include="..\ARMeilleure\ARMeilleure.csproj" />
|
||||
|
18
Ryujinx.Tests/Time/TimeZoneRuleTests.cs
Normal file
18
Ryujinx.Tests/Time/TimeZoneRuleTests.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using NUnit.Framework;
|
||||
using Ryujinx.HLE.HOS.Services.Time.TimeZone;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ryujinx.Tests.Time
|
||||
{
|
||||
internal class TimeZoneRuleTests
|
||||
{
|
||||
class EffectInfoParameterTests
|
||||
{
|
||||
[Test]
|
||||
public void EnsureTypeSize()
|
||||
{
|
||||
Assert.AreEqual(0x4000, Unsafe.SizeOf<TimeZoneRule>());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user