using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Xml.Serialization; namespace BurnOutSharp.Builder { public static class Extensions { #region Byte Arrays /// /// Read a byte and increment the pointer to an array /// public static byte ReadByte(this byte[] content, ref int offset) { return content[offset++]; } /// /// Read a byte array and increment the pointer to an array /// public static byte[] ReadBytes(this byte[] content, ref int offset, int count) { byte[] buffer = new byte[count]; Array.Copy(content, offset, buffer, 0, Math.Min(count, content.Length - offset)); offset += count; return buffer; } /// /// Read a char and increment the pointer to an array /// public static char ReadChar(this byte[] content, ref int offset) { return (char)content[offset++]; } /// /// Read a character array and increment the pointer to an array /// public static char[] ReadChars(this byte[] content, ref int offset, int count) => content.ReadChars(ref offset, count, Encoding.Default); /// /// Read a character array and increment the pointer to an array /// public static char[] ReadChars(this byte[] content, ref int offset, int count, Encoding encoding) { // TODO: Fix the code below to make it work with byte arrays and not streams return null; // byte[] buffer = new byte[count]; // stream.Read(buffer, 0, count); // return encoding.GetString(buffer).ToCharArray(); } /// /// Read a short and increment the pointer to an array /// public static short ReadInt16(this byte[] content, ref int offset) { short value = BitConverter.ToInt16(content, offset); offset += 2; return value; } /// /// Read a ushort and increment the pointer to an array /// public static ushort ReadUInt16(this byte[] content, ref int offset) { ushort value = BitConverter.ToUInt16(content, offset); offset += 2; return value; } /// /// Read a int and increment the pointer to an array /// public static int ReadInt32(this byte[] content, ref int offset) { int value = BitConverter.ToInt32(content, offset); offset += 4; return value; } /// /// Read a uint and increment the pointer to an array /// public static uint ReadUInt32(this byte[] content, ref int offset) { uint value = BitConverter.ToUInt32(content, offset); offset += 4; return value; } /// /// Read a long and increment the pointer to an array /// public static long ReadInt64(this byte[] content, ref int offset) { long value = BitConverter.ToInt64(content, offset); offset += 8; return value; } /// /// Read a ulong and increment the pointer to an array /// public static ulong ReadUInt64(this byte[] content, ref int offset) { ulong value = BitConverter.ToUInt64(content, offset); offset += 8; return value; } /// /// Read a null-terminated string from the stream /// public static string ReadString(this byte[] content, ref int offset) => content.ReadString(ref offset, Encoding.Default); /// /// Read a null-terminated string from the stream /// public static string ReadString(this byte[] content, ref int offset, Encoding encoding) { byte[] nullTerminator = encoding.GetBytes(new char[] { '\0' }); int charWidth = nullTerminator.Length; List keyChars = new List(); while (BitConverter.ToUInt16(content, offset) != 0x0000) { keyChars.Add(encoding.GetChars(content, offset, charWidth)[0]); offset += charWidth; } offset += 2; return new string(keyChars.ToArray()); } #endregion #region Streams /// /// Read a byte from the stream /// public static byte ReadByteValue(this Stream stream) { byte[] buffer = new byte[1]; stream.Read(buffer, 0, 1); return buffer[0]; } /// /// Read a byte array from the stream /// public static byte[] ReadBytes(this Stream stream, int count) { byte[] buffer = new byte[count]; stream.Read(buffer, 0, count); return buffer; } /// /// Read a character from the stream /// public static char ReadChar(this Stream stream) { byte[] buffer = new byte[1]; stream.Read(buffer, 0, 1); return (char)buffer[0]; } /// /// Read a character array from the stream /// public static char[] ReadChars(this Stream stream, int count) => stream.ReadChars(count, Encoding.Default); /// /// Read a character array from the stream /// public static char[] ReadChars(this Stream stream, int count, Encoding encoding) { byte[] buffer = new byte[count]; stream.Read(buffer, 0, count); return encoding.GetString(buffer).ToCharArray(); } /// /// Read a short from the stream /// public static short ReadInt16(this Stream stream) { byte[] buffer = new byte[2]; stream.Read(buffer, 0, 2); return BitConverter.ToInt16(buffer, 0); } /// /// Read a ushort from the stream /// public static ushort ReadUInt16(this Stream stream) { byte[] buffer = new byte[2]; stream.Read(buffer, 0, 2); return BitConverter.ToUInt16(buffer, 0); } /// /// Read an int from the stream /// public static int ReadInt32(this Stream stream) { byte[] buffer = new byte[4]; stream.Read(buffer, 0, 4); return BitConverter.ToInt32(buffer, 0); } /// /// Read a uint from the stream /// public static uint ReadUInt32(this Stream stream) { byte[] buffer = new byte[4]; stream.Read(buffer, 0, 4); return BitConverter.ToUInt32(buffer, 0); } /// /// Read a long from the stream /// public static long ReadInt64(this Stream stream) { byte[] buffer = new byte[8]; stream.Read(buffer, 0, 8); return BitConverter.ToInt64(buffer, 0); } /// /// Read a ulong from the stream /// public static ulong ReadUInt64(this Stream stream) { byte[] buffer = new byte[8]; stream.Read(buffer, 0, 8); return BitConverter.ToUInt64(buffer, 0); } /// /// Read a null-terminated string from the stream /// public static string ReadString(this Stream stream) => stream.ReadString(Encoding.Default); /// /// Read a null-terminated string from the stream /// public static string ReadString(this Stream stream, Encoding encoding) { byte[] nullTerminator = encoding.GetBytes(new char[] { '\0' }); int charWidth = nullTerminator.Length; List tempBuffer = new List(); byte[] buffer = new byte[charWidth]; while (stream.Read(buffer, 0, charWidth) != 0 && !buffer.SequenceEqual(nullTerminator)) { tempBuffer.AddRange(buffer); } return encoding.GetString(tempBuffer.ToArray()); } #endregion #region New Executable /// /// Determine if a resource type information entry is an integer or offset /// /// Resource type information entry to check /// True if the entry is an integer type, false if an offset, null on error public static bool? IsIntegerType(this Models.NewExecutable.ResourceTypeInformationEntry entry) { // We can't do anything with an invalid entry if (entry == null) return null; // If the highest order bit is set, it's an integer type return (entry.TypeID & 0x8000) != 0; } /// /// Determine if a resource type resource entry is an integer or offset /// /// Resource type resource entry to check /// True if the entry is an integer type, false if an offset, null on error public static bool? IsIntegerType(this Models.NewExecutable.ResourceTypeResourceEntry entry) { // We can't do anything with an invalid entry if (entry == null) return null; // If the highest order bit is set, it's an integer type return (entry.ResourceID & 0x8000) != 0; } /// /// Get the segment entry type for an entry table bundle /// /// Entry table bundle to check /// SegmentEntryType corresponding to the type public static Models.NewExecutable.SegmentEntryType GetEntryType(this Models.NewExecutable.EntryTableBundle entry) { // We can't do anything with an invalid entry if (entry == null) return Models.NewExecutable.SegmentEntryType.Unused; // Determine the entry type based on segment indicator if (entry.SegmentIndicator == 0x00) return Models.NewExecutable.SegmentEntryType.Unused; else if (entry.SegmentIndicator >= 0x01 && entry.SegmentIndicator <= 0xFE) return Models.NewExecutable.SegmentEntryType.FixedSegment; else if (entry.SegmentIndicator == 0xFF) return Models.NewExecutable.SegmentEntryType.MoveableSegment; // We should never get here return Models.NewExecutable.SegmentEntryType.Unused; } #endregion // TODO: Implement other resource types from https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats #region Portable Executable /// /// Convert a relative virtual address to a physical one /// /// Relative virtual address to convert /// Array of sections to check against /// Physical address, 0 on error public static uint ConvertVirtualAddress(this uint rva, Models.PortableExecutable.SectionHeader[] sections) { // If we have an invalid section table, we can't do anything if (sections == null || sections.Length == 0) return 0; // If the RVA is 0, we just return 0 because it's invalid if (rva == 0) return 0; // If the RVA matches a section start exactly, use that var matchingSection = sections.FirstOrDefault(s => s.VirtualAddress == rva); if (matchingSection != null) return rva - matchingSection.VirtualAddress + matchingSection.PointerToRawData; // Loop through all of the sections for (int i = 0; i < sections.Length; i++) { // If the section is invalid, just skip it if (sections[i] == null) continue; // If the section "starts" at 0, just skip it if (sections[i].PointerToRawData == 0) continue; // Attempt to derive the physical address from the current section var section = sections[i]; if (rva >= section.VirtualAddress && section.VirtualSize != 0 && rva <= section.VirtualAddress + section.VirtualSize) return rva - section.VirtualAddress + section.PointerToRawData; else if (rva >= section.VirtualAddress && section.SizeOfRawData != 0 && rva <= section.VirtualAddress + section.SizeOfRawData) return rva - section.VirtualAddress + section.PointerToRawData; } return 0; } /// /// Read resource data as a resource header /// /// Data to parse into a resource header /// Offset into the byte array /// A filled resource header on success, null on error public static Models.PortableExecutable.ResourceHeader AsResourceHeader(this byte[] data, ref int offset) { // If we have data that's invalid, we can't do anything if (data == null) return null; // Read in the table var header = new Models.PortableExecutable.ResourceHeader(); header.DataSize = data.ReadUInt32(ref offset); header.HeaderSize = data.ReadUInt32(ref offset); header.ResourceType = (Models.PortableExecutable.ResourceType)data.ReadUInt32(ref offset); // TODO: Could be a string too header.Name = data.ReadUInt32(ref offset); // TODO: Could be a string too header.DataVersion = data.ReadUInt32(ref offset); header.MemoryFlags = (Models.PortableExecutable.MemoryFlags)data.ReadUInt16(ref offset); header.LanguageId = data.ReadUInt16(ref offset); header.Version = data.ReadUInt32(ref offset); header.Characteristics = data.ReadUInt32(ref offset); return header; } /// /// Read resource data as an accelerator table resource /// /// Resource data entry to parse into an accelerator table resource /// A filled accelerator table resource on success, null on error public static Models.PortableExecutable.AcceleratorTableEntry[] AsAcceleratorTableResource(this Models.PortableExecutable.ResourceDataEntry entry) { // If we have data that's invalid for this resource type, we can't do anything if (entry?.Data == null || entry.Data.Length % 8 != 0) return null; // Get the number of entries int count = entry.Data.Length / 8; // Initialize the iterator int offset = 0; // Create the output object var table = new Models.PortableExecutable.AcceleratorTableEntry[count]; // Read in the table for (int i = 0; i < count; i++) { var acceleratorTableEntry = new Models.PortableExecutable.AcceleratorTableEntry(); acceleratorTableEntry.Flags = (Models.PortableExecutable.AcceleratorTableFlags)entry.Data.ReadUInt16(ref offset); acceleratorTableEntry.Ansi = entry.Data.ReadUInt16(ref offset); acceleratorTableEntry.Id = entry.Data.ReadUInt16(ref offset); acceleratorTableEntry.Padding = entry.Data.ReadUInt16(ref offset); table[i] = acceleratorTableEntry; } return table; } /// /// Read resource data as a side-by-side assembly manifest /// /// Resource data entry to parse into a side-by-side assembly manifest /// A filled side-by-side assembly manifest on success, null on error public static Models.PortableExecutable.AssemblyManifest AsAssemblyManifest(this Models.PortableExecutable.ResourceDataEntry entry) { // If we have an invalid entry, just skip if (entry?.Data == null) return null; try { XmlSerializer serializer = new XmlSerializer(typeof(Models.PortableExecutable.AssemblyManifest)); return serializer.Deserialize(new MemoryStream(entry.Data)) as Models.PortableExecutable.AssemblyManifest; } catch { return null; } } /// /// Read resource data as a font group /// /// Resource data entry to parse into a font group /// A filled font group on success, null on error public static Models.PortableExecutable.FontGroupHeader AsFontGroup(this Models.PortableExecutable.ResourceDataEntry entry) { // If we have an invalid entry, just skip if (entry?.Data == null) return null; // Initialize the iterator int offset = 0; // Create the output object var fontGroupHeader = new Models.PortableExecutable.FontGroupHeader(); fontGroupHeader.NumberOfFonts = entry.Data.ReadUInt16(ref offset); if (fontGroupHeader.NumberOfFonts > 0) { fontGroupHeader.DE = new Models.PortableExecutable.DirEntry[fontGroupHeader.NumberOfFonts]; for (int i = 0; i < fontGroupHeader.NumberOfFonts; i++) { var dirEntry = new Models.PortableExecutable.DirEntry(); dirEntry.FontOrdinal = entry.Data.ReadUInt16(ref offset); dirEntry.Entry = new Models.PortableExecutable.FontDirEntry(); dirEntry.Entry.Version = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.Size = entry.Data.ReadUInt32(ref offset); dirEntry.Entry.Copyright = entry.Data.ReadBytes(ref offset, 60); dirEntry.Entry.Type = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.Points = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.VertRes = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.HorizRes = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.Ascent = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.InternalLeading = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.ExternalLeading = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.Italic = entry.Data.ReadByte(ref offset); dirEntry.Entry.Underline = entry.Data.ReadByte(ref offset); dirEntry.Entry.StrikeOut = entry.Data.ReadByte(ref offset); dirEntry.Entry.Weight = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.CharSet = entry.Data.ReadByte(ref offset); dirEntry.Entry.PixWidth = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.PixHeight = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.PitchAndFamily = entry.Data.ReadByte(ref offset); dirEntry.Entry.AvgWidth = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.MaxWidth = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.FirstChar = entry.Data.ReadByte(ref offset); dirEntry.Entry.LastChar = entry.Data.ReadByte(ref offset); dirEntry.Entry.DefaultChar = entry.Data.ReadByte(ref offset); dirEntry.Entry.BreakChar = entry.Data.ReadByte(ref offset); dirEntry.Entry.WidthBytes = entry.Data.ReadUInt16(ref offset); dirEntry.Entry.Device = entry.Data.ReadUInt32(ref offset); dirEntry.Entry.Face = entry.Data.ReadUInt32(ref offset); dirEntry.Entry.Reserved = entry.Data.ReadUInt32(ref offset); // TODO: Determine how to read these two? Immediately after? dirEntry.Entry.DeviceName = entry.Data.ReadString(ref offset); dirEntry.Entry.FaceName = entry.Data.ReadString(ref offset); fontGroupHeader.DE[i] = dirEntry; } } // TODO: Implement entry parsing return null; } /// /// Read resource data as a string table resource /// /// Resource data entry to parse into a string table resource /// A filled string table resource on success, null on error public static Dictionary AsStringTable(this Models.PortableExecutable.ResourceDataEntry entry) { // If we have an invalid entry, just skip if (entry?.Data == null) return null; // Initialize the iterators int offset = 0, stringIndex = 0; // Create the output table var stringTable = new Dictionary(); // Create the string encoding Encoding stringEncoding = (entry.Codepage != 0 ? Encoding.GetEncoding((int)entry.Codepage) : Encoding.Unicode); // Loop through and add while (offset < entry.Data.Length) { ushort stringLength = entry.Data.ReadUInt16(ref offset); if (stringLength == 0) { stringTable[stringIndex++] = "[EMPTY]"; } else { string fullEncodedString = stringEncoding.GetString(entry.Data, offset, entry.Data.Length - offset); if (stringLength > fullEncodedString.Length) { // TODO: Do something better than print to console Console.WriteLine($"Requested {stringLength} but only have {fullEncodedString.Length} remaining, truncating..."); stringLength = (ushort)fullEncodedString.Length; } string stringValue = fullEncodedString.Substring(0, stringLength); offset += stringEncoding.GetByteCount(stringValue); stringValue = stringValue.Replace("\n", "\\n").Replace("\r", "\\r"); stringTable[stringIndex++] = stringValue; } } return stringTable; } /// /// Read resource data as a version info resource /// /// Resource data entry to parse into a version info resource /// A filled version info resource on success, null on error public static Models.PortableExecutable.VersionInfo AsVersionInfo(this Models.PortableExecutable.ResourceDataEntry entry) { // If we have an invalid entry, just skip if (entry?.Data == null) return null; // Initialize the iterator int offset = 0; // Create the output object var versionInfo = new Models.PortableExecutable.VersionInfo(); versionInfo.Length = entry.Data.ReadUInt16(ref offset); versionInfo.ValueLength = entry.Data.ReadUInt16(ref offset); versionInfo.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); versionInfo.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); if (versionInfo.Key != "VS_VERSION_INFO") return null; while ((offset % 4) != 0) versionInfo.Padding1 = entry.Data.ReadUInt16(ref offset); // Read fixed file info if (versionInfo.ValueLength != 0) { var fixedFileInfo = new Models.PortableExecutable.FixedFileInfo(); fixedFileInfo.Signature = entry.Data.ReadUInt32(ref offset); if (fixedFileInfo.Signature != 0xFEEF04BD) return null; fixedFileInfo.StrucVersion = entry.Data.ReadUInt32(ref offset); fixedFileInfo.FileVersionMS = entry.Data.ReadUInt32(ref offset); fixedFileInfo.FileVersionLS = entry.Data.ReadUInt32(ref offset); fixedFileInfo.ProductVersionMS = entry.Data.ReadUInt32(ref offset); fixedFileInfo.ProductVersionLS = entry.Data.ReadUInt32(ref offset); fixedFileInfo.FileFlagsMask = entry.Data.ReadUInt32(ref offset); fixedFileInfo.FileFlags = (Models.PortableExecutable.FixedFileInfoFlags)(entry.Data.ReadUInt32(ref offset) & fixedFileInfo.FileFlagsMask); fixedFileInfo.FileOS = (Models.PortableExecutable.FixedFileInfoOS)entry.Data.ReadUInt32(ref offset); fixedFileInfo.FileType = (Models.PortableExecutable.FixedFileInfoFileType)entry.Data.ReadUInt32(ref offset); fixedFileInfo.FileSubtype = (Models.PortableExecutable.FixedFileInfoFileSubtype)entry.Data.ReadUInt32(ref offset); fixedFileInfo.FileDateMS = entry.Data.ReadUInt32(ref offset); fixedFileInfo.FileDateLS = entry.Data.ReadUInt32(ref offset); versionInfo.Value = fixedFileInfo; } while ((offset % 4) != 0) versionInfo.Padding2 = entry.Data.ReadUInt16(ref offset); // TODO: Make the following block a private helper method // Determine if we have a StringFileInfo or VarFileInfo next if (offset < versionInfo.Length) { // Cache the current offset for reading int currentOffset = offset; offset += 6; string nextKey = entry.Data.ReadString(ref offset, Encoding.Unicode); offset = currentOffset; if (nextKey == "StringFileInfo") { var stringFileInfo = new Models.PortableExecutable.StringFileInfo(); stringFileInfo.Length = entry.Data.ReadUInt16(ref offset); stringFileInfo.ValueLength = entry.Data.ReadUInt16(ref offset); stringFileInfo.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); stringFileInfo.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); if (stringFileInfo.Key != "StringFileInfo") return null; while ((offset % 4) != 0) stringFileInfo.Padding = entry.Data.ReadUInt16(ref offset); var stringFileInfoChildren = new List(); while (offset < stringFileInfo.Length) { var stringTable = new Models.PortableExecutable.StringTable(); stringTable.Length = entry.Data.ReadUInt16(ref offset); stringTable.ValueLength = entry.Data.ReadUInt16(ref offset); stringTable.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); stringTable.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); while ((offset % 4) != 0) stringTable.Padding = entry.Data.ReadUInt16(ref offset); var stringTableChildren = new List(); while (offset < stringTable.Length) { var stringData = new Models.PortableExecutable.StringData(); stringData.Length = entry.Data.ReadUInt16(ref offset); stringData.ValueLength = entry.Data.ReadUInt16(ref offset); stringData.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); stringData.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); while ((offset % 4) != 0) stringData.Padding = entry.Data.ReadUInt16(ref offset); stringData.Value = entry.Data.ReadString(ref offset, Encoding.Unicode); while ((offset % 4) != 0) _ = entry.Data.ReadUInt16(ref offset); stringTableChildren.Add(stringData); } stringTable.Children = stringTableChildren.ToArray(); stringFileInfoChildren.Add(stringTable); } stringFileInfo.Children = stringFileInfoChildren.ToArray(); versionInfo.StringFileInfo = stringFileInfo; } else if (nextKey == "VarFileInfo") { var varFileInfo = new Models.PortableExecutable.VarFileInfo(); varFileInfo.Length = entry.Data.ReadUInt16(ref offset); varFileInfo.ValueLength = entry.Data.ReadUInt16(ref offset); varFileInfo.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); varFileInfo.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); if (varFileInfo.Key != "VarFileInfo") return null; while ((offset % 4) != 0) varFileInfo.Padding = entry.Data.ReadUInt16(ref offset); var varFileInfoChildren = new List(); while (offset < varFileInfo.Length) { var varData = new Models.PortableExecutable.VarData(); varData.Length = entry.Data.ReadUInt16(ref offset); varData.ValueLength = entry.Data.ReadUInt16(ref offset); varData.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); varData.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); if (varData.Key != "Translation") return null; while ((offset % 4) != 0) varData.Padding = entry.Data.ReadUInt16(ref offset); var varDataValue = new List(); while (offset < (varData.ValueLength * sizeof(ushort))) { uint languageAndCodeIdentifierPair = entry.Data.ReadUInt32(ref offset); varDataValue.Add(languageAndCodeIdentifierPair); } varData.Value = varDataValue.ToArray(); varFileInfoChildren.Add(varData); } varFileInfo.Children = varFileInfoChildren.ToArray(); versionInfo.VarFileInfo = varFileInfo; } } // And again if (offset < versionInfo.Length) { // Cache the current offset for reading int currentOffset = offset; offset += 6; string nextKey = entry.Data.ReadString(ref offset, Encoding.Unicode); offset = currentOffset; if (nextKey == "StringFileInfo") { var stringFileInfo = new Models.PortableExecutable.StringFileInfo(); stringFileInfo.Length = entry.Data.ReadUInt16(ref offset); stringFileInfo.ValueLength = entry.Data.ReadUInt16(ref offset); stringFileInfo.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); stringFileInfo.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); if (stringFileInfo.Key != "StringFileInfo") return null; while ((offset % 4) != 0) stringFileInfo.Padding = entry.Data.ReadUInt16(ref offset); var stringFileInfoChildren = new List(); while (offset < stringFileInfo.Length) { var stringTable = new Models.PortableExecutable.StringTable(); stringTable.Length = entry.Data.ReadUInt16(ref offset); stringTable.ValueLength = entry.Data.ReadUInt16(ref offset); stringTable.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); stringTable.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); while ((offset % 4) != 0) stringTable.Padding = entry.Data.ReadUInt16(ref offset); var stringTableChildren = new List(); while (offset < stringTable.Length) { var stringData = new Models.PortableExecutable.StringData(); stringData.Length = entry.Data.ReadUInt16(ref offset); stringData.ValueLength = entry.Data.ReadUInt16(ref offset); stringData.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); stringData.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); while ((offset % 4) != 0) stringData.Padding = entry.Data.ReadUInt16(ref offset); stringData.Value = entry.Data.ReadString(ref offset, Encoding.Unicode); while ((offset % 4) != 0) _ = entry.Data.ReadUInt16(ref offset); stringTableChildren.Add(stringData); } stringTable.Children = stringTableChildren.ToArray(); stringFileInfoChildren.Add(stringTable); } stringFileInfo.Children = stringFileInfoChildren.ToArray(); versionInfo.StringFileInfo = stringFileInfo; } else if (nextKey == "VarFileInfo") { var varFileInfo = new Models.PortableExecutable.VarFileInfo(); varFileInfo.Length = entry.Data.ReadUInt16(ref offset); varFileInfo.ValueLength = entry.Data.ReadUInt16(ref offset); varFileInfo.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); varFileInfo.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); if (varFileInfo.Key != "VarFileInfo") return null; while ((offset % 4) != 0) varFileInfo.Padding = entry.Data.ReadUInt16(ref offset); var varFileInfoChildren = new List(); while (offset < varFileInfo.Length) { var varData = new Models.PortableExecutable.VarData(); varData.Length = entry.Data.ReadUInt16(ref offset); varData.ValueLength = entry.Data.ReadUInt16(ref offset); varData.ResourceType = (Models.PortableExecutable.VersionResourceType)entry.Data.ReadUInt16(ref offset); varData.Key = entry.Data.ReadString(ref offset, Encoding.Unicode); if (varData.Key != "Translation") return null; while ((offset % 4) != 0) varData.Padding = entry.Data.ReadUInt16(ref offset); var varDataValue = new List(); while (offset < (varData.ValueLength * sizeof(ushort))) { uint languageAndCodeIdentifierPair = entry.Data.ReadUInt32(ref offset); varDataValue.Add(languageAndCodeIdentifierPair); } varData.Value = varDataValue.ToArray(); varFileInfoChildren.Add(varData); } varFileInfo.Children = varFileInfoChildren.ToArray(); versionInfo.VarFileInfo = varFileInfo; } } return versionInfo; } #endregion } }