mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Support reading Switch user and system partition contents
This commit is contained in:
parent
d5ddde4909
commit
44fb0d39d4
6 changed files with 192 additions and 69 deletions
|
@ -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,
|
||||||
|
|
|
@ -107,7 +107,7 @@ namespace libhac
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
keyset.TitleKeys.Add(rightsId, titleKey);
|
keyset.TitleKeys[rightsId] = titleKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
using System;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace libhac
|
namespace libhac
|
||||||
{
|
{
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
130
libhac/SdFs.cs
130
libhac/SdFs.cs
|
@ -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 = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue