mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add FsTrim for savedata
This commit is contained in:
parent
9c0e6030e5
commit
5c84f5c2a4
8 changed files with 134 additions and 9 deletions
|
@ -299,6 +299,42 @@ namespace LibHac.IO.Save
|
||||||
return totalLength;
|
return totalLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FsTrimList(int blockIndex)
|
||||||
|
{
|
||||||
|
int index = blockIndex;
|
||||||
|
|
||||||
|
int tableSize = Header.AllocationTableBlockCount;
|
||||||
|
int nodesIterated = 0;
|
||||||
|
|
||||||
|
while (index != -1)
|
||||||
|
{
|
||||||
|
ReadEntry(index, out int next, out int _, out int length);
|
||||||
|
|
||||||
|
if (length > 3)
|
||||||
|
{
|
||||||
|
int fillOffset = BlockToEntryIndex(index + 2) * EntrySize;
|
||||||
|
int fillLength = (length - 3) * EntrySize;
|
||||||
|
|
||||||
|
BaseStorage.Slice(fillOffset, fillLength).Fill(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
nodesIterated++;
|
||||||
|
|
||||||
|
if (nodesIterated > tableSize)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FsTrim()
|
||||||
|
{
|
||||||
|
int tableSize = BlockToEntryIndex(Header.AllocationTableBlockCount) * EntrySize;
|
||||||
|
BaseStorage.Slice(tableSize).Fill(0x00);
|
||||||
|
}
|
||||||
|
|
||||||
private void ReadEntries(int entryIndex, Span<AllocationTableEntry> entries)
|
private void ReadEntries(int entryIndex, Span<AllocationTableEntry> entries)
|
||||||
{
|
{
|
||||||
Debug.Assert(entries.Length >= 2);
|
Debug.Assert(entries.Length >= 2);
|
||||||
|
|
|
@ -11,13 +11,14 @@ namespace LibHac.IO.Save
|
||||||
|
|
||||||
private long _length;
|
private long _length;
|
||||||
|
|
||||||
public AllocationTableStorage(IStorage data, AllocationTable table, int blockSize, int initialBlock, long length)
|
public AllocationTableStorage(IStorage data, AllocationTable table, int blockSize, int initialBlock)
|
||||||
{
|
{
|
||||||
BaseStorage = data;
|
BaseStorage = data;
|
||||||
BlockSize = blockSize;
|
BlockSize = blockSize;
|
||||||
_length = length;
|
|
||||||
Fat = table;
|
Fat = table;
|
||||||
InitialBlock = initialBlock;
|
InitialBlock = initialBlock;
|
||||||
|
|
||||||
|
_length = table.GetListLength(initialBlock) * blockSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ReadImpl(Span<byte> destination, long offset)
|
protected override void ReadImpl(Span<byte> destination, long offset)
|
||||||
|
|
|
@ -200,6 +200,11 @@ namespace LibHac.IO.Save
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void FsTrim()
|
||||||
|
{
|
||||||
|
SaveDataFileSystemCore.FsTrim();
|
||||||
|
}
|
||||||
|
|
||||||
public Validity Verify(IProgressReport logger = null)
|
public Validity Verify(IProgressReport logger = null)
|
||||||
{
|
{
|
||||||
Validity validity = IvfcStorage.Validate(true, logger);
|
Validity validity = IvfcStorage.Validate(true, logger);
|
||||||
|
|
|
@ -20,9 +20,8 @@ namespace LibHac.IO.Save
|
||||||
|
|
||||||
Header = new SaveHeader(HeaderStorage);
|
Header = new SaveHeader(HeaderStorage);
|
||||||
|
|
||||||
// todo: Query the FAT for the file size when none is given
|
AllocationTableStorage dirTableStorage = OpenFatStorage(AllocationTable.Header.DirectoryTableBlock);
|
||||||
AllocationTableStorage dirTableStorage = OpenFatBlock(AllocationTable.Header.DirectoryTableBlock, 1000000);
|
AllocationTableStorage fileTableStorage = OpenFatStorage(AllocationTable.Header.FileTableBlock);
|
||||||
AllocationTableStorage fileTableStorage = OpenFatBlock(AllocationTable.Header.FileTableBlock, 1000000);
|
|
||||||
|
|
||||||
FileTable = new HierarchicalSaveFileTable(dirTableStorage, fileTableStorage);
|
FileTable = new HierarchicalSaveFileTable(dirTableStorage, fileTableStorage);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +91,7 @@ namespace LibHac.IO.Save
|
||||||
return new NullFile();
|
return new NullFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
AllocationTableStorage storage = OpenFatBlock(file.StartBlock, file.Length);
|
AllocationTableStorage storage = OpenFatStorage(file.StartBlock);
|
||||||
|
|
||||||
return new SaveDataFile(storage, 0, file.Length, mode);
|
return new SaveDataFile(storage, 0, file.Length, mode);
|
||||||
}
|
}
|
||||||
|
@ -139,9 +138,31 @@ namespace LibHac.IO.Save
|
||||||
public IStorage GetBaseStorage() => BaseStorage.AsReadOnly();
|
public IStorage GetBaseStorage() => BaseStorage.AsReadOnly();
|
||||||
public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly();
|
public IStorage GetHeaderStorage() => HeaderStorage.AsReadOnly();
|
||||||
|
|
||||||
private AllocationTableStorage OpenFatBlock(int blockIndex, long size)
|
public void FsTrim()
|
||||||
{
|
{
|
||||||
return new AllocationTableStorage(BaseStorage, AllocationTable, (int)Header.BlockSize, blockIndex, size);
|
AllocationTable.FsTrim();
|
||||||
|
|
||||||
|
foreach (DirectoryEntry file in this.EnumerateEntries("*", SearchOptions.RecurseSubdirectories))
|
||||||
|
{
|
||||||
|
if (FileTable.TryOpenFile(file.FullPath, out SaveFileInfo fileInfo) && fileInfo.StartBlock >= 0)
|
||||||
|
{
|
||||||
|
AllocationTable.FsTrimList(fileInfo.StartBlock);
|
||||||
|
|
||||||
|
OpenFatStorage(fileInfo.StartBlock).Slice(fileInfo.Length).Fill(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int freeIndex = AllocationTable.GetFreeListBlockIndex();
|
||||||
|
if (freeIndex == 0) return;
|
||||||
|
|
||||||
|
AllocationTable.FsTrimList(freeIndex);
|
||||||
|
|
||||||
|
OpenFatStorage(freeIndex).Fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AllocationTableStorage OpenFatStorage(int blockIndex)
|
||||||
|
{
|
||||||
|
return new AllocationTableStorage(BaseStorage, AllocationTable, (int)Header.BlockSize, blockIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,6 +97,59 @@ namespace LibHac.IO
|
||||||
progress?.SetTotal(0);
|
progress?.SetTotal(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Fill(this IStorage input, byte value, IProgressReport progress = null)
|
||||||
|
{
|
||||||
|
const int threshold = 0x400;
|
||||||
|
|
||||||
|
long length = input.GetSize();
|
||||||
|
if (length > threshold)
|
||||||
|
{
|
||||||
|
input.FillLarge(value, progress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> buf = stackalloc byte[(int)length];
|
||||||
|
buf.Fill(value);
|
||||||
|
|
||||||
|
input.Write(buf, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void FillLarge(this IStorage input, byte value, IProgressReport progress = null)
|
||||||
|
{
|
||||||
|
const int bufferSize = 0x4000;
|
||||||
|
|
||||||
|
long remaining = input.GetSize();
|
||||||
|
if (remaining < 0) throw new ArgumentException("Storage must have an explicit length");
|
||||||
|
progress?.SetTotal(remaining);
|
||||||
|
|
||||||
|
long pos = 0;
|
||||||
|
|
||||||
|
byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
buffer.AsSpan(0, (int)Math.Min(remaining, bufferSize)).Fill(value);
|
||||||
|
|
||||||
|
while (remaining > 0)
|
||||||
|
{
|
||||||
|
int toFill = (int)Math.Min(bufferSize, remaining);
|
||||||
|
Span<byte> buf = buffer.AsSpan(0, toFill);
|
||||||
|
|
||||||
|
input.Write(buf, pos);
|
||||||
|
|
||||||
|
remaining -= toFill;
|
||||||
|
pos += toFill;
|
||||||
|
|
||||||
|
progress?.ReportAdd(toFill);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
ArrayPool<byte>.Shared.Return(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress?.SetTotal(0);
|
||||||
|
}
|
||||||
|
|
||||||
public static void WriteAllBytes(this IStorage input, string filename, IProgressReport progress = null)
|
public static void WriteAllBytes(this IStorage input, string filename, IProgressReport progress = null)
|
||||||
{
|
{
|
||||||
using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.Write))
|
using (var outFile = new FileStream(filename, FileMode.Create, FileAccess.Write))
|
||||||
|
|
|
@ -52,6 +52,7 @@ namespace hactoolnet
|
||||||
new CliOption("listromfs", 0, (o, a) => o.ListRomFs = true),
|
new CliOption("listromfs", 0, (o, a) => o.ListRomFs = true),
|
||||||
new CliOption("listfiles", 0, (o, a) => o.ListFiles = true),
|
new CliOption("listfiles", 0, (o, a) => o.ListFiles = true),
|
||||||
new CliOption("sign", 0, (o, a) => o.SignSave = true),
|
new CliOption("sign", 0, (o, a) => o.SignSave = true),
|
||||||
|
new CliOption("trim", 0, (o, a) => o.TrimSave = true),
|
||||||
new CliOption("readbench", 0, (o, a) => o.ReadBench = true),
|
new CliOption("readbench", 0, (o, a) => o.ReadBench = true),
|
||||||
new CliOption("hashedfs", 0, (o, a) => o.BuildHfs = true),
|
new CliOption("hashedfs", 0, (o, a) => o.BuildHfs = true),
|
||||||
new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])),
|
new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])),
|
||||||
|
@ -232,6 +233,7 @@ namespace hactoolnet
|
||||||
sb.AppendLine(" --outdir <dir> Specify directory path to save contents to.");
|
sb.AppendLine(" --outdir <dir> Specify directory path to save contents to.");
|
||||||
sb.AppendLine(" --debugoutdir <dir> Specify directory path to save intermediate data to for debugging.");
|
sb.AppendLine(" --debugoutdir <dir> Specify directory path to save intermediate data to for debugging.");
|
||||||
sb.AppendLine(" --sign Sign the save file. (Requires device_key in key file)");
|
sb.AppendLine(" --sign Sign the save file. (Requires device_key in key file)");
|
||||||
|
sb.AppendLine(" --trim Trim garbage data in the save file. (Requires device_key in key file)");
|
||||||
sb.AppendLine(" --listfiles List files in save file.");
|
sb.AppendLine(" --listfiles List files in save file.");
|
||||||
sb.AppendLine(" --replacefile <filename in save> <file> Replaces a file in the save data");
|
sb.AppendLine(" --replacefile <filename in save> <file> Replaces a file in the save data");
|
||||||
sb.AppendLine("NDV0 (Delta) options:");
|
sb.AppendLine("NDV0 (Delta) options:");
|
||||||
|
|
|
@ -45,6 +45,7 @@ namespace hactoolnet
|
||||||
public bool ListRomFs;
|
public bool ListRomFs;
|
||||||
public bool ListFiles;
|
public bool ListFiles;
|
||||||
public bool SignSave;
|
public bool SignSave;
|
||||||
|
public bool TrimSave;
|
||||||
public bool ReadBench;
|
public bool ReadBench;
|
||||||
public bool BuildHfs;
|
public bool BuildHfs;
|
||||||
public ulong TitleId;
|
public ulong TitleId;
|
||||||
|
|
|
@ -129,8 +129,14 @@ namespace hactoolnet
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.Options.SignSave)
|
if (ctx.Options.SignSave || ctx.Options.TrimSave)
|
||||||
{
|
{
|
||||||
|
if (ctx.Options.TrimSave)
|
||||||
|
{
|
||||||
|
save.FsTrim();
|
||||||
|
ctx.Logger.LogMessage("Trimmed save file");
|
||||||
|
}
|
||||||
|
|
||||||
if (save.Commit(ctx.Keyset))
|
if (save.Commit(ctx.Keyset))
|
||||||
{
|
{
|
||||||
ctx.Logger.LogMessage("Successfully signed save file");
|
ctx.Logger.LogMessage("Successfully signed save file");
|
||||||
|
|
Loading…
Reference in a new issue