Skip to content

Instantly share code, notes, and snippets.

@jbevain
Last active November 12, 2024 04:05
Show Gist options
  • Save jbevain/7a976aaffff79366ac633fc3dfad3c40 to your computer and use it in GitHub Desktop.
Save jbevain/7a976aaffff79366ac633fc3dfad3c40 to your computer and use it in GitHub Desktop.
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
internal struct FileVersionReader
{
private readonly PEReader _pe;
private BlobReader _reader;
public static Version? ReadFileVersion(PEReader pe)
{
ArgumentNullException.ThrowIfNull(pe);
if (pe.PEHeaders?.PEHeader?.ResourceTableDirectory is { } rtd && rtd.RelativeVirtualAddress != 0)
{
var reader = new FileVersionReader(pe, rtd.RelativeVirtualAddress);
return reader.ReadFileVersion();
}
return null;
}
private FileVersionReader(PEReader pe, int rva)
{
_pe = pe;
_reader = pe.GetSectionData(rva).GetReader();
}
private Version? ReadFileVersion()
{
const uint RT_VERSION = 16;
var directory = new IMAGE_RESOURCE_DIRECTORY(ref _reader);
for (uint i = 0; i < directory.NumberOfNamedEntries + directory.NumberOfIdEntries; i++)
{
var entry = new IMAGE_RESOURCE_DIRECTORY_ENTRY(ref _reader);
if (entry.Name == RT_VERSION && (entry.OffsetToData & 0x80000000) != 0)
{
_reader.Offset = checked((int)(entry.OffsetToData & ~0x80000000));
return ReadID();
}
}
return null;
}
private Version? ReadID()
{
var directory = new IMAGE_RESOURCE_DIRECTORY(ref _reader);
for (uint i = 0; i < directory.NumberOfNamedEntries + directory.NumberOfIdEntries; i++)
{
var entry = new IMAGE_RESOURCE_DIRECTORY_ENTRY(ref _reader);
if (entry.Name == 1 && (entry.OffsetToData & 0x80000000) != 0)
{
_reader.Offset = checked((int)(entry.OffsetToData & ~0x80000000));
return ReadLanguage();
}
}
return null;
}
private Version? ReadLanguage()
{
_ = new IMAGE_RESOURCE_DIRECTORY(ref _reader);
var entry = new IMAGE_RESOURCE_DIRECTORY_ENTRY(ref _reader);
_reader.Offset = checked((int)entry.OffsetToData);
return ReadVersionInfo();
}
// "VS_VERSION_INFO\0" in LE Unicode, which is what is stored in the PE file
private static readonly byte[] VersionInfoKey = [86, 0, 83, 0, 95, 0, 86, 0, 69, 0, 82, 0, 83, 0, 73, 0, 79, 0, 78, 0, 95, 0, 73, 0, 78, 0, 70, 0, 79, 0, 0, 0];
private Version? ReadVersionInfo()
{
var entry = new IMAGE_RESOURCE_DATA_ENTRY(ref _reader);
var data = _pe.GetSectionData((int)entry.OffsetToData).GetReader(0, (int)entry.Size);
_ = data.ReadUInt16();
_ = data.ReadUInt16();
_ = data.ReadUInt16();
for (int i = 0; i < VersionInfoKey.Length; i++)
{
if (data.ReadByte() != VersionInfoKey[i])
{
return null;
}
}
_ = data.ReadUInt16();
if (data.ReadUInt32() != 0xFEEF04BD)
{
return null;
}
_ = data.ReadUInt32();
var minor = data.ReadUInt16();
var major = data.ReadUInt16();
var revision = data.ReadUInt16();
var build = data.ReadUInt16();
return new(major, minor, build, revision);
}
private readonly struct IMAGE_RESOURCE_DIRECTORY
{
public readonly uint Characteristics, TimeDateStamp;
public readonly ushort MajorVersion, MinorVersion, NumberOfNamedEntries, NumberOfIdEntries;
public IMAGE_RESOURCE_DIRECTORY(ref BlobReader blobReader)
{
Characteristics = blobReader.ReadUInt32();
TimeDateStamp = blobReader.ReadUInt32();
MajorVersion = blobReader.ReadUInt16();
MinorVersion = blobReader.ReadUInt16();
NumberOfNamedEntries = blobReader.ReadUInt16();
NumberOfIdEntries = blobReader.ReadUInt16();
}
}
private readonly struct IMAGE_RESOURCE_DIRECTORY_ENTRY
{
public readonly uint Name, OffsetToData;
public IMAGE_RESOURCE_DIRECTORY_ENTRY(ref BlobReader blobReader)
{
Name = blobReader.ReadUInt32();
OffsetToData = blobReader.ReadUInt32();
}
}
private readonly struct IMAGE_RESOURCE_DATA_ENTRY
{
public readonly uint OffsetToData, Size, CodePage, Reserved;
public IMAGE_RESOURCE_DATA_ENTRY(ref BlobReader blobReader)
{
OffsetToData = blobReader.ReadUInt32();
Size = blobReader.ReadUInt32();
CodePage = blobReader.ReadUInt32();
Reserved = blobReader.ReadUInt32();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment