Skip to content

Commit 6468b3d

Browse files
Merge pull request #1287 from EOS-Contrib/ARM64
Support for windows ARM64
2 parents 13593f7 + 87512b6 commit 6468b3d

27 files changed

Lines changed: 766 additions & 112 deletions

Assets/Plugins/Source/Editor/Build/BuildRunner.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ public void OnPreprocessBuild(BuildReport report)
7474
// Run the static builder's prebuild.
7575
s_builder?.PreBuild(report);
7676

77-
#if !DISABLESTEAMWORKS
77+
#if !DISABLESTEAMWORKS && !EOS_PLATFORM_WINDOWS_ARM64
7878
// If we're using Steamworks, then look at the user's Steam configuration file
79+
// (Steamworks does not ship Windows ARM64 binaries, so it is gated off there.)
7980
// If the "steamApiInterfaceVersionsArray" is empty, try to set it for the user
8081
SteamConfig config = Config.Get<SteamConfig>();
8182
if (config != null && (config.steamApiInterfaceVersionsArray == null || config.steamApiInterfaceVersionsArray.Count == 0))

Assets/Plugins/Source/Editor/Build/PlatformSpecificBuilder.cs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,7 @@ protected PlatformSpecificBuilder(string nativeCodeOutputDirectory, params Build
9999
/// <param name="report">The prebuild report.</param>
100100
public void OnPreprocessBuild(BuildReport report)
101101
{
102-
// If the platform being built is one of the platforms that this
103-
// builder builds to, then set this as the builder with the
104-
// BuildRunner.
105-
if (_buildTargets.Contains(report.summary.platform))
102+
if (ShouldHandle(report))
106103
{
107104
// Note that in this context, despite being within an abstract
108105
// class, the most derived instance will be returned when
@@ -111,6 +108,19 @@ public void OnPreprocessBuild(BuildReport report)
111108
}
112109
}
113110

111+
/// <summary>
112+
/// Determines whether this builder should handle the indicated build.
113+
/// Default implementation matches by BuildTarget. Override for
114+
/// finer-grained dispatch (e.g. architecture sub-options on Windows
115+
/// where x64 and ARM64 share BuildTarget.StandaloneWindows64).
116+
/// </summary>
117+
/// <param name="report">The build report being processed.</param>
118+
/// <returns>True if this builder is responsible for this build.</returns>
119+
protected virtual bool ShouldHandle(BuildReport report)
120+
{
121+
return _buildTargets.Contains(report.summary.platform);
122+
}
123+
114124
/// <summary>
115125
/// Adds a mapping of solution file to expected binary file output.
116126
/// </summary>
@@ -167,7 +177,7 @@ public virtual void BuildNativeCode()
167177
// via UPM.
168178
if (_projectFileToBinaryFilesMap.Keys.All(File.Exists))
169179
{
170-
BuildUtility.BuildNativeBinaries(_projectFileToBinaryFilesMap, _nativeCodeOutputDirectory, true);
180+
BuildUtility.BuildNativeBinaries(_projectFileToBinaryFilesMap, _nativeCodeOutputDirectory, true, GetPlatformString());
171181
}
172182
else
173183
{

Assets/Plugins/Source/Editor/EditorWindows/CheckDeploymentWindow.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class CheckDeploymentWindow : EOSEditorWindow
4040
private const string WindowsPluginDirectory = "Assets/Plugins/Windows";
4141
private const string EOSWindows64DllFile = "x64/EOSSDK-Win64-Shipping.dll";
4242
private const string EOSWindows32DllFile = "x86/EOSSDK-Win32-Shipping.dll";
43+
private const string EOSWindowsARM64DllFile = "ARM64/EOSSDK-Win64-Shippingarm64.dll";
4344

4445
private string content;
4546
private string currentPath;
@@ -84,6 +85,11 @@ protected override void RenderWindow()
8485
LoadDLLFile(Path.Combine(FileSystemUtility.GetProjectPath(), WindowsPluginDirectory, EOSWindows64DllFile));
8586
}
8687

88+
if (GUILayout.Button(EOSWindowsARM64DllFile))
89+
{
90+
LoadDLLFile(Path.Combine(FileSystemUtility.GetProjectPath(), WindowsPluginDirectory, EOSWindowsARM64DllFile));
91+
}
92+
8793
if (!string.IsNullOrWhiteSpace(currentPath))
8894
{
8995
scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.MaxHeight(Screen.height));

Assets/Plugins/Source/Editor/Platforms/Windows/WindowsBuilder.cs

Lines changed: 266 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,31 +30,288 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Build
3030
#endif
3131
using Config;
3232
using Config = EpicOnlineServices.Config;
33+
using System;
3334
using System.IO;
3435
using UnityEditor;
3536
using UnityEditor.Build;
3637
using UnityEditor.Build.Reporting;
3738
using UnityEngine;
39+
using Utility;
3840

3941
/// <summary>
40-
/// WindowsBuilder for 64-bit deployment.
42+
/// Scripting define set when targeting the Windows ARM64 architecture sub-option
43+
/// of <see cref="BuildTarget.StandaloneWindows64"/>. Unity does not provide a built-in
44+
/// scripting define to distinguish ARM64 from x64 on Standalone Windows, so this
45+
/// project-local symbol is auto-managed by the Windows builders during preprocess.
46+
///
47+
/// IMPORTANT: scripting defines set during build preprocess only affect the *next*
48+
/// build's script compilation, not the current one. Switching between x64 and ARM64
49+
/// for Windows builds requires running the menu item under "EOS Plugin/Advanced/Windows
50+
/// ARM64..." once before the first build, or running a build twice (the first to set
51+
/// the define, the second to compile against it).
52+
/// </summary>
53+
internal static class WindowsArm64Define
54+
{
55+
public const string Symbol = "EOS_PLATFORM_WINDOWS_ARM64";
56+
internal const string Architecture = "ARM64";
57+
58+
#if UNITY_6000_0_OR_NEWER
59+
/// <summary>
60+
/// True when the active Standalone build is configured to produce ARM64 binaries.
61+
/// Uses <see cref="PlayerSettings.GetArchitecture"/> which returns 1 for ARM64 on
62+
/// the Standalone Windows target in Unity 6. Falls back to the scripting define as
63+
/// a manual escape hatch if the API throws.
64+
/// </summary>
65+
public static bool IsArm64Active()
66+
{
67+
// Guard: only meaningful when actively building for Windows 64-bit.
68+
// Also protects against Apple Silicon Macs where BuildTargetGroup.Standalone
69+
// architecture would otherwise reflect the host Mac architecture.
70+
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.StandaloneWindows64)
71+
{
72+
return false;
73+
}
74+
75+
try
76+
{
77+
// The "Architecture" dropdown in the Build Settings window is stored as a
78+
// platform setting, not in PlayerSettings. "ARM64" is set when the user
79+
// selects "ARM 64-bit"; the field is empty or "x64" for Intel 64-bit.
80+
string arch = EditorUserBuildSettings.GetPlatformSettings(
81+
BuildPipeline.GetBuildTargetName(BuildTarget.StandaloneWindows64),
82+
"Architecture");
83+
if (!string.IsNullOrEmpty(arch))
84+
{
85+
return string.Equals(arch, Architecture, System.StringComparison.OrdinalIgnoreCase);
86+
}
87+
}
88+
catch (Exception ex)
89+
{
90+
Debug.LogWarning($"EOS Plugin: Could not read ARM64 architecture setting: {ex.Message}");
91+
}
92+
93+
// Manual escape hatch: respect the scripting define if the user set it explicitly.
94+
try
95+
{
96+
NamedBuildTarget named = NamedBuildTarget.FromBuildTargetGroup(BuildTargetGroup.Standalone);
97+
string defines = PlayerSettings.GetScriptingDefineSymbols(named);
98+
return !string.IsNullOrEmpty(defines)
99+
&& Array.IndexOf(defines.Split(';'), Symbol) >= 0;
100+
}
101+
catch (Exception ex)
102+
{
103+
Debug.LogWarning($"EOS Plugin: Could not read scripting defines: {ex.Message}");
104+
return false;
105+
}
106+
}
107+
#else
108+
public static bool IsArm64Active() => false;
109+
#endif
110+
111+
/// <summary>
112+
/// Returns true if <see cref="Symbol"/> is currently present in the Standalone
113+
/// scripting defines, regardless of the active build target or architecture setting.
114+
/// Used to detect define/architecture mismatches before a build compiles scripts.
115+
/// </summary>
116+
public static bool IsDefineSet()
117+
{
118+
try
119+
{
120+
NamedBuildTarget named = NamedBuildTarget.FromBuildTargetGroup(BuildTargetGroup.Standalone);
121+
string defines = PlayerSettings.GetScriptingDefineSymbols(named);
122+
return !string.IsNullOrEmpty(defines)
123+
&& Array.IndexOf(defines.Split(';'), Symbol) >= 0;
124+
}
125+
catch (Exception ex)
126+
{
127+
Debug.LogWarning($"EOS Plugin: Could not read scripting defines: {ex.Message}");
128+
return false;
129+
}
130+
}
131+
132+
#if UNITY_6000_0_OR_NEWER
133+
[InitializeOnLoad]
134+
private static class BuildGuard
135+
{
136+
static BuildGuard()
137+
{
138+
// Intercept the Build button BEFORE Unity compiles scripts, so any define
139+
// change takes effect in the same compilation rather than requiring a second build.
140+
BuildPlayerWindow.RegisterBuildPlayerHandler(OnBuildPlayer);
141+
}
142+
143+
private static void OnBuildPlayer(BuildPlayerOptions options)
144+
{
145+
if (options.target == BuildTarget.StandaloneWindows64)
146+
{
147+
bool wantsArm64 = IsArm64Active();
148+
bool defineIsSet = IsDefineSet();
149+
150+
if (wantsArm64 && !defineIsSet)
151+
{
152+
ScriptingDefineUtility.AddDefine(BuildTarget.StandaloneWindows64, Symbol);
153+
EditorUtility.DisplayDialog(
154+
"EOS Plugin — Windows ARM64",
155+
$"The scripting define '{Symbol}' was missing and has been added.\n\n" +
156+
"Scripts are recompiling. Please click Build again once compilation finishes.",
157+
"OK");
158+
return;
159+
}
160+
161+
if (!wantsArm64 && defineIsSet)
162+
{
163+
ScriptingDefineUtility.RemoveDefine(BuildTarget.StandaloneWindows64, Symbol);
164+
EditorUtility.DisplayDialog(
165+
"EOS Plugin — Windows x64",
166+
$"The scripting define '{Symbol}' was left over from a previous ARM64 build and has been removed.\n\n" +
167+
"Scripts are recompiling. Please click Build again once compilation finishes.",
168+
"OK");
169+
return;
170+
}
171+
}
172+
173+
BuildPlayerWindow.DefaultBuildMethods.BuildPlayer(options);
174+
}
175+
}
176+
#endif
177+
178+
#if UNITY_6000_0_OR_NEWER
179+
/// <summary>
180+
/// Manually enables the ARM64 scripting define. Use this as a workaround before
181+
/// the first ARM64 build to ensure scripts compile with ARM64 support on the first
182+
/// attempt, without needing to build twice.
183+
/// </summary>
184+
[MenuItem("EOS Plugin/Advanced/Windows ARM64/Enable scripting define")]
185+
private static void EnableArm64Define()
186+
{
187+
ScriptingDefineUtility.AddDefine(BuildTarget.StandaloneWindows64, Symbol);
188+
UnityEngine.Debug.Log($"Scripting define '{Symbol}' enabled for Standalone Windows. Build for ARM64 architecture to produce ARM64 binaries.");
189+
}
190+
191+
/// <summary>
192+
/// Manually disables the ARM64 scripting define. Use this as a workaround before
193+
/// switching back to an x64 build to ensure scripts compile without ARM64 support
194+
/// on the first attempt, without needing to build twice.
195+
/// </summary>
196+
[MenuItem("EOS Plugin/Advanced/Windows ARM64/Disable scripting define")]
197+
private static void DisableArm64Define()
198+
{
199+
ScriptingDefineUtility.RemoveDefine(BuildTarget.StandaloneWindows64, Symbol);
200+
UnityEngine.Debug.Log($"Scripting define '{Symbol}' disabled for Standalone Windows. Subsequent builds will produce x64 binaries.");
201+
}
202+
#endif
203+
}
204+
205+
/// <summary>
206+
/// WindowsBuilder for 64-bit (x86_64) deployment.
41207
/// </summary>
42208
public class WindowsBuilder64 : WindowsBuilder
43209
{
44-
public WindowsBuilder64() : base("Plugins/Windows/x64", BuildTarget.StandaloneWindows64)
210+
private const string PlatformId = "x64";
211+
212+
public WindowsBuilder64() : base($"Plugins/Windows/{PlatformId}", BuildTarget.StandaloneWindows64)
213+
{
214+
AddProjectFileToBinaryMapping(
215+
"DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.sln",
216+
$"DynamicLibraryLoaderHelper-{PlatformId}.dll",
217+
$"GfxPluginNativeRender-{PlatformId}.dll");
218+
}
219+
220+
public override string GetPlatformString()
221+
{
222+
return PlatformId;
223+
}
224+
225+
protected override bool ShouldHandle(BuildReport report)
226+
{
227+
// StandaloneWindows64 covers both x64 and ARM64 in Unity 6; only handle x64 here.
228+
if (!base.ShouldHandle(report))
229+
{
230+
return false;
231+
}
232+
return !WindowsArm64Define.IsArm64Active();
233+
}
234+
235+
public override void PreBuild(BuildReport report)
236+
{
237+
#if UNITY_6000_0_OR_NEWER
238+
Debug.Log(WindowsArm64Define.IsArm64Active()
239+
? "Targeting Windows ARM64"
240+
: "Targeting Windows x64 (Intel/AMD)");
241+
#endif
242+
// Safety net for scripted/CI builds that bypass RegisterBuildPlayerHandler.
243+
if (WindowsArm64Define.IsDefineSet())
244+
{
245+
ScriptingDefineUtility.RemoveDefine(BuildTarget.StandaloneWindows64, WindowsArm64Define.Symbol);
246+
throw new BuildFailedException(
247+
$"Scripting define '{WindowsArm64Define.Symbol}' was active from a previous ARM64 build. " +
248+
"It has been removed. Please rebuild to compile with Windows x64 support.");
249+
}
250+
251+
base.PreBuild(report);
252+
}
253+
}
254+
255+
#if UNITY_6000_0_OR_NEWER
256+
/// <summary>
257+
/// WindowsBuilder for ARM64 deployment. Available in Unity 6000.0+ where
258+
/// Standalone Windows ARM64 is supported as an architecture sub-option of
259+
/// <see cref="BuildTarget.StandaloneWindows64"/>. Steam features are unavailable
260+
/// on this architecture (no Steam binaries ship for Windows ARM64).
261+
/// </summary>
262+
public class WindowsBuilderArm64 : WindowsBuilder
263+
{
264+
private const string PlatformId = WindowsArm64Define.Architecture;
265+
266+
public WindowsBuilderArm64() : base($"Plugins/Windows/{PlatformId}", BuildTarget.StandaloneWindows64)
45267
{
46268
AddProjectFileToBinaryMapping(
47269
"DynamicLibraryLoaderHelper/DynamicLibraryLoaderHelper.sln",
48-
"DynamicLibraryLoaderHelper-x64.dll",
49-
"GfxPluginNativeRender-x64.dll");
270+
$"DynamicLibraryLoaderHelper-{PlatformId}.dll",
271+
$"GfxPluginNativeRender-{PlatformId}.dll");
272+
}
273+
274+
public override string GetPlatformString()
275+
{
276+
return PlatformId;
277+
}
278+
279+
protected override bool ShouldHandle(BuildReport report)
280+
{
281+
if (!base.ShouldHandle(report))
282+
{
283+
return false;
284+
}
285+
return WindowsArm64Define.IsArm64Active();
286+
}
287+
288+
public override void PreBuild(BuildReport report)
289+
{
290+
Debug.Log(WindowsArm64Define.IsArm64Active()
291+
? "Targeting Windows ARM64"
292+
: "Targeting Windows x64 (Intel/AMD)");
293+
294+
// Safety net for scripted/CI builds that bypass RegisterBuildPlayerHandler.
295+
if (!WindowsArm64Define.IsDefineSet())
296+
{
297+
ScriptingDefineUtility.AddDefine(BuildTarget.StandaloneWindows64, WindowsArm64Define.Symbol);
298+
throw new BuildFailedException(
299+
$"Scripting define '{WindowsArm64Define.Symbol}' was not set before this build. " +
300+
"It has been added. Please rebuild to compile with Windows ARM64 support.");
301+
}
302+
303+
base.PreBuild(report);
50304
}
51305
}
306+
#endif
52307

53308
/// <summary>
54309
/// WindowsBuilder for 32-bit deployment.
55310
/// </summary>
56311
public class WindowsBuilder32 : WindowsBuilder
57312
{
313+
private const string PlatformId = "Win32";
314+
58315
public WindowsBuilder32() : base("Plugins/Windows/x86", BuildTarget.StandaloneWindows)
59316
{
60317
// TODO: These libraries do not appear to be building properly - and the process
@@ -65,6 +322,11 @@ public WindowsBuilder32() : base("Plugins/Windows/x86", BuildTarget.StandaloneWi
65322
"DynamicLibraryLoaderHelper-x86.dll",
66323
"GfxPluginNativeRender-x86.dll");
67324
}
325+
326+
public override string GetPlatformString()
327+
{
328+
return PlatformId;
329+
}
68330
}
69331

70332
/// <summary>

0 commit comments

Comments
 (0)