aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThog <me@thog.eu>2020-03-25 18:09:38 +0100
committerGitHub <noreply@github.com>2020-03-25 18:09:38 +0100
commit5423daea56345237cd10e02870139143d5922aa2 (patch)
tree93bab4b37c7fcc811fe4653df678dc739c29ad3e
parentd5670aff7721e710ad5f4157827d5716b82bc329 (diff)
ui: Make it possible to open the device save directory (#1040)
* Add an open device folder option * Simplify logic from previous commit * Address Xpl0itR's comments * Address Ac_K comment
-rw-r--r--Ryujinx/Ui/ApplicationData.cs7
-rw-r--r--Ryujinx/Ui/ApplicationLibrary.cs13
-rw-r--r--Ryujinx/Ui/GameTableContextMenu.cs106
-rw-r--r--Ryujinx/Ui/GameTableContextMenu.glade15
-rw-r--r--Ryujinx/Ui/MainWindow.cs13
5 files changed, 121 insertions, 33 deletions
diff --git a/Ryujinx/Ui/ApplicationData.cs b/Ryujinx/Ui/ApplicationData.cs
index defc5e98..49d7dfaa 100644
--- a/Ryujinx/Ui/ApplicationData.cs
+++ b/Ryujinx/Ui/ApplicationData.cs
@@ -1,4 +1,8 @@
-namespace Ryujinx.Ui
+using LibHac;
+using LibHac.Common;
+using LibHac.Ns;
+
+namespace Ryujinx.Ui
{
public struct ApplicationData
{
@@ -14,5 +18,6 @@
public string FileSize { get; set; }
public string Path { get; set; }
public string SaveDataPath { get; set; }
+ public BlitStruct<ApplicationControlProperty> ControlHolder { get; set; }
}
}
diff --git a/Ryujinx/Ui/ApplicationLibrary.cs b/Ryujinx/Ui/ApplicationLibrary.cs
index 3ff7a54c..02deb7ca 100644
--- a/Ryujinx/Ui/ApplicationLibrary.cs
+++ b/Ryujinx/Ui/ApplicationLibrary.cs
@@ -6,6 +6,7 @@ using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ncm;
+using LibHac.Ns;
using LibHac.Spl;
using Ryujinx.Common.Logging;
using Ryujinx.Configuration.System;
@@ -81,6 +82,12 @@ namespace Ryujinx.Ui
}
}
+ public static void ReadControlData(IFileSystem controlFs, Span<byte> outProperty)
+ {
+ controlFs.OpenFile(out IFile controlFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
+ controlFile.Read(out long _, 0, outProperty, ReadOption.None).ThrowIfFailure();
+ }
+
public static void LoadApplications(List<string> appDirs, VirtualFileSystem virtualFileSystem, Language desiredTitleLanguage)
{
int numApplicationsFound = 0;
@@ -127,6 +134,7 @@ namespace Ryujinx.Ui
string version = "0";
string saveDataPath = null;
byte[] applicationIcon = null;
+ BlitStruct<ApplicationControlProperty> controlHolder = new BlitStruct<ApplicationControlProperty>(1);
try
{
@@ -204,6 +212,8 @@ namespace Ryujinx.Ui
// Store the ControlFS in variable called controlFs
GetControlFsAndTitleId(pfs, out IFileSystem controlFs, out titleId);
+ ReadControlData(controlFs, controlHolder.ByteSpan);
+
// Creates NACP class from the NACP file
controlFs.OpenFile(out IFile controlNacpFile, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
@@ -413,7 +423,8 @@ namespace Ryujinx.Ui
FileExtension = Path.GetExtension(applicationPath).ToUpper().Remove(0 ,1),
FileSize = (fileSize < 1) ? (fileSize * 1024).ToString("0.##") + "MB" : fileSize.ToString("0.##") + "GB",
Path = applicationPath,
- SaveDataPath = saveDataPath
+ SaveDataPath = saveDataPath,
+ ControlHolder = controlHolder
};
numApplicationsLoaded++;
diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs
index 8c0bd0bc..5b3b9df4 100644
--- a/Ryujinx/Ui/GameTableContextMenu.cs
+++ b/Ryujinx/Ui/GameTableContextMenu.cs
@@ -1,21 +1,25 @@
using Gtk;
using LibHac;
+using LibHac.Account;
using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.FsSystem.NcaUtils;
using LibHac.Ncm;
+using LibHac.Ns;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.FileSystem;
using System;
using System.Buffers;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
+using static LibHac.Fs.ApplicationSaveDataManagement;
using GUI = Gtk.Builder.ObjectAttribute;
namespace Ryujinx.Ui
@@ -28,23 +32,31 @@ namespace Ryujinx.Ui
private MessageDialog _dialog;
private bool _cancel;
+ private BlitStruct<ApplicationControlProperty> _controlData;
+
#pragma warning disable CS0649
#pragma warning disable IDE0044
- [GUI] MenuItem _openSaveDir;
+ [GUI] MenuItem _openSaveUserDir;
+ [GUI] MenuItem _openSaveDeviceDir;
[GUI] MenuItem _extractRomFs;
[GUI] MenuItem _extractExeFs;
[GUI] MenuItem _extractLogo;
#pragma warning restore CS0649
#pragma warning restore IDE0044
- public GameTableContextMenu(ListStore gameTableStore, TreeIter rowIter, VirtualFileSystem virtualFileSystem)
- : this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, rowIter, virtualFileSystem) { }
+ public GameTableContextMenu(ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem)
+ : this(new Builder("Ryujinx.Ui.GameTableContextMenu.glade"), gameTableStore, controlData, rowIter, virtualFileSystem) { }
- private GameTableContextMenu(Builder builder, ListStore gameTableStore, TreeIter rowIter, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_contextMenu").Handle)
+ private GameTableContextMenu(Builder builder, ListStore gameTableStore, BlitStruct<ApplicationControlProperty> controlData, TreeIter rowIter, VirtualFileSystem virtualFileSystem) : base(builder.GetObject("_contextMenu").Handle)
{
builder.Autoconnect(this);
- _openSaveDir.Activated += OpenSaveDir_Clicked;
+ _openSaveUserDir.Activated += OpenSaveUserDir_Clicked;
+ _openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked;
+
+ _openSaveUserDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.UserAccountSaveDataSize > 0;
+ _openSaveDeviceDir.Sensitive = !Util.IsEmpty(controlData.ByteSpan) && controlData.Value.DeviceSaveDataSize > 0;
+
_extractRomFs.Activated += ExtractRomFs_Clicked;
_extractExeFs.Activated += ExtractExeFs_Clicked;
_extractLogo.Activated += ExtractLogo_Clicked;
@@ -52,6 +64,7 @@ namespace Ryujinx.Ui
_gameTableStore = gameTableStore;
_rowIter = rowIter;
_virtualFileSystem = virtualFileSystem;
+ _controlData = controlData;
string ext = System.IO.Path.GetExtension(_gameTableStore.GetValue(_rowIter, 9).ToString()).ToLower();
if (ext != ".nca" && ext != ".nsp" && ext != ".pfs0" && ext != ".xci")
@@ -62,21 +75,10 @@ namespace Ryujinx.Ui
}
}
- private bool TryFindSaveData(string titleName, string titleIdText, out ulong saveDataId)
+ private bool TryFindSaveData(string titleName, ulong titleId, BlitStruct<ApplicationControlProperty> controlHolder, SaveDataFilter filter, out ulong saveDataId)
{
saveDataId = default;
- if (!ulong.TryParse(titleIdText, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleId))
- {
- GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
-
- return false;
- }
-
- SaveDataFilter filter = new SaveDataFilter();
- filter.SetUserId(new UserId(1, 0));
- filter.SetProgramId(new TitleId(titleId));
-
Result result = _virtualFileSystem.FsClient.FindSaveDataWithFilter(out SaveDataInfo saveDataInfo, SaveDataSpaceId.User, ref filter);
if (ResultFs.TargetNotFound.Includes(result))
@@ -84,10 +86,10 @@ namespace Ryujinx.Ui
// Savedata was not found. Ask the user if they want to create it
using MessageDialog messageDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Question, ButtonsType.YesNo, null)
{
- Title = "Ryujinx",
- Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
- Text = $"There is no savedata for {titleName} [{titleId:x16}]",
- SecondaryText = "Would you like to create savedata for this game?",
+ Title = "Ryujinx",
+ Icon = new Gdk.Pixbuf(Assembly.GetExecutingAssembly(), "Ryujinx.Ui.assets.Icon.png"),
+ Text = $"There is no savedata for {titleName} [{titleId:x16}]",
+ SecondaryText = "Would you like to create savedata for this game?",
WindowPosition = WindowPosition.Center
};
@@ -96,7 +98,25 @@ namespace Ryujinx.Ui
return false;
}
- result = _virtualFileSystem.FsClient.CreateSaveData(new TitleId(titleId), new UserId(1, 0), new TitleId(titleId), 0, 0, 0);
+ ref ApplicationControlProperty control = ref controlHolder.Value;
+
+ if (LibHac.Util.IsEmpty(controlHolder.ByteSpan))
+ {
+ // If the current application doesn't have a loaded control property, create a dummy one
+ // and set the savedata sizes so a user savedata will be created.
+ control = ref new BlitStruct<ApplicationControlProperty>(1).Value;
+
+ // The set sizes don't actually matter as long as they're non-zero because we use directory savedata.
+ control.UserAccountSaveDataSize = 0x4000;
+ control.UserAccountSaveDataJournalSize = 0x4000;
+
+ Logger.PrintWarning(LogClass.Application,
+ "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games.");
+ }
+
+ Uid user = new Uid(1, 0);
+
+ result = EnsureApplicationSaveData(_virtualFileSystem.FsClient, out _, new TitleId(titleId), ref control, ref user);
if (result.IsFailure())
{
@@ -392,12 +412,29 @@ namespace Ryujinx.Ui
}
// Events
- private void OpenSaveDir_Clicked(object sender, EventArgs args)
+ private void OpenSaveUserDir_Clicked(object sender, EventArgs args)
{
string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
- string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
+ string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
- if (!TryFindSaveData(titleName, titleId, out ulong saveDataId))
+ if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
+ {
+ GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
+
+ return;
+ }
+
+ SaveDataFilter filter = new SaveDataFilter();
+ filter.SetUserId(new UserId(1, 0));
+
+ OpenSaveDir(titleName, titleIdNumber, filter);
+ }
+
+ private void OpenSaveDir(string titleName, ulong titleId, SaveDataFilter filter)
+ {
+ filter.SetProgramId(new TitleId(titleId));
+
+ if (!TryFindSaveData(titleName, titleId, _controlData, filter, out ulong saveDataId))
{
return;
}
@@ -412,6 +449,25 @@ namespace Ryujinx.Ui
});
}
+ // Events
+ private void OpenSaveDeviceDir_Clicked(object sender, EventArgs args)
+ {
+ string titleName = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[0];
+ string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
+
+ if (!ulong.TryParse(titleId, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong titleIdNumber))
+ {
+ GtkDialog.CreateErrorDialog("UI error: The selected game did not have a valid title ID");
+
+ return;
+ }
+
+ SaveDataFilter filter = new SaveDataFilter();
+ filter.SetSaveDataType(SaveDataType.Device);
+
+ OpenSaveDir(titleName, titleIdNumber, filter);
+ }
+
private void ExtractRomFs_Clicked(object sender, EventArgs args)
{
ExtractSection(NcaSectionType.Data);
diff --git a/Ryujinx/Ui/GameTableContextMenu.glade b/Ryujinx/Ui/GameTableContextMenu.glade
index 13bade4e..96b49339 100644
--- a/Ryujinx/Ui/GameTableContextMenu.glade
+++ b/Ryujinx/Ui/GameTableContextMenu.glade
@@ -6,11 +6,20 @@
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
- <object class="GtkMenuItem" id="_openSaveDir">
+ <object class="GtkMenuItem" id="_openSaveUserDir">
<property name="visible">True</property>
<property name="can_focus">False</property>
- <property name="tooltip_text" translatable="yes">Open the folder where saves for the application is loaded</property>
- <property name="label" translatable="yes">Open Save Directory</property>
+ <property name="tooltip_text" translatable="yes">Open the folder where the User save for the application is loaded</property>
+ <property name="label" translatable="yes">Open User Save Directory</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="_openSaveDeviceDir">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Open the folder where the Device save for the application is loaded</property>
+ <property name="label" translatable="yes">Open Device Save Directory</property>
<property name="use_underline">True</property>
</object>
</child>
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index f658bea4..05c9686d 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -1,5 +1,7 @@
using Gtk;
using JsonPrettyPrinterPlus;
+using LibHac.Common;
+using LibHac.Ns;
using Ryujinx.Audio;
using Ryujinx.Common.Logging;
using Ryujinx.Configuration;
@@ -9,6 +11,7 @@ using Ryujinx.Graphics.OpenGL;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.FileSystem.Content;
using System;
+using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
@@ -156,7 +159,8 @@ namespace Ryujinx.Ui
typeof(string),
typeof(string),
typeof(string),
- typeof(string));
+ typeof(string),
+ typeof(BlitStruct<ApplicationControlProperty>));
_tableStore.SetSortFunc(5, TimePlayedSort);
_tableStore.SetSortFunc(6, LastPlayedSort);
@@ -580,7 +584,8 @@ namespace Ryujinx.Ui
args.AppData.LastPlayed,
args.AppData.FileExtension,
args.AppData.FileSize,
- args.AppData.Path);
+ args.AppData.Path,
+ args.AppData.ControlHolder);
});
}
@@ -653,7 +658,9 @@ namespace Ryujinx.Ui
if (treeIter.UserData == IntPtr.Zero) return;
- GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, treeIter, _virtualFileSystem);
+ BlitStruct<ApplicationControlProperty> controlData = (BlitStruct<ApplicationControlProperty>)_tableStore.GetValue(treeIter, 10);
+
+ GameTableContextMenu contextMenu = new GameTableContextMenu(_tableStore, controlData, treeIter, _virtualFileSystem);
contextMenu.ShowAll();
contextMenu.PopupAtPointer(null);
}