2020-04-12 17:02:37 -04:00
using Gtk ;
using LibHac ;
using LibHac.Common ;
using LibHac.Fs ;
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 ;
using System ;
using System.Collections.Generic ;
using System.IO ;
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 ;
_updateJsonPath = System . IO . Path . Combine ( _virtualFileSystem . GetBasePath ( ) , "games" , _titleId , "updates.json" ) ;
_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-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
}
_noUpdateRadioButton . Active = true ;
foreach ( KeyValuePair < RadioButton , string > keyValuePair in _radioButtonToPathDictionary )
{
if ( keyValuePair . Value = = _titleUpdateWindowData . Selected )
{
keyValuePair . Key . Active = true ;
}
}
}
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-05-15 02:16:46 -04:00
_virtualFileSystem . ImportTickets ( nsp ) ;
2020-04-12 17:02:37 -04:00
foreach ( DirectoryEntryEx fileEntry in nsp . EnumerateEntries ( "/" , "*.nca" ) )
{
nsp . OpenFile ( out IFile ncaFile , fileEntry . FullPath . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
2020-05-02 19:43:22 -04:00
try
2020-04-12 17:02:37 -04:00
{
2020-05-02 19:43:22 -04:00
Nca nca = new Nca ( _virtualFileSystem . KeySet , ncaFile . AsStorage ( ) ) ;
if ( $"{nca.Header.TitleId.ToString(" x16 ")[..^3]}000" = = _titleId )
2020-04-12 17:02:37 -04:00
{
2020-05-02 19:43:22 -04:00
if ( nca . Header . ContentType = = NcaContentType . Control )
{
ApplicationControlProperty controlData = new ApplicationControlProperty ( ) ;
2020-04-12 17:02:37 -04:00
2020-05-02 19:43:22 -04:00
nca . 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 ( ) ;
2020-04-12 17:02:37 -04:00
2020-05-02 19:43:22 -04:00
RadioButton radioButton = new RadioButton ( $"Version {controlData.DisplayVersion.ToString()} - {path}" ) ;
radioButton . JoinGroup ( _noUpdateRadioButton ) ;
2020-04-12 17:02:37 -04:00
2020-05-02 19:43:22 -04:00
_availableUpdatesBox . Add ( radioButton ) ;
_radioButtonToPathDictionary . Add ( radioButton , path ) ;
radioButton . Show ( ) ;
radioButton . Active = true ;
}
}
else
{
GtkDialog . CreateErrorDialog ( "The specified file does not contain an update for the selected title!" ) ;
break ;
}
}
catch ( InvalidDataException exception )
{
Logger . PrintError ( LogClass . Application , $"{exception.Message}. Errored File: {path}" ) ;
2020-04-12 17:02:37 -04:00
2020-05-02 19:43:22 -04:00
if ( showErrorDialog )
{
2020-05-02 22:00:53 -04:00
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." ) ;
2020-04-12 17:02:37 -04:00
}
2020-05-02 19:43:22 -04:00
break ;
2020-04-12 17:02:37 -04:00
}
2020-05-02 19:43:22 -04:00
catch ( MissingKeyException exception )
2020-04-12 17:02:37 -04:00
{
2020-05-02 19:43:22 -04:00
Logger . PrintError ( LogClass . Application , $"Your key set is missing a key with the name: {exception.Name}. Errored File: {path}" ) ;
if ( showErrorDialog )
{
2020-05-02 22:00:53 -04:00
GtkDialog . CreateInfoDialog ( "Ryujinx - Error" , "Add Update Failed!" , $"Your key set is missing a key with the name: {exception.Name}" ) ;
2020-05-02 19:43:22 -04:00
}
2020-04-12 17:02:37 -04:00
break ;
}
}
}
}
}
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 )
{
foreach ( RadioButton radioButton in _noUpdateRadioButton . Group )
{
if ( radioButton . Label ! = "No Update" & & radioButton . Active )
{
_availableUpdatesBox . Remove ( radioButton ) ;
_radioButtonToPathDictionary . Remove ( radioButton ) ;
radioButton . Dispose ( ) ;
}
}
}
private void SaveButton_Clicked ( object sender , EventArgs args )
{
_titleUpdateWindowData . Paths . Clear ( ) ;
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 ( ) ;
}
}
}