diff --git a/hactoolnet/Program.cs b/hactoolnet/Program.cs index 02166d93..93376717 100644 --- a/hactoolnet/Program.cs +++ b/hactoolnet/Program.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using libhac; namespace hactoolnet @@ -6,6 +7,11 @@ namespace hactoolnet public static class Program { static void Main(string[] args) + { + ListSdContents(args); + } + + static void DecryptNax0(string[] args) { var keyset = ExternalKeys.ReadKeyFile(args[0]); keyset.SetSdSeed(args[1].ToBytes()); @@ -21,5 +27,18 @@ namespace hactoolnet nax0.Stream.CopyStream(output, nax0.Stream.Length, progress); } } + + static void ListSdContents(string[] args) + { + var keyset = ExternalKeys.ReadKeyFile(args[0]); + keyset.SetSdSeed(args[1].ToBytes()); + var sdfs = new SdFs(keyset, args[2]); + var ncas = sdfs.ReadAllNca(); + + foreach (var nca in ncas) + { + Console.WriteLine($"{nca.TitleId:X8} {nca.ContentType} {nca.Name}"); + } + } } } diff --git a/libhac/Nca.cs b/libhac/Nca.cs index aa5a0f72..b6df9b50 100644 --- a/libhac/Nca.cs +++ b/libhac/Nca.cs @@ -17,6 +17,7 @@ namespace libhac public uint SdkVersion { get; set; } // What SDK was this built with? public byte CryptoType2 { get; set; } // Which keyblob (field 2) public byte[] RightsId { get; set; } + public string Name { get; set; } public Nca(Keyset keyset, Stream stream) { diff --git a/libhac/SdFs.cs b/libhac/SdFs.cs new file mode 100644 index 00000000..50f6a16f --- /dev/null +++ b/libhac/SdFs.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace libhac +{ + public class SdFs + { + public Keyset Keyset { get; } + public string RootDir { get; } + public string ContentsDir { get; } + public string[] Files { get; } + + public SdFs(Keyset keyset, string sdPath) + { + if (Directory.Exists(Path.Combine(sdPath, "Nintendo"))) + { + RootDir = sdPath; + Keyset = keyset; + ContentsDir = Path.Combine(sdPath, "Nintendo", "Contents"); + } + + Files = Directory.GetFiles(ContentsDir, "00", SearchOption.AllDirectories).Select(Path.GetDirectoryName).ToArray(); + } + + public List ReadAllNca() + { + List ncas = new List(); + foreach (var file in Files) + { + var sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/'); + var nax0 = new Nax0(Keyset, file, sdPath); + var nca = new Nca(Keyset, nax0.Stream); + ncas.Add(nca); + nca.Name = Path.GetFileName(file); + } + + return ncas; + } + } +} diff --git a/libhac/Util.cs b/libhac/Util.cs index e566c856..74012cf1 100644 --- a/libhac/Util.cs +++ b/libhac/Util.cs @@ -79,6 +79,22 @@ namespace libhac return Encoding.ASCII.GetString(reader.ReadBytes(size), 0, size); } + // todo Maybe make less naive + public static string GetRelativePath(string path, string basePath) + { + var directory = new DirectoryInfo(basePath); + var file = new FileInfo(path); + + string fullDirectory = directory.FullName; + string fullFile = file.FullName; + + if (!fullFile.StartsWith(fullDirectory)) + { + throw new ArgumentException($"{nameof(path)} is not a subpath of {nameof(basePath)}"); + } + + return fullFile.Substring(fullDirectory.Length + 1); + } private static int HexToInt(char c) { diff --git a/libhac/XTSSharp/RandomAccessSectorStream.cs b/libhac/XTSSharp/RandomAccessSectorStream.cs index 7ce5ff86..d81d3b16 100644 --- a/libhac/XTSSharp/RandomAccessSectorStream.cs +++ b/libhac/XTSSharp/RandomAccessSectorStream.cs @@ -41,6 +41,7 @@ namespace libhac.XTSSharp private bool _bufferDirty; private bool _bufferLoaded; private int _bufferPos; + private int _currentBufferSize; /// /// Creates a new stream @@ -106,7 +107,7 @@ namespace libhac.XTSSharp /// The current position within the stream. public override long Position { - get { return _bufferLoaded ? (_s.Position - _bufferSize + _bufferPos) : _s.Position + _bufferPos; } + get { return _bufferLoaded ? (_s.Position - _currentBufferSize + _bufferPos) : _s.Position + _bufferPos; } set { if (value < 0L) @@ -299,6 +300,7 @@ namespace libhac.XTSSharp _bufferLoaded = true; _bufferPos = 0; _bufferDirty = false; + _currentBufferSize = bytesRead; } ///