diff --git a/src/LibHac/ProgressBar.cs b/src/LibHac/ProgressBar.cs index b5d4f5f0..05472790 100644 --- a/src/LibHac/ProgressBar.cs +++ b/src/LibHac/ProgressBar.cs @@ -1,6 +1,7 @@ // Adapted from https://gist.github.com/0ab6a96899cc5377bf54 using System; +using System.Diagnostics; using System.Text; using System.Threading; @@ -13,6 +14,10 @@ namespace LibHac private long _total; private readonly Timer _timer; + private bool _isMeasuringSpeed; + private Stopwatch _watch; + private long _timedBytes; + private readonly TimeSpan _animationInterval = TimeSpan.FromSeconds(1.0 / 30); private const string Animation = @"|/-\"; @@ -36,6 +41,7 @@ namespace LibHac public void ReportAdd(long value) { Interlocked.Add(ref _progress, value); + if (_isMeasuringSpeed) Interlocked.Add(ref _timedBytes, value); } public void LogMessage(string message) @@ -52,6 +58,38 @@ namespace LibHac Report(0); } + public void StartNewStopWatch() + { + _isMeasuringSpeed = true; + _timedBytes = 0; + _watch = Stopwatch.StartNew(); + } + + public void PauseStopWatch() + { + _isMeasuringSpeed = false; + _watch.Stop(); + } + + public void ResumeStopWatch() + { + _isMeasuringSpeed = true; + + if (_watch == null) + { + _watch = Stopwatch.StartNew(); + } + else + { + _watch.Start(); + } + } + + public string GetRateString() + { + return Util.GetBytesReadable((long) (_timedBytes / _watch.Elapsed.TotalSeconds)) + "/s"; + } + private void TimerHandler(object state) { lock (_timer) @@ -59,12 +97,18 @@ namespace LibHac if (_disposed) return; string text = string.Empty; + string speed = string.Empty; + + if (_isMeasuringSpeed) + { + speed = $" {GetRateString()}"; + } if (_total > 0) { double progress = _total == 0 ? 0 : (double)_progress / _total; int progressBlockCount = (int)Math.Min(progress * BlockCount, BlockCount); - text = $"[{new string('#', progressBlockCount)}{new string('-', BlockCount - progressBlockCount)}] {_progress}/{_total} {progress:P1} {Animation[_animationIndex++ % Animation.Length]}"; + text = $"[{new string('#', progressBlockCount)}{new string('-', BlockCount - progressBlockCount)}] {_progress}/{_total} {progress:P1} {Animation[_animationIndex++ % Animation.Length]}{speed}"; } UpdateText(text); diff --git a/src/hactoolnet/CliParser.cs b/src/hactoolnet/CliParser.cs index cbe21ec1..06be3c81 100644 --- a/src/hactoolnet/CliParser.cs +++ b/src/hactoolnet/CliParser.cs @@ -50,6 +50,7 @@ namespace hactoolnet new CliOption("listromfs", 0, (o, a) => o.ListRomFs = true), new CliOption("listfiles", 0, (o, a) => o.ListFiles = true), new CliOption("sign", 0, (o, a) => o.SignSave = true), + new CliOption("readbench", 0, (o, a) => o.ReadBench = true), new CliOption("title", 1, (o, a) => o.TitleId = ParseTitleId(a[0])), new CliOption("bench", 1, (o, a) => o.BenchType = a[0]), @@ -161,7 +162,7 @@ namespace hactoolnet sb.AppendLine(" -y, --verify Verify all hashes in the input file."); sb.AppendLine(" -h, --enablehash Enable hash checks when reading the input file."); sb.AppendLine(" -k, --keyset Load keys from an external file."); - sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, ini1, kip1, switchfs, save, ndv0 keygen]"); + sb.AppendLine(" -t, --intype=type Specify input file type [nca, xci, romfs, pk11, pk21, ini1, kip1, switchfs, save, ndv0, keygen]"); sb.AppendLine(" --titlekeys Load title keys from an external file."); sb.AppendLine("NCA options:"); sb.AppendLine(" --plaintext Specify file path for saving a decrypted copy of the NCA."); diff --git a/src/hactoolnet/Options.cs b/src/hactoolnet/Options.cs index f5f00e41..4cfdc7af 100644 --- a/src/hactoolnet/Options.cs +++ b/src/hactoolnet/Options.cs @@ -43,6 +43,7 @@ namespace hactoolnet public bool ListRomFs; public bool ListFiles; public bool SignSave; + public bool ReadBench; public ulong TitleId; public string BenchType; @@ -80,6 +81,6 @@ namespace hactoolnet { public Options Options; public Keyset Keyset; - public IProgressReport Logger; + public ProgressBar Logger; } } diff --git a/src/hactoolnet/ProcessNca.cs b/src/hactoolnet/ProcessNca.cs index 433cc28d..e538f12e 100644 --- a/src/hactoolnet/ProcessNca.cs +++ b/src/hactoolnet/ProcessNca.cs @@ -52,7 +52,7 @@ namespace hactoolnet } } - if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null) + if (ctx.Options.RomfsOutDir != null || ctx.Options.RomfsOut != null || ctx.Options.ReadBench) { NcaSection section = nca.Sections.FirstOrDefault(x => x?.Type == SectionType.Romfs || x?.Type == SectionType.Bktr); @@ -78,6 +78,27 @@ namespace hactoolnet IFileSystem romfs = nca.OpenSectionFileSystem(section.SectionNum, ctx.Options.IntegrityLevel); romfs.Extract(ctx.Options.RomfsOutDir, ctx.Logger); } + + if (ctx.Options.ReadBench) + { + long bytesToRead = 1024L * 1024 * 1024 * 5; + IStorage storage = nca.OpenSection(section.SectionNum, false, ctx.Options.IntegrityLevel, true); + var dest = new NullStorage(storage.Length); + + int iterations = (int)(bytesToRead / storage.Length) + 1; + ctx.Logger.LogMessage(iterations.ToString()); + + ctx.Logger.StartNewStopWatch(); + + for (int i = 0; i < iterations; i++) + { + storage.CopyTo(dest, ctx.Logger); + ctx.Logger.LogMessage(ctx.Logger.GetRateString()); + } + + ctx.Logger.PauseStopWatch(); + ctx.Logger.LogMessage(ctx.Logger.GetRateString()); + } } if (ctx.Options.ExefsOutDir != null || ctx.Options.ExefsOut != null) @@ -113,7 +134,7 @@ namespace hactoolnet nca.OpenDecryptedNca().WriteAllBytes(ctx.Options.PlaintextOut, ctx.Logger); } - ctx.Logger.LogMessage(nca.Print()); + if (!ctx.Options.ReadBench) ctx.Logger.LogMessage(nca.Print()); } }