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 types, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + builder.AppendLine(level, $"{padding}Table level"); + builder.AppendLine(table.Characteristics, $"{padding}Characteristics"); + builder.AppendLine(table.TimeDateStamp, $"{padding}Time/Date stamp"); + builder.AppendLine(table.MajorVersion, $"{padding}Major version"); + builder.AppendLine(table.MinorVersion, $"{padding}Minor version"); + builder.AppendLine(table.NumberOfNameEntries, $"{padding}Number of name entries"); + builder.AppendLine(table.NumberOfIDEntries, $"{padding}Number of ID entries"); + builder.AppendLine(); + + builder.AppendLine($"{padding}Entries"); + builder.AppendLine($"{padding}-------------------------"); + if (table.NumberOfNameEntries == 0 && table.NumberOfIDEntries == 0) + { + builder.AppendLine($"{padding}No entries"); + builder.AppendLine(); + } + else + { + if (table.Entries == null) + return; + + for (int i = 0; i < table.Entries.Length; i++) + { + var entry = table.Entries[i]; + if (entry == null) + continue; + + var newTypes = new List(types ?? []); + if (entry.Name?.UnicodeString != null) + newTypes.Add(Encoding.UTF8.GetString(entry.Name.UnicodeString)); + else + newTypes.Add(entry.IntegerID); + + PrintResourceDirectoryEntry(entry, level + 1, newTypes, builder); + } + } + } + + private static void PrintResourceDirectoryEntry(ResourceDirectoryEntry entry, int level, List types, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + builder.AppendLine(level, $"{padding}Item level"); + if (entry.NameOffset != default) + { + builder.AppendLine(entry.NameOffset, $"{padding}Name offset"); + builder.AppendLine(entry.Name?.UnicodeString, $"{padding}Name ({entry.Name?.Length ?? 0})", Encoding.Unicode); + } + else + { + builder.AppendLine(entry.IntegerID, $"{padding}Integer ID"); + } + + if (entry.DataEntry != null) + PrintResourceDataEntry(entry.DataEntry, level: level + 1, types, builder); + else if (entry.Subdirectory != null) + Print(entry.Subdirectory, level: level + 1, types, builder); + } + + private static void PrintResourceDataEntry(ResourceDataEntry entry, int level, List types, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + // TODO: Use ordered list of base types to determine the shape of the data + builder.AppendLine($"{padding}Base types: {string.Join(", ", types.Select(t => t.ToString()).ToArray())}"); + + builder.AppendLine(level, $"{padding}Entry level"); + builder.AppendLine(entry.DataRVA, $"{padding}Data RVA"); + builder.AppendLine(entry.Size, $"{padding}Size"); + builder.AppendLine(entry.Codepage, $"{padding}Codepage"); + builder.AppendLine(entry.Reserved, $"{padding}Reserved"); + + // TODO: Print out per-type data + if (types != null && types.Count > 0 && types[0] is uint resourceType) + { + switch ((ResourceType)resourceType) + { + case ResourceType.RT_CURSOR: + PrintResourceRT_CURSOR(entry, level, builder); + break; + case ResourceType.RT_BITMAP: + PrintResourceRT_BITMAP(entry, level, builder); + break; + case ResourceType.RT_ICON: + PrintResourceRT_ICON(entry, level, builder); + break; + case ResourceType.RT_MENU: + PrintResourceRT_MENU(entry, level, builder); + break; + case ResourceType.RT_DIALOG: + PrintResourceRT_DIALOG(entry, level, builder); + break; + case ResourceType.RT_STRING: + PrintResourceRT_STRING(entry, level, builder); + break; + case ResourceType.RT_FONTDIR: + PrintResourceRT_FONTDIR(entry, level, builder); + break; + case ResourceType.RT_FONT: + PrintResourceRT_FONT(entry, level, builder); + break; + case ResourceType.RT_ACCELERATOR: + PrintResourceRT_ACCELERATOR(entry, level, builder); + break; + case ResourceType.RT_RCDATA: + PrintResourceRT_RCDATA(entry, level, builder); + break; + case ResourceType.RT_MESSAGETABLE: + PrintResourceRT_MESSAGETABLE(entry, level, builder); + break; + case ResourceType.RT_GROUP_CURSOR: + PrintResourceRT_GROUP_CURSOR(entry, level, builder); + break; + case ResourceType.RT_GROUP_ICON: + PrintResourceRT_GROUP_ICON(entry, level, builder); + break; + case ResourceType.RT_VERSION: + PrintResourceRT_VERSION(entry, level, builder); + break; + case ResourceType.RT_DLGINCLUDE: + PrintResourceRT_DLGINCLUDE(entry, level, builder); + break; + case ResourceType.RT_PLUGPLAY: + PrintResourceRT_PLUGPLAY(entry, level, builder); + break; + case ResourceType.RT_VXD: + PrintResourceRT_VXD(entry, level, builder); + break; + case ResourceType.RT_ANICURSOR: + PrintResourceRT_ANICURSOR(entry, level, builder); + break; + case ResourceType.RT_ANIICON: + PrintResourceRT_ANIICON(entry, level, builder); + break; + case ResourceType.RT_HTML: + PrintResourceRT_HTML(entry, level, builder); + break; + case ResourceType.RT_MANIFEST: + PrintResourceRT_MANIFEST(entry, level, builder); + break; + default: + PrintResourceUNKNOWN(entry, level, types[0], builder); + break; + } + } + else if (types != null && types.Count > 0 && types[0] is string resourceString) + { + PrintResourceUNKNOWN(entry, level, types[0], builder); + } + + builder.AppendLine(); + } + + private static void PrintResourceRT_CURSOR(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Hardware-dependent cursor resource found, not parsed yet"); + } + + private static void PrintResourceRT_BITMAP(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Bitmap resource found, not parsed yet"); + } + + private static void PrintResourceRT_ICON(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Hardware-dependent icon resource found, not parsed yet"); + } + + private static void PrintResourceRT_MENU(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + MenuResource? menu = null; + try { menu = entry.AsMenu(); } catch { } + if (menu == null) + { + builder.AppendLine($"{padding}Menu resource found, but malformed"); + return; + } + + if (menu.MenuHeader != null) + { + builder.AppendLine(menu.MenuHeader.Version, $"{padding}Version"); + builder.AppendLine(menu.MenuHeader.HeaderSize, $"{padding}Header size"); + builder.AppendLine(); + builder.AppendLine($"{padding}Menu items"); + builder.AppendLine($"{padding}-------------------------"); + if (menu.MenuItems == null || menu.MenuItems.Length == 0) + { + builder.AppendLine($"{padding}No menu items"); + return; + } + + for (int i = 0; i < menu.MenuItems.Length; i++) + { + var menuItem = menu.MenuItems[i]; + builder.AppendLine($"{padding}Menu item {i}"); + if (menuItem == null) + { + builder.AppendLine($"{padding} [NULL]"); + continue; + } + + if (menuItem is NormalMenuItem normalMenuItem) + { + builder.AppendLine($"{padding} Resource info: {normalMenuItem.NormalResInfo} (0x{normalMenuItem.NormalResInfo:X})"); + builder.AppendLine(normalMenuItem.NormalMenuText, $"{padding} Menu text"); + } + else if (menuItem is PopupMenuItem popupMenuItem) + { + builder.AppendLine($"{padding} Item type: {popupMenuItem.PopupItemType} (0x{popupMenuItem.PopupItemType:X})"); + builder.AppendLine($"{padding} State: {popupMenuItem.PopupState} (0x{popupMenuItem.PopupState:X})"); + builder.AppendLine(popupMenuItem.PopupID, $"{padding} ID"); + builder.AppendLine($"{padding} Resource info: {popupMenuItem.PopupResInfo} (0x{popupMenuItem.PopupResInfo:X})"); + builder.AppendLine(popupMenuItem.PopupMenuText, $"{padding} Menu text"); + } + } + } + else if (menu.ExtendedMenuHeader != null) + { + builder.AppendLine(menu.ExtendedMenuHeader.Version, $"{padding}Version"); + builder.AppendLine(menu.ExtendedMenuHeader.Offset, $"{padding}Offset"); + builder.AppendLine(menu.ExtendedMenuHeader.HelpID, $"{padding}Help ID"); + builder.AppendLine(); + builder.AppendLine($"{padding}Menu items"); + builder.AppendLine($"{padding}-------------------------"); + if (menu.ExtendedMenuHeader.Offset == 0 + || menu.ExtendedMenuItems == null + || menu.ExtendedMenuItems.Length == 0) + { + builder.AppendLine($"{padding}No menu items"); + return; + } + + for (int i = 0; i < menu.ExtendedMenuItems.Length; i++) + { + var menuItem = menu.ExtendedMenuItems[i]; + + builder.AppendLine($"{padding}Menu item {i}"); + if (menuItem == null) + { + builder.AppendLine($"{padding} [NULL]"); + continue; + } + + builder.AppendLine($"{padding} Item type: {menuItem.ItemType} (0x{menuItem.ItemType:X})"); + builder.AppendLine($"{padding} State: {menuItem.State} (0x{menuItem.State:X})"); + builder.AppendLine(menuItem.ID, $"{padding} ID"); + builder.AppendLine($"{padding} Flags: {menuItem.Flags} (0x{menuItem.Flags:X})"); + builder.AppendLine(menuItem.MenuText, $"{padding} Menu text"); + } + } + else + { + builder.AppendLine($"{padding}Menu resource found, but malformed"); + } + } + + private static void PrintResourceRT_DIALOG(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + DialogBoxResource? dialogBox = null; + try { dialogBox = entry.AsDialogBox(); } catch { } + if (dialogBox == null) + { + builder.AppendLine($"{padding}Dialog box resource found, but malformed"); + return; + } + + if (dialogBox.DialogTemplate != null) + { + builder.AppendLine($"{padding}Style: {dialogBox.DialogTemplate.Style} (0x{dialogBox.DialogTemplate.Style:X})"); + builder.AppendLine($"{padding}Extended style: {dialogBox.DialogTemplate.ExtendedStyle} (0x{dialogBox.DialogTemplate.ExtendedStyle:X})"); + builder.AppendLine(dialogBox.DialogTemplate.ItemCount, $"{padding}Item count"); + builder.AppendLine(dialogBox.DialogTemplate.PositionX, $"{padding}X-coordinate of upper-left corner"); + builder.AppendLine(dialogBox.DialogTemplate.PositionY, $"{padding}Y-coordinate of upper-left corner"); + builder.AppendLine(dialogBox.DialogTemplate.WidthX, $"{padding}Width of the dialog box"); + builder.AppendLine(dialogBox.DialogTemplate.HeightY, $"{padding}Height of the dialog box"); + builder.AppendLine(dialogBox.DialogTemplate.MenuResource, $"{padding}Menu resource"); + builder.AppendLine(dialogBox.DialogTemplate.MenuResourceOrdinal, $"{padding}Menu resource ordinal"); + builder.AppendLine(dialogBox.DialogTemplate.ClassResource, $"{padding}Class resource"); + builder.AppendLine(dialogBox.DialogTemplate.ClassResourceOrdinal, $"{padding}Class resource ordinal"); + builder.AppendLine(dialogBox.DialogTemplate.TitleResource, $"{padding}Title resource"); + builder.AppendLine(dialogBox.DialogTemplate.PointSizeValue, $"{padding}Point size value"); + builder.AppendLine(dialogBox.DialogTemplate.Typeface, $"{padding}Typeface"); + builder.AppendLine(); + builder.AppendLine($"{padding}Dialog item templates"); + builder.AppendLine($"{padding}-------------------------"); + if (dialogBox.DialogTemplate.ItemCount == 0 + || dialogBox.DialogItemTemplates == null + || dialogBox.DialogItemTemplates.Length == 0) + { + builder.AppendLine($"{padding}No dialog item templates"); + return; + } + + for (int i = 0; i < dialogBox.DialogItemTemplates.Length; i++) + { + var dialogItemTemplate = dialogBox.DialogItemTemplates[i]; + builder.AppendLine($"{padding}Dialog item template {i}"); + if (dialogItemTemplate == null) + { + builder.AppendLine($"{padding} [NULL]"); + continue; + } + + builder.AppendLine($"{padding} Style: {dialogItemTemplate.Style} (0x{dialogItemTemplate.Style:X})"); + builder.AppendLine($"{padding} Extended style: {dialogItemTemplate.ExtendedStyle} (0x{dialogItemTemplate.ExtendedStyle:X})"); + builder.AppendLine(dialogItemTemplate.PositionX, $"{padding} X-coordinate of upper-left corner"); + builder.AppendLine(dialogItemTemplate.PositionY, $"{padding} Y-coordinate of upper-left corner"); + builder.AppendLine(dialogItemTemplate.WidthX, $"{padding} Width of the control"); + builder.AppendLine(dialogItemTemplate.HeightY, $"{padding} Height of the control"); + builder.AppendLine(dialogItemTemplate.ID, $"{padding} ID"); + builder.AppendLine(dialogItemTemplate.ClassResource, $"{padding} Class resource"); + builder.AppendLine($"{padding} Class resource ordinal: {dialogItemTemplate.ClassResourceOrdinal} (0x{dialogItemTemplate.ClassResourceOrdinal:X})"); + builder.AppendLine(dialogItemTemplate.TitleResource, $"{padding} Title resource"); + builder.AppendLine(dialogItemTemplate.TitleResourceOrdinal, $"{padding} Title resource ordinal"); + builder.AppendLine(dialogItemTemplate.CreationDataSize, $"{padding} Creation data size"); + if (dialogItemTemplate.CreationData != null && dialogItemTemplate.CreationData.Length != 0) + builder.AppendLine(dialogItemTemplate.CreationData, $"{padding} Creation data"); + else + builder.AppendLine($"{padding} Creation data: [EMPTY]"); + } + } + else if (dialogBox.ExtendedDialogTemplate != null) + { + builder.AppendLine(dialogBox.ExtendedDialogTemplate.Version, $"{padding}Version"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.Signature, $"{padding}Signature"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.HelpID, $"{padding}Help ID"); + builder.AppendLine($"{padding}Extended style: {dialogBox.ExtendedDialogTemplate.ExtendedStyle} (0x{dialogBox.ExtendedDialogTemplate.ExtendedStyle:X})"); + builder.AppendLine($"{padding}Style: {dialogBox.ExtendedDialogTemplate.Style} (0x{dialogBox.ExtendedDialogTemplate.Style:X})"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.DialogItems, $"{padding}Item count"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.PositionX, $"{padding}X-coordinate of upper-left corner"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.PositionY, $"{padding}Y-coordinate of upper-left corner"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.WidthX, $"{padding}Width of the dialog box"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.HeightY, $"{padding}Height of the dialog box"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.MenuResource, $"{padding}Menu resource"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.MenuResourceOrdinal, $"{padding}Menu resource ordinal"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.ClassResource, $"{padding}Class resource"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.ClassResourceOrdinal, $"{padding}Class resource ordinal"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.TitleResource, $"{padding}Title resource"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.PointSize, $"{padding}Point size"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.Weight, $"{padding}Weight"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.Italic, $"{padding}Italic"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.CharSet, $"{padding}Character set"); + builder.AppendLine(dialogBox.ExtendedDialogTemplate.Typeface, $"{padding}Typeface"); + builder.AppendLine(); + builder.AppendLine($"{padding}Dialog item templates"); + builder.AppendLine($"{padding}-------------------------"); + if (dialogBox.ExtendedDialogTemplate.DialogItems == 0 + || dialogBox.ExtendedDialogItemTemplates == null + || dialogBox.ExtendedDialogItemTemplates.Length == 0) + { + builder.AppendLine($"{padding}No dialog item templates"); + return; + } + + for (int i = 0; i < dialogBox.ExtendedDialogItemTemplates.Length; i++) + { + var dialogItemTemplate = dialogBox.ExtendedDialogItemTemplates[i]; + builder.AppendLine($"{padding}Dialog item template {i}"); + if (dialogItemTemplate == null) + { + builder.AppendLine($"{padding} [NULL]"); + continue; + } + + builder.AppendLine(dialogItemTemplate.HelpID, $"{padding} Help ID"); + builder.AppendLine($"{padding} Extended style: {dialogItemTemplate.ExtendedStyle} (0x{dialogItemTemplate.ExtendedStyle:X})"); + builder.AppendLine($"{padding} Style: {dialogItemTemplate.Style} (0x{dialogItemTemplate.Style:X})"); + builder.AppendLine(dialogItemTemplate.PositionX, $"{padding} X-coordinate of upper-left corner"); + builder.AppendLine(dialogItemTemplate.PositionY, $"{padding} Y-coordinate of upper-left corner"); + builder.AppendLine(dialogItemTemplate.WidthX, $"{padding} Width of the control"); + builder.AppendLine(dialogItemTemplate.HeightY, $"{padding} Height of the control"); + builder.AppendLine(dialogItemTemplate.ID, $"{padding} ID"); + builder.AppendLine(dialogItemTemplate.ClassResource, $"{padding} Class resource"); + builder.AppendLine($"{padding} Class resource ordinal: {dialogItemTemplate.ClassResourceOrdinal} (0x{dialogItemTemplate.ClassResourceOrdinal:X})"); + builder.AppendLine(dialogItemTemplate.TitleResource, $"{padding} Title resource"); + builder.AppendLine(dialogItemTemplate.TitleResourceOrdinal, $"{padding} Title resource ordinal"); + builder.AppendLine(dialogItemTemplate.CreationDataSize, $"{padding} Creation data size"); + if (dialogItemTemplate.CreationData != null && dialogItemTemplate.CreationData.Length != 0) + builder.AppendLine(dialogItemTemplate.CreationData, $"{padding} Creation data"); + else + builder.AppendLine($"{padding} Creation data: [EMPTY]"); + } + } + else + { + builder.AppendLine($"{padding}Dialog box resource found, but malformed"); + } + } + + private static void PrintResourceRT_STRING(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + Dictionary? stringTable = null; + try { stringTable = entry.AsStringTable(); } catch { } + if (stringTable == null) + { + builder.AppendLine($"{padding}String table resource found, but malformed"); + return; + } + + foreach (var kvp in stringTable) + { + int index = kvp.Key; + string? stringValue = kvp.Value; + builder.AppendLine(stringValue, $"{padding}String entry {index}"); + } + } + + private static void PrintResourceRT_FONTDIR(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Font directory resource found, not parsed yet"); + } + + private static void PrintResourceRT_FONT(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Font resource found, not parsed yet"); + } + + private static void PrintResourceRT_ACCELERATOR(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + AcceleratorTableEntry[]? acceleratorTable = null; + try { acceleratorTable = entry.AsAcceleratorTableResource(); } catch { } + if (acceleratorTable == null) + { + builder.AppendLine($"{padding}Accelerator table resource found, but malformed"); + return; + } + + for (int i = 0; i < acceleratorTable.Length; i++) + { + var acceleratorTableEntry = acceleratorTable[i]; + builder.AppendLine($"{padding}Accelerator Table Entry {i}:"); + builder.AppendLine($"{padding} Flags: {acceleratorTableEntry.Flags} (0x{acceleratorTableEntry.Flags:X})"); + builder.AppendLine(acceleratorTableEntry.Ansi, $"{padding} Ansi"); + builder.AppendLine(acceleratorTableEntry.Id, $"{padding} Id"); + builder.AppendLine(acceleratorTableEntry.Padding, $"{padding} Padding"); + } + } + + private static void PrintResourceRT_RCDATA(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Application-defined resource found, not parsed yet"); + + // Then print the data, if needed + if (entry.Data == null) + { + builder.AppendLine($"{padding}Data: [NULL] (This may indicate a very large resource)"); + } + else + { + int offset = 0; + byte[]? magic = entry.Data.ReadBytes(ref offset, Math.Min(entry.Data.Length, 16)); + + if (magic == null) + { + // No-op + } + else if (magic[0] == 0x4D && magic[1] == 0x5A) + { + builder.AppendLine($"{padding}Data: [Embedded Executable File]"); // TODO: Parse this out and print separately + } + else if (magic[0] == 0x4D && magic[1] == 0x53 && magic[2] == 0x46 && magic[3] == 0x54) + { + builder.AppendLine($"{padding}Data: [Embedded OLE Library File]"); // TODO: Parse this out and print separately + } + else + { + builder.AppendLine(magic, $"{padding}Data"); + + //if (entry.Data != null) + // builder.AppendLine(entry.Data, $"{padding}Value (Byte Data)"); + //if (entry.Data != null) + // builder.AppendLine(Encoding.ASCII.GetString(entry.Data), $"{padding}Value (ASCII)"); + //if (entry.Data != null) + // builder.AppendLine(Encoding.UTF8.GetString(entry.Data), $"{padding}Value (UTF-8)"); + //if (entry.Data != null) + // builder.AppendLine(Encoding.Unicode.GetString(entry.Data), $"{padding}Value (Unicode)"); + } + } + } + + private static void PrintResourceRT_MESSAGETABLE(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + MessageResourceData? messageTable = null; + try { messageTable = entry.AsMessageResourceData(); } catch { } + if (messageTable == null) + { + builder.AppendLine($"{padding}Message resource data found, but malformed"); + return; + } + + builder.AppendLine(messageTable.NumberOfBlocks, $"{padding}Number of blocks"); + builder.AppendLine(); + builder.AppendLine($"{padding}Message resource blocks"); + builder.AppendLine($"{padding}-------------------------"); + if (messageTable.NumberOfBlocks == 0 + || messageTable.Blocks == null + || messageTable.Blocks.Length == 0) + { + builder.AppendLine($"{padding}No message resource blocks"); + } + else + { + for (int i = 0; i < messageTable.Blocks.Length; i++) + { + var messageResourceBlock = messageTable.Blocks[i]; + builder.AppendLine($"{padding}Message resource block {i}"); + if (messageResourceBlock == null) + { + builder.AppendLine($"{padding} [NULL]"); + continue; + } + + builder.AppendLine(messageResourceBlock.LowId, $"{padding} Low ID"); + builder.AppendLine(messageResourceBlock.HighId, $"{padding} High ID"); + builder.AppendLine(messageResourceBlock.OffsetToEntries, $"{padding} Offset to entries"); + } + } + builder.AppendLine(); + + builder.AppendLine($"{padding}Message resource entries"); + builder.AppendLine($"{padding}-------------------------"); + if (messageTable.Entries == null || messageTable.Entries.Count == 0) + { + builder.AppendLine($"{padding}No message resource entries"); + } + else + { + foreach (var kvp in messageTable.Entries) + { + uint index = kvp.Key; + var messageResourceEntry = kvp.Value; + builder.AppendLine($"{padding}Message resource entry {index}"); + if (messageResourceEntry == null) + { + builder.AppendLine($"{padding} [NULL]"); + continue; + } + + builder.AppendLine(messageResourceEntry.Length, $"{padding} Length"); + builder.AppendLine(messageResourceEntry.Flags, $"{padding} Flags"); + builder.AppendLine(messageResourceEntry.Text, $"{padding} Text"); + } + } + } + + private static void PrintResourceRT_GROUP_CURSOR(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Hardware-independent cursor resource found, not parsed yet"); + } + + private static void PrintResourceRT_GROUP_ICON(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Hardware-independent icon resource found, not parsed yet"); + } + + private static void PrintResourceRT_VERSION(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + VersionInfo? versionInfo = null; + try { versionInfo = entry.AsVersionInfo(); } catch { } + if (versionInfo == null) + { + builder.AppendLine($"{padding}Version info resource found, but malformed"); + return; + } + + builder.AppendLine(versionInfo.Length, $"{padding}Length"); + builder.AppendLine(versionInfo.ValueLength, $"{padding}Value length"); + builder.AppendLine($"{padding}Resource type: {versionInfo.ResourceType} (0x{versionInfo.ResourceType:X})"); + builder.AppendLine(versionInfo.Key, $"{padding}Key"); + if (versionInfo.ValueLength != 0 && versionInfo.Value != null) + { + builder.AppendLine(versionInfo.Value.Signature, $"{padding}[Fixed File Info] Signature"); + builder.AppendLine(versionInfo.Value.StrucVersion, $"{padding}[Fixed File Info] Struct version"); + builder.AppendLine(versionInfo.Value.FileVersionMS, $"{padding}[Fixed File Info] File version (MS)"); + builder.AppendLine(versionInfo.Value.FileVersionLS, $"{padding}[Fixed File Info] File version (LS)"); + builder.AppendLine(versionInfo.Value.ProductVersionMS, $"{padding}[Fixed File Info] Product version (MS)"); + builder.AppendLine(versionInfo.Value.ProductVersionLS, $"{padding}[Fixed File Info] Product version (LS)"); + builder.AppendLine(versionInfo.Value.FileFlagsMask, $"{padding}[Fixed File Info] File flags mask"); + builder.AppendLine($"{padding}[Fixed File Info] File flags: {versionInfo.Value.FileFlags} (0x{versionInfo.Value.FileFlags:X})"); + builder.AppendLine($"{padding}[Fixed File Info] File OS: {versionInfo.Value.FileOS} (0x{versionInfo.Value.FileOS:X})"); + builder.AppendLine($"{padding}[Fixed File Info] Type: {versionInfo.Value.FileType} (0x{versionInfo.Value.FileType:X})"); + builder.AppendLine($"{padding}[Fixed File Info] Subtype: {versionInfo.Value.FileSubtype} (0x{versionInfo.Value.FileSubtype:X})"); + builder.AppendLine(versionInfo.Value.FileDateMS, $"{padding}[Fixed File Info] File date (MS)"); + builder.AppendLine(versionInfo.Value.FileDateLS, $"{padding}[Fixed File Info] File date (LS)"); + } + + if (versionInfo.StringFileInfo != null) + { + builder.AppendLine(versionInfo.StringFileInfo.Length, $"{padding}[String File Info] Length"); + builder.AppendLine(versionInfo.StringFileInfo.ValueLength, $"{padding}[String File Info] Value length"); + builder.AppendLine($"{padding}[String File Info] Resource type: {versionInfo.StringFileInfo.ResourceType} (0x{versionInfo.StringFileInfo.ResourceType:X})"); + builder.AppendLine(versionInfo.StringFileInfo.Key, $"{padding}[String File Info] Key"); + builder.AppendLine($"{padding}Children:"); + builder.AppendLine($"{padding}-------------------------"); + if (versionInfo.StringFileInfo.Children == null || versionInfo.StringFileInfo.Children.Length == 0) + { + builder.AppendLine($"{padding}No string file info children"); + } + else + { + for (int i = 0; i < versionInfo.StringFileInfo.Children.Length; i++) + { + var stringFileInfoChildEntry = versionInfo.StringFileInfo.Children[i]; + if (stringFileInfoChildEntry == null) + { + builder.AppendLine($"{padding} [String Table {i}] [NULL]"); + continue; + } + + builder.AppendLine(stringFileInfoChildEntry.Length, $"{padding} [String Table {i}] Length"); + builder.AppendLine(stringFileInfoChildEntry.ValueLength, $"{padding} [String Table {i}] Value length"); + builder.AppendLine($"{padding} [String Table {i}] ResourceType: {stringFileInfoChildEntry.ResourceType} (0x{stringFileInfoChildEntry.ResourceType:X})"); + builder.AppendLine(stringFileInfoChildEntry.Key, $"{padding} [String Table {i}] Key"); + builder.AppendLine($"{padding} [String Table {i}] Children:"); + builder.AppendLine($"{padding} -------------------------"); + if (stringFileInfoChildEntry.Children == null || stringFileInfoChildEntry.Children.Length == 0) + { + builder.AppendLine($"{padding} No string table {i} children"); + } + else + { + for (int j = 0; j < stringFileInfoChildEntry.Children.Length; j++) + { + var stringDataEntry = stringFileInfoChildEntry.Children[j]; + if (stringDataEntry == null) + { + builder.AppendLine($"{padding} [String Data {j}] [NULL]"); + continue; + } + + builder.AppendLine(stringDataEntry.Length, $"{padding} [String Data {j}] Length"); + builder.AppendLine(stringDataEntry.ValueLength, $"{padding} [String Data {j}] Value length"); + builder.AppendLine($"{padding} [String Data {j}] ResourceType: {stringDataEntry.ResourceType} (0x{stringDataEntry.ResourceType:X})"); + builder.AppendLine(stringDataEntry.Key, $"{padding} [String Data {j}] Key"); + builder.AppendLine(stringDataEntry.Value, $"{padding} [String Data {j}] Value"); + } + } + } + } + } + + if (versionInfo.VarFileInfo != null) + { + builder.AppendLine(versionInfo.VarFileInfo.Length, $"{padding}[Var File Info] Length"); + builder.AppendLine(versionInfo.VarFileInfo.ValueLength, $"{padding}[Var File Info] Value length"); + builder.AppendLine($"{padding}[Var File Info] Resource type: {versionInfo.VarFileInfo.ResourceType} (0x{versionInfo.VarFileInfo.ResourceType:X})"); + builder.AppendLine(versionInfo.VarFileInfo.Key, $"{padding}[Var File Info] Key"); + builder.AppendLine($"{padding}Children:"); + builder.AppendLine($"{padding}-------------------------"); + if (versionInfo.VarFileInfo.Children == null || versionInfo.VarFileInfo.Children.Length == 0) + { + builder.AppendLine($"{padding}No var file info children"); + } + else + { + for (int i = 0; i < versionInfo.VarFileInfo.Children.Length; i++) + { + var varFileInfoChildEntry = versionInfo.VarFileInfo.Children[i]; + if (varFileInfoChildEntry == null) + { + builder.AppendLine($"{padding} [String Table {i}] [NULL]"); + continue; + } + + builder.AppendLine(varFileInfoChildEntry.Length, $"{padding} [String Table {i}] Length"); + builder.AppendLine(varFileInfoChildEntry.ValueLength, $"{padding} [String Table {i}] Value length"); + builder.AppendLine($"{padding} [String Table {i}] ResourceType: {varFileInfoChildEntry.ResourceType} (0x{varFileInfoChildEntry.ResourceType:X})"); + builder.AppendLine(varFileInfoChildEntry.Key, $"{padding} [String Table {i}] Key"); + builder.AppendLine(varFileInfoChildEntry.Value, $"{padding} [String Table {i}] Value"); + } + } + } + } + + private static void PrintResourceRT_DLGINCLUDE(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}External header resource found, not parsed yet"); + } + + private static void PrintResourceRT_PLUGPLAY(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Plug and Play resource found, not parsed yet"); + } + + private static void PrintResourceRT_VXD(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}VXD found, not parsed yet"); + } + + private static void PrintResourceRT_ANICURSOR(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Animated cursor found, not parsed yet"); + } + + private static void PrintResourceRT_ANIICON(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}Animated icon found, not parsed yet"); + } + + private static void PrintResourceRT_HTML(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + builder.AppendLine($"{padding}HTML resource found, not parsed yet"); + + //if (entry.Data != null) + // builder.AppendLine(Encoding.ASCII.GetString(entry.Data), $"{padding}Value (ASCII)"); + //if (entry.Data != null) + // builder.AppendLine(Encoding.UTF8.GetString(entry.Data), $"{padding}Value (UTF-8)"); + //if (entry.Data != null) + // builder.AppendLine(Encoding.Unicode.GetString(entry.Data), $"{padding}Value (Unicode)"); + } + + private static void PrintResourceRT_MANIFEST(ResourceDataEntry entry, int level, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + AssemblyManifest? assemblyManifest = null; + try { assemblyManifest = entry.AsAssemblyManifest(); } catch { } + if (assemblyManifest == null) + { + builder.AppendLine($"{padding}Assembly manifest found, but malformed"); + return; + } + + builder.AppendLine(assemblyManifest.ManifestVersion, $"{padding}Manifest version"); + if (assemblyManifest.AssemblyIdentities != null && assemblyManifest.AssemblyIdentities.Length > 0) + { + for (int i = 0; i < assemblyManifest.AssemblyIdentities.Length; i++) + { + var assemblyIdentity = assemblyManifest.AssemblyIdentities[i]; + if (assemblyIdentity == null) + { + builder.AppendLine($"{padding} [Assembly Identity {i}] [NULL]"); + continue; + } + + builder.AppendLine(assemblyIdentity.Name, $"{padding}[Assembly Identity {i}] Name"); + builder.AppendLine(assemblyIdentity.Version, $"{padding}[Assembly Identity {i}] Version"); + builder.AppendLine(assemblyIdentity.Type, $"{padding}[Assembly Identity {i}] Type"); + builder.AppendLine(assemblyIdentity.ProcessorArchitecture, $"{padding}[Assembly Identity {i}] Processor architecture"); + builder.AppendLine(assemblyIdentity.PublicKeyToken, $"{padding}[Assembly Identity {i}] Public key token"); + builder.AppendLine(assemblyIdentity.Language, $"{padding}[Assembly Identity {i}] Language"); + } + } + + if (assemblyManifest.Description != null) + builder.AppendLine(assemblyManifest.Description.Value, $"{padding}[Assembly Description] Value"); + + if (assemblyManifest.COMInterfaceExternalProxyStub != null && assemblyManifest.COMInterfaceExternalProxyStub.Length > 0) + { + for (int i = 0; i < assemblyManifest.COMInterfaceExternalProxyStub.Length; i++) + { + var comInterfaceExternalProxyStub = assemblyManifest.COMInterfaceExternalProxyStub[i]; + if (comInterfaceExternalProxyStub == null) + { + builder.AppendLine($"{padding} [COM Interface External Proxy Stub {i}] [NULL]"); + continue; + } + + builder.AppendLine(comInterfaceExternalProxyStub.IID, $"{padding}[COM Interface External Proxy Stub {i}] IID"); + builder.AppendLine(comInterfaceExternalProxyStub.Name, $"{padding}[COM Interface External Proxy Stub {i}] Name"); + builder.AppendLine(comInterfaceExternalProxyStub.TLBID, $"{padding}[COM Interface External Proxy Stub {i}] TLBID"); + builder.AppendLine(comInterfaceExternalProxyStub.NumMethods, $"{padding}[COM Interface External Proxy Stub {i}] Number of methods"); + builder.AppendLine(comInterfaceExternalProxyStub.ProxyStubClsid32, $"{padding}[COM Interface External Proxy Stub {i}] Proxy stub (CLSID32)"); + builder.AppendLine(comInterfaceExternalProxyStub.BaseInterface, $"{padding}[COM Interface External Proxy Stub {i}] Base interface"); + } + } + + if (assemblyManifest.Dependency != null && assemblyManifest.Dependency.Length > 0) + { + for (int i = 0; i < assemblyManifest.Dependency.Length; i++) + { + var dependency = assemblyManifest.Dependency[i]; + if (dependency?.DependentAssembly != null) + { + if (dependency.DependentAssembly.AssemblyIdentity != null) + { + builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.Name, $"{padding}[Dependency {i} Assembly Identity] Name"); + builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.Version, $"{padding}[Dependency {i} Assembly Identity] Version"); + builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.Type, $"{padding}[Dependency {i} Assembly Identity] Type"); + builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.ProcessorArchitecture, $"{padding}[Dependency {i} Assembly Identity] Processor architecture"); + builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.PublicKeyToken, $"{padding}[Dependency {i} Assembly Identity] Public key token"); + builder.AppendLine(dependency.DependentAssembly.AssemblyIdentity.Language, $"{padding}[Dependency {i} Assembly Identity] Language"); + } + if (dependency.DependentAssembly.BindingRedirect != null && dependency.DependentAssembly.BindingRedirect.Length > 0) + { + for (int j = 0; j < dependency.DependentAssembly.BindingRedirect.Length; j++) + { + var bindingRedirect = dependency.DependentAssembly.BindingRedirect[j]; + if (bindingRedirect == null) + { + builder.AppendLine($"{padding}[Dependency {i} Binding Redirect {j}] [NULL]"); + continue; + } + + builder.AppendLine(bindingRedirect.OldVersion, $"{padding}[Dependency {i} Binding Redirect {j}] Old version"); + builder.AppendLine(bindingRedirect.NewVersion, $"{padding}[Dependency {i} Binding Redirect {j}] New version"); + } + } + } + + if (dependency != null) + builder.AppendLine(dependency.Optional, $"{padding}[Dependency {i}] Optional"); + } + } + + if (assemblyManifest.File != null && assemblyManifest.File.Length > 0) + { + for (int i = 0; i < assemblyManifest.File.Length; i++) + { + var file = assemblyManifest.File[i]; + if (file == null) + { + builder.AppendLine($"{padding}[File {i}] [NULL]"); + continue; + } + + builder.AppendLine(file.Name, $"{padding}[File {i}] Name"); + builder.AppendLine(file.Hash, $"{padding}[File {i}] Hash"); + builder.AppendLine(file.HashAlgorithm, $"{padding}[File {i}] Hash algorithm"); + builder.AppendLine(file.Size, $"{padding}[File {i}] Size"); + + if (file.COMClass != null && file.COMClass.Length > 0) + { + for (int j = 0; j < file.COMClass.Length; j++) + { + var comClass = file.COMClass[j]; + if (comClass == null) + { + builder.AppendLine($"{padding}[File {i} COM Class {j}] [NULL]"); + continue; + } + + builder.AppendLine(comClass.CLSID, $"{padding}[File {i} COM Class {j}] CLSID"); + builder.AppendLine(comClass.ThreadingModel, $"{padding}[File {i} COM Class {j}] Threading model"); + builder.AppendLine(comClass.ProgID, $"{padding}[File {i} COM Class {j}] Prog ID"); + builder.AppendLine(comClass.TLBID, $"{padding}[File {i} COM Class {j}] TLBID"); + builder.AppendLine(comClass.Description, $"{padding}[File {i} COM Class {j}] Description"); + + if (comClass.ProgIDs != null && comClass.ProgIDs.Length > 0) + { + for (int k = 0; k < comClass.ProgIDs.Length; k++) + { + var progId = comClass.ProgIDs[k]; + if (progId == null) + { + builder.AppendLine($"{padding}[File {i} COM Class {j} Prog ID {k}] [NULL]"); + continue; + } + + builder.AppendLine(progId.Value, $"{padding}[File {i} COM Class {j} Prog ID {k}] Value"); + } + } + } + } + + if (file.COMInterfaceProxyStub != null && file.COMInterfaceProxyStub.Length > 0) + { + for (int j = 0; j < file.COMInterfaceProxyStub.Length; j++) + { + var comInterfaceProxyStub = file.COMInterfaceProxyStub[j]; + if (comInterfaceProxyStub == null) + { + builder.AppendLine($"{padding}[File {i} COM Interface Proxy Stub {j}] [NULL]"); + continue; + } + + builder.AppendLine(comInterfaceProxyStub.IID, $"{padding}[File {i} COM Interface Proxy Stub {j}] IID"); + builder.AppendLine(comInterfaceProxyStub.Name, $"{padding}[File {i} COM Interface Proxy Stub {j}] Name"); + builder.AppendLine(comInterfaceProxyStub.TLBID, $"{padding}[File {i} COM Interface Proxy Stub {j}] TLBID"); + builder.AppendLine(comInterfaceProxyStub.NumMethods, $"{padding}[File {i} COM Interface Proxy Stub {j}] Number of methods"); + builder.AppendLine(comInterfaceProxyStub.ProxyStubClsid32, $"{padding}[File {i} COM Interface Proxy Stub {j}] Proxy stub (CLSID32)"); + builder.AppendLine(comInterfaceProxyStub.BaseInterface, $"{padding}[File {i} COM Interface Proxy Stub {j}] Base interface"); + } + } + + if (file.Typelib != null && file.Typelib.Length > 0) + { + for (int j = 0; j < file.Typelib.Length; j++) + { + var typeLib = file.Typelib[j]; + if (typeLib == null) + { + builder.AppendLine($"{padding}[File {i} Type Lib {j}] [NULL]"); + continue; + } + + builder.AppendLine(typeLib.TLBID, $"{padding}[File {i} Type Lib {j}] TLBID"); + builder.AppendLine(typeLib.Version, $"{padding}[File {i} Type Lib {j}] Version"); + builder.AppendLine(typeLib.HelpDir, $"{padding}[File {i} Type Lib {j}] Help directory"); + builder.AppendLine(typeLib.ResourceID, $"{padding}[File {i} Type Lib {j}] Resource ID"); + builder.AppendLine(typeLib.Flags, $"{padding}[File {i} Type Lib {j}] Flags"); + } + } + + if (file.WindowClass != null && file.WindowClass.Length > 0) + { + for (int j = 0; j < file.WindowClass.Length; j++) + { + var windowClass = file.WindowClass[j]; + if (windowClass == null) + { + builder.AppendLine($"{padding}[File {i} Window Class {j}] [NULL]"); + continue; + } + + builder.AppendLine(windowClass.Versioned, $"{padding}[File {i} Window Class {j}] Versioned"); + builder.AppendLine(windowClass.Value, $"{padding}[File {i} Window Class {j}] Value"); + } + } + } + } + + if (assemblyManifest.EverythingElse != null && assemblyManifest.EverythingElse.Length > 0) + { + for (int i = 0; i < assemblyManifest.EverythingElse.Length; i++) + { + var thing = assemblyManifest.EverythingElse[i]; + if (thing is XmlElement element) + { + builder.AppendLine(element.OuterXml, $"{padding}Unparsed XML Element {i}"); + } + else + { + builder.AppendLine($"{padding}Unparsed Item {i}: {thing ?? "[NULL]"}"); + } + } + } + } + + private static void PrintResourceUNKNOWN(ResourceDataEntry entry, int level, object resourceType, StringBuilder builder) + { + string padding = new(' ', (level + 1) * 2); + + // Print the type first + if (resourceType is uint numericType) + builder.AppendLine($"{padding}Type {(ResourceType)numericType} found, not parsed yet"); + else if (resourceType is string stringType) + builder.AppendLine($"{padding}Type {stringType} found, not parsed yet"); + else + builder.AppendLine($"{padding}Unknown type {resourceType} found, not parsed yet"); + + // Then print the data, if needed + if (entry.Data == null) + { + builder.AppendLine($"{padding}Data: [NULL] (This may indicate a very large resource)"); + } + else + { + int offset = 0; + + byte[]? magic = entry.Data.ReadBytes(ref offset, Math.Min(entry.Data.Length, 16)); + if (magic == null) + { + // No-op + } + else if (magic[0] == 0x4D && magic[1] == 0x5A) + { + builder.AppendLine($"{padding}Data: [Embedded Executable File]"); // TODO: Parse this out and print separately + } + else if (magic[0] == 0x4D && magic[1] == 0x53 && magic[2] == 0x46 && magic[3] == 0x54) + { + builder.AppendLine($"{padding}Data: [Embedded OLE Library File]"); // TODO: Parse this out and print separately + } + else + { + builder.AppendLine(magic, $"{padding}Data"); + + //if (entry.Data != null) + // builder.AppendLine(entry.Data, $"{padding}Value (Byte Data)"); + //if (entry.Data != null) + // builder.AppendLine(Encoding.ASCII.GetString(entry.Data), $"{padding}Value (ASCII)"); + //if (entry.Data != null) + // builder.AppendLine(Encoding.UTF8.GetString(entry.Data), $"{padding}Value (UTF-8)"); + //if (entry.Data != null) + // builder.AppendLine(Encoding.Unicode.GetString(entry.Data), $"{padding}Value (Unicode)"); + } + } + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/Quantum.cs b/SabreTools.Serialization/Printers/Quantum.cs new file mode 100644 index 00000000..ec1de9c2 --- /dev/null +++ b/SabreTools.Serialization/Printers/Quantum.cs @@ -0,0 +1,79 @@ +using System.Text; +using SabreTools.Models.Quantum; +using SabreTools.Serialization.Interfaces; + +namespace SabreTools.Serialization.Printers +{ + public class Quantum : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, Archive model) + => Print(builder, model); + + public static void Print(StringBuilder builder, Archive archive) + { + builder.AppendLine("Quantum Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(); + + Print(builder, archive.Header); + Print(builder, archive.FileList); + builder.AppendLine(archive.CompressedDataOffset, " Compressed data offset"); + builder.AppendLine(); + } + + 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.MajorVersion, " Major version"); + builder.AppendLine(header.MinorVersion, " Minor version"); + builder.AppendLine(header.FileCount, " File count"); + builder.AppendLine(header.TableSize, " Table size"); + builder.AppendLine(header.CompressionFlags, " Compression flags"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, FileDescriptor?[]? entries) + { + builder.AppendLine(" File List Information:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) + { + builder.AppendLine(" No file list items"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < entries.Length; i++) + { + var fileDescriptor = entries[i]; + builder.AppendLine($" File Descriptor {i}"); + if (fileDescriptor == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(fileDescriptor.FileNameSize, " File name size"); + builder.AppendLine(fileDescriptor.FileName, " File name"); + builder.AppendLine(fileDescriptor.CommentFieldSize, " Comment field size"); + builder.AppendLine(fileDescriptor.CommentField, " Comment field"); + builder.AppendLine(fileDescriptor.ExpandedFileSize, " Expanded file size"); + builder.AppendLine(fileDescriptor.FileTime, " File time"); + builder.AppendLine(fileDescriptor.FileDate, " File date"); + if (fileDescriptor.Unknown != null) + builder.AppendLine(fileDescriptor.Unknown.Value, " Unknown (Checksum?)"); + } + builder.AppendLine(); + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/SGA.cs b/SabreTools.Serialization/Printers/SGA.cs new file mode 100644 index 00000000..4e945f8e --- /dev/null +++ b/SabreTools.Serialization/Printers/SGA.cs @@ -0,0 +1,406 @@ +using System.Text; +using SabreTools.Models.SGA; +using SabreTools.Serialization.Interfaces; + +namespace SabreTools.Serialization.Printers +{ + public class SGA : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, File model) + => Print(builder, model); + + public static void Print(StringBuilder builder, File file) + { + builder.AppendLine("SGA Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(); + + // Header + Print(builder, file.Header); + + // Directory + Print(builder, file.Directory); + // TODO: Should we print the string table? + } + + 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.MajorVersion, " Major version"); + builder.AppendLine(header.MinorVersion, " Minor version"); + switch (header) + { + case Header4 header4: + builder.AppendLine(header4.FileMD5, " File MD5"); + builder.AppendLine(header4.Name, " Name"); + builder.AppendLine(header4.HeaderMD5, " Header MD5"); + builder.AppendLine(header4.HeaderLength, " Header length"); + builder.AppendLine(header4.FileDataOffset, " File data offset"); + builder.AppendLine(header4.Dummy0, " Dummy 0"); + break; + + case Header6 header6: + builder.AppendLine(header6.Name, " Name"); + builder.AppendLine(header6.HeaderLength, " Header length"); + builder.AppendLine(header6.FileDataOffset, " File data offset"); + builder.AppendLine(header6.Dummy0, " Dummy 0"); + break; + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, Directory? directory) + { + builder.AppendLine(" Directory Information:"); + builder.AppendLine(" -------------------------"); + if (directory == null) + { + builder.AppendLine(" No directory"); + builder.AppendLine(); + return; + } + + switch (directory) + { + case Directory4 directory4: + Print(builder, directory4.DirectoryHeader); + Print(builder, directory4.Sections); + Print(builder, directory4.Folders); + Print(builder, directory4.Files); + break; + + case Directory5 directory5: + Print(builder, directory5.DirectoryHeader); + Print(builder, directory5.Sections); + Print(builder, directory5.Folders); + Print(builder, directory5.Files); + break; + + case Directory6 directory6: + Print(builder, directory6.DirectoryHeader); + Print(builder, directory6.Sections); + Print(builder, directory6.Folders); + Print(builder, directory6.Files); + break; + + case Directory7 directory7: + Print(builder, directory7.DirectoryHeader); + Print(builder, directory7.Sections); + Print(builder, directory7.Folders); + Print(builder, directory7.Files); + break; + + default: + builder.AppendLine($" Unrecognized directory type"); + break; + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, DirectoryHeader4? header) + { + builder.AppendLine(" Directory Header Information:"); + builder.AppendLine(" -------------------------"); + if (header == null) + { + builder.AppendLine(" No directory header"); + builder.AppendLine(); + return; + } + + builder.AppendLine(header.SectionOffset, " Section offset"); + builder.AppendLine(header.SectionCount, " Section count"); + builder.AppendLine(header.FolderOffset, " Folder offset"); + builder.AppendLine(header.FolderCount, " Folder count"); + builder.AppendLine(header.FileOffset, " File offset"); + builder.AppendLine(header.FileCount, " File count"); + builder.AppendLine(header.StringTableOffset, " String table offset"); + builder.AppendLine(header.StringTableCount, " String table count"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, DirectoryHeader5? header) + { + builder.AppendLine(" Directory Header Information:"); + builder.AppendLine(" -------------------------"); + if (header == null) + { + builder.AppendLine(" No directory header"); + builder.AppendLine(); + return; + } + + builder.AppendLine(header.SectionOffset, " Section offset"); + builder.AppendLine(header.SectionCount, " Section count"); + builder.AppendLine(header.FolderOffset, " Folder offset"); + builder.AppendLine(header.FolderCount, " Folder count"); + builder.AppendLine(header.FileOffset, " File offset"); + builder.AppendLine(header.FileCount, " File count"); + builder.AppendLine(header.StringTableOffset, " String table offset"); + builder.AppendLine(header.StringTableCount, " String table count"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, DirectoryHeader7? header) + { + builder.AppendLine(" Directory Header Information:"); + builder.AppendLine(" -------------------------"); + if (header == null) + { + builder.AppendLine(" No directory header"); + builder.AppendLine(); + return; + } + + builder.AppendLine(header.SectionOffset, " Section offset"); + builder.AppendLine(header.SectionCount, " Section count"); + builder.AppendLine(header.FolderOffset, " Folder offset"); + builder.AppendLine(header.FolderCount, " Folder count"); + builder.AppendLine(header.FileOffset, " File offset"); + builder.AppendLine(header.FileCount, " File count"); + builder.AppendLine(header.StringTableOffset, " String table offset"); + builder.AppendLine(header.StringTableCount, " String table count"); + builder.AppendLine(header.HashTableOffset, " Hash table offset"); + builder.AppendLine(header.BlockSize, " Block size"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, Section4?[]? sections) + { + builder.AppendLine(" Sections Information:"); + builder.AppendLine(" -------------------------"); + if (sections == null || sections.Length == 0) + { + builder.AppendLine(" No sections"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < sections.Length; i++) + { + builder.AppendLine($" Section {i}"); + var section = sections[i]; + if (section == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(section.Alias, " Alias"); + builder.AppendLine(section.Name, " Name"); + builder.AppendLine(section.FolderStartIndex, " Folder start index"); + builder.AppendLine(section.FolderEndIndex, " Folder end index"); + builder.AppendLine(section.FileStartIndex, " File start index"); + builder.AppendLine(section.FileEndIndex, " File end index"); + builder.AppendLine(section.FolderRootIndex, " Folder root index"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, Section5?[]? sections) + { + builder.AppendLine(" Sections Information:"); + builder.AppendLine(" -------------------------"); + if (sections == null || sections.Length == 0) + { + builder.AppendLine(" No sections"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < sections.Length; i++) + { + builder.AppendLine($" Section {i}"); + var section = sections[i]; + if (section == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(section.Alias, " Alias"); + builder.AppendLine(section.Name, " Name"); + builder.AppendLine(section.FolderStartIndex, " Folder start index"); + builder.AppendLine(section.FolderEndIndex, " Folder end index"); + builder.AppendLine(section.FileStartIndex, " File start index"); + builder.AppendLine(section.FileEndIndex, " File end index"); + builder.AppendLine(section.FolderRootIndex, " Folder root index"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, Folder4?[]? folders) + { + builder.AppendLine(" Folders Information:"); + builder.AppendLine(" -------------------------"); + if (folders == null || folders.Length == 0) + { + builder.AppendLine(" No folders"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < folders.Length; i++) + { + builder.AppendLine($" Folder {i}"); + var folder = folders[i]; + if (folder == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(folder.NameOffset, " Name offset"); + builder.AppendLine(folder.Name, " Name"); + builder.AppendLine(folder.FolderStartIndex, " Folder start index"); + builder.AppendLine(folder.FolderEndIndex, " Folder end index"); + builder.AppendLine(folder.FileStartIndex, " File start index"); + builder.AppendLine(folder.FileEndIndex, " File end index"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, Folder5?[]? folders) + { + builder.AppendLine(" Folders Information:"); + builder.AppendLine(" -------------------------"); + if (folders == null || folders.Length == 0) + { + builder.AppendLine(" No folders"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < folders.Length; i++) + { + builder.AppendLine($" Folder {i}"); + var folder = folders[i] as Folder5; + if (folder == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(folder.NameOffset, " Name offset"); + builder.AppendLine(folder.Name, " Name"); + builder.AppendLine(folder.FolderStartIndex, " Folder start index"); + builder.AppendLine(folder.FolderEndIndex, " Folder end index"); + builder.AppendLine(folder.FileStartIndex, " File start index"); + builder.AppendLine(folder.FileEndIndex, " File end index"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, File4?[]? files) + { + builder.AppendLine(" Files Information:"); + builder.AppendLine(" -------------------------"); + if (files == null || files.Length == 0) + { + builder.AppendLine(" No files"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < files.Length; i++) + { + builder.AppendLine($" File {i}"); + var file = files[i]; + if (file == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(file.NameOffset, " Name offset"); + builder.AppendLine(file.Name, " Name"); + builder.AppendLine(file.Offset, " Offset"); + builder.AppendLine(file.SizeOnDisk, " Size on disk"); + builder.AppendLine(file.Size, " Size"); + builder.AppendLine(file.TimeModified, " Time modified"); + builder.AppendLine(file.Dummy0, " Dummy 0"); + builder.AppendLine(file.Type, " Type"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, File6?[]? files) + { + builder.AppendLine(" Files Information:"); + builder.AppendLine(" -------------------------"); + if (files == null || files.Length == 0) + { + builder.AppendLine(" No files"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < files.Length; i++) + { + builder.AppendLine($" File {i}"); + var file = files[i]; + if (file == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(file.NameOffset, " Name offset"); + builder.AppendLine(file.Name, " Name"); + builder.AppendLine(file.Offset, " Offset"); + builder.AppendLine(file.SizeOnDisk, " Size on disk"); + builder.AppendLine(file.Size, " Size"); + builder.AppendLine(file.TimeModified, " Time modified"); + builder.AppendLine(file.Dummy0, " Dummy 0"); + builder.AppendLine(file.Type, " Type"); + builder.AppendLine(file.CRC32, " CRC32"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, File7?[]? files) + { + builder.AppendLine(" Files Information:"); + builder.AppendLine(" -------------------------"); + if (files == null || files.Length == 0) + { + builder.AppendLine(" No files"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < files.Length; i++) + { + builder.AppendLine($" File {i}"); + var file = files[i]; + if (file == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(file.NameOffset, " Name offset"); + builder.AppendLine(file.Name, " Name"); + builder.AppendLine(file.Offset, " Offset"); + builder.AppendLine(file.SizeOnDisk, " Size on disk"); + builder.AppendLine(file.Size, " Size"); + builder.AppendLine(file.TimeModified, " Time modified"); + builder.AppendLine(file.Dummy0, " Dummy 0"); + builder.AppendLine(file.Type, " Type"); + builder.AppendLine(file.CRC32, " CRC32"); + builder.AppendLine(file.HashOffset, " Hash offset"); + } + builder.AppendLine(); + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/VBSP.cs b/SabreTools.Serialization/Printers/VBSP.cs new file mode 100644 index 00000000..84673caa --- /dev/null +++ b/SabreTools.Serialization/Printers/VBSP.cs @@ -0,0 +1,82 @@ +using System.Text; +using SabreTools.Models.VBSP; +using SabreTools.Serialization.Interfaces; +using static SabreTools.Models.VBSP.Constants; + +namespace SabreTools.Serialization.Printers +{ + public class VBSP : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, File model) + => Print(builder, model); + + public static void Print(StringBuilder builder, File file) + { + builder.AppendLine("VBSP Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(); + + Print(builder, file.Header); + } + + 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.Version, " Version"); + builder.AppendLine(header.MapRevision, " Map revision"); + builder.AppendLine(); + + Print(builder, header.Lumps); + } + + 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_VBSP_LUMP_ENTITIES: + specialLumpName = " (entities)"; + break; + case HL_VBSP_LUMP_PAKFILE: + specialLumpName = " (pakfile)"; + 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(lump.Version, " Version"); + builder.AppendLine(lump.FourCC, " 4CC"); + } + builder.AppendLine(); + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/VPK.cs b/SabreTools.Serialization/Printers/VPK.cs new file mode 100644 index 00000000..b5585526 --- /dev/null +++ b/SabreTools.Serialization/Printers/VPK.cs @@ -0,0 +1,140 @@ +using System.Text; +using SabreTools.Models.VPK; +using SabreTools.Serialization.Interfaces; + +namespace SabreTools.Serialization.Printers +{ + public class VPK : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, File model) + => Print(builder, model); + + public static void Print(StringBuilder builder, File file) + { + builder.AppendLine("VPK Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(); + + Print(builder, file.Header); + Print(builder, file.ExtendedHeader); + Print(builder, file.ArchiveHashes); + 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.Version, " Version"); + builder.AppendLine(header.DirectoryLength, " Directory length"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, ExtendedHeader? header) + { + builder.AppendLine(" Extended Header Information:"); + builder.AppendLine(" -------------------------"); + if (header == null) + { + builder.AppendLine(" No extended header"); + builder.AppendLine(); + return; + } + + builder.AppendLine(header.Dummy0, " Dummy 0"); + builder.AppendLine(header.ArchiveHashLength, " Archive hash length"); + builder.AppendLine(header.ExtraLength, " Extra length"); + builder.AppendLine(header.Dummy1, " Dummy 1"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, ArchiveHash?[]? entries) + { + builder.AppendLine(" Archive Hashes Information:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) + { + builder.AppendLine(" No archive hashes"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + builder.AppendLine($" Archive Hash {i}"); + if (entry == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(entry.ArchiveIndex, " Archive index"); + builder.AppendLine(entry.ArchiveOffset, " Archive offset"); + builder.AppendLine(entry.Length, " Length"); + builder.AppendLine(entry.Hash, " Hash"); + } + 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]"); + builder.AppendLine(); + continue; + } + + builder.AppendLine(entry.Extension, " Extension"); + builder.AppendLine(entry.Path, " Path"); + builder.AppendLine(entry.Name, " Name"); + builder.AppendLine(); + + Print(builder, entry.DirectoryEntry); + // TODO: Print out preload data? + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, DirectoryEntry? entry) + { + builder.AppendLine(" Directory Entry:"); + builder.AppendLine(" -------------------------"); + if (entry == null) + { + builder.AppendLine(" [NULL]"); + return; + } + + builder.AppendLine(entry.CRC, " CRC"); + builder.AppendLine(entry.PreloadBytes, " Preload bytes"); + builder.AppendLine(entry.ArchiveIndex, " Archive index"); + builder.AppendLine(entry.EntryOffset, " Entry offset"); + builder.AppendLine(entry.EntryLength, " Entry length"); + builder.AppendLine(entry.Dummy0, " Dummy 0"); + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/WAD.cs b/SabreTools.Serialization/Printers/WAD.cs new file mode 100644 index 00000000..5730944e --- /dev/null +++ b/SabreTools.Serialization/Printers/WAD.cs @@ -0,0 +1,107 @@ +using System.Text; +using SabreTools.Models.WAD; +using SabreTools.Serialization.Interfaces; + +namespace SabreTools.Serialization.Printers +{ + public class WAD : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, File model) + => Print(builder, model); + + public static void Print(StringBuilder builder, File file) + { + builder.AppendLine("WAD Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(); + + Print(builder, file.Header); + Print(builder, file.Lumps); + Print(builder, file.LumpInfos); + } + + 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.LumpCount, " Lump count"); + builder.AppendLine(header.LumpOffset, " Lump offset"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, Lump?[]? entries) + { + builder.AppendLine(" Lumps Information:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) + { + builder.AppendLine(" No lumps"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + builder.AppendLine($" Lump {i}"); + if (entry == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(entry.Offset, " Offset"); + builder.AppendLine(entry.DiskLength, " Disk length"); + builder.AppendLine(entry.Length, " Length"); + builder.AppendLine(entry.Type, " Type"); + builder.AppendLine(entry.Compression, " Compression"); + builder.AppendLine(entry.Padding0, " Padding 0"); + builder.AppendLine(entry.Padding1, " Padding 1"); + builder.AppendLine(entry.Name, " Name"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, LumpInfo?[]? entries) + { + builder.AppendLine(" Lump Infos Information:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) + { + builder.AppendLine(" No lump infos"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + builder.AppendLine($" Lump Info {i}"); + if (entry == null) + { + builder.AppendLine(" Lump is compressed"); + continue; + } + + builder.AppendLine(entry.Name, " Name"); + builder.AppendLine(entry.Width, " Width"); + builder.AppendLine(entry.Height, " Height"); + builder.AppendLine(entry.PixelOffset, " Pixel offset"); + // TODO: Print unknown data? + // TODO: Print pixel data? + builder.AppendLine(entry.PaletteSize, " Palette size"); + // TODO: Print palette data? + } + builder.AppendLine(); + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/XMID.cs b/SabreTools.Serialization/Printers/XMID.cs new file mode 100644 index 00000000..920c4ebc --- /dev/null +++ b/SabreTools.Serialization/Printers/XMID.cs @@ -0,0 +1,28 @@ +using System.Text; +using SabreTools.Serialization.Interfaces; +using static SabreTools.Models.Xbox.Constants; + +namespace SabreTools.Serialization.Printers +{ + public class XMID : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, Models.Xbox.XMID model) + => Print(builder, model); + + public static void Print(StringBuilder builder, Models.Xbox.XMID xmid) + { + builder.AppendLine("Xbox Media Identifier Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(xmid.PublisherIdentifier, "Publisher identifier"); + if (!string.IsNullOrEmpty(xmid.PublisherIdentifier) && Publishers.ContainsKey(xmid.PublisherIdentifier ?? string.Empty)) + builder.AppendLine(Publishers[xmid.PublisherIdentifier ?? string.Empty], "Publisher"); + builder.AppendLine(xmid.GameID, "Game ID"); + builder.AppendLine(xmid.VersionNumber, "Version number"); + builder.AppendLine(xmid.RegionIdentifier, "Region identifier"); + if (Regions.ContainsKey(xmid.RegionIdentifier)) + builder.AppendLine(Regions[xmid.RegionIdentifier], "Region"); + builder.AppendLine(); + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/XZP.cs b/SabreTools.Serialization/Printers/XZP.cs new file mode 100644 index 00000000..b62b02e1 --- /dev/null +++ b/SabreTools.Serialization/Printers/XZP.cs @@ -0,0 +1,149 @@ +using System.Text; +using SabreTools.Models.XZP; +using SabreTools.Serialization.Interfaces; + +namespace SabreTools.Serialization.Printers +{ + public class XZP : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, File model) + => Print(builder, model); + + public static void Print(StringBuilder builder, File file) + { + builder.AppendLine("XZP Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(); + + Print(builder, file.Header); + Print(builder, file.DirectoryEntries, "Directory"); + Print(builder, file.PreloadDirectoryEntries, "Preload Directory"); + Print(builder, file.PreloadDirectoryMappings); + Print(builder, file.DirectoryItems); + Print(builder, file.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.Signature, " Signature"); + builder.AppendLine(header.Version, " Version"); + builder.AppendLine(header.PreloadDirectoryEntryCount, " Preload directory entry count"); + builder.AppendLine(header.DirectoryEntryCount, " Directory entry count"); + builder.AppendLine(header.PreloadBytes, " Preload bytes"); + builder.AppendLine(header.HeaderLength, " Header length"); + builder.AppendLine(header.DirectoryItemCount, " Directory item count"); + builder.AppendLine(header.DirectoryItemOffset, " Directory item offset"); + builder.AppendLine(header.DirectoryItemLength, " Directory item length"); + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, DirectoryEntry?[]? entries, string prefix) + { + builder.AppendLine($" {prefix} 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.FileNameCRC, " File name CRC"); + builder.AppendLine(entry.EntryLength, " Entry length"); + builder.AppendLine(entry.EntryOffset, " Entry offset"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, DirectoryMapping?[]? entries) + { + builder.AppendLine(" Preload Directory Mappings Information:"); + builder.AppendLine(" -------------------------"); + if (entries == null || entries.Length == 0) + { + builder.AppendLine(" No preload directory mappings"); + builder.AppendLine(); + return; + } + + for (int i = 0; i < entries.Length; i++) + { + var entry = entries[i]; + builder.AppendLine($" Directory Mapping {i}"); + if (entry == null) + { + builder.AppendLine(" [NULL]"); + continue; + } + + builder.AppendLine(entry.PreloadDirectoryEntryIndex, " Preload directory entry index"); + } + 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.FileNameCRC, " File name CRC"); + builder.AppendLine(entry.NameOffset, " Name offset"); + builder.AppendLine(entry.Name, " Name"); + builder.AppendLine(entry.TimeCreated, " Time created"); + } + builder.AppendLine(); + } + + private static void Print(StringBuilder builder, Footer? footer) + { + builder.AppendLine(" Footer Information:"); + builder.AppendLine(" -------------------------"); + if (footer == null) + { + builder.AppendLine(" No header"); + builder.AppendLine(); + return; + } + + builder.AppendLine(footer.FileLength, " File length"); + builder.AppendLine(footer.Signature, " Signature"); + builder.AppendLine(); + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/Printers/XeMID.cs b/SabreTools.Serialization/Printers/XeMID.cs new file mode 100644 index 00000000..15e265d8 --- /dev/null +++ b/SabreTools.Serialization/Printers/XeMID.cs @@ -0,0 +1,35 @@ +using System.Text; +using SabreTools.Serialization.Interfaces; +using static SabreTools.Models.Xbox.Constants; + +namespace SabreTools.Serialization.Printers +{ + public class XeMID : IPrinter + { + /// + public void PrintInformation(StringBuilder builder, Models.Xbox.XeMID model) + => Print(builder, model); + + public static void Print(StringBuilder builder, Models.Xbox.XeMID xemid) + { + builder.AppendLine("Xbox Media Identifier Information:"); + builder.AppendLine("-------------------------"); + builder.AppendLine(xemid.PublisherIdentifier, "Publisher identifier"); + if (!string.IsNullOrEmpty(xemid.PublisherIdentifier) && Publishers.ContainsKey(xemid.PublisherIdentifier ?? string.Empty)) + builder.AppendLine(Publishers[xemid.PublisherIdentifier ?? string.Empty], "Publisher"); + builder.AppendLine(xemid.PlatformIdentifier, "Platform identifier"); + builder.AppendLine(xemid.GameID, "Game ID"); + builder.AppendLine(xemid.SKU, "SKU"); + builder.AppendLine(xemid.RegionIdentifier, "Region identifier"); + if (Regions.ContainsKey(xemid.RegionIdentifier)) + builder.AppendLine(Regions[xemid.RegionIdentifier], "Region"); + builder.AppendLine(xemid.BaseVersion, "Base version"); + builder.AppendLine(xemid.MediaSubtypeIdentifier, "Media subtype identifier"); + if (MediaSubtypes.ContainsKey(xemid.MediaSubtypeIdentifier)) + builder.AppendLine(MediaSubtypes[xemid.MediaSubtypeIdentifier], "Media subtype"); + builder.AppendLine(xemid.DiscNumberIdentifier, "Disc number identifier"); + builder.AppendLine(xemid.CertificationSubmissionIdentifier, "Certification submission identifier"); + builder.AppendLine(); + } + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/SabreTools.Serialization.csproj b/SabreTools.Serialization/SabreTools.Serialization.csproj index 59496f36..120c2db2 100644 --- a/SabreTools.Serialization/SabreTools.Serialization.csproj +++ b/SabreTools.Serialization/SabreTools.Serialization.csproj @@ -28,6 +28,7 @@ + diff --git a/SabreTools.Serialization/StringBuilderExtensions.cs b/SabreTools.Serialization/StringBuilderExtensions.cs new file mode 100644 index 00000000..9f518b6e --- /dev/null +++ b/SabreTools.Serialization/StringBuilderExtensions.cs @@ -0,0 +1,209 @@ +using System; +using System.Linq; +using System.Text; + +namespace SabreTools.Serialization +{ + // TODO: Add extension for printing enums, if possible + internal static class StringBuilderExtensions + { + /// + /// Append a line containing a boolean to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, bool? value, string prefixString) + { + value ??= false; + return sb.AppendLine($"{prefixString}: {value}"); + } + + /// + /// Append a line containing a Char to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, char? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : value.Value.ToString()); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Int8 to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, sbyte? value, string prefixString) + { + value ??= 0; + string valueString = $"{value} (0x{value:X})"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt8 to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, byte? value, string prefixString) + { + value ??= 0; + string valueString = $"{value} (0x{value:X})"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Int16 to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, short? value, string prefixString) + { + value ??= 0; + string valueString = $"{value} (0x{value:X})"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt16 to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, ushort? value, string prefixString) + { + value ??= 0; + string valueString = $"{value} (0x{value:X})"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Int32 to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, int? value, string prefixString) + { + value ??= 0; + string valueString = $"{value} (0x{value:X})"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt32 to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, uint? value, string prefixString) + { + value ??= 0; + string valueString = $"{value} (0x{value:X})"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Int64 to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, long? value, string prefixString) + { + value ??= 0; + string valueString = $"{value} (0x{value:X})"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt64 to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, ulong? value, string prefixString) + { + value ??= 0; + string valueString = $"{value} (0x{value:X})"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a string to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, string? value, string prefixString) + { + value ??= string.Empty; + string valueString = value ?? "[NULL]"; + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Guid to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, Guid? value, string prefixString) + { + value ??= Guid.Empty; + string valueString = value.Value.ToString(); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt8[] value to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, byte[]? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : BitConverter.ToString(value).Replace('-', ' ')); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt8[] value as a string to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, byte[]? value, string prefixString, Encoding encoding) + { + string valueString = (value == null ? "[NULL]" : encoding.GetString(value).Replace("\0", string.Empty)); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Char[] value to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, char[]? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : string.Join(", ", value.Select(c => c.ToString()).ToArray())); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Int16[] value to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, short[]? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : string.Join(", ", value.Select(s => s.ToString()).ToArray())); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt16[] value to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, ushort[]? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : string.Join(", ", value.Select(u => u.ToString()).ToArray())); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Int32[] value to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, int[]? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : string.Join(", ", value.Select(i => i.ToString()).ToArray())); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt32[] value to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, uint[]? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : string.Join(", ", value.Select(u => u.ToString()).ToArray())); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a Int64[] value to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, long[]? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : string.Join(", ", value.Select(l => l.ToString()).ToArray())); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + + /// + /// Append a line containing a UInt64[] value to a StringBuilder + /// + public static StringBuilder AppendLine(this StringBuilder sb, ulong[]? value, string prefixString) + { + string valueString = (value == null ? "[NULL]" : string.Join(", ", value.Select(u => u.ToString()).ToArray())); + return sb.AppendLine($"{prefixString}: {valueString}"); + } + } +} \ No newline at end of file diff --git a/Test/FileTypes.cs b/Test/FileTypes.cs new file mode 100644 index 00000000..66cb8aaf --- /dev/null +++ b/Test/FileTypes.cs @@ -0,0 +1,812 @@ +using System; +using SabreTools.Matching; + +namespace Test +{ + internal static class FileTypes + { + /// + /// Get the supported file type for a magic string + /// + /// Recommend sending in 16 bytes to check + public static SupportedFileType GetFileType(byte[] magic) + { + // If we have an invalid magic byte array + if (magic == null || magic.Length == 0) + return SupportedFileType.UNKNOWN; + + // TODO: For all modelled types, use the constants instead of hardcoded values here + #region AACSMediaKeyBlock + + // Block starting with verify media key record + if (magic.StartsWith(new byte?[] { 0x81, 0x00, 0x00, 0x14 })) + return SupportedFileType.AACSMediaKeyBlock; + + // Block starting with type and version record + if (magic.StartsWith(new byte?[] { 0x10, 0x00, 0x00, 0x0C })) + return SupportedFileType.AACSMediaKeyBlock; + + #endregion + + #region BDPlusSVM + + if (magic.StartsWith(new byte?[] { 0x42, 0x44, 0x53, 0x56, 0x4D, 0x5F, 0x43, 0x43 })) + return SupportedFileType.BDPlusSVM; + + #endregion + + #region BFPK + + if (magic.StartsWith(new byte?[] { 0x42, 0x46, 0x50, 0x4b })) + return SupportedFileType.BFPK; + + #endregion + + #region BSP + + if (magic.StartsWith(new byte?[] { 0x1e, 0x00, 0x00, 0x00 })) + return SupportedFileType.BSP; + + #endregion + + #region BZip2 + + if (magic.StartsWith(new byte?[] { 0x42, 0x52, 0x68 })) + return SupportedFileType.BZip2; + + #endregion + + #region CFB + + if (magic.StartsWith(new byte?[] { 0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1 })) + return SupportedFileType.CFB; + + #endregion + + #region CIA + + // No magic checks for CIA + + #endregion + + #region Executable + + // DOS MZ executable file format (and descendants) + if (magic.StartsWith(new byte?[] { 0x4d, 0x5a })) + return SupportedFileType.Executable; + + /* + // None of the following are supported in scans yet + + // Executable and Linkable Format + if (magic.StartsWith(new byte?[] { 0x7f, 0x45, 0x4c, 0x46 })) + return FileTypes.Executable; + + // Mach-O binary (32-bit) + if (magic.StartsWith(new byte?[] { 0xfe, 0xed, 0xfa, 0xce })) + return FileTypes.Executable; + + // Mach-O binary (32-bit, reverse byte ordering scheme) + if (magic.StartsWith(new byte?[] { 0xce, 0xfa, 0xed, 0xfe })) + return FileTypes.Executable; + + // Mach-O binary (64-bit) + if (magic.StartsWith(new byte?[] { 0xfe, 0xed, 0xfa, 0xcf })) + return FileTypes.Executable; + + // Mach-O binary (64-bit, reverse byte ordering scheme) + if (magic.StartsWith(new byte?[] { 0xcf, 0xfa, 0xed, 0xfe })) + return FileTypes.Executable; + + // Prefrred Executable File Format + if (magic.StartsWith(new byte?[] { 0x4a, 0x6f, 0x79, 0x21, 0x70, 0x65, 0x66, 0x66 })) + return FileTypes.Executable; + */ + + #endregion + + #region GCF + + if (magic.StartsWith(new byte?[] { 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00 })) + return SupportedFileType.GCF; + + #endregion + + #region GZIP + + if (magic.StartsWith(new byte?[] { 0x1f, 0x8b })) + return SupportedFileType.GZIP; + + #endregion + + #region IniFile + + // No magic checks for IniFile + + #endregion + + #region InstallShieldArchiveV3 + + if (magic.StartsWith(new byte?[] { 0x13, 0x5D, 0x65, 0x8C })) + return SupportedFileType.InstallShieldArchiveV3; + + #endregion + + #region InstallShieldCAB + + if (magic.StartsWith(new byte?[] { 0x49, 0x53, 0x63 })) + return SupportedFileType.InstallShieldCAB; + + #endregion + + #region LDSCRYPT + + if (magic.StartsWith(new byte?[] { 0x4C, 0x44, 0x53, 0x43, 0x52, 0x59, 0x50, 0x54 })) + return SupportedFileType.LDSCRYPT; + + #endregion + + #region MicrosoftCAB + + if (magic.StartsWith(new byte?[] { 0x4d, 0x53, 0x43, 0x46 })) + return SupportedFileType.MicrosoftCAB; + + #endregion + + #region MicrosoftLZ + + if (magic.StartsWith(new byte?[] { 0x53, 0x5a, 0x44, 0x44, 0x88, 0xf0, 0x27, 0x33 })) + return SupportedFileType.MicrosoftLZ; + + #endregion + + #region MPQ + + if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1a })) + return SupportedFileType.MPQ; + + if (magic.StartsWith(new byte?[] { 0x4d, 0x50, 0x51, 0x1b })) + return SupportedFileType.MPQ; + + #endregion + + #region N3DS + + // No magic checks for N3DS + + #endregion + + #region NCF + + if (magic.StartsWith(new byte?[] { 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00 })) + return SupportedFileType.NCF; + + #endregion + + #region Nitro + + // No magic checks for Nitro + + #endregion + + #region PAK + + if (magic.StartsWith(new byte?[] { 0x50, 0x41, 0x43, 0x4B })) + return SupportedFileType.PAK; + + #endregion + + #region PFF + + // Version 2 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x32 })) + return SupportedFileType.PFF; + + // Version 3 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x33 })) + return SupportedFileType.PFF; + + // Version 4 + if (magic.StartsWith(new byte?[] { 0x14, 0x00, 0x00, 0x00, 0x50, 0x46, 0x46, 0x34 })) + return SupportedFileType.PFF; + + #endregion + + #region PKZIP + + // PKZIP (Unknown) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x00, 0x00 })) + return SupportedFileType.PKZIP; + + // PKZIP + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x03, 0x04 })) + return SupportedFileType.PKZIP; + + // PKZIP (Empty Archive) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x05, 0x06 })) + return SupportedFileType.PKZIP; + + // PKZIP (Spanned Archive) + if (magic.StartsWith(new byte?[] { 0x50, 0x4b, 0x07, 0x08 })) + return SupportedFileType.PKZIP; + + #endregion + + #region PLJ + + // https://www.iana.org/assignments/media-types/audio/vnd.everad.plj + if (magic.StartsWith(new byte?[] { 0xFF, 0x9D, 0x53, 0x4B })) + return SupportedFileType.PLJ; + + #endregion + + #region Quantum + + if (magic.StartsWith(new byte?[] { 0x44, 0x53 })) + return SupportedFileType.Quantum; + + #endregion + + #region RAR + + // RAR archive version 1.50 onwards + if (magic.StartsWith(new byte?[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 })) + return SupportedFileType.RAR; + + // RAR archive version 5.0 onwards + if (magic.StartsWith(new byte?[] { 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00 })) + return SupportedFileType.RAR; + + #endregion + + #region RealArcade + + // RASGI2.0 + // Found in the ".rgs files in IA item "Nova_RealArcadeCD_USA". + if (magic.StartsWith(new byte?[] { 0x52, 0x41, 0x53, 0x47, 0x49, 0x32, 0x2E, 0x30 })) + return SupportedFileType.RealArcadeInstaller; + + // XZip2.0 + // Found in the ".mez" files in IA item "Nova_RealArcadeCD_USA". + if (magic.StartsWith(new byte?[] { 0x58, 0x5A, 0x69, 0x70, 0x32, 0x2E, 0x30 })) + return SupportedFileType.RealArcadeMezzanine; + + #endregion + + #region SevenZip + + if (magic.StartsWith(new byte?[] { 0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c })) + return SupportedFileType.SevenZip; + + #endregion + + #region SFFS + + // Found in Redump entry 81756, confirmed to be "StarForce Filesystem" by PiD. + if (magic.StartsWith(new byte?[] { 0x53, 0x46, 0x46, 0x53 })) + return SupportedFileType.SFFS; + + #endregion + + #region SGA + + if (magic.StartsWith(new byte?[] { 0x5F, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56, 0x45 })) + return SupportedFileType.SGA; + + #endregion + + #region TapeArchive + + if (magic.StartsWith(new byte?[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30, 0x30 })) + return SupportedFileType.TapeArchive; + + if (magic.StartsWith(new byte?[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00 })) + return SupportedFileType.TapeArchive; + + #endregion + + #region Textfile + + // Not all textfiles can be determined through magic number + + // HTML + if (magic.StartsWith(new byte?[] { 0x3c, 0x68, 0x74, 0x6d, 0x6c })) + return SupportedFileType.Textfile; + + // HTML and XML + if (magic.StartsWith(new byte?[] { 0x3c, 0x21, 0x44, 0x4f, 0x43, 0x54, 0x59, 0x50, 0x45 })) + return SupportedFileType.Textfile; + + // InstallShield Compiled Rules + if (magic.StartsWith(new byte?[] { 0x61, 0x4C, 0x75, 0x5A })) + return SupportedFileType.Textfile; + + // Microsoft Office File (old) + if (magic.StartsWith(new byte?[] { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 })) + return SupportedFileType.Textfile; + + // Rich Text File + if (magic.StartsWith(new byte?[] { 0x7b, 0x5c, 0x72, 0x74, 0x66, 0x31 })) + return SupportedFileType.Textfile; + + // Windows Help File + if (magic.StartsWith(new byte?[] { 0x3F, 0x5F, 0x03, 0x00 })) + return SupportedFileType.Textfile; + + // XML + // " + /// Get the supported file type for an extension + /// + /// This is less accurate than a magic string match + public static SupportedFileType GetFileType(string extension) + { + // If we have an invalid extension + if (string.IsNullOrEmpty(extension)) + return SupportedFileType.UNKNOWN; + + // Normalize the extension + extension = extension.TrimStart('.').Trim(); + + #region AACSMediaKeyBlock + + // Shares an extension with INF setup information so it can't be used accurately + // Blu-ray + // if (extension.Equals("inf", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.AACSMediaKeyBlock; + + // HD-DVD + if (extension.Equals("aacs", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.AACSMediaKeyBlock; + + #endregion + + #region BDPlusSVM + + if (extension.Equals("svm", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.BDPlusSVM; + + #endregion + + #region BFPK + + // No extensions registered for BFPK + + #endregion + + #region BSP + + // Shares an extension with VBSP so it can't be used accurately + // if (extension.Equals("bsp", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.BSP; + + #endregion + + #region BZip2 + + if (extension.Equals("bz2", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.BZip2; + + #endregion + + #region CFB + + // Installer package + if (extension.Equals("msi", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + // Merge module + else if (extension.Equals("msm", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + // Patch Package + else if (extension.Equals("msp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + // Transform + else if (extension.Equals("mst", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + // Patch Creation Properties + else if (extension.Equals("pcp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CFB; + + #endregion + + #region CIA + + if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.CIA; + + #endregion + + #region Executable + + // DOS MZ executable file format (and descendants) + if (extension.Equals("exe", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Executable; + + // DOS MZ library file format (and descendants) + if (extension.Equals("dll", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Executable; + + #endregion + + #region GCF + + if (extension.Equals("gcf", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.GCF; + + #endregion + + #region GZIP + + if (extension.Equals("gz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.GZIP; + + #endregion + + #region IniFile + + if (extension.Equals("ini", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.IniFile; + + #endregion + + #region InstallShieldArchiveV3 + + if (extension.Equals("z", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.InstallShieldArchiveV3; + + #endregion + + #region InstallShieldCAB + + // No extensions registered for InstallShieldCAB + // Both InstallShieldCAB and MicrosoftCAB share the same extension + + #endregion + + #region MicrosoftCAB + + // No extensions registered for InstallShieldCAB + // Both InstallShieldCAB and MicrosoftCAB share the same extension + + #endregion + + #region MPQ + + if (extension.Equals("mpq", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.MPQ; + + #endregion + + #region N3DS + + // 3DS cart image + if (extension.Equals("3ds", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.N3DS; + + // CIA package -- Not currently supported + // else if (extension.Equals("cia", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.N3DS; + + #endregion + + #region NCF + + if (extension.Equals("ncf", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.NCF; + + #endregion + + #region Nitro + + // DS cart image + if (extension.Equals("nds", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Nitro; + + // DS development cart image + else if (extension.Equals("srl", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Nitro; + + // DSi cart image + else if (extension.Equals("dsi", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Nitro; + + // iQue DS cart image + else if (extension.Equals("ids", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Nitro; + + #endregion + + #region PAK + + // No extensions registered for PAK + // Both PAK and Quantum share one extension + // if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.PAK; + + #endregion + + #region PFF + + if (extension.Equals("pff", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PFF; + + #endregion + + #region PKZIP + + // PKZIP + if (extension.Equals("zip", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Android package + if (extension.Equals("apk", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Java archive + if (extension.Equals("jar", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Google Earth saved working session file + if (extension.Equals("kmz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // KWord document + if (extension.Equals("kwd", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Microsoft Office Open XML Format (OOXML) Document + if (extension.Equals("docx", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Microsoft Office Open XML Format (OOXML) Presentation + if (extension.Equals("pptx", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Microsoft Office Open XML Format (OOXML) Spreadsheet + if (extension.Equals("xlsx", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenDocument text document + if (extension.Equals("odt", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenDocument presentation + if (extension.Equals("odp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenDocument text document template + if (extension.Equals("ott", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Microsoft Open XML paper specification file + if (extension.Equals("oxps", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenOffice spreadsheet + if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenOffice drawing + if (extension.Equals("sxd", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenOffice presentation + if (extension.Equals("sxi", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // OpenOffice word processing + if (extension.Equals("sxw", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // StarOffice spreadsheet + if (extension.Equals("sxc", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Windows Media compressed skin file + if (extension.Equals("wmz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // Mozilla Browser Archive + if (extension.Equals("xpi", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // XML paper specification file + if (extension.Equals("xps", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + // eXact Packager Models + if (extension.Equals("xpt", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PKZIP; + + #endregion + + #region PLJ + + // https://www.iana.org/assignments/media-types/audio/vnd.everad.plj + if (extension.Equals("plj", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.PLJ; + + #endregion + + #region Quantum + + if (extension.Equals("q", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Quantum; + + // Both PAK and Quantum share one extension + // if (extension.Equals("pak", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.Quantum; + + #endregion + + #region RAR + + if (extension.Equals("rar", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.RAR; + + #endregion + + #region SevenZip + + if (extension.Equals("7z", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.SevenZip; + + #endregion + + #region SGA + + if (extension.Equals("sga", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.SGA; + + #endregion + + #region TapeArchive + + if (extension.Equals("tar", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.SevenZip; + + #endregion + + #region Textfile + + // "Description in Zip" + if (extension.Equals("diz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Generic textfile (no header) + if (extension.Equals("txt", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // HTML + if (extension.Equals("htm", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + if (extension.Equals("html", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // InstallShield Script + if (extension.Equals("ins", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Microsoft Office File (old) + if (extension.Equals("doc", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Property list + if (extension.Equals("plist", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Rich Text File + if (extension.Equals("rtf", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Setup information + if (extension.Equals("inf", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // Windows Help File + if (extension.Equals("hlp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // WZC + if (extension.Equals("wzc", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + // XML + if (extension.Equals("xml", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.Textfile; + + + + #endregion + + #region VBSP + + // Shares an extension with BSP so it can't be used accurately + // if (extension.Equals("bsp", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.VBSP; + + #endregion + + #region VPK + + // Common extension so this cannot be used accurately + // if (extension.Equals("vpk", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.VPK; + + #endregion + + #region WAD + + // Common extension so this cannot be used accurately + // if (extension.Equals("wad", StringComparison.OrdinalIgnoreCase)) + // return SupportedFileType.WAD; + + #endregion + + #region XZ + + if (extension.Equals("xz", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.XZ; + + #endregion + + #region XZP + + if (extension.Equals("xzp", StringComparison.OrdinalIgnoreCase)) + return SupportedFileType.XZP; + + #endregion + + // We couldn't find a supported match + return SupportedFileType.UNKNOWN; + } + } +} \ No newline at end of file diff --git a/Test/Options.cs b/Test/Options.cs new file mode 100644 index 00000000..1678ca31 --- /dev/null +++ b/Test/Options.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; + +namespace Test +{ + /// + /// Set of options for the test executable + /// + internal sealed class Options + { + #region Properties + + /// + /// Enable debug output for relevant operations + /// + public bool Debug { get; private set; } = false; + + /// + /// Set of input paths to use for operations + /// + public List InputPaths { get; private set; } = []; + +#if NETCOREAPP3_1_OR_GREATER + /// + /// Enable JSON output + /// + public bool Json { get; private set; } = false; +#endif + + #endregion + + /// + /// Parse commandline arguments into an Options object + /// + public static Options? ParseOptions(string[] args) + { + // If we have invalid arguments + if (args == null || args.Length == 0) + return null; + + // Create an Options object + var options = new Options(); + + // Parse the features + int index = 0; + for (; index < args.Length; index++) + { + string arg = args[index]; + bool featureFound = false; + switch (arg) + { + case "-?": + case "-h": + case "--help": + return null; + + default: + break; + } + + // If the flag wasn't a feature + if (!featureFound) + break; + } + + // Parse the options and paths + for (; index < args.Length; index++) + { + string arg = args[index]; + switch (arg) + { + case "-d": + case "--debug": + options.Debug = true; + break; + + case "-j": + case "--json": +#if NET6_0_OR_GREATER + options.Json = true; +#else + Console.WriteLine("JSON output not available in .NET Framework"); +#endif + break; + + default: + options.InputPaths.Add(arg); + break; + } + } + + // Validate we have any input paths to work on + if (options.InputPaths.Count == 0) + { + Console.WriteLine("At least one path is required!"); + return null; + } + + return options; + } + + /// + /// Display help text + /// + public static void DisplayHelp() + { + Console.WriteLine("SabreTools.Printing Test Program"); + Console.WriteLine(); + Console.WriteLine("test.exe file|directory ..."); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine("-?, -h, --help Display this help text and quit"); + Console.WriteLine("-d, --debug Enable debug mode"); +#if NET6_0_OR_GREATER + Console.WriteLine("-j, --json Print executable info as JSON"); +#endif + } + } +} \ No newline at end of file diff --git a/Test/Program.cs b/Test/Program.cs index 5fc79b2c..af607446 100644 --- a/Test/Program.cs +++ b/Test/Program.cs @@ -1,12 +1,141 @@ -using SabreTools.Serialization; +using System; +using System.IO; +using SabreTools.IO.Extensions; +using SabreTools.Serialization; namespace Test { - class Program + public static class Program { public static void Main(string[] args) { - // DO NOTHING FOR NOW + // Get the options from the arguments + var options = Options.ParseOptions(args); + + // If we have an invalid state + if (options == null) + { + Options.DisplayHelp(); + Console.WriteLine("Press enter to close the program..."); + Console.ReadLine(); + return; + } + + // Loop through the input paths + foreach (string inputPath in args) + { +#if NETFRAMEWORK + PrintPathInfo(inputPath, false, options.Debug); +#else + PrintPathInfo(inputPath, options.Json, options.Debug); +#endif + } + } + + /// + /// Wrapper to print information for a single path + /// + /// File or directory path + /// Enable JSON output, if supported + /// Enable debug output + private static void PrintPathInfo(string path, bool json, bool debug) + { + Console.WriteLine($"Checking possible path: {path}"); + + // Check if the file or directory exists + if (File.Exists(path)) + { + PrintFileInfo(path, json, debug); + } + else if (Directory.Exists(path)) + { +#if NET20 || NET35 + foreach (string file in Directory.GetFiles(path, "*", SearchOption.AllDirectories)) +#else + foreach (string file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) +#endif + { + PrintFileInfo(file, json, debug); + } + } + else + { + Console.WriteLine($"{path} does not exist, skipping..."); + } + } + + /// + /// Print information for a single file, if possible + /// + private static void PrintFileInfo(string file, bool json, bool debug) + { + Console.WriteLine($"Attempting to print info for {file}"); + + try + { + using Stream stream = File.Open(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + // Read the first 8 bytes + byte[]? magic = stream.ReadBytes(8); + stream.Seek(0, SeekOrigin.Begin); + + // Get the file type + SupportedFileType ft = FileTypes.GetFileType(magic ?? []); + if (ft == SupportedFileType.UNKNOWN) + { + string extension = Path.GetExtension(file).TrimStart('.'); + ft = FileTypes.GetFileType(extension); + } + + // Print out the file format + Console.WriteLine($"File format found: {ft}"); + + // Setup the wrapper to print + var wrapper = WrapperFactory.CreateWrapper(ft, stream); + + // If we don't have a wrapper + if (wrapper == null) + { + Console.WriteLine($"Either {ft} is not supported or something went wrong during parsing!"); + Console.WriteLine(); + return; + } + + // Get the base info output name + string filenameBase = $"info-{DateTime.Now:yyyy-MM-dd_HHmmss.ffff}"; + +#if NET6_0_OR_GREATER + // If we have the JSON flag + if (json) + { + // Create the output data + string serializedData = wrapper.ExportJSON(); + Console.WriteLine(serializedData); + + // Write the output data + using var jsw = new StreamWriter(File.OpenWrite($"{filenameBase}.json")); + jsw.WriteLine(serializedData); + } +#endif + + // Create the output data + var builder = wrapper.ExportStringBuilder(); + if (builder == null) + { + Console.WriteLine("No item information could be generated"); + return; + } + + // Write the output data + Console.WriteLine(builder); + using var sw = new StreamWriter(File.OpenWrite($"{filenameBase}.txt")); + sw.WriteLine(builder.ToString()); + } + catch (Exception ex) + { + Console.WriteLine(debug ? ex : "[Exception opening file, please try again]"); + Console.WriteLine(); + } } } } \ No newline at end of file diff --git a/Test/SupportedFileType.cs b/Test/SupportedFileType.cs new file mode 100644 index 00000000..038e09a2 --- /dev/null +++ b/Test/SupportedFileType.cs @@ -0,0 +1,203 @@ +namespace Test +{ + /// + /// Subset of file types that are supported by the library + /// + internal enum SupportedFileType + { + /// + /// Unknown or unsupported + /// + UNKNOWN, + + /// + /// AACS media key block + /// + AACSMediaKeyBlock, + + /// + /// BD+ SVM + /// + BDPlusSVM, + + /// + /// BFPK custom archive + /// + BFPK, + + /// + /// Half-Life Level + /// + BSP, + + /// + /// bzip2 archive + /// + BZip2, + + /// + /// Compound File Binary + /// + CFB, + + /// + /// CTR Importable Archive + /// + CIA, + + /// + /// Executable or library + /// + Executable, + + /// + /// Half-Life Game Cache File + /// + GCF, + + /// + /// gzip archive + /// + GZIP, + + /// + /// Key-value pair INI file + /// + IniFile, + + /// + /// InstallShield archive v3 + /// + InstallShieldArchiveV3, + + /// + /// InstallShield cabinet file + /// + InstallShieldCAB, + + /// + /// Link Data Security encrypted file + /// + LDSCRYPT, + + /// + /// Microsoft cabinet file + /// + MicrosoftCAB, + + /// + /// Microsoft LZ-compressed file + /// + MicrosoftLZ, + + /// + /// MPQ game data archive + /// + MPQ, + + /// + /// Nintendo 3DS cart image + /// + N3DS, + + /// + /// Half-Life No Cache File + /// + NCF, + + /// + /// Nintendo DS/DSi cart image + /// + Nitro, + + /// + /// Half-Life Package File + /// + PAK, + + /// + /// NovaLogic Game Archive Format + /// + PFF, + + /// + /// PKWARE ZIP archive and derivatives + /// + PKZIP, + + /// + /// PlayJ audio file + /// + PLJ, + + /// + /// Quantum archive + /// + Quantum, + + /// + /// RAR archive + /// + RAR, + + /// + /// RealArcade Installer + /// + RealArcadeInstaller, + + /// + /// RealArcade Mezzanine + /// + RealArcadeMezzanine, + + /// + /// 7-zip archive + /// + SevenZip, + + /// + /// StarForce FileSystem file + /// + SFFS, + + /// + /// SGA + /// + SGA, + + /// + /// Tape archive + /// + TapeArchive, + + /// + /// Various generic textfile formats + /// + Textfile, + + /// + /// Half-Life 2 Level + /// + VBSP, + + /// + /// Valve Package File + /// + VPK, + + /// + /// Half-Life Texture Package File + /// + WAD, + + /// + /// xz archive + /// + XZ, + + /// + /// Xbox Package File + /// + XZP, + } +} \ No newline at end of file diff --git a/Test/WrapperFactory.cs b/Test/WrapperFactory.cs new file mode 100644 index 00000000..7b4855ee --- /dev/null +++ b/Test/WrapperFactory.cs @@ -0,0 +1,116 @@ +using System.IO; +using SabreTools.IO.Extensions; +using SabreTools.Matching; +using SabreTools.Serialization.Interfaces; +using SabreTools.Serialization.Wrappers; + +namespace Test +{ + internal static class WrapperFactory + { + /// + /// Create an instance of a wrapper based on file type + /// + public static IWrapper? CreateWrapper(SupportedFileType fileType, Stream? data) + { + switch (fileType) + { + case SupportedFileType.AACSMediaKeyBlock: return AACSMediaKeyBlock.Create(data); + case SupportedFileType.BDPlusSVM: return BDPlusSVM.Create(data); + case SupportedFileType.BFPK: return BFPK.Create(data); + case SupportedFileType.BSP: return BSP.Create(data); + //case SupportedFileType.BZip2: return BZip2.Create(data); + case SupportedFileType.CFB: return CFB.Create(data); + case SupportedFileType.CIA: return CIA.Create(data); + case SupportedFileType.Executable: return CreateExecutableWrapper(data); + case SupportedFileType.GCF: return GCF.Create(data); + //case SupportedFileType.GZIP: return GZIP.Create(data); + //case SupportedFileType.IniFile: return IniFile.Create(data); + //case SupportedFileType.InstallShieldArchiveV3: return InstallShieldArchiveV3.Create(data); + case SupportedFileType.InstallShieldCAB: return InstallShieldCabinet.Create(data); + //case SupportedFileType.LDSCRYPT: return LDSCRYPT.Create(data); + case SupportedFileType.MicrosoftCAB: return MicrosoftCabinet.Create(data); + //case SupportedFileType.MicrosoftLZ: return MicrosoftLZ.Create(data); + case SupportedFileType.MPQ: return MoPaQ.Create(data); + case SupportedFileType.N3DS: return N3DS.Create(data); + case SupportedFileType.NCF: return NCF.Create(data); + case SupportedFileType.Nitro: return Nitro.Create(data); + case SupportedFileType.PAK: return PAK.Create(data); + case SupportedFileType.PFF: return PFF.Create(data); + //case SupportedFileType.PIC: return PIC.Create(data); + //case SupportedFileType.PKZIP: return PKZIP.Create(data); + case SupportedFileType.PLJ: return PlayJAudioFile.Create(data); + //case SupportedFileType.PLJPlaylist: return PlayJPlaylist.Create(data); + case SupportedFileType.Quantum: return Quantum.Create(data); + //case SupportedFileType.RAR: return RAR.Create(data); + //case SupportedFileType.SevenZip: return SevenZip.Create(data); + //case SupportedFileType.SFFS: return SFFS.Create(data); + case SupportedFileType.SGA: return SGA.Create(data); + //case SupportedFileType.TapeArchive: return TapeArchive.Create(data); + //case SupportedFileType.Textfile: return Textfile.Create(data); + case SupportedFileType.VBSP: return VBSP.Create(data); + case SupportedFileType.VPK: return VPK.Create(data); + case SupportedFileType.WAD: return WAD.Create(data); + //case SupportedFileType.XZ: return XZ.Create(data); + case SupportedFileType.XZP: return XZP.Create(data); + default: return null; + } + } + + /// + /// Create an instance of a wrapper based on the executable type + /// + /// Stream data to parse + /// IWrapper representing the executable, null on error + public static IWrapper? CreateExecutableWrapper(Stream? stream) + { + // If we have no stream + if (stream == null) + return null; + + // Try to get an MS-DOS wrapper first + var wrapper = MSDOS.Create(stream); + if (wrapper == null || !(wrapper is MSDOS msdos)) + return null; + + // Check for a valid new executable address + if (msdos.Model.Header?.NewExeHeaderAddr == null || msdos.Model.Header.NewExeHeaderAddr >= stream.Length) + return wrapper; + + // Try to read the executable info + stream.Seek(msdos.Model.Header.NewExeHeaderAddr, SeekOrigin.Begin); + var magic = stream.ReadBytes(4); + + // If we didn't get valid data at the offset + if (magic == null) + { + return wrapper; + } + + // New Executable + else if (magic.StartsWith(SabreTools.Models.NewExecutable.Constants.SignatureBytes)) + { + stream.Seek(0, SeekOrigin.Begin); + return NewExecutable.Create(stream); + } + + // Linear Executable + else if (magic.StartsWith(SabreTools.Models.LinearExecutable.Constants.LESignatureBytes) + || magic.StartsWith(SabreTools.Models.LinearExecutable.Constants.LXSignatureBytes)) + { + stream.Seek(0, SeekOrigin.Begin); + return LinearExecutable.Create(stream); + } + + // Portable Executable + else if (magic.StartsWith(SabreTools.Models.PortableExecutable.Constants.SignatureBytes)) + { + stream.Seek(0, SeekOrigin.Begin); + return PortableExecutable.Create(stream); + } + + // Everything else fails + return null; + } + } +}