Add ability to trim and untrim XCI files from the application context menu AND in Bulk

This commit is contained in:
TheToid
2024-12-12 00:37:03 -06:00
committed by KeatonTheBot
parent 7bd5a61b65
commit 88530d7435
21 changed files with 1457 additions and 56 deletions
+57 -20
View File
@@ -1,9 +1,13 @@
using Ryujinx.Common.Logging;
// Uncomment the line below to ensure XCIFileTrimmer does not modify files
//#define XCI_TRIMMER_READ_ONLY_MODE
using Gommon;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
namespace Ryujinx.Common.Utilities
{
@@ -91,6 +95,7 @@ namespace Ryujinx.Common.Utilities
public enum OperationOutcome
{
Undetermined,
InvalidXCIFile,
NoTrimNecessary,
NoUntrimPossible,
@@ -98,7 +103,8 @@ namespace Ryujinx.Common.Utilities
FileIOWriteError,
ReadOnlyFileCannotFix,
FileSizeChanged,
Successful
Successful,
Cancelled
}
public enum LogType
@@ -159,7 +165,7 @@ namespace Ryujinx.Common.Utilities
ReadHeader();
}
public void CheckFreeSpace()
public void CheckFreeSpace(CancellationToken? cancelToken = null)
{
if (FreeSpaceChecked)
return;
@@ -178,14 +184,14 @@ namespace Ryujinx.Common.Utilities
bool freeSpaceValid = true;
long readSizeB = FileSizeB - TrimmedFileSizeB;
TimeSpan time = Performance.Measure(() =>
Stopwatch timedSw = Lambda.Timed(() =>
{
freeSpaceValid = CheckPadding(readSizeB);
freeSpaceValid = CheckPadding(readSizeB, cancelToken);
});
if (time.TotalSeconds > 0)
if (timedSw.Elapsed.TotalSeconds > 0)
{
Log?.Write(LogType.Info, $"Checked at {readSizeB / (double)XCIFileTrimmer.BytesInAMegabyte / time.TotalSeconds:N} Mb/sec");
Log?.Write(LogType.Info, $"Checked at {readSizeB / (double)XCIFileTrimmer.BytesInAMegabyte / timedSw.Elapsed.TotalSeconds:N} Mb/sec");
}
if (freeSpaceValid)
@@ -211,7 +217,7 @@ namespace Ryujinx.Common.Utilities
}
}
private bool CheckPadding(long readSizeB)
private bool CheckPadding(long readSizeB, CancellationToken? cancelToken = null)
{
long maxReads = readSizeB / XCIFileTrimmer.BufferSize;
long read = 0;
@@ -219,6 +225,11 @@ namespace Ryujinx.Common.Utilities
while (true)
{
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
{
return false;
}
int bytes = _fileStream.Read(buffer, 0, XCIFileTrimmer.BufferSize);
if (bytes == 0)
break;
@@ -243,7 +254,7 @@ namespace Ryujinx.Common.Utilities
ReadHeader();
}
public OperationOutcome Trim()
public OperationOutcome Trim(CancellationToken? cancelToken = null)
{
if (!FileOK)
{
@@ -257,12 +268,19 @@ namespace Ryujinx.Common.Utilities
if (!FreeSpaceChecked)
{
CheckFreeSpace();
CheckFreeSpace(cancelToken);
}
if (!FreeSpaceValid)
{
return OperationOutcome.FreeSpaceCheckFailed;
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
{
return OperationOutcome.Cancelled;
}
else
{
return OperationOutcome.FreeSpaceCheckFailed;
}
}
Log?.Write(LogType.Info, "Trimming...");
@@ -294,7 +312,10 @@ namespace Ryujinx.Common.Utilities
try
{
outfileStream.SetLength(TrimmedFileSizeB);
#if !XCI_TRIMMER_READ_ONLY_MODE
outfileStream.SetLength(TrimmedFileSizeB);
#endif
return OperationOutcome.Successful;
}
finally
@@ -310,7 +331,7 @@ namespace Ryujinx.Common.Utilities
}
}
public OperationOutcome Untrim()
public OperationOutcome Untrim(CancellationToken? cancelToken = null)
{
if (!FileOK)
{
@@ -352,17 +373,24 @@ namespace Ryujinx.Common.Utilities
try
{
TimeSpan time = Performance.Measure(() =>
Stopwatch timedSw = Lambda.Timed(() =>
{
WritePadding(outfileStream, bytesToWriteB);
WritePadding(outfileStream, bytesToWriteB, cancelToken);
});
if (time.TotalSeconds > 0)
if (timedSw.Elapsed.TotalSeconds > 0)
{
Log?.Write(LogType.Info, $"Wrote at {bytesToWriteB / (double)XCIFileTrimmer.BytesInAMegabyte / time.TotalSeconds:N} Mb/sec");
Log?.Write(LogType.Info, $"Wrote at {bytesToWriteB / (double)XCIFileTrimmer.BytesInAMegabyte / timedSw.Elapsed.TotalSeconds:N} Mb/sec");
}
return OperationOutcome.Successful;
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
{
return OperationOutcome.Cancelled;
}
else
{
return OperationOutcome.Successful;
}
}
finally
{
@@ -377,7 +405,7 @@ namespace Ryujinx.Common.Utilities
}
}
private void WritePadding(FileStream outfileStream, long bytesToWriteB)
private void WritePadding(FileStream outfileStream, long bytesToWriteB, CancellationToken? cancelToken = null)
{
long bytesLeftToWriteB = bytesToWriteB;
long writes = bytesLeftToWriteB / XCIFileTrimmer.BufferSize;
@@ -390,8 +418,17 @@ namespace Ryujinx.Common.Utilities
while (bytesLeftToWriteB > 0)
{
if (cancelToken.HasValue && cancelToken.Value.IsCancellationRequested)
{
return;
}
long bytesToWrite = Math.Min(XCIFileTrimmer.BufferSize, bytesLeftToWriteB);
outfileStream.Write(buffer, 0, (int)bytesToWrite);
#if !XCI_TRIMMER_READ_ONLY_MODE
outfileStream.Write(buffer, 0, (int)bytesToWrite);
#endif
bytesLeftToWriteB -= bytesToWrite;
Log?.Progress(write, writes, "Writing padding data...", false);
write++;