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