1
1
mirror of https://github.com/ryujinx-mirror/ryujinx.git synced 2025-10-03 04:45:51 -05:00

Add ability to trim XCI files from the application context menu (#33)

This commit is contained in:
TheToid
2024-10-23 01:25:40 +10:00
committed by GitHub
parent 7e9a293dab
commit 6253fe143a
15 changed files with 915 additions and 2 deletions

View File

@@ -82,8 +82,11 @@
"GameListContextMenuOpenModsDirectoryToolTip": "Opens the directory which contains Application's Mods",
"GameListContextMenuOpenSdModsDirectory": "Open Atmosphere Mods Directory",
"GameListContextMenuOpenSdModsDirectoryToolTip": "Opens the alternative SD card Atmosphere directory which contains Application's Mods. Useful for mods that are packaged for real hardware.",
"GameListContextMenuTrimXCI": "Check and Trim XCI File",
"GameListContextMenuTrimXCIToolTip": "Check and Trim XCI File to Save Disk Space",
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
"StatusBarSystemVersion": "System Version: {0}",
"StatusBarXCIFileTrimming": "Trimming XCI File '{0}'",
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
"LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}",
"LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.",
@@ -704,6 +707,16 @@
"SelectDlcDialogTitle": "Select DLC files",
"SelectUpdateDialogTitle": "Select update files",
"SelectModDialogTitle": "Select mod directory",
"TrimXCIFileDialogTitle": "Check and Trim XCI File",
"TrimXCIFileDialogPrimaryText": "This function will first check the empty space and then trim the XCI File to save disk space.",
"TrimXCIFileDialogSecondaryText": "Current File Size: {0:n} MB\nGame Data Size: {1:n} MB\nDisk Space Savings: {2:n} MB",
"TrimXCIFileNoTrimNecessary": "XCI File does not need to be trimmed. Check logs for further details",
"TrimXCIFileReadOnlyFileCannotFix": "XCI File is Read Only and could not be made writable. Check logs for further details",
"TrimXCIFileFileSizeChanged": "XCI File has changed in size since it was scanned. Please check the file is not being written to and try again.",
"TrimXCIFileFreeSpaceCheckFailed": "XCI File has data in the free space area, it is not safe to trim",
"TrimXCIFileInvalidXCIFile": "XCI File contains invalid data. Check logs for further details",
"TrimXCIFileFileIOWriteError": "XCI File could not be opened for writing. Check logs for further details",
"TrimXCIFileFailedPrimaryText": "Trimming of the XCI file failed",
"UserProfileWindowTitle": "User Profiles Manager",
"CheatWindowTitle": "Cheats Manager",
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
@@ -714,6 +727,7 @@
"DlcWindowHeading": "{0} Downloadable Content(s)",
"ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Edit Selected",
"Continue": "Continue",
"Cancel": "Cancel",
"Save": "Save",
"Discard": "Discard",

View File

@@ -0,0 +1,24 @@
using Ryujinx.Ava.UI.ViewModels;
namespace Ryujinx.Ava.Common
{
internal class XCIFileTrimmerLog : Ryujinx.Common.Logging.XCIFileTrimmerLog
{
private readonly MainWindowViewModel _viewModel;
public XCIFileTrimmerLog(MainWindowViewModel viewModel)
{
_viewModel = viewModel;
}
public override void Progress(long current, long total, string text, bool complete)
{
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
{
_viewModel.StatusBarProgressMaximum = (int)(total);
_viewModel.StatusBarProgressValue = (int)(current);
});
}
}
}

View File

@@ -59,6 +59,12 @@
Click="OpenSdModsDirectory_Click"
Header="{locale:Locale GameListContextMenuOpenSdModsDirectory}"
ToolTip.Tip="{locale:Locale GameListContextMenuOpenSdModsDirectoryToolTip}" />
<Separator />
<MenuItem
Click="TrimXCI_Click"
Header="{locale:Locale GameListContextMenuTrimXCI}"
IsEnabled="{Binding TrimXCIEnabled}"
ToolTip.Tip="{locale:Locale GameListContextMenuTrimXCIToolTip}" />
<Separator />
<MenuItem Header="{locale:Locale GameListContextMenuCacheManagement}">
<MenuItem

View File

@@ -1,6 +1,7 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Threading;
using LibHac.Fs;
using LibHac.Tools.FsSystem.NcaUtils;
using Ryujinx.Ava.Common;
@@ -15,6 +16,8 @@ using Ryujinx.UI.Common.Helper;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Path = System.IO.Path;
namespace Ryujinx.Ava.UI.Controls
@@ -355,5 +358,15 @@ namespace Ryujinx.Ava.UI.Controls
await viewModel.LoadApplication(viewModel.SelectedApplication);
}
}
public async void TrimXCI_Click(object sender, RoutedEventArgs args)
{
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
if (viewModel?.SelectedApplication != null)
{
await viewModel.TrimXCIFile(viewModel.SelectedApplication.Path);
}
}
}
}

View File

@@ -20,6 +20,7 @@ using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Configuration;
using Ryujinx.Common.Logging;
using Ryujinx.Common.Utilities;
using Ryujinx.Cpu;
using Ryujinx.HLE;
using Ryujinx.HLE.FileSystem;
@@ -36,6 +37,7 @@ using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Threading;
@@ -78,6 +80,8 @@ namespace Ryujinx.Ava.UI.ViewModels
private bool _isAppletMenuActive;
private int _statusBarProgressMaximum;
private int _statusBarProgressValue;
private string _statusBarProgressStatusText;
private bool _statusBarProgressStatusVisible;
private bool _isPaused;
private bool _showContent = true;
private bool _isLoadingIndeterminate = true;
@@ -366,6 +370,8 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0;
public bool TrimXCIEnabled => Ryujinx.Common.Utilities.XCIFileTrimmer.CanTrim(SelectedApplication.Path, new Common.XCIFileTrimmerLog(this));
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild;
@@ -480,6 +486,28 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool StatusBarProgressStatusVisible
{
get => _statusBarProgressStatusVisible;
set
{
_statusBarProgressStatusVisible = value;
OnPropertyChanged();
}
}
public string StatusBarProgressStatusText
{
get => _statusBarProgressStatusText;
set
{
_statusBarProgressStatusText = value;
OnPropertyChanged();
}
}
public string FifoStatusText
{
get => _fifoStatusText;
@@ -1747,6 +1775,114 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
}
public async void ProcessTrimResult(String filename, Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome operationOutcome)
{
string notifyUser = null;
switch (operationOutcome)
{
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.NoTrimNecessary:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileNoTrimNecessary];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.ReadOnlyFileCannotFix:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileReadOnlyFileCannotFix];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FreeSpaceCheckFailed:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileFreeSpaceCheckFailed];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.InvalidXCIFile:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileInvalidXCIFile];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FileIOWriteError:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileFileIOWriteError];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.FileSizeChanged:
notifyUser = LocaleManager.Instance[LocaleKeys.TrimXCIFileFileSizeChanged];
break;
case Ryujinx.Common.Utilities.XCIFileTrimmer.OperationOutcome.Successful:
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
if (desktop.MainWindow is MainWindow mainWindow)
mainWindow.LoadApplications();
}
break;
}
if (notifyUser != null)
{
await ContentDialogHelper.CreateWarningDialog(
LocaleManager.Instance[LocaleKeys.TrimXCIFileFailedPrimaryText],
notifyUser
);
}
}
public async Task TrimXCIFile(string filename)
{
if (filename == null)
{
return;
}
var trimmer = new XCIFileTrimmer(filename, new Common.XCIFileTrimmerLog(this));
if (trimmer.CanBeTrimmed)
{
var savings = (double)trimmer.DiskSpaceSavingsB / 1024.0 / 1024.0;
var currentFileSize = (double)trimmer.FileSizeB / 1024.0 / 1024.0;
var cartDataSize = (double)trimmer.DataSizeB / 1024.0 / 1024.0;
string secondaryText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.TrimXCIFileDialogSecondaryText, currentFileSize, cartDataSize, savings);
var result = await ContentDialogHelper.CreateConfirmationDialog(
LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogPrimaryText],
secondaryText,
LocaleManager.Instance[LocaleKeys.Continue],
LocaleManager.Instance[LocaleKeys.Cancel],
LocaleManager.Instance[LocaleKeys.TrimXCIFileDialogTitle]
);
if (result == UserResult.Yes)
{
Thread XCIFileTrimThread = new(() =>
{
Dispatcher.UIThread.Post(() =>
{
StatusBarProgressStatusText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarXCIFileTrimming, Path.GetFileName(filename));
StatusBarProgressStatusVisible = true;
StatusBarProgressMaximum = 1;
StatusBarProgressValue = 0;
StatusBarVisible = true;
});
try
{
XCIFileTrimmer.OperationOutcome operationOutcome = trimmer.Trim();
Dispatcher.UIThread.Post(() =>
{
ProcessTrimResult(filename, operationOutcome);
});
}
finally
{
Dispatcher.UIThread.Post(() =>
{
StatusBarProgressStatusVisible = false;
StatusBarProgressStatusText = string.Empty;
StatusBarVisible = false;
});
}
})
{
Name = "GUI.XCFileTrimmerThread",
IsBackground = true,
};
XCIFileTrimThread.Start();
}
}
}
#endregion
}
}

View File

@@ -36,6 +36,7 @@
IsVisible="{Binding EnableNonGameRunningControls}">
<Grid Margin="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
@@ -60,9 +61,16 @@
VerticalAlignment="Center"
IsVisible="{Binding EnableNonGameRunningControls}"
Text="{locale:Locale StatusBarGamesLoaded}" />
<TextBlock
Name="StatusBarProgressStatus"
Grid.Column="2"
Margin="10,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding StatusBarProgressStatusVisible}"
Text="{Binding StatusBarProgressStatusText}" />
<ProgressBar
Name="LoadProgressBar"
Grid.Column="2"
Grid.Column="3"
Height="6"
VerticalAlignment="Center"
Foreground="{DynamicResource SystemAccentColorLight2}"