mirror of
https://github.com/Thealexbarney/LibHac.git
synced 2024-11-14 10:49:41 +01:00
Add XCI signature checks and info output
This commit is contained in:
parent
c321f65f2a
commit
1f62706d4c
7 changed files with 264 additions and 158 deletions
132
LibHac/Nca.cs
132
LibHac/Nca.cs
|
@ -2,7 +2,6 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
|
||||||
using LibHac.Streams;
|
using LibHac.Streams;
|
||||||
using LibHac.XTSSharp;
|
using LibHac.XTSSharp;
|
||||||
|
|
||||||
|
@ -430,136 +429,5 @@ namespace LibHac
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Dump(this Nca nca)
|
|
||||||
{
|
|
||||||
int colLen = 36;
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
sb.AppendLine();
|
|
||||||
|
|
||||||
sb.AppendLine("NCA:");
|
|
||||||
PrintItem("Magic:", nca.Header.Magic);
|
|
||||||
PrintItem("Fixed-Key Signature:", nca.Header.Signature1);
|
|
||||||
PrintItem("NPDM Signature:", nca.Header.Signature2);
|
|
||||||
PrintItem("Content Size:", $"0x{nca.Header.NcaSize:x12}");
|
|
||||||
PrintItem("TitleID:", $"{nca.Header.TitleId:X16}");
|
|
||||||
PrintItem("SDK Version:", nca.Header.SdkVersion);
|
|
||||||
PrintItem("Distribution type:", nca.Header.Distribution);
|
|
||||||
PrintItem("Content Type:", nca.Header.ContentType);
|
|
||||||
PrintItem("Master Key Revision:", $"{nca.CryptoType} ({Util.GetKeyRevisionSummary(nca.CryptoType)})");
|
|
||||||
PrintItem("Encryption Type:", $"{(nca.HasRightsId ? "Titlekey crypto" : "Standard crypto")}");
|
|
||||||
|
|
||||||
if (nca.HasRightsId)
|
|
||||||
{
|
|
||||||
PrintItem("Rights ID:", nca.Header.RightsId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PrintItem("Key Area Encryption Key:", nca.Header.KaekInd);
|
|
||||||
sb.AppendLine("Key Area (Encrypted):");
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
PrintItem($" Key {i} (Encrypted):", nca.Header.EncryptedKeys[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
sb.AppendLine("Key Area (Decrypted):");
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
PrintItem($" Key {i} (Decrypted):", nca.DecryptedKeys[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintSections();
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
|
|
||||||
void PrintSections()
|
|
||||||
{
|
|
||||||
sb.AppendLine("Sections:");
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
NcaSection sect = nca.Sections[i];
|
|
||||||
if (sect == null) continue;
|
|
||||||
|
|
||||||
sb.AppendLine($" Section {i}:");
|
|
||||||
PrintItem(" Offset:", $"0x{sect.Offset:x12}");
|
|
||||||
PrintItem(" Size:", $"0x{sect.Size:x12}");
|
|
||||||
PrintItem(" Partition Type:", sect.IsExefs ? "ExeFS" : sect.Type.ToString());
|
|
||||||
PrintItem(" Section CTR:", sect.Header.Ctr);
|
|
||||||
|
|
||||||
switch (sect.Type)
|
|
||||||
{
|
|
||||||
case SectionType.Pfs0:
|
|
||||||
PrintPfs0(sect);
|
|
||||||
break;
|
|
||||||
case SectionType.Romfs:
|
|
||||||
PrintRomfs(sect);
|
|
||||||
break;
|
|
||||||
case SectionType.Bktr:
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
sb.AppendLine(" Unknown/invalid superblock!");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintPfs0(NcaSection sect)
|
|
||||||
{
|
|
||||||
var sBlock = sect.Pfs0.Superblock;
|
|
||||||
PrintItem($" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.MasterHash);
|
|
||||||
sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
|
|
||||||
|
|
||||||
PrintItem(" Offset:", $"0x{sBlock.HashTableOffset:x12}");
|
|
||||||
PrintItem(" Size:", $"0x{sBlock.HashTableSize:x12}");
|
|
||||||
PrintItem(" Block Size:", $"0x{sBlock.BlockSize:x}");
|
|
||||||
PrintItem(" PFS0 Offset:", $"0x{sBlock.Pfs0Offset:x12}");
|
|
||||||
PrintItem(" PFS0 Size:", $"0x{sBlock.Pfs0Size:x12}");
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintRomfs(NcaSection sect)
|
|
||||||
{
|
|
||||||
var sBlock = sect.Romfs.Superblock;
|
|
||||||
var levels = sect.Romfs.IvfcLevels;
|
|
||||||
|
|
||||||
PrintItem($" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.IvfcHeader.MasterHash);
|
|
||||||
PrintItem(" Magic:", sBlock.IvfcHeader.Magic);
|
|
||||||
PrintItem(" ID:", $"{sBlock.IvfcHeader.Id:x8}");
|
|
||||||
|
|
||||||
for (int i = 0; i < Romfs.IvfcMaxLevel; i++)
|
|
||||||
{
|
|
||||||
var level = levels[i];
|
|
||||||
sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
|
|
||||||
PrintItem(" Data Offset:", $"0x{level.DataOffset:x12}");
|
|
||||||
PrintItem(" Data Size:", $"0x{level.DataSize:x12}");
|
|
||||||
PrintItem(" Hash Offset:", $"0x{level.HashOffset:x12}");
|
|
||||||
PrintItem(" Hash BlockSize:", $"0x{level.HashBlockSize:x8}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintItem(string prefix, object data)
|
|
||||||
{
|
|
||||||
if (data is byte[] byteData)
|
|
||||||
{
|
|
||||||
sb.MemDump(prefix.PadRight(colLen), byteData);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sb.AppendLine(prefix.PadRight(colLen) + data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string GetValidityString(this Validity validity)
|
|
||||||
{
|
|
||||||
switch (validity)
|
|
||||||
{
|
|
||||||
case Validity.Invalid: return " (FAIL)";
|
|
||||||
case Validity.Valid: return " (GOOD)";
|
|
||||||
default: return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace LibHac
|
||||||
{
|
{
|
||||||
public class Romfs
|
public class Romfs
|
||||||
{
|
{
|
||||||
internal const int IvfcMaxLevel = 6;
|
public const int IvfcMaxLevel = 6;
|
||||||
public RomfsHeader Header { get; }
|
public RomfsHeader Header { get; }
|
||||||
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
|
public List<RomfsDir> Directories { get; } = new List<RomfsDir>();
|
||||||
public List<RomfsFile> Files { get; } = new List<RomfsFile>();
|
public List<RomfsFile> Files { get; } = new List<RomfsFile>();
|
||||||
|
|
|
@ -251,7 +251,7 @@ namespace LibHac
|
||||||
return new string(result);
|
return new string(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static long MediaToReal(long media)
|
public static long MediaToReal(long media)
|
||||||
{
|
{
|
||||||
return MediaSize * media;
|
return MediaSize * media;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,66 @@
|
||||||
using System.IO;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using LibHac.Streams;
|
using LibHac.Streams;
|
||||||
|
|
||||||
namespace LibHac
|
namespace LibHac
|
||||||
{
|
{
|
||||||
public class Xci
|
public class Xci
|
||||||
{
|
{
|
||||||
|
private const string RootPartitionName = "rootpt";
|
||||||
private const string UpdatePartitionName = "update";
|
private const string UpdatePartitionName = "update";
|
||||||
private const string NormalPartitionName = "normal";
|
private const string NormalPartitionName = "normal";
|
||||||
private const string SecurePartitionName = "secure";
|
private const string SecurePartitionName = "secure";
|
||||||
private const string LogoPartitionName = "logo";
|
private const string LogoPartitionName = "logo";
|
||||||
|
|
||||||
public XciHeader Header { get; }
|
public XciHeader Header { get; }
|
||||||
public Pfs RootPartition { get; }
|
|
||||||
public Pfs UpdatePartition { get; }
|
public XciPartition RootPartition { get; }
|
||||||
public Pfs NormalPartition { get; }
|
public XciPartition UpdatePartition { get; }
|
||||||
public Pfs SecurePartition { get; }
|
public XciPartition NormalPartition { get; }
|
||||||
public Pfs LogoPartition { get; }
|
public XciPartition SecurePartition { get; }
|
||||||
|
public XciPartition LogoPartition { get; }
|
||||||
|
|
||||||
|
public List<XciPartition> Partitions { get; } = new List<XciPartition>();
|
||||||
|
|
||||||
public Xci(Keyset keyset, Stream stream)
|
public Xci(Keyset keyset, Stream stream)
|
||||||
{
|
{
|
||||||
Header = new XciHeader(keyset, stream);
|
Header = new XciHeader(keyset, stream);
|
||||||
var hfs0Stream = new SubStream(stream, Header.PartitionFsHeaderAddress);
|
var hfs0Stream = new SubStream(stream, Header.PartitionFsHeaderAddress);
|
||||||
RootPartition = new Pfs(hfs0Stream);
|
|
||||||
|
|
||||||
if (RootPartition.TryOpenFile(UpdatePartitionName, out var updateStream))
|
RootPartition = new XciPartition(hfs0Stream)
|
||||||
{
|
{
|
||||||
UpdatePartition = new Pfs(updateStream);
|
Name = RootPartitionName,
|
||||||
|
Offset = Header.PartitionFsHeaderAddress
|
||||||
|
};
|
||||||
|
|
||||||
|
Partitions.Add(RootPartition);
|
||||||
|
|
||||||
|
foreach (PfsFileEntry file in RootPartition.Files)
|
||||||
|
{
|
||||||
|
Stream partitionStream = RootPartition.OpenFile(file);
|
||||||
|
|
||||||
|
var partition = new XciPartition(partitionStream)
|
||||||
|
{
|
||||||
|
Name = file.Name,
|
||||||
|
Offset = Header.PartitionFsHeaderAddress + RootPartition.HeaderSize + file.Offset
|
||||||
|
};
|
||||||
|
|
||||||
|
Partitions.Add(partition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RootPartition.TryOpenFile(NormalPartitionName, out var normalStream))
|
UpdatePartition = Partitions.FirstOrDefault(x => x.Name == UpdatePartitionName);
|
||||||
{
|
NormalPartition = Partitions.FirstOrDefault(x => x.Name == NormalPartitionName);
|
||||||
NormalPartition = new Pfs(normalStream);
|
SecurePartition = Partitions.FirstOrDefault(x => x.Name == SecurePartitionName);
|
||||||
}
|
LogoPartition = Partitions.FirstOrDefault(x => x.Name == LogoPartitionName);
|
||||||
|
|
||||||
if (RootPartition.TryOpenFile(SecurePartitionName, out var secureStream))
|
|
||||||
{
|
|
||||||
SecurePartition = new Pfs(secureStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (RootPartition.TryOpenFile(LogoPartitionName, out var logoStream))
|
|
||||||
{
|
|
||||||
LogoPartition = new Pfs(logoStream);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class XciPartition : Pfs
|
||||||
|
{
|
||||||
|
public string Name { get; internal set; }
|
||||||
|
public long Offset { get; internal set; }
|
||||||
|
|
||||||
|
public XciPartition(Stream stream) : base(stream) { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
30
hactoolnet/Print.cs
Normal file
30
hactoolnet/Print.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using System.Text;
|
||||||
|
using LibHac;
|
||||||
|
|
||||||
|
namespace hactoolnet
|
||||||
|
{
|
||||||
|
internal static class Print
|
||||||
|
{
|
||||||
|
public static void PrintItem(StringBuilder sb, int colLen, string prefix, object data)
|
||||||
|
{
|
||||||
|
if (data is byte[] byteData)
|
||||||
|
{
|
||||||
|
sb.MemDump(prefix.PadRight(colLen), byteData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sb.AppendLine(prefix.PadRight(colLen) + data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetValidityString(this Validity validity)
|
||||||
|
{
|
||||||
|
switch (validity)
|
||||||
|
{
|
||||||
|
case Validity.Invalid: return " (FAIL)";
|
||||||
|
case Validity.Valid: return " (GOOD)";
|
||||||
|
default: return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
using static hactoolnet.Print;
|
||||||
|
|
||||||
namespace hactoolnet
|
namespace hactoolnet
|
||||||
{
|
{
|
||||||
|
@ -96,7 +98,115 @@ namespace hactoolnet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Logger.LogMessage(nca.Dump());
|
ctx.Logger.LogMessage(nca.Print());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Print(this Nca nca)
|
||||||
|
{
|
||||||
|
int colLen = 36;
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine("NCA:");
|
||||||
|
PrintItem(sb, colLen, "Magic:", nca.Header.Magic);
|
||||||
|
PrintItem(sb, colLen, "Fixed-Key Signature:", nca.Header.Signature1);
|
||||||
|
PrintItem(sb, colLen, "NPDM Signature:", nca.Header.Signature2);
|
||||||
|
PrintItem(sb, colLen, "Content Size:", $"0x{nca.Header.NcaSize:x12}");
|
||||||
|
PrintItem(sb, colLen, "TitleID:", $"{nca.Header.TitleId:X16}");
|
||||||
|
PrintItem(sb, colLen, "SDK Version:", nca.Header.SdkVersion);
|
||||||
|
PrintItem(sb, colLen, "Distribution type:", nca.Header.Distribution);
|
||||||
|
PrintItem(sb, colLen, "Content Type:", nca.Header.ContentType);
|
||||||
|
PrintItem(sb, colLen, "Master Key Revision:", $"{nca.CryptoType} ({Util.GetKeyRevisionSummary(nca.CryptoType)})");
|
||||||
|
PrintItem(sb, colLen, "Encryption Type:", $"{(nca.HasRightsId ? "Titlekey crypto" : "Standard crypto")}");
|
||||||
|
|
||||||
|
if (nca.HasRightsId)
|
||||||
|
{
|
||||||
|
PrintItem(sb, colLen, "Rights ID:", nca.Header.RightsId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintItem(sb, colLen, "Key Area Encryption Key:", nca.Header.KaekInd);
|
||||||
|
sb.AppendLine("Key Area (Encrypted):");
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
PrintItem(sb, colLen, $" Key {i} (Encrypted):", nca.Header.EncryptedKeys[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("Key Area (Decrypted):");
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
PrintItem(sb, colLen, $" Key {i} (Decrypted):", nca.DecryptedKeys[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintSections();
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
|
||||||
|
void PrintSections()
|
||||||
|
{
|
||||||
|
sb.AppendLine("Sections:");
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
NcaSection sect = nca.Sections[i];
|
||||||
|
if (sect == null) continue;
|
||||||
|
|
||||||
|
sb.AppendLine($" Section {i}:");
|
||||||
|
PrintItem(sb, colLen, " Offset:", $"0x{sect.Offset:x12}");
|
||||||
|
PrintItem(sb, colLen, " Size:", $"0x{sect.Size:x12}");
|
||||||
|
PrintItem(sb, colLen, " Partition Type:", sect.IsExefs ? "ExeFS" : sect.Type.ToString());
|
||||||
|
PrintItem(sb, colLen, " Section CTR:", sect.Header.Ctr);
|
||||||
|
|
||||||
|
switch (sect.Type)
|
||||||
|
{
|
||||||
|
case SectionType.Pfs0:
|
||||||
|
PrintPfs0(sect);
|
||||||
|
break;
|
||||||
|
case SectionType.Romfs:
|
||||||
|
PrintRomfs(sect);
|
||||||
|
break;
|
||||||
|
case SectionType.Bktr:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sb.AppendLine(" Unknown/invalid superblock!");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintPfs0(NcaSection sect)
|
||||||
|
{
|
||||||
|
var sBlock = sect.Pfs0.Superblock;
|
||||||
|
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.MasterHash);
|
||||||
|
sb.AppendLine($" Hash Table{sect.Pfs0.Validity.GetValidityString()}:");
|
||||||
|
|
||||||
|
PrintItem(sb, colLen, " Offset:", $"0x{sBlock.HashTableOffset:x12}");
|
||||||
|
PrintItem(sb, colLen, " Size:", $"0x{sBlock.HashTableSize:x12}");
|
||||||
|
PrintItem(sb, colLen, " Block Size:", $"0x{sBlock.BlockSize:x}");
|
||||||
|
PrintItem(sb, colLen, " PFS0 Offset:", $"0x{sBlock.Pfs0Offset:x12}");
|
||||||
|
PrintItem(sb, colLen, " PFS0 Size:", $"0x{sBlock.Pfs0Size:x12}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintRomfs(NcaSection sect)
|
||||||
|
{
|
||||||
|
var sBlock = sect.Romfs.Superblock;
|
||||||
|
var levels = sect.Romfs.IvfcLevels;
|
||||||
|
|
||||||
|
PrintItem(sb, colLen, $" Superblock Hash{sect.SuperblockHashValidity.GetValidityString()}:", sBlock.IvfcHeader.MasterHash);
|
||||||
|
PrintItem(sb, colLen, " Magic:", sBlock.IvfcHeader.Magic);
|
||||||
|
PrintItem(sb, colLen, " ID:", $"{sBlock.IvfcHeader.Id:x8}");
|
||||||
|
|
||||||
|
for (int i = 0; i < Romfs.IvfcMaxLevel; i++)
|
||||||
|
{
|
||||||
|
var level = levels[i];
|
||||||
|
sb.AppendLine($" Level {i}{level.HashValidity.GetValidityString()}:");
|
||||||
|
PrintItem(sb, colLen, " Data Offset:", $"0x{level.DataOffset:x12}");
|
||||||
|
PrintItem(sb, colLen, " Data Size:", $"0x{level.DataSize:x12}");
|
||||||
|
PrintItem(sb, colLen, " Hash Offset:", $"0x{level.HashOffset:x12}");
|
||||||
|
PrintItem(sb, colLen, " Hash BlockSize:", $"0x{level.HashBlockSize:x8}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
using static hactoolnet.Print;
|
||||||
|
|
||||||
namespace hactoolnet
|
namespace hactoolnet
|
||||||
{
|
{
|
||||||
|
@ -12,6 +14,8 @@ namespace hactoolnet
|
||||||
{
|
{
|
||||||
var xci = new Xci(ctx.Keyset, file);
|
var xci = new Xci(ctx.Keyset, file);
|
||||||
|
|
||||||
|
ctx.Logger.LogMessage(xci.Print());
|
||||||
|
|
||||||
if (ctx.Options.RootDir != null)
|
if (ctx.Options.RootDir != null)
|
||||||
{
|
{
|
||||||
xci.RootPartition?.Extract(ctx.Options.RootDir, ctx.Logger);
|
xci.RootPartition?.Extract(ctx.Options.RootDir, ctx.Logger);
|
||||||
|
@ -139,5 +143,80 @@ namespace hactoolnet
|
||||||
|
|
||||||
return mainNca;
|
return mainNca;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string Print(this Xci xci)
|
||||||
|
{
|
||||||
|
const int colLen = 36;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
sb.AppendLine("XCI:");
|
||||||
|
|
||||||
|
PrintItem(sb, colLen, "Magic:", xci.Header.Magic);
|
||||||
|
PrintItem(sb, colLen, $"Header Signature:{xci.Header.SignatureValidity.GetValidityString()}", xci.Header.Signature);
|
||||||
|
PrintItem(sb, colLen, "Cartridge Type:", GetCartridgeType(xci.Header.RomSize));
|
||||||
|
PrintItem(sb, colLen, "Cartridge Size:", $"0x{Util.MediaToReal(xci.Header.ValidDataEndPage + 1):x12}");
|
||||||
|
PrintItem(sb, colLen, "Header IV:", xci.Header.AesCbcIv);
|
||||||
|
|
||||||
|
foreach (XciPartition partition in xci.Partitions.OrderBy(x => x.Offset))
|
||||||
|
{
|
||||||
|
PrintPartition(sb, colLen, partition);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintPartition(StringBuilder sb, int colLen, XciPartition partition)
|
||||||
|
{
|
||||||
|
const int fileNameLen = 57;
|
||||||
|
|
||||||
|
sb.AppendLine($"{GetDisplayName(partition.Name)} Partition:");
|
||||||
|
PrintItem(sb, colLen, " Magic:", partition.Header.Magic);
|
||||||
|
PrintItem(sb, colLen, " Offset:", $"{partition.Offset:x12}");
|
||||||
|
PrintItem(sb, colLen, " Number of files:", partition.Files.Length);
|
||||||
|
|
||||||
|
if (partition.Files.Length > 0 && partition.Files.Length < 100)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < partition.Files.Length; i++)
|
||||||
|
{
|
||||||
|
PfsFileEntry file = partition.Files[i];
|
||||||
|
|
||||||
|
string label = i == 0 ? " Files:" : "";
|
||||||
|
string offsets = $"{file.Offset:x12}-{file.Offset + file.Size:x12}";
|
||||||
|
string data = $"{partition.Name}:/{file.Name}".PadRight(fileNameLen) + offsets;
|
||||||
|
|
||||||
|
PrintItem(sb, colLen, label, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetDisplayName(string name)
|
||||||
|
{
|
||||||
|
switch (name)
|
||||||
|
{
|
||||||
|
case "rootpt": return "Root";
|
||||||
|
case "update": return "Update";
|
||||||
|
case "normal": return "Normal";
|
||||||
|
case "secure": return "Secure";
|
||||||
|
case "logo": return "Logo";
|
||||||
|
default: return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetCartridgeType(RomSize size)
|
||||||
|
{
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case RomSize.Size1Gb: return "1GB";
|
||||||
|
case RomSize.Size2Gb: return "2GB";
|
||||||
|
case RomSize.Size4Gb: return "4GB";
|
||||||
|
case RomSize.Size8Gb: return "8GB";
|
||||||
|
case RomSize.Size16Gb: return "16GB";
|
||||||
|
case RomSize.Size32Gb: return "32GB";
|
||||||
|
default: return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue