diff --git a/BurnOutSharp.Builder/Extensions.cs b/BurnOutSharp.Builder/Extensions.cs index 1ddff617..14247fe6 100644 --- a/BurnOutSharp.Builder/Extensions.cs +++ b/BurnOutSharp.Builder/Extensions.cs @@ -459,6 +459,286 @@ namespace BurnOutSharp.Builder 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); + + 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); + + 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 } } \ No newline at end of file diff --git a/BurnOutSharp.Models/PortableExecutable/VarFileInfo.cs b/BurnOutSharp.Models/PortableExecutable/VarFileInfo.cs index e9f054ad..0719d0c3 100644 --- a/BurnOutSharp.Models/PortableExecutable/VarFileInfo.cs +++ b/BurnOutSharp.Models/PortableExecutable/VarFileInfo.cs @@ -36,6 +36,6 @@ /// /// Typically contains a list of languages that the application or DLL supports. /// - public Var[] Children; + public VarData[] Children; } } diff --git a/BurnOutSharp.Models/PortableExecutable/VersionResource.cs b/BurnOutSharp.Models/PortableExecutable/VersionInfo.cs similarity index 100% rename from BurnOutSharp.Models/PortableExecutable/VersionResource.cs rename to BurnOutSharp.Models/PortableExecutable/VersionInfo.cs diff --git a/ExecutableTest/Program.cs b/ExecutableTest/Program.cs index 129bd6ee..ed499d59 100644 --- a/ExecutableTest/Program.cs +++ b/ExecutableTest/Program.cs @@ -931,6 +931,15 @@ namespace ExecutableTest break; case BurnOutSharp.Models.PortableExecutable.ResourceType.RT_VERSION: Console.WriteLine($"{padding}Version resource found, not parsed yet"); + var versionInfo = entry.AsVersionInfo(); + if (versionInfo == null) + { + Console.WriteLine($"{padding}Version info resource found, but malformed"); + } + else + { + // TODO: Add pretty-printing for version info + } break; case BurnOutSharp.Models.PortableExecutable.ResourceType.RT_DLGINCLUDE: Console.WriteLine($"{padding}External header resource found, not parsed yet");