Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1b28ecd63e | ||
|
895d9b53bc | ||
|
0e06aace45 | ||
|
adf4ebcd60 | ||
|
470a8031a4 | ||
|
5440d4ad5c | ||
|
dde208b480 | ||
|
4c3d2d5d75 |
@@ -590,6 +590,7 @@
|
||||
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
|
||||
"UpdateWindowTitle": "Title Update Manager",
|
||||
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
||||
"BuildId": "BuildId:",
|
||||
"DlcWindowHeading": "{0} Downloadable Content(s)",
|
||||
"UserProfilesEditProfile": "Edit Selected",
|
||||
"Cancel": "Cancel",
|
||||
@@ -644,5 +645,6 @@
|
||||
"UserEditorTitleCreate" : "Create User",
|
||||
"SettingsTabNetworkInterface": "Network Interface:",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
||||
"NetworkInterfaceDefault": "Default"
|
||||
"NetworkInterfaceDefault": "Default",
|
||||
"PackagingShaders": "Packaging Shaders"
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
@@ -118,7 +119,11 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
await new CheatWindow(viewModel.VirtualFileSystem, viewModel.SelectedApplication.TitleId, viewModel.SelectedApplication.TitleName).ShowDialog(viewModel.TopLevel as Window);
|
||||
await new CheatWindow(
|
||||
viewModel.VirtualFileSystem,
|
||||
viewModel.SelectedApplication.TitleId,
|
||||
viewModel.SelectedApplication.TitleName,
|
||||
viewModel.SelectedApplication.Path).ShowDialog(viewModel.TopLevel as Window);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1099,6 +1099,10 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingShaders];
|
||||
IsLoadingIndeterminate = false;
|
||||
break;
|
||||
case ShaderCacheLoadingState.Packaging:
|
||||
LoadHeading = LocaleManager.Instance[LocaleKeys.PackagingShaders];
|
||||
IsLoadingIndeterminate = false;
|
||||
break;
|
||||
case ShaderCacheLoadingState.Loaded:
|
||||
LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, TitleName);
|
||||
IsLoadingIndeterminate = true;
|
||||
|
@@ -238,8 +238,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public DateTimeOffset DateOffset { get; set; }
|
||||
public TimeSpan TimeOffset { get; set; }
|
||||
public DateTimeOffset CurrentDate { get; set; }
|
||||
public TimeSpan CurrentTime { get; set; }
|
||||
|
||||
internal AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||
public AvaloniaList<string> GameDirectories { get; set; }
|
||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||
@@ -397,10 +398,11 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
Language = (int)config.System.Language.Value;
|
||||
TimeZone = config.System.TimeZone;
|
||||
|
||||
DateTime dateTimeOffset = DateTime.Now.AddSeconds(config.System.SystemTimeOffset);
|
||||
DateTime currentDateTime = DateTime.Now;
|
||||
|
||||
CurrentDate = currentDateTime.Date;
|
||||
CurrentTime = currentDateTime.TimeOfDay.Add(TimeSpan.FromSeconds(config.System.SystemTimeOffset));
|
||||
|
||||
DateOffset = dateTimeOffset.Date;
|
||||
TimeOffset = dateTimeOffset.TimeOfDay;
|
||||
EnableVsync = config.Graphics.EnableVsync;
|
||||
EnableFsIntegrityChecks = config.System.EnableFsIntegrityChecks;
|
||||
ExpandDramSize = config.System.ExpandRam;
|
||||
@@ -487,9 +489,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.System.TimeZone.Value = TimeZone;
|
||||
}
|
||||
|
||||
TimeSpan systemTimeOffset = DateOffset - DateTime.Now;
|
||||
|
||||
config.System.SystemTimeOffset.Value = systemTimeOffset.Seconds;
|
||||
config.System.SystemTimeOffset.Value = Convert.ToInt64((CurrentDate.ToUnixTimeSeconds() + CurrentTime.TotalSeconds) - DateTimeOffset.Now.ToUnixTimeSeconds());
|
||||
config.Graphics.EnableVsync.Value = EnableVsync;
|
||||
config.System.EnableFsIntegrityChecks.Value = EnableFsIntegrityChecks;
|
||||
config.System.ExpandRam.Value = ExpandDramSize;
|
||||
|
@@ -11,6 +11,7 @@ using Ryujinx.Common;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
@@ -176,7 +177,11 @@ namespace Ryujinx.Ava.UI.Views.Main
|
||||
|
||||
string name = ViewModel.AppHost.Device.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)ViewModel.AppHost.Device.System.State.DesiredTitleLanguage].NameString.ToString();
|
||||
|
||||
await new CheatWindow(Window.VirtualFileSystem, ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText, name).ShowDialog(Window);
|
||||
await new CheatWindow(
|
||||
Window.VirtualFileSystem,
|
||||
ViewModel.AppHost.Device.Processes.ActiveApplication.ProgramIdText,
|
||||
name,
|
||||
Window.ViewModel.SelectedApplication.Path).ShowDialog(Window);
|
||||
|
||||
ViewModel.AppHost.Device.EnableCheats();
|
||||
}
|
||||
|
@@ -3,12 +3,12 @@
|
||||
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:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
x:DataType="viewModels:SettingsViewModel"
|
||||
mc:Ignorable="d">
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
@@ -27,13 +27,15 @@
|
||||
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabSystemCore}" />
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Vertical">
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemRegion}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding Region}"
|
||||
ToolTip.Tip="{locale:Locale RegionTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
Width="350">
|
||||
<TextBlock
|
||||
Width="250"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemRegion}" />
|
||||
<ComboBox
|
||||
Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
SelectedIndex="{Binding Region}"
|
||||
ToolTip.Tip="{locale:Locale RegionTooltip}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemRegionJapan}" />
|
||||
</ComboBoxItem>
|
||||
@@ -58,20 +60,21 @@
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
|
||||
ToolTip.Tip="{locale:Locale LanguageTooltip}"
|
||||
Width="250" />
|
||||
<ComboBox SelectedIndex="{Binding Language}"
|
||||
ToolTip.Tip="{locale:Locale LanguageTooltip}"
|
||||
HorizontalContentAlignment="Left"
|
||||
Width="350">
|
||||
<TextBlock
|
||||
Width="250"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguage}"
|
||||
ToolTip.Tip="{locale:Locale LanguageTooltip}" />
|
||||
<ComboBox
|
||||
Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
SelectedIndex="{Binding Language}"
|
||||
ToolTip.Tip="{locale:Locale LanguageTooltip}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageJapanese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageAmericanEnglish}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageFrench}" />
|
||||
@@ -104,71 +107,67 @@
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTaiwanese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageBritishEnglish}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageCanadianFrench}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageLatinAmericanSpanish}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageSimplifiedChinese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageTraditionalChinese}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemSystemLanguageBrazilianPortuguese}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
|
||||
ToolTip.Tip="{locale:Locale TimezoneTooltip}"
|
||||
Width="250" />
|
||||
<TextBlock
|
||||
Width="250"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemTimeZone}"
|
||||
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
|
||||
<AutoCompleteBox
|
||||
Name="TimeZoneBox"
|
||||
Width="350"
|
||||
MaxDropDownHeight="500"
|
||||
FilterMode="Contains"
|
||||
Items="{Binding TimeZones}"
|
||||
MaxDropDownHeight="500"
|
||||
SelectionChanged="TimeZoneBox_OnSelectionChanged"
|
||||
Text="{Binding Path=TimeZone, Mode=OneWay}"
|
||||
TextChanged="TimeZoneBox_OnTextChanged"
|
||||
ToolTip.Tip="{locale:Locale TimezoneTooltip}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="0,0,0,10" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemTime}"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}"
|
||||
Width="250"/>
|
||||
<DatePicker VerticalAlignment="Center" SelectedDate="{Binding DateOffset}"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}"
|
||||
Width="350" />
|
||||
<TextBlock
|
||||
Width="250"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabSystemSystemTime}"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}" />
|
||||
<DatePicker
|
||||
Width="350"
|
||||
VerticalAlignment="Center"
|
||||
SelectedDate="{Binding CurrentDate}"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="250,0,0,10" Orientation="Horizontal">
|
||||
<TimePicker
|
||||
Width="350"
|
||||
VerticalAlignment="Center"
|
||||
ClockIdentifier="24HourClock"
|
||||
SelectedTime="{Binding TimeOffset}"
|
||||
Width="350"
|
||||
SelectedTime="{Binding CurrentTime}"
|
||||
ToolTip.Tip="{locale:Locale TimeTooltip}" />
|
||||
</StackPanel>
|
||||
<CheckBox IsChecked="{Binding EnableVsync}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}"
|
||||
ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableVsync}" ToolTip.Tip="{locale:Locale VSyncToggleTooltip}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding EnableFsIntegrityChecks}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}"
|
||||
ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemEnableFsIntegrityChecks}" ToolTip.Tip="{locale:Locale FsIntegrityToggleTooltip}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<Separator Height="1" />
|
||||
@@ -180,12 +179,10 @@
|
||||
Margin="10,0,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical">
|
||||
<CheckBox IsChecked="{Binding ExpandDramSize}"
|
||||
ToolTip.Tip="{locale:Locale DRamTooltip}">
|
||||
<CheckBox IsChecked="{Binding ExpandDramSize}" ToolTip.Tip="{locale:Locale DRamTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemExpandDramSize}" />
|
||||
</CheckBox>
|
||||
<CheckBox IsChecked="{Binding IgnoreMissingServices}"
|
||||
ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
|
||||
<CheckBox IsChecked="{Binding IgnoreMissingServices}" ToolTip.Tip="{locale:Locale IgnoreMissingServicesTooltip}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabSystemIgnoreMissingServices}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
|
@@ -21,23 +21,52 @@
|
||||
</Window.Styles>
|
||||
<Grid Name="CheatGrid" Margin="15">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
MaxWidth="500"
|
||||
Margin="20,15,20,20"
|
||||
Margin="20,15,20,5"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
LineHeight="18"
|
||||
Text="{Binding Heading}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<Border
|
||||
<TextBlock
|
||||
Grid.Row="2"
|
||||
Grid.Column="0"
|
||||
MaxWidth="500"
|
||||
Margin="140,15,20,5"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
LineHeight="30"
|
||||
Text="{locale:Locale BuildId}"
|
||||
TextAlignment="Center"
|
||||
TextWrapping="Wrap" />
|
||||
<TextBox
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Margin="0,5,110,5"
|
||||
MinWidth="160"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding BuildId}"
|
||||
IsReadOnly="True" />
|
||||
<Border
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="5"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
@@ -81,7 +110,9 @@
|
||||
</TreeView>
|
||||
</Border>
|
||||
<DockPanel
|
||||
Grid.Row="3"
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="2"
|
||||
Margin="0"
|
||||
HorizontalAlignment="Stretch">
|
||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
||||
|
@@ -1,8 +1,10 @@
|
||||
using Avalonia.Collections;
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -17,6 +19,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
private AvaloniaList<CheatsList> LoadedCheats { get; }
|
||||
|
||||
public string Heading { get; }
|
||||
public string BuildId { get; }
|
||||
|
||||
public CheatWindow()
|
||||
{
|
||||
@@ -27,12 +30,13 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
Title = $"Ryujinx {Program.Version} - " + LocaleManager.Instance[LocaleKeys.CheatWindowTitle];
|
||||
}
|
||||
|
||||
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName)
|
||||
public CheatWindow(VirtualFileSystem virtualFileSystem, string titleId, string titleName, string titlePath)
|
||||
{
|
||||
LoadedCheats = new AvaloniaList<CheatsList>();
|
||||
|
||||
Heading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.CheatWindowHeading, titleName, titleId.ToUpper());
|
||||
|
||||
BuildId = ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath);
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
string modsBasePath = ModLoader.GetModsBasePath();
|
||||
|
@@ -299,10 +299,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
if (_programList.Count != 0)
|
||||
{
|
||||
_stateChangeCallback(ShaderCacheState.Packaging, 0, _programList.Count);
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Rebuilding {_programList.Count} shaders...");
|
||||
|
||||
using var streams = _hostStorage.GetOutputStreams(_context);
|
||||
|
||||
int packagedShaders = 0;
|
||||
foreach (var kv in _programList)
|
||||
{
|
||||
if (!Active)
|
||||
@@ -311,7 +314,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
|
||||
(CachedShaderProgram program, byte[] binaryCode) = kv.Value;
|
||||
|
||||
_hostStorage.AddShader(_context, program, binaryCode, streams);
|
||||
|
||||
_stateChangeCallback(ShaderCacheState.Packaging, ++packagedShaders, _programList.Count);
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully.");
|
||||
|
@@ -7,6 +7,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
Start,
|
||||
/// <summary>Shader cache is loading</summary>
|
||||
Loading,
|
||||
/// <summary>Shader cache is written to disk</summary>
|
||||
Packaging,
|
||||
/// <summary>Shader cache finished loading</summary>
|
||||
Loaded
|
||||
}
|
||||
|
@@ -105,6 +105,23 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryIncrementReferenceCount()
|
||||
{
|
||||
int lastValue;
|
||||
do
|
||||
{
|
||||
lastValue = _referenceCount;
|
||||
|
||||
if (lastValue == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void IncrementReferenceCount()
|
||||
{
|
||||
if (Interlocked.Increment(ref _referenceCount) == 1)
|
||||
|
@@ -599,9 +599,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
Auto<DisposableBuffer> dst,
|
||||
int srcOffset,
|
||||
int dstOffset,
|
||||
int size)
|
||||
int size,
|
||||
bool registerSrcUsage = true)
|
||||
{
|
||||
var srcBuffer = src.Get(cbs, srcOffset, size).Value;
|
||||
var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
|
||||
var dstBuffer = dst.Get(cbs, dstOffset, size).Value;
|
||||
|
||||
InsertBufferBarrier(
|
||||
|
@@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public SemaphoreHolder Semaphore;
|
||||
|
||||
public List<IAuto> Dependants;
|
||||
public HashSet<MultiFenceHolder> Waitables;
|
||||
public List<MultiFenceHolder> Waitables;
|
||||
public HashSet<SemaphoreHolder> Dependencies;
|
||||
|
||||
public void Initialize(Vk api, Device device, CommandPool pool)
|
||||
@@ -47,7 +47,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
api.AllocateCommandBuffers(device, allocateInfo, out CommandBuffer);
|
||||
|
||||
Dependants = new List<IAuto>();
|
||||
Waitables = new HashSet<MultiFenceHolder>();
|
||||
Waitables = new List<MultiFenceHolder>();
|
||||
Dependencies = new HashSet<SemaphoreHolder>();
|
||||
}
|
||||
}
|
||||
@@ -143,8 +143,10 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public void AddWaitable(int cbIndex, MultiFenceHolder waitable)
|
||||
{
|
||||
ref var entry = ref _commandBuffers[cbIndex];
|
||||
waitable.AddFence(cbIndex, entry.Fence);
|
||||
entry.Waitables.Add(waitable);
|
||||
if (waitable.AddFence(cbIndex, entry.Fence))
|
||||
{
|
||||
entry.Waitables.Add(waitable);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasWaitableOnRentedCommandBuffer(MultiFenceHolder waitable, int offset, int size)
|
||||
@@ -156,7 +158,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ref var entry = ref _commandBuffers[i];
|
||||
|
||||
if (entry.InUse &&
|
||||
entry.Waitables.Contains(waitable) &&
|
||||
waitable.HasFence(i) &&
|
||||
waitable.IsBufferRangeInUse(i, offset, size))
|
||||
{
|
||||
return true;
|
||||
@@ -331,7 +333,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
foreach (var waitable in entry.Waitables)
|
||||
{
|
||||
waitable.RemoveFence(cbIndex, entry.Fence);
|
||||
waitable.RemoveFence(cbIndex);
|
||||
waitable.RemoveBufferUses(cbIndex);
|
||||
}
|
||||
|
||||
|
@@ -32,6 +32,25 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _fence;
|
||||
}
|
||||
|
||||
public bool TryGet(out Fence fence)
|
||||
{
|
||||
int lastValue;
|
||||
do
|
||||
{
|
||||
lastValue = _referenceCount;
|
||||
|
||||
if (lastValue == 0)
|
||||
{
|
||||
fence = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while (Interlocked.CompareExchange(ref _referenceCount, lastValue + 1, lastValue) != lastValue);
|
||||
|
||||
fence = _fence;
|
||||
return true;
|
||||
}
|
||||
|
||||
public Fence Get()
|
||||
{
|
||||
Interlocked.Increment(ref _referenceCount);
|
||||
|
@@ -1,6 +1,5 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
@@ -11,7 +10,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
private static int BufferUsageTrackingGranularity = 4096;
|
||||
|
||||
private readonly Dictionary<FenceHolder, int> _fences;
|
||||
private readonly FenceHolder[] _fences;
|
||||
private BufferUsageBitmap _bufferUsageBitmap;
|
||||
|
||||
/// <summary>
|
||||
@@ -19,7 +18,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// </summary>
|
||||
public MultiFenceHolder()
|
||||
{
|
||||
_fences = new Dictionary<FenceHolder, int>();
|
||||
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -28,7 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// <param name="size">Size of the buffer</param>
|
||||
public MultiFenceHolder(int size)
|
||||
{
|
||||
_fences = new Dictionary<FenceHolder, int>();
|
||||
_fences = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
|
||||
_bufferUsageBitmap = new BufferUsageBitmap(size, BufferUsageTrackingGranularity);
|
||||
}
|
||||
|
||||
@@ -80,25 +79,37 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
|
||||
/// <param name="fence">Fence to be added</param>
|
||||
public void AddFence(int cbIndex, FenceHolder fence)
|
||||
/// <returns>True if the command buffer's previous fence value was null</returns>
|
||||
public bool AddFence(int cbIndex, FenceHolder fence)
|
||||
{
|
||||
lock (_fences)
|
||||
ref FenceHolder fenceRef = ref _fences[cbIndex];
|
||||
|
||||
if (fenceRef == null)
|
||||
{
|
||||
_fences.TryAdd(fence, cbIndex);
|
||||
fenceRef = fence;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a fence from the holder.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Command buffer index of the command buffer that owns the fence</param>
|
||||
/// <param name="fence">Fence to be removed</param>
|
||||
public void RemoveFence(int cbIndex, FenceHolder fence)
|
||||
public void RemoveFence(int cbIndex)
|
||||
{
|
||||
lock (_fences)
|
||||
{
|
||||
_fences.Remove(fence);
|
||||
}
|
||||
_fences[cbIndex] = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a fence referenced on the given command buffer.
|
||||
/// </summary>
|
||||
/// <param name="cbIndex">Index of the command buffer to check if it's used</param>
|
||||
/// <returns>True if referenced, false otherwise</returns>
|
||||
public bool HasFence(int cbIndex)
|
||||
{
|
||||
return _fences[cbIndex] != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -147,21 +158,29 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
/// <returns>True if all fences were signaled before the timeout expired, false otherwise</returns>
|
||||
private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
|
||||
{
|
||||
FenceHolder[] fenceHolders;
|
||||
Fence[] fences;
|
||||
Span<FenceHolder> fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
|
||||
|
||||
lock (_fences)
|
||||
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
|
||||
Span<Fence> fences = stackalloc Fence[count];
|
||||
|
||||
int fenceCount = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
fenceHolders = size != 0 ? GetOverlappingFences(offset, size) : _fences.Keys.ToArray();
|
||||
fences = new Fence[fenceHolders.Length];
|
||||
|
||||
for (int i = 0; i < fenceHolders.Length; i++)
|
||||
if (fenceHolders[i].TryGet(out Fence fence))
|
||||
{
|
||||
fences[i] = fenceHolders[i].Get();
|
||||
fences[fenceCount] = fence;
|
||||
|
||||
if (fenceCount < i)
|
||||
{
|
||||
fenceHolders[fenceCount] = fenceHolders[i];
|
||||
}
|
||||
|
||||
fenceCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fences.Length == 0)
|
||||
if (fenceCount == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -170,14 +189,14 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (hasTimeout)
|
||||
{
|
||||
signaled = FenceHelper.AllSignaled(api, device, fences, timeout);
|
||||
signaled = FenceHelper.AllSignaled(api, device, fences.Slice(0, fenceCount), timeout);
|
||||
}
|
||||
else
|
||||
{
|
||||
FenceHelper.WaitAllIndefinitely(api, device, fences);
|
||||
FenceHelper.WaitAllIndefinitely(api, device, fences.Slice(0, fenceCount));
|
||||
}
|
||||
|
||||
for (int i = 0; i < fenceHolders.Length; i++)
|
||||
for (int i = 0; i < fenceCount; i++)
|
||||
{
|
||||
fenceHolders[i].Put();
|
||||
}
|
||||
@@ -186,27 +205,49 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets fences to wait for use of a given buffer region.
|
||||
/// Gets fences to wait for.
|
||||
/// </summary>
|
||||
/// <param name="offset">Offset of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>Fences for the specified region</returns>
|
||||
private FenceHolder[] GetOverlappingFences(int offset, int size)
|
||||
/// <param name="storage">Span to store fences in</param>
|
||||
/// <returns>Number of fences placed in storage</returns>
|
||||
private int GetFences(Span<FenceHolder> storage)
|
||||
{
|
||||
List<FenceHolder> overlapping = new List<FenceHolder>();
|
||||
int count = 0;
|
||||
|
||||
foreach (var kv in _fences)
|
||||
for (int i = 0; i < _fences.Length; i++)
|
||||
{
|
||||
var fence = kv.Key;
|
||||
var ownerCbIndex = kv.Value;
|
||||
var fence = _fences[i];
|
||||
|
||||
if (_bufferUsageBitmap.OverlapsWith(ownerCbIndex, offset, size))
|
||||
if (fence != null)
|
||||
{
|
||||
overlapping.Add(fence);
|
||||
storage[count++] = fence;
|
||||
}
|
||||
}
|
||||
|
||||
return overlapping.ToArray();
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets fences to wait for use of a given buffer region.
|
||||
/// </summary>
|
||||
/// <param name="storage">Span to store overlapping fences in</param>
|
||||
/// <param name="offset">Offset of the range</param>
|
||||
/// <param name="size">Size of the range in bytes</param>
|
||||
/// <returns>Number of fences for the specified region placed in storage</returns>
|
||||
private int GetOverlappingFences(Span<FenceHolder> storage, int offset, int size)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < _fences.Length; i++)
|
||||
{
|
||||
var fence = _fences[i];
|
||||
|
||||
if (fence != null && _bufferUsageBitmap.OverlapsWith(i, offset, size))
|
||||
{
|
||||
storage[count++] = fence;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -34,16 +34,26 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public Span<byte> GetBufferData(CommandBufferPool cbp, BufferHolder buffer, int offset, int size)
|
||||
{
|
||||
var flushStorage = ResizeIfNeeded(size);
|
||||
Auto<DisposableBuffer> srcBuffer;
|
||||
|
||||
using (var cbs = cbp.Rent())
|
||||
{
|
||||
var srcBuffer = buffer.GetBuffer(cbs.CommandBuffer);
|
||||
srcBuffer = buffer.GetBuffer(cbs.CommandBuffer);
|
||||
var dstBuffer = flushStorage.GetBuffer(cbs.CommandBuffer);
|
||||
|
||||
BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, 0, size);
|
||||
if (srcBuffer.TryIncrementReferenceCount())
|
||||
{
|
||||
BufferHolder.Copy(_gd, cbs, srcBuffer, dstBuffer, offset, 0, size, registerSrcUsage: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Source buffer is no longer alive, don't copy anything to flush storage.
|
||||
srcBuffer = null;
|
||||
}
|
||||
}
|
||||
|
||||
flushStorage.WaitForFences();
|
||||
srcBuffer?.DecrementReferenceCount();
|
||||
return flushStorage.GetDataStorage(0, size);
|
||||
}
|
||||
|
||||
|
@@ -66,6 +66,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private ulong _vertexBuffersDirty;
|
||||
protected Rectangle<int> ClearScissor;
|
||||
|
||||
private readonly VertexBufferUpdater _vertexBufferUpdater;
|
||||
|
||||
public SupportBufferUpdater SupportBufferUpdater;
|
||||
public IndexBufferPattern QuadsToTrisPattern;
|
||||
public IndexBufferPattern TriFanToTrisPattern;
|
||||
@@ -96,6 +98,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
|
||||
|
||||
_descriptorSetUpdater = new DescriptorSetUpdater(gd, this);
|
||||
_vertexBufferUpdater = new VertexBufferUpdater(gd);
|
||||
|
||||
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
|
||||
_vertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers + 1];
|
||||
@@ -1185,6 +1188,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
int validCount = 1;
|
||||
|
||||
BufferHandle lastHandle = default;
|
||||
Auto<DisposableBuffer> lastBuffer = default;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var vertexBuffer = vertexBuffers[i];
|
||||
@@ -1194,7 +1200,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
if (vertexBuffer.Buffer.Handle != BufferHandle.Null)
|
||||
{
|
||||
var vb = Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
|
||||
Auto<DisposableBuffer> vb = (vertexBuffer.Buffer.Handle == lastHandle) ? lastBuffer :
|
||||
Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
|
||||
|
||||
lastHandle = vertexBuffer.Buffer.Handle;
|
||||
lastBuffer = vb;
|
||||
|
||||
if (vb != null)
|
||||
{
|
||||
int binding = i + 1;
|
||||
@@ -1222,24 +1233,29 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
ref var buffer = ref _vertexBuffers[binding];
|
||||
int oldScalarAlign = buffer.AttributeScalarAlignment;
|
||||
|
||||
buffer.Dispose();
|
||||
|
||||
if (Gd.Capabilities.VertexBufferAlignment < 2 &&
|
||||
(vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
|
||||
{
|
||||
buffer = new VertexBufferState(
|
||||
vb,
|
||||
descriptorIndex,
|
||||
vertexBuffer.Buffer.Offset,
|
||||
vbSize,
|
||||
vertexBuffer.Stride);
|
||||
if (!buffer.Matches(vb, descriptorIndex, vertexBuffer.Buffer.Offset, vbSize, vertexBuffer.Stride))
|
||||
{
|
||||
buffer.Dispose();
|
||||
|
||||
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState);
|
||||
buffer = new VertexBufferState(
|
||||
vb,
|
||||
descriptorIndex,
|
||||
vertexBuffer.Buffer.Offset,
|
||||
vbSize,
|
||||
vertexBuffer.Stride);
|
||||
|
||||
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// May need to be rewritten. Bind this buffer before draw.
|
||||
|
||||
buffer.Dispose();
|
||||
|
||||
buffer = new VertexBufferState(
|
||||
vertexBuffer.Buffer.Handle,
|
||||
descriptorIndex,
|
||||
@@ -1255,6 +1271,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
_vertexBufferUpdater.Commit(Cbs);
|
||||
|
||||
_newState.VertexBindingDescriptionsCount = (uint)validCount;
|
||||
SignalStateChange();
|
||||
}
|
||||
@@ -1596,10 +1614,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
int i = BitOperations.TrailingZeroCount(_vertexBuffersDirty);
|
||||
|
||||
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState);
|
||||
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater);
|
||||
|
||||
_vertexBuffersDirty &= ~(1UL << i);
|
||||
}
|
||||
|
||||
_vertexBufferUpdater.Commit(Cbs);
|
||||
}
|
||||
|
||||
if (_stateDirty || Pbp != pbp)
|
||||
@@ -1712,6 +1732,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
_framebuffer?.Dispose();
|
||||
_newState.Dispose();
|
||||
_descriptorSetUpdater.Dispose();
|
||||
_vertexBufferUpdater.Dispose();
|
||||
|
||||
for (int i = 0; i < _vertexBuffers.Length; i++)
|
||||
{
|
||||
|
@@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AttributeScalarAlignment = 1;
|
||||
}
|
||||
|
||||
public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding, ref PipelineState state)
|
||||
public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding, ref PipelineState state, VertexBufferUpdater updater)
|
||||
{
|
||||
var autoBuffer = _buffer;
|
||||
|
||||
@@ -65,21 +65,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
var buffer = autoBuffer.Get(cbs, 0, newSize).Value;
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
||||
cbs.CommandBuffer,
|
||||
binding,
|
||||
1,
|
||||
buffer,
|
||||
0,
|
||||
(ulong)newSize,
|
||||
(ulong)stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, 0);
|
||||
}
|
||||
updater.BindVertexBuffer(cbs, binding, buffer, 0, (ulong)newSize, (ulong)stride);
|
||||
|
||||
_buffer = autoBuffer;
|
||||
|
||||
@@ -106,21 +92,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
var buffer = autoBuffer.Get(cbs, _offset, _size).Value;
|
||||
|
||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
||||
cbs.CommandBuffer,
|
||||
binding,
|
||||
1,
|
||||
buffer,
|
||||
(ulong)_offset,
|
||||
(ulong)_size,
|
||||
(ulong)_stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset);
|
||||
}
|
||||
updater.BindVertexBuffer(cbs, binding, buffer, (ulong)_offset, (ulong)_size, (ulong)_stride);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +101,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return _buffer == buffer;
|
||||
}
|
||||
|
||||
public bool Matches(Auto<DisposableBuffer> buffer, int descriptorIndex, int offset, int size, int stride = 0)
|
||||
{
|
||||
return _buffer == buffer && DescriptorIndex == descriptorIndex && _offset == offset && _size == size && _stride == stride;
|
||||
}
|
||||
|
||||
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||
{
|
||||
if (_buffer == from)
|
||||
|
84
src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs
Normal file
84
src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
internal class VertexBufferUpdater : IDisposable
|
||||
{
|
||||
private VulkanRenderer _gd;
|
||||
|
||||
private uint _baseBinding;
|
||||
private uint _count;
|
||||
|
||||
private NativeArray<VkBuffer> _buffers;
|
||||
private NativeArray<ulong> _offsets;
|
||||
private NativeArray<ulong> _sizes;
|
||||
private NativeArray<ulong> _strides;
|
||||
|
||||
public VertexBufferUpdater(VulkanRenderer gd)
|
||||
{
|
||||
_gd = gd;
|
||||
|
||||
_buffers = new NativeArray<VkBuffer>(Constants.MaxVertexBuffers);
|
||||
_offsets = new NativeArray<ulong>(Constants.MaxVertexBuffers);
|
||||
_sizes = new NativeArray<ulong>(Constants.MaxVertexBuffers);
|
||||
_strides = new NativeArray<ulong>(Constants.MaxVertexBuffers);
|
||||
}
|
||||
|
||||
public void BindVertexBuffer(CommandBufferScoped cbs, uint binding, VkBuffer buffer, ulong offset, ulong size, ulong stride)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
_baseBinding = binding;
|
||||
}
|
||||
else if (_baseBinding + _count != binding)
|
||||
{
|
||||
Commit(cbs);
|
||||
_baseBinding = binding;
|
||||
}
|
||||
|
||||
int index = (int)_count;
|
||||
|
||||
_buffers[index] = buffer;
|
||||
_offsets[index] = offset;
|
||||
_sizes[index] = size;
|
||||
_strides[index] = stride;
|
||||
|
||||
_count++;
|
||||
}
|
||||
|
||||
public unsafe void Commit(CommandBufferScoped cbs)
|
||||
{
|
||||
if (_count != 0)
|
||||
{
|
||||
if (_gd.Capabilities.SupportsExtendedDynamicState)
|
||||
{
|
||||
_gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
||||
cbs.CommandBuffer,
|
||||
_baseBinding,
|
||||
_count,
|
||||
_buffers.Pointer,
|
||||
_offsets.Pointer,
|
||||
_sizes.Pointer,
|
||||
_strides.Pointer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, _baseBinding, _count, _buffers.Pointer, _offsets.Pointer);
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_buffers.Dispose();
|
||||
_offsets.Dispose();
|
||||
_sizes.Dispose();
|
||||
_strides.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
@@ -200,9 +200,10 @@ namespace Ryujinx.HLE.HOS
|
||||
|
||||
LibHacHorizonManager = device.Configuration.LibHacHorizonManager;
|
||||
|
||||
// We hardcode a clock source id to avoid it changing between each start.
|
||||
// TODO: use set:sys (and get external clock source id from settings)
|
||||
// TODO: use "time!standard_steady_clock_rtc_update_interval_minutes" and implement a worker thread to be accurate.
|
||||
UInt128 clockSourceId = UInt128Utils.CreateRandom();
|
||||
UInt128 clockSourceId = new UInt128(0x36a0328702ce8bc1, 0x1608eaba02333284);
|
||||
IRtcManager.GetExternalRtcValue(out ulong rtcValue);
|
||||
|
||||
// We assume the rtc is system time.
|
||||
@@ -222,22 +223,22 @@ namespace Ryujinx.HLE.HOS
|
||||
internalOffset = internalOffset.AddSeconds(-3600L);
|
||||
}
|
||||
|
||||
internalOffset = new TimeSpanType(-internalOffset.NanoSeconds);
|
||||
systemTime = new TimeSpanType(systemTime.NanoSeconds + internalOffset.NanoSeconds);
|
||||
|
||||
// First init the standard steady clock
|
||||
TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, systemTime, internalOffset, TimeSpanType.Zero, false);
|
||||
TimeServiceManager.Instance.SetupStandardSteadyClock(TickSource, clockSourceId, TimeSpanType.Zero, TimeSpanType.Zero, TimeSpanType.Zero, false);
|
||||
TimeServiceManager.Instance.SetupStandardLocalSystemClock(TickSource, new SystemClockContext(), systemTime.ToSeconds());
|
||||
TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext);
|
||||
|
||||
if (NxSettings.Settings.TryGetValue("time!standard_network_clock_sufficient_accuracy_minutes", out object standardNetworkClockSufficientAccuracyMinutes))
|
||||
{
|
||||
TimeSpanType standardNetworkClockSufficientAccuracy = new TimeSpanType((int)standardNetworkClockSufficientAccuracyMinutes * 60000000000);
|
||||
|
||||
// The network system clock needs a valid system clock, as such we setup this system clock using the local system clock.
|
||||
TimeServiceManager.Instance.StandardLocalSystemClock.GetClockContext(TickSource, out SystemClockContext localSytemClockContext);
|
||||
TimeServiceManager.Instance.SetupStandardNetworkSystemClock(localSytemClockContext, standardNetworkClockSufficientAccuracy);
|
||||
}
|
||||
|
||||
TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, false, SteadyClockTimePoint.GetRandom());
|
||||
TimeServiceManager.Instance.SetupStandardUserSystemClock(TickSource, true, localSytemClockContext.SteadyTimePoint);
|
||||
|
||||
// FIXME: TimeZone should be init here but it's actually done in ContentManager
|
||||
|
||||
|
@@ -0,0 +1,13 @@
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.HLE.HOS.Services.Time.Clock.Types
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
struct ContinuousAdjustmentTimePoint
|
||||
{
|
||||
public ulong ClockOffset;
|
||||
public long Multiplier;
|
||||
public long DivisorLog2;
|
||||
public SystemClockContext Context;
|
||||
}
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
using Ryujinx.Cpu;
|
||||
using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Clock.Types;
|
||||
using Ryujinx.HLE.HOS.Services.Time.Types;
|
||||
using Ryujinx.HLE.Utilities;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
@@ -16,10 +16,11 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||
private SharedMemoryStorage _timeSharedMemoryStorage;
|
||||
private int _timeSharedMemorySize;
|
||||
|
||||
private const uint SteadyClockContextOffset = 0x00;
|
||||
private const uint LocalSystemClockContextOffset = 0x38;
|
||||
private const uint NetworkSystemClockContextOffset = 0x80;
|
||||
private const uint SteadyClockContextOffset = 0x00;
|
||||
private const uint LocalSystemClockContextOffset = 0x38;
|
||||
private const uint NetworkSystemClockContextOffset = 0x80;
|
||||
private const uint AutomaticCorrectionEnabledOffset = 0xC8;
|
||||
private const uint ContinuousAdjustmentTimePointOffset = 0xD0;
|
||||
|
||||
public void Initialize(Switch device, KSharedMemory sharedMemory, SharedMemoryStorage timeSharedMemoryStorage, int timeSharedMemorySize)
|
||||
{
|
||||
@@ -39,15 +40,7 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||
|
||||
public void SetupStandardSteadyClock(ITickSource tickSource, UInt128 clockSourceId, TimeSpanType currentTimePoint)
|
||||
{
|
||||
TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency);
|
||||
|
||||
SteadyClockContext context = new SteadyClockContext
|
||||
{
|
||||
InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds),
|
||||
ClockSourceId = clockSourceId
|
||||
};
|
||||
|
||||
WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context);
|
||||
UpdateSteadyClock(tickSource, clockSourceId, currentTimePoint);
|
||||
}
|
||||
|
||||
public void SetAutomaticCorrectionEnabled(bool isAutomaticCorrectionEnabled)
|
||||
@@ -58,10 +51,38 @@ namespace Ryujinx.HLE.HOS.Services.Time
|
||||
|
||||
public void SetSteadyClockRawTimePoint(ITickSource tickSource, TimeSpanType currentTimePoint)
|
||||
{
|
||||
SteadyClockContext context = ReadObjectFromSharedMemory<SteadyClockContext>(SteadyClockContextOffset, 4);
|
||||
TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency);
|
||||
SteadyClockContext context = ReadObjectFromSharedMemory<SteadyClockContext>(SteadyClockContextOffset, 4);
|
||||
|
||||
context.InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds);
|
||||
UpdateSteadyClock(tickSource, context.ClockSourceId, currentTimePoint);
|
||||
}
|
||||
|
||||
private void UpdateSteadyClock(ITickSource tickSource, UInt128 clockSourceId, TimeSpanType currentTimePoint)
|
||||
{
|
||||
TimeSpanType ticksTimeSpan = TimeSpanType.FromTicks(tickSource.Counter, tickSource.Frequency);
|
||||
|
||||
ContinuousAdjustmentTimePoint adjustmentTimePoint = new ContinuousAdjustmentTimePoint
|
||||
{
|
||||
ClockOffset = (ulong)ticksTimeSpan.NanoSeconds,
|
||||
Multiplier = 1,
|
||||
DivisorLog2 = 0,
|
||||
Context = new SystemClockContext
|
||||
{
|
||||
Offset = 0,
|
||||
SteadyTimePoint = new SteadyClockTimePoint
|
||||
{
|
||||
ClockSourceId = clockSourceId,
|
||||
TimePoint = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WriteObjectToSharedMemory(ContinuousAdjustmentTimePointOffset, 4, adjustmentTimePoint);
|
||||
|
||||
SteadyClockContext context = new SteadyClockContext
|
||||
{
|
||||
InternalOffset = (ulong)(currentTimePoint.NanoSeconds - ticksTimeSpan.NanoSeconds),
|
||||
ClockSourceId = clockSourceId
|
||||
};
|
||||
|
||||
WriteObjectToSharedMemory(SteadyClockContextOffset, 4, context);
|
||||
}
|
||||
|
@@ -116,7 +116,7 @@ namespace Ryujinx.HLE.Loaders.Processes.Extensions
|
||||
device,
|
||||
device.System.KernelContext,
|
||||
metaLoader,
|
||||
nacpData.Value,
|
||||
nacpData,
|
||||
enablePtc,
|
||||
allowCodeMemoryForJit,
|
||||
programName,
|
||||
|
@@ -209,7 +209,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
ProcessResult processResult = ProcessLoaderHelper.LoadNsos(_device,
|
||||
_device.System.KernelContext,
|
||||
dummyExeFs.GetNpdm(),
|
||||
nacpData.Value,
|
||||
nacpData,
|
||||
diskCacheEnabled: false,
|
||||
allowCodeMemoryForJit: true,
|
||||
programName,
|
||||
|
@@ -219,7 +219,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
Switch device,
|
||||
KernelContext context,
|
||||
MetaLoader metaLoader,
|
||||
ApplicationControlProperty applicationControlProperties,
|
||||
BlitStruct<ApplicationControlProperty> applicationControlProperties,
|
||||
bool diskCacheEnabled,
|
||||
bool allowCodeMemoryForJit,
|
||||
string name,
|
||||
@@ -355,7 +355,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
context.Device.System.TickSource,
|
||||
context.Device.Gpu,
|
||||
$"{programId:x16}",
|
||||
applicationControlProperties.DisplayVersionString.ToString(),
|
||||
applicationControlProperties.Value.DisplayVersionString.ToString(),
|
||||
diskCacheEnabled,
|
||||
codeStart,
|
||||
codeSize);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
using LibHac.Loader;
|
||||
using LibHac.Common;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Ns;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Cpu;
|
||||
@@ -9,9 +10,9 @@ using System.Linq;
|
||||
|
||||
namespace Ryujinx.HLE.Loaders.Processes
|
||||
{
|
||||
public struct ProcessResult
|
||||
public class ProcessResult
|
||||
{
|
||||
public static ProcessResult Failed => new(null, new ApplicationControlProperty(), false, false, null, 0, 0, 0, TitleLanguage.AmericanEnglish);
|
||||
public static ProcessResult Failed => new(null, new BlitStruct<ApplicationControlProperty>(1), false, false, null, 0, 0, 0, TitleLanguage.AmericanEnglish);
|
||||
|
||||
private readonly byte _mainThreadPriority;
|
||||
private readonly uint _mainThreadStackSize;
|
||||
@@ -31,15 +32,15 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
public readonly bool AllowCodeMemoryForJit;
|
||||
|
||||
public ProcessResult(
|
||||
MetaLoader metaLoader,
|
||||
ApplicationControlProperty applicationControlProperties,
|
||||
bool diskCacheEnabled,
|
||||
bool allowCodeMemoryForJit,
|
||||
IDiskCacheLoadState diskCacheLoadState,
|
||||
ulong pid,
|
||||
byte mainThreadPriority,
|
||||
uint mainThreadStackSize,
|
||||
TitleLanguage titleLanguage)
|
||||
MetaLoader metaLoader,
|
||||
BlitStruct<ApplicationControlProperty> applicationControlProperties,
|
||||
bool diskCacheEnabled,
|
||||
bool allowCodeMemoryForJit,
|
||||
IDiskCacheLoadState diskCacheLoadState,
|
||||
ulong pid,
|
||||
byte mainThreadPriority,
|
||||
uint mainThreadStackSize,
|
||||
TitleLanguage titleLanguage)
|
||||
{
|
||||
_mainThreadPriority = mainThreadPriority;
|
||||
_mainThreadStackSize = mainThreadStackSize;
|
||||
@@ -48,7 +49,7 @@ namespace Ryujinx.HLE.Loaders.Processes
|
||||
ProcessId = pid;
|
||||
|
||||
MetaLoader = metaLoader;
|
||||
ApplicationControlProperties = applicationControlProperties;
|
||||
ApplicationControlProperties = applicationControlProperties.Value;
|
||||
|
||||
if (metaLoader is not null)
|
||||
{
|
||||
|
@@ -1,5 +1,16 @@
|
||||
using LibHac.Common;
|
||||
using LibHac.Ns;
|
||||
using LibHac.Fs;
|
||||
using LibHac.Fs.Fsa;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Loader;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Ui.App.Common
|
||||
{
|
||||
@@ -19,5 +30,122 @@ namespace Ryujinx.Ui.App.Common
|
||||
public double FileSizeBytes { get; set; }
|
||||
public string Path { get; set; }
|
||||
public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
|
||||
|
||||
public static string GetApplicationBuildId(VirtualFileSystem virtualFileSystem, string titleFilePath)
|
||||
{
|
||||
using FileStream file = new(titleFilePath, FileMode.Open, FileAccess.Read);
|
||||
|
||||
Nca mainNca = null;
|
||||
Nca patchNca = null;
|
||||
|
||||
if (!System.IO.Path.Exists(titleFilePath))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"File does not exists. {titleFilePath}");
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
string extension = System.IO.Path.GetExtension(titleFilePath).ToLower();
|
||||
|
||||
if (extension is ".nsp" or ".xci")
|
||||
{
|
||||
PartitionFileSystem pfs;
|
||||
|
||||
if (extension == ".xci")
|
||||
{
|
||||
Xci xci = new(virtualFileSystem.KeySet, file.AsStorage());
|
||||
|
||||
pfs = xci.OpenPartition(XciPartitionType.Secure);
|
||||
}
|
||||
else
|
||||
{
|
||||
pfs = new PartitionFileSystem(file.AsStorage());
|
||||
}
|
||||
|
||||
foreach (DirectoryEntryEx fileEntry in pfs.EnumerateEntries("/", "*.nca"))
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new(virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||
|
||||
if (nca.Header.ContentType != NcaContentType.Program)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int dataIndex = Nca.GetSectionIndexFromType(NcaSectionType.Data, NcaContentType.Program);
|
||||
|
||||
if (nca.Header.GetFsHeader(dataIndex).IsPatchSection())
|
||||
{
|
||||
patchNca = nca;
|
||||
}
|
||||
else
|
||||
{
|
||||
mainNca = nca;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (extension == ".nca")
|
||||
{
|
||||
mainNca = new Nca(virtualFileSystem.KeySet, file.AsStorage());
|
||||
}
|
||||
|
||||
if (mainNca == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, "Extraction failure. The main NCA was not present in the selected file");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
(Nca updatePatchNca, _) = ApplicationLibrary.GetGameUpdateData(virtualFileSystem, mainNca.Header.TitleId.ToString("x16"), 0, out _);
|
||||
|
||||
if (updatePatchNca != null)
|
||||
{
|
||||
patchNca = updatePatchNca;
|
||||
}
|
||||
|
||||
IFileSystem codeFs = null;
|
||||
|
||||
if (patchNca == null)
|
||||
{
|
||||
if (mainNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystem(NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (patchNca.CanOpenSection(NcaSectionType.Code))
|
||||
{
|
||||
codeFs = mainNca.OpenFileSystemWithPatch(patchNca, NcaSectionType.Code, IntegrityCheckLevel.ErrorOnInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
if (codeFs == null)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No ExeFS found in NCA");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
const string mainExeFs = "main";
|
||||
|
||||
if (!codeFs.FileExists($"/{mainExeFs}"))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Loader, "No main binary ExeFS found in ExeFS");
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
using var nsoFile = new UniqueRef<IFile>();
|
||||
|
||||
codeFs.OpenFile(ref nsoFile.Ref, $"/{mainExeFs}".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
NsoReader reader = new NsoReader();
|
||||
reader.Initialize(nsoFile.Release().AsStorage().AsFile(OpenMode.Read)).ThrowIfFailure();
|
||||
|
||||
return BitConverter.ToString(reader.Header.ModuleId.ItemsRo.ToArray()).Replace("-", "").ToUpper()[..16];
|
||||
}
|
||||
}
|
||||
}
|
@@ -1626,9 +1626,12 @@ namespace Ryujinx.Ui
|
||||
|
||||
private void ManageCheats_Pressed(object sender, EventArgs args)
|
||||
{
|
||||
var window = new CheatWindow(_virtualFileSystem,
|
||||
_emulationContext.Processes.ActiveApplication.ProgramId,
|
||||
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString());
|
||||
var window = new CheatWindow(
|
||||
_virtualFileSystem,
|
||||
_emulationContext.Processes.ActiveApplication.ProgramId,
|
||||
_emulationContext.Processes.ActiveApplication.ApplicationControlProperties
|
||||
.Title[(int)_emulationContext.System.State.DesiredTitleLanguage].NameString.ToString(),
|
||||
_currentEmulatedGamePath);
|
||||
|
||||
window.Destroyed += CheatWindow_Destroyed;
|
||||
window.Show();
|
||||
|
@@ -461,7 +461,7 @@ namespace Ryujinx.Ui.Widgets
|
||||
|
||||
private void ManageCheats_Clicked(object sender, EventArgs args)
|
||||
{
|
||||
new CheatWindow(_virtualFileSystem, _titleId, _titleName).Show();
|
||||
new CheatWindow(_virtualFileSystem, _titleId, _titleName, _titleFilePath).Show();
|
||||
}
|
||||
|
||||
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using Gtk;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -17,16 +18,18 @@ namespace Ryujinx.Ui.Windows
|
||||
|
||||
#pragma warning disable CS0649, IDE0044
|
||||
[GUI] Label _baseTitleInfoLabel;
|
||||
[GUI] TextView _buildIdTextView;
|
||||
[GUI] TreeView _cheatTreeView;
|
||||
[GUI] Button _saveButton;
|
||||
#pragma warning restore CS0649, IDE0044
|
||||
|
||||
public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName) { }
|
||||
public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : this(new Builder("Ryujinx.Ui.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName, titlePath) { }
|
||||
|
||||
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : base(builder.GetRawOwnedObject("_cheatWindow"))
|
||||
private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName, string titlePath) : base(builder.GetRawOwnedObject("_cheatWindow"))
|
||||
{
|
||||
builder.Autoconnect(this);
|
||||
_baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]";
|
||||
_buildIdTextView.Buffer.Text = $"BuildId: {ApplicationData.GetApplicationBuildId(virtualFileSystem, titlePath)}";
|
||||
|
||||
string modsBasePath = ModLoader.GetModsBasePath();
|
||||
string titleModsPath = ModLoader.GetTitleDir(modsBasePath, titleId.ToString("X16"));
|
||||
|
@@ -31,6 +31,21 @@
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTextView" id="_buildIdTextView">
|
||||
<property name="visible">True</property>
|
||||
<property name="margin_top">10</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="margin_bottom">10</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="cursor_visible">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
@@ -57,7 +72,7 @@
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
|
Reference in New Issue
Block a user