2019-12-22 03:49:51 +01:00
using Gtk ;
2020-01-05 12:49:44 +01:00
using LibHac ;
2020-03-25 18:09:38 +01:00
using LibHac.Account ;
2020-02-08 19:22:45 +01:00
using LibHac.Common ;
2020-01-05 12:49:44 +01:00
using LibHac.Fs ;
2020-09-01 22:08:59 +02:00
using LibHac.Fs.Fsa ;
2020-01-05 12:49:44 +01:00
using LibHac.Fs.Shim ;
2020-02-08 19:22:45 +01:00
using LibHac.FsSystem ;
using LibHac.FsSystem.NcaUtils ;
2020-01-05 12:49:44 +01:00
using LibHac.Ncm ;
2020-03-25 18:09:38 +01:00
using LibHac.Ns ;
2020-04-23 21:21:32 +02:00
using Ryujinx.Common.Configuration ;
2020-02-08 19:22:45 +01:00
using Ryujinx.Common.Logging ;
2019-12-22 03:49:51 +01:00
using Ryujinx.HLE.FileSystem ;
2020-09-21 05:45:30 +02:00
using Ryujinx.HLE.HOS ;
2021-04-13 03:16:43 +02:00
using Ryujinx.HLE.HOS.Services.Account.Acc ;
2021-01-08 09:14:13 +01:00
using Ryujinx.Ui.Helper ;
using Ryujinx.Ui.Windows ;
2019-12-22 03:49:51 +01:00
using System ;
2020-02-08 19:22:45 +01:00
using System.Buffers ;
2020-08-13 19:31:13 +02:00
using System.Collections.Generic ;
2020-01-05 12:49:44 +01:00
using System.Globalization ;
2019-12-22 03:49:51 +01:00
using System.IO ;
2021-01-18 21:33:58 +01:00
using System.Reflection ;
2020-02-08 19:22:45 +01:00
using System.Threading ;
2020-06-23 02:32:07 +02:00
2020-03-25 18:09:38 +01:00
using static LibHac . Fs . ApplicationSaveDataManagement ;
2019-12-22 03:49:51 +01:00
2021-01-08 09:14:13 +01:00
namespace Ryujinx.Ui.Widgets
2019-12-22 03:49:51 +01:00
{
2021-01-08 09:14:13 +01:00
public partial class GameTableContextMenu : Menu
2019-12-22 03:49:51 +01:00
{
2021-01-08 09:14:13 +01:00
private readonly MainWindow _parent ;
private readonly VirtualFileSystem _virtualFileSystem ;
2021-04-13 03:16:43 +02:00
private readonly AccountManager _accountManager ;
2021-08-12 23:56:24 +02:00
private readonly HorizonClient _horizonClient ;
2020-06-23 02:32:07 +02:00
private readonly BlitStruct < ApplicationControlProperty > _controlData ;
2019-12-22 03:49:51 +01:00
2021-01-08 09:14:13 +01:00
private readonly string _titleFilePath ;
private readonly string _titleName ;
private readonly string _titleIdText ;
private readonly ulong _titleId ;
2020-06-23 02:32:07 +02:00
private MessageDialog _dialog ;
private bool _cancel ;
2019-12-22 03:49:51 +01:00
2021-08-12 23:56:24 +02:00
public GameTableContextMenu ( MainWindow parent , VirtualFileSystem virtualFileSystem , AccountManager accountManager , HorizonClient horizonClient , string titleFilePath , string titleName , string titleId , BlitStruct < ApplicationControlProperty > controlData )
2020-06-23 02:32:07 +02:00
{
2021-01-08 09:14:13 +01:00
_parent = parent ;
2020-06-23 02:32:07 +02:00
2021-01-08 09:14:13 +01:00
InitializeComponent ( ) ;
2020-06-23 02:32:07 +02:00
2021-01-08 09:14:13 +01:00
_virtualFileSystem = virtualFileSystem ;
2021-04-13 03:16:43 +02:00
_accountManager = accountManager ;
2021-08-12 23:56:24 +02:00
_horizonClient = horizonClient ;
2021-01-08 09:14:13 +01:00
_titleFilePath = titleFilePath ;
_titleName = titleName ;
_titleIdText = titleId ;
_controlData = controlData ;
2020-06-23 02:32:07 +02:00
2021-01-08 09:14:13 +01:00
if ( ! ulong . TryParse ( _titleIdText , NumberStyles . HexNumber , CultureInfo . InvariantCulture , out _titleId ) )
2020-06-23 02:32:07 +02:00
{
2021-01-08 09:14:13 +01:00
GtkDialog . CreateErrorDialog ( "The selected game did not have a valid Title Id" ) ;
2020-07-04 01:04:04 +02:00
2021-01-08 09:14:13 +01:00
return ;
}
2020-11-13 00:15:34 +01:00
2021-08-12 23:56:24 +02:00
_openSaveUserDirMenuItem . Sensitive = ! Utilities . IsZeros ( controlData . ByteSpan ) & & controlData . Value . UserAccountSaveDataSize > 0 ;
_openSaveDeviceDirMenuItem . Sensitive = ! Utilities . IsZeros ( controlData . ByteSpan ) & & controlData . Value . DeviceSaveDataSize > 0 ;
_openSaveBcatDirMenuItem . Sensitive = ! Utilities . IsZeros ( controlData . ByteSpan ) & & controlData . Value . BcatDeliveryCacheStorageSize > 0 ;
2020-11-13 00:15:34 +01:00
2021-01-08 09:14:13 +01:00
string fileExt = System . IO . Path . GetExtension ( _titleFilePath ) . ToLower ( ) ;
bool hasNca = fileExt = = ".nca" | | fileExt = = ".nsp" | | fileExt = = ".pfs0" | | fileExt = = ".xci" ;
2020-11-13 00:15:34 +01:00
2021-01-08 09:14:13 +01:00
_extractRomFsMenuItem . Sensitive = hasNca ;
_extractExeFsMenuItem . Sensitive = hasNca ;
_extractLogoMenuItem . Sensitive = hasNca ;
2020-11-13 00:15:34 +01:00
2021-01-08 09:14:13 +01:00
PopupAtPointer ( null ) ;
2020-01-05 12:49:44 +01:00
}
2020-03-25 18:09:38 +01:00
private bool TryFindSaveData ( string titleName , ulong titleId , BlitStruct < ApplicationControlProperty > controlHolder , SaveDataFilter filter , out ulong saveDataId )
2020-01-05 12:49:44 +01:00
{
saveDataId = default ;
2021-08-12 23:56:24 +02:00
Result result = _horizonClient . Fs . FindSaveDataWithFilter ( out SaveDataInfo saveDataInfo , SaveDataSpaceId . User , in filter ) ;
2020-01-05 12:49:44 +01:00
2020-03-03 15:07:06 +01:00
if ( ResultFs . TargetNotFound . Includes ( result ) )
2020-01-05 12:49:44 +01:00
{
// 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 )
2019-12-22 03:49:51 +01:00
{
2020-04-12 23:02:37 +02:00
Title = "Ryujinx" ,
2021-01-18 21:33:58 +01:00
Icon = new Gdk . Pixbuf ( Assembly . GetExecutingAssembly ( ) , "Ryujinx.Ui.Resources.Logo_Ryujinx.png" ) ,
2020-04-12 23:02:37 +02:00
Text = $"There is no savedata for {titleName} [{titleId:x16}]" ,
SecondaryText = "Would you like to create savedata for this game?" ,
2019-12-22 03:49:51 +01:00
WindowPosition = WindowPosition . Center
} ;
2020-01-05 12:49:44 +01:00
if ( messageDialog . Run ( ) ! = ( int ) ResponseType . Yes )
2019-12-22 03:49:51 +01:00
{
2020-01-05 12:49:44 +01:00
return false ;
2019-12-22 03:49:51 +01:00
}
2020-01-05 12:49:44 +01:00
2020-03-25 18:09:38 +01:00
ref ApplicationControlProperty control = ref controlHolder . Value ;
2021-08-12 23:56:24 +02:00
if ( Utilities . IsZeros ( controlHolder . ByteSpan ) )
2020-03-25 18:09:38 +01:00
{
// 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.
2020-04-12 23:02:37 +02:00
control . UserAccountSaveDataSize = 0x4000 ;
2020-03-25 18:09:38 +01:00
control . UserAccountSaveDataJournalSize = 0x4000 ;
2021-01-08 09:14:13 +01:00
Logger . Warning ? . Print ( LogClass . Application , "No control file was found for this game. Using a dummy one instead. This may cause inaccuracies in some games." ) ;
2020-03-25 18:09:38 +01:00
}
2021-04-23 22:26:31 +02:00
Uid user = new Uid ( ( ulong ) _accountManager . LastOpenedUser . UserId . High , ( ulong ) _accountManager . LastOpenedUser . UserId . Low ) ;
2020-03-25 18:09:38 +01:00
2021-08-12 23:56:24 +02:00
result = EnsureApplicationSaveData ( _horizonClient . Fs , out _ , new LibHac . Ncm . ApplicationId ( titleId ) , ref control , ref user ) ;
2020-01-05 12:49:44 +01:00
if ( result . IsFailure ( ) )
2019-12-22 03:49:51 +01:00
{
2020-01-05 12:49:44 +01:00
GtkDialog . CreateErrorDialog ( $"There was an error creating the specified savedata: {result.ToStringWithName()}" ) ;
2019-12-22 03:49:51 +01:00
2020-01-05 12:49:44 +01:00
return false ;
2019-12-22 03:49:51 +01:00
}
2020-01-05 12:49:44 +01:00
// Try to find the savedata again after creating it
2021-08-12 23:56:24 +02:00
result = _horizonClient . Fs . FindSaveDataWithFilter ( out saveDataInfo , SaveDataSpaceId . User , in filter ) ;
2019-12-22 03:49:51 +01:00
}
2020-01-05 12:49:44 +01:00
if ( result . IsSuccess ( ) )
2019-12-22 03:49:51 +01:00
{
2020-01-05 12:49:44 +01:00
saveDataId = saveDataInfo . SaveDataId ;
return true ;
}
GtkDialog . CreateErrorDialog ( $"There was an error finding the specified savedata: {result.ToStringWithName()}" ) ;
return false ;
}
2021-01-08 09:14:13 +01:00
private void OpenSaveDir ( SaveDataFilter saveDataFilter )
2020-01-05 12:49:44 +01:00
{
2021-01-08 09:14:13 +01:00
saveDataFilter . SetProgramId ( new ProgramId ( _titleId ) ) ;
if ( ! TryFindSaveData ( _titleName , _titleId , _controlData , saveDataFilter , out ulong saveDataId ) )
{
return ;
}
2020-01-24 17:01:21 +01:00
string saveRootPath = System . IO . Path . Combine ( _virtualFileSystem . GetNandPath ( ) , $"user/save/{saveDataId:x16}" ) ;
2020-01-05 12:49:44 +01:00
if ( ! Directory . Exists ( saveRootPath ) )
{
// Inconsistent state. Create the directory
Directory . CreateDirectory ( saveRootPath ) ;
}
string committedPath = System . IO . Path . Combine ( saveRootPath , "0" ) ;
2020-02-08 19:22:45 +01:00
string workingPath = System . IO . Path . Combine ( saveRootPath , "1" ) ;
2020-01-05 12:49:44 +01:00
// If the committed directory exists, that path will be loaded the next time the savedata is mounted
if ( Directory . Exists ( committedPath ) )
{
2021-01-08 09:14:13 +01:00
OpenHelper . OpenFolder ( committedPath ) ;
2020-01-05 12:49:44 +01:00
}
2021-01-08 09:14:13 +01:00
else
2020-01-05 12:49:44 +01:00
{
2021-01-08 09:14:13 +01:00
// If the working directory exists and the committed directory doesn't,
// the working directory will be loaded the next time the savedata is mounted
if ( ! Directory . Exists ( workingPath ) )
{
Directory . CreateDirectory ( workingPath ) ;
}
2020-01-05 12:49:44 +01:00
2021-01-08 09:14:13 +01:00
OpenHelper . OpenFolder ( workingPath ) ;
}
2019-12-22 03:49:51 +01:00
}
2020-02-08 19:22:45 +01:00
2020-09-21 05:45:30 +02:00
private void ExtractSection ( NcaSectionType ncaSectionType , int programIndex = 0 )
2020-02-08 19:22:45 +01:00
{
2021-09-14 23:52:08 +02:00
FileChooserNative fileChooser = new FileChooserNative ( "Choose the folder to extract into" , null , FileChooserAction . SelectFolder , "Extract" , "Cancel" ) ;
2020-02-08 19:22:45 +01:00
2021-01-08 09:14:13 +01:00
ResponseType response = ( ResponseType ) fileChooser . Run ( ) ;
string destination = fileChooser . Filename ;
2020-02-08 19:22:45 +01:00
fileChooser . Dispose ( ) ;
2021-01-08 09:14:13 +01:00
if ( response = = ResponseType . Accept )
2020-02-08 19:22:45 +01:00
{
Thread extractorThread = new Thread ( ( ) = >
{
Gtk . Application . Invoke ( delegate
{
_dialog = new MessageDialog ( null , DialogFlags . DestroyWithParent , MessageType . Info , ButtonsType . Cancel , null )
{
Title = "Ryujinx - NCA Section Extractor" ,
2021-01-18 21:33:58 +01:00
Icon = new Gdk . Pixbuf ( Assembly . GetExecutingAssembly ( ) , "Ryujinx.Ui.Resources.Logo_Ryujinx.png" ) ,
2021-01-08 09:14:13 +01:00
SecondaryText = $"Extracting {ncaSectionType} section from {System.IO.Path.GetFileName(_titleFilePath)}..." ,
2020-02-08 19:22:45 +01:00
WindowPosition = WindowPosition . Center
} ;
2020-04-23 21:21:32 +02:00
2020-02-08 19:22:45 +01:00
int dialogResponse = _dialog . Run ( ) ;
if ( dialogResponse = = ( int ) ResponseType . Cancel | | dialogResponse = = ( int ) ResponseType . DeleteEvent )
{
_cancel = true ;
_dialog . Dispose ( ) ;
}
} ) ;
2021-01-08 09:14:13 +01:00
using ( FileStream file = new FileStream ( _titleFilePath , FileMode . Open , FileAccess . Read ) )
2020-02-08 19:22:45 +01:00
{
Nca mainNca = null ;
Nca patchNca = null ;
2021-01-08 09:14:13 +01:00
if ( ( System . IO . Path . GetExtension ( _titleFilePath ) . ToLower ( ) = = ".nsp" ) | |
( System . IO . Path . GetExtension ( _titleFilePath ) . ToLower ( ) = = ".pfs0" ) | |
( System . IO . Path . GetExtension ( _titleFilePath ) . ToLower ( ) = = ".xci" ) )
2020-02-08 19:22:45 +01:00
{
PartitionFileSystem pfs ;
2021-01-08 09:14:13 +01:00
if ( System . IO . Path . GetExtension ( _titleFilePath ) = = ".xci" )
2020-02-08 19:22:45 +01:00
{
Xci xci = new Xci ( _virtualFileSystem . KeySet , file . AsStorage ( ) ) ;
pfs = xci . OpenPartition ( XciPartitionType . Secure ) ;
}
else
{
pfs = new PartitionFileSystem ( file . AsStorage ( ) ) ;
}
foreach ( DirectoryEntryEx fileEntry in pfs . EnumerateEntries ( "/" , "*.nca" ) )
{
2020-03-25 09:14:35 +01:00
pfs . OpenFile ( out IFile ncaFile , fileEntry . FullPath . ToU8Span ( ) , OpenMode . Read ) . ThrowIfFailure ( ) ;
2020-02-08 19:22:45 +01:00
Nca nca = new Nca ( _virtualFileSystem . KeySet , ncaFile . AsStorage ( ) ) ;
if ( nca . Header . ContentType = = NcaContentType . Program )
{
int dataIndex = Nca . GetSectionIndexFromType ( NcaSectionType . Data , NcaContentType . Program ) ;
if ( nca . Header . GetFsHeader ( dataIndex ) . IsPatchSection ( ) )
{
patchNca = nca ;
}
else
{
mainNca = nca ;
}
}
}
}
2021-01-08 09:14:13 +01:00
else if ( System . IO . Path . GetExtension ( _titleFilePath ) . ToLower ( ) = = ".nca" )
2020-02-08 19:22:45 +01:00
{
mainNca = new Nca ( _virtualFileSystem . KeySet , file . AsStorage ( ) ) ;
}
if ( mainNca = = null )
{
2021-03-18 23:44:39 +01:00
Logger . Error ? . Print ( LogClass . Application , "Extraction failure. The main NCA is not present in the selected file." ) ;
2020-02-08 19:22:45 +01:00
Gtk . Application . Invoke ( delegate
{
2021-03-18 23:44:39 +01:00
GtkDialog . CreateErrorDialog ( "Extraction failure. The main NCA is not present in the selected file." ) ;
2020-02-08 19:22:45 +01:00
} ) ;
return ;
}
2020-09-21 05:45:30 +02:00
( Nca updatePatchNca , _ ) = ApplicationLoader . GetGameUpdateData ( _virtualFileSystem , mainNca . Header . TitleId . ToString ( "x16" ) , programIndex , out _ ) ;
2020-04-23 21:21:32 +02:00
2020-09-21 05:45:30 +02:00
if ( updatePatchNca ! = null )
{
patchNca = updatePatchNca ;
2020-04-23 21:21:32 +02:00
}
2020-02-08 19:22:45 +01:00
int index = Nca . GetSectionIndexFromType ( ncaSectionType , mainNca . Header . ContentType ) ;
IFileSystem ncaFileSystem = patchNca ! = null ? mainNca . OpenFileSystemWithPatch ( patchNca , index , IntegrityCheckLevel . ErrorOnInvalid )
: mainNca . OpenFileSystem ( index , IntegrityCheckLevel . ErrorOnInvalid ) ;
2021-08-12 23:56:24 +02:00
FileSystemClient fsClient = _horizonClient . Fs ;
2020-02-08 19:22:45 +01:00
2021-01-08 09:14:13 +01:00
string source = DateTime . Now . ToFileTime ( ) . ToString ( ) [ 10. . ] ;
string output = DateTime . Now . ToFileTime ( ) . ToString ( ) [ 10. . ] ;
2020-02-08 19:22:45 +01:00
fsClient . Register ( source . ToU8Span ( ) , ncaFileSystem ) ;
fsClient . Register ( output . ToU8Span ( ) , new LocalFileSystem ( destination ) ) ;
( Result ? resultCode , bool canceled ) = CopyDirectory ( fsClient , $"{source}:/" , $"{output}:/" ) ;
if ( ! canceled )
{
if ( resultCode . Value . IsFailure ( ) )
{
2020-08-04 01:32:53 +02:00
Logger . Error ? . Print ( LogClass . Application , $"LibHac returned error code: {resultCode.Value.ErrorCode}" ) ;
2020-02-08 19:22:45 +01:00
Gtk . Application . Invoke ( delegate
{
_dialog ? . Dispose ( ) ;
GtkDialog . CreateErrorDialog ( "Extraction failed. Read the log file for further information." ) ;
} ) ;
}
else if ( resultCode . Value . IsSuccess ( ) )
{
Gtk . Application . Invoke ( delegate
{
_dialog ? . Dispose ( ) ;
MessageDialog dialog = new MessageDialog ( null , DialogFlags . DestroyWithParent , MessageType . Info , ButtonsType . Ok , null )
{
Title = "Ryujinx - NCA Section Extractor" ,
2021-01-18 21:33:58 +01:00
Icon = new Gdk . Pixbuf ( Assembly . GetExecutingAssembly ( ) , "Ryujinx.Ui.Resources.Logo_Ryujinx.png" ) ,
2021-03-18 23:44:39 +01:00
SecondaryText = "Extraction completed successfully." ,
2020-02-08 19:22:45 +01:00
WindowPosition = WindowPosition . Center
} ;
dialog . Run ( ) ;
dialog . Dispose ( ) ;
} ) ;
}
}
2020-03-25 09:14:35 +01:00
fsClient . Unmount ( source . ToU8Span ( ) ) ;
fsClient . Unmount ( output . ToU8Span ( ) ) ;
2020-02-08 19:22:45 +01:00
}
} ) ;
extractorThread . Name = "GUI.NcaSectionExtractorThread" ;
extractorThread . IsBackground = true ;
extractorThread . Start ( ) ;
}
}
private ( Result ? result , bool canceled ) CopyDirectory ( FileSystemClient fs , string sourcePath , string destPath )
{
2020-03-25 09:14:35 +01:00
Result rc = fs . OpenDirectory ( out DirectoryHandle sourceHandle , sourcePath . ToU8Span ( ) , OpenDirectoryMode . All ) ;
2020-02-08 19:22:45 +01:00
if ( rc . IsFailure ( ) ) return ( rc , false ) ;
using ( sourceHandle )
{
foreach ( DirectoryEntryEx entry in fs . EnumerateEntries ( sourcePath , "*" , SearchOptions . Default ) )
{
if ( _cancel )
{
return ( null , true ) ;
}
string subSrcPath = PathTools . Normalize ( PathTools . Combine ( sourcePath , entry . Name ) ) ;
string subDstPath = PathTools . Normalize ( PathTools . Combine ( destPath , entry . Name ) ) ;
if ( entry . Type = = DirectoryEntryType . Directory )
{
fs . EnsureDirectoryExists ( subDstPath ) ;
( Result ? result , bool canceled ) = CopyDirectory ( fs , subSrcPath , subDstPath ) ;
if ( canceled | | result . Value . IsFailure ( ) )
{
return ( result , canceled ) ;
}
}
if ( entry . Type = = DirectoryEntryType . File )
{
fs . CreateOrOverwriteFile ( subDstPath , entry . Size ) ;
rc = CopyFile ( fs , subSrcPath , subDstPath ) ;
if ( rc . IsFailure ( ) ) return ( rc , false ) ;
}
}
}
return ( Result . Success , false ) ;
}
public Result CopyFile ( FileSystemClient fs , string sourcePath , string destPath )
{
2020-03-25 09:14:35 +01:00
Result rc = fs . OpenFile ( out FileHandle sourceHandle , sourcePath . ToU8Span ( ) , OpenMode . Read ) ;
2020-02-08 19:22:45 +01:00
if ( rc . IsFailure ( ) ) return rc ;
using ( sourceHandle )
{
2020-03-25 09:14:35 +01:00
rc = fs . OpenFile ( out FileHandle destHandle , destPath . ToU8Span ( ) , OpenMode . Write | OpenMode . AllowAppend ) ;
2020-02-08 19:22:45 +01:00
if ( rc . IsFailure ( ) ) return rc ;
using ( destHandle )
{
const int maxBufferSize = 1024 * 1024 ;
rc = fs . GetFileSize ( out long fileSize , sourceHandle ) ;
if ( rc . IsFailure ( ) ) return rc ;
int bufferSize = ( int ) Math . Min ( maxBufferSize , fileSize ) ;
byte [ ] buffer = ArrayPool < byte > . Shared . Rent ( bufferSize ) ;
try
{
for ( long offset = 0 ; offset < fileSize ; offset + = bufferSize )
{
int toRead = ( int ) Math . Min ( fileSize - offset , bufferSize ) ;
Span < byte > buf = buffer . AsSpan ( 0 , toRead ) ;
rc = fs . ReadFile ( out long _ , sourceHandle , offset , buf ) ;
if ( rc . IsFailure ( ) ) return rc ;
2021-08-12 23:56:24 +02:00
rc = fs . WriteFile ( destHandle , offset , buf , WriteOption . None ) ;
2020-02-08 19:22:45 +01:00
if ( rc . IsFailure ( ) ) return rc ;
}
}
finally
{
ArrayPool < byte > . Shared . Return ( buffer ) ;
}
rc = fs . FlushFile ( destHandle ) ;
if ( rc . IsFailure ( ) ) return rc ;
}
}
return Result . Success ;
}
2021-01-08 09:14:13 +01:00
//
2020-02-08 19:22:45 +01:00
// Events
2021-01-08 09:14:13 +01:00
//
2020-03-25 18:09:38 +01:00
private void OpenSaveUserDir_Clicked ( object sender , EventArgs args )
2020-02-08 19:22:45 +01:00
{
2021-01-08 09:14:13 +01:00
SaveDataFilter saveDataFilter = new SaveDataFilter ( ) ;
2021-04-13 03:16:43 +02:00
saveDataFilter . SetUserId ( new LibHac . Fs . UserId ( ( ulong ) _accountManager . LastOpenedUser . UserId . High , ( ulong ) _accountManager . LastOpenedUser . UserId . Low ) ) ;
2020-02-08 19:22:45 +01:00
2021-01-08 09:14:13 +01:00
OpenSaveDir ( saveDataFilter ) ;
2020-02-08 19:22:45 +01:00
}
2020-03-25 18:09:38 +01:00
private void OpenSaveDeviceDir_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
SaveDataFilter saveDataFilter = new SaveDataFilter ( ) ;
saveDataFilter . SetSaveDataType ( SaveDataType . Device ) ;
2020-03-25 18:09:38 +01:00
2021-01-08 09:14:13 +01:00
OpenSaveDir ( saveDataFilter ) ;
2020-03-25 18:09:38 +01:00
}
2020-04-30 06:58:19 +02:00
private void OpenSaveBcatDir_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
SaveDataFilter saveDataFilter = new SaveDataFilter ( ) ;
saveDataFilter . SetSaveDataType ( SaveDataType . Bcat ) ;
2020-04-30 06:58:19 +02:00
2021-01-08 09:14:13 +01:00
OpenSaveDir ( saveDataFilter ) ;
2020-04-30 06:58:19 +02:00
}
2020-04-12 23:02:37 +02:00
private void ManageTitleUpdates_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
new TitleUpdateWindow ( _parent , _virtualFileSystem , _titleIdText , _titleName ) . Show ( ) ;
2020-04-12 23:02:37 +02:00
}
2020-06-23 02:32:07 +02:00
private void ManageDlc_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
new DlcWindow ( _virtualFileSystem , _titleIdText , _titleName ) . Show ( ) ;
2020-06-23 02:32:07 +02:00
}
2020-07-09 06:31:15 +02:00
private void OpenTitleModDir_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
string modsBasePath = _virtualFileSystem . ModLoader . GetModsBasePath ( ) ;
string titleModsPath = _virtualFileSystem . ModLoader . GetTitleDir ( modsBasePath , _titleIdText ) ;
2020-07-09 06:31:15 +02:00
2021-01-08 09:14:13 +01:00
OpenHelper . OpenFolder ( titleModsPath ) ;
2020-07-09 06:31:15 +02:00
}
2020-02-08 19:22:45 +01:00
private void ExtractRomFs_Clicked ( object sender , EventArgs args )
{
ExtractSection ( NcaSectionType . Data ) ;
}
private void ExtractExeFs_Clicked ( object sender , EventArgs args )
{
ExtractSection ( NcaSectionType . Code ) ;
}
private void ExtractLogo_Clicked ( object sender , EventArgs args )
{
ExtractSection ( NcaSectionType . Logo ) ;
}
2020-07-04 01:04:04 +02:00
private void OpenPtcDir_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
string ptcDir = System . IO . Path . Combine ( AppDataManager . GamesDirPath , _titleIdText , "cache" , "cpu" ) ;
2020-07-04 01:04:04 +02:00
string mainPath = System . IO . Path . Combine ( ptcDir , "0" ) ;
string backupPath = System . IO . Path . Combine ( ptcDir , "1" ) ;
if ( ! Directory . Exists ( ptcDir ) )
{
Directory . CreateDirectory ( ptcDir ) ;
Directory . CreateDirectory ( mainPath ) ;
Directory . CreateDirectory ( backupPath ) ;
}
2021-01-08 09:14:13 +01:00
OpenHelper . OpenFolder ( ptcDir ) ;
2020-07-04 01:04:04 +02:00
}
2020-11-13 00:15:34 +01:00
private void OpenShaderCacheDir_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
string shaderCacheDir = System . IO . Path . Combine ( AppDataManager . GamesDirPath , _titleIdText , "cache" , "shader" ) ;
2020-11-13 00:15:34 +01:00
if ( ! Directory . Exists ( shaderCacheDir ) )
{
Directory . CreateDirectory ( shaderCacheDir ) ;
}
2021-01-08 09:14:13 +01:00
OpenHelper . OpenFolder ( shaderCacheDir ) ;
2020-11-13 00:15:34 +01:00
}
2020-07-04 01:04:04 +02:00
private void PurgePtcCache_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
DirectoryInfo mainDir = new DirectoryInfo ( System . IO . Path . Combine ( AppDataManager . GamesDirPath , _titleIdText , "cache" , "cpu" , "0" ) ) ;
DirectoryInfo backupDir = new DirectoryInfo ( System . IO . Path . Combine ( AppDataManager . GamesDirPath , _titleIdText , "cache" , "cpu" , "1" ) ) ;
MessageDialog warningDialog = GtkDialog . CreateConfirmationDialog ( "Warning" , $"You are about to delete the PPTC cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?" ) ;
2020-08-13 19:31:13 +02:00
List < FileInfo > cacheFiles = new List < FileInfo > ( ) ;
2021-01-08 09:14:13 +01:00
if ( mainDir . Exists )
{
cacheFiles . AddRange ( mainDir . EnumerateFiles ( "*.cache" ) ) ;
}
if ( backupDir . Exists )
{
cacheFiles . AddRange ( backupDir . EnumerateFiles ( "*.cache" ) ) ;
}
2020-08-13 19:31:13 +02:00
if ( cacheFiles . Count > 0 & & warningDialog . Run ( ) = = ( int ) ResponseType . Yes )
2020-07-04 01:04:04 +02:00
{
2020-08-13 19:31:13 +02:00
foreach ( FileInfo file in cacheFiles )
2020-07-04 01:04:04 +02:00
{
2020-08-13 19:31:13 +02:00
try
{
file . Delete ( ) ;
}
catch ( Exception e )
{
2021-01-08 09:14:13 +01:00
GtkDialog . CreateErrorDialog ( $"Error purging PPTC cache {file.Name}: {e}" ) ;
2020-08-13 19:31:13 +02:00
}
2020-07-04 01:04:04 +02:00
}
}
2020-08-13 19:31:13 +02:00
2020-07-04 01:04:04 +02:00
warningDialog . Dispose ( ) ;
}
2020-11-13 00:15:34 +01:00
private void PurgeShaderCache_Clicked ( object sender , EventArgs args )
{
2021-01-08 09:14:13 +01:00
DirectoryInfo shaderCacheDir = new DirectoryInfo ( System . IO . Path . Combine ( AppDataManager . GamesDirPath , _titleIdText , "cache" , "shader" ) ) ;
2020-11-13 00:15:34 +01:00
2021-01-08 09:14:13 +01:00
MessageDialog warningDialog = GtkDialog . CreateConfirmationDialog ( "Warning" , $"You are about to delete the shader cache for :\n\n<b>{_titleName}</b>\n\nAre you sure you want to proceed?" ) ;
2020-11-13 00:15:34 +01:00
List < DirectoryInfo > cacheDirectory = new List < DirectoryInfo > ( ) ;
2021-01-08 09:14:13 +01:00
if ( shaderCacheDir . Exists )
{
cacheDirectory . AddRange ( shaderCacheDir . EnumerateDirectories ( "*" ) ) ;
}
2020-11-13 00:15:34 +01:00
if ( cacheDirectory . Count > 0 & & warningDialog . Run ( ) = = ( int ) ResponseType . Yes )
{
foreach ( DirectoryInfo directory in cacheDirectory )
{
try
{
directory . Delete ( true ) ;
}
catch ( Exception e )
{
2021-03-18 23:44:39 +01:00
GtkDialog . CreateErrorDialog ( $"Error purging shader cache at {directory.Name}: {e}" ) ;
2020-11-13 00:15:34 +01:00
}
}
}
warningDialog . Dispose ( ) ;
}
2019-12-22 03:49:51 +01:00
}
2021-03-18 23:44:39 +01:00
}