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 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,
|
||||
|
|
|
@ -107,7 +107,7 @@ namespace libhac
|
|||
continue;
|
||||
}
|
||||
|
||||
keyset.TitleKeys.Add(rightsId, titleKey);
|
||||
keyset.TitleKeys[rightsId] = titleKey;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.IO;
|
||||
|
||||
namespace libhac
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
130
libhac/SdFs.cs
130
libhac/SdFs.cs
|
@ -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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue