forked from Squirrel/Squirrel.Windows
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSquirrelAwareExecutableDetector.cs
More file actions
107 lines (90 loc) · 3.97 KB
/
SquirrelAwareExecutableDetector.cs
File metadata and controls
107 lines (90 loc) · 3.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Mono.Cecil;
namespace Squirrel
{
static class SquirrelAwareExecutableDetector
{
public static List<string> GetAllSquirrelAwareApps(string directory, int minimumVersion = 1)
{
var di = new DirectoryInfo(directory);
return di.EnumerateFiles()
.Where(x => x.Name.EndsWith(".exe", StringComparison.OrdinalIgnoreCase))
.Select(x => x.FullName)
.Where(x => (GetPESquirrelAwareVersion(x) ?? -1) >= minimumVersion)
.ToList();
}
public static int? GetPESquirrelAwareVersion(string executable)
{
if (!File.Exists(executable)) return null;
var fullname = Path.GetFullPath(executable);
return Utility.Retry<int?>(() =>
GetAssemblySquirrelAwareVersion(fullname) ?? GetVersionBlockSquirrelAwareValue(fullname));
}
static int? GetAssemblySquirrelAwareVersion(string executable)
{
try {
using (var assembly = AssemblyDefinition.ReadAssembly(executable)) {
if (!assembly.HasCustomAttributes) return null;
var attrs = assembly.CustomAttributes;
var attribute = attrs.FirstOrDefault(x => {
if (x.AttributeType.FullName != typeof(AssemblyMetadataAttribute).FullName) return false;
if (x.ConstructorArguments.Count != 2) return false;
return x.ConstructorArguments[0].Value.ToString() == "SquirrelAwareVersion";
});
if (attribute == null) return null;
int result;
if (!Int32.TryParse(attribute.ConstructorArguments[1].Value.ToString(), NumberStyles.Integer, CultureInfo.CurrentCulture, out result)) {
return null;
}
return result;
}
}
catch (FileLoadException) { return null; }
catch (BadImageFormatException) { return null; }
}
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}\\SquirrelAwareVersion",
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
}
}
}