A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.IO; 4using System.Linq; 5using System.Xml.Linq; 6using Unity.VisualScripting; 7using UnityEditor; 8using UnityEditor.Build; 9using UnityEditor.Build.Reporting; 10using UnityEditor.Callbacks; 11using UnityEditor.SceneManagement; 12using UnityEngine; 13using UnityEngine.SceneManagement; 14 15internal class LinkerCreator : IPreprocessBuildWithReport 16{ 17 private static string linkerPath => Path.Combine(BoltCore.Paths.persistentGenerated, "link.xml"); 18 19 public int callbackOrder { get; } 20 21 private static ManagedStrippingLevel GetManagedStrippingLevel(BuildTargetGroup buildTarget) 22 { 23#if UNITY_2023_1_OR_NEWER 24 var namedBuildTarget = UnityEditor.Build.NamedBuildTarget.FromBuildTargetGroup(buildTarget); 25 return PlayerSettings.GetManagedStrippingLevel(namedBuildTarget); 26#else 27 return PlayerSettings.GetManagedStrippingLevel(buildTarget); 28#endif 29 } 30 31 public void OnPreprocessBuild(BuildReport report) 32 { 33 if (VSUsageUtility.isVisualScriptingUsed) 34 { 35 try 36 { 37 if (GetManagedStrippingLevel(EditorUserBuildSettings.selectedBuildTargetGroup) != 38 ManagedStrippingLevel.Disabled) 39 { 40 GenerateLinker(); 41 } 42 } 43 catch (Exception ex) 44 { 45 Debug.LogException(ex); 46 47 DeleteLinker(); 48 } 49 } 50 } 51 52 [PostProcessBuild] 53 private static void OnPostprocessBuild(BuildTarget buildTarget, string path) 54 { 55 if (VSUsageUtility.isVisualScriptingUsed) 56 { 57 DeleteLinker(); 58 } 59 } 60 61 private static void DeleteLinker() 62 { 63 PathUtility.DeleteProjectFileIfExists(linkerPath, true); 64 } 65 66 // Automatically generates the link.xml file to prevent stripping. 67 // Currently only used for plugin assemblies, because blanket preserving 68 // all setting assemblies sometimes causes the IL2CPP process to fail. 69 // For settings assemblies, the AOT stubs are good enough to fool 70 // the static code analysis without needing this full coverage. 71 // https://docs.unity3d.com/Manual/iphone-playerSizeOptimization.html 72 // However, for FullSerializer, we need to preserve our custom assemblies. 73 // This is mostly because IL2CPP will attempt to transform non-public 74 // property setters used in deserialization into read-only accessors 75 // that return false on PropertyInfo.CanWrite, but only in stripped builds. 76 // Therefore, in stripped builds, FS will skip properties that should be 77 // deserialized without any error (and that took hours of debugging to figure out). 78 public static void GenerateLinker() 79 { 80 var linker = new XDocument(); 81 82 var linkerNode = new XElement("linker"); 83 84 if (!PluginContainer.initialized) 85 PluginContainer.Initialize(); 86 87 foreach (var pluginAssembly in PluginContainer.plugins 88 .SelectMany(plugin => plugin.GetType() 89 .GetAttributes<PluginRuntimeAssemblyAttribute>() 90 .Select(a => a.assemblyName)) 91 .Distinct()) 92 { 93 var assemblyNode = new XElement("assembly"); 94 var fullnameAttribute = new XAttribute("fullname", pluginAssembly); 95 var preserveAttribute = new XAttribute("preserve", "all"); 96 assemblyNode.Add(fullnameAttribute); 97 assemblyNode.Add(preserveAttribute); 98 linkerNode.Add(assemblyNode); 99 } 100 101 linker.Add(linkerNode); 102 103 AddCustomNodesToLinker(linkerNode, PluginContainer.plugins); 104 105 PathUtility.CreateDirectoryIfNeeded(BoltCore.Paths.transientGenerated); 106 107 PathUtility.DeleteProjectFileIfExists(linkerPath, true); 108 109 // Using ToString instead of Save to omit the <?xml> declaration, 110 // which doesn't appear in the Unity documentation page for the linker. 111 File.WriteAllText(linkerPath, linker.ToString()); 112 } 113 114 private static void AddCustomNodesToLinker(XElement linkerNode, IEnumerable<Plugin> plugins) 115 { 116 var types = FindAllCustomTypes(); 117 118 var customTypes = types.Where(t => !plugins.Any(p => t.Assembly == p.runtimeAssembly)); 119 120 foreach (var type in customTypes) 121 { 122 var userAssembly = type.Assembly.GetName().Name; 123 124 var assemblyNode = new XElement("assembly"); 125 var fullnameAttribute = new XAttribute("fullname", userAssembly); 126 var preserveAttribute = new XAttribute("preserve", "all"); 127 128 assemblyNode.Add(fullnameAttribute); 129 130 var customType = new XElement("type"); 131 var fullnameType = new XAttribute("fullname", type.FullName); 132 133 customType.Add(fullnameType); 134 customType.Add(preserveAttribute); 135 136 assemblyNode.Add(customType); 137 138 linkerNode.Add(assemblyNode); 139 } 140 } 141 142 private static void ProcessSubGraphs(HashSet<Type> types, SubgraphUnit subgraph) 143 { 144 foreach (var unit in subgraph.nest.graph.units) 145 { 146 AddTypeToHashSet(types, unit); 147 } 148 } 149 150 private static void AddTypeToHashSet(HashSet<Type> types, IUnit unit) 151 { 152 if (unit.GetType() == typeof(SubgraphUnit)) 153 { 154 ProcessSubGraphs(types, (SubgraphUnit)unit); 155 } 156 else 157 { 158 types.Add(unit.GetType()); 159 } 160 } 161 162 private static HashSet<Type> FindGraphsOnAssets() 163 { 164 var scriptGraphAssets = AssetUtility.GetAllAssetsOfType<ScriptGraphAsset>(); 165 166 var types = new HashSet<Type>(); 167 168 var index = 0; 169 var total = scriptGraphAssets.Count(); 170 171 foreach (var scriptGraphAsset in scriptGraphAssets) 172 { 173 if (EditorUtility.DisplayCancelableProgressBar($"Processing on assets {index}/{total}", 174 $"Asset {scriptGraphAsset.name}", (float)index / (float)total)) 175 { 176 break; 177 } 178 179 index++; 180 181 if (scriptGraphAsset.graph != null) 182 { 183 foreach (var unit in scriptGraphAsset.graph.units) 184 { 185 AddTypeToHashSet(types, unit); 186 } 187 } 188 } 189 190 EditorUtility.ClearProgressBar(); 191 192 return types; 193 } 194 195 private static HashSet<Type> FindGraphsOnScenes(bool includeGraphAssets) 196 { 197 var activeScenePath = SceneManager.GetActiveScene().path; 198 var scenePaths = EditorBuildSettings.scenes.Select(s => s.path).ToArray(); 199 200 var index = 0; 201 var total = scenePaths.Count(); 202 var types = new HashSet<Type>(); 203 204 foreach (var scenePath in scenePaths) 205 { 206 index++; 207 208 if (EditorUtility.DisplayCancelableProgressBar($"Processing scenes {index} / {total}", $"Scene {scenePath}", 209 (float)index / (float)total)) 210 { 211 break; 212 } 213 214 if (!string.IsNullOrEmpty(scenePath)) 215 { 216 EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); 217 218 var scriptMachines = UnityObjectUtility.FindObjectsOfTypeIncludingInactive<ScriptMachine>(); 219 220 foreach (var scriptMachine in scriptMachines) 221 { 222 if (scriptMachine.nest != null && 223 (scriptMachine.nest.source == GraphSource.Macro && includeGraphAssets) || 224 scriptMachine.nest.source == GraphSource.Embed) 225 { 226 foreach (var unit in scriptMachine.graph.units) 227 { 228 AddTypeToHashSet(types, unit); 229 } 230 } 231 } 232 } 233 } 234 235 if (!string.IsNullOrEmpty(activeScenePath)) 236 { 237 EditorSceneManager.OpenScene(activeScenePath); 238 } 239 240 GC.Collect(); 241 242 EditorUtility.ClearProgressBar(); 243 244 return types; 245 } 246 247 private static HashSet<Type> FindGraphsOnPrefabs(bool includeGraphAssets) 248 { 249 var types = new HashSet<Type>(); 250 251 var files = System.IO.Directory.GetFiles(Application.dataPath, "*.prefab", 252 System.IO.SearchOption.AllDirectories); 253 254 var index = 0; 255 var total = files.Count(); 256 257 var currentScene = EditorSceneManager.GetActiveScene(); 258 var scenePath = currentScene.path; 259 260 EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); 261 262 foreach (var file in files) 263 { 264 index++; 265 266 if (EditorUtility.DisplayCancelableProgressBar($"Processing prefabs {index} / {total}", $"Prefab {file}", 267 (float)index / (float)total)) 268 { 269 break; 270 } 271 272 var prefabPath = file.Replace(Application.dataPath, "Assets"); 273 274 var prefab = UnityEditor.AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject)) as GameObject; 275 276 if (prefab != null) 277 { 278 FindGraphInPrefab(types, prefab, includeGraphAssets); 279 prefab = null; 280 281 EditorUtility.UnloadUnusedAssetsImmediate(true); 282 } 283 } 284 285 EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single); 286 287 EditorUtility.UnloadUnusedAssetsImmediate(true); 288 289 GC.Collect(); 290 291 EditorUtility.ClearProgressBar(); 292 293 return types; 294 } 295 296 private static void FindGraphInPrefab(HashSet<Type> types, GameObject gameObject, bool includeGraphAssets) 297 { 298 var scriptMachines = gameObject.GetComponents<ScriptMachine>(); 299 300 foreach (var scriptMachine in scriptMachines) 301 { 302 if (scriptMachine.nest != null && 303 (scriptMachine.nest.source == GraphSource.Macro && includeGraphAssets) || 304 scriptMachine.nest.source == GraphSource.Embed) 305 { 306 foreach (var unit in scriptMachine.graph.units) 307 { 308 AddTypeToHashSet(types, unit); 309 } 310 } 311 } 312 313 foreach (Transform child in gameObject.transform) 314 { 315 FindGraphInPrefab(types, child.gameObject, includeGraphAssets); 316 } 317 } 318 319 private static HashSet<Type> FindAllCustomTypes() 320 { 321 var types = new HashSet<Type>(); 322 323 var settings = (List<bool>)BoltCore.Configuration.GetMetadata("LinkerPropertyProviderSettings").value; 324 325 if (settings[(int)BoltCoreConfiguration.LinkerScanTarget.GraphAssets]) 326 { 327 types.AddRange(FindGraphsOnAssets()); 328 } 329 330 var includeGraphAssets = !settings[(int)BoltCoreConfiguration.LinkerScanTarget.GraphAssets]; 331 332 if (settings[(int)BoltCoreConfiguration.LinkerScanTarget.EmbeddedSceneGraphs]) 333 { 334 types.AddRange(FindGraphsOnScenes(includeGraphAssets)); 335 } 336 337 if (settings[(int)BoltCoreConfiguration.LinkerScanTarget.EmbeddedPrefabGraphs]) 338 { 339 types.AddRange(FindGraphsOnPrefabs(includeGraphAssets)); 340 } 341 342 return types; 343 } 344}