Add FsTrim for savedata

This commit is contained in:
Alex Barney 2019-04-15 14:38:18 -04:00
parent 9c0e6030e5
commit 5c84f5c2a4
8 changed files with 134 additions and 9 deletions

View file

@ -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);

View file

@ -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)

View file

@ -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);

View file

@ -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);
} }
} }

View file

@ -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))

View file

@ -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:");

View file

@ -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;

View file

@ -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");