aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Ryujinx.Ava/Assets/Locales/en_US.json1
-rw-r--r--src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs7
-rw-r--r--src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs7
-rw-r--r--src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml37
-rw-r--r--src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs10
-rw-r--r--src/Ryujinx.Ui.Common/App/ApplicationData.cs128
-rw-r--r--src/Ryujinx/Ui/MainWindow.cs9
-rw-r--r--src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs2
-rw-r--r--src/Ryujinx/Ui/Windows/CheatWindow.cs7
-rw-r--r--src/Ryujinx/Ui/Windows/CheatWindow.glade17
10 files changed, 210 insertions, 15 deletions
diff --git a/src/Ryujinx.Ava/Assets/Locales/en_US.json b/src/Ryujinx.Ava/Assets/Locales/en_US.json
index 965dfa3a..c9b10f54 100644
--- a/src/Ryujinx.Ava/Assets/Locales/en_US.json
+++ b/src/Ryujinx.Ava/Assets/Locales/en_US.json
@@ -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",
diff --git a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
index 90c72e02..a9269386 100644
--- a/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Controls/ApplicationContextMenu.axaml.cs
@@ -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);
}
}
diff --git a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
index bdf2cf9f..557528eb 100644
--- a/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
@@ -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();
}
diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml
index 3557ed69..11e86211 100644
--- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml
+++ b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml
@@ -23,21 +23,50 @@
<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">
diff --git a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
index 241a6c34..f5bba7d2 100644
--- a/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
+++ b/src/Ryujinx.Ava/UI/Windows/CheatWindow.axaml.cs
@@ -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();
diff --git a/src/Ryujinx.Ui.Common/App/ApplicationData.cs b/src/Ryujinx.Ui.Common/App/ApplicationData.cs
index ba430172..d9d3cf68 100644
--- a/src/Ryujinx.Ui.Common/App/ApplicationData.cs
+++ b/src/Ryujinx.Ui.Common/App/ApplicationData.cs
@@ -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];
+ }
}
} \ No newline at end of file
diff --git a/src/Ryujinx/Ui/MainWindow.cs b/src/Ryujinx/Ui/MainWindow.cs
index 4911c900..b61855e4 100644
--- a/src/Ryujinx/Ui/MainWindow.cs
+++ b/src/Ryujinx/Ui/MainWindow.cs
@@ -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();
diff --git a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
index 28ec5a43..74f6043d 100644
--- a/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
+++ b/src/Ryujinx/Ui/Widgets/GameTableContextMenu.cs
@@ -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)
diff --git a/src/Ryujinx/Ui/Windows/CheatWindow.cs b/src/Ryujinx/Ui/Windows/CheatWindow.cs
index 7dbea012..32df2c0c 100644
--- a/src/Ryujinx/Ui/Windows/CheatWindow.cs
+++ b/src/Ryujinx/Ui/Windows/CheatWindow.cs
@@ -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"));
diff --git a/src/Ryujinx/Ui/Windows/CheatWindow.glade b/src/Ryujinx/Ui/Windows/CheatWindow.glade
index 37b1cbe0..9a165f1a 100644
--- a/src/Ryujinx/Ui/Windows/CheatWindow.glade
+++ b/src/Ryujinx/Ui/Windows/CheatWindow.glade
@@ -32,6 +32,21 @@
</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>
<property name="can_focus">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>