diff --git a/NandReaderGui/App.config b/NandReaderGui/App.config new file mode 100644 index 00000000..8324aa6f --- /dev/null +++ b/NandReaderGui/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/NandReaderGui/App.xaml b/NandReaderGui/App.xaml new file mode 100644 index 00000000..bd2febe2 --- /dev/null +++ b/NandReaderGui/App.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/NandReaderGui/App.xaml.cs b/NandReaderGui/App.xaml.cs new file mode 100644 index 00000000..7ddcbb21 --- /dev/null +++ b/NandReaderGui/App.xaml.cs @@ -0,0 +1,9 @@ +namespace NandReaderGui +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App + { + } +} diff --git a/NandReaderGui/DeviceStream.cs b/NandReaderGui/DeviceStream.cs new file mode 100644 index 00000000..aa7c685c --- /dev/null +++ b/NandReaderGui/DeviceStream.cs @@ -0,0 +1,163 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; + +namespace NandReaderGui +{ + public class DeviceStream : Stream + { + public const short FileAttributeNormal = 0x80; + public const short InvalidHandleValue = -1; + public const uint GenericRead = 0x80000000; + public const uint GenericWrite = 0x40000000; + public const uint CreateNew = 1; + public const uint CreateAlways = 2; + public const uint OpenExisting = 3; + + // Use interop to call the CreateFile function. + // For more information about CreateFile, + // see the unmanaged MSDN reference library. + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, + uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, + uint dwFlagsAndAttributes, IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool ReadFile( + IntPtr hFile, // handle to file + byte[] lpBuffer, // data buffer + int nNumberOfBytesToRead, // number of bytes to read + ref int lpNumberOfBytesRead, // number of bytes read + IntPtr lpOverlapped + // + // ref OVERLAPPED lpOverlapped // overlapped buffer + ); + + private SafeFileHandle _handleValue; + private FileStream _fs; + + public DeviceStream(string device, long length) + { + Load(device); + Length = length; + } + + private void Load(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + + // Try to open the file. + IntPtr ptr = CreateFile(path, GenericRead, 0, IntPtr.Zero, OpenExisting, 0, IntPtr.Zero); + + _handleValue = new SafeFileHandle(ptr, true); + _fs = new FileStream(_handleValue, FileAccess.Read); + + // If the handle is invalid, + // get the last Win32 error + // and throw a Win32Exception. + if (_handleValue.IsInvalid) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + } + + public override bool CanRead { get; } = true; + + public override bool CanSeek => true; + + public override bool CanWrite => false; + + public override void Flush() { } + + public override long Length { get; } + + public override long Position + { + get => _fs.Position; + set => _fs.Position = value; + } + + public override int Read(byte[] buffer, int offset, int count) + { + int bytesRead = 0; + var bufBytes = new byte[count]; + if (!ReadFile(_handleValue.DangerousGetHandle(), bufBytes, count, ref bytesRead, IntPtr.Zero)) + { + Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); + } + for (int i = 0; i < bytesRead; i++) + { + buffer[offset + i] = bufBytes[i]; + } + return bytesRead; + } + + public override int ReadByte() + { + int bytesRead = 0; + var lpBuffer = new byte[1]; + if (!ReadFile( + _handleValue.DangerousGetHandle(), // handle to file + lpBuffer, // data buffer + 1, // number of bytes to read + ref bytesRead, // number of bytes read + IntPtr.Zero + )) + { Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); } + return lpBuffer[0]; + } + + public override long Seek(long offset, SeekOrigin origin) => _fs.Seek(offset, origin); + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } + + public override void Close() + { + _handleValue.Close(); + _handleValue.Dispose(); + _handleValue = null; + base.Close(); + } + private bool _disposed; + + private new void Dispose() + { + Dispose(true); + base.Dispose(); + GC.SuppressFinalize(this); + } + + private new void Dispose(bool disposing) + { + // Check to see if Dispose has already been called. + if (!_disposed) + { + if (disposing) + { + if (_handleValue != null) + { + _fs.Dispose(); + _handleValue.Close(); + _handleValue.Dispose(); + _handleValue = null; + } + } + // Note disposing has been done. + _disposed = true; + + } + } + } +} diff --git a/NandReaderGui/FodyWeavers.xml b/NandReaderGui/FodyWeavers.xml new file mode 100644 index 00000000..43fc6a63 --- /dev/null +++ b/NandReaderGui/FodyWeavers.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/NandReaderGui/MainWindow.xaml b/NandReaderGui/MainWindow.xaml new file mode 100644 index 00000000..f6e9046f --- /dev/null +++ b/NandReaderGui/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/NandReaderGui/MainWindow.xaml.cs b/NandReaderGui/MainWindow.xaml.cs new file mode 100644 index 00000000..b0a88d70 --- /dev/null +++ b/NandReaderGui/MainWindow.xaml.cs @@ -0,0 +1,13 @@ +namespace NandReaderGui +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow + { + public MainWindow() + { + InitializeComponent(); + } + } +} diff --git a/NandReaderGui/NandReaderGui.csproj b/NandReaderGui/NandReaderGui.csproj new file mode 100644 index 00000000..2e5b7f42 --- /dev/null +++ b/NandReaderGui/NandReaderGui.csproj @@ -0,0 +1,126 @@ + + + + + Debug + AnyCPU + {3CBD38B0-6575-4768-8E94-A8AF2D2C9F43} + WinExe + NandReaderGui + NandReaderGui + v4.6 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + + Nand.xaml + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + Designer + MSBuild:Compile + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + 3.1.0 + + + 5.4.1 + + + + + {ab503d24-f702-4e6e-b615-a9c7bda218d1} + libhac.Nand + + + {ffca6c31-d9d4-4ed8-a06d-0cc6b94422b8} + libhac + + + + \ No newline at end of file diff --git a/NandReaderGui/Properties/AssemblyInfo.cs b/NandReaderGui/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..867f8690 --- /dev/null +++ b/NandReaderGui/Properties/AssemblyInfo.cs @@ -0,0 +1,53 @@ +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("RawNandReader")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("RawNandReader")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NandReaderGui/Properties/Resources.Designer.cs b/NandReaderGui/Properties/Resources.Designer.cs new file mode 100644 index 00000000..6396e556 --- /dev/null +++ b/NandReaderGui/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NandReaderGui.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("NandReaderGui.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/NandReaderGui/Properties/Resources.resx b/NandReaderGui/Properties/Resources.resx new file mode 100644 index 00000000..af7dbebb --- /dev/null +++ b/NandReaderGui/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/NandReaderGui/Properties/Settings.Designer.cs b/NandReaderGui/Properties/Settings.Designer.cs new file mode 100644 index 00000000..974adc6a --- /dev/null +++ b/NandReaderGui/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace NandReaderGui.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/NandReaderGui/Properties/Settings.settings b/NandReaderGui/Properties/Settings.settings new file mode 100644 index 00000000..033d7a5e --- /dev/null +++ b/NandReaderGui/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/NandReaderGui/View/Nand.xaml b/NandReaderGui/View/Nand.xaml new file mode 100644 index 00000000..5f8881d3 --- /dev/null +++ b/NandReaderGui/View/Nand.xaml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/NandReaderGui/View/Nand.xaml.cs b/NandReaderGui/View/Nand.xaml.cs new file mode 100644 index 00000000..469a1aa9 --- /dev/null +++ b/NandReaderGui/View/Nand.xaml.cs @@ -0,0 +1,13 @@ +namespace NandReaderGui.View +{ + /// + /// Interaction logic for Nand.xaml + /// + public partial class Nand + { + public Nand() + { + InitializeComponent(); + } + } +} diff --git a/NandReaderGui/ViewModel/NandViewModel.cs b/NandReaderGui/ViewModel/NandViewModel.cs new file mode 100644 index 00000000..631f5b87 --- /dev/null +++ b/NandReaderGui/ViewModel/NandViewModel.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Management; +using System.Windows.Input; +using GalaSoft.MvvmLight; +using GalaSoft.MvvmLight.Command; +using libhac; +using libhac.Nand; +using libhac.XTSSharp; + +namespace NandReaderGui.ViewModel +{ + public class NandViewModel : ViewModelBase + { + public List Disks { get; } = new List(); + public ICommand OpenCommand { get; set; } + public DiskInfo SelectedDisk { get; set; } + + public NandViewModel() + { + OpenCommand = new RelayCommand(Open); + + var query = new WqlObjectQuery("SELECT * FROM Win32_DiskDrive"); + using (var searcher = new ManagementObjectSearcher(query)) + { + foreach (var drive in searcher.Get()) + { + if (drive.GetPropertyValue("Size") == null) continue; + var info = new DiskInfo(); + info.PhysicalName = (string)drive.GetPropertyValue("Name"); + info.Name = (string)drive.GetPropertyValue("Caption"); + info.Model = (string)drive.GetPropertyValue("Model"); + info.Length = (long)((ulong)drive.GetPropertyValue("Size")); + info.SectorSize = (int)((uint)drive.GetPropertyValue("BytesPerSector")); + info.DisplaySize = Util.GetBytesReadable((long)((ulong)drive.GetPropertyValue("Size"))); + + Disks.Add(info); + } + } + } + + public void Open() + { + var disk = SelectedDisk; + var stream = new RandomAccessSectorStream(new SectorStream(new DeviceStream(disk.PhysicalName, disk.Length), disk.SectorSize * 100)); + + var keyset = OpenKeyset(); + var nand = new Nand(stream, keyset); + + var prodinfo = nand.OpenProdInfo(); + var calibration = new Calibration(prodinfo); + + keyset.eticket_ext_key_rsa = Crypto.DecryptRsaKey(calibration.EticketExtKeyRsa, keyset.eticket_rsa_kek); + var tickets = GetTickets(nand); + + using (var outStream = new StreamWriter("titlekeys.txt")) + { + foreach (var ticket in tickets) + { + var key = ticket.GetTitleKey(keyset); + outStream.WriteLine($"{ticket.RightsId.ToHexString()},{key.ToHexString()}"); + } + } + } + + private static Ticket[] GetTickets(Nand nand, IProgressReport logger = null) + { + var tickets = new List(); + var system = nand.OpenSystemPartition(); + + logger?.LogMessage("Searching save 80000000000000E1"); + var saveE1 = system.OpenFile("save\\80000000000000E1", FileMode.Open, FileAccess.Read); + tickets.AddRange(Ticket.SearchTickets(saveE1, logger)); + + logger?.LogMessage("Searching save 80000000000000E2"); + var saveE2 = system.OpenFile("save\\80000000000000E2", FileMode.Open, FileAccess.Read); + tickets.AddRange(Ticket.SearchTickets(saveE2, logger)); + + logger?.LogMessage($"Found {tickets.Count} tickets"); + + return tickets.ToArray(); + } + + private static Keyset OpenKeyset() + { + var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + var homeKeyFile = Path.Combine(home, ".switch", "prod.keys"); + var homeTitleKeyFile = Path.Combine(home, ".switch", "title.keys"); + var homeConsoleKeyFile = Path.Combine(home, ".switch", "console.keys"); + string keyFile = null; + string titleKeyFile = null; + string consoleKeyFile = null; + + if (File.Exists(homeKeyFile)) + { + keyFile = homeKeyFile; + } + + if (File.Exists(homeTitleKeyFile)) + { + titleKeyFile = homeTitleKeyFile; + } + + if (File.Exists(homeConsoleKeyFile)) + { + consoleKeyFile = homeConsoleKeyFile; + } + + return ExternalKeys.ReadKeyFile(keyFile, titleKeyFile, consoleKeyFile); + } + } + + public class DiskInfo + { + public string PhysicalName { get; set; } + public string Name { get; set; } + public string Model { get; set; } + public long Length { get; set; } + public int SectorSize { get; set; } + public string DisplaySize { get; set; } + public string Display => $"{Name} ({DisplaySize})"; + } +} diff --git a/NandReaderGui/ViewModel/ViewModelLocator.cs b/NandReaderGui/ViewModel/ViewModelLocator.cs new file mode 100644 index 00000000..e4266ff8 --- /dev/null +++ b/NandReaderGui/ViewModel/ViewModelLocator.cs @@ -0,0 +1,48 @@ +/* + In App.xaml: + + + + + In the View: + DataContext="{Binding Source={StaticResource Locator}, Path=ViewModelName}" +*/ + +using CommonServiceLocator; +using GalaSoft.MvvmLight.Ioc; + +namespace NandReaderGui.ViewModel +{ + /// + /// This class contains static references to all the view models in the + /// application and provides an entry point for the bindings. + /// + /// See http://www.mvvmlight.net + /// + /// + public class ViewModelLocator + { + static ViewModelLocator() + { + ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); + + SimpleIoc.Default.Register(); + } + + /// + /// Gets the Main property. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", + "CA1822:MarkMembersAsStatic", + Justification = "This non-static member is needed for data binding purposes.")] + public NandViewModel Main => ServiceLocator.Current.GetInstance(); + + /// + /// Cleans up all the resources. + /// + public static void Cleanup() + { + } + } +} \ No newline at end of file diff --git a/libhac.sln b/libhac.sln index 58b903ae..923d9c16 100644 --- a/libhac.sln +++ b/libhac.sln @@ -7,9 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libhac", "libhac\libhac.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "hactoolnet", "hactoolnet\hactoolnet.csproj", "{B1633A64-125F-40A3-9E15-654B4DE5FD98}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "libhac.Nand", "libhac.Nand\libhac.Nand.csproj", "{AB503D24-F702-4E6E-B615-A9C7BDA218D1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "libhac.Nand", "libhac.Nand\libhac.Nand.csproj", "{AB503D24-F702-4E6E-B615-A9C7BDA218D1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NandReader", "NandReader\NandReader.csproj", "{9889C467-284F-4061-B4DB-EC94051C29C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NandReader", "NandReader\NandReader.csproj", "{9889C467-284F-4061-B4DB-EC94051C29C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NandReaderGui", "NandReaderGui\NandReaderGui.csproj", "{3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,6 +35,10 @@ Global {9889C467-284F-4061-B4DB-EC94051C29C0}.Debug|Any CPU.Build.0 = Debug|Any CPU {9889C467-284F-4061-B4DB-EC94051C29C0}.Release|Any CPU.ActiveCfg = Release|Any CPU {9889C467-284F-4061-B4DB-EC94051C29C0}.Release|Any CPU.Build.0 = Release|Any CPU + {3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CBD38B0-6575-4768-8E94-A8AF2D2C9F43}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/libhac/XTSSharp/RandomAccessSectorStream.cs b/libhac/XTSSharp/RandomAccessSectorStream.cs index f79f2aa4..63cef1bc 100644 --- a/libhac/XTSSharp/RandomAccessSectorStream.cs +++ b/libhac/XTSSharp/RandomAccessSectorStream.cs @@ -25,6 +25,7 @@ // THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using System; +using System.Collections.Generic; using System.IO; namespace libhac.XTSSharp @@ -34,7 +35,7 @@ namespace libhac.XTSSharp /// public class RandomAccessSectorStream : Stream { - private readonly byte[] _buffer; + private byte[] _buffer; private readonly int _bufferSize; private readonly SectorStream _s; private readonly bool _keepOpen; @@ -43,6 +44,15 @@ namespace libhac.XTSSharp private int _bufferPos; private int _currentBufferSize; + private long PhysicalRead { get; set; } + private long VirtualRead { get; set; } + private int CacheMisses { get; set; } + private int CacheHits { get; set; } + + // List should work just fine for a tiny cache + private List Cache { get; } + private const int CacheSize = 4; + /// /// Creates a new stream /// @@ -63,6 +73,17 @@ namespace libhac.XTSSharp _keepOpen = keepOpen; _buffer = new byte[s.SectorSize]; _bufferSize = s.SectorSize; + + Cache = new List(CacheSize); + + for (int i = 0; i < CacheSize; i++) + { + Cache.Add(new Sector + { + Position = -1, + Data = new byte[_bufferSize] + }); + } } /// @@ -246,6 +267,8 @@ namespace libhac.XTSSharp ReadSector(); } + VirtualRead += totalBytesRead; + return totalBytesRead; } @@ -291,7 +314,37 @@ namespace libhac.XTSSharp return; } + var sectorPosition = _s.Position; + + for (int i = 0; i < CacheSize; i++) + { + var sector = Cache[i]; + if (sector.Position == sectorPosition) + { + if (i != 0) + { + Cache.RemoveAt(i); + Cache.Insert(0, sector); + } + + _buffer = sector.Data; + + _bufferLoaded = true; + _bufferPos = 0; + _bufferDirty = false; + _currentBufferSize = sector.Length; + CacheHits++; + _s.Position += _bufferSize; + return; + } + } + + var item = Cache[CacheSize - 1]; + Cache.RemoveAt(CacheSize - 1); + _buffer = item.Data; + var bytesRead = _s.Read(_buffer, 0, _buffer.Length); + PhysicalRead += bytesRead; //clean the end of it if (bytesRead != _bufferSize) @@ -301,6 +354,11 @@ namespace libhac.XTSSharp _bufferPos = 0; _bufferDirty = false; _currentBufferSize = bytesRead; + + item.Position = sectorPosition; + item.Length = bytesRead; + Cache.Insert(0, item); + CacheMisses++; } /// @@ -321,5 +379,12 @@ namespace libhac.XTSSharp _bufferPos = 0; Array.Clear(_buffer, 0, _bufferSize); } + + private class Sector + { + public long Position { get; set; } + public byte[] Data { get; set; } + public int Length { get; set; } + } } } \ No newline at end of file