Support reading Switch user and system partition contents

This commit is contained in:
Alex Barney 2018-07-01 15:12:59 -05:00
parent d5ddde4909
commit 44fb0d39d4
6 changed files with 192 additions and 69 deletions

View file

@ -5,7 +5,7 @@ namespace libhac
public class Cnmt public class Cnmt
{ {
public ulong TitleId { get; set; } public ulong TitleId { get; set; }
public uint TitleVersion { get; set; } public TitleVersion TitleVersion { get; set; }
public TitleType Type { get; set; } public TitleType Type { get; set; }
public byte FieldD { get; set; } public byte FieldD { get; set; }
public int TableOffset { get; set; } public int TableOffset { get; set; }
@ -13,27 +13,57 @@ namespace libhac
public int MetaEntryCount { get; set; } public int MetaEntryCount { get; set; }
public CnmtContentEntry[] ContentEntries { get; set; } public CnmtContentEntry[] ContentEntries { get; set; }
public CnmtMetaEntry[] MetaEntries { get; set; }
public ulong ApplicationTitleId { get; set; }
public ulong PatchTitleId { get; set; }
public TitleVersion MinimumSystemVersion { get; }
public TitleVersion MinimumApplicationVersion { get; }
public Cnmt(Stream file) public Cnmt(Stream file)
{ {
using (var reader = new BinaryReader(file)) using (var reader = new BinaryReader(file))
{ {
TitleId = reader.ReadUInt64(); TitleId = reader.ReadUInt64();
TitleVersion = reader.ReadUInt32(); var version = reader.ReadUInt32();
Type = (TitleType)reader.ReadByte(); Type = (TitleType)reader.ReadByte();
TitleVersion = new TitleVersion(version, Type < TitleType.Application);
FieldD = reader.ReadByte(); FieldD = reader.ReadByte();
TableOffset = reader.ReadUInt16(); TableOffset = reader.ReadUInt16();
ContentEntryCount = reader.ReadUInt16(); ContentEntryCount = reader.ReadUInt16();
MetaEntryCount = reader.ReadUInt16(); MetaEntryCount = reader.ReadUInt16();
file.Position += 12; file.Position += 12;
file.Position += TableOffset;
switch (Type)
{
case TitleType.Application:
PatchTitleId = reader.ReadUInt64();
MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true);
break;
case TitleType.Patch:
ApplicationTitleId = reader.ReadUInt64();
MinimumSystemVersion = new TitleVersion(reader.ReadUInt32(), true);
break;
case TitleType.AddOnContent:
ApplicationTitleId = reader.ReadUInt64();
MinimumApplicationVersion = new TitleVersion(reader.ReadUInt32());
break;
}
file.Position = 0x20 + TableOffset;
ContentEntries = new CnmtContentEntry[ContentEntryCount]; ContentEntries = new CnmtContentEntry[ContentEntryCount];
MetaEntries = new CnmtMetaEntry[MetaEntryCount];
for (int i = 0; i < ContentEntryCount; i++) for (int i = 0; i < ContentEntryCount; i++)
{ {
ContentEntries[i] = new CnmtContentEntry(reader); ContentEntries[i] = new CnmtContentEntry(reader);
} }
for (int i = 0; i < MetaEntryCount; i++)
{
MetaEntries[i] = new CnmtMetaEntry(reader);
}
} }
} }
} }
@ -56,6 +86,22 @@ namespace libhac
} }
} }
public class CnmtMetaEntry
{
public ulong TitleId { get; }
public TitleVersion Version { get; }
public CnmtContentType Type { get; }
public CnmtMetaEntry(BinaryReader reader)
{
TitleId = reader.ReadUInt64();
Version = new TitleVersion(reader.ReadUInt32(), true);
Type = (CnmtContentType)reader.ReadByte();
reader.BaseStream.Position += 3;
}
}
public enum CnmtContentType public enum CnmtContentType
{ {
Meta, Meta,
@ -70,10 +116,10 @@ namespace libhac
public enum TitleType public enum TitleType
{ {
SystemProgram = 1, SystemProgram = 1,
SystemDataArchive, SystemData,
SystemUpdate, SystemUpdate,
FirmwarePackageA, BootImagePackage,
FirmwarePackageB, BootImagePackageSafe,
Application = 0x80, Application = 0x80,
Patch, Patch,
AddOnContent, AddOnContent,

View file

@ -107,7 +107,7 @@ namespace libhac
continue; continue;
} }
keyset.TitleKeys.Add(rightsId, titleKey); keyset.TitleKeys[rightsId] = titleKey;
} }
} }

View file

@ -1,7 +1,4 @@
using System; using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace libhac namespace libhac
{ {

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
@ -78,40 +77,6 @@ namespace libhac
if (!isValid) throw new ArgumentException("NAX0 key derivation failed."); if (!isValid) throw new ArgumentException("NAX0 key derivation failed.");
} }
public static Nax0 CreateFromPath(Keyset keyset, string path, string sdPath)
{
List<string> files = new List<string>();
List<Stream> streams = new List<Stream>();
if (Directory.Exists(path))
{
while (true)
{
var partName = Path.Combine(path, $"{files.Count:D2}");
if (!File.Exists(partName)) break;
files.Add(partName);
}
}
else if (File.Exists(path))
{
files.Add(path);
}
else
{
throw new FileNotFoundException("Could not find the input file or directory");
}
foreach (var file in files)
{
streams.Add(new FileStream(file, FileMode.Open));
}
var stream = new CombinationStream(streams);
return new Nax0(keyset, stream, sdPath, false);
}
public void Dispose() public void Dispose()
{ {
if (!KeepOpen) if (!KeepOpen)

View file

@ -225,18 +225,29 @@ namespace libhac
public class TitleVersion public class TitleVersion
{ {
public uint Version { get; } public uint Version { get; }
public byte Major { get; } public int Major { get; }
public byte Minor { get; } public int Minor { get; }
public byte Patch { get; } public int Patch { get; }
public byte Revision { get; } public int Revision { get; }
public TitleVersion(uint version) public TitleVersion(uint version, bool isSystemTitle = false)
{ {
Version = version; Version = version;
Revision = (byte)version;
Patch = (byte)(version >> 8); if (isSystemTitle)
Minor = (byte)(version >> 16); {
Major = (byte)(version >> 24); Revision = (int)(version & ((1 << 16) - 1));
Patch = (int)((version >> 16) & ((1 << 4) - 1));
Minor = (int)((version >> 20) & ((1 << 6) - 1));
Major = (int)((version >> 26) & ((1 << 6) - 1));
}
else
{
Revision = (byte)version;
Patch = (byte)(version >> 8);
Minor = (byte)(version >> 16);
Major = (byte)(version >> 24);
}
} }
public override string ToString() public override string ToString()
@ -244,7 +255,7 @@ namespace libhac
return $"{Major}.{Minor}.{Patch}.{Revision}"; return $"{Major}.{Minor}.{Patch}.{Revision}";
} }
} }
public enum ContentType public enum ContentType
{ {
Program, Program,

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
namespace libhac namespace libhac
{ {
@ -11,23 +12,26 @@ namespace libhac
public Keyset Keyset { get; } public Keyset Keyset { get; }
public string RootDir { get; } public string RootDir { get; }
public string ContentsDir { get; } public string ContentsDir { get; }
public string[] Files { get; }
public Dictionary<string, Nca> Ncas { get; } = new Dictionary<string, Nca>(StringComparer.OrdinalIgnoreCase); public Dictionary<string, Nca> Ncas { get; } = new Dictionary<string, Nca>(StringComparer.OrdinalIgnoreCase);
public Dictionary<ulong, Title> Titles { get; } = new Dictionary<ulong, Title>(); 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 rootDir)
{ {
if (Directory.Exists(Path.Combine(sdPath, "Nintendo"))) RootDir = rootDir;
Keyset = keyset;
if (Directory.Exists(Path.Combine(rootDir, "Nintendo")))
{ {
RootDir = sdPath; ContentsDir = Path.Combine(rootDir, "Nintendo", "Contents");
Keyset = keyset; }
ContentsDir = Path.Combine(sdPath, "Nintendo", "Contents"); else if (Directory.Exists(Path.Combine(rootDir, "Contents")))
{
ContentsDir = Path.Combine(rootDir, "Contents");
} }
Files = Directory.GetFiles(ContentsDir, "00", SearchOption.AllDirectories).Select(Path.GetDirectoryName).ToArray();
OpenAllNcas(); OpenAllNcas();
ReadTitles(); ReadTitles();
ReadControls(); ReadControls();
@ -35,15 +39,36 @@ namespace libhac
private void OpenAllNcas() private void OpenAllNcas()
{ {
foreach (var file in Files) string[] files = Directory.GetFileSystemEntries(ContentsDir, "*.nca", SearchOption.AllDirectories).ToArray();
foreach (var file in files)
{ {
Nca nca = null; Nca nca = null;
try try
{ {
var sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/'); bool isNax0;
var nax0 = Nax0.CreateFromPath(Keyset, file, sdPath); Stream stream = OpenSplitNcaStream(file);
Nax0s.Add(nax0); if (stream == null) continue;
nca = new Nca(Keyset, nax0.Stream, false);
using (var reader = new BinaryReader(stream, Encoding.Default, true))
{
stream.Position = 0x20;
isNax0 = reader.ReadUInt32() == 0x3058414E; // NAX0
stream.Position = 0;
}
if (isNax0)
{
var sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/');
var nax0 = new Nax0(Keyset, stream, sdPath, false);
Nax0s.Add(nax0);
nca = new Nca(Keyset, nax0.Stream, false);
}
else
{
nca = new Nca(Keyset, stream, false);
}
nca.NcaId = Path.GetFileNameWithoutExtension(file); nca.NcaId = Path.GetFileNameWithoutExtension(file);
var extention = nca.Header.ContentType == ContentType.Meta ? ".cnmt.nca" : ".nca"; var extention = nca.Header.ContentType == ContentType.Meta ? ".cnmt.nca" : ".nca";
nca.Filename = nca.NcaId + extention; nca.Filename = nca.NcaId + extention;
@ -70,7 +95,7 @@ namespace libhac
var metadata = new Cnmt(new MemoryStream(file)); var metadata = new Cnmt(new MemoryStream(file));
title.Id = metadata.TitleId; title.Id = metadata.TitleId;
title.Version = new TitleVersion(metadata.TitleVersion); title.Version = metadata.TitleVersion;
title.Metadata = metadata; title.Metadata = metadata;
title.MetaNca = nca; title.MetaNca = nca;
title.Ncas.Add(nca); title.Ncas.Add(nca);
@ -122,6 +147,45 @@ namespace libhac
} }
} }
internal static Stream OpenSplitNcaStream(string path)
{
List<string> files = new List<string>();
List<Stream> streams = new List<Stream>();
if (Directory.Exists(path))
{
while (true)
{
var partName = Path.Combine(path, $"{files.Count:D2}");
if (!File.Exists(partName)) break;
files.Add(partName);
}
}
else if (File.Exists(path))
{
if (Path.GetFileName(path) != "00")
{
return new FileStream(path, FileMode.Open, FileAccess.Read);
}
files.Add(path);
}
else
{
throw new FileNotFoundException("Could not find the input file or directory");
}
foreach (var file in files)
{
streams.Add(new FileStream(file, FileMode.Open, FileAccess.Read));
}
if (streams.Count == 0) return null;
var stream = new CombinationStream(streams);
return stream;
}
private void DisposeNcas() private void DisposeNcas()
{ {
foreach (Nca nca in Ncas.Values) foreach (Nca nca in Ncas.Values)
@ -155,4 +219,44 @@ namespace libhac
public Nca ProgramNca { get; internal set; } public Nca ProgramNca { get; internal set; }
public Nca ControlNca { get; internal set; } public Nca ControlNca { get; internal set; }
} }
public class Application
{
public Title Main { get; private set; }
public Title Patch { get; private set; }
public List<Title> AddOnContent { get; private set; }
public string Name { get; private set; }
public string Version { get; private set; }
public void SetMainTitle(Title title)
{
Main = title;
}
public void SetPatchTitle(Title title)
{
if (title.Metadata.Type != TitleType.Patch) throw new InvalidDataException("Title is not a patch");
Patch = title;
}
private void UpdateName()
{
if (Patch != null)
{
Name = Patch.Name;
Version = Patch.Control?.Version ?? "";
}
else if (Main != null)
{
Name = Main.Name;
Version = Main.Control?.Version ?? "";
}
else
{
Name = "";
Version = "";
}
}
}
} }