diff --git a/src/LibHac.Nand/NandPartition.cs b/src/LibHac.Nand/NandPartition.cs deleted file mode 100644 index 22fff573..00000000 --- a/src/LibHac.Nand/NandPartition.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System.IO; -using System.Linq; -using DiscUtils.Fat; - -namespace LibHac.Nand -{ - public class NandPartition : IFileSystemOld - { - public FatFileSystem Fs { get; } - - public NandPartition(FatFileSystem fileSystem) - { - Fs = fileSystem; - } - - public bool FileExists(string path) - { - return Fs.FileExists(path); - } - - public bool DirectoryExists(string path) - { - return Fs.DirectoryExists(path); - } - - public Stream OpenFile(string path, FileMode mode) - { - return Fs.OpenFile(path, mode); - } - - public Stream OpenFile(string path, FileMode mode, FileAccess access) - { - return Fs.OpenFile(path, mode, access); - } - - public string[] GetFileSystemEntries(string path, string searchPattern) - { - return Fs.GetFileSystemEntries(path, searchPattern); - } - - public string[] GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption) - { - string[] files = Fs.GetFiles(path, searchPattern, searchOption); - string[] dirs = Fs.GetDirectories(path, searchPattern, searchOption); - return files.Concat(dirs).ToArray(); - } - - public string GetFullPath(string path) - { - return path; - } - } -} diff --git a/src/LibHac/FileSystem.cs b/src/LibHac/FileSystem.cs deleted file mode 100644 index d2286e93..00000000 --- a/src/LibHac/FileSystem.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace LibHac -{ - public class FileSystem : IFileSystemOld - { - public string Root { get; } - - public FileSystem(string rootDir) - { - Root = Path.GetFullPath(rootDir); - } - - public bool FileExists(string path) - { - return File.Exists(Path.Combine(Root, path)); - } - - public bool DirectoryExists(string path) - { - return Directory.Exists(Path.Combine(Root, path)); - } - - public Stream OpenFile(string path, FileMode mode) - { - return new FileStream(Path.Combine(Root, path), mode); - } - - public Stream OpenFile(string path, FileMode mode, FileAccess access) - { - return new FileStream(Path.Combine(Root, path), mode, access); - } - - public string[] GetFileSystemEntries(string path, string searchPattern) - { - return Directory.GetFileSystemEntries(Path.Combine(Root, path), searchPattern); - } - - public string[] GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption) - { - //return Directory.GetFileSystemEntries(Path.Combine(Root, path), searchPattern, searchOption); - var result = new List(); - - try - { - result.AddRange(GetFileSystemEntries(Path.Combine(Root, path), searchPattern)); - } - catch (UnauthorizedAccessException) { /* Skip this directory */ } - - if (searchOption == SearchOption.TopDirectoryOnly) - return result.ToArray(); - - string[] searchDirectories = Directory.GetDirectories(Path.Combine(Root, path)); - foreach (string search in searchDirectories) - { - try - { - result.AddRange(GetFileSystemEntries(search, searchPattern, searchOption)); - } - catch (UnauthorizedAccessException) { /* Skip this result */ } - } - - return result.ToArray(); - } - - public string GetFullPath(string path) - { - return Path.Combine(Root, path); - } - } -} diff --git a/src/LibHac/IFileSystemOld.cs b/src/LibHac/IFileSystemOld.cs deleted file mode 100644 index 84b650d4..00000000 --- a/src/LibHac/IFileSystemOld.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.IO; - -namespace LibHac -{ - public interface IFileSystemOld - { - bool FileExists(string path); - bool DirectoryExists(string path); - Stream OpenFile(string path, FileMode mode); - Stream OpenFile(string path, FileMode mode, FileAccess access); - string[] GetFileSystemEntries(string path, string searchPattern); - string[] GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption); - string GetFullPath(string path); - } -} diff --git a/src/LibHac/Nax0.cs b/src/LibHac/Nax0.cs deleted file mode 100644 index cba2cea9..00000000 --- a/src/LibHac/Nax0.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using LibHac.IO; - -namespace LibHac -{ - public class Nax0 : IDisposable - { - private const int SectorSize = 0x4000; - - public byte[] Hmac { get; private set; } - public byte[][] EncKeys { get; } = Util.CreateJaggedArray(2, 0x10); - public byte[][] Keys { get; } = Util.CreateJaggedArray(2, 0x10); - public byte[] Key { get; } = new byte[0x20]; - public long Length { get; private set; } - public IStorage BaseStorage { get; } - private bool LeaveOpen { get; } - - public Nax0(Keyset keyset, IStorage storage, string sdPath, bool leaveOpen) - { - LeaveOpen = leaveOpen; - ReadHeader(storage.AsStream()); - DeriveKeys(keyset, sdPath, storage); - - BaseStorage = new CachedStorage(new Aes128XtsStorage(storage.Slice(SectorSize), Key, SectorSize, leaveOpen), 4, leaveOpen); - } - - private void ReadHeader(Stream stream) - { - var reader = new BinaryReader(stream); - - Hmac = reader.ReadBytes(0x20); - string magic = reader.ReadAscii(4); - reader.BaseStream.Position += 4; - if (magic != "NAX0") throw new InvalidDataException("Not an NAX0 file"); - EncKeys[0] = reader.ReadBytes(0x10); - EncKeys[1] = reader.ReadBytes(0x10); - Length = reader.ReadInt64(); - } - - private void DeriveKeys(Keyset keyset, string sdPath, IStorage storage) - { - var validationHashKey = new byte[0x60]; - storage.Read(validationHashKey, 0x20); - - // Try both the NCA and save key sources and pick the one that works - for (int k = 0; k < 2; k++) - { - var naxSpecificKeys = Util.CreateJaggedArray(2, 0x10); - var hashKey = new byte[0x10]; - Array.Copy(keyset.SdCardKeys[k], hashKey, 0x10); - - // Use the sd path to generate the kek for this NAX0 - var hash = new HMACSHA256(hashKey); - byte[] sdPathBytes = Encoding.ASCII.GetBytes(sdPath); - byte[] checksum = hash.ComputeHash(sdPathBytes, 0, sdPathBytes.Length); - Array.Copy(checksum, 0, naxSpecificKeys[0], 0, 0x10); - Array.Copy(checksum, 0x10, naxSpecificKeys[1], 0, 0x10); - - // Decrypt this NAX0's keys - Crypto.DecryptEcb(naxSpecificKeys[0], EncKeys[0], Keys[0], 0x10); - Crypto.DecryptEcb(naxSpecificKeys[1], EncKeys[1], Keys[1], 0x10); - Array.Copy(Keys[0], 0, Key, 0, 0x10); - Array.Copy(Keys[1], 0, Key, 0x10, 0x10); - - // Copy the decrypted keys into the NAX0 header and use that for the HMAC key - // for validating that the keys are correct - Array.Copy(Keys[0], 0, validationHashKey, 8, 0x10); - Array.Copy(Keys[1], 0, validationHashKey, 0x18, 0x10); - - var validationHash = new HMACSHA256(validationHashKey); - byte[] validationMac = validationHash.ComputeHash(keyset.SdCardKeys[k], 0x10, 0x10); - - if (Util.ArraysEqual(Hmac, validationMac)) - { - return; - } - } - - throw new ArgumentException("NAX0 key derivation failed."); - } - - public void Dispose() - { - if (!LeaveOpen) - { - BaseStorage?.Dispose(); - } - } - } -} diff --git a/src/LibHac/SwitchFs.cs b/src/LibHac/SwitchFs.cs index fb75b7f9..75b2e70f 100644 --- a/src/LibHac/SwitchFs.cs +++ b/src/LibHac/SwitchFs.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using System.Text; using LibHac.IO; using LibHac.IO.Save; @@ -12,44 +11,23 @@ namespace LibHac public class SwitchFs : IDisposable { public Keyset Keyset { get; } - public IFileSystemOld Fs { get; } - public string ContentsDir { get; } - public string SaveDir { get; } + public IAttributeFileSystem BaseFs { get; } + public AesXtsFileSystem Fs { get; } public Dictionary Ncas { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public Dictionary Saves { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public Dictionary Titles { get; } = new Dictionary(); public Dictionary Applications { get; } = new Dictionary(); - public SwitchFs(Keyset keyset, IFileSystemOld fs) + public SwitchFs(Keyset keyset, IAttributeFileSystem fs) { - Fs = fs; + BaseFs = fs; Keyset = keyset; - if (fs.DirectoryExists("Nintendo")) - { - ContentsDir = fs.GetFullPath(Path.Combine("Nintendo", "Contents")); - SaveDir = fs.GetFullPath(Path.Combine("Nintendo", "save")); - } - else - { - if (fs.DirectoryExists("Contents")) - { - ContentsDir = fs.GetFullPath("Contents"); - } + var concatFs = new ConcatenationFileSystem(BaseFs); + Fs = new AesXtsFileSystem(concatFs, keyset.SdCardKeys[1], 0x4000); - if (fs.DirectoryExists("save")) - { - SaveDir = fs.GetFullPath("save"); - } - } - - if (ContentsDir == null) - { - throw new DirectoryNotFoundException("Could not find \"Contents\" directory"); - } - - OpenAllSaves(); + // OpenAllSaves(); OpenAllNcas(); ReadTitles(); ReadControls(); @@ -58,88 +36,70 @@ namespace LibHac private void OpenAllNcas() { - string[] files = Fs.GetFileSystemEntries(ContentsDir, "*.nca", SearchOption.AllDirectories); + IEnumerable files = Fs.OpenDirectory("/", OpenDirectoryMode.All).EnumerateEntries("*.nca", SearchOptions.RecurseSubdirectories); - foreach (string file in files) + foreach (DirectoryEntry fileEntry in files) { Nca nca = null; try { - bool isNax0; - IStorage storage = OpenSplitNcaStorage(Fs, file); - if (storage == null) continue; + var storage = new FileStorage(Fs.OpenFile(fileEntry.FullPath, OpenMode.Read)); - using (var reader = new BinaryReader(storage.AsStream(), Encoding.Default, true)) - { - reader.BaseStream.Position = 0x20; - isNax0 = reader.ReadUInt32() == 0x3058414E; // NAX0 - reader.BaseStream.Position = 0; - } + nca = new Nca(Keyset, storage, false); - if (isNax0) - { - string sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/'); - var nax0 = new Nax0(Keyset, storage, sdPath, false); - nca = new Nca(Keyset, nax0.BaseStorage, false); - } - else - { - nca = new Nca(Keyset, storage, false); - } - - nca.NcaId = Path.GetFileNameWithoutExtension(file); + nca.NcaId = Path.GetFileNameWithoutExtension(fileEntry.Name); string extension = nca.Header.ContentType == ContentType.Meta ? ".cnmt.nca" : ".nca"; nca.Filename = nca.NcaId + extension; } catch (MissingKeyException ex) { if (ex.Name == null) - { Console.WriteLine($"{ex.Message} File:\n{file}"); } + { Console.WriteLine($"{ex.Message} File:\n{fileEntry}"); } else { string name = ex.Type == KeyType.Title ? $"Title key for rights ID {ex.Name}" : ex.Name; - Console.WriteLine($"{ex.Message}\nKey: {name}\nFile: {file}"); + Console.WriteLine($"{ex.Message}\nKey: {name}\nFile: {fileEntry}"); } } catch (Exception ex) { - Console.WriteLine($"{ex.Message} File: {file}"); + Console.WriteLine($"{ex.Message} File: {fileEntry}"); } if (nca?.NcaId != null) Ncas.Add(nca.NcaId, nca); } } - private void OpenAllSaves() - { - if (SaveDir == null) return; + //private void OpenAllSaves() + //{ + // if (SaveDir == null) return; - string[] files = Fs.GetFileSystemEntries(SaveDir, "*"); + // string[] files = Fs.GetFileSystemEntries(SaveDir, "*"); - foreach (string file in files) - { - SaveDataFileSystem save = null; - string saveName = Path.GetFileNameWithoutExtension(file); + // foreach (string file in files) + // { + // SaveDataFileSystem save = null; + // string saveName = Path.GetFileNameWithoutExtension(file); - try - { - IStorage storage = Fs.OpenFile(file, FileMode.Open).AsStorage(); + // try + // { + // IStorage storage = Fs.OpenFile(file, FileMode.Open).AsStorage(); - string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/'); - var nax0 = new Nax0(Keyset, storage, sdPath, false); - save = new SaveDataFileSystem(Keyset, nax0.BaseStorage, IntegrityCheckLevel.None, true); - } - catch (Exception ex) - { - Console.WriteLine($"{ex.Message} File: {file}"); - } + // string sdPath = "/" + Util.GetRelativePath(file, SaveDir).Replace('\\', '/'); + // var nax0 = new Nax0(Keyset, storage, sdPath, false); + // save = new SaveDataFileSystem(Keyset, nax0.BaseStorage, IntegrityCheckLevel.None, true); + // } + // catch (Exception ex) + // { + // Console.WriteLine($"{ex.Message} File: {file}"); + // } - if (save != null && saveName != null) - { - Saves[saveName] = save; - } - } - } + // if (save != null && saveName != null) + // { + // Saves[saveName] = save; + // } + // } + //} private void ReadTitles() { @@ -232,49 +192,6 @@ namespace LibHac } } - internal static IStorage OpenSplitNcaStorage(IFileSystemOld fs, string path) - { - var files = new List(); - var storages = new List(); - - if (fs.DirectoryExists(path)) - { - while (true) - { - string partName = Path.Combine(path, $"{files.Count:D2}"); - if (!fs.FileExists(partName)) break; - - files.Add(partName); - } - } - else if (fs.FileExists(path)) - { - if (Path.GetFileName(path) != "00") - { - return fs.OpenFile(path, FileMode.Open, FileAccess.Read).AsStorage(); - } - files.Add(path); - } - else - { - throw new FileNotFoundException("Could not find the input file or directory"); - } - - if (files.Count == 1) - { - return fs.OpenFile(files[0], FileMode.Open, FileAccess.Read).AsStorage(); - } - - foreach (string file in files) - { - storages.Add(fs.OpenFile(file, FileMode.Open, FileAccess.Read).AsStorage()); - } - - if (storages.Count == 0) return null; //todo - - return new ConcatenationStorage(storages, true); - } - private void DisposeNcas() { foreach (Nca nca in Ncas.Values) diff --git a/src/hactoolnet/ProcessSwitchFs.cs b/src/hactoolnet/ProcessSwitchFs.cs index ab2eaf10..912c406e 100644 --- a/src/hactoolnet/ProcessSwitchFs.cs +++ b/src/hactoolnet/ProcessSwitchFs.cs @@ -13,7 +13,7 @@ namespace hactoolnet { public static void Process(Context ctx) { - var switchFs = new SwitchFs(ctx.Keyset, new FileSystem(ctx.Options.InFile)); + var switchFs = new SwitchFs(ctx.Keyset, new LocalFileSystem($"{ctx.Options.InFile}/Nintendo/Contents")); if (ctx.Options.ListNcas) {