diff --git a/SabreTools.Serialization/Interfaces/IPrinter.cs b/SabreTools.Serialization/Interfaces/IPrinter.cs
new file mode 100644
index 00000000..24050ba1
--- /dev/null
+++ b/SabreTools.Serialization/Interfaces/IPrinter.cs
@@ -0,0 +1,18 @@
+using System.Text;
+
+namespace SabreTools.Serialization.Interfaces
+{
+ ///
+ /// Marks a class as a printer associated with a model
+ ///
+ /// Type of the top-level model
+ public interface IPrinter
+ {
+ ///
+ /// Print information associated with a model
+ ///
+ /// StringBuilder to append information to
+ /// Model to print
+ void PrintInformation(StringBuilder builder, TModel model);
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/PrintExtensions.cs b/SabreTools.Serialization/PrintExtensions.cs
new file mode 100644
index 00000000..aead0dc7
--- /dev/null
+++ b/SabreTools.Serialization/PrintExtensions.cs
@@ -0,0 +1,430 @@
+using System;
+using System.Text;
+using SabreTools.Serialization.Interfaces;
+using SabreTools.Serialization.Printers;
+using Wrapper = SabreTools.Serialization.Wrappers;
+
+namespace SabreTools.Serialization
+{
+ ///
+ /// Generic wrapper around printing methods
+ ///
+ public static class PrintExtensions
+ {
+ ///
+ /// Print the item information from a wrapper to console as
+ /// pretty-printed text
+ ///
+ public static void PrintToConsole(this IWrapper wrapper)
+ {
+ var sb = wrapper.ExportStringBuilder();
+ if (sb == null)
+ {
+ Console.WriteLine("No item information could be generated");
+ return;
+ }
+
+ Console.WriteLine(sb.ToString());
+ }
+
+ ///
+ /// Export the item information as a StringBuilder
+ ///
+ public static StringBuilder? ExportStringBuilder(this IWrapper wrapper)
+ {
+ return wrapper switch
+ {
+ Wrapper.AACSMediaKeyBlock item => item.PrettyPrint(),
+ Wrapper.BDPlusSVM item => item.PrettyPrint(),
+ Wrapper.BFPK item => item.PrettyPrint(),
+ Wrapper.BSP item => item.PrettyPrint(),
+ Wrapper.CFB item => item.PrettyPrint(),
+ Wrapper.CIA item => item.PrettyPrint(),
+ Wrapper.GCF item => item.PrettyPrint(),
+ Wrapper.InstallShieldCabinet item => item.PrettyPrint(),
+ Wrapper.IRD item => item.PrettyPrint(),
+ Wrapper.LinearExecutable item => item.PrettyPrint(),
+ Wrapper.MicrosoftCabinet item => item.PrettyPrint(),
+ Wrapper.MoPaQ item => item.PrettyPrint(),
+ Wrapper.MSDOS item => item.PrettyPrint(),
+ Wrapper.N3DS item => item.PrettyPrint(),
+ Wrapper.NCF item => item.PrettyPrint(),
+ Wrapper.NewExecutable item => item.PrettyPrint(),
+ Wrapper.Nitro item => item.PrettyPrint(),
+ Wrapper.PAK item => item.PrettyPrint(),
+ Wrapper.PFF item => item.PrettyPrint(),
+ Wrapper.PIC item => item.PrettyPrint(),
+ Wrapper.PlayJAudioFile item => item.PrettyPrint(),
+ Wrapper.PlayJPlaylist item => item.PrettyPrint(),
+ Wrapper.PortableExecutable item => item.PrettyPrint(),
+ Wrapper.Quantum item => item.PrettyPrint(),
+ Wrapper.SGA item => item.PrettyPrint(),
+ Wrapper.VBSP item => item.PrettyPrint(),
+ Wrapper.VPK item => item.PrettyPrint(),
+ Wrapper.WAD item => item.PrettyPrint(),
+ Wrapper.XeMID item => item.PrettyPrint(),
+ Wrapper.XMID item => item.PrettyPrint(),
+ Wrapper.XZP item => item.PrettyPrint(),
+ _ => null,
+ };
+ }
+
+#if NET6_0_OR_GREATER
+ ///
+ /// Export the item information as JSON
+ ///
+ public static string ExportJSON(this IWrapper wrapper)
+ {
+ return wrapper switch
+ {
+ Wrapper.AACSMediaKeyBlock item => item.ExportJSON(),
+ Wrapper.BDPlusSVM item => item.ExportJSON(),
+ Wrapper.BFPK item => item.ExportJSON(),
+ Wrapper.BSP item => item.ExportJSON(),
+ Wrapper.CFB item => item.ExportJSON(),
+ Wrapper.CIA item => item.ExportJSON(),
+ Wrapper.GCF item => item.ExportJSON(),
+ Wrapper.InstallShieldCabinet item => item.ExportJSON(),
+ Wrapper.IRD item => item.ExportJSON(),
+ Wrapper.LinearExecutable item => item.ExportJSON(),
+ Wrapper.MicrosoftCabinet item => item.ExportJSON(),
+ Wrapper.MoPaQ item => item.ExportJSON(),
+ Wrapper.MSDOS item => item.ExportJSON(),
+ Wrapper.N3DS item => item.ExportJSON(),
+ Wrapper.NCF item => item.ExportJSON(),
+ Wrapper.NewExecutable item => item.ExportJSON(),
+ Wrapper.Nitro item => item.ExportJSON(),
+ Wrapper.PAK item => item.ExportJSON(),
+ Wrapper.PFF item => item.ExportJSON(),
+ Wrapper.PIC item => item.ExportJSON(),
+ Wrapper.PlayJAudioFile item => item.ExportJSON(),
+ Wrapper.PlayJPlaylist item => item.ExportJSON(),
+ Wrapper.PortableExecutable item => item.ExportJSON(),
+ Wrapper.Quantum item => item.ExportJSON(),
+ Wrapper.SGA item => item.ExportJSON(),
+ Wrapper.VBSP item => item.ExportJSON(),
+ Wrapper.VPK item => item.ExportJSON(),
+ Wrapper.WAD item => item.ExportJSON(),
+ Wrapper.XeMID item => item.ExportJSON(),
+ Wrapper.XMID item => item.ExportJSON(),
+ Wrapper.XZP item => item.ExportJSON(),
+ _ => string.Empty,
+ };
+ }
+#endif
+
+ #region Static Printing Implementations
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.AACSMediaKeyBlock item)
+ {
+ var builder = new StringBuilder();
+ AACSMediaKeyBlock.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.BDPlusSVM item)
+ {
+ var builder = new StringBuilder();
+ BDPlusSVM.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.BFPK item)
+ {
+ var builder = new StringBuilder();
+ BFPK.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.BSP item)
+ {
+ var builder = new StringBuilder();
+ BSP.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.CFB item)
+ {
+ var builder = new StringBuilder();
+ CFB.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.CIA item)
+ {
+ var builder = new StringBuilder();
+ CIA.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.GCF item)
+ {
+ var builder = new StringBuilder();
+ GCF.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.InstallShieldCabinet item)
+ {
+ var builder = new StringBuilder();
+ InstallShieldCabinet.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.IRD item)
+ {
+ var builder = new StringBuilder();
+ IRD.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.LinearExecutable item)
+ {
+ var builder = new StringBuilder();
+ LinearExecutable.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.MicrosoftCabinet item)
+ {
+ var builder = new StringBuilder();
+ MicrosoftCabinet.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.MoPaQ item)
+ {
+ var builder = new StringBuilder();
+ MoPaQ.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.MSDOS item)
+ {
+ var builder = new StringBuilder();
+ MSDOS.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.N3DS item)
+ {
+ var builder = new StringBuilder();
+ N3DS.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.NCF item)
+ {
+ var builder = new StringBuilder();
+ NCF.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.NewExecutable item)
+ {
+ var builder = new StringBuilder();
+ NewExecutable.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.Nitro item)
+ {
+ var builder = new StringBuilder();
+ Nitro.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.PAK item)
+ {
+ var builder = new StringBuilder();
+ PAK.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.PFF item)
+ {
+ var builder = new StringBuilder();
+ PFF.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.PIC item)
+ {
+ var builder = new StringBuilder();
+ PIC.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.PlayJAudioFile item)
+ {
+ var builder = new StringBuilder();
+ PlayJAudioFile.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.PlayJPlaylist item)
+ {
+ var builder = new StringBuilder();
+ PlayJPlaylist.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.PortableExecutable item)
+ {
+ var builder = new StringBuilder();
+ PortableExecutable.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.Quantum item)
+ {
+ var builder = new StringBuilder();
+ Quantum.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.SGA item)
+ {
+ var builder = new StringBuilder();
+ SGA.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.VBSP item)
+ {
+ var builder = new StringBuilder();
+ VBSP.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.VPK item)
+ {
+ var builder = new StringBuilder();
+ VPK.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.WAD item)
+ {
+ var builder = new StringBuilder();
+ WAD.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.XeMID item)
+ {
+ var builder = new StringBuilder();
+ XeMID.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.XMID item)
+ {
+ var builder = new StringBuilder();
+ XMID.Print(builder, item.Model);
+ return builder;
+ }
+
+ ///
+ /// Export the item information as pretty-printed text
+ ///
+ private static StringBuilder PrettyPrint(this Wrapper.XZP item)
+ {
+ var builder = new StringBuilder();
+ XZP.Print(builder, item.Model);
+ return builder;
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/AACSMediaKeyBlock.cs b/SabreTools.Serialization/Printers/AACSMediaKeyBlock.cs
new file mode 100644
index 00000000..5b35f522
--- /dev/null
+++ b/SabreTools.Serialization/Printers/AACSMediaKeyBlock.cs
@@ -0,0 +1,290 @@
+using System.Text;
+using SabreTools.Models.AACS;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class AACSMediaKeyBlock : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, MediaKeyBlock model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, MediaKeyBlock mediaKeyBlock)
+ {
+ builder.AppendLine("AACS Media Key Block Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, mediaKeyBlock.Records);
+ }
+
+ private static void Print(StringBuilder builder, Record?[]? records)
+ {
+ builder.AppendLine(" Records Information:");
+ builder.AppendLine(" -------------------------");
+ if (records == null || records.Length == 0)
+ {
+ builder.AppendLine(" No records");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < records.Length; i++)
+ {
+ var record = records[i];
+ Print(builder, record, i);
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Record? record, int index)
+ {
+ builder.AppendLine($" Record Entry {index}");
+ if (record == null)
+ {
+ builder.AppendLine(" [NULL]");
+ return;
+ }
+
+ builder.AppendLine($" Record type: {record.RecordType} (0x{record.RecordType:X})");
+ builder.AppendLine(record.RecordLength, " Record length");
+
+ switch (record)
+ {
+ case EndOfMediaKeyBlockRecord eomkb:
+ Print(builder, eomkb);
+ break;
+ case ExplicitSubsetDifferenceRecord esd:
+ Print(builder, esd);
+ break;
+ case MediaKeyDataRecord mkd:
+ Print(builder, mkd);
+ break;
+ case SubsetDifferenceIndexRecord sdi:
+ Print(builder, sdi);
+ break;
+ case TypeAndVersionRecord tav:
+ Print(builder, tav);
+ break;
+ case DriveRevocationListRecord drl:
+ Print(builder, drl);
+ break;
+ case HostRevocationListRecord hrl:
+ Print(builder, hrl);
+ break;
+ case VerifyMediaKeyRecord vmk:
+ Print(builder, vmk);
+ break;
+ case CopyrightRecord c:
+ Print(builder, c);
+ break;
+ }
+ }
+
+ private static void Print(StringBuilder builder, EndOfMediaKeyBlockRecord record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine(record.SignatureData, " Signature data");
+ }
+
+ private static void Print(StringBuilder builder, ExplicitSubsetDifferenceRecord? record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine(" Subset Differences:");
+ builder.AppendLine(" -------------------------");
+ if (record?.SubsetDifferences == null || record.SubsetDifferences.Length == 0)
+ {
+ builder.AppendLine(" No subset differences");
+ return;
+ }
+
+ for (int j = 0; j < record.SubsetDifferences.Length; j++)
+ {
+ var sd = record.SubsetDifferences[j];
+ builder.AppendLine($" Subset Difference {j}");
+ if (sd == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(sd.Mask, " Mask");
+ builder.AppendLine(sd.Number, " Number");
+ }
+ }
+ }
+
+ private static void Print(StringBuilder builder, MediaKeyDataRecord? record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine(" Media Keys:");
+ builder.AppendLine(" -------------------------");
+ if (record?.MediaKeyData == null || record.MediaKeyData.Length == 0)
+ {
+ builder.AppendLine(" No media keys");
+ return;
+ }
+
+ for (int j = 0; j < record.MediaKeyData.Length; j++)
+ {
+ var mk = record.MediaKeyData[j];
+ builder.AppendLine(mk, $" Media key {j}");
+ }
+ }
+
+ private static void Print(StringBuilder builder, SubsetDifferenceIndexRecord? record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine($" Span: {record.Span} (0x{record.Span:X})");
+ builder.AppendLine(" Offsets:");
+ builder.AppendLine(" -------------------------");
+ if (record.Offsets == null || record.Offsets.Length == 0)
+ {
+ builder.AppendLine(" No offsets");
+ return;
+ }
+
+ for (int j = 0; j < record.Offsets.Length; j++)
+ {
+ var offset = record.Offsets[j];
+ builder.AppendLine(offset, $" Offset {j}");
+ }
+ }
+
+ private static void Print(StringBuilder builder, TypeAndVersionRecord? record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine($" Media key block type: {record.MediaKeyBlockType} (0x{record.MediaKeyBlockType:X})");
+ builder.AppendLine(record.VersionNumber, " Version number");
+ }
+
+ private static void Print(StringBuilder builder, DriveRevocationListRecord? record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine(record.TotalNumberOfEntries, " Total number of entries");
+ builder.AppendLine(" Signature Blocks:");
+ builder.AppendLine(" -------------------------");
+ if (record.SignatureBlocks == null || record.SignatureBlocks.Length == 0)
+ {
+ builder.AppendLine(" No signature blocks");
+ return;
+ }
+
+ for (int j = 0; j < record.SignatureBlocks.Length; j++)
+ {
+ var block = record.SignatureBlocks[j];
+ builder.AppendLine($" Signature Block {j}");
+ if (block == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(block.NumberOfEntries, " Number of entries");
+ builder.AppendLine(" Entry Fields:");
+ builder.AppendLine(" -------------------------");
+ if (block.EntryFields == null || block.EntryFields.Length == 0)
+ {
+ builder.AppendLine(" No entry fields");
+ }
+ else
+ {
+ for (int k = 0; k < block.EntryFields.Length; k++)
+ {
+ var ef = block.EntryFields[k];
+ builder.AppendLine($" Entry {k}");
+ if (ef == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(ef.Range, " Range");
+ builder.AppendLine(ef.DriveID, " Drive ID");
+ }
+ }
+ }
+ }
+ }
+
+ private static void Print(StringBuilder builder, HostRevocationListRecord? record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine($" Total number of entries: {record.TotalNumberOfEntries} (0x{record.TotalNumberOfEntries:X})");
+ builder.AppendLine(" Signature Blocks:");
+ builder.AppendLine(" -------------------------");
+ if (record.SignatureBlocks == null || record.SignatureBlocks.Length == 0)
+ {
+ builder.AppendLine(" No signature blocks");
+ return;
+ }
+
+ for (int j = 0; j < record.SignatureBlocks.Length; j++)
+ {
+ builder.AppendLine($" Signature Block {j}");
+ var block = record.SignatureBlocks[j];
+ if (block == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(block.NumberOfEntries, " Number of entries");
+ builder.AppendLine(" Entry Fields:");
+ builder.AppendLine(" -------------------------");
+ if (block.EntryFields == null || block.EntryFields.Length == 0)
+ {
+ builder.AppendLine(" No entry fields");
+ continue;
+ }
+
+ for (int k = 0; k < block.EntryFields.Length; k++)
+ {
+ var ef = block.EntryFields[k];
+ builder.AppendLine($" Entry {k}");
+ if (ef == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+
+ builder.AppendLine(ef.Range, " Range");
+ builder.AppendLine(ef.HostID, " Host ID");
+ }
+ }
+ }
+ }
+
+ private static void Print(StringBuilder builder, VerifyMediaKeyRecord? record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine(record.CiphertextValue, " Ciphertext value");
+ }
+
+ private static void Print(StringBuilder builder, CopyrightRecord? record)
+ {
+ if (record == null)
+ return;
+
+ builder.AppendLine(record.Copyright, " Copyright");
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/BDPlusSVM.cs b/SabreTools.Serialization/Printers/BDPlusSVM.cs
new file mode 100644
index 00000000..4aade150
--- /dev/null
+++ b/SabreTools.Serialization/Printers/BDPlusSVM.cs
@@ -0,0 +1,28 @@
+using System.Text;
+using SabreTools.Models.BDPlus;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class BDPlusSVM : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, SVM model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, SVM svm)
+ {
+ builder.AppendLine("BD+ SVM Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine(svm.Signature, "Signature");
+ builder.AppendLine(svm.Unknown1, "Unknown 1");
+ builder.AppendLine(svm.Year, "Year");
+ builder.AppendLine(svm.Month, "Month");
+ builder.AppendLine(svm.Day, "Day");
+ builder.AppendLine(svm.Unknown2, "Unknown 2");
+ builder.AppendLine(svm.Length, "Length");
+ //builder.AppendLine(svm.Data, "Data");
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/BFPK.cs b/SabreTools.Serialization/Printers/BFPK.cs
new file mode 100644
index 00000000..5ef27ac7
--- /dev/null
+++ b/SabreTools.Serialization/Printers/BFPK.cs
@@ -0,0 +1,68 @@
+using System.Text;
+using SabreTools.Models.BFPK;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class BFPK : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Archive model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Archive archive)
+ {
+ builder.AppendLine("BFPK Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, archive.Header);
+ Print(builder, archive.Files);
+ }
+
+ private static void Print(StringBuilder builder, Header? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Magic, " Magic");
+ builder.AppendLine(header.Version, " Version");
+ builder.AppendLine(header.Files, " Files");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, FileEntry?[]? files)
+ {
+ builder.AppendLine(" File Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (files == null || files.Length == 0)
+ {
+ builder.AppendLine(" No file table items");
+ return;
+ }
+
+ for (int i = 0; i < files.Length; i++)
+ {
+ var entry = files[i];
+ builder.AppendLine($" File Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.NameSize, " Name size");
+ builder.AppendLine(entry.Name, " Name");
+ builder.AppendLine(entry.UncompressedSize, " Uncompressed size");
+ builder.AppendLine(entry.Offset, " Offset");
+ builder.AppendLine(entry.CompressedSize, " Compressed size");
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/BSP.cs b/SabreTools.Serialization/Printers/BSP.cs
new file mode 100644
index 00000000..efbb084d
--- /dev/null
+++ b/SabreTools.Serialization/Printers/BSP.cs
@@ -0,0 +1,150 @@
+using System.Text;
+using SabreTools.Models.BSP;
+using SabreTools.Serialization.Interfaces;
+using static SabreTools.Models.BSP.Constants;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class BSP : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, File model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, File file)
+ {
+ builder.AppendLine("BSP Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, file.Header);
+ Print(builder, file.Lumps);
+ Print(builder, file.TextureHeader);
+ Print(builder, file.Textures);
+ }
+
+ private static void Print(StringBuilder builder, Header? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Version, " Version");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Lump?[]? lumps)
+ {
+ builder.AppendLine(" Lumps Information:");
+ builder.AppendLine(" -------------------------");
+ if (lumps == null || lumps.Length == 0)
+ {
+ builder.AppendLine(" No lumps");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < lumps.Length; i++)
+ {
+ var lump = lumps[i];
+ string specialLumpName = string.Empty;
+ switch (i)
+ {
+ case HL_BSP_LUMP_ENTITIES:
+ specialLumpName = " (entities)";
+ break;
+ case HL_BSP_LUMP_TEXTUREDATA:
+ specialLumpName = " (texture data)";
+ break;
+ }
+
+ builder.AppendLine($" Lump {i}{specialLumpName}");
+ if (lump == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(lump.Offset, " Offset");
+ builder.AppendLine(lump.Length, " Length");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, TextureHeader? header)
+ {
+ builder.AppendLine(" Texture Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No texture header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.TextureCount, " Texture count");
+ builder.AppendLine(" Offsets:");
+ if (header.Offsets == null || header.Offsets.Length == 0)
+ {
+ builder.AppendLine(" No offsets");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < header.Offsets.Length; i++)
+ {
+ builder.AppendLine(header.Offsets[i], $" Offset {i}");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Texture?[]? textures)
+ {
+ builder.AppendLine(" Textures Information:");
+ builder.AppendLine(" -------------------------");
+ if (textures == null || textures.Length == 0)
+ {
+ builder.AppendLine(" No textures");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < textures.Length; i++)
+ {
+ var texture = textures[i];
+ builder.AppendLine($" Texture {i}");
+ if (texture == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(texture.Name, " Name");
+ builder.AppendLine(texture.Width, " Width");
+ builder.AppendLine(texture.Height, " Height");
+ builder.AppendLine(" Offsets:");
+ if (texture.Offsets == null || texture.Offsets.Length == 0)
+ {
+ builder.AppendLine(" No offsets");
+ continue;
+ }
+ else
+ {
+ for (int j = 0; j < texture.Offsets.Length; j++)
+ {
+ builder.AppendLine(texture.Offsets[i], $" Offset {j}");
+ }
+ }
+ // Skip texture data
+ builder.AppendLine(texture.PaletteSize, " Palette size");
+ // Skip palette data
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/CFB.cs b/SabreTools.Serialization/Printers/CFB.cs
new file mode 100644
index 00000000..2eab14aa
--- /dev/null
+++ b/SabreTools.Serialization/Printers/CFB.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Text;
+using SabreTools.Models.CFB;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class CFB : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Binary model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Binary binary)
+ {
+ builder.AppendLine("Compound File Binary Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, binary.Header);
+ Print(builder, binary.FATSectorNumbers, "FAT");
+ Print(builder, binary.MiniFATSectorNumbers, "Mini FAT");
+ Print(builder, binary.DIFATSectorNumbers, "DIFAT");
+ Print(builder, binary.DirectoryEntries);
+ }
+
+ private static void Print(StringBuilder builder, FileHeader? header)
+ {
+ builder.AppendLine(" File Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No file header");
+ return;
+ }
+
+ builder.AppendLine(header.Signature, " Signature");
+ builder.AppendLine(header.CLSID, " CLSID");
+ builder.AppendLine(header.MinorVersion, " Minor version");
+ builder.AppendLine(header.MajorVersion, " Major version");
+ builder.AppendLine(header.ByteOrder, " Byte order");
+ builder.AppendLine($" Sector shift: {header.SectorShift} (0x{header.SectorShift:X}) => {Math.Pow(2, header.SectorShift)}");
+ builder.AppendLine($" Mini sector shift: {header.MiniSectorShift} (0x{header.MiniSectorShift:X}) => {Math.Pow(2, header.MiniSectorShift)}");
+ builder.AppendLine(header.Reserved, " Reserved");
+ builder.AppendLine(header.NumberOfDirectorySectors, " Number of directory sectors");
+ builder.AppendLine(header.NumberOfFATSectors, " Number of FAT sectors");
+ builder.AppendLine(header.FirstDirectorySectorLocation, " First directory sector location");
+ builder.AppendLine(header.TransactionSignatureNumber, " Transaction signature number");
+ builder.AppendLine(header.MiniStreamCutoffSize, " Mini stream cutoff size");
+ builder.AppendLine(header.FirstMiniFATSectorLocation, " First mini FAT sector location");
+ builder.AppendLine(header.NumberOfMiniFATSectors, " Number of mini FAT sectors");
+ builder.AppendLine(header.FirstDIFATSectorLocation, " First DIFAT sector location");
+ builder.AppendLine(header.NumberOfDIFATSectors, " Number of DIFAT sectors");
+ builder.AppendLine(" DIFAT:");
+ if (header.DIFAT == null || header.DIFAT.Length == 0)
+ {
+ builder.AppendLine(" No DIFAT entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < header.DIFAT.Length; i++)
+ {
+ builder.AppendLine($" DIFAT Entry {i}: {header.DIFAT[i]} (0x{header.DIFAT[i]:X})");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, SectorNumber[]? sectorNumbers, string name)
+ {
+ builder.AppendLine($" {name} Sectors Information:");
+ builder.AppendLine(" -------------------------");
+ if (sectorNumbers == null || sectorNumbers.Length == 0)
+ {
+ builder.AppendLine($" No {name} sectors");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < sectorNumbers.Length; i++)
+ {
+ builder.AppendLine($" {name} Sector Entry {i}: {sectorNumbers[i]} (0x{sectorNumbers[i]:X})");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryEntry?[]? directoryEntries)
+ {
+ builder.AppendLine(" Directory Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (directoryEntries == null || directoryEntries.Length == 0)
+ {
+ builder.AppendLine(" No directory entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < directoryEntries.Length; i++)
+ {
+ var directoryEntry = directoryEntries[i];
+ builder.AppendLine($" Directory Entry {i}");
+ if (directoryEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(directoryEntry.Name, " Name");
+ builder.AppendLine(directoryEntry.NameLength, " Name length");
+ builder.AppendLine($" Object type: {directoryEntry.ObjectType} (0x{directoryEntry.ObjectType:X})");
+ builder.AppendLine($" Color flag: {directoryEntry.ColorFlag} (0x{directoryEntry.ColorFlag:X})");
+ builder.AppendLine($" Left sibling ID: {directoryEntry.LeftSiblingID} (0x{directoryEntry.LeftSiblingID:X})");
+ builder.AppendLine($" Right sibling ID: {directoryEntry.RightSiblingID} (0x{directoryEntry.RightSiblingID:X})");
+ builder.AppendLine($" Child ID: {directoryEntry.ChildID} (0x{directoryEntry.ChildID:X})");
+ builder.AppendLine(directoryEntry.CLSID, " CLSID");
+ builder.AppendLine(directoryEntry.StateBits, " State bits");
+ builder.AppendLine(directoryEntry.CreationTime, " Creation time");
+ builder.AppendLine(directoryEntry.ModifiedTime, " Modification time");
+ builder.AppendLine(directoryEntry.StartingSectorLocation, " Staring sector location");
+ builder.AppendLine(directoryEntry.StreamSize, " Stream size");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/CIA.cs b/SabreTools.Serialization/Printers/CIA.cs
new file mode 100644
index 00000000..45c2af05
--- /dev/null
+++ b/SabreTools.Serialization/Printers/CIA.cs
@@ -0,0 +1,454 @@
+using System.Text;
+using SabreTools.Models.N3DS;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class CIA : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Models.N3DS.CIA model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Models.N3DS.CIA cia)
+ {
+ builder.AppendLine("CIA Archive Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, cia.Header);
+ Print(builder, cia.CertificateChain);
+ Print(builder, cia.Ticket);
+ Print(builder, cia.TMDFileData);
+ Print(builder, cia.Partitions);
+ Print(builder, cia.MetaData);
+ }
+
+ private static void Print(StringBuilder builder, CIAHeader? header)
+ {
+ builder.AppendLine(" CIA Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No CIA header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.HeaderSize, " Header size");
+ builder.AppendLine(header.Type, " Type");
+ builder.AppendLine(header.Version, " Version");
+ builder.AppendLine(header.CertificateChainSize, " Certificate chain size");
+ builder.AppendLine(header.TicketSize, " Ticket size");
+ builder.AppendLine(header.TMDFileSize, " TMD file size");
+ builder.AppendLine(header.MetaSize, " Meta size");
+ builder.AppendLine(header.ContentSize, " Content size");
+ builder.AppendLine(header.ContentIndex, " Content index");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Certificate?[]? certificateChain)
+ {
+ builder.AppendLine(" Certificate Chain Information:");
+ builder.AppendLine(" -------------------------");
+ if (certificateChain == null || certificateChain.Length == 0)
+ {
+ builder.AppendLine(" No certificates, expected 3");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < certificateChain.Length; i++)
+ {
+ var certificate = certificateChain[i];
+
+ string certificateName = string.Empty;
+ switch (i)
+ {
+ case 0: certificateName = " (CA)"; break;
+ case 1: certificateName = " (Ticket)"; break;
+ case 2: certificateName = " (TMD)"; break;
+ }
+
+ builder.AppendLine($" Certificate {i}{certificateName}");
+ if (certificate == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine($" Signature type: {certificate.SignatureType} (0x{certificate.SignatureType:X})");
+ builder.AppendLine(certificate.SignatureSize, " Signature size");
+ builder.AppendLine(certificate.PaddingSize, " Padding size");
+ builder.AppendLine(certificate.Signature, " Signature");
+ builder.AppendLine(certificate.Padding, " Padding");
+ builder.AppendLine(certificate.Issuer, " Issuer");
+ builder.AppendLine($" Key type: {certificate.KeyType} (0x{certificate.KeyType:X})");
+ builder.AppendLine(certificate.Name, " Name");
+ builder.AppendLine(certificate.ExpirationTime, " Expiration time");
+ switch (certificate.KeyType)
+ {
+ case PublicKeyType.RSA_4096:
+ case PublicKeyType.RSA_2048:
+ builder.AppendLine(certificate.RSAModulus, " Modulus");
+ builder.AppendLine(certificate.RSAPublicExponent, " Public exponent");
+ builder.AppendLine(certificate.RSAPadding, " Padding");
+ break;
+ case PublicKeyType.EllipticCurve:
+ builder.AppendLine(certificate.ECCPublicKey, " Public key");
+ builder.AppendLine(certificate.ECCPadding, " Padding");
+ break;
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Ticket? ticket)
+ {
+ builder.AppendLine(" Ticket Information:");
+ builder.AppendLine(" -------------------------");
+ if (ticket == null)
+ {
+ builder.AppendLine(" No ticket");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine($" Signature type: {ticket.SignatureType} (0x{ticket.SignatureType:X})");
+ builder.AppendLine(ticket.SignatureSize, " Signature size");
+ builder.AppendLine(ticket.PaddingSize, " Padding size");
+ builder.AppendLine(ticket.Signature, " Signature");
+ builder.AppendLine(ticket.Padding, " Padding");
+ builder.AppendLine(ticket.Issuer, " Issuer");
+ builder.AppendLine(ticket.ECCPublicKey, " ECC public key");
+ builder.AppendLine(ticket.Version, " Version");
+ builder.AppendLine(ticket.CaCrlVersion, " CaCrlVersion");
+ builder.AppendLine(ticket.SignerCrlVersion, " SignerCrlVersion");
+ builder.AppendLine(ticket.TitleKey, " Title key");
+ builder.AppendLine(ticket.Reserved1, " Reserved 1");
+ builder.AppendLine(ticket.TicketID, " Ticket ID");
+ builder.AppendLine(ticket.ConsoleID, " Console ID");
+ builder.AppendLine(ticket.TitleID, " Title ID");
+ builder.AppendLine(ticket.Reserved2, " Reserved 2");
+ builder.AppendLine(ticket.TicketTitleVersion, " Ticket title version");
+ builder.AppendLine(ticket.Reserved3, " Reserved 3");
+ builder.AppendLine(ticket.LicenseType, " License type");
+ builder.AppendLine(ticket.CommonKeyYIndex, " Common key Y index");
+ builder.AppendLine(ticket.Reserved4, " Reserved 4");
+ builder.AppendLine(ticket.eShopAccountID, " eShop Account ID");
+ builder.AppendLine(ticket.Reserved5, " Reserved 5");
+ builder.AppendLine(ticket.Audit, " Audit");
+ builder.AppendLine(ticket.Reserved6, " Reserved 6");
+ builder.AppendLine(" Limits:");
+ if (ticket.Limits == null || ticket.Limits.Length == 0)
+ {
+ builder.AppendLine(" No limits");
+ }
+ else
+ {
+ for (int i = 0; i < ticket.Limits.Length; i++)
+ {
+ builder.AppendLine(ticket.Limits[i], $" Limit {i}");
+ }
+ }
+ builder.AppendLine(ticket.ContentIndexSize, " Content index size");
+ builder.AppendLine(ticket.ContentIndex, " Content index");
+ builder.AppendLine();
+
+ builder.AppendLine(" Ticket Certificate Chain Information:");
+ builder.AppendLine(" -------------------------");
+ if (ticket.CertificateChain == null || ticket.CertificateChain.Length == 0)
+ {
+ builder.AppendLine(" No certificates, expected 2");
+ }
+ else
+ {
+ for (int i = 0; i < ticket.CertificateChain.Length; i++)
+ {
+ var certificate = ticket.CertificateChain[i];
+
+ string certificateName = string.Empty;
+ switch (i)
+ {
+ case 0: certificateName = " (Ticket)"; break;
+ case 1: certificateName = " (CA)"; break;
+ }
+
+ builder.AppendLine($" Certificate {i}{certificateName}");
+ if (certificate == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine($" Signature type: {certificate.SignatureType} (0x{certificate.SignatureType:X})");
+ builder.AppendLine(certificate.SignatureSize, " Signature size");
+ builder.AppendLine(certificate.PaddingSize, " Padding size");
+ builder.AppendLine(certificate.Signature, " Signature");
+ builder.AppendLine(certificate.Padding, " Padding");
+ builder.AppendLine(certificate.Issuer, " Issuer");
+ builder.AppendLine($" Key type: {certificate.KeyType} (0x{certificate.KeyType:X})");
+ builder.AppendLine(certificate.Name, " Name");
+ builder.AppendLine(certificate.ExpirationTime, " Expiration time");
+ switch (certificate.KeyType)
+ {
+ case PublicKeyType.RSA_4096:
+ case PublicKeyType.RSA_2048:
+ builder.AppendLine(certificate.RSAModulus, " Modulus");
+ builder.AppendLine(certificate.RSAPublicExponent, " Public exponent");
+ builder.AppendLine(certificate.RSAPadding, " Padding");
+ break;
+ case PublicKeyType.EllipticCurve:
+ builder.AppendLine(certificate.ECCPublicKey, " Public key");
+ builder.AppendLine(certificate.ECCPadding, " Padding");
+ break;
+ }
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, TitleMetadata? tmd)
+ {
+ builder.AppendLine(" Title Metadata Information:");
+ builder.AppendLine(" -------------------------");
+ if (tmd == null)
+ {
+ builder.AppendLine(" No title metadata");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine($" Signature type: {tmd.SignatureType} (0x{tmd.SignatureType:X})");
+ builder.AppendLine(tmd.SignatureSize, " Signature size");
+ builder.AppendLine(tmd.PaddingSize, " Padding size");
+ builder.AppendLine(tmd.Signature, " Signature");
+ builder.AppendLine(tmd.Padding1, " Padding 1");
+ builder.AppendLine(tmd.Issuer, " Issuer");
+ builder.AppendLine(tmd.Version, " Version");
+ builder.AppendLine(tmd.CaCrlVersion, " CaCrlVersion");
+ builder.AppendLine(tmd.SignerCrlVersion, " SignerCrlVersion");
+ builder.AppendLine(tmd.Reserved1, " Reserved 1");
+ builder.AppendLine(tmd.SystemVersion, " System version");
+ builder.AppendLine(tmd.TitleID, " Title ID");
+ builder.AppendLine(tmd.TitleType, " Title type");
+ builder.AppendLine(tmd.GroupID, " Group ID");
+ builder.AppendLine(tmd.SaveDataSize, " Save data size");
+ builder.AppendLine(tmd.SRLPrivateSaveDataSize, " SRL private save data size");
+ builder.AppendLine(tmd.Reserved2, " Reserved 2");
+ builder.AppendLine(tmd.SRLFlag, " SRL flag");
+ builder.AppendLine(tmd.Reserved3, " Reserved 3");
+ builder.AppendLine(tmd.AccessRights, " Access rights");
+ builder.AppendLine(tmd.TitleVersion, " Title version");
+ builder.AppendLine(tmd.ContentCount, " Content count");
+ builder.AppendLine(tmd.BootContent, " Boot content");
+ builder.AppendLine(tmd.Padding2, " Padding 2");
+ builder.AppendLine(tmd.SHA256HashContentInfoRecords, " SHA-256 hash of the content info records");
+ builder.AppendLine();
+
+ builder.AppendLine(" Ticket Content Info Records Information:");
+ builder.AppendLine(" -------------------------");
+ if (tmd.ContentInfoRecords == null || tmd.ContentInfoRecords.Length == 0)
+ {
+ builder.AppendLine(" No content info records, expected 64");
+ }
+ else
+ {
+ for (int i = 0; i < tmd.ContentInfoRecords.Length; i++)
+ {
+ var contentInfoRecord = tmd.ContentInfoRecords[i];
+ builder.AppendLine($" Content Info Record {i}");
+ if (contentInfoRecord == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(contentInfoRecord.ContentIndexOffset, " Content index offset");
+ builder.AppendLine(contentInfoRecord.ContentCommandCount, " Content command count");
+ builder.AppendLine(contentInfoRecord.UnhashedContentRecordsSHA256Hash, $" SHA-256 hash of the next {contentInfoRecord.ContentCommandCount} records not hashed");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Ticket Content Chunk Records Information:");
+ builder.AppendLine(" -------------------------");
+ if (tmd.ContentChunkRecords == null || tmd.ContentChunkRecords.Length == 0)
+ {
+ builder.AppendLine($" No content chunk records, expected {tmd.ContentCount}");
+ }
+ else
+ {
+ for (int i = 0; i < tmd.ContentChunkRecords.Length; i++)
+ {
+ var contentChunkRecord = tmd.ContentChunkRecords[i];
+ builder.AppendLine($" Content Chunk Record {i}");
+ if (contentChunkRecord == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(contentChunkRecord.ContentId, " Content ID");
+ builder.AppendLine($" Content index: {contentChunkRecord.ContentIndex} (0x{contentChunkRecord.ContentIndex:X})");
+ builder.AppendLine($" Content type: {contentChunkRecord.ContentType} (0x{contentChunkRecord.ContentType:X})");
+ builder.AppendLine(contentChunkRecord.ContentSize, " Content size");
+ builder.AppendLine(contentChunkRecord.SHA256Hash, " SHA-256 hash");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Ticket Certificate Chain Information:");
+ builder.AppendLine(" -------------------------");
+ if (tmd.CertificateChain == null || tmd.CertificateChain.Length == 0)
+ {
+ builder.AppendLine(" No certificates, expected 2");
+ }
+ else
+ {
+ for (int i = 0; i < tmd.CertificateChain.Length; i++)
+ {
+ var certificate = tmd.CertificateChain[i];
+
+ string certificateName = string.Empty;
+ switch (i)
+ {
+ case 0: certificateName = " (TMD)"; break;
+ case 1: certificateName = " (CA)"; break;
+ }
+
+ builder.AppendLine($" Certificate {i}{certificateName}");
+ if (certificate == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine($" Signature type: {certificate.SignatureType} (0x{certificate.SignatureType:X})");
+ builder.AppendLine(certificate.SignatureSize, " Signature size");
+ builder.AppendLine(certificate.PaddingSize, " Padding size");
+ builder.AppendLine(certificate.Signature, " Signature");
+ builder.AppendLine(certificate.Padding, " Padding");
+ builder.AppendLine(certificate.Issuer, " Issuer");
+ builder.AppendLine($" Key type: {certificate.KeyType} (0x{certificate.KeyType:X})");
+ builder.AppendLine(certificate.Name, " Name");
+ builder.AppendLine(certificate.ExpirationTime, " Expiration time");
+ switch (certificate.KeyType)
+ {
+ case PublicKeyType.RSA_4096:
+ case PublicKeyType.RSA_2048:
+ builder.AppendLine(certificate.RSAModulus, " Modulus");
+ builder.AppendLine(certificate.RSAPublicExponent, " Public exponent");
+ builder.AppendLine(certificate.RSAPadding, " Padding");
+ break;
+ case PublicKeyType.EllipticCurve:
+ builder.AppendLine(certificate.ECCPublicKey, " Public key");
+ builder.AppendLine(certificate.ECCPadding, " Padding");
+ break;
+ }
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, NCCHHeader?[]? partitions)
+ {
+ builder.AppendLine(" NCCH Partition Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (partitions == null || partitions.Length == 0)
+ {
+ builder.AppendLine(" No NCCH partition headers");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < partitions.Length; i++)
+ {
+ var partitionHeader = partitions[i];
+ builder.AppendLine($" NCCH Partition Header {i}");
+ if (partitionHeader == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ if (partitionHeader.MagicID == string.Empty)
+ {
+ builder.AppendLine(" Empty partition, no data can be parsed");
+ continue;
+ }
+ else if (partitionHeader.MagicID != Constants.NCCHMagicNumber)
+ {
+ builder.AppendLine(" Unrecognized partition data, no data can be parsed");
+ continue;
+ }
+
+ builder.AppendLine(partitionHeader.RSA2048Signature, " RSA-2048 SHA-256 signature");
+ builder.AppendLine(partitionHeader.MagicID, " Magic ID");
+ builder.AppendLine(partitionHeader.ContentSizeInMediaUnits, " Content size in media units");
+ builder.AppendLine(partitionHeader.PartitionId, " Partition ID");
+ builder.AppendLine(partitionHeader.MakerCode, " Maker code");
+ builder.AppendLine(partitionHeader.Version, " Version");
+ builder.AppendLine(partitionHeader.VerificationHash, " Verification hash");
+ builder.AppendLine(partitionHeader.ProgramId, " Program ID");
+ builder.AppendLine(partitionHeader.Reserved1, " Reserved 1");
+ builder.AppendLine(partitionHeader.LogoRegionHash, " Logo region SHA-256 hash");
+ builder.AppendLine(partitionHeader.ProductCode, " Product code");
+ builder.AppendLine(partitionHeader.ExtendedHeaderHash, " Extended header SHA-256 hash");
+ builder.AppendLine(partitionHeader.ExtendedHeaderSizeInBytes, " Extended header size in bytes");
+ builder.AppendLine(partitionHeader.Reserved2, " Reserved 2");
+ builder.AppendLine(" Flags:");
+ if (partitionHeader.Flags == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(partitionHeader.Flags.Reserved0, " Reserved 0");
+ builder.AppendLine(partitionHeader.Flags.Reserved1, " Reserved 1");
+ builder.AppendLine(partitionHeader.Flags.Reserved2, " Reserved 2");
+ builder.AppendLine($" Crypto method: {partitionHeader.Flags.CryptoMethod} (0x{partitionHeader.Flags.CryptoMethod:X})");
+ builder.AppendLine($" Content platform: {partitionHeader.Flags.ContentPlatform} (0x{partitionHeader.Flags.ContentPlatform:X})");
+ builder.AppendLine($" Content type: {partitionHeader.Flags.MediaPlatformIndex} (0x{partitionHeader.Flags.MediaPlatformIndex:X})");
+ builder.AppendLine(partitionHeader.Flags.ContentUnitSize, " Content unit size");
+ builder.AppendLine($" Bitmasks: {partitionHeader.Flags.BitMasks} (0x{partitionHeader.Flags.BitMasks:X})");
+ }
+ builder.AppendLine(partitionHeader.PlainRegionOffsetInMediaUnits, " Plain region offset, in media units");
+ builder.AppendLine(partitionHeader.PlainRegionSizeInMediaUnits, " Plain region size, in media units");
+ builder.AppendLine(partitionHeader.LogoRegionOffsetInMediaUnits, " Logo region offset, in media units");
+ builder.AppendLine(partitionHeader.LogoRegionSizeInMediaUnits, " Logo region size, in media units");
+ builder.AppendLine(partitionHeader.ExeFSOffsetInMediaUnits, " ExeFS offset, in media units");
+ builder.AppendLine(partitionHeader.ExeFSSizeInMediaUnits, " ExeFS size, in media units");
+ builder.AppendLine(partitionHeader.ExeFSHashRegionSizeInMediaUnits, " ExeFS hash region offset, in media units");
+ builder.AppendLine(partitionHeader.Reserved3, " Reserved 3");
+ builder.AppendLine(partitionHeader.RomFSOffsetInMediaUnits, " RomFS offset, in media units");
+ builder.AppendLine(partitionHeader.RomFSSizeInMediaUnits, " RomFS size, in media units");
+ builder.AppendLine(partitionHeader.RomFSHashRegionSizeInMediaUnits, " RomFS hash region offset, in media units");
+ builder.AppendLine(partitionHeader.Reserved4, " Reserved 4");
+ builder.AppendLine(partitionHeader.ExeFSSuperblockHash, " ExeFS superblock SHA-256 hash");
+ builder.AppendLine(partitionHeader.RomFSSuperblockHash, " RomFS superblock SHA-256 hash");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, MetaData? metaData)
+ {
+ builder.AppendLine(" Meta Data Information:");
+ builder.AppendLine(" -------------------------");
+ if (metaData == null)
+ {
+ builder.AppendLine(" No meta file data");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(metaData.TitleIDDependencyList, " Title ID dependency list");
+ builder.AppendLine(metaData.Reserved1, " Reserved 1");
+ builder.AppendLine(metaData.CoreVersion, " Core version");
+ builder.AppendLine(metaData.Reserved2, " Reserved 2");
+ builder.AppendLine(metaData.IconData, " Icon data");
+ builder.AppendLine();
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/GCF.cs b/SabreTools.Serialization/Printers/GCF.cs
new file mode 100644
index 00000000..fa4a69b5
--- /dev/null
+++ b/SabreTools.Serialization/Printers/GCF.cs
@@ -0,0 +1,539 @@
+using System.Collections.Generic;
+using System.Text;
+using SabreTools.Models.GCF;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class GCF : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, File model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, File file)
+ {
+ builder.AppendLine("GCF Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ // Header
+ Print(builder, file.Header);
+
+ // Block Entries
+ Print(builder, file.BlockEntryHeader);
+ Print(builder, file.BlockEntries);
+
+ // Fragmentation Maps
+ Print(builder, file.FragmentationMapHeader);
+ Print(builder, file.FragmentationMaps);
+
+ // Block Entry Maps
+ Print(builder, file.BlockEntryMapHeader);
+ Print(builder, file.BlockEntryMaps);
+
+ // Directory and Directory Maps
+ Print(builder, file.DirectoryHeader);
+ Print(builder, file.DirectoryEntries, file.DirectoryNames);
+ // TODO: Should we print out the entire string table?
+ Print(builder, file.DirectoryInfo1Entries);
+ Print(builder, file.DirectoryInfo2Entries);
+ Print(builder, file.DirectoryCopyEntries);
+ Print(builder, file.DirectoryLocalEntries);
+ Print(builder, file.DirectoryMapHeader);
+ Print(builder, file.DirectoryMapEntries);
+
+ // Checksums and Checksum Maps
+ Print(builder, file.ChecksumHeader);
+ Print(builder, file.ChecksumMapHeader);
+ Print(builder, file.ChecksumMapEntries);
+ Print(builder, file.ChecksumEntries);
+
+ // Data Blocks
+ Print(builder, file.DataBlockHeader);
+ }
+
+ private static void Print(StringBuilder builder, Header? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.MajorVersion, " Major version");
+ builder.AppendLine(header.MinorVersion, " Minor version");
+ builder.AppendLine(header.CacheID, " Cache ID");
+ builder.AppendLine(header.LastVersionPlayed, " Last version played");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine(header.Dummy2, " Dummy 2");
+ builder.AppendLine(header.FileSize, " File size");
+ builder.AppendLine(header.BlockSize, " Block size");
+ builder.AppendLine(header.BlockCount, " Block count");
+ builder.AppendLine(header.Dummy3, " Dummy 3");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, BlockEntryHeader? header)
+ {
+ builder.AppendLine(" Block Entry Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No block entry header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.BlockCount, " Block count");
+ builder.AppendLine(header.BlocksUsed, " Blocks used");
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine(header.Dummy2, " Dummy 2");
+ builder.AppendLine(header.Dummy3, " Dummy 3");
+ builder.AppendLine(header.Dummy4, " Dummy 4");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, BlockEntry?[]? entries)
+ {
+ builder.AppendLine(" Block Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No block entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Block Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.EntryFlags, " Entry flags");
+ builder.AppendLine(entry.FileDataOffset, " File data offset");
+ builder.AppendLine(entry.FileDataSize, " File data size");
+ builder.AppendLine(entry.FirstDataBlockIndex, " First data block index");
+ builder.AppendLine(entry.NextBlockEntryIndex, " Next block entry index");
+ builder.AppendLine(entry.PreviousBlockEntryIndex, " Previous block entry index");
+ builder.AppendLine(entry.DirectoryIndex, " Directory index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, FragmentationMapHeader? header)
+ {
+ builder.AppendLine(" Fragmentation Map Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No fragmentation map header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.BlockCount, " Block count");
+ builder.AppendLine(header.FirstUnusedEntry, " First unused entry");
+ builder.AppendLine(header.Terminator, " Terminator");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, FragmentationMap?[]? entries)
+ {
+ builder.AppendLine(" Fragmentation Maps Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No fragmentation maps");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Fragmentation Map {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.NextDataBlockIndex, " Next data block index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, BlockEntryMapHeader? header)
+ {
+ builder.AppendLine(" Block Entry Map Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No block entry map header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.BlockCount, " Block count");
+ builder.AppendLine(header.FirstBlockEntryIndex, " First block entry index");
+ builder.AppendLine(header.LastBlockEntryIndex, " Last block entry index");
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, BlockEntryMap?[]? entries)
+ {
+ builder.AppendLine(" Block Entry Maps Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No block entry maps");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Block Entry Map {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.PreviousBlockEntryIndex, " Previous data block index");
+ builder.AppendLine(entry.NextBlockEntryIndex, " Next data block index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryHeader? header)
+ {
+ builder.AppendLine(" Directory Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No directory header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.CacheID, " Cache ID");
+ builder.AppendLine(header.LastVersionPlayed, " Last version played");
+ builder.AppendLine(header.ItemCount, " Item count");
+ builder.AppendLine(header.FileCount, " File count");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine(header.DirectorySize, " Directory size");
+ builder.AppendLine(header.NameSize, " Name size");
+ builder.AppendLine(header.Info1Count, " Info 1 count");
+ builder.AppendLine(header.CopyCount, " Copy count");
+ builder.AppendLine(header.LocalCount, " Local count");
+ builder.AppendLine(header.Dummy2, " Dummy 2");
+ builder.AppendLine(header.Dummy3, " Dummy 3");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryEntry?[]? entries, Dictionary? entryNames)
+ {
+ builder.AppendLine(" Directory Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.NameOffset, " Name offset");
+ builder.AppendLine(entryNames![entry.NameOffset], " Name");
+ builder.AppendLine(entry.ItemSize, " Item size");
+ builder.AppendLine(entry.ChecksumIndex, " Checksum index");
+ builder.AppendLine($" Directory flags: {entry.DirectoryFlags} (0x{entry.DirectoryFlags:X})");
+ builder.AppendLine(entry.ParentIndex, " Parent index");
+ builder.AppendLine(entry.NextIndex, " Next index");
+ builder.AppendLine(entry.FirstIndex, " First index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryInfo1Entry?[]? entries)
+ {
+ builder.AppendLine(" Directory Info 1 Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory info 1 entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Info 1 Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Dummy0, " Dummy 0");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryInfo2Entry?[]? entries)
+ {
+ builder.AppendLine(" Directory Info 2 Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory info 2 entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Info 2 Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Dummy0, " Dummy 0");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryCopyEntry?[]? entries)
+ {
+ builder.AppendLine(" Directory Copy Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory copy entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Copy Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.DirectoryIndex, " Directory index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryLocalEntry?[]? entries)
+ {
+ builder.AppendLine(" Directory Local Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory local entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Local Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.DirectoryIndex, " Directory index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryMapHeader? header)
+ {
+ builder.AppendLine(" Directory Map Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No directory map header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryMapEntry?[]? entries)
+ {
+ builder.AppendLine(" Directory Map Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory map entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Map Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.FirstBlockIndex, " First block index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ChecksumHeader? header)
+ {
+ builder.AppendLine(" Checksum Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No checksum header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.ChecksumSize, " Checksum size");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ChecksumMapHeader? header)
+ {
+ builder.AppendLine(" Checksum Map Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No checksum map header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine(header.ItemCount, " Item count");
+ builder.AppendLine(header.ChecksumCount, " Checksum count");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ChecksumMapEntry?[]? entries)
+ {
+ builder.AppendLine(" Checksum Map Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No checksum map entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Checksum Map Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.ChecksumCount, " Checksum count");
+ builder.AppendLine(entry.FirstChecksumIndex, " First checksum index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ChecksumEntry?[]? entries)
+ {
+ builder.AppendLine(" Checksum Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No checksum entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Checksum Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Checksum, " Checksum");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DataBlockHeader? header)
+ {
+ builder.AppendLine(" Data Block Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No data block header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.LastVersionPlayed, " Last version played");
+ builder.AppendLine(header.BlockCount, " Block count");
+ builder.AppendLine(header.BlockSize, " Block size");
+ builder.AppendLine(header.FirstBlockOffset, " First block offset");
+ builder.AppendLine(header.BlocksUsed, " Blocks used");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/IRD.cs b/SabreTools.Serialization/Printers/IRD.cs
new file mode 100644
index 00000000..aac5f867
--- /dev/null
+++ b/SabreTools.Serialization/Printers/IRD.cs
@@ -0,0 +1,55 @@
+using System.Text;
+using SabreTools.Models.IRD;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class IRD : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, File model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, File ird)
+ {
+ builder.AppendLine("IRD Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine(ird.Magic, "Magic", Encoding.ASCII);
+ builder.AppendLine(ird.Version, "Version");
+ builder.AppendLine(ird.TitleID, "Title ID");
+ builder.AppendLine(ird.TitleLength, "Title length");
+ builder.AppendLine(ird.Title, "Title");
+ builder.AppendLine(ird.SystemVersion, "System version");
+ builder.AppendLine(ird.GameVersion, "Game version");
+ builder.AppendLine(ird.AppVersion, "App version");
+ builder.AppendLine(ird.HeaderLength, "Header length");
+ builder.AppendLine(ird.Header, "Header");
+ builder.AppendLine(ird.FooterLength, "Footer length");
+ builder.AppendLine(ird.Footer, "Footer");
+ builder.AppendLine(ird.RegionCount, "Region count");
+ if (ird.RegionCount != 0 && ird.RegionHashes != null && ird.RegionHashes.Length != 0)
+ {
+ for (int i = 0; i < ird.RegionCount; i++)
+ {
+ builder.AppendLine(ird.RegionHashes[i], $"Region {i} hash");
+ }
+ }
+ builder.AppendLine(ird.FileCount, "File count");
+ for (int i = 0; i < ird.FileCount; i++)
+ {
+ if (ird.FileKeys != null)
+ builder.AppendLine(ird.FileKeys[i], $"File {i} key");
+ if (ird.FileHashes != null)
+ builder.AppendLine(ird.FileHashes[i], $"File {i} hash");
+ }
+ builder.AppendLine(ird.ExtraConfig, "Extra config");
+ builder.AppendLine(ird.Attachments, "Attachments");
+ builder.AppendLine(ird.Data1Key, "Data 1 key");
+ builder.AppendLine(ird.Data2Key, "Data 2 key");
+ builder.AppendLine(ird.PIC, "PIC");
+ builder.AppendLine(ird.UID, "UID");
+ builder.AppendLine(ird.CRC, "CRC");
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/InstallShieldCabinet.cs b/SabreTools.Serialization/Printers/InstallShieldCabinet.cs
new file mode 100644
index 00000000..ac133590
--- /dev/null
+++ b/SabreTools.Serialization/Printers/InstallShieldCabinet.cs
@@ -0,0 +1,417 @@
+using System.Collections.Generic;
+using System.Text;
+using SabreTools.Models.InstallShieldCabinet;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class InstallShieldCabinet : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Cabinet model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Cabinet cabinet)
+ {
+ builder.AppendLine("InstallShield Cabinet Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ // Major Version
+ int majorVersion = GetMajorVersion(cabinet.CommonHeader);
+
+ // Headers
+ Print(builder, cabinet.CommonHeader, majorVersion);
+ Print(builder, cabinet.VolumeHeader, majorVersion);
+ Print(builder, cabinet.Descriptor);
+
+ // File Descriptors
+ Print(builder, cabinet.FileDescriptorOffsets);
+ Print(builder, cabinet.DirectoryNames);
+ Print(builder, cabinet.FileDescriptors);
+
+ // File Groups
+ Print(builder, cabinet.FileGroupOffsets, "File Group");
+ Print(builder, cabinet.FileGroups);
+
+ // Components
+ Print(builder, cabinet.ComponentOffsets, "Component");
+ Print(builder, cabinet.Components);
+ }
+
+ private static int GetMajorVersion(CommonHeader? header)
+ {
+ uint majorVersion = header?.Version ?? 0;
+ if (majorVersion >> 24 == 1)
+ {
+ majorVersion = (majorVersion >> 12) & 0x0F;
+ }
+ else if (majorVersion >> 24 == 2 || majorVersion >> 24 == 4)
+ {
+ majorVersion = majorVersion & 0xFFFF;
+ if (majorVersion != 0)
+ majorVersion /= 100;
+ }
+
+ return (int)majorVersion;
+ }
+
+ private static void Print(StringBuilder builder, CommonHeader? header, int majorVersion)
+ {
+ builder.AppendLine(" Common Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(value: " No common header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Signature, " Signature");
+ builder.AppendLine($" Version: {header.Version} (0x{header.Version:X}) [{majorVersion}]");
+ builder.AppendLine(header.VolumeInfo, " Volume info");
+ builder.AppendLine(header.DescriptorOffset, " Descriptor offset");
+ builder.AppendLine(header.DescriptorSize, " Descriptor size");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, VolumeHeader? header, int majorVersion)
+ {
+ builder.AppendLine(" Volume Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(value: " No volume header");
+ builder.AppendLine();
+ return;
+ }
+
+ if (majorVersion <= 5)
+ {
+ builder.AppendLine(header.DataOffset, " Data offset");
+ builder.AppendLine(header.FirstFileIndex, " First file index");
+ builder.AppendLine(header.LastFileIndex, " Last file index");
+ builder.AppendLine(header.FirstFileOffset, " First file offset");
+ builder.AppendLine(header.FirstFileSizeExpanded, " First file size expanded");
+ builder.AppendLine(header.FirstFileSizeCompressed, " First file size compressed");
+ builder.AppendLine(header.LastFileOffset, " Last file offset");
+ builder.AppendLine(header.LastFileSizeExpanded, " Last file size expanded");
+ builder.AppendLine(header.LastFileSizeCompressed, " Last file size compressed");
+ }
+ else
+ {
+ builder.AppendLine(header.DataOffset, " Data offset");
+ builder.AppendLine(header.DataOffsetHigh, " Data offset high");
+ builder.AppendLine(header.FirstFileIndex, " First file index");
+ builder.AppendLine(header.LastFileIndex, " Last file index");
+ builder.AppendLine(header.FirstFileOffset, " First file offset");
+ builder.AppendLine(header.FirstFileOffsetHigh, " First file offset high");
+ builder.AppendLine(header.FirstFileSizeExpanded, " First file size expanded");
+ builder.AppendLine(header.FirstFileSizeExpandedHigh, " First file size expanded high");
+ builder.AppendLine(header.FirstFileSizeCompressed, " First file size compressed");
+ builder.AppendLine(header.FirstFileSizeCompressedHigh, " First file size compressed high");
+ builder.AppendLine(header.LastFileOffset, " Last file offset");
+ builder.AppendLine(header.LastFileOffsetHigh, " Last file offset high");
+ builder.AppendLine(header.LastFileSizeExpanded, " Last file size expanded");
+ builder.AppendLine(header.LastFileSizeExpandedHigh, " Last file size expanded high");
+ builder.AppendLine(header.LastFileSizeCompressed, " Last file size compressed");
+ builder.AppendLine(header.LastFileSizeCompressedHigh, " Last file size compressed high");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Descriptor? descriptor)
+ {
+ builder.AppendLine(" Descriptor Information:");
+ builder.AppendLine(" -------------------------");
+ if (descriptor == null)
+ {
+ builder.AppendLine(" No descriptor");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(descriptor.StringsOffset, " Strings offset");
+ builder.AppendLine(descriptor.Reserved0, " Reserved 0");
+ builder.AppendLine(descriptor.ComponentListOffset, " Component list offset");
+ builder.AppendLine(descriptor.FileTableOffset, " File table offset");
+ builder.AppendLine(descriptor.Reserved1, " Reserved 1");
+ builder.AppendLine(descriptor.FileTableSize, " File table size");
+ builder.AppendLine(descriptor.FileTableSize2, " File table size 2");
+ builder.AppendLine(descriptor.DirectoryCount, " Directory count");
+ builder.AppendLine(descriptor.Reserved2, " Reserved 2");
+ builder.AppendLine(descriptor.Reserved3, " Reserved 3");
+ builder.AppendLine(descriptor.Reserved4, " Reserved 4");
+ builder.AppendLine(descriptor.FileCount, " File count");
+ builder.AppendLine(descriptor.FileTableOffset2, " File table offset 2");
+ builder.AppendLine(descriptor.ComponentTableInfoCount, " Component table info count");
+ builder.AppendLine(descriptor.ComponentTableOffset, " Component table offset");
+ builder.AppendLine(descriptor.Reserved5, " Reserved 5");
+ builder.AppendLine(descriptor.Reserved6, " Reserved 6");
+ builder.AppendLine();
+
+ builder.AppendLine(" File group offsets:");
+ builder.AppendLine(" -------------------------");
+ if (descriptor.FileGroupOffsets == null || descriptor.FileGroupOffsets.Length == 0)
+ {
+ builder.AppendLine(" No file group offsets");
+ }
+ else
+ {
+ for (int i = 0; i < descriptor.FileGroupOffsets.Length; i++)
+ {
+ builder.AppendLine(descriptor.FileGroupOffsets[i], $" File Group Offset {i}");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Component offsets:");
+ builder.AppendLine(" -------------------------");
+ if (descriptor.ComponentOffsets == null || descriptor.ComponentOffsets.Length == 0)
+ {
+ builder.AppendLine(" No component offsets");
+ }
+ else
+ {
+ for (int i = 0; i < descriptor.ComponentOffsets.Length; i++)
+ {
+ builder.AppendLine(descriptor.ComponentOffsets[i], $" Component Offset {i}");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(descriptor.SetupTypesOffset, " Setup types offset");
+ builder.AppendLine(descriptor.SetupTableOffset, " Setup table offset");
+ builder.AppendLine(descriptor.Reserved7, " Reserved 7");
+ builder.AppendLine(descriptor.Reserved8, " Reserved 8");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, uint[]? entries)
+ {
+ builder.AppendLine(" File Descriptor Offsets:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No file descriptor offsets");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ builder.AppendLine(entries[i], $" File Descriptor Offset {i}");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, string?[]? entries)
+ {
+ builder.AppendLine(" Directory Names:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory names");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ builder.AppendLine(entries[i], $" Directory Name {i}");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, FileDescriptor?[]? entries)
+ {
+ builder.AppendLine(" File Descriptors:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No file descriptors");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" File Descriptor {i}:");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.NameOffset, " Name offset");
+ builder.AppendLine(entry.Name, " Name");
+ builder.AppendLine(entry.DirectoryIndex, " Directory index");
+ builder.AppendLine($" Flags: {entry.Flags} (0x{entry.Flags:X})");
+ builder.AppendLine(entry.ExpandedSize, " Expanded size");
+ builder.AppendLine(entry.CompressedSize, " Compressed size");
+ builder.AppendLine(entry.DataOffset, " Data offset");
+ builder.AppendLine(entry.MD5, " MD5");
+ builder.AppendLine(entry.Volume, " Volume");
+ builder.AppendLine(entry.LinkPrevious, " Link previous");
+ builder.AppendLine(entry.LinkNext, " Link next");
+ builder.AppendLine($" Link flags: {entry.LinkFlags} (0x{entry.LinkFlags:X})");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Dictionary? entries, string name)
+ {
+ builder.AppendLine($" {name} Offsets:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Count == 0)
+ {
+ builder.AppendLine($" No {name.ToLowerInvariant()} offsets");
+ builder.AppendLine();
+ return;
+ }
+
+ foreach (var kvp in entries)
+ {
+ long offset = kvp.Key;
+ var value = kvp.Value;
+
+ builder.AppendLine($" {name} Offset {offset}:");
+ if (value == null)
+ {
+ builder.AppendLine($" Unassigned {name.ToLowerInvariant()}");
+ continue;
+ }
+
+ builder.AppendLine(value.NameOffset, " Name offset");
+ builder.AppendLine(value.Name, " Name");
+ builder.AppendLine(value.DescriptorOffset, " Descriptor offset");
+ builder.AppendLine(value.NextOffset, " Next offset");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, FileGroup?[]? entries)
+ {
+ builder.AppendLine(" File Groups:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No file groups");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var fileGroup = entries[i];
+ builder.AppendLine($" File Group {i}:");
+ if (fileGroup == null)
+ {
+ builder.AppendLine(" Unassigned file group");
+ continue;
+ }
+
+ builder.AppendLine(fileGroup.NameOffset, " Name offset");
+ builder.AppendLine(fileGroup.Name, " Name");
+ builder.AppendLine(fileGroup.ExpandedSize, " Expanded size");
+ builder.AppendLine(fileGroup.Reserved0, " Reserved 0");
+ builder.AppendLine(fileGroup.CompressedSize, " Compressed size");
+ builder.AppendLine(fileGroup.Reserved1, " Reserved 1");
+ builder.AppendLine(fileGroup.Reserved2, " Reserved 2");
+ builder.AppendLine(fileGroup.Attribute1, " Attribute 1");
+ builder.AppendLine(fileGroup.Attribute2, " Attribute 2");
+ builder.AppendLine(fileGroup.FirstFile, " First file");
+ builder.AppendLine(fileGroup.LastFile, " Last file");
+ builder.AppendLine(fileGroup.UnknownOffset, " Unknown offset");
+ builder.AppendLine(fileGroup.Var4Offset, " Var 4 offset");
+ builder.AppendLine(fileGroup.Var1Offset, " Var 1 offset");
+ builder.AppendLine(fileGroup.HTTPLocationOffset, " HTTP location offset");
+ builder.AppendLine(fileGroup.FTPLocationOffset, " FTP location offset");
+ builder.AppendLine(fileGroup.MiscOffset, " Misc. offset");
+ builder.AppendLine(fileGroup.Var2Offset, " Var 2 offset");
+ builder.AppendLine(fileGroup.TargetDirectoryOffset, " Target directory offset");
+ builder.AppendLine(fileGroup.Reserved3, " Reserved 3");
+ builder.AppendLine(fileGroup.Reserved4, " Reserved 4");
+ builder.AppendLine(fileGroup.Reserved5, " Reserved 5");
+ builder.AppendLine(fileGroup.Reserved6, " Reserved 6");
+ builder.AppendLine(fileGroup.Reserved7, " Reserved 7");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Component?[]? entries)
+ {
+ builder.AppendLine(" Components:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No components");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var component = entries[i];
+ builder.AppendLine($" Component {i}:");
+ if (component == null)
+ {
+ builder.AppendLine(" Unassigned component");
+ continue;
+ }
+
+ builder.AppendLine(component.IdentifierOffset, " Identifier offset");
+ builder.AppendLine(component.Identifier, " Identifier");
+ builder.AppendLine(component.DescriptorOffset, " Descriptor offset");
+ builder.AppendLine(component.DisplayNameOffset, " Display name offset");
+ builder.AppendLine(component.DisplayName, " Display name");
+ builder.AppendLine(component.Reserved0, " Reserved 0");
+ builder.AppendLine(component.ReservedOffset0, " Reserved offset 0");
+ builder.AppendLine(component.ReservedOffset1, " Reserved offset 1");
+ builder.AppendLine(component.ComponentIndex, " Component index");
+ builder.AppendLine(component.NameOffset, " Name offset");
+ builder.AppendLine(component.Name, " Name");
+ builder.AppendLine(component.ReservedOffset2, " Reserved offset 2");
+ builder.AppendLine(component.ReservedOffset3, " Reserved offset 3");
+ builder.AppendLine(component.ReservedOffset4, " Reserved offset 4");
+ builder.AppendLine(component.Reserved1, " Reserved 1");
+ builder.AppendLine(component.CLSIDOffset, " CLSID offset");
+ builder.AppendLine(component.CLSID, " CLSID");
+ builder.AppendLine(component.Reserved2, " Reserved 2");
+ builder.AppendLine(component.Reserved3, " Reserved 3");
+ builder.AppendLine(component.DependsCount, " Depends count");
+ builder.AppendLine(component.DependsOffset, " Depends offset");
+ builder.AppendLine(component.FileGroupCount, " File group count");
+ builder.AppendLine(component.FileGroupNamesOffset, " File group names offset");
+ builder.AppendLine();
+
+ builder.AppendLine(" File group names:");
+ builder.AppendLine(" -------------------------");
+ if (component.FileGroupNames == null || component.FileGroupNames.Length == 0)
+ {
+ builder.AppendLine(" No file group names");
+ }
+ else
+ {
+ for (int j = 0; j < component.FileGroupNames.Length; j++)
+ {
+ builder.AppendLine(component.FileGroupNames[j], $" File Group Name {j}");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(component.X3Count, " X3 count");
+ builder.AppendLine(component.X3Offset, " X3 offset");
+ builder.AppendLine(component.SubComponentsCount, " Sub-components count");
+ builder.AppendLine(component.SubComponentsOffset, " Sub-components offset");
+ builder.AppendLine(component.NextComponentOffset, " Next component offset");
+ builder.AppendLine(component.ReservedOffset5, " Reserved offset 5");
+ builder.AppendLine(component.ReservedOffset6, " Reserved offset 6");
+ builder.AppendLine(component.ReservedOffset7, " Reserved offset 7");
+ builder.AppendLine(component.ReservedOffset8, " Reserved offset 8");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/LinearExecutable.cs b/SabreTools.Serialization/Printers/LinearExecutable.cs
new file mode 100644
index 00000000..d5874136
--- /dev/null
+++ b/SabreTools.Serialization/Printers/LinearExecutable.cs
@@ -0,0 +1,783 @@
+using System.Text;
+using SabreTools.Models.LinearExecutable;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class LinearExecutable : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Executable model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Executable executable)
+ {
+ builder.AppendLine("New Executable Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ // Stub
+ Print(builder, executable.Stub?.Header);
+
+ // Information Block
+ Print(builder, executable.InformationBlock);
+
+ // Tables
+ Print(builder, executable.ObjectTable);
+ Print(builder, executable.ObjectPageMap);
+ Print(builder, executable.ResourceTable);
+ Print(builder, executable.ResidentNamesTable);
+ Print(builder, executable.EntryTable);
+ Print(builder, executable.ModuleFormatDirectivesTable);
+ Print(builder, executable.VerifyRecordDirectiveTable);
+ Print(builder, executable.FixupPageTable);
+ Print(builder, executable.FixupRecordTable);
+ Print(builder, executable.ImportModuleNameTable);
+ Print(builder, executable.ImportModuleProcedureNameTable);
+ Print(builder, executable.PerPageChecksumTable);
+ Print(builder, executable.NonResidentNamesTable);
+
+ // Debug
+ Print(builder, executable.DebugInformation);
+ }
+
+ private static void Print(StringBuilder builder, Models.MSDOS.ExecutableHeader? header)
+ {
+ builder.AppendLine(" MS-DOS Stub Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No MS-DOS stub header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Magic, " Magic number");
+ builder.AppendLine(header.LastPageBytes, " Last page bytes");
+ builder.AppendLine(header.Pages, " Pages");
+ builder.AppendLine(header.RelocationItems, " Relocation items");
+ builder.AppendLine(header.HeaderParagraphSize, " Header paragraph size");
+ builder.AppendLine(header.MinimumExtraParagraphs, " Minimum extra paragraphs");
+ builder.AppendLine(header.MaximumExtraParagraphs, " Maximum extra paragraphs");
+ builder.AppendLine(header.InitialSSValue, " Initial SS value");
+ builder.AppendLine(header.InitialSPValue, " Initial SP value");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine(header.InitialIPValue, " Initial IP value");
+ builder.AppendLine(header.InitialCSValue, " Initial CS value");
+ builder.AppendLine(header.RelocationTableAddr, " Relocation table address");
+ builder.AppendLine(header.OverlayNumber, " Overlay number");
+ builder.AppendLine();
+
+ builder.AppendLine(" MS-DOS Stub Extended Header Information:");
+ builder.AppendLine(" -------------------------");
+ builder.AppendLine(header.Reserved1, " Reserved words");
+ builder.AppendLine(header.OEMIdentifier, " OEM identifier");
+ builder.AppendLine(header.OEMInformation, " OEM information");
+ builder.AppendLine(header.Reserved2, " Reserved words");
+ builder.AppendLine(header.NewExeHeaderAddr, " New EXE header address");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, InformationBlock? block)
+ {
+ builder.AppendLine(" Information Block Information:");
+ builder.AppendLine(" -------------------------");
+ if (block == null)
+ {
+ builder.AppendLine(" No information block");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(block.Signature, " Signature");
+ builder.AppendLine($" Byte order: {block.ByteOrder} (0x{block.ByteOrder:X})");
+ builder.AppendLine($" Word order: {block.WordOrder} (0x{block.WordOrder:X})");
+ builder.AppendLine(block.ExecutableFormatLevel, " Executable format level");
+ builder.AppendLine($" CPU type: {block.CPUType} (0x{block.CPUType:X})");
+ builder.AppendLine($" Module OS: {block.ModuleOS} (0x{block.ModuleOS:X})");
+ builder.AppendLine(block.ModuleVersion, " Module version");
+ builder.AppendLine($" Module type flags: {block.ModuleTypeFlags} (0x{block.ModuleTypeFlags:X})");
+ builder.AppendLine(block.ModuleNumberPages, " Module number pages");
+ builder.AppendLine(block.InitialObjectCS, " Initial object CS");
+ builder.AppendLine(block.InitialEIP, " Initial EIP");
+ builder.AppendLine(block.InitialObjectSS, " Initial object SS");
+ builder.AppendLine(block.InitialESP, " Initial ESP");
+ builder.AppendLine(block.MemoryPageSize, " Memory page size");
+ builder.AppendLine(block.BytesOnLastPage, " Bytes on last page");
+ builder.AppendLine(block.FixupSectionSize, " Fix-up section size");
+ builder.AppendLine(block.FixupSectionChecksum, " Fix-up section checksum");
+ builder.AppendLine(block.LoaderSectionSize, " Loader section size");
+ builder.AppendLine(block.LoaderSectionChecksum, " Loader section checksum");
+ builder.AppendLine(block.ObjectTableOffset, " Object table offset");
+ builder.AppendLine(block.ObjectTableCount, " Object table count");
+ builder.AppendLine(block.ObjectPageMapOffset, " Object page map offset");
+ builder.AppendLine(block.ObjectIterateDataMapOffset, " Object iterate data map offset");
+ builder.AppendLine(block.ResourceTableOffset, " Resource table offset");
+ builder.AppendLine(block.ResourceTableCount, " Resource table count");
+ builder.AppendLine(block.ResidentNamesTableOffset, " Resident names table offset");
+ builder.AppendLine(block.EntryTableOffset, " Entry table offset");
+ builder.AppendLine(block.ModuleDirectivesTableOffset, " Module directives table offset");
+ builder.AppendLine(block.ModuleDirectivesCount, " Module directives table count");
+ builder.AppendLine(block.FixupPageTableOffset, " Fix-up page table offset");
+ builder.AppendLine(block.FixupRecordTableOffset, " Fix-up record table offset");
+ builder.AppendLine(block.ImportedModulesNameTableOffset, " Imported modules name table offset");
+ builder.AppendLine(block.ImportedModulesCount, " Imported modules count");
+ builder.AppendLine(block.ImportProcedureNameTableOffset, " Imported procedure name table count");
+ builder.AppendLine(block.PerPageChecksumTableOffset, " Per-page checksum table offset");
+ builder.AppendLine(block.DataPagesOffset, " Data pages offset");
+ builder.AppendLine(block.PreloadPageCount, " Preload page count");
+ builder.AppendLine(block.NonResidentNamesTableOffset, " Non-resident names table offset");
+ builder.AppendLine(block.NonResidentNamesTableLength, " Non-resident names table length");
+ builder.AppendLine(block.NonResidentNamesTableChecksum, " Non-resident names table checksum");
+ builder.AppendLine(block.AutomaticDataObject, " Automatic data object");
+ builder.AppendLine(block.DebugInformationOffset, " Debug information offset");
+ builder.AppendLine(block.DebugInformationLength, " Debug information length");
+ builder.AppendLine(block.PreloadInstancePagesNumber, " Preload instance pages number");
+ builder.AppendLine(block.DemandInstancePagesNumber, " Demand instance pages number");
+ builder.AppendLine(block.ExtraHeapAllocation, " Extra heap allocation");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ObjectTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Object Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No object table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Object Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.VirtualSegmentSize, " Virtual segment size");
+ builder.AppendLine(entry.RelocationBaseAddress, " Relocation base address");
+ builder.AppendLine($" Object flags: {entry.ObjectFlags} (0x{entry.ObjectFlags:X})");
+ builder.AppendLine(entry.PageTableIndex, " Page table index");
+ builder.AppendLine(entry.PageTableEntries, " Page table entries");
+ builder.AppendLine(entry.Reserved, " Reserved");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ObjectPageMapEntry?[]? entries)
+ {
+ builder.AppendLine(" Object Page Map Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No object page map entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Object Page Map Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.PageDataOffset, " Page data offset");
+ builder.AppendLine(entry.DataSize, " Data size");
+ builder.AppendLine($" Flags: {entry.Flags} (0x{entry.Flags:X})");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ResourceTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Resource Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No resource table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Resource Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine($" Type ID: {entry.TypeID} (0x{entry.TypeID:X})");
+ builder.AppendLine(entry.NameID, " Name ID");
+ builder.AppendLine(entry.ResourceSize, " Resource size");
+ builder.AppendLine(entry.ObjectNumber, " Object number");
+ builder.AppendLine(entry.Offset, " Offset");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ResidentNamesTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Resident Names Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No resident names table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Resident Names Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Length, " Length");
+ builder.AppendLine(entry.Name, " Name");
+ builder.AppendLine(entry.OrdinalNumber, " Ordinal number");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, EntryTableBundle?[]? bundles)
+ {
+ builder.AppendLine(" Entry Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (bundles == null || bundles.Length == 0)
+ {
+ builder.AppendLine(" No entry table bundles");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < bundles.Length; i++)
+ {
+ var bundle = bundles[i];
+ builder.AppendLine($" Entry Table Bundle {i}");
+ if (bundle == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(bundle.Entries, " Entries");
+ builder.AppendLine($" Bundle type: {bundle.BundleType} (0x{bundle.BundleType:X})");
+ builder.AppendLine();
+
+ builder.AppendLine(" Entry Table Entries:");
+ builder.AppendLine(" -------------------------");
+ if (bundle.TableEntries == null || bundle.TableEntries.Length == 0)
+ {
+ builder.AppendLine(" No entry table entries");
+ builder.AppendLine();
+ continue;
+ }
+
+ for (int j = 0; j < bundle.TableEntries.Length; j++)
+ {
+ var entry = bundle.TableEntries[j];
+ builder.AppendLine($" Entry Table Entry {j}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ switch (bundle.BundleType & ~BundleType.ParameterTypingInformationPresent)
+ {
+ case BundleType.UnusedEntry:
+ builder.AppendLine(" Unused, empty entry");
+ break;
+
+ case BundleType.SixteenBitEntry:
+ builder.AppendLine(entry.SixteenBitObjectNumber, " Object number");
+ builder.AppendLine($" Entry flags: {entry.SixteenBitEntryFlags} (0x{entry.SixteenBitEntryFlags:X})");
+ builder.AppendLine(entry.SixteenBitOffset, " Offset");
+ break;
+
+ case BundleType.TwoEightySixCallGateEntry:
+ builder.AppendLine(entry.TwoEightySixObjectNumber, " Object number");
+ builder.AppendLine($" Entry flags: {entry.TwoEightySixEntryFlags} (0x{entry.TwoEightySixEntryFlags:X})");
+ builder.AppendLine(entry.TwoEightySixOffset, " Offset");
+ builder.AppendLine(entry.TwoEightySixCallgate, " Callgate");
+ break;
+
+ case BundleType.ThirtyTwoBitEntry:
+ builder.AppendLine(entry.ThirtyTwoBitObjectNumber, " Object number");
+ builder.AppendLine($" Entry flags: {entry.ThirtyTwoBitEntryFlags} (0x{entry.ThirtyTwoBitEntryFlags:X})");
+ builder.AppendLine(entry.ThirtyTwoBitOffset, " Offset");
+ break;
+
+ case BundleType.ForwarderEntry:
+ builder.AppendLine(entry.ForwarderReserved, " Reserved");
+ builder.AppendLine($" Forwarder flags: {entry.ForwarderFlags} (0x{entry.ForwarderFlags:X})");
+ builder.AppendLine(entry.ForwarderModuleOrdinalNumber, " Module ordinal number");
+ builder.AppendLine(entry.ProcedureNameOffset, " Procedure name offset");
+ builder.AppendLine(entry.ImportOrdinalNumber, " Import ordinal number");
+ break;
+
+ default:
+ builder.AppendLine($" Unknown entry type {bundle.BundleType}");
+ break;
+ }
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ModuleFormatDirectivesTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Module Format Directives Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No module format directives table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Moduile Format Directives Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine($" Directive number: {entry.DirectiveNumber} (0x{entry.DirectiveNumber:X})");
+ builder.AppendLine(entry.DirectiveDataLength, " Directive data length");
+ builder.AppendLine(entry.DirectiveDataOffset, " Directive data offset");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, VerifyRecordDirectiveTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Verify Record Directive Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No verify record directive table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Verify Record Directive Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.EntryCount, " Entry count");
+ builder.AppendLine(entry.OrdinalIndex, " Ordinal index");
+ builder.AppendLine(entry.Version, " Version");
+ builder.AppendLine(entry.ObjectEntriesCount, " Object entries count");
+ builder.AppendLine(entry.ObjectNumberInModule, " Object number in module");
+ builder.AppendLine(entry.ObjectLoadBaseAddress, " Object load base address");
+ builder.AppendLine(entry.ObjectVirtualAddressSize, " Object virtual address size");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, FixupPageTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Fix-up Page Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No fix-up page table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Fix-up Page Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Offset, " Offset");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, FixupRecordTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Fix-up Record Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No fix-up record table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Fix-up Record Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine($" Source type: {entry.SourceType} (0x{entry.SourceType:X})");
+ builder.AppendLine($" Target flags: {entry.TargetFlags} (0x{entry.TargetFlags:X})");
+
+ // Source list flag
+#if NET20 || NET35
+ if ((entry.SourceType & FixupRecordSourceType.SourceListFlag) != 0)
+#else
+ if (entry.SourceType.HasFlag(FixupRecordSourceType.SourceListFlag))
+#endif
+ builder.AppendLine(entry.SourceOffsetListCount, " Source offset list count");
+ else
+ builder.AppendLine(entry.SourceOffset, " Source offset");
+
+ // OBJECT / TRGOFF
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.InternalReference) != 0)
+#else
+ if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.InternalReference))
+#endif
+ {
+ // 16-bit Object Number/Module Ordinal Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag) != 0)
+#else
+ if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag))
+#endif
+ builder.AppendLine(entry.TargetObjectNumberWORD, " Target object number");
+ else
+ builder.AppendLine(entry.TargetObjectNumberByte, " Target object number");
+
+ // 16-bit Selector fixup
+#if NET20 || NET35
+ if ((entry.SourceType & FixupRecordSourceType.SixteenBitSelectorFixup) != 0)
+#else
+ if (!entry.SourceType.HasFlag(FixupRecordSourceType.SixteenBitSelectorFixup))
+#endif
+ {
+ // 32-bit Target Offset Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag) != 0)
+#else
+ if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag))
+#endif
+ builder.AppendLine(entry.TargetOffsetDWORD, " Target offset");
+ else
+ builder.AppendLine(entry.TargetOffsetWORD, " Target offset");
+ }
+ }
+
+ // MOD ORD# / IMPORT ORD / ADDITIVE
+#if NET20 || NET35
+ else if ((entry.TargetFlags & FixupRecordTargetFlags.ImportedReferenceByOrdinal) != 0)
+#else
+ else if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ImportedReferenceByOrdinal))
+#endif
+ {
+ // 16-bit Object Number/Module Ordinal Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag))
+#endif
+ builder.AppendLine(entry.OrdinalIndexImportModuleNameTableWORD, " Ordinal index import module name table");
+ else
+ builder.AppendLine(entry.OrdinalIndexImportModuleNameTableByte, " Ordinal index import module name table");
+
+ // 8-bit Ordinal Flag & 32-bit Target Offset Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.EightBitOrdinalFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.EightBitOrdinalFlag))
+#endif
+ builder.AppendLine(entry.ImportedOrdinalNumberByte, " Imported ordinal number");
+#if NET20 || NET35
+ else if ((entry.TargetFlags & FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag) != 0)
+#else
+ else if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag))
+#endif
+ builder.AppendLine(entry.ImportedOrdinalNumberDWORD, " Imported ordinal number");
+ else
+ builder.AppendLine(entry.ImportedOrdinalNumberWORD, " Imported ordinal number");
+
+ // Additive Fixup Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.AdditiveFixupFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.AdditiveFixupFlag))
+#endif
+ {
+ // 32-bit Additive Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag))
+#endif
+ builder.AppendLine(entry.AdditiveFixupValueDWORD, " Additive fixup value");
+ else
+ builder.AppendLine(entry.AdditiveFixupValueWORD, " Additive fixup value");
+ }
+ }
+
+ // MOD ORD# / PROCEDURE NAME OFFSET / ADDITIVE
+#if NET20 || NET35
+ else if ((entry.TargetFlags & FixupRecordTargetFlags.ImportedReferenceByName) != 0)
+#else
+ else if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ImportedReferenceByName))
+#endif
+ {
+ // 16-bit Object Number/Module Ordinal Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag))
+#endif
+ builder.AppendLine(entry.OrdinalIndexImportModuleNameTableWORD, " Ordinal index import module name table");
+ else
+ builder.AppendLine(entry.OrdinalIndexImportModuleNameTableByte, " Ordinal index import module name table");
+
+ // 32-bit Target Offset Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitTargetOffsetFlag))
+#endif
+ builder.AppendLine(entry.OffsetImportProcedureNameTableDWORD, " Offset import procedure name table");
+ else
+ builder.AppendLine(entry.OffsetImportProcedureNameTableWORD, " Offset import procedure name table");
+
+ // Additive Fixup Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.AdditiveFixupFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.AdditiveFixupFlag))
+#endif
+ {
+ // 32-bit Additive Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag))
+#endif
+ builder.AppendLine(entry.AdditiveFixupValueDWORD, " Additive fixup value");
+ else
+ builder.AppendLine(entry.AdditiveFixupValueWORD, " Additive fixup value");
+ }
+ }
+
+ // ORD # / ADDITIVE
+#if NET20 || NET35
+ else if ((entry.TargetFlags & FixupRecordTargetFlags.InternalReferenceViaEntryTable) != 0)
+#else
+ else if (entry.TargetFlags.HasFlag(FixupRecordTargetFlags.InternalReferenceViaEntryTable))
+#endif
+ {
+ // 16-bit Object Number/Module Ordinal Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.SixteenBitObjectNumberModuleOrdinalFlag))
+#endif
+ builder.AppendLine(entry.TargetObjectNumberWORD, " Target object number");
+ else
+ builder.AppendLine(entry.TargetObjectNumberByte, " Target object number");
+
+ // Additive Fixup Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.AdditiveFixupFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.AdditiveFixupFlag))
+#endif
+ {
+ // 32-bit Additive Flag
+#if NET20 || NET35
+ if ((entry.TargetFlags & FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag) != 0)
+#else
+ if (!entry.TargetFlags.HasFlag(FixupRecordTargetFlags.ThirtyTwoBitAdditiveFixupFlag))
+#endif
+ builder.AppendLine(entry.AdditiveFixupValueDWORD, " Additive fixup value");
+ else
+ builder.AppendLine(entry.AdditiveFixupValueWORD, " Additive fixup value");
+ }
+ }
+
+ // No other top-level flags recognized
+ else
+ {
+ builder.AppendLine(" Unknown entry format");
+ }
+
+ builder.AppendLine();
+ builder.AppendLine(" Source Offset List:");
+ builder.AppendLine(" -------------------------");
+ if (entry.SourceOffsetList == null || entry.SourceOffsetList.Length == 0)
+ {
+ builder.AppendLine(" No source offset list entries");
+ }
+ else
+ {
+ for (int j = 0; j < entry.SourceOffsetList.Length; j++)
+ {
+ builder.AppendLine(entry.SourceOffsetList[j], $" Source Offset List Entry {j}");
+ }
+ }
+ builder.AppendLine();
+ }
+ }
+
+ private static void Print(StringBuilder builder, ImportModuleNameTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Import Module Name Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No import module name table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Import Module Name Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Length, " Length");
+ builder.AppendLine(entry.Name, " Name");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ImportModuleProcedureNameTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Import Module Procedure Name Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No import module procedure name table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Import Module Procedure Name Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Length, " Length");
+ builder.AppendLine(entry.Name, " Name");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, PerPageChecksumTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Per-Page Checksum Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No per-page checksum table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Per-Page Checksum Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Checksum, " Checksum");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, NonResidentNamesTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Non-Resident Names Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No non-resident names table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Non-Resident Names Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Length, " Length");
+ builder.AppendLine(entry.Name, " Name");
+ builder.AppendLine(entry.OrdinalNumber, " Ordinal number");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DebugInformation? di)
+ {
+ builder.AppendLine(" Debug Information:");
+ builder.AppendLine(" -------------------------");
+ if (di == null)
+ {
+ builder.AppendLine(" No debug information");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(di.Signature, " Signature");
+ builder.AppendLine($" Format type: {di.FormatType} (0x{di.FormatType:X})");
+ // Debugger data
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/MSDOS.cs b/SabreTools.Serialization/Printers/MSDOS.cs
new file mode 100644
index 00000000..4ed2dbaf
--- /dev/null
+++ b/SabreTools.Serialization/Printers/MSDOS.cs
@@ -0,0 +1,78 @@
+using System.Text;
+using SabreTools.Models.MSDOS;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class MSDOS : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Executable model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Executable executable)
+ {
+ builder.AppendLine("MS-DOS Executable Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, executable.Header);
+ Print(builder, executable.RelocationTable);
+ }
+
+ private static void Print(StringBuilder builder, ExecutableHeader? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Magic, " Magic number");
+ builder.AppendLine(header.LastPageBytes, " Last page bytes");
+ builder.AppendLine(header.Pages, " Pages");
+ builder.AppendLine(header.RelocationItems, " Relocation items");
+ builder.AppendLine(header.HeaderParagraphSize, " Header paragraph size");
+ builder.AppendLine(header.MinimumExtraParagraphs, " Minimum extra paragraphs");
+ builder.AppendLine(header.MaximumExtraParagraphs, " Maximum extra paragraphs");
+ builder.AppendLine(header.InitialSSValue, " Initial SS value");
+ builder.AppendLine(header.InitialSPValue, " Initial SP value");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine(header.InitialIPValue, " Initial IP value");
+ builder.AppendLine(header.InitialCSValue, " Initial CS value");
+ builder.AppendLine(header.RelocationTableAddr, " Relocation table address");
+ builder.AppendLine(header.OverlayNumber, " Overlay number");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, RelocationEntry?[]? entries)
+ {
+ builder.AppendLine(" Relocation Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No relocation table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Relocation Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Offset, " Offset");
+ builder.AppendLine(entry.Segment, " Segment");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/MicrosoftCabinet.cs b/SabreTools.Serialization/Printers/MicrosoftCabinet.cs
new file mode 100644
index 00000000..8a953c16
--- /dev/null
+++ b/SabreTools.Serialization/Printers/MicrosoftCabinet.cs
@@ -0,0 +1,172 @@
+using System.Text;
+using SabreTools.Models.MicrosoftCabinet;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class MicrosoftCabinet : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Cabinet model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Cabinet cabinet)
+ {
+ builder.AppendLine("Microsoft Cabinet Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, cabinet.Header);
+ Print(builder, cabinet.Folders);
+ Print(builder, cabinet.Files);
+ }
+
+ private static void Print(StringBuilder builder, CFHEADER? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Signature, " Signature");
+ builder.AppendLine(header.Reserved1, " Reserved 1");
+ builder.AppendLine(header.CabinetSize, " Cabinet size");
+ builder.AppendLine(header.Reserved2, " Reserved 2");
+ builder.AppendLine(header.FilesOffset, " Files offset");
+ builder.AppendLine(header.Reserved3, " Reserved 3");
+ builder.AppendLine(header.VersionMinor, " Minor version");
+ builder.AppendLine(header.VersionMajor, " Major version");
+ builder.AppendLine(header.FolderCount, " Folder count");
+ builder.AppendLine(header.FileCount, " File count");
+ builder.AppendLine($" Flags: {header.Flags} (0x{header.Flags:X})");
+ builder.AppendLine(header.SetID, " Set ID");
+ builder.AppendLine(header.CabinetIndex, " Cabinet index");
+
+#if NET20 || NET35
+ if ((header.Flags & HeaderFlags.RESERVE_PRESENT) != 0)
+#else
+ if (header.Flags.HasFlag(HeaderFlags.RESERVE_PRESENT))
+#endif
+ {
+ builder.AppendLine(header.HeaderReservedSize, " Header reserved size");
+ builder.AppendLine(header.FolderReservedSize, " Folder reserved size");
+ builder.AppendLine(header.DataReservedSize, " Data reserved size");
+ builder.AppendLine(header.ReservedData, " Reserved data");
+ }
+
+#if NET20 || NET35
+ if ((header.Flags & HeaderFlags.PREV_CABINET) != 0)
+#else
+ if (header.Flags.HasFlag(HeaderFlags.PREV_CABINET))
+#endif
+ {
+ builder.AppendLine(header.CabinetPrev, " Previous cabinet");
+ builder.AppendLine(header.DiskPrev, " Previous disk");
+ }
+
+#if NET20 || NET35
+ if ((header.Flags & HeaderFlags.NEXT_CABINET) != 0)
+#else
+ if (header.Flags.HasFlag(HeaderFlags.NEXT_CABINET))
+#endif
+ {
+ builder.AppendLine(header.CabinetNext, " Next cabinet");
+ builder.AppendLine(header.DiskNext, " Next disk");
+ }
+
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, CFFOLDER?[]? entries)
+ {
+ builder.AppendLine(" Folders:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No folders");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Folder {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.CabStartOffset, " Cab start offset");
+ builder.AppendLine(entry.DataCount, " Data count");
+ builder.AppendLine($" Compression type: {entry.CompressionType} (0x{entry.CompressionType:X})");
+ builder.AppendLine($" Masked compression type: {entry.CompressionType & CompressionType.MASK_TYPE}");
+ builder.AppendLine(entry.ReservedData, " Reserved data");
+ builder.AppendLine();
+
+ builder.AppendLine(" Data Blocks");
+ builder.AppendLine(" -------------------------");
+ if (entry.DataBlocks == null || entry.DataBlocks.Length == 0)
+ {
+ builder.AppendLine(" No data blocks");
+ continue;
+ }
+
+ for (int j = 0; j < entry.DataBlocks.Length; j++)
+ {
+ var dataBlock = entry.DataBlocks[j];
+ builder.AppendLine($" Data Block {j}");
+ if (dataBlock == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(dataBlock.Checksum, " Checksum");
+ builder.AppendLine(dataBlock.CompressedSize, " Compressed size");
+ builder.AppendLine(dataBlock.UncompressedSize, " Uncompressed size");
+ builder.AppendLine(dataBlock.ReservedData, " Reserved data");
+ //builder.AppendLine(dataBlock.CompressedData, " Compressed data");
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, CFFILE?[]? entries)
+ {
+ builder.AppendLine(" Files:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No files");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" File {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.FileSize, " File size");
+ builder.AppendLine(entry.FolderStartOffset, " Folder start offset");
+ builder.AppendLine($" Folder index: {entry.FolderIndex} (0x{entry.FolderIndex:X})");
+ builder.AppendLine(entry.Date, " Date");
+ builder.AppendLine(entry.Time, " Time");
+ builder.AppendLine($" Attributes: {entry.Attributes} (0x{entry.Attributes:X})");
+ builder.AppendLine(entry.Name, " Name");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/MoPaQ.cs b/SabreTools.Serialization/Printers/MoPaQ.cs
new file mode 100644
index 00000000..3c2276c9
--- /dev/null
+++ b/SabreTools.Serialization/Printers/MoPaQ.cs
@@ -0,0 +1,243 @@
+using System.Text;
+using SabreTools.Models.MoPaQ;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class MoPaQ : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Archive model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Archive archive)
+ {
+ builder.AppendLine("MoPaQ Archive Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, archive.UserData);
+ Print(builder, archive.ArchiveHeader);
+ Print(builder, archive.HetTable);
+ Print(builder, archive.BetTable);
+ Print(builder, archive.HashTable);
+ Print(builder, archive.BlockTable);
+ Print(builder, archive.HiBlockTable);
+ }
+
+ private static void Print(StringBuilder builder, UserData? userData)
+ {
+ builder.AppendLine(" User Data Information:");
+ builder.AppendLine(" -------------------------");
+ if (userData == null)
+ {
+ builder.AppendLine(" No user data");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(userData.Signature, " Signature");
+ builder.AppendLine(userData.UserDataSize, " User data size");
+ builder.AppendLine(userData.HeaderOffset, " Header offset");
+ builder.AppendLine(userData.UserDataHeaderSize, " User data header size");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ArchiveHeader? header)
+ {
+ builder.AppendLine(" Archive Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No archive header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Signature, " Signature");
+ builder.AppendLine(header.HeaderSize, " Header size");
+ builder.AppendLine(header.ArchiveSize, " Archive size");
+ builder.AppendLine($" Format version: {header.FormatVersion} (0x{header.FormatVersion:X})");
+ builder.AppendLine(header.BlockSize, " Block size");
+ builder.AppendLine(header.HashTablePosition, " Hash table position");
+ builder.AppendLine(header.BlockTablePosition, " Block table position");
+ builder.AppendLine(header.HashTableSize, " Hash table size");
+ builder.AppendLine(header.BlockTableSize, " Block table size");
+ builder.AppendLine(header.HiBlockTablePosition, " Hi-block table position");
+ builder.AppendLine(header.HashTablePositionHi, " Hash table position hi");
+ builder.AppendLine(header.BlockTablePositionHi, " Block table position hi");
+ builder.AppendLine(header.ArchiveSizeLong, " Archive size long");
+ builder.AppendLine(header.BetTablePosition, " BET table position");
+ builder.AppendLine(header.HetTablePosition, " HET table position");
+ builder.AppendLine(header.HashTableSizeLong, " Hash table size long");
+ builder.AppendLine(header.BlockTableSizeLong, " Block table size long");
+ builder.AppendLine(header.HiBlockTableSize, " Hi-block table size");
+ builder.AppendLine(header.HetTableSize, " HET table size");
+ builder.AppendLine(header.BetTablesize, " BET table size"); // TODO: Fix casing
+ builder.AppendLine(header.RawChunkSize, " Raw chunk size");
+ builder.AppendLine(header.BlockTableMD5, " Block table MD5");
+ builder.AppendLine(header.HashTableMD5, " Hash table MD5");
+ builder.AppendLine(header.HiBlockTableMD5, " Hi-block table MD5");
+ builder.AppendLine(header.BetTableMD5, " BET table MD5");
+ builder.AppendLine(header.HetTableMD5, " HET table MD5");
+ builder.AppendLine(header.MpqHeaderMD5, " MPQ header MD5");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, HetTable? table)
+ {
+ builder.AppendLine(" HET Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null)
+ {
+ builder.AppendLine(" No HET table");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(table.Signature, " Signature");
+ builder.AppendLine(table.Version, " Version");
+ builder.AppendLine(table.DataSize, " Data size");
+ builder.AppendLine(table.TableSize, " Table size");
+ builder.AppendLine(table.MaxFileCount, " Max file count");
+ builder.AppendLine(table.HashTableSize, " Hash table size");
+ builder.AppendLine(table.HashEntrySize, " Hash entry size");
+ builder.AppendLine(table.TotalIndexSize, " Total index size");
+ builder.AppendLine(table.IndexSizeExtra, " Index size extra");
+ builder.AppendLine(table.IndexSize, " Index size");
+ builder.AppendLine(table.BlockTableSize, " Block table size");
+ builder.AppendLine(table.HashTable, " Hash table");
+
+ builder.AppendLine(" File indexes:");
+ builder.AppendLine(" -------------------------");
+ if (table.FileIndexes == null)
+ {
+ builder.AppendLine(" No file indexes ");
+ }
+ else
+ {
+ for (int i = 0; i < table.FileIndexes.Length; i++)
+ {
+ builder.AppendLine(table.FileIndexes[i], $" File index {i}");
+ }
+ }
+
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, BetTable? table)
+ {
+ builder.AppendLine(" BET Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null)
+ {
+ builder.AppendLine(" No BET table");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(table.Signature, " Signature");
+ builder.AppendLine(table.Version, " Version");
+ builder.AppendLine(table.DataSize, " Data size");
+ builder.AppendLine(table.TableSize, " Table size");
+ builder.AppendLine(table.FileCount, " File count");
+ builder.AppendLine(table.Unknown, " Unknown");
+ builder.AppendLine(table.TableEntrySize, " Table entry size");
+ builder.AppendLine(table.FilePositionBitIndex, " File position bit index");
+ builder.AppendLine(table.FileSizeBitIndex, " File size bit index");
+ builder.AppendLine(table.CompressedSizeBitIndex, " Compressed size bit index");
+ builder.AppendLine(table.FlagIndexBitIndex, " Flag index bit index");
+ builder.AppendLine(table.UnknownBitIndex, " Unknown bit index");
+ builder.AppendLine(table.FilePositionBitCount, " File position bit count");
+ builder.AppendLine(table.FileSizeBitCount, " File size bit count");
+ builder.AppendLine(table.CompressedSizeBitCount, " Compressed size bit count");
+ builder.AppendLine(table.FlagIndexBitCount, " Flag index bit count");
+ builder.AppendLine(table.UnknownBitCount, " Unknown bit count");
+ builder.AppendLine(table.TotalBetHashSize, " Total BET hash size");
+ builder.AppendLine(table.BetHashSizeExtra, " BET hash size extra");
+ builder.AppendLine(table.BetHashSize, " BET hash size");
+ builder.AppendLine(table.BetHashArraySize, " BET hash array size");
+ builder.AppendLine(table.FlagCount, " Flag count");
+ builder.AppendLine(table.FlagsArray, " Flags array");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, HashEntry?[]? entries)
+ {
+ builder.AppendLine(" Hash Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No hash table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Hash Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.NameHashPartA, " Name hash, part A");
+ builder.AppendLine(entry.NameHashPartB, " Name hash, part B");
+ builder.AppendLine($" Locale: {entry.Locale} (0x{entry.Locale:X})");
+ builder.AppendLine(entry.Platform, " Platform");
+ builder.AppendLine(entry.BlockIndex, " BlockIndex");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, BlockEntry?[]? entries)
+ {
+ builder.AppendLine(" Block Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No block table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Block Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.FilePosition, " File position");
+ builder.AppendLine(entry.CompressedSize, " Compressed size");
+ builder.AppendLine(entry.UncompressedSize, " Uncompressed size");
+ builder.AppendLine($" Flags: {entry.Flags} (0x{entry.Flags:X})");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, short[]? entries)
+ {
+ builder.AppendLine(" Hi-block Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No hi-block table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Hi-block Table Entry {i}: {entry}");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/N3DS.cs b/SabreTools.Serialization/Printers/N3DS.cs
new file mode 100644
index 00000000..970e71fe
--- /dev/null
+++ b/SabreTools.Serialization/Printers/N3DS.cs
@@ -0,0 +1,646 @@
+using System.Text;
+using SabreTools.Models.N3DS;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class N3DS : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Cart model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Cart cart)
+ {
+ builder.AppendLine("3DS Cart Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, cart.Header);
+ Print(builder, cart.CardInfoHeader);
+ Print(builder, cart.DevelopmentCardInfoHeader);
+ Print(builder, cart.Partitions);
+ Print(builder, cart.ExtendedHeaders);
+ Print(builder, cart.ExeFSHeaders);
+ Print(builder, cart.RomFSHeaders);
+ }
+
+ private static void Print(StringBuilder builder, NCSDHeader? header)
+ {
+ builder.AppendLine(" NCSD Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No NCSD header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.RSA2048Signature, " RSA-2048 SHA-256 signature");
+ builder.AppendLine(header.MagicNumber, " Magic number");
+ builder.AppendLine(header.ImageSizeInMediaUnits, " Image size in media units");
+ builder.AppendLine(header.MediaId, " Media ID");
+ builder.AppendLine($" Partitions filesystem type: {header.PartitionsFSType} (0x{header.PartitionsFSType:X})");
+ builder.AppendLine(header.PartitionsCryptType, " Partitions crypt type");
+ builder.AppendLine();
+
+ builder.AppendLine(" Partition table:");
+ builder.AppendLine(" -------------------------");
+ if (header.PartitionsTable == null || header.PartitionsTable.Length == 0)
+ {
+ builder.AppendLine(" No partition table entries");
+ }
+ else
+ {
+ for (int i = 0; i < header.PartitionsTable.Length; i++)
+ {
+ var partitionTableEntry = header.PartitionsTable[i];
+ builder.AppendLine($" Partition table entry {i}");
+ if (partitionTableEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(partitionTableEntry.Offset, " Offset");
+ builder.AppendLine(partitionTableEntry.Length, " Length");
+ }
+ }
+ builder.AppendLine();
+
+ // If we have a cart image
+ if (header.PartitionsFSType == FilesystemType.Normal || header.PartitionsFSType == FilesystemType.None)
+ {
+ builder.AppendLine(header.ExheaderHash, " Exheader SHA-256 hash");
+ builder.AppendLine(header.AdditionalHeaderSize, " Additional header size");
+ builder.AppendLine(header.SectorZeroOffset, " Sector zero offset");
+ builder.AppendLine(header.PartitionFlags, " Partition flags");
+ builder.AppendLine();
+
+ builder.AppendLine(" Partition ID table:");
+ builder.AppendLine(" -------------------------");
+ if (header.PartitionIdTable == null || header.PartitionIdTable.Length == 0)
+ {
+ builder.AppendLine(" No partition ID table entries");
+ }
+ else
+ {
+ for (int i = 0; i < header.PartitionIdTable.Length; i++)
+ {
+ builder.AppendLine(header.PartitionIdTable[i], $" Partition {i} ID");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(header.Reserved1, " Reserved 1");
+ builder.AppendLine(header.Reserved2, " Reserved 2");
+ builder.AppendLine(header.FirmUpdateByte1, " Firmware update byte 1");
+ builder.AppendLine(header.FirmUpdateByte2, " Firmware update byte 2");
+ }
+
+ // If we have a firmware image
+ else if (header.PartitionsFSType == FilesystemType.FIRM)
+ {
+ builder.AppendLine(header.Unknown, " Unknown");
+ builder.AppendLine(header.EncryptedMBR, " Encrypted MBR");
+ }
+
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, CardInfoHeader? header)
+ {
+ builder.AppendLine(" Card Info Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No card info header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.WritableAddressMediaUnits, " Writable address in media units");
+ builder.AppendLine(header.CardInfoBitmask, " Card info bitmask");
+ builder.AppendLine(header.Reserved1, " Reserved 1");
+ builder.AppendLine(header.FilledSize, " Filled size of cartridge");
+ builder.AppendLine(header.Reserved2, " Reserved 2");
+ builder.AppendLine(header.TitleVersion, " Title version");
+ builder.AppendLine(header.CardRevision, " Card revision");
+ builder.AppendLine(header.Reserved3, " Reserved 3");
+ builder.AppendLine(header.CVerTitleID, " Title ID of CVer in included update partition");
+ builder.AppendLine(header.CVerVersionNumber, " Version number of CVer in included update partition");
+ builder.AppendLine(header.Reserved4, " Reserved 4");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DevelopmentCardInfoHeader? header)
+ {
+ builder.AppendLine(" Development Card Info Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No development card info header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine();
+ builder.AppendLine(" Initial Data:");
+ builder.AppendLine(" -------------------------");
+ if (header.InitialData == null)
+ {
+ builder.AppendLine(" No initial data");
+ }
+ else
+ {
+ builder.AppendLine(header.InitialData.CardSeedKeyY, " Card seed keyY");
+ builder.AppendLine(header.InitialData.EncryptedCardSeed, " Encrypted card seed");
+ builder.AppendLine(header.InitialData.CardSeedAESMAC, " Card seed AES-MAC");
+ builder.AppendLine(header.InitialData.CardSeedNonce, " Card seed nonce");
+ builder.AppendLine(header.InitialData.Reserved, " Reserved");
+ builder.AppendLine();
+
+ builder.AppendLine(" Backup Header:");
+ builder.AppendLine(" -------------------------");
+ if (header.InitialData.BackupHeader == null)
+ {
+ builder.AppendLine(" No backup header");
+ }
+ else
+ {
+ builder.AppendLine(header.InitialData.BackupHeader.MagicID, " Magic ID");
+ builder.AppendLine(header.InitialData.BackupHeader.ContentSizeInMediaUnits, " Content size in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.PartitionId, " Partition ID");
+ builder.AppendLine(header.InitialData.BackupHeader.MakerCode, " Maker code");
+ builder.AppendLine(header.InitialData.BackupHeader.Version, " Version");
+ builder.AppendLine(header.InitialData.BackupHeader.VerificationHash, " Verification hash");
+ builder.AppendLine(header.InitialData.BackupHeader.ProgramId, " Program ID");
+ builder.AppendLine(header.InitialData.BackupHeader.Reserved1, " Reserved 1");
+ builder.AppendLine(header.InitialData.BackupHeader.LogoRegionHash, " Logo region SHA-256 hash");
+ builder.AppendLine(header.InitialData.BackupHeader.ProductCode, " Product code");
+ builder.AppendLine(header.InitialData.BackupHeader.ExtendedHeaderHash, " Extended header SHA-256 hash");
+ builder.AppendLine(header.InitialData.BackupHeader.ExtendedHeaderSizeInBytes, " Extended header size in bytes");
+ builder.AppendLine(header.InitialData.BackupHeader.Reserved2, " Reserved 2");
+ builder.AppendLine($" Flags: {header.InitialData.BackupHeader.Flags} (0x{header.InitialData.BackupHeader.Flags:X})");
+ builder.AppendLine(header.InitialData.BackupHeader.PlainRegionOffsetInMediaUnits, " Plain region offset, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.PlainRegionSizeInMediaUnits, " Plain region size, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.LogoRegionOffsetInMediaUnits, " Logo region offset, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.LogoRegionSizeInMediaUnits, " Logo region size, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.ExeFSOffsetInMediaUnits, " ExeFS offset, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.ExeFSSizeInMediaUnits, " ExeFS size, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.ExeFSHashRegionSizeInMediaUnits, " ExeFS hash region size, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.Reserved3, " Reserved 3");
+ builder.AppendLine(header.InitialData.BackupHeader.RomFSOffsetInMediaUnits, " RomFS offset, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.RomFSSizeInMediaUnits, " RomFS size, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.RomFSHashRegionSizeInMediaUnits, " RomFS hash region size, in media units");
+ builder.AppendLine(header.InitialData.BackupHeader.Reserved4, " Reserved 4");
+ builder.AppendLine(header.InitialData.BackupHeader.ExeFSSuperblockHash, " ExeFS superblock SHA-256 hash");
+ builder.AppendLine(header.InitialData.BackupHeader.RomFSSuperblockHash, " RomFS superblock SHA-256 hash");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(header.CardDeviceReserved1, " Card device reserved 1");
+ builder.AppendLine(header.TitleKey, " Title key");
+ builder.AppendLine(header.CardDeviceReserved2, " Card device reserved 2");
+ builder.AppendLine();
+
+ builder.AppendLine(" Test Data:");
+ builder.AppendLine(" -------------------------");
+ if (header.TestData == null)
+ {
+ builder.AppendLine(" No test data");
+ }
+ else
+ {
+ builder.AppendLine(header.TestData.Signature, " Signature");
+ builder.AppendLine(header.TestData.AscendingByteSequence, " Ascending byte sequence");
+ builder.AppendLine(header.TestData.DescendingByteSequence, " Descending byte sequence");
+ builder.AppendLine(header.TestData.Filled00, " Filled with 00");
+ builder.AppendLine(header.TestData.FilledFF, " Filled with FF");
+ builder.AppendLine(header.TestData.Filled0F, " Filled with 0F");
+ builder.AppendLine(header.TestData.FilledF0, " Filled with F0");
+ builder.AppendLine(header.TestData.Filled55, " Filled with 55");
+ builder.AppendLine(header.TestData.FilledAA, " Filled with AA");
+ builder.AppendLine(header.TestData.FinalByte, " Final byte");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, NCCHHeader?[]? entries)
+ {
+ builder.AppendLine(" NCCH Partition Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No NCCH partition headers");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" NCCH Partition Header {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ if (entry.MagicID == string.Empty)
+ {
+ builder.AppendLine(" Empty partition, no data can be parsed");
+ }
+ else if (entry.MagicID != Constants.NCCHMagicNumber)
+ {
+ builder.AppendLine(" Unrecognized partition data, no data can be parsed");
+ }
+ else
+ {
+ builder.AppendLine(entry.RSA2048Signature, " RSA-2048 SHA-256 signature");
+ builder.AppendLine(entry.MagicID, " Magic ID");
+ builder.AppendLine(entry.ContentSizeInMediaUnits, " Content size in media units");
+ builder.AppendLine(entry.PartitionId, " Partition ID");
+ builder.AppendLine(entry.MakerCode, " Maker code");
+ builder.AppendLine(entry.Version, " Version");
+ builder.AppendLine(entry.VerificationHash, " Verification hash");
+ builder.AppendLine(entry.ProgramId, " Program ID");
+ builder.AppendLine(entry.Reserved1, " Reserved 1");
+ builder.AppendLine(entry.LogoRegionHash, " Logo region SHA-256 hash");
+ builder.AppendLine(entry.ProductCode, " Product code");
+ builder.AppendLine(entry.ExtendedHeaderHash, " Extended header SHA-256 hash");
+ builder.AppendLine(entry.ExtendedHeaderSizeInBytes, " Extended header size in bytes");
+ builder.AppendLine(entry.Reserved2, " Reserved 2");
+ builder.AppendLine(" Flags:");
+ if (entry.Flags == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.Flags.Reserved0, " Reserved 0");
+ builder.AppendLine(entry.Flags.Reserved1, " Reserved 1");
+ builder.AppendLine(entry.Flags.Reserved2, " Reserved 2");
+ builder.AppendLine($" Crypto method: {entry.Flags.CryptoMethod} (0x{entry.Flags.CryptoMethod:X})");
+ builder.AppendLine($" Content platform: {entry.Flags.ContentPlatform} (0x{entry.Flags.ContentPlatform:X})");
+ builder.AppendLine($" Content type: {entry.Flags.MediaPlatformIndex} (0x{entry.Flags.MediaPlatformIndex:X})");
+ builder.AppendLine(entry.Flags.ContentUnitSize, " Content unit size");
+ builder.AppendLine($" Bitmasks: {entry.Flags.BitMasks} (0x{entry.Flags.BitMasks:X})");
+ }
+ builder.AppendLine(entry.PlainRegionOffsetInMediaUnits, " Plain region offset, in media units");
+ builder.AppendLine(entry.PlainRegionSizeInMediaUnits, " Plain region size, in media units");
+ builder.AppendLine(entry.LogoRegionOffsetInMediaUnits, " Logo region offset, in media units");
+ builder.AppendLine(entry.LogoRegionSizeInMediaUnits, " Logo region size, in media units");
+ builder.AppendLine(entry.ExeFSOffsetInMediaUnits, " ExeFS offset, in media units");
+ builder.AppendLine(entry.ExeFSSizeInMediaUnits, " ExeFS size, in media units");
+ builder.AppendLine(entry.ExeFSHashRegionSizeInMediaUnits, " ExeFS hash region size, in media units");
+ builder.AppendLine(entry.Reserved3, " Reserved 3");
+ builder.AppendLine(entry.RomFSOffsetInMediaUnits, " RomFS offset, in media units");
+ builder.AppendLine(entry.RomFSSizeInMediaUnits, " RomFS size, in media units");
+ builder.AppendLine(entry.RomFSHashRegionSizeInMediaUnits, " RomFS hash region size, in media units");
+ builder.AppendLine(entry.Reserved4, " Reserved 4");
+ builder.AppendLine(entry.ExeFSSuperblockHash, " ExeFS superblock SHA-256 hash");
+ builder.AppendLine(entry.RomFSSuperblockHash, " RomFS superblock SHA-256 hash");
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, NCCHExtendedHeader?[]? entries)
+ {
+ builder.AppendLine(" NCCH Extended Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No NCCH extended headers");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" NCCH Extended Header {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" Unrecognized partition data, no data can be parsed");
+ continue;
+ }
+
+ builder.AppendLine(" System control info:");
+ if (entry.SCI == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.SCI.ApplicationTitle, " Application title");
+ builder.AppendLine(entry.SCI.Reserved1, " Reserved 1");
+ builder.AppendLine(entry.SCI.Flag, " Flag");
+ builder.AppendLine(entry.SCI.RemasterVersion, " Remaster version");
+
+ builder.AppendLine(" Text code set info:");
+ if (entry.SCI.TextCodeSetInfo == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.SCI.TextCodeSetInfo.Address, " Address");
+ builder.AppendLine(entry.SCI.TextCodeSetInfo.PhysicalRegionSizeInPages, " Physical region size (in page-multiples)");
+ builder.AppendLine(entry.SCI.TextCodeSetInfo.SizeInBytes, " Size (in bytes)");
+ }
+
+ builder.AppendLine(entry.SCI.StackSize, " Stack size");
+
+ builder.AppendLine(" Read-only code set info:");
+ if (entry.SCI.ReadOnlyCodeSetInfo == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.SCI.ReadOnlyCodeSetInfo.Address, " Address");
+ builder.AppendLine(entry.SCI.ReadOnlyCodeSetInfo.PhysicalRegionSizeInPages, " Physical region size (in page-multiples)");
+ builder.AppendLine(entry.SCI.ReadOnlyCodeSetInfo.SizeInBytes, " Size (in bytes)");
+ }
+
+ builder.AppendLine(entry.SCI.Reserved2, " Reserved 2");
+
+ builder.AppendLine(" Data code set info:");
+ if (entry.SCI.DataCodeSetInfo == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.SCI.DataCodeSetInfo.Address, " Address");
+ builder.AppendLine(entry.SCI.DataCodeSetInfo.PhysicalRegionSizeInPages, " Physical region size (in page-multiples)");
+ builder.AppendLine(entry.SCI.DataCodeSetInfo.SizeInBytes, " Size (in bytes)");
+ }
+
+ builder.AppendLine(entry.SCI.BSSSize, " BSS size");
+ builder.AppendLine(entry.SCI.DependencyModuleList, " Dependency module list");
+
+ builder.AppendLine(" System info:");
+ if (entry.SCI.SystemInfo == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.SCI.SystemInfo.SaveDataSize, " SaveData size");
+ builder.AppendLine(entry.SCI.SystemInfo.JumpID, " Jump ID");
+ builder.AppendLine(entry.SCI.SystemInfo.Reserved, " Reserved");
+ }
+ }
+
+ builder.AppendLine(" Access control info:");
+ if (entry.ACI == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(" ARM11 local system capabilities:");
+ if (entry.ACI.ARM11LocalSystemCapabilities == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.ProgramID, " Program ID");
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.CoreVersion, " Core version");
+ builder.AppendLine($" Flag 1: {entry.ACI.ARM11LocalSystemCapabilities.Flag1} (0x{entry.ACI.ARM11LocalSystemCapabilities.Flag1:X})");
+ builder.AppendLine($" Flag 2: {entry.ACI.ARM11LocalSystemCapabilities.Flag2} (0x{entry.ACI.ARM11LocalSystemCapabilities.Flag2:X})");
+ builder.AppendLine($" Flag 0: {entry.ACI.ARM11LocalSystemCapabilities.Flag0} (0x{entry.ACI.ARM11LocalSystemCapabilities.Flag0:X})");
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.Priority, " Priority");
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.ResourceLimitDescriptors, " Resource limit descriptors");
+ builder.AppendLine(" Storage info:");
+ if (entry.ACI.ARM11LocalSystemCapabilities.StorageInfo == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.StorageInfo.ExtdataID, " Extdata ID");
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.StorageInfo.SystemSavedataIDs, " System savedata IDs");
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.StorageInfo.StorageAccessibleUniqueIDs, " Storage accessible unique IDs");
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.StorageInfo.FileSystemAccessInfo, " File system access info");
+ builder.AppendLine($" Other attributes: {entry.ACI.ARM11LocalSystemCapabilities.StorageInfo.OtherAttributes} (0x{entry.ACI.ARM11LocalSystemCapabilities.StorageInfo.OtherAttributes:X})");
+ }
+
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.ServiceAccessControl, " Service access control");
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.ExtendedServiceAccessControl, " Extended service access control");
+ builder.AppendLine(entry.ACI.ARM11LocalSystemCapabilities.Reserved, " Reserved");
+ builder.AppendLine($" Resource limit cateogry: {entry.ACI.ARM11LocalSystemCapabilities.ResourceLimitCategory} (0x{entry.ACI.ARM11LocalSystemCapabilities.ResourceLimitCategory:X})");
+ }
+
+ builder.AppendLine(" ARM11 kernel capabilities:");
+ if (entry.ACI.ARM11KernelCapabilities == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.ACI.ARM11KernelCapabilities.Descriptors, " Descriptors");
+ builder.AppendLine(entry.ACI.ARM11KernelCapabilities.Reserved, " Reserved");
+ }
+
+ builder.AppendLine(" ARM9 access control:");
+ if (entry.ACI.ARM9AccessControl == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.ACI.ARM9AccessControl.Descriptors, " Descriptors");
+ builder.AppendLine(entry.ACI.ARM9AccessControl.DescriptorVersion, " Descriptor version");
+ }
+
+ builder.AppendLine(entry.AccessDescSignature, " AccessDec signature (RSA-2048-SHA256)");
+ builder.AppendLine(entry.NCCHHDRPublicKey, " NCCH HDR RSA-2048 public key");
+ }
+
+ builder.AppendLine(" Access control info (for limitations of first ACI):");
+ if (entry.ACIForLimitations == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(" ARM11 local system capabilities:");
+ if (entry.ACIForLimitations.ARM11LocalSystemCapabilities == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.ProgramID, " Program ID");
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.CoreVersion, " Core version");
+ builder.AppendLine($" Flag 1: {entry.ACIForLimitations.ARM11LocalSystemCapabilities.Flag1} (0x{entry.ACIForLimitations.ARM11LocalSystemCapabilities.Flag1:X})");
+ builder.AppendLine($" Flag 2: {entry.ACIForLimitations.ARM11LocalSystemCapabilities.Flag2} (0x{entry.ACIForLimitations.ARM11LocalSystemCapabilities.Flag2:X})");
+ builder.AppendLine($" Flag 0: {entry.ACIForLimitations.ARM11LocalSystemCapabilities.Flag0} (0x{entry.ACIForLimitations.ARM11LocalSystemCapabilities.Flag0:X})");
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.Priority, " Priority");
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.ResourceLimitDescriptors, " Resource limit descriptors");
+
+ builder.AppendLine(" Storage info:");
+ if (entry.ACIForLimitations.ARM11LocalSystemCapabilities.StorageInfo == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.StorageInfo.ExtdataID, " Extdata ID");
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.StorageInfo.SystemSavedataIDs, " System savedata IDs");
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.StorageInfo.StorageAccessibleUniqueIDs, " Storage accessible unique IDs");
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.StorageInfo.FileSystemAccessInfo, " File system access info");
+ builder.AppendLine($" Other attributes: {entry.ACIForLimitations.ARM11LocalSystemCapabilities.StorageInfo.OtherAttributes} (0x{entry.ACIForLimitations.ARM11LocalSystemCapabilities.StorageInfo.OtherAttributes:X})");
+ }
+
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.ServiceAccessControl, " Service access control");
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.ExtendedServiceAccessControl, " Extended service access control");
+ builder.AppendLine(entry.ACIForLimitations.ARM11LocalSystemCapabilities.Reserved, " Reserved");
+ builder.AppendLine($" Resource limit cateogry: {entry.ACIForLimitations.ARM11LocalSystemCapabilities.ResourceLimitCategory} (0x{entry.ACIForLimitations.ARM11LocalSystemCapabilities.ResourceLimitCategory:X})");
+ }
+
+ builder.AppendLine(" ARM11 kernel capabilities:");
+ if (entry.ACIForLimitations.ARM11KernelCapabilities == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.ACIForLimitations.ARM11KernelCapabilities.Descriptors, " Descriptors");
+ builder.AppendLine(entry.ACIForLimitations.ARM11KernelCapabilities.Reserved, " Reserved");
+ }
+
+ builder.AppendLine(" ARM9 access control:");
+ if (entry.ACIForLimitations.ARM9AccessControl == null)
+ {
+ builder.AppendLine(" [NULL]");
+ }
+ else
+ {
+ builder.AppendLine(entry.ACIForLimitations.ARM9AccessControl.Descriptors, " Descriptors");
+ builder.AppendLine(entry.ACIForLimitations.ARM9AccessControl.DescriptorVersion, " Descriptor version");
+ }
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ExeFSHeader?[]? entries)
+ {
+ builder.AppendLine(" ExeFS Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No ExeFS headers");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" ExeFS Header {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" Unrecognized partition data, no data can be parsed");
+ continue;
+ }
+
+ builder.AppendLine(" File headers:");
+ if (entry.FileHeaders == null || entry.FileHeaders.Length == 0)
+ {
+ builder.AppendLine(" No file headers");
+ }
+ else
+ {
+ for (int j = 0; j < entry.FileHeaders.Length; j++)
+ {
+ var fileHeader = entry.FileHeaders[j];
+ builder.AppendLine($" File Header {j}");
+ if (fileHeader == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(fileHeader.FileName, " File name");
+ builder.AppendLine(fileHeader.FileOffset, " File offset");
+ builder.AppendLine(fileHeader.FileSize, " File size");
+ }
+ }
+
+ builder.AppendLine(entry.Reserved, " Reserved");
+
+ builder.AppendLine(" File hashes:");
+ if (entry.FileHashes == null || entry.FileHashes.Length == 0)
+ {
+ builder.AppendLine(" No file hashes");
+ }
+ else
+ {
+ for (int j = 0; j < entry.FileHashes.Length; j++)
+ {
+ var fileHash = entry.FileHashes[j];
+ builder.AppendLine($" File Hash {j}");
+ if (fileHash == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(fileHash, " SHA-256");
+ }
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, RomFSHeader?[]? entries)
+ {
+ builder.AppendLine(" RomFS Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No RomFS headers");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var romFSHeader = entries[i];
+ builder.AppendLine($" RomFS Header {i}");
+ if (romFSHeader == null)
+ {
+ builder.AppendLine(" Unrecognized RomFS data, no data can be parsed");
+ continue;
+ }
+
+ builder.AppendLine(romFSHeader.MagicString, " Magic string");
+ builder.AppendLine(romFSHeader.MagicNumber, " Magic number");
+ builder.AppendLine(romFSHeader.MasterHashSize, " Master hash size");
+ builder.AppendLine(romFSHeader.Level1LogicalOffset, " Level 1 logical offset");
+ builder.AppendLine(romFSHeader.Level1HashdataSize, " Level 1 hashdata size");
+ builder.AppendLine(romFSHeader.Level1BlockSizeLog2, " Level 1 block size");
+ builder.AppendLine(romFSHeader.Reserved1, " Reserved 1");
+ builder.AppendLine(romFSHeader.Level2LogicalOffset, " Level 2 logical offset");
+ builder.AppendLine(romFSHeader.Level2HashdataSize, " Level 2 hashdata size");
+ builder.AppendLine(romFSHeader.Level2BlockSizeLog2, " Level 2 block size");
+ builder.AppendLine(romFSHeader.Reserved2, " Reserved 2");
+ builder.AppendLine(romFSHeader.Level3LogicalOffset, " Level 3 logical offset");
+ builder.AppendLine(romFSHeader.Level3HashdataSize, " Level 3 hashdata size");
+ builder.AppendLine(romFSHeader.Level3BlockSizeLog2, " Level 3 block size");
+ builder.AppendLine(romFSHeader.Reserved3, " Reserved 3");
+ builder.AppendLine(romFSHeader.Reserved4, " Reserved 4");
+ builder.AppendLine(romFSHeader.OptionalInfoSize, " Optional info size");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/NCF.cs b/SabreTools.Serialization/Printers/NCF.cs
new file mode 100644
index 00000000..0ef9a558
--- /dev/null
+++ b/SabreTools.Serialization/Printers/NCF.cs
@@ -0,0 +1,360 @@
+using System.Collections.Generic;
+using System.Text;
+using SabreTools.Models.NCF;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class NCF : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, File model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, File file)
+ {
+ builder.AppendLine("NCF Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ // Header
+ Print(builder, file.Header);
+
+ // Directory and Directory Maps
+ Print(builder, file.DirectoryHeader);
+ Print(builder, file.DirectoryEntries, file.DirectoryNames);
+ // TODO: Should we print out the entire string table?
+ Print(builder, file.DirectoryInfo1Entries);
+ Print(builder, file.DirectoryInfo2Entries);
+ Print(builder, file.DirectoryCopyEntries);
+ Print(builder, file.DirectoryLocalEntries);
+ Print(builder, file.UnknownHeader);
+ Print(builder, file.UnknownEntries);
+
+ // Checksums and Checksum Maps
+ Print(builder, file.ChecksumHeader);
+ Print(builder, file.ChecksumMapHeader);
+ Print(builder, file.ChecksumMapEntries);
+ Print(builder, file.ChecksumEntries);
+ }
+
+ private static void Print(StringBuilder builder, Header? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.MajorVersion, " Major version");
+ builder.AppendLine(header.MinorVersion, " Minor version");
+ builder.AppendLine(header.CacheID, " Cache ID");
+ builder.AppendLine(header.LastVersionPlayed, " Last version played");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine(header.Dummy2, " Dummy 2");
+ builder.AppendLine(header.FileSize, " File size");
+ builder.AppendLine(header.BlockSize, " Block size");
+ builder.AppendLine(header.BlockCount, " Block count");
+ builder.AppendLine(header.Dummy3, " Dummy 3");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryHeader? header)
+ {
+ builder.AppendLine(" Directory Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No directory header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.CacheID, " Cache ID");
+ builder.AppendLine(header.LastVersionPlayed, " Last version played");
+ builder.AppendLine(header.ItemCount, " Item count");
+ builder.AppendLine(header.FileCount, " File count");
+ builder.AppendLine(header.ChecksumDataLength, " Checksum data length");
+ builder.AppendLine(header.DirectorySize, " Directory size");
+ builder.AppendLine(header.NameSize, " Name size");
+ builder.AppendLine(header.Info1Count, " Info 1 count");
+ builder.AppendLine(header.CopyCount, " Copy count");
+ builder.AppendLine(header.LocalCount, " Local count");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine(header.Dummy2, " Dummy 2");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryEntry?[]? entries, Dictionary? entryNames)
+ {
+ builder.AppendLine(" Directory Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.NameOffset, " Name offset");
+ builder.AppendLine(entryNames![entry.NameOffset], " Name");
+ builder.AppendLine(entry.ItemSize, " Item size");
+ builder.AppendLine(entry.ChecksumIndex, " Checksum index");
+ builder.AppendLine($" Directory flags: {entry.DirectoryFlags} (0x{entry.DirectoryFlags:X})");
+ builder.AppendLine(entry.ParentIndex, " Parent index");
+ builder.AppendLine(entry.NextIndex, " Next index");
+ builder.AppendLine(entry.FirstIndex, " First index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryInfo1Entry?[]? entries)
+ {
+ builder.AppendLine(" Directory Info 1 Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory info 1 entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Info 1 Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Dummy0, " Dummy 0");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryInfo2Entry?[]? entries)
+ {
+ builder.AppendLine(" Directory Info 2 Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory info 2 entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Info 2 Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Dummy0, " Dummy 0");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryCopyEntry?[]? entries)
+ {
+ builder.AppendLine(" Directory Copy Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory copy entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Copy Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.DirectoryIndex, " Directory index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryLocalEntry?[]? entries)
+ {
+ builder.AppendLine(" Directory Local Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory local entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Local Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.DirectoryIndex, " Directory index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, UnknownHeader? header)
+ {
+ builder.AppendLine(" Unknown Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No unknown header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, UnknownEntry?[]? entries)
+ {
+ builder.AppendLine(" Unknown Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No unknown entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Unknown Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Dummy0, " Dummy 0");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ChecksumHeader? header)
+ {
+ builder.AppendLine(" Checksum Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No checksum header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.ChecksumSize, " Checksum size");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ChecksumMapHeader? header)
+ {
+ builder.AppendLine(" Checksum Map Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No checksum map header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Dummy0, " Dummy 0");
+ builder.AppendLine(header.Dummy1, " Dummy 1");
+ builder.AppendLine(header.ItemCount, " Item count");
+ builder.AppendLine(header.ChecksumCount, " Checksum count");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ChecksumMapEntry?[]? entries)
+ {
+ builder.AppendLine(" Checksum Map Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No checksum map entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Checksum Map Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.ChecksumCount, " Checksum count");
+ builder.AppendLine(entry.FirstChecksumIndex, " First checksum index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ChecksumEntry?[]? entries)
+ {
+ builder.AppendLine(" Checksum Entries Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No checksum entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Checksum Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Checksum, " Checksum");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/NewExecutable.cs b/SabreTools.Serialization/Printers/NewExecutable.cs
new file mode 100644
index 00000000..b6fb7251
--- /dev/null
+++ b/SabreTools.Serialization/Printers/NewExecutable.cs
@@ -0,0 +1,379 @@
+using System.Collections.Generic;
+using System.Text;
+using SabreTools.Models.NewExecutable;
+using SabreTools.Serialization.Interfaces;
+using static SabreTools.Serialization.Extensions;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class NewExecutable : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Executable model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Executable executable)
+ {
+ builder.AppendLine("New Executable Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ // Stub
+ Print(builder, executable.Stub?.Header);
+
+ // Header
+ Print(builder, executable.Header);
+
+ // Tables
+ Print(builder, executable.SegmentTable);
+ Print(builder, executable.ResourceTable);
+ Print(builder, executable.ResidentNameTable);
+ Print(builder, executable.ModuleReferenceTable, executable.Stub?.Header, executable.Header);
+ Print(builder, executable.ImportedNameTable);
+ Print(builder, executable.EntryTable);
+ Print(builder, executable.NonResidentNameTable);
+ }
+
+ private static void Print(StringBuilder builder, Models.MSDOS.ExecutableHeader? header)
+ {
+ builder.AppendLine(" MS-DOS Stub Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No MS-DOS stub header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Magic, " Magic number");
+ builder.AppendLine(header.LastPageBytes, " Last page bytes");
+ builder.AppendLine(header.Pages, " Pages");
+ builder.AppendLine(header.RelocationItems, " Relocation items");
+ builder.AppendLine(header.HeaderParagraphSize, " Header paragraph size");
+ builder.AppendLine(header.MinimumExtraParagraphs, " Minimum extra paragraphs");
+ builder.AppendLine(header.MaximumExtraParagraphs, " Maximum extra paragraphs");
+ builder.AppendLine(header.InitialSSValue, " Initial SS value");
+ builder.AppendLine(header.InitialSPValue, " Initial SP value");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine(header.InitialIPValue, " Initial IP value");
+ builder.AppendLine(header.InitialCSValue, " Initial CS value");
+ builder.AppendLine(header.RelocationTableAddr, " Relocation table address");
+ builder.AppendLine(header.OverlayNumber, " Overlay number");
+ builder.AppendLine();
+
+ builder.AppendLine(" MS-DOS Stub Extended Header Information:");
+ builder.AppendLine(" -------------------------");
+ builder.AppendLine(header.Reserved1, " Reserved words");
+ builder.AppendLine(header.OEMIdentifier, " OEM identifier");
+ builder.AppendLine(header.OEMInformation, " OEM information");
+ builder.AppendLine(header.Reserved2, " Reserved words");
+ builder.AppendLine(header.NewExeHeaderAddr, " New EXE header address");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ExecutableHeader? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Magic, " Magic number");
+ builder.AppendLine(header.LinkerVersion, " Linker version");
+ builder.AppendLine(header.LinkerRevision, " Linker revision");
+ builder.AppendLine(header.EntryTableOffset, " Entry table offset");
+ builder.AppendLine(header.EntryTableSize, " Entry table size");
+ builder.AppendLine(header.CrcChecksum, " CRC checksum");
+ builder.AppendLine($" Flag word: {header.FlagWord} (0x{header.FlagWord:X})");
+ builder.AppendLine(header.AutomaticDataSegmentNumber, " Automatic data segment number");
+ builder.AppendLine(header.InitialHeapAlloc, " Initial heap allocation");
+ builder.AppendLine(header.InitialStackAlloc, " Initial stack allocation");
+ builder.AppendLine(header.InitialCSIPSetting, " Initial CS:IP setting");
+ builder.AppendLine(header.InitialSSSPSetting, " Initial SS:SP setting");
+ builder.AppendLine(header.FileSegmentCount, " File segment count");
+ builder.AppendLine(header.ModuleReferenceTableSize, " Module reference table size");
+ builder.AppendLine(header.NonResidentNameTableSize, " Non-resident name table size");
+ builder.AppendLine(header.SegmentTableOffset, " Segment table offset");
+ builder.AppendLine(header.ResourceTableOffset, " Resource table offset");
+ builder.AppendLine(header.ResidentNameTableOffset, " Resident name table offset");
+ builder.AppendLine(header.ModuleReferenceTableOffset, " Module reference table offset");
+ builder.AppendLine(header.ImportedNamesTableOffset, " Imported names table offset");
+ builder.AppendLine(header.NonResidentNamesTableOffset, " Non-resident name table offset");
+ builder.AppendLine(header.MovableEntriesCount, " Moveable entries count");
+ builder.AppendLine(header.SegmentAlignmentShiftCount, " Segment alignment shift count");
+ builder.AppendLine(header.ResourceEntriesCount, " Resource entries count");
+ builder.AppendLine($" Target operating system: {header.TargetOperatingSystem} (0x{header.TargetOperatingSystem:X})");
+ builder.AppendLine($" Additional flags: {header.AdditionalFlags} (0x{header.AdditionalFlags:X})");
+ builder.AppendLine(header.ReturnThunkOffset, " Return thunk offset");
+ builder.AppendLine(header.SegmentReferenceThunkOffset, " Segment reference thunk offset");
+ builder.AppendLine(header.MinCodeSwapAreaSize, " Minimum code swap area size");
+ builder.AppendLine(header.WindowsSDKRevision, " Windows SDK revision");
+ builder.AppendLine(header.WindowsSDKVersion, " Windows SDK version");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, SegmentTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Segment Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No segment table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Segment Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Offset, " Offset");
+ builder.AppendLine(entry.Length, " Length");
+ builder.AppendLine($" Flag word: {entry.FlagWord} (0x{entry.FlagWord:X})");
+ builder.AppendLine(entry.MinimumAllocationSize, " Minimum allocation size");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ResourceTable? table)
+ {
+ builder.AppendLine(" Resource Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null)
+ {
+ builder.AppendLine(" No resource table");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(table.AlignmentShiftCount, " Alignment shift count");
+ if (table.ResourceTypes == null || table.ResourceTypes.Length == 0)
+ {
+ builder.AppendLine(" No resource table items");
+ }
+ else
+ {
+ for (int i = 0; i < table.ResourceTypes.Length; i++)
+ {
+ // TODO: If not integer type, print out name
+ var entry = table.ResourceTypes[i];
+ builder.AppendLine($" Resource Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.TypeID, " Type ID");
+ builder.AppendLine(entry.ResourceCount, " Resource count");
+ builder.AppendLine(entry.Reserved, " Reserved");
+ builder.AppendLine(" Resources = ");
+ if (entry.ResourceCount == 0 || entry.Resources == null || entry.Resources.Length == 0)
+ {
+ builder.AppendLine(" No resource items");
+ }
+ else
+ {
+ for (int j = 0; j < entry.Resources.Length; j++)
+ {
+ // TODO: If not integer type, print out name
+ var resource = entry.Resources[j];
+ builder.AppendLine($" Resource Entry {i}");
+ if (resource == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(resource.Offset, " Offset");
+ builder.AppendLine(resource.Length, " Length");
+ builder.AppendLine($" Flag word: {resource.FlagWord} (0x{resource.FlagWord:X})");
+ builder.AppendLine(resource.ResourceID, " Resource ID");
+ builder.AppendLine(resource.Reserved, " Reserved");
+ }
+ }
+ }
+ }
+
+ if (table.TypeAndNameStrings == null || table.TypeAndNameStrings.Count == 0)
+ {
+ builder.AppendLine(" No resource table type/name strings");
+ }
+ else
+ {
+ foreach (var typeAndNameString in table.TypeAndNameStrings)
+ {
+ builder.AppendLine($" Resource Type/Name Offset {typeAndNameString.Key}");
+ if (typeAndNameString.Value == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(typeAndNameString.Value.Length, " Length");
+ builder.AppendLine(typeAndNameString.Value.Text, " Text", Encoding.ASCII);
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ResidentNameTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Resident-Name Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No resident-name table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Resident-Name Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Length, " Length");
+ builder.AppendLine(entry.NameString, " Name string", Encoding.ASCII);
+ builder.AppendLine(entry.OrdinalNumber, " Ordinal number");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ModuleReferenceTableEntry?[]? entries, Models.MSDOS.ExecutableHeader? stub, ExecutableHeader? header)
+ {
+ builder.AppendLine(" Module-Reference Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No module-reference table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ // TODO: Read the imported names table and print value here
+ var entry = entries[i];
+ builder.AppendLine($" Module-Reference Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine($" Offset: {entry.Offset} (adjusted to be {entry.Offset + (stub?.NewExeHeaderAddr ?? 0) + (header?.ImportedNamesTableOffset ?? 0)})");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Dictionary? entries)
+ {
+ builder.AppendLine(" Imported-Name Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Count == 0)
+ {
+ builder.AppendLine(" No imported-name table items");
+ builder.AppendLine();
+ return;
+ }
+
+ foreach (var entry in entries)
+ {
+ builder.AppendLine($" Imported-Name Table at Offset {entry.Key}");
+ if (entry.Value == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Value.Length, " Length");
+ builder.AppendLine(entry.Value.NameString, " Name string", Encoding.ASCII);
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, EntryTableBundle?[]? entries)
+ {
+ builder.AppendLine(" Entry Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No entry table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Entry Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.EntryCount, " Entry count");
+ builder.AppendLine(entry.SegmentIndicator, " Segment indicator");
+ switch (entry.GetEntryType())
+ {
+ case SegmentEntryType.FixedSegment:
+ builder.AppendLine($" Flag word: {entry.FixedFlagWord} (0x{entry.FixedFlagWord:X})");
+ builder.AppendLine(entry.FixedOffset, " Offset");
+ break;
+ case SegmentEntryType.MoveableSegment:
+ builder.AppendLine($" Flag word: {entry.MoveableFlagWord} (0x{entry.MoveableFlagWord:X})");
+ builder.AppendLine(entry.MoveableReserved, " Reserved");
+ builder.AppendLine(entry.MoveableSegmentNumber, " Segment number");
+ builder.AppendLine(entry.MoveableOffset, " Offset");
+ break;
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, NonResidentNameTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Nonresident-Name Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No nonresident-name table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Nonresident-Name Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Length, " Length");
+ builder.AppendLine(entry.NameString, " Name string", Encoding.ASCII);
+ builder.AppendLine(entry.OrdinalNumber, " Ordinal number");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/Nitro.cs b/SabreTools.Serialization/Printers/Nitro.cs
new file mode 100644
index 00000000..cb71fca0
--- /dev/null
+++ b/SabreTools.Serialization/Printers/Nitro.cs
@@ -0,0 +1,259 @@
+using System.Text;
+using SabreTools.Models.Nitro;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class Nitro : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Cart model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Cart cart)
+ {
+ builder.AppendLine("NDS Cart Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, cart.CommonHeader);
+ Print(builder, cart.ExtendedDSiHeader);
+ Print(builder, cart.SecureArea);
+ Print(builder, cart.NameTable);
+ Print(builder, cart.FileAllocationTable);
+ }
+
+ private static void Print(StringBuilder builder, CommonHeader? header)
+ {
+ builder.AppendLine(" Common Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No common header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.GameTitle, " Game title");
+ builder.AppendLine(header.GameCode, " Game code");
+ builder.AppendLine(header.MakerCode, " Maker code");
+ builder.AppendLine($" Unit code: {header.UnitCode} (0x{header.UnitCode:X})");
+ builder.AppendLine(header.EncryptionSeedSelect, " Encryption seed select");
+ builder.AppendLine(header.DeviceCapacity, " Device capacity");
+ builder.AppendLine(header.Reserved1, " Reserved 1");
+ builder.AppendLine(header.GameRevision, " Game revision");
+ builder.AppendLine(header.RomVersion, " Rom version");
+ builder.AppendLine(header.ARM9RomOffset, " ARM9 rom offset");
+ builder.AppendLine(header.ARM9EntryAddress, " ARM9 entry address");
+ builder.AppendLine(header.ARM9LoadAddress, " ARM9 load address");
+ builder.AppendLine(header.ARM9Size, " ARM9 size");
+ builder.AppendLine(header.ARM7RomOffset, " ARM7 rom offset");
+ builder.AppendLine(header.ARM7EntryAddress, " ARM7 entry address");
+ builder.AppendLine(header.ARM7LoadAddress, " ARM7 load address");
+ builder.AppendLine(header.ARM7Size, " ARM7 size");
+ builder.AppendLine(header.FileNameTableOffset, " File name table offset");
+ builder.AppendLine(header.FileNameTableLength, " File name table length");
+ builder.AppendLine(header.FileAllocationTableOffset, " File allocation table offset");
+ builder.AppendLine(header.FileAllocationTableLength, " File allocation table length");
+ builder.AppendLine(header.ARM9OverlayOffset, " ARM9 overlay offset");
+ builder.AppendLine(header.ARM9OverlayLength, " ARM9 overlay length");
+ builder.AppendLine(header.ARM7OverlayOffset, " ARM7 overlay offset");
+ builder.AppendLine(header.ARM7OverlayLength, " ARM7 overlay length");
+ builder.AppendLine(header.NormalCardControlRegisterSettings, " Normal card control register settings");
+ builder.AppendLine(header.SecureCardControlRegisterSettings, " Secure card control register settings");
+ builder.AppendLine(header.IconBannerOffset, " Icon banner offset");
+ builder.AppendLine(header.SecureAreaCRC, " Secure area CRC");
+ builder.AppendLine(header.SecureTransferTimeout, " Secure transfer timeout");
+ builder.AppendLine(header.ARM9Autoload, " ARM9 autoload");
+ builder.AppendLine(header.ARM7Autoload, " ARM7 autoload");
+ builder.AppendLine(header.SecureDisable, " Secure disable");
+ builder.AppendLine(header.NTRRegionRomSize, " NTR region rom size");
+ builder.AppendLine(header.HeaderSize, " Header size");
+ builder.AppendLine(header.Reserved2, " Reserved 2");
+ builder.AppendLine(header.NintendoLogo, " Nintendo logo");
+ builder.AppendLine(header.NintendoLogoCRC, " Nintendo logo CRC");
+ builder.AppendLine(header.HeaderCRC, " Header CRC");
+ builder.AppendLine(header.DebuggerReserved, " Debugger reserved");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ExtendedDSiHeader? header)
+ {
+ builder.AppendLine(" Extended DSi Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No extended DSi header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.GlobalMBK15Settings, " Global MBK1..MBK5 settings");
+ builder.AppendLine(header.LocalMBK68SettingsARM9, " Local MBK6..MBK8 settings for ARM9");
+ builder.AppendLine(header.LocalMBK68SettingsARM7, " Local MBK6..MBK8 settings for ARM7");
+ builder.AppendLine(header.GlobalMBK9Setting, " Global MBK9 setting");
+ builder.AppendLine(header.RegionFlags, " Region flags");
+ builder.AppendLine(header.AccessControl, " Access control");
+ builder.AppendLine(header.ARM7SCFGEXTMask, " ARM7 SCFG EXT mask");
+ builder.AppendLine(header.ReservedFlags, " Reserved/flags?");
+ builder.AppendLine(header.ARM9iRomOffset, " ARM9i rom offset");
+ builder.AppendLine(header.Reserved3, " Reserved 3");
+ builder.AppendLine(header.ARM9iLoadAddress, " ARM9i load address");
+ builder.AppendLine(header.ARM9iSize, " ARM9i size");
+ builder.AppendLine(header.ARM7iRomOffset, " ARM7i rom offset");
+ builder.AppendLine(header.Reserved4, " Reserved 4");
+ builder.AppendLine(header.ARM7iLoadAddress, " ARM7i load address");
+ builder.AppendLine(header.ARM7iSize, " ARM7i size");
+ builder.AppendLine(header.DigestNTRRegionOffset, " Digest NTR region offset");
+ builder.AppendLine(header.DigestNTRRegionLength, " Digest NTR region length");
+ builder.AppendLine(header.DigestTWLRegionOffset, " Digest TWL region offset");
+ builder.AppendLine(header.DigestTWLRegionLength, " Digest TWL region length");
+ builder.AppendLine(header.DigestSectorHashtableRegionOffset, " Digest sector hashtable region offset");
+ builder.AppendLine(header.DigestSectorHashtableRegionLength, " Digest sector hashtable region length");
+ builder.AppendLine(header.DigestBlockHashtableRegionOffset, " Digest block hashtable region offset");
+ builder.AppendLine(header.DigestBlockHashtableRegionLength, " Digest block hashtable region length");
+ builder.AppendLine(header.DigestSectorSize, " Digest sector size");
+ builder.AppendLine(header.DigestBlockSectorCount, " Digest block sector count");
+ builder.AppendLine(header.IconBannerSize, " Icon banner size");
+ builder.AppendLine(header.Unknown1, " Unknown 1");
+ builder.AppendLine(header.ModcryptArea1Offset, " Modcrypt area 1 offset");
+ builder.AppendLine(header.ModcryptArea1Size, " Modcrypt area 1 size");
+ builder.AppendLine(header.ModcryptArea2Offset, " Modcrypt area 2 offset");
+ builder.AppendLine(header.ModcryptArea2Size, " Modcrypt area 2 size");
+ builder.AppendLine(header.TitleID, " Title ID");
+ builder.AppendLine(header.DSiWarePublicSavSize, " DSiWare 'public.sav' size");
+ builder.AppendLine(header.DSiWarePrivateSavSize, " DSiWare 'private.sav' size");
+ builder.AppendLine(header.ReservedZero, " Reserved (zero)");
+ builder.AppendLine(header.Unknown2, " Unknown 2");
+ builder.AppendLine(header.ARM9WithSecureAreaSHA1HMACHash, " ARM9 (with encrypted secure area) SHA1 HMAC hash");
+ builder.AppendLine(header.ARM7SHA1HMACHash, " ARM7 SHA1 HMAC hash");
+ builder.AppendLine(header.DigestMasterSHA1HMACHash, " Digest master SHA1 HMAC hash");
+ builder.AppendLine(header.BannerSHA1HMACHash, " Banner SHA1 HMAC hash");
+ builder.AppendLine(header.ARM9iDecryptedSHA1HMACHash, " ARM9i (decrypted) SHA1 HMAC hash");
+ builder.AppendLine(header.ARM7iDecryptedSHA1HMACHash, " ARM7i (decrypted) SHA1 HMAC hash");
+ builder.AppendLine(header.Reserved5, " Reserved 5");
+ builder.AppendLine(header.ARM9NoSecureAreaSHA1HMACHash, " ARM9 (without secure area) SHA1 HMAC hash");
+ builder.AppendLine(header.Reserved6, " Reserved 6");
+ builder.AppendLine(header.ReservedAndUnchecked, " Reserved and unchecked region");
+ builder.AppendLine(header.RSASignature, " RSA signature");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, byte[]? secureArea)
+ {
+ builder.AppendLine(" Secure Area Information:");
+ builder.AppendLine(" -------------------------");
+ builder.AppendLine(secureArea, " Secure Area");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, NameTable? table)
+ {
+ builder.AppendLine(" Name Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null)
+ {
+ builder.AppendLine(" No name table");
+ builder.AppendLine();
+ return;
+ }
+ builder.AppendLine();
+
+ Print(builder, table.FolderAllocationTable);
+ Print(builder, table.NameList);
+ }
+
+ private static void Print(StringBuilder builder, FolderAllocationTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Folder Allocation Table:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No folder allocation table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Folder Allocation Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.StartOffset, " Start offset");
+ builder.AppendLine(entry.FirstFileIndex, " First file index");
+ if (entry.Unknown == 0xF0)
+ {
+ builder.AppendLine(entry.ParentFolderIndex, " Parent folder index");
+ builder.AppendLine(entry.Unknown, " Unknown");
+ }
+ else
+ {
+ ushort totalEntries = (ushort)((entry.Unknown << 8) | entry.ParentFolderIndex);
+ builder.AppendLine(totalEntries, " Total entries");
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, NameListEntry?[]? entries)
+ {
+ builder.AppendLine(" Name List:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No name list entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Name List Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Folder, " Folder");
+ builder.AppendLine(entry.Name, " Name");
+ if (entry.Folder)
+ builder.AppendLine(entry.Index, " Index");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, FileAllocationTableEntry?[]? entries)
+ {
+ builder.AppendLine(" File Allocation Table:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No file allocation table entries");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" File Allocation Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.StartOffset, " Start offset");
+ builder.AppendLine(entry.EndOffset, " End offset");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/PAK.cs b/SabreTools.Serialization/Printers/PAK.cs
new file mode 100644
index 00000000..50143461
--- /dev/null
+++ b/SabreTools.Serialization/Printers/PAK.cs
@@ -0,0 +1,68 @@
+using System.Text;
+using SabreTools.Models.PAK;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class PAK : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, File model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, File file)
+ {
+ builder.AppendLine("PAK Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, file.Header);
+ Print(builder, file.DirectoryItems);
+ }
+
+ private static void Print(StringBuilder builder, Header? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Signature, " Signature");
+ builder.AppendLine(header.DirectoryOffset, " Directory offset");
+ builder.AppendLine(header.DirectoryLength, " Directory length");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DirectoryItem?[]? entries)
+ {
+ builder.AppendLine(" Directory Items Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No directory items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Directory Item {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.ItemName, " Item name");
+ builder.AppendLine(entry.ItemOffset, " Item offset");
+ builder.AppendLine(entry.ItemLength, " Item length");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/PFF.cs b/SabreTools.Serialization/Printers/PFF.cs
new file mode 100644
index 00000000..71306ac3
--- /dev/null
+++ b/SabreTools.Serialization/Printers/PFF.cs
@@ -0,0 +1,92 @@
+using System.Text;
+using SabreTools.Models.PFF;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class PFF : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Archive model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Archive archive)
+ {
+ builder.AppendLine("PFF Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, archive.Header);
+ Print(builder, archive.Segments);
+ Print(builder, archive.Footer);
+ }
+
+ private static void Print(StringBuilder builder, Header? header)
+ {
+ builder.AppendLine(" Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.HeaderSize, " Header size");
+ builder.AppendLine(header.Signature, " Signature");
+ builder.AppendLine(header.NumberOfFiles, " Number of files");
+ builder.AppendLine(header.FileSegmentSize, " File segment size");
+ builder.AppendLine(header.FileListOffset, " File list offset");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Segment?[]? segments)
+ {
+ builder.AppendLine(" Segments Information:");
+ builder.AppendLine(" -------------------------");
+ if (segments == null || segments.Length == 0)
+ {
+ builder.AppendLine(" No segments");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < segments.Length; i++)
+ {
+ var segment = segments[i];
+ builder.AppendLine($" Segment {i}");
+ if (segment == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(segment.Deleted, " Deleted");
+ builder.AppendLine(segment.FileLocation, " File location");
+ builder.AppendLine(segment.FileSize, " File size");
+ builder.AppendLine(segment.PackedDate, " Packed date");
+ builder.AppendLine(segment.FileName, " File name");
+ builder.AppendLine(segment.ModifiedDate, " Modified date");
+ builder.AppendLine(segment.CompressionLevel, " Compression level");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, Footer? footer)
+ {
+ builder.AppendLine(" Footer Information:");
+ builder.AppendLine(" -------------------------");
+ if (footer == null)
+ {
+ builder.AppendLine(" No footer");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(footer.SystemIP, " System IP");
+ builder.AppendLine(footer.Reserved, " Reserved");
+ builder.AppendLine(footer.KingTag, " King tag");
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/PIC.cs b/SabreTools.Serialization/Printers/PIC.cs
new file mode 100644
index 00000000..bf082a20
--- /dev/null
+++ b/SabreTools.Serialization/Printers/PIC.cs
@@ -0,0 +1,87 @@
+using System.Text;
+using SabreTools.Models.PIC;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class PIC : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, DiscInformation model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, DiscInformation di)
+ {
+ builder.AppendLine("PIC Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine(di.DataStructureLength, "Data structure length");
+ builder.AppendLine(di.Reserved0, "Reserved");
+ builder.AppendLine(di.Reserved1, "Reserved");
+ builder.AppendLine();
+
+ Print(builder, di.Units);
+ }
+
+ private static void Print(StringBuilder builder, DiscInformationUnit?[]? entries)
+ {
+ builder.AppendLine(" Disc Information Units:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No disc information units");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Disc Information Unit {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ if (entry.Header == null)
+ {
+ builder.AppendLine(" No header");
+ }
+ else
+ {
+ var header = entry.Header;
+ builder.AppendLine(header.DiscInformationIdentifier, " Disc information identifier");
+ builder.AppendLine(header.DiscInformationFormat, " Disc information format");
+ builder.AppendLine(header.Reserved0, " Reserved");
+ builder.AppendLine(header.SequenceNumber, " Sequence number");
+ builder.AppendLine(header.BytesInUse, " Bytes in use");
+ builder.AppendLine(header.Reserved1, " Reserved");
+ }
+ if (entry.Body == null)
+ {
+ builder.AppendLine(" No body");
+ }
+ else
+ {
+ DiscInformationUnitBody body = entry.Body;
+ builder.AppendLine(body.DiscTypeIdentifier, " Disc type identifer");
+ builder.AppendLine(body.DiscSizeClassVersion, " Disc size class version");
+ builder.AppendLine(body.FormatDependentContents, " Format-dependent contents");
+ }
+ if (entry.Trailer == null)
+ {
+ builder.AppendLine(" No trailer");
+ }
+ else
+ {
+ DiscInformationUnitTrailer trailer = entry.Trailer;
+ builder.AppendLine(trailer.DiscManufacturerID, " Disc manufacturer ID");
+ builder.AppendLine(trailer.MediaTypeID, " Media type ID");
+ builder.AppendLine(trailer.TimeStamp, " Timestamp");
+ builder.AppendLine(trailer.ProductRevisionNumber, " Product revision number");
+ }
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/PlayJAudioFile.cs b/SabreTools.Serialization/Printers/PlayJAudioFile.cs
new file mode 100644
index 00000000..df5284db
--- /dev/null
+++ b/SabreTools.Serialization/Printers/PlayJAudioFile.cs
@@ -0,0 +1,183 @@
+using System.Text;
+using SabreTools.Models.PlayJ;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class PlayJAudioFile : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, AudioFile model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, AudioFile audio)
+ {
+ builder.AppendLine("PlayJ Audio File Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, audio.Header);
+ Print(builder, audio.UnknownBlock1);
+
+ if (audio.Header?.Version == 0x00000000)
+ {
+ Print(builder, audio.UnknownValue2);
+ Print(builder, audio.UnknownBlock3);
+ }
+ else if (audio.Header?.Version == 0x0000000A)
+ {
+ Print(builder, audio.DataFilesCount, audio.DataFiles);
+ }
+ }
+
+ private static void Print(StringBuilder builder, AudioHeader? header)
+ {
+ builder.AppendLine(" Audio Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No audio header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Signature, " Signature");
+ builder.AppendLine(header.Version, " Version");
+ if (header.Version == 0x00000000 && header is AudioHeaderV1 headerV1)
+ {
+ builder.AppendLine(headerV1.TrackID, " Track ID");
+ builder.AppendLine(headerV1.UnknownOffset1, " Unknown offset 1");
+ builder.AppendLine(headerV1.UnknownOffset2, " Unknown offset 2");
+ builder.AppendLine(headerV1.UnknownOffset3, " Unknown offset 3");
+ builder.AppendLine(headerV1.Unknown1, " Unknown 1");
+ builder.AppendLine(headerV1.Unknown2, " Unknown 2");
+ builder.AppendLine(headerV1.Year, " Year");
+ builder.AppendLine(headerV1.TrackNumber, " Track number");
+ builder.AppendLine($" Subgenre: {headerV1.Subgenre} (0x{headerV1.Subgenre:X})");
+ builder.AppendLine(headerV1.Duration, " Duration in seconds");
+ }
+ else if (header.Version == 0x0000000A && header is AudioHeaderV2 headerV2)
+ {
+ builder.AppendLine(headerV2.Unknown1, " Unknown 1");
+ builder.AppendLine(headerV2.Unknown2, " Unknown 2");
+ builder.AppendLine(headerV2.Unknown3, " Unknown 3");
+ builder.AppendLine(headerV2.Unknown4, " Unknown 4");
+ builder.AppendLine(headerV2.Unknown5, " Unknown 5");
+ builder.AppendLine(headerV2.Unknown6, " Unknown 6");
+ builder.AppendLine(headerV2.UnknownOffset1, " Unknown Offset 1");
+ builder.AppendLine(headerV2.Unknown7, " Unknown 7");
+ builder.AppendLine(headerV2.Unknown8, " Unknown 8");
+ builder.AppendLine(headerV2.Unknown9, " Unknown 9");
+ builder.AppendLine(headerV2.UnknownOffset2, " Unknown Offset 2");
+ builder.AppendLine(headerV2.Unknown10, " Unknown 10");
+ builder.AppendLine(headerV2.Unknown11, " Unknown 11");
+ builder.AppendLine(headerV2.Unknown12, " Unknown 12");
+ builder.AppendLine(headerV2.Unknown13, " Unknown 13");
+ builder.AppendLine(headerV2.Unknown14, " Unknown 14");
+ builder.AppendLine(headerV2.Unknown15, " Unknown 15");
+ builder.AppendLine(headerV2.Unknown16, " Unknown 16");
+ builder.AppendLine(headerV2.Unknown17, " Unknown 17");
+ builder.AppendLine(headerV2.TrackID, " Track ID");
+ builder.AppendLine(headerV2.Year, " Year");
+ builder.AppendLine(headerV2.TrackNumber, " Track number");
+ builder.AppendLine(headerV2.Unknown18, " Unknown 18");
+ }
+ else
+ {
+ builder.AppendLine(" Unrecognized version, not parsed...");
+ }
+
+ builder.AppendLine(header.TrackLength, " Track length");
+ builder.AppendLine(header.Track, " Track");
+ builder.AppendLine(header.ArtistLength, " Artist length");
+ builder.AppendLine(header.Artist, " Artist");
+ builder.AppendLine(header.AlbumLength, " Album length");
+ builder.AppendLine(header.Album, " Album");
+ builder.AppendLine(header.WriterLength, " Writer length");
+ builder.AppendLine(header.Writer, " Writer");
+ builder.AppendLine(header.PublisherLength, " Publisher length");
+ builder.AppendLine(header.Publisher, " Publisher");
+ builder.AppendLine(header.LabelLength, " Label length");
+ builder.AppendLine(header.Label, " Label");
+ builder.AppendLine(header.CommentsLength, " Comments length");
+ builder.AppendLine(header.Comments, " Comments");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, UnknownBlock1? block)
+ {
+ builder.AppendLine(" Unknown Block 1 Information:");
+ builder.AppendLine(" -------------------------");
+ if (block == null)
+ {
+ builder.AppendLine(" No unknown block 1r");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(block.Length, " Length");
+ builder.AppendLine(block.Data, " Data");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, uint? value)
+ {
+ builder.AppendLine(" Unknown Value 2 Information:");
+ builder.AppendLine(" -------------------------");
+ if (value == null)
+ {
+ builder.AppendLine(" No unknown block 1r");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(value, " Value");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, UnknownBlock3? block)
+ {
+ builder.AppendLine(" Unknown Block 3 Information:");
+ builder.AppendLine(" -------------------------");
+ if (block == null)
+ {
+ builder.AppendLine(" No unknown block 1r");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(block.Data, " Data");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, uint count, DataFile?[]? entries)
+ {
+ builder.AppendLine(" Data Files Information:");
+ builder.AppendLine(" -------------------------");
+ builder.AppendLine(count, " Data files count");
+ if (count == 0 || entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No data files");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Data File {i}:");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.FileNameLength, " File name length");
+ builder.AppendLine(entry.FileName, " File name");
+ builder.AppendLine(entry.DataLength, " Data length");
+ builder.AppendLine(entry.Data, " Data");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/PlayJPlaylist.cs b/SabreTools.Serialization/Printers/PlayJPlaylist.cs
new file mode 100644
index 00000000..e8170799
--- /dev/null
+++ b/SabreTools.Serialization/Printers/PlayJPlaylist.cs
@@ -0,0 +1,226 @@
+using System.Text;
+using SabreTools.Models.PlayJ;
+using SabreTools.Serialization.Interfaces;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class PlayJPlaylist : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Playlist model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Playlist playlist)
+ {
+ builder.AppendLine("PlayJ Playlist Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ Print(builder, playlist.Header);
+ Print(builder, playlist.AudioFiles);
+ }
+
+ private static void Print(StringBuilder builder, PlaylistHeader? header)
+ {
+ builder.AppendLine(" Playlist Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No playlist header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.TrackCount, " Track count");
+ builder.AppendLine(header.Data, " Data");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, AudioFile?[]? entries)
+ {
+ builder.AppendLine(" Audio Files Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No audio files");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Audio File {i}:");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ Print(builder, entry.Header);
+ Print(builder, entry.UnknownBlock1);
+
+ if (entry.Header?.Version == 0x00000000)
+ {
+ Print(builder, entry.UnknownValue2);
+ Print(builder, entry.UnknownBlock3);
+ }
+ else if (entry.Header?.Version == 0x0000000A)
+ {
+ Print(builder, entry.DataFilesCount, entry.DataFiles);
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, AudioHeader? header)
+ {
+ builder.AppendLine(" Audio Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No audio header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Signature, " Signature");
+ builder.AppendLine(header.Version, " Version");
+ if (header.Version == 0x00000000 && header is AudioHeaderV1 headerV1)
+ {
+ builder.AppendLine(headerV1.TrackID, " Track ID");
+ builder.AppendLine(headerV1.UnknownOffset1, " Unknown offset 1");
+ builder.AppendLine(headerV1.UnknownOffset2, " Unknown offset 2");
+ builder.AppendLine(headerV1.UnknownOffset3, " Unknown offset 3");
+ builder.AppendLine(headerV1.Unknown1, " Unknown 1");
+ builder.AppendLine(headerV1.Unknown2, " Unknown 2");
+ builder.AppendLine(headerV1.Year, " Year");
+ builder.AppendLine(headerV1.TrackNumber, " Track number");
+ builder.AppendLine($" Subgenre: {headerV1.Subgenre} (0x{headerV1.Subgenre:X})");
+ builder.AppendLine(headerV1.Duration, " Duration in seconds");
+ }
+ else if (header.Version == 0x0000000A && header is AudioHeaderV2 headerV2)
+ {
+ builder.AppendLine(headerV2.Unknown1, " Unknown 1");
+ builder.AppendLine(headerV2.Unknown2, " Unknown 2");
+ builder.AppendLine(headerV2.Unknown3, " Unknown 3");
+ builder.AppendLine(headerV2.Unknown4, " Unknown 4");
+ builder.AppendLine(headerV2.Unknown5, " Unknown 5");
+ builder.AppendLine(headerV2.Unknown6, " Unknown 6");
+ builder.AppendLine(headerV2.UnknownOffset1, " Unknown Offset 1");
+ builder.AppendLine(headerV2.Unknown7, " Unknown 7");
+ builder.AppendLine(headerV2.Unknown8, " Unknown 8");
+ builder.AppendLine(headerV2.Unknown9, " Unknown 9");
+ builder.AppendLine(headerV2.UnknownOffset2, " Unknown Offset 2");
+ builder.AppendLine(headerV2.Unknown10, " Unknown 10");
+ builder.AppendLine(headerV2.Unknown11, " Unknown 11");
+ builder.AppendLine(headerV2.Unknown12, " Unknown 12");
+ builder.AppendLine(headerV2.Unknown13, " Unknown 13");
+ builder.AppendLine(headerV2.Unknown14, " Unknown 14");
+ builder.AppendLine(headerV2.Unknown15, " Unknown 15");
+ builder.AppendLine(headerV2.Unknown16, " Unknown 16");
+ builder.AppendLine(headerV2.Unknown17, " Unknown 17");
+ builder.AppendLine(headerV2.TrackID, " Track ID");
+ builder.AppendLine(headerV2.Year, " Year");
+ builder.AppendLine(headerV2.TrackNumber, " Track number");
+ builder.AppendLine(headerV2.Unknown18, " Unknown 18");
+ }
+ else
+ {
+ builder.AppendLine(" Unrecognized version, not parsed...");
+ }
+
+ builder.AppendLine(header.TrackLength, " Track length");
+ builder.AppendLine(header.Track, " Track");
+ builder.AppendLine(header.ArtistLength, " Artist length");
+ builder.AppendLine(header.Artist, " Artist");
+ builder.AppendLine(header.AlbumLength, " Album length");
+ builder.AppendLine(header.Album, " Album");
+ builder.AppendLine(header.WriterLength, " Writer length");
+ builder.AppendLine(header.Writer, " Writer");
+ builder.AppendLine(header.PublisherLength, " Publisher length");
+ builder.AppendLine(header.Publisher, " Publisher");
+ builder.AppendLine(header.LabelLength, " Label length");
+ builder.AppendLine(header.Label, " Label");
+ builder.AppendLine(header.CommentsLength, " Comments length");
+ builder.AppendLine(header.Comments, " Comments");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, UnknownBlock1? block)
+ {
+ builder.AppendLine(" Unknown Block 1 Information:");
+ builder.AppendLine(" -------------------------");
+ if (block == null)
+ {
+ builder.AppendLine(" No unknown block 1r");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(block.Length, " Length");
+ builder.AppendLine(block.Data, " Data");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, uint? value)
+ {
+ builder.AppendLine(" Unknown Value 2 Information:");
+ builder.AppendLine(" -------------------------");
+ if (value == null)
+ {
+ builder.AppendLine(" No unknown block 1r");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(value, " Value");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, UnknownBlock3? block)
+ {
+ builder.AppendLine(" Unknown Block 3 Information:");
+ builder.AppendLine(" -------------------------");
+ if (block == null)
+ {
+ builder.AppendLine(" No unknown block 1r");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(block.Data, " Data");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, uint count, DataFile?[]? entries)
+ {
+ builder.AppendLine(" Data Files Information:");
+ builder.AppendLine(" -------------------------");
+ builder.AppendLine(count, " Data files count");
+ if (count == 0 || entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No data files");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Data File {i}:");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.FileNameLength, " File name length");
+ builder.AppendLine(entry.FileName, " File name");
+ builder.AppendLine(entry.DataLength, " Data length");
+ builder.AppendLine(entry.Data, " Data");
+ }
+ builder.AppendLine();
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Serialization/Printers/PortableExecutable.cs b/SabreTools.Serialization/Printers/PortableExecutable.cs
new file mode 100644
index 00000000..cf0a83d7
--- /dev/null
+++ b/SabreTools.Serialization/Printers/PortableExecutable.cs
@@ -0,0 +1,1943 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml;
+using SabreTools.ASN1;
+using SabreTools.IO.Extensions;
+using SabreTools.Models.PortableExecutable;
+using SabreTools.Serialization.Interfaces;
+using static SabreTools.Serialization.Extensions;
+
+namespace SabreTools.Serialization.Printers
+{
+ public class PortableExecutable : IPrinter
+ {
+ ///
+ public void PrintInformation(StringBuilder builder, Executable model)
+ => Print(builder, model);
+
+ public static void Print(StringBuilder builder, Executable executable)
+ {
+ builder.AppendLine("Portable Executable Information:");
+ builder.AppendLine("-------------------------");
+ builder.AppendLine();
+
+ // Stub
+ Print(builder, executable.Stub?.Header);
+
+ // Header
+ Print(builder, executable.Signature, executable.COFFFileHeader);
+ Print(builder, executable.OptionalHeader, executable.SectionTable);
+
+ // Tables
+ Print(builder, executable.SectionTable);
+ Print(builder, executable.COFFSymbolTable);
+ Print(builder, executable.COFFStringTable);
+ Print(builder, executable.AttributeCertificateTable);
+ Print(builder, executable.DelayLoadDirectoryTable);
+
+ // Named Sections
+ Print(builder, executable.BaseRelocationTable, executable.SectionTable);
+ Print(builder, executable.DebugTable);
+ Print(builder, executable.ExportTable);
+ Print(builder, executable.ImportTable, executable.SectionTable);
+ Print(builder, executable.ResourceDirectoryTable);
+ }
+
+ private static void Print(StringBuilder builder, Models.MSDOS.ExecutableHeader? header)
+ {
+ builder.AppendLine(" MS-DOS Stub Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No MS-DOS stub header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(header.Magic, " Magic number");
+ builder.AppendLine(header.LastPageBytes, " Last page bytes");
+ builder.AppendLine(header.Pages, " Pages");
+ builder.AppendLine(header.RelocationItems, " Relocation items");
+ builder.AppendLine(header.HeaderParagraphSize, " Header paragraph size");
+ builder.AppendLine(header.MinimumExtraParagraphs, " Minimum extra paragraphs");
+ builder.AppendLine(header.MaximumExtraParagraphs, " Maximum extra paragraphs");
+ builder.AppendLine(header.InitialSSValue, " Initial SS value");
+ builder.AppendLine(header.InitialSPValue, " Initial SP value");
+ builder.AppendLine(header.Checksum, " Checksum");
+ builder.AppendLine(header.InitialIPValue, " Initial IP value");
+ builder.AppendLine(header.InitialCSValue, " Initial CS value");
+ builder.AppendLine(header.RelocationTableAddr, " Relocation table address");
+ builder.AppendLine(header.OverlayNumber, " Overlay number");
+ builder.AppendLine();
+
+ builder.AppendLine(" MS-DOS Stub Extended Header Information:");
+ builder.AppendLine(" -------------------------");
+ builder.AppendLine(header.Reserved1, " Reserved words");
+ builder.AppendLine(header.OEMIdentifier, " OEM identifier");
+ builder.AppendLine(header.OEMInformation, " OEM information");
+ builder.AppendLine(header.Reserved2, " Reserved words");
+ builder.AppendLine(header.NewExeHeaderAddr, " New EXE header address");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, string? signature, COFFFileHeader? header)
+ {
+ builder.AppendLine(" COFF File Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No COFF file header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(signature, " Signature");
+ builder.AppendLine($" Machine: {header.Machine} (0x{header.Machine:X})");
+ builder.AppendLine(header.NumberOfSections, " Number of sections");
+ builder.AppendLine(header.TimeDateStamp, " Time/Date stamp");
+ builder.AppendLine(header.PointerToSymbolTable, " Pointer to symbol table");
+ builder.AppendLine(header.NumberOfSymbols, " Number of symbols");
+ builder.AppendLine(header.SizeOfOptionalHeader, " Size of optional header");
+ builder.AppendLine($" Characteristics: {header.Characteristics} (0x{header.Characteristics:X})");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, OptionalHeader? header, SectionHeader?[]? table)
+ {
+ builder.AppendLine(" Optional Header Information:");
+ builder.AppendLine(" -------------------------");
+ if (header == null)
+ {
+ builder.AppendLine(" No optional header");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine($" Magic: {header.Magic} (0x{header.Magic:X})");
+ builder.AppendLine(header.MajorLinkerVersion, " Major linker version");
+ builder.AppendLine(header.MinorLinkerVersion, " Minor linker version");
+ builder.AppendLine(header.SizeOfCode, " Size of code section");
+ builder.AppendLine(header.SizeOfInitializedData, " Size of initialized data");
+ builder.AppendLine(header.SizeOfUninitializedData, " Size of uninitialized data");
+ builder.AppendLine(header.AddressOfEntryPoint, " Address of entry point");
+ builder.AppendLine(header.BaseOfCode, " Base of code");
+ if (header.Magic == OptionalHeaderMagicNumber.PE32)
+ builder.AppendLine(header.BaseOfData, " Base of data");
+
+ if (header.Magic == OptionalHeaderMagicNumber.PE32)
+ builder.AppendLine(header.ImageBase_PE32, " Image base");
+ else
+ builder.AppendLine(header.ImageBase_PE32Plus, " Image base");
+ builder.AppendLine(header.SectionAlignment, " Section alignment");
+ builder.AppendLine(header.FileAlignment, " File alignment");
+ builder.AppendLine(header.MajorOperatingSystemVersion, " Major operating system version");
+ builder.AppendLine(header.MinorOperatingSystemVersion, " Minor operating system version");
+ builder.AppendLine(header.MajorImageVersion, " Major image version");
+ builder.AppendLine(header.MinorImageVersion, " Minor image version");
+ builder.AppendLine(header.MajorSubsystemVersion, " Major subsystem version");
+ builder.AppendLine(header.MinorSubsystemVersion, " Minor subsystem version");
+ builder.AppendLine(header.Win32VersionValue, " Win32 version value");
+ builder.AppendLine(header.SizeOfImage, " Size of image");
+ builder.AppendLine(header.SizeOfHeaders, " Size of headers");
+ builder.AppendLine(header.CheckSum, " Checksum");
+ builder.AppendLine($" Subsystem: {header.Subsystem} (0x{header.Subsystem:X})");
+ builder.AppendLine($" DLL characteristics: {header.DllCharacteristics} (0x{header.DllCharacteristics:X})");
+ if (header.Magic == OptionalHeaderMagicNumber.PE32)
+ {
+ builder.AppendLine(header.SizeOfStackReserve_PE32, " Size of stack reserve");
+ builder.AppendLine(header.SizeOfStackCommit_PE32, " Size of stack commit");
+ builder.AppendLine(header.SizeOfHeapReserve_PE32, " Size of heap reserve");
+ builder.AppendLine(header.SizeOfHeapCommit_PE32, " Size of heap commit");
+ }
+ else
+ {
+ builder.AppendLine(header.SizeOfStackReserve_PE32Plus, " Size of stack reserve");
+ builder.AppendLine(header.SizeOfStackCommit_PE32Plus, " Size of stack commit");
+ builder.AppendLine(header.SizeOfHeapReserve_PE32Plus, " Size of heap reserve");
+ builder.AppendLine(header.SizeOfHeapCommit_PE32Plus, " Size of heap commit");
+ }
+ builder.AppendLine(header.LoaderFlags, " Loader flags");
+ builder.AppendLine(header.NumberOfRvaAndSizes, " Number of data-directory entries");
+
+ if (header.ExportTable != null)
+ {
+ builder.AppendLine(" Export Table (1)");
+ builder.AppendLine(header.ExportTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.ExportTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.ExportTable.Size, " Size");
+ }
+ if (header.ImportTable != null)
+ {
+ builder.AppendLine(" Import Table (2)");
+ builder.AppendLine(header.ImportTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.ImportTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.ImportTable.Size, " Size");
+ }
+ if (header.ResourceTable != null)
+ {
+ builder.AppendLine(" Resource Table (3)");
+ builder.AppendLine(header.ResourceTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.ResourceTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.ResourceTable.Size, " Size");
+ }
+ if (header.ExceptionTable != null)
+ {
+ builder.AppendLine(" Exception Table (4)");
+ builder.AppendLine(header.ExceptionTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.ExceptionTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.ExceptionTable.Size, " Size");
+ }
+ if (header.CertificateTable != null)
+ {
+ builder.AppendLine(" Certificate Table (5)");
+ builder.AppendLine(header.CertificateTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.CertificateTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.CertificateTable.Size, " Size");
+ }
+ if (header.BaseRelocationTable != null)
+ {
+ builder.AppendLine(" Base Relocation Table (6)");
+ builder.AppendLine(header.BaseRelocationTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.BaseRelocationTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.BaseRelocationTable.Size, " Size");
+ }
+ if (header.Debug != null)
+ {
+ builder.AppendLine(" Debug Table (7)");
+ builder.AppendLine(header.Debug.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.Debug.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.Debug.Size, " Size");
+ }
+ if (header.NumberOfRvaAndSizes >= 8)
+ {
+ builder.AppendLine(" Architecture Table (8)");
+ builder.AppendLine(" Virtual address: 0 (0x00000000)");
+ builder.AppendLine(" Physical address: 0 (0x00000000)");
+ builder.AppendLine(" Size: 0 (0x00000000)");
+ }
+ if (header.GlobalPtr != null)
+ {
+ builder.AppendLine(" Global Pointer Register (9)");
+ builder.AppendLine(header.GlobalPtr.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.GlobalPtr.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.GlobalPtr.Size, " Size");
+ }
+ if (header.ThreadLocalStorageTable != null)
+ {
+ builder.AppendLine(" Thread Local Storage (TLS) Table (10)");
+ builder.AppendLine(header.ThreadLocalStorageTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.ThreadLocalStorageTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.ThreadLocalStorageTable.Size, " Size");
+ }
+ if (header.LoadConfigTable != null)
+ {
+ builder.AppendLine(" Load Config Table (11)");
+ builder.AppendLine(header.LoadConfigTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.LoadConfigTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.LoadConfigTable.Size, " Size");
+ }
+ if (header.BoundImport != null)
+ {
+ builder.AppendLine(" Bound Import Table (12)");
+ builder.AppendLine(header.BoundImport.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.BoundImport.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.BoundImport.Size, " Size");
+ }
+ if (header.ImportAddressTable != null)
+ {
+ builder.AppendLine(" Import Address Table (13)");
+ builder.AppendLine(header.ImportAddressTable.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.ImportAddressTable.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.ImportAddressTable.Size, " Size");
+ }
+ if (header.DelayImportDescriptor != null)
+ {
+ builder.AppendLine(" Delay Import Descriptior (14)");
+ builder.AppendLine(header.DelayImportDescriptor.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.DelayImportDescriptor.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.DelayImportDescriptor.Size, " Size");
+ }
+ if (header.CLRRuntimeHeader != null)
+ {
+ builder.AppendLine(" CLR Runtime Header (15)");
+ builder.AppendLine(header.CLRRuntimeHeader.VirtualAddress, " Virtual address");
+ builder.AppendLine(header.CLRRuntimeHeader.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(header.CLRRuntimeHeader.Size, " Size");
+ }
+ if (header.NumberOfRvaAndSizes >= 16)
+ {
+ builder.AppendLine(" Reserved (16)");
+ builder.AppendLine(" Virtual address: 0 (0x00000000)");
+ builder.AppendLine(" Physical address: 0 (0x00000000)");
+ builder.AppendLine(" Size: 0 (0x00000000)");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, SectionHeader?[]? table)
+ {
+ builder.AppendLine(" Section Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null || table.Length == 0)
+ {
+ builder.AppendLine(" No section table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < table!.Length; i++)
+ {
+ var entry = table[i];
+ builder.AppendLine($" Section Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Name, " Name", Encoding.ASCII);
+ builder.AppendLine(entry.VirtualSize, " Virtual size");
+ builder.AppendLine(entry.VirtualAddress, " Virtual address");
+ builder.AppendLine(entry.VirtualAddress.ConvertVirtualAddress(table), " Physical address");
+ builder.AppendLine(entry.SizeOfRawData, " Size of raw data");
+ builder.AppendLine(entry.PointerToRawData, " Pointer to raw data");
+ builder.AppendLine(entry.PointerToRelocations, " Pointer to relocations");
+ builder.AppendLine(entry.PointerToLinenumbers, " Pointer to linenumbers");
+ builder.AppendLine(entry.NumberOfRelocations, " Number of relocations");
+ builder.AppendLine(entry.NumberOfLinenumbers, " Number of linenumbers");
+ builder.AppendLine($" Characteristics: {entry.Characteristics} (0x{entry.Characteristics:X})");
+ // TODO: Add COFFRelocations
+ // TODO: Add COFFLineNumbers
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, COFFSymbolTableEntry?[]? symbolTable)
+ {
+ builder.AppendLine(" COFF Symbol Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (symbolTable == null || symbolTable.Length == 0)
+ {
+ builder.AppendLine(" No COFF symbol table items");
+ builder.AppendLine();
+ return;
+ }
+
+ int auxSymbolsRemaining = 0;
+ int currentSymbolType = 0;
+
+ for (int i = 0; i < symbolTable.Length; i++)
+ {
+ var entry = symbolTable[i];
+ builder.AppendLine($" COFF Symbol Table Entry {i} (Subtype {currentSymbolType})");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ if (currentSymbolType == 0)
+ {
+ if (entry.ShortName != null)
+ {
+ builder.AppendLine(entry.ShortName, " Short name", Encoding.ASCII);
+ }
+ else
+ {
+ builder.AppendLine(entry.Zeroes, " Zeroes");
+ builder.AppendLine(entry.Offset, " Offset");
+ }
+ builder.AppendLine(entry.Value, " Value");
+ builder.AppendLine(entry.SectionNumber, " Section number");
+ builder.AppendLine($" Symbol type: {entry.SymbolType} (0x{entry.SymbolType:X})");
+ builder.AppendLine($" Storage class: {entry.StorageClass} (0x{entry.StorageClass:X})");
+ builder.AppendLine(entry.NumberOfAuxSymbols, " Number of aux symbols");
+
+ auxSymbolsRemaining = entry.NumberOfAuxSymbols;
+ if (auxSymbolsRemaining == 0)
+ continue;
+
+ if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL
+ && entry.SymbolType == SymbolType.IMAGE_SYM_TYPE_FUNC
+ && entry.SectionNumber > 0)
+ {
+ currentSymbolType = 1;
+ }
+ else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FUNCTION
+ && entry.ShortName != null
+ && ((entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x62 && entry.ShortName[2] == 0x66) // .bf
+ || (entry.ShortName[0] == 0x2E && entry.ShortName[1] == 0x65 && entry.ShortName[2] == 0x66))) // .ef
+ {
+ currentSymbolType = 2;
+ }
+ else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_EXTERNAL
+ && entry.SectionNumber == (ushort)SectionNumber.IMAGE_SYM_UNDEFINED
+ && entry.Value == 0)
+ {
+ currentSymbolType = 3;
+ }
+ else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_FILE)
+ {
+ // TODO: Symbol name should be ".file"
+ currentSymbolType = 4;
+ }
+ else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_STATIC)
+ {
+ // TODO: Should have the name of a section (like ".text")
+ currentSymbolType = 5;
+ }
+ else if (entry.StorageClass == StorageClass.IMAGE_SYM_CLASS_CLR_TOKEN)
+ {
+ currentSymbolType = 6;
+ }
+ }
+ else if (currentSymbolType == 1)
+ {
+ builder.AppendLine(entry.AuxFormat1TagIndex, " Tag index");
+ builder.AppendLine(entry.AuxFormat1TotalSize, " Total size");
+ builder.AppendLine(entry.AuxFormat1PointerToLinenumber, " Pointer to linenumber");
+ builder.AppendLine(entry.AuxFormat1PointerToNextFunction, " Pointer to next function");
+ builder.AppendLine(entry.AuxFormat1Unused, " Unused");
+ auxSymbolsRemaining--;
+ }
+ else if (currentSymbolType == 2)
+ {
+ builder.AppendLine(entry.AuxFormat2Unused1, " Unused");
+ builder.AppendLine(entry.AuxFormat2Linenumber, " Linenumber");
+ builder.AppendLine(entry.AuxFormat2Unused2, " Unused");
+ builder.AppendLine(entry.AuxFormat2PointerToNextFunction, " Pointer to next function");
+ builder.AppendLine(entry.AuxFormat2Unused3, " Unused");
+ auxSymbolsRemaining--;
+ }
+ else if (currentSymbolType == 3)
+ {
+ builder.AppendLine(entry.AuxFormat3TagIndex, " Tag index");
+ builder.AppendLine(entry.AuxFormat3Characteristics, " Characteristics");
+ builder.AppendLine(entry.AuxFormat3Unused, " Unused");
+ auxSymbolsRemaining--;
+ }
+ else if (currentSymbolType == 4)
+ {
+ builder.AppendLine(entry.AuxFormat4FileName, " File name", Encoding.ASCII);
+ auxSymbolsRemaining--;
+ }
+ else if (currentSymbolType == 5)
+ {
+ builder.AppendLine(entry.AuxFormat5Length, " Length");
+ builder.AppendLine(entry.AuxFormat5NumberOfRelocations, " Number of relocations");
+ builder.AppendLine(entry.AuxFormat5NumberOfLinenumbers, " Number of linenumbers");
+ builder.AppendLine(entry.AuxFormat5CheckSum, " Checksum");
+ builder.AppendLine(entry.AuxFormat5Number, " Number");
+ builder.AppendLine(entry.AuxFormat5Selection, " Selection");
+ builder.AppendLine(entry.AuxFormat5Unused, " Unused");
+ auxSymbolsRemaining--;
+ }
+ else if (currentSymbolType == 6)
+ {
+ builder.AppendLine(entry.AuxFormat6AuxType, " Aux type");
+ builder.AppendLine(entry.AuxFormat6Reserved1, " Reserved");
+ builder.AppendLine(entry.AuxFormat6SymbolTableIndex, " Symbol table index");
+ builder.AppendLine(entry.AuxFormat6Reserved2, " Reserved");
+ auxSymbolsRemaining--;
+ }
+
+ // If we hit the last aux symbol, go back to normal format
+ if (auxSymbolsRemaining == 0)
+ currentSymbolType = 0;
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, COFFStringTable? stringTable)
+ {
+ builder.AppendLine(" COFF String Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (stringTable?.Strings == null || stringTable.Strings.Length == 0)
+ {
+ builder.AppendLine(" No COFF string table items");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(stringTable.TotalSize, " Total size");
+ for (int i = 0; i < stringTable.Strings.Length; i++)
+ {
+ string? entry = stringTable.Strings[i];
+ builder.AppendLine($" COFF String Table Entry {i})");
+ builder.AppendLine(entry, " Value");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, AttributeCertificateTableEntry?[]? entries)
+ {
+ builder.AppendLine(" Attribute Certificate Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No attribute certificate table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var entry = entries[i];
+ builder.AppendLine($" Attribute Certificate Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Length, " Length");
+ builder.AppendLine($" Revision: {entry.Revision} (0x{entry.Revision:X})");
+ builder.AppendLine($" Certificate type: {entry.CertificateType} (0x{entry.CertificateType:X})");
+ builder.AppendLine();
+ if (entry.CertificateType == WindowsCertificateType.WIN_CERT_TYPE_PKCS_SIGNED_DATA)
+ {
+ builder.AppendLine(" Certificate Data [Formatted]");
+ builder.AppendLine(" -------------------------");
+ if (entry.Certificate == null)
+ {
+ builder.AppendLine(" INVALID DATA FOUND");
+ }
+ else
+ {
+ var topLevelValues = AbstractSyntaxNotationOne.Parse(entry.Certificate, 0);
+ if (topLevelValues == null)
+ {
+ builder.AppendLine(" INVALID DATA FOUND");
+ builder.AppendLine(entry.Certificate, " Raw data");
+ }
+ else
+ {
+ foreach (TypeLengthValue tlv in topLevelValues)
+ {
+ string tlvString = tlv.Format(paddingLevel: 4);
+ builder.AppendLine(tlvString);
+ }
+ }
+ }
+ }
+ else
+ {
+ builder.AppendLine(" Certificate Data [Binary]");
+ builder.AppendLine(" -------------------------");
+ try
+ {
+ builder.AppendLine(entry.Certificate, " Raw data");
+ }
+ catch
+ {
+ builder.AppendLine(" [DATA TOO LARGE TO FORMAT]");
+ }
+ }
+
+ builder.AppendLine();
+ }
+ }
+
+ private static void Print(StringBuilder builder, DelayLoadDirectoryTable? table)
+ {
+ builder.AppendLine(" Delay-Load Directory Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null)
+ {
+ builder.AppendLine(" No delay-load directory table items");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(table.Attributes, " Attributes");
+ builder.AppendLine(table.Name, " Name RVA");
+ builder.AppendLine(table.ModuleHandle, " Module handle");
+ builder.AppendLine(table.DelayImportAddressTable, " Delay import address table RVA");
+ builder.AppendLine(table.DelayImportNameTable, " Delay import name table RVA");
+ builder.AppendLine(table.BoundDelayImportTable, " Bound delay import table RVA");
+ builder.AppendLine(table.UnloadDelayImportTable, " Unload delay import table RVA");
+ builder.AppendLine(table.TimeStamp, " Timestamp");
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, BaseRelocationBlock?[]? entries, SectionHeader?[]? table)
+ {
+ builder.AppendLine(" Base Relocation Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (entries == null || entries.Length == 0)
+ {
+ builder.AppendLine(" No base relocation table items");
+ builder.AppendLine();
+ return;
+ }
+
+ for (int i = 0; i < entries.Length; i++)
+ {
+ var baseRelocationTableEntry = entries[i];
+ builder.AppendLine($" Base Relocation Table Entry {i}");
+ if (baseRelocationTableEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(baseRelocationTableEntry.PageRVA, " Page RVA");
+ builder.AppendLine(baseRelocationTableEntry.PageRVA.ConvertVirtualAddress(table), " Page physical address");
+ builder.AppendLine(baseRelocationTableEntry.BlockSize, " Block size");
+
+ builder.AppendLine($" Base Relocation Table {i} Type and Offset Information:");
+ builder.AppendLine(" -------------------------");
+ if (baseRelocationTableEntry.TypeOffsetFieldEntries == null || baseRelocationTableEntry.TypeOffsetFieldEntries.Length == 0)
+ {
+ builder.AppendLine(" No base relocation table type and offset entries");
+ continue;
+ }
+
+ for (int j = 0; j < baseRelocationTableEntry.TypeOffsetFieldEntries.Length; j++)
+ {
+ var typeOffsetFieldEntry = baseRelocationTableEntry.TypeOffsetFieldEntries[j];
+ builder.AppendLine($" Type and Offset Entry {j}");
+ if (typeOffsetFieldEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine($" Type: {typeOffsetFieldEntry.BaseRelocationType} (0x{typeOffsetFieldEntry.BaseRelocationType:X})");
+ builder.AppendLine(typeOffsetFieldEntry.Offset, " Offset");
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, DebugTable? table)
+ {
+ builder.AppendLine(" Debug Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table?.DebugDirectoryTable == null || table.DebugDirectoryTable.Length == 0)
+ {
+ builder.AppendLine(" No debug table items");
+ builder.AppendLine();
+ return;
+ }
+
+ // TODO: If more sections added, model this after the Export Table
+ for (int i = 0; i < table.DebugDirectoryTable.Length; i++)
+ {
+ var entry = table.DebugDirectoryTable[i];
+ builder.AppendLine($" Debug Directory Table Entry {i}");
+ if (entry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(entry.Characteristics, " Characteristics");
+ builder.AppendLine(entry.TimeDateStamp, " Time/Date stamp");
+ builder.AppendLine(entry.MajorVersion, " Major version");
+ builder.AppendLine(entry.MinorVersion, " Minor version");
+ builder.AppendLine($" Debug type: {entry.DebugType} (0x{entry.DebugType:X})");
+ builder.AppendLine(entry.SizeOfData, " Size of data");
+ builder.AppendLine(entry.AddressOfRawData, " Address of raw data");
+ builder.AppendLine(entry.PointerToRawData, " Pointer to raw data");
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ExportTable? table)
+ {
+ builder.AppendLine(" Export Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null)
+ {
+ builder.AppendLine(" No export table");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine(" Export Directory Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.ExportDirectoryTable == null)
+ {
+ builder.AppendLine(" No export directory table");
+ }
+ else
+ {
+ builder.AppendLine(table.ExportDirectoryTable.ExportFlags, " Export flags");
+ builder.AppendLine(table.ExportDirectoryTable.TimeDateStamp, " Time/Date stamp");
+ builder.AppendLine(table.ExportDirectoryTable.MajorVersion, " Major version");
+ builder.AppendLine(table.ExportDirectoryTable.MinorVersion, " Minor version");
+ builder.AppendLine(table.ExportDirectoryTable.NameRVA, " Name RVA");
+ builder.AppendLine(table.ExportDirectoryTable.Name, " Name");
+ builder.AppendLine(table.ExportDirectoryTable.OrdinalBase, " Ordinal base");
+ builder.AppendLine(table.ExportDirectoryTable.AddressTableEntries, " Address table entries");
+ builder.AppendLine(table.ExportDirectoryTable.NumberOfNamePointers, " Number of name pointers");
+ builder.AppendLine(table.ExportDirectoryTable.ExportAddressTableRVA, " Export address table RVA");
+ builder.AppendLine(table.ExportDirectoryTable.NamePointerRVA, " Name pointer table RVA");
+ builder.AppendLine(table.ExportDirectoryTable.OrdinalTableRVA, " Ordinal table RVA");
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Export Address Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.ExportAddressTable == null || table.ExportAddressTable.Length == 0)
+ {
+ builder.AppendLine(" No export address table items");
+ }
+ else
+ {
+ for (int i = 0; i < table.ExportAddressTable.Length; i++)
+ {
+ var exportAddressTableEntry = table.ExportAddressTable[i];
+ builder.AppendLine($" Export Address Table Entry {i}");
+ if (exportAddressTableEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(exportAddressTableEntry.ExportRVA, " Export RVA / Forwarder RVA");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Name Pointer Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.NamePointerTable?.Pointers == null || table.NamePointerTable.Pointers.Length == 0)
+ {
+ builder.AppendLine(" No name pointer table items");
+ }
+ else
+ {
+ for (int i = 0; i < table.NamePointerTable.Pointers.Length; i++)
+ {
+ var namePointerTableEntry = table.NamePointerTable.Pointers[i];
+ builder.AppendLine($" Name Pointer Table Entry {i}");
+ builder.AppendLine(namePointerTableEntry, " Pointer");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Ordinal Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.OrdinalTable?.Indexes == null || table.OrdinalTable.Indexes.Length == 0)
+ {
+ builder.AppendLine(" No ordinal table items");
+ }
+ else
+ {
+ for (int i = 0; i < table.OrdinalTable.Indexes.Length; i++)
+ {
+ var ordinalTableEntry = table.OrdinalTable.Indexes[i];
+ builder.AppendLine($" Ordinal Table Entry {i}");
+ builder.AppendLine(ordinalTableEntry, " Index");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Export Name Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.ExportNameTable?.Strings == null || table.ExportNameTable.Strings.Length == 0)
+ {
+ builder.AppendLine(" No export name table items");
+ }
+ else
+ {
+ for (int i = 0; i < table.ExportNameTable.Strings.Length; i++)
+ {
+ var exportNameTableEntry = table.ExportNameTable.Strings[i];
+ builder.AppendLine($" Export Name Table Entry {i}");
+ builder.AppendLine(exportNameTableEntry, " String");
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ImportTable? table, SectionHeader?[]? sectionTable)
+ {
+ builder.AppendLine(" Import Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null)
+ {
+ builder.AppendLine(" No import table");
+ builder.AppendLine();
+ return;
+ }
+
+ builder.AppendLine();
+ builder.AppendLine(" Import Directory Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.ImportDirectoryTable == null || table.ImportDirectoryTable.Length == 0)
+ {
+ builder.AppendLine(" No import directory table items");
+ }
+ else
+ {
+ for (int i = 0; i < table.ImportDirectoryTable.Length; i++)
+ {
+ var importDirectoryTableEntry = table.ImportDirectoryTable[i];
+ builder.AppendLine($" Import Directory Table Entry {i}");
+ if (importDirectoryTableEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(importDirectoryTableEntry.ImportLookupTableRVA, " Import lookup table RVA");
+ builder.AppendLine(importDirectoryTableEntry.ImportLookupTableRVA.ConvertVirtualAddress(sectionTable), " Import lookup table Physical Address");
+ builder.AppendLine(importDirectoryTableEntry.TimeDateStamp, " Time/Date stamp");
+ builder.AppendLine(importDirectoryTableEntry.ForwarderChain, " Forwarder chain");
+ builder.AppendLine(importDirectoryTableEntry.NameRVA, " Name RVA");
+ builder.AppendLine(importDirectoryTableEntry.Name, " Name");
+ builder.AppendLine(importDirectoryTableEntry.ImportAddressTableRVA, " Import address table RVA");
+ builder.AppendLine(importDirectoryTableEntry.ImportAddressTableRVA.ConvertVirtualAddress(sectionTable), " Import address table Physical Address");
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Import Lookup Tables Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.ImportLookupTables == null || table.ImportLookupTables.Count == 0)
+ {
+ builder.AppendLine(" No import lookup tables");
+ }
+ else
+ {
+ foreach (var kvp in table.ImportLookupTables)
+ {
+ int index = kvp.Key;
+ var importLookupTable = kvp.Value;
+
+ builder.AppendLine();
+ builder.AppendLine($" Import Lookup Table {index} Information:");
+ builder.AppendLine(" -------------------------");
+ if (importLookupTable == null || importLookupTable.Length == 0)
+ {
+ builder.AppendLine(" No import lookup table items");
+ continue;
+ }
+
+ for (int i = 0; i < importLookupTable.Length; i++)
+ {
+ var importLookupTableEntry = importLookupTable[i];
+ builder.AppendLine($" Import Lookup Table {index} Entry {i}");
+ if (importLookupTableEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(importLookupTableEntry.OrdinalNameFlag, " Ordinal/Name flag");
+ if (importLookupTableEntry.OrdinalNameFlag)
+ {
+ builder.AppendLine(importLookupTableEntry.OrdinalNumber, " Ordinal number");
+ }
+ else
+ {
+ builder.AppendLine(importLookupTableEntry.HintNameTableRVA, " Hint/Name table RVA");
+ builder.AppendLine(importLookupTableEntry.HintNameTableRVA.ConvertVirtualAddress(sectionTable), " Hint/Name table Physical Address");
+ }
+ }
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Import Address Tables Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.ImportAddressTables == null || table.ImportAddressTables.Count == 0)
+ {
+ builder.AppendLine(" No import address tables");
+ }
+ else
+ {
+ foreach (var kvp in table.ImportAddressTables)
+ {
+ int index = kvp.Key;
+ var importAddressTable = kvp.Value;
+
+ builder.AppendLine();
+ builder.AppendLine($" Import Address Table {index} Information:");
+ builder.AppendLine(" -------------------------");
+ if (importAddressTable == null || importAddressTable.Length == 0)
+ {
+ builder.AppendLine(" No import address table items");
+ continue;
+ }
+
+ for (int i = 0; i < importAddressTable.Length; i++)
+ {
+ var importAddressTableEntry = importAddressTable[i];
+ builder.AppendLine($" Import Address Table {index} Entry {i}");
+ if (importAddressTableEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(importAddressTableEntry.OrdinalNameFlag, " Ordinal/Name flag");
+ if (importAddressTableEntry.OrdinalNameFlag)
+ {
+ builder.AppendLine(importAddressTableEntry.OrdinalNumber, " Ordinal number");
+ }
+ else
+ {
+ builder.AppendLine(importAddressTableEntry.HintNameTableRVA, " Hint/Name table RVA");
+ builder.AppendLine(importAddressTableEntry.HintNameTableRVA.ConvertVirtualAddress(sectionTable), " Hint/Name table Physical Address");
+ }
+ }
+ }
+ }
+ builder.AppendLine();
+
+ builder.AppendLine(" Hint/Name Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table.HintNameTable == null || table.HintNameTable.Length == 0)
+ {
+ builder.AppendLine(" No hint/name table items");
+ }
+ else
+ {
+ for (int i = 0; i < table.HintNameTable.Length; i++)
+ {
+ var hintNameTableEntry = table.HintNameTable[i];
+ builder.AppendLine($" Hint/Name Table Entry {i}");
+ if (hintNameTableEntry == null)
+ {
+ builder.AppendLine(" [NULL]");
+ continue;
+ }
+
+ builder.AppendLine(hintNameTableEntry.Hint, " Hint");
+ builder.AppendLine(hintNameTableEntry.Name, " Name");
+ }
+ }
+ builder.AppendLine();
+ }
+
+ private static void Print(StringBuilder builder, ResourceDirectoryTable? table)
+ {
+ builder.AppendLine(" Resource Directory Table Information:");
+ builder.AppendLine(" -------------------------");
+ if (table == null)
+ {
+ builder.AppendLine(" No resource directory table items");
+ builder.AppendLine();
+ return;
+ }
+
+ Print(table, level: 0, types: [], builder);
+ builder.AppendLine();
+ }
+
+ private static void Print(ResourceDirectoryTable table, int level, List