Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Commit

Permalink
Search all version block languages and fix bug with non-ansi strings
Browse files Browse the repository at this point in the history
  • Loading branch information
caesay committed Jan 5, 2022
1 parent 59f151a commit b8a0d84
Show file tree
Hide file tree
Showing 2 changed files with 240 additions and 39 deletions.
44 changes: 5 additions & 39 deletions src/Squirrel/Internal/SquirrelAwareExecutableDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,45 +60,11 @@ public static List<string> GetAllSquirrelAwareApps(string directory, int minimum

static int? GetVersionBlockSquirrelAwareValue(string executable)
{
int size = NativeMethods.GetFileVersionInfoSize(executable, IntPtr.Zero);

// Nice try, buffer overflow
if (size <= 0 || size > 4096) return null;

var buf = new byte[size];
if (!NativeMethods.GetFileVersionInfo(executable, 0, size, buf)) return null;

const string englishUS = "040904B0";
const string neutral = "000004B0";
var supportedLanguageCodes = new[] { englishUS, neutral };

IntPtr result;
int resultSize;
if (!supportedLanguageCodes.Any(
languageCode =>
NativeMethods.VerQueryValue(
buf,
$"\\StringFileInfo\\{languageCode}\\{SQUIRREL_AWARE_KEY}",
out result, out resultSize
)
)) {
return null;
}

// NB: I have **no** idea why, but Atom.exe won't return the version
// number "1" despite it being in the resource file and being 100%
// identical to the version block that actually works. I've got stuff
// to ship, so we're just going to return '1' if we find the name in
// the block at all. I hate myself for this.
return 1;

#if __NOT__DEFINED_EVAR__
int ret;
string resultData = Marshal.PtrToStringAnsi(result, resultSize-1 /* Subtract one for null terminator */);
if (!Int32.TryParse(resultData, NumberStyles.Integer, CultureInfo.CurrentCulture, out ret)) return null;

return ret;
#endif
return StringFileInfo.ReadVersionInfo(executable, out var vi)
.Where(i => i.Key == SQUIRREL_AWARE_KEY)
.Where(i => int.TryParse(i.Value, out var _))
.Select(i => (int?)int.Parse(i.Value))
.FirstOrDefault(i => i > 0);
}

static int? GetSidecarSquirrelAwareValue(string executable)
Expand Down
235 changes: 235 additions & 0 deletions src/Squirrel/Internal/StringFileInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Squirrel
{
// https://stackoverflow.com/a/43229358/184746
#if NET5_0_OR_GREATER
[System.Runtime.Versioning.SupportedOSPlatform("windows")]
#endif
internal class StringFileInfo
{
[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetFileVersionInfoSize(string lptstrFilename, out int lpdwHandle);

[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetFileVersionInfo(string lptstrFilename, int dwHandle, int dwLen, byte[] lpData);

[DllImport("version.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool VerQueryValue(byte[] pBlock, string lpSubBlock, out IntPtr lplpBuffer, out int puLen);

public readonly Version FileVersion;
public readonly Version ProductVersion;
public readonly uint FileFlagsMask;
public readonly uint FileFlags;
public readonly uint FileOS;
public readonly uint FileType;
public readonly uint FileSubtype;
// Always null
public readonly DateTime? FileDate;

protected StringFileInfo(Version fileVersion, Version productVersion, uint fileFlagsMask, uint fileFlags, uint fileOS, uint fileType, uint fileSubtype, DateTime? fileDate)
{
FileVersion = fileVersion;
ProductVersion = productVersion;
FileFlagsMask = fileFlagsMask;
FileFlags = fileFlags;
FileOS = fileOS;
FileType = fileType;
FileSubtype = fileSubtype;
FileDate = fileDate;
}

// vi can be null on exit
// Item1 = language | codepage
// Item2 = Key
// Item3 = Value
public static IEnumerable<(uint CodePage, string Key, string Value)> ReadVersionInfo(string fileName, out StringFileInfo vi)
{
int num;
int size = GetFileVersionInfoSize(fileName, out num);

if (size == 0) {
throw new Win32Exception();
}

var buffer = new byte[size];
bool success = GetFileVersionInfo(fileName, 0, size, buffer);

if (!success) {
throw new Win32Exception();
}

return ReadVersionInfo(buffer, out vi);

}

// vi can be null on exit
// Item1 = language | codepage
// Item2 = Key
// Item3 = Value
public static IEnumerable<(uint CodePage, string Key, string Value)> ReadVersionInfo(byte[] buffer, out StringFileInfo vi)
{
int offset;
// The offset calculated here is unused
var fibs = ReadFileInfoBaseStruct(buffer, 0, out offset);

if (fibs.Key != "VS_VERSION_INFO") {
throw new Exception(fibs.Key);
}

// Value = VS_FIXEDFILEINFO
if (fibs.ValueLength != 0) {
uint signature = BitConverter.ToUInt32(buffer, fibs.ValueOffset);

if (signature != 0xFEEF04BD) {
throw new Exception(signature.ToString("X8"));
}

uint strucVersion = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 4);

var fileVersion = new Version(BitConverter.ToUInt16(buffer, fibs.ValueOffset + 10), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 8), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 14), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 12));
var productVersion = new Version(BitConverter.ToUInt16(buffer, fibs.ValueOffset + 18), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 16), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 22), BitConverter.ToUInt16(buffer, fibs.ValueOffset + 20));

uint fileFlagsMask = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 24);
uint fileFlags = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 28);
uint fileOS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 32);
uint fileType = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 36);
uint fileSubtype = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 40);

uint fileDateMS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 44);
uint fileDateLS = BitConverter.ToUInt32(buffer, fibs.ValueOffset + 48);
DateTime? fileDate = fileDateMS != 0 || fileDateLS != 0 ?
(DateTime?) DateTime.FromFileTime((long) fileDateMS << 32 | fileDateLS) :
null;

vi = new StringFileInfo(fileVersion, productVersion, fileFlagsMask, fileFlags, fileOS, fileType, fileSubtype, fileDate);
} else {
vi = null;
}

return ReadVersionInfoInternal(buffer, fibs);
}

protected static IEnumerable<(uint CodePage, string Key, string Value)> ReadVersionInfoInternal(byte[] buffer, FileInfoBaseStruct fibs)
{
int sfiOrValOffset = (fibs.ValueOffset + fibs.ValueLength + 3) & (~3);

while (sfiOrValOffset < fibs.Length) {
int nextSfiOrValOffset;

var sfiOrVal = ReadFileInfoBaseStruct(buffer, sfiOrValOffset, out nextSfiOrValOffset);

if (sfiOrVal.Key == "StringFileInfo") {
int stOffset = sfiOrVal.ValueOffset;

while (stOffset < sfiOrVal.EndOffset) {
int nextStOffset;

var st = ReadFileInfoBaseStruct(buffer, stOffset, out nextStOffset);

uint langCharset = uint.Parse(st.Key, NumberStyles.HexNumber);

int striOffset = st.ValueOffset;

while (striOffset < st.EndOffset) {
int nextStriOffset;

var stri = ReadFileInfoBaseStruct(buffer, striOffset, out nextStriOffset);

// Here stri.ValueLength is in words!
int len = FindLengthUnicodeSZ(buffer, stri.ValueOffset, stri.ValueOffset + (stri.ValueLength * 2));
string value = Encoding.Unicode.GetString(buffer, stri.ValueOffset, len * 2);

yield return (langCharset, stri.Key, value);

striOffset = nextStriOffset;
}

stOffset = nextStOffset;
}
} else if (sfiOrVal.Key == "VarFileInfo") {
int varOffset = sfiOrVal.ValueOffset;

while (varOffset < sfiOrVal.EndOffset) {
int nextVarOffset;

var var = ReadFileInfoBaseStruct(buffer, varOffset, out nextVarOffset);

if (var.Key != "Translation") {
throw new Exception(var.Key);
}

int langOffset = var.ValueOffset;

while (langOffset < var.EndOffset) {
unchecked {
// We invert the order suggested by the Var description!
uint high = (uint) BitConverter.ToInt16(buffer, langOffset);
uint low = (uint) BitConverter.ToInt16(buffer, langOffset + 2);
uint lang = (high << 16) | low;

langOffset += 4;
}
}

varOffset = nextVarOffset;
}
} else {
Debug.WriteLine("Unrecognized " + sfiOrVal.Key);
}

sfiOrValOffset = nextSfiOrValOffset;
}
}

protected static FileInfoBaseStruct ReadFileInfoBaseStruct(byte[] buffer, int offset, out int nextOffset)
{
var fibs = new FileInfoBaseStruct {
Length = BitConverter.ToInt16(buffer, offset),
ValueLength = BitConverter.ToInt16(buffer, offset + 2),
Type = BitConverter.ToInt16(buffer, offset + 4)
};

int len = FindLengthUnicodeSZ(buffer, offset + 6, offset + fibs.Length);
fibs.Key = Encoding.Unicode.GetString(buffer, offset + 6, len * 2);

// Padding
fibs.ValueOffset = ((offset + 6 + (len + 1) * 2) + 3) & (~3);

fibs.EndOffset = offset + fibs.Length;
nextOffset = (fibs.EndOffset + 3) & (~3);

return fibs;
}

protected static int FindLengthUnicodeSZ(byte[] buffer, int offset, int endOffset)
{
int offset2 = offset;
while (offset2 < endOffset && BitConverter.ToInt16(buffer, offset2) != 0) {
offset2 += 2;
}

// In chars
return (offset2 - offset) / 2;
}

// Used internally
protected class FileInfoBaseStruct
{
public short Length { get; set; }
public short ValueLength { get; set; }
public short Type { get; set; }
public string Key { get; set; }
public int ValueOffset { get; set; }
public int EndOffset { get; set; }
}
}
}

0 comments on commit b8a0d84

Please sign in to comment.