@@ -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