Use SdFs class to read titles

This commit is contained in:
Alex Barney 2018-06-26 19:10:21 -05:00
parent dbf3eba32a
commit e4d9b46e60
5 changed files with 144 additions and 93 deletions

View file

@ -11,108 +11,59 @@ namespace hactoolnet
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
var sdfs = LoadSdFs(args);
Console.WriteLine("Listing NCA files"); Console.WriteLine("Listing NCA files");
ListSdContents(args); ListNcas(sdfs);
Console.WriteLine("Listing titles"); Console.WriteLine("Listing titles");
var watch = Stopwatch.StartNew(); ListTitles(sdfs);
DumpMeta(args); //DecryptNax0(sdfs, "C0628FB07A89E9050BDA258F74868E8D");
watch.Stop();
Console.WriteLine(watch.Elapsed.TotalMilliseconds);
} }
static void DecryptNax0(string[] args) static void DecryptNax0(SdFs sdFs, string name)
{ {
var keyset = ExternalKeys.ReadKeyFile(args[0]); var nca = sdFs.Ncas[name];
keyset.SetSdSeed(args[1].ToBytes()); using (var output = new FileStream($"{nca.NcaId}.nca", FileMode.Create))
using (var progress = new ProgressBar())
using (var nax0 = Nax0.CreateFromPath(keyset, args[2], args[3]))
{ {
var nca = new Nca(keyset, nax0.Stream, true); progress.LogMessage($"Title ID: {nca.Header.TitleId:X16}");
progress.LogMessage($"Writing {nca.NcaId}.nca");
using (var output = new FileStream(args[4], FileMode.Create)) nca.Stream.Position = 0;
using (var progress = new ProgressBar()) nca.Stream.CopyStream(output, nca.Stream.Length, progress);
{
progress.LogMessage($"Title ID: {nca.Header.TitleId:X16}");
progress.LogMessage($"Writing {args[4]}");
nax0.Stream.CopyStream(output, nax0.Stream.Length, progress);
}
} }
} }
static void ListSdContents(string[] args) static SdFs LoadSdFs(string[] args)
{ {
Console.WriteLine($"Using key file {args[0]}");
Console.WriteLine($"SD seed {BitConverter.ToString(args[1].ToBytes())}");
Console.WriteLine($"SD path {args[2]}");
var keyset = ExternalKeys.ReadKeyFile(args[0]); var keyset = ExternalKeys.ReadKeyFile(args[0]);
if (keyset.master_keys[0].IsEmpty())
{
Console.WriteLine("Need master key 0");
}
keyset.SetSdSeed(args[1].ToBytes()); keyset.SetSdSeed(args[1].ToBytes());
using (var sdfs = new SdFs(keyset, args[2])) var sdfs = new SdFs(keyset, args[2]);
{ return sdfs;
sdfs.OpenAllNcas(); }
foreach (Nca nca in sdfs.Ncas) static void ListNcas(SdFs sdfs)
{
foreach (Nca nca in sdfs.Ncas.Values.OrderBy(x => x.Header.TitleId))
{
Console.WriteLine($"{nca.Header.TitleId:X16} {nca.Header.ContentType.ToString().PadRight(10, ' ')} {nca.NcaId}");
}
}
static void ListTitles(SdFs sdfs)
{
foreach (var title in sdfs.Titles.Values.OrderBy(x => x.Id))
{
Console.WriteLine($"{title.Id:X16} v{title.Version.Version} ({title.Version}) {title.Metadata.Type}");
foreach (var content in title.Metadata.ContentEntries)
{ {
Console.WriteLine( Console.WriteLine(
$"{nca.Header.TitleId:X16} {nca.Header.ContentType.ToString().PadRight(10, ' ')} {nca.Name}"); $" {BitConverter.ToString(content.NcaId).Replace("-", "")}.nca {content.Type} {Util.GetBytesReadable(content.Size)}");
} }
}
}
static void DumpMeta(string[] args) Console.WriteLine("");
{
var keyset = ExternalKeys.ReadKeyFile(args[0]);
keyset.SetSdSeed(args[1].ToBytes());
List<Nca> ncas;
using (var sdfs = new SdFs(keyset, args[2]))
{
sdfs.OpenAllNcas();
ncas = sdfs.Ncas;
var metadata = new List<Cnmt>();
using (var progress = new ProgressBar())
{
foreach (var nca in ncas.Where(x => x.Header.ContentType == ContentType.Meta))
{
foreach (var section in nca.Sections.Where(x => x.Header.FsType == SectionFsType.Pfs0))
{
var sect = nca.OpenSection(section.SectionNum);
var pfs0 = new Pfs0(sect);
foreach (var entry in pfs0.Entries)
{
var path = Path.Combine("meta", entry.Name);
Directory.CreateDirectory(Path.GetDirectoryName(path));
var file = pfs0.GetFile(entry.Index);
metadata.Add(new Cnmt(new MemoryStream(file)));
File.WriteAllBytes(path, file);
}
}
}
foreach (var meta in metadata.OrderBy(x => x.TitleId))
{
progress.LogMessage($"{meta.TitleId:X16} v{meta.TitleVersion} {meta.Type}");
foreach (var content in meta.ContentEntries)
{
// Add an actual hexdump function
progress.LogMessage(
$" {BitConverter.ToString(content.NcaId).Replace("-", "")}.nca {content.Type}");
}
progress.LogMessage("");
}
}
} }
} }
} }

View file

@ -8,7 +8,7 @@ namespace libhac
public class Nca : IDisposable public class Nca : IDisposable
{ {
public NcaHeader Header { get; private set; } public NcaHeader Header { get; private set; }
public string Name { get; set; } public string NcaId { get; set; }
public bool HasRightsId { get; private set; } public bool HasRightsId { get; private set; }
public int CryptoType { get; private set; } public int CryptoType { get; private set; }
public byte[][] DecryptedKeys { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x10); public byte[][] DecryptedKeys { get; } = Util.CreateJaggedArray<byte[][]>(4, 0x10);

View file

@ -31,7 +31,7 @@ namespace libhac
head.Signature1 = reader.ReadBytes(0x100); head.Signature1 = reader.ReadBytes(0x100);
head.Signature2 = reader.ReadBytes(0x100); head.Signature2 = reader.ReadBytes(0x100);
head.Magic = reader.ReadAscii(4); head.Magic = reader.ReadAscii(4);
if(head.Magic != "NCA3") throw new InvalidDataException("Not an NCA3 file"); if (head.Magic != "NCA3") throw new InvalidDataException("Not an NCA3 file");
head.Distribution = reader.ReadByte(); head.Distribution = reader.ReadByte();
head.ContentType = (ContentType)reader.ReadByte(); head.ContentType = (ContentType)reader.ReadByte();
head.CryptoType = reader.ReadByte(); head.CryptoType = reader.ReadByte();
@ -216,6 +216,29 @@ namespace libhac
} }
} }
public class TitleVersion
{
public uint Version { get; }
public byte Major { get; }
public byte Minor { get; }
public byte Patch { get; }
public byte Revision { get; }
public TitleVersion(uint version)
{
Version = version;
Revision = (byte)version;
Patch = (byte)(version >> 8);
Minor = (byte)(version >> 16);
Major = (byte)(version >> 24);
}
public override string ToString()
{
return $"{Major}.{Minor}.{Patch}.{Revision}";
}
}
public enum ContentType public enum ContentType
{ {
Program, Program,

View file

@ -11,7 +11,10 @@ namespace libhac
public string RootDir { get; } public string RootDir { get; }
public string ContentsDir { get; } public string ContentsDir { get; }
public string[] Files { get; } public string[] Files { get; }
public List<Nca> Ncas { get; } = new List<Nca>();
public Dictionary<string, Nca> Ncas { get; } = new Dictionary<string, Nca>(StringComparer.OrdinalIgnoreCase);
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>();
private List<Nax0> Nax0s { get; } = new List<Nax0>(); private List<Nax0> Nax0s { get; } = new List<Nax0>();
public SdFs(Keyset keyset, string sdPath) public SdFs(Keyset keyset, string sdPath)
@ -24,6 +27,8 @@ namespace libhac
} }
Files = Directory.GetFiles(ContentsDir, "00", SearchOption.AllDirectories).Select(Path.GetDirectoryName).ToArray(); Files = Directory.GetFiles(ContentsDir, "00", SearchOption.AllDirectories).Select(Path.GetDirectoryName).ToArray();
OpenAllNcas();
ReadTitles();
} }
public void OpenAllNcas() public void OpenAllNcas()
@ -37,30 +42,47 @@ namespace libhac
var nax0 = Nax0.CreateFromPath(Keyset, file, sdPath); var nax0 = Nax0.CreateFromPath(Keyset, file, sdPath);
Nax0s.Add(nax0); Nax0s.Add(nax0);
nca = new Nca(Keyset, nax0.Stream, false); nca = new Nca(Keyset, nax0.Stream, false);
nca.Name = Path.GetFileName(file); nca.NcaId = Path.GetFileNameWithoutExtension(file);
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"{ex.Message} {file}"); Console.WriteLine($"{ex.Message} {file}");
} }
if (nca != null) Ncas.Add(nca); if (nca != null) Ncas.Add(nca.NcaId, nca);
}
}
public void ReadTitles()
{
foreach (var nca in Ncas.Values.Where(x => x.Header.ContentType == ContentType.Meta))
{
var title = new Title();
// Meta contents always have 1 Partition FS section with 1 file in it
Stream sect = nca.OpenSection(0);
var pfs0 = new Pfs0(sect);
var file = pfs0.GetFile(0);
var metadata = new Cnmt(new MemoryStream(file));
title.Id = metadata.TitleId;
title.Version = new TitleVersion(metadata.TitleVersion);
title.Metadata = metadata;
Titles.Add(title.Id, title);
} }
} }
private void DisposeNcas() private void DisposeNcas()
{ {
foreach (var nca in Ncas) foreach (Nca nca in Ncas.Values)
{ {
nca.Dispose(); nca.Dispose();
} }
Ncas.Clear(); Ncas.Clear();
foreach (var nax0 in Nax0s) // Disposing the Nca disposes the Nax0 as well
{
nax0.Dispose();
}
Nax0s.Clear(); Nax0s.Clear();
Titles.Clear();
} }
public void Dispose() public void Dispose()
@ -68,4 +90,12 @@ namespace libhac
DisposeNcas(); DisposeNcas();
} }
} }
public class Title
{
public ulong Id { get; internal set; }
public TitleVersion Version { get; internal set; }
public List<Nca> Ncas { get; } = new List<Nca>();
public Cnmt Metadata { get; internal set; }
}
} }

View file

@ -181,5 +181,52 @@ namespace libhac
{ {
return MediaSize * media; return MediaSize * media;
} }
public static string GetBytesReadable(long bytes)
{
// Get absolute value
long absBytes = (bytes < 0 ? -bytes : bytes);
// Determine the suffix and readable value
string suffix;
double readable;
if (absBytes >= 0x1000000000000000) // Exabyte
{
suffix = "EB";
readable = (bytes >> 50);
}
else if (absBytes >= 0x4000000000000) // Petabyte
{
suffix = "PB";
readable = (bytes >> 40);
}
else if (absBytes >= 0x10000000000) // Terabyte
{
suffix = "TB";
readable = (bytes >> 30);
}
else if (absBytes >= 0x40000000) // Gigabyte
{
suffix = "GB";
readable = (bytes >> 20);
}
else if (absBytes >= 0x100000) // Megabyte
{
suffix = "MB";
readable = (bytes >> 10);
}
else if (absBytes >= 0x400) // Kilobyte
{
suffix = "KB";
readable = bytes;
}
else
{
return bytes.ToString("0 B"); // Byte
}
// Divide by 1024 to get fractional value
readable = (readable / 1024);
// Return formatted number with suffix
return readable.ToString("0.### ") + suffix;
}
} }
} }