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 ulong TitleId { get; set; }
public uint TitleVersion { get; set; }
public TitleVersion TitleVersion { get; set; }
public TitleType Type { get; set; }
public byte FieldD { get; set; }
public int TableOffset { get; set; }
@ -13,27 +13,57 @@ namespace libhac
public int MetaEntryCount { 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)
{
using (var reader = new BinaryReader(file))
{
TitleId = reader.ReadUInt64();
TitleVersion = reader.ReadUInt32();
var version = reader.ReadUInt32();
Type = (TitleType)reader.ReadByte();
TitleVersion = new TitleVersion(version, Type < TitleType.Application);
FieldD = reader.ReadByte();
TableOffset = reader.ReadUInt16();
ContentEntryCount = reader.ReadUInt16();
MetaEntryCount = reader.ReadUInt16();
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];
MetaEntries = new CnmtMetaEntry[MetaEntryCount];
for (int i = 0; i < ContentEntryCount; i++)
{
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
{
Meta,
@ -70,10 +116,10 @@ namespace libhac
public enum TitleType
{
SystemProgram = 1,
SystemDataArchive,
SystemData,
SystemUpdate,
FirmwarePackageA,
FirmwarePackageB,
BootImagePackage,
BootImagePackageSafe,
Application = 0x80,
Patch,
AddOnContent,

View file

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

View file

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

View file

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
@ -78,40 +77,6 @@ namespace libhac
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()
{
if (!KeepOpen)

View file

@ -225,18 +225,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 int Major { get; }
public int Minor { get; }
public int Patch { get; }
public int Revision { get; }
public TitleVersion(uint version)
public TitleVersion(uint version, bool isSystemTitle = false)
{
Version = version;
Revision = (byte)version;
Patch = (byte)(version >> 8);
Minor = (byte)(version >> 16);
Major = (byte)(version >> 24);
if (isSystemTitle)
{
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()

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
namespace libhac
{
@ -11,23 +12,26 @@ namespace libhac
public Keyset Keyset { get; }
public string RootDir { get; }
public string ContentsDir { get; }
public string[] Files { get; }
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>();
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;
Keyset = keyset;
ContentsDir = Path.Combine(sdPath, "Nintendo", "Contents");
ContentsDir = Path.Combine(rootDir, "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();
ReadTitles();
ReadControls();
@ -35,15 +39,36 @@ namespace libhac
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;
try
{
var sdPath = "/" + Util.GetRelativePath(file, ContentsDir).Replace('\\', '/');
var nax0 = Nax0.CreateFromPath(Keyset, file, sdPath);
Nax0s.Add(nax0);
nca = new Nca(Keyset, nax0.Stream, false);
bool isNax0;
Stream stream = OpenSplitNcaStream(file);
if (stream == null) continue;
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);
var extention = nca.Header.ContentType == ContentType.Meta ? ".cnmt.nca" : ".nca";
nca.Filename = nca.NcaId + extention;
@ -70,7 +95,7 @@ namespace libhac
var metadata = new Cnmt(new MemoryStream(file));
title.Id = metadata.TitleId;
title.Version = new TitleVersion(metadata.TitleVersion);
title.Version = metadata.TitleVersion;
title.Metadata = metadata;
title.MetaNca = 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()
{
foreach (Nca nca in Ncas.Values)
@ -155,4 +219,44 @@ namespace libhac
public Nca ProgramNca { 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 = "";
}
}
}
}