2020-04-12 17:02:37 -04:00
using Gtk ;
using LibHac ;
using LibHac.Common ;
using LibHac.Fs ;
2020-09-01 16:08:59 -04:00
using LibHac.Fs.Fsa ;
2020-04-12 17:02:37 -04:00
using LibHac.FsSystem ;
using LibHac.FsSystem.NcaUtils ;
using LibHac.Ns ;
using Ryujinx.Common.Configuration ;
2020-05-02 19:43:22 -04:00
using Ryujinx.Common.Logging ;
2020-04-12 17:02:37 -04:00
using Ryujinx.HLE.FileSystem ;
2020-09-20 23:45:30 -04:00
using Ryujinx.HLE.HOS ;
2020-04-12 17:02:37 -04:00
using System ;
using System.Collections.Generic ;
using System.IO ;
2020-12-01 17:51:49 -05:00
using System.Linq ;
2020-06-22 20:32:07 -04:00
using System.Text ;
2020-04-12 17:02:37 -04:00
2020-06-22 20:32:07 -04:00
using GUI = Gtk . Builder . ObjectAttribute ;
2020-04-30 08:07:41 -04:00
using JsonHelper = Ryujinx . Common . Utilities . JsonHelper ;
2020-04-12 17:02:37 -04:00
namespace Ryujinx.Ui
{
public class TitleUpdateWindow : Window
{
private readonly VirtualFileSystem _virtualFileSystem ;
2020-06-22 20:32:07 -04:00
private readonly string _titleId ;
private readonly string _updateJsonPath ;
2020-04-12 17:02:37 -04:00
2020-06-22 20:32:07 -04:00
private TitleUpdateMetadata _titleUpdateWindowData ;
private Dictionary < RadioButton , string > _radioButtonToPathDictionary ;
2020-04-12 17:02:37 -04:00
2020-05-02 19:43:22 -04:00
#pragma warning disable CS0649 , IDE0044
2020-04-12 17:02:37 -04:00
[GUI] Label _baseTitleInfoLabel ;
[GUI] Box _availableUpdatesBox ;
[GUI] RadioButton _noUpdateRadioButton ;
2020-05-02 19:43:22 -04:00
#pragma warning restore CS0649 , IDE0044
2020-04-12 17:02:37 -04:00
public TitleUpdateWindow ( string titleId , string titleName , VirtualFileSystem virtualFileSystem ) : this ( new Builder ( "Ryujinx.Ui.TitleUpdateWindow.glade" ) , titleId , titleName , virtualFileSystem ) { }
private TitleUpdateWindow ( Builder builder , string titleId , string titleName , VirtualFileSystem virtualFileSystem ) : base ( builder . GetObject ( "_titleUpdateWindow" ) . Handle )
{
builder . Autoconnect ( this ) ;
2020-06-22 20:32:07 -04:00
_titleId = titleId ;
_virtualFileSystem = virtualFileSystem ;
2020-08-30 12:51:53 -04:00
_updateJsonPath = System . IO . Path . Combine ( AppDataManager . GamesDirPath , _titleId , "updates.json" ) ;
2020-06-22 20:32:07 -04:00
_radioButtonToPathDictionary = new Dictionary < RadioButton , string > ( ) ;
2020-04-12 17:02:37 -04:00
try
{
2020-06-22 20:32:07 -04:00
_titleUpdateWindowData = JsonHelper . DeserializeFromFile < TitleUpdateMetadata > ( _updateJsonPath ) ;
2020-04-12 17:02:37 -04:00
}
catch
{
_titleUpdateWindowData = new TitleUpdateMetadata
{
Selected = "" ,
Paths = new List < string > ( )
} ;
}
2020-06-22 20:32:07 -04:00
_baseTitleInfoLabel . Text = $"Updates Available for {titleName} [{titleId.ToUpper()}]" ;
2020-12-01 17:51:49 -05:00
_noUpdateRadioButton . Active = true ;
2020-04-12 17:02:37 -04:00
foreach ( string path in _titleUpdateWindowData . Paths )
{
2020-05-02 19:43:22 -04:00
AddUpdate ( path , false ) ;
2020-04-12 17:02:37 -04:00
}
2020-12-01 17:51:49 -05:00
foreach ( ( RadioButton update , var _ ) in _radioButtonToPathDictionary . Where ( keyValuePair = > keyValuePair . Value = = _titleUpdateWindowData . Selected ) )
2020-04-12 17:02:37 -04:00
{
2020-12-01 17:51:49 -05:00
update . Active = true ;
2020-04-12 17:02:37 -04:00
}
}
2020-05-02 19:43:22 -04:00
private void AddUpdate ( string path , bool showErrorDialog = true )
2020-04-12 17:02:37 -04:00
{
if ( File . Exists ( path ) )
{
using ( FileStream file = new FileStream ( path , FileMode . Open , FileAccess . Read ) )
{
PartitionFileSystem nsp = new PartitionFileSystem ( file . AsStorage ( ) ) ;
2020-09-20 23:45:30 -04:00
try
2020-04-12 17:02:37 -04:00
{
2020-09-20 23:45:30 -04:00
( Nca patchNca , Nca controlNca ) = ApplicationLoader . GetGameUpdateDataFromPartition ( _virtualFileSystem , nsp , _titleId , 0 ) ;
2020-04-12 17:02:37 -04:00
2020-09-20 23:45:30 -04:00
if ( controlNca ! = null & & patchNca ! = null )
2020-04-12 17:02:37 -04:00
{
2020-09-20 23:45:30 -04:00
ApplicationControlProperty controlData = new ApplicationControlProperty ( ) ;
controlNca . OpenFileSystem ( NcaSectionType . Data , IntegrityCheckLevel . None ) . OpenFile ( out IFile nacpFile , "/control.nacp" . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
nacpFile . Read ( out _ , 0 , SpanHelpers . AsByteSpan ( ref controlData ) , ReadOption . None ) . ThrowIfFailure ( ) ;
RadioButton radioButton = new RadioButton ( $"Version {controlData.DisplayVersion.ToString()} - {path}" ) ;
radioButton . JoinGroup ( _noUpdateRadioButton ) ;
_availableUpdatesBox . Add ( radioButton ) ;
_radioButtonToPathDictionary . Add ( radioButton , path ) ;
radioButton . Show ( ) ;
radioButton . Active = true ;
2020-05-02 19:43:22 -04:00
}
2020-09-20 23:45:30 -04:00
else
2020-05-02 19:43:22 -04:00
{
2020-09-20 23:45:30 -04:00
GtkDialog . CreateErrorDialog ( "The specified file does not contain an update for the selected title!" ) ;
2020-04-12 17:02:37 -04:00
}
2020-09-20 23:45:30 -04:00
}
catch ( InvalidDataException exception )
{
Logger . Error ? . Print ( LogClass . Application , $"{exception.Message}. Errored File: {path}" ) ;
2020-05-02 19:43:22 -04:00
2020-09-20 23:45:30 -04:00
if ( showErrorDialog )
{
GtkDialog . CreateInfoDialog ( "Ryujinx - Error" , "Add Update Failed!" , "The NCA header content type check has failed. This is usually because the header key is incorrect or missing." ) ;
}
}
catch ( MissingKeyException exception )
{
Logger . Error ? . Print ( LogClass . Application , $"Your key set is missing a key with the name: {exception.Name}. Errored File: {path}" ) ;
2020-05-02 19:43:22 -04:00
2020-09-20 23:45:30 -04:00
if ( showErrorDialog )
{
GtkDialog . CreateInfoDialog ( "Ryujinx - Error" , "Add Update Failed!" , $"Your key set is missing a key with the name: {exception.Name}" ) ;
2020-04-12 17:02:37 -04:00
}
}
}
}
}
2020-12-01 17:51:49 -05:00
private void RemoveUpdates ( bool removeSelectedOnly = false )
{
foreach ( RadioButton radioButton in _noUpdateRadioButton . Group )
{
if ( radioButton . Label ! = "No Update" & & ( ! removeSelectedOnly | | radioButton . Active ) )
{
_availableUpdatesBox . Remove ( radioButton ) ;
_radioButtonToPathDictionary . Remove ( radioButton ) ;
radioButton . Dispose ( ) ;
}
}
}
2020-04-12 17:02:37 -04:00
private void AddButton_Clicked ( object sender , EventArgs args )
{
FileChooserDialog fileChooser = new FileChooserDialog ( "Select update files" , this , FileChooserAction . Open , "Cancel" , ResponseType . Cancel , "Add" , ResponseType . Accept )
{
SelectMultiple = true ,
Filter = new FileFilter ( )
} ;
fileChooser . SetPosition ( WindowPosition . Center ) ;
fileChooser . Filter . AddPattern ( "*.nsp" ) ;
if ( fileChooser . Run ( ) = = ( int ) ResponseType . Accept )
{
foreach ( string path in fileChooser . Filenames )
{
AddUpdate ( path ) ;
}
}
fileChooser . Dispose ( ) ;
}
private void RemoveButton_Clicked ( object sender , EventArgs args )
{
2020-12-01 17:51:49 -05:00
RemoveUpdates ( true ) ;
}
private void RemoveAllButton_Clicked ( object sender , EventArgs args )
{
RemoveUpdates ( ) ;
2020-04-12 17:02:37 -04:00
}
private void SaveButton_Clicked ( object sender , EventArgs args )
{
_titleUpdateWindowData . Paths . Clear ( ) ;
2020-12-01 17:51:49 -05:00
_titleUpdateWindowData . Selected = "" ;
2020-04-12 17:02:37 -04:00
foreach ( string paths in _radioButtonToPathDictionary . Values )
{
_titleUpdateWindowData . Paths . Add ( paths ) ;
}
foreach ( RadioButton radioButton in _noUpdateRadioButton . Group )
{
if ( radioButton . Active )
{
_titleUpdateWindowData . Selected = _radioButtonToPathDictionary . TryGetValue ( radioButton , out string updatePath ) ? updatePath : "" ;
}
}
2020-06-22 20:32:07 -04:00
using ( FileStream dlcJsonStream = File . Create ( _updateJsonPath , 4096 , FileOptions . WriteThrough ) )
{
dlcJsonStream . Write ( Encoding . UTF8 . GetBytes ( JsonHelper . Serialize ( _titleUpdateWindowData , true ) ) ) ;
}
2020-04-12 17:02:37 -04:00
MainWindow . UpdateGameTable ( ) ;
Dispose ( ) ;
}
private void CancelButton_Clicked ( object sender , EventArgs args )
{
Dispose ( ) ;
}
}
}