Skip to content

Commit 3f172db

Browse files
committed
Add assembly framework and architecture checks for xna launcher
1 parent e21677f commit 3f172db

File tree

4 files changed

+173
-3
lines changed

4 files changed

+173
-3
lines changed

BepInEx.NetLauncher/NetPreloader.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using BepInEx.Preloader.Core.Logging;
1313
using BepInEx.Preloader.Core.Patching;
1414
using HarmonyLib;
15+
using Mono.Cecil;
1516
using MonoMod.Utils;
1617

1718
namespace BepInEx.NetLauncher;
@@ -78,6 +79,29 @@ public static void Start(string[] args)
7879

7980
Log.Log(LogLevel.Info, $"CLR runtime version: {Environment.Version}");
8081

82+
83+
AssemblyBuildInfo executableInfo, launcherInfo;
84+
85+
using (var executableAssembly = AssemblyDefinition.ReadAssembly(executablePath))
86+
executableInfo = AssemblyBuildInfo.DetermineInfo(executableAssembly);
87+
88+
using (var launcherAssembly = AssemblyDefinition.ReadAssembly(typeof(NetPreloader).Assembly.Location))
89+
launcherInfo = AssemblyBuildInfo.DetermineInfo(launcherAssembly);
90+
91+
// we don't particularly care about AnyCPU here since the fallback bitness is almost never the case
92+
if (executableInfo.Is64Bit != launcherInfo.Is64Bit)
93+
{
94+
Log.LogError($"Game executable is {(executableInfo.Is64Bit ? "64" : "32")}-bit while BepInEx has been compiled as {(launcherInfo.Is64Bit ? "64" : "32")}-bit. Expect crashes");
95+
}
96+
97+
if (executableInfo.NetFrameworkVersion != launcherInfo.NetFrameworkVersion)
98+
{
99+
Log.LogWarning($"Game executable has been compiled as .NET Framework {executableInfo.NetFrameworkVersion}, while BepInEx has been compiled as .NET Framework {launcherInfo.NetFrameworkVersion}. There may be issues within the game caused by this");
100+
}
101+
102+
Log.LogInfo($"Game executable build architecture: {executableInfo}");
103+
Log.LogInfo($"BepInEx launcher build architecture: {launcherInfo}");
104+
81105
Log.Log(LogLevel.Message, "Preloader started");
82106

83107
Assembly entrypointAssembly;
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
using System;
2+
using System.Linq;
3+
using Mono.Cecil;
4+
5+
namespace BepInEx.Preloader.Core
6+
{
7+
public class AssemblyBuildInfo
8+
{
9+
public Version NetFrameworkVersion { get; private set; }
10+
11+
public bool IsAnyCpu { get; set; }
12+
13+
public bool Is64Bit { get; set; }
14+
15+
private static Version GetNet4Version(AssemblyDefinition assemblyDefinition)
16+
{
17+
var targetFrameworkAttribute = assemblyDefinition.CustomAttributes.FirstOrDefault(x =>
18+
x.AttributeType.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute");
19+
20+
if (targetFrameworkAttribute == null)
21+
return null;
22+
23+
if (targetFrameworkAttribute.ConstructorArguments.Count < 1)
24+
return null;
25+
26+
if (targetFrameworkAttribute.ConstructorArguments[0].Type.Name != "String")
27+
return null;
28+
29+
string versionInfo = (string)targetFrameworkAttribute.ConstructorArguments[0].Value;
30+
31+
var values = versionInfo.Split(',');
32+
33+
foreach (var value in values)
34+
{
35+
if (!value.StartsWith("Version=v"))
36+
continue;
37+
38+
try
39+
{
40+
return new Version(value.Substring("Version=v".Length));
41+
}
42+
catch {}
43+
}
44+
45+
return null;
46+
}
47+
48+
public static AssemblyBuildInfo DetermineInfo(AssemblyDefinition assemblyDefinition)
49+
{
50+
var buildInfo = new AssemblyBuildInfo();
51+
52+
// framework version
53+
54+
var runtime = assemblyDefinition.MainModule.Runtime;
55+
56+
if (runtime == TargetRuntime.Net_1_0)
57+
{
58+
buildInfo.NetFrameworkVersion = new Version(1, 0);
59+
}
60+
else if (runtime == TargetRuntime.Net_1_1)
61+
{
62+
buildInfo.NetFrameworkVersion = new Version(1, 1);
63+
}
64+
else if (runtime == TargetRuntime.Net_2_0)
65+
{
66+
// Assume 3.5 here. The code to determine versions between 2.0 and 3.5 is not worth the amount of non-unity games that use it, if any
67+
68+
buildInfo.NetFrameworkVersion = new Version(3, 5);
69+
}
70+
else
71+
{
72+
buildInfo.NetFrameworkVersion = GetNet4Version(assemblyDefinition) ?? new Version(4, 0);
73+
}
74+
75+
// bitness
76+
77+
/*
78+
AnyCPU 64-bit preferred
79+
MainModule.Architecture: I386
80+
MainModule.Attributes: ILOnly
81+
82+
AnyCPU 32-bit preferred
83+
MainModule.Architecture: I386
84+
MainModule.Attributes: ILOnly, Required32Bit, Preferred32Bit
85+
86+
x86
87+
MainModule.Architecture: I386
88+
MainModule.Attributes: ILOnly, Required32Bit
89+
90+
x64
91+
MainModule.Architecture: AMD64
92+
MainModule.Attributes: ILOnly
93+
*/
94+
95+
var architecture = assemblyDefinition.MainModule.Architecture;
96+
var attributes = assemblyDefinition.MainModule.Attributes;
97+
98+
if (architecture == TargetArchitecture.AMD64)
99+
{
100+
buildInfo.Is64Bit = true;
101+
buildInfo.IsAnyCpu = false;
102+
}
103+
else if (architecture == TargetArchitecture.I386 && HasFlag(attributes, ModuleAttributes.Preferred32Bit | ModuleAttributes.Required32Bit))
104+
{
105+
buildInfo.Is64Bit = false;
106+
buildInfo.IsAnyCpu = true;
107+
}
108+
else if (architecture == TargetArchitecture.I386 && HasFlag(attributes, ModuleAttributes.Required32Bit))
109+
{
110+
buildInfo.Is64Bit = false;
111+
buildInfo.IsAnyCpu = false;
112+
}
113+
else if (architecture == TargetArchitecture.I386)
114+
{
115+
buildInfo.Is64Bit = true;
116+
buildInfo.IsAnyCpu = true;
117+
}
118+
else
119+
{
120+
throw new Exception("Unable to determine assembly architecture");
121+
}
122+
123+
return buildInfo;
124+
}
125+
126+
private static bool HasFlag(ModuleAttributes value, ModuleAttributes flag)
127+
{
128+
return (value & flag) == flag;
129+
}
130+
131+
/// <inheritdoc />
132+
public override string ToString()
133+
{
134+
if (IsAnyCpu)
135+
{
136+
return $".NET Framework {NetFrameworkVersion}, AnyCPU ({(Is64Bit ? "64" : "32")}-bit preferred)";
137+
}
138+
139+
return $".NET Framework {NetFrameworkVersion}, {(Is64Bit ? "x64" : "x86")}";
140+
}
141+
}
142+
}

BepInEx.Preloader.Core/Logging/ChainloaderLogHelper.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Linq;
1+
using System;
2+
using System.Linq;
23
using BepInEx.Logging;
34
using BepInEx.Shared;
45
using MonoMod.Utils;
@@ -17,14 +18,15 @@ public static void PrintLogInfo(ManualLogSource log)
1718

1819
//See BuildInfoAttribute for more information about this section.
1920
var attributes = typeof(BuildInfoAttribute).Assembly.GetCustomAttributes(typeof(BuildInfoAttribute), false);
20-
21+
2122
if (attributes.Length > 0)
2223
{
2324
var attribute = (BuildInfoAttribute) attributes[0];
2425
log.Log(LogLevel.Message, attribute.Info);
2526
}
2627

2728
Logger.Log(LogLevel.Info, $"System platform: {PlatformHelper.Current}");
29+
Logger.Log(LogLevel.Info, $"Process bitness: {(PlatformUtils.ProcessIs64Bit ? "64-bit (x64)" : "32-bit (x86)")}");
2830
}
2931

3032
public static void RewritePreloaderLogs()

BepInEx.Preloader.Core/PlatformUtils.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.IO;
33
using System.Reflection;
44
using System.Runtime.InteropServices;
@@ -16,6 +16,8 @@ internal static class PlatformUtils
1616
CharSet = CharSet.Ansi)]
1717
private static extern IntPtr uname_osx(ref utsname_osx utsname);
1818

19+
public static readonly bool ProcessIs64Bit = IntPtr.Size >= 8;
20+
1921
/// <summary>
2022
/// Recreation of MonoMod's PlatformHelper.DeterminePlatform method, but with libc calls instead of creating processes.
2123
/// </summary>

0 commit comments

Comments
 (0)