A game about forced loneliness, made by TACStudios
1using System;
2using System.IO;
3using System.Linq;
4using System.Collections.Generic;
5using System.Reflection;
6using UnityEditor.SceneManagement;
7using UnityEditor.U2D.Sprites;
8using UnityEngine;
9using UnityEngine.SceneManagement;
10using UnityEngine.U2D.Animation;
11
12using Object = UnityEngine.Object;
13
14namespace UnityEditor.U2D.Animation.Upgrading
15{
16 internal class SpriteLibUpgrader : BaseUpgrader
17 {
18 static class Contents
19 {
20 public static readonly string ProgressBarTitle = L10n.Tr("Upgrading Sprite Library Assets");
21 public static readonly string VerifyingSelection = L10n.Tr("Verifying the selection");
22 public static readonly string CreatingNewLibraries = L10n.Tr("Creating new Sprite Library Assets");
23 public static readonly string ReassignAssetsInComponents = L10n.Tr("Re-assigning assets in components");
24 public static readonly string RemoveOldSpriteLibraries = L10n.Tr("Removing old Sprite Library Assets");
25 }
26
27 const string k_SpriteLibTypeId = "t:SpriteLibraryAsset";
28 const string k_PsbImporterCategoriesId = "m_SpriteCategoryList.m_Categories";
29 static readonly Dictionary<Type, List<FieldInfo>> k_SpriteLibraryReferenceLookup;
30
31 bool m_OnlyFindOldAssets;
32 bool m_OnlySearchInAssets;
33
34 HashSet<int> m_IndicesWithAssetBundleConnection = new HashSet<int>();
35 HashSet<string> m_AssetBundlesNeedingUpgrade = new HashSet<string>();
36
37 static SpriteLibUpgrader()
38 {
39 k_SpriteLibraryReferenceLookup = GetSpriteLibReferenceLookup();
40 }
41
42 /// <param name="onlyFindOldAssets">Set this to true if you only want to find .asset SpriteLibraries</param>
43 /// <param name="onlySearchInAssets">Set this to true if you only want to find SpriteLibraries in the Assets/ folder or its children</param>
44 public SpriteLibUpgrader(bool onlyFindOldAssets = true, bool onlySearchInAssets = true)
45 {
46 m_OnlyFindOldAssets = onlyFindOldAssets;
47 m_OnlySearchInAssets = onlySearchInAssets;
48 }
49
50 static Dictionary<Type, List<FieldInfo>> GetSpriteLibReferenceLookup()
51 {
52 var result = new Dictionary<Type, List<FieldInfo>>();
53
54 var allObjectsWithSpriteLibProperties = TypeCache
55 .GetTypesDerivedFrom<Component>()
56 .Where(type => type
57 .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
58 .Any(HasSpriteLibField));
59
60 foreach (var property in allObjectsWithSpriteLibProperties)
61 {
62 if (!result.ContainsKey(property))
63 result.Add(property, new List<FieldInfo>());
64
65 var libraryFields = property
66 .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
67 .Where(HasSpriteLibField)
68 .ToList();
69
70 foreach (var field in libraryFields)
71 {
72 result[property].Add(field);
73 }
74 }
75
76 return result;
77 }
78
79 static bool HasSpriteLibField(FieldInfo field)
80 {
81 return (field.FieldType == typeof(SpriteLibraryAsset) ||
82 field.FieldType == typeof(SpriteLibraryAsset[]) ||
83 field.FieldType == typeof(List<SpriteLibraryAsset>)) &&
84 (field.IsPublic || field.IsDefined(typeof(SerializeField)));
85 }
86
87 internal override List<Object> GetUpgradableAssets()
88 {
89 var rootFolder = m_OnlySearchInAssets ? new[] { "Assets" } : null;
90 var assetPaths = AssetDatabase.FindAssets(k_SpriteLibTypeId, rootFolder)
91 .Select(AssetDatabase.GUIDToAssetPath).ToArray();
92
93 if (m_OnlyFindOldAssets)
94 {
95 assetPaths = assetPaths
96 .Where(x => x.EndsWith(".asset") || UpgradeUtilities.IsPsbImportedFile(x))
97 .ToArray();
98 }
99
100 var assets = assetPaths
101 .Select(AssetDatabase.LoadAssetAtPath<SpriteLibraryAsset>)
102 .Cast<Object>()
103 .ToList();
104 return assets;
105 }
106
107 internal override UpgradeReport UpgradeSelection(List<ObjectIndexPair> objects)
108 {
109 EditorUtility.DisplayProgressBar(Contents.ProgressBarTitle, Contents.VerifyingSelection, 0f);
110
111 var entries = new List<UpgradeEntry>();
112
113 var libraryIndexPairs = new Dictionary<int, SpriteLibraryAsset>();
114 string msg;
115 foreach (var obj in objects)
116 {
117 if (obj.Target == null)
118 {
119 msg = "The upgrade failed. Invalid selection.";
120 m_Logger.Add(msg);
121 m_Logger.AddLineBreak();
122 entries.Add(new UpgradeEntry()
123 {
124 Result = UpgradeResult.Error,
125 Target = obj.Target,
126 Index = obj.Index,
127 Message = msg
128 });
129 }
130 else if (obj.Target is SpriteLibraryAsset lib)
131 {
132 libraryIndexPairs.Add(obj.Index, lib);
133 }
134 else
135 {
136 msg = $"The upgrade failed. {obj.Target.name} is not a SpriteLibraryAsset.";
137 m_Logger.Add(msg);
138 m_Logger.AddLineBreak();
139 entries.Add(new UpgradeEntry()
140 {
141 Result = UpgradeResult.Error,
142 Target = obj.Target,
143 Index = obj.Index,
144 Message = msg
145 });
146 }
147 }
148 var oldLibraries = libraryIndexPairs.Values.ToList();
149
150 EditorUtility.DisplayProgressBar(Contents.ProgressBarTitle, Contents.CreatingNewLibraries, 0.2f);
151 m_Logger.AddLineBreak();
152 m_Logger.Add(Contents.CreatingNewLibraries);
153 var newSourceAssetPaths = CreateNewAssetLibraries(libraryIndexPairs);
154 AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
155 var newLibraries = LoadNewLibraries(newSourceAssetPaths);
156
157 EditorUtility.DisplayProgressBar(Contents.ProgressBarTitle, Contents.ReassignAssetsInComponents, 0.4f);
158 m_Logger.AddLineBreak();
159 m_Logger.Add(Contents.ReassignAssetsInComponents);
160 var assetPaths = GetAllAssetPaths(libraryIndexPairs);
161 if (assetPaths.Length > 0)
162 ReassignAssets(assetPaths, oldLibraries, newLibraries);
163
164 EditorUtility.DisplayProgressBar(Contents.ProgressBarTitle, Contents.RemoveOldSpriteLibraries, 0.8f);
165 m_Logger.AddLineBreak();
166 m_Logger.Add(Contents.RemoveOldSpriteLibraries);
167 RemoveOldLibraries(oldLibraries);
168 AssetDatabase.Refresh();
169
170 for (var i = 0; i < newLibraries.Count; ++i)
171 {
172 UpgradeResult result;
173 if (m_IndicesWithAssetBundleConnection.Contains(newSourceAssetPaths[i].Item1))
174 {
175 msg = $"Successfully replaced {newLibraries[i].name}. Note that the asset is connected to an AssetBundle. Make sure to rebuild this AssetBundle to complete the upgrade. See more info in the upgrade log.";
176 result = UpgradeResult.Warning;
177 }
178 else
179 {
180 msg = $"Successfully replaced {newLibraries[i].name}";
181 result = UpgradeResult.Successful;
182 }
183
184 m_Logger.Add(msg);
185 entries.Add(new UpgradeEntry()
186 {
187 Result = result,
188 Target = newLibraries[i],
189 Index = newSourceAssetPaths[i].Item1,
190 Message = msg
191 });
192 }
193
194 EditorUtility.ClearProgressBar();
195
196 if (m_AssetBundlesNeedingUpgrade.Count > 0)
197 AddAssetBundlesToLog();
198
199 var report = new UpgradeReport()
200 {
201 UpgradeEntries = entries,
202 Log = m_Logger.GetLog()
203 };
204
205 m_Logger.Clear();
206 m_AssetBundlesNeedingUpgrade.Clear();
207 return report;
208 }
209
210 static string[] GetAllAssetPaths(Dictionary<int, SpriteLibraryAsset> spriteLibraries)
211 {
212 var ids = new List<string>();
213 foreach(var lib in spriteLibraries.Values)
214 ids.Add(GetObjectIDString(lib));
215 var spriteLibIds = ids.ToArray();
216
217 var assetPaths = new List<string>();
218 var allAssetPaths = AssetDatabase.GetAllAssetPaths();
219 foreach (var path in allAssetPaths)
220 {
221 if (!IsPrefabOrScenePath(path, spriteLibIds))
222 continue;
223
224 assetPaths.Add(path);
225 }
226
227 return assetPaths.ToArray();
228 }
229
230 static string GetObjectIDString(Object obj)
231 {
232 if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj.GetInstanceID(), out string guid, out long localId))
233 return "fileID: " + localId + ", guid: " + guid;
234
235 return null;
236 }
237
238 static bool IsPrefabOrScenePath(string path, string[] ids)
239 {
240 if (string.IsNullOrEmpty(path))
241 throw new ArgumentNullException(nameof(path));
242
243 if (path.StartsWith("Packages"))
244 return false;
245
246 if (path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase) || path.EndsWith(".unity", StringComparison.OrdinalIgnoreCase))
247 return DoesFileContainString(path, ids);
248
249 return false;
250 }
251
252 static bool DoesFileContainString(string path, string[] strings)
253 {
254 if (strings != null && strings.Length > 0)
255 {
256 using (var file = File.OpenText(path))
257 {
258 string line;
259 while ((line = file.ReadLine()) != null)
260 {
261 for (var i = 0; i < strings.Length; i++)
262 {
263 if (line.Contains(strings[i]))
264 return true;
265 }
266 }
267 }
268 }
269
270 return false;
271 }
272
273 List<(int, string)> CreateNewAssetLibraries(Dictionary<int, SpriteLibraryAsset> spriteLibraries)
274 {
275 var newLibraryPaths = new List<(int, string)>();
276 foreach (var pair in spriteLibraries)
277 {
278 var sourceAsset = pair.Value;
279
280 var path = AssetDatabase.GetAssetPath(sourceAsset);
281 var currentAssetPath = Path.GetDirectoryName(path);
282 var fileName = Path.GetFileNameWithoutExtension(path);
283 var convertFileName = fileName + SpriteLibrarySourceAsset.extension;
284 convertFileName = AssetDatabase.GenerateUniqueAssetPath(Path.Combine(currentAssetPath, convertFileName));
285
286 var destAsset = ScriptableObject.CreateInstance<SpriteLibrarySourceAsset>();
287 destAsset.SetLibrary(new List<SpriteLibCategoryOverride>(sourceAsset.categories.Count));
288 foreach (var sourceCat in sourceAsset.categories)
289 {
290 var destCat = new SpriteLibCategoryOverride()
291 {
292 overrideEntries = new List<SpriteCategoryEntryOverride>(sourceCat.categoryList.Count),
293 name = sourceCat.name,
294 entryOverrideCount = sourceCat.categoryList.Count,
295 fromMain = false
296 };
297 destAsset.AddCategory(destCat);
298 foreach (var entry in sourceCat.categoryList)
299 {
300 destCat.overrideEntries.Add(new SpriteCategoryEntryOverride()
301 {
302 name = entry.name,
303 sprite = entry.sprite,
304 fromMain = false,
305 spriteOverride = entry.sprite
306 });
307 }
308 }
309
310 var assetBundle = AssetDatabase.GetImplicitAssetBundleName(path);
311 if (!string.IsNullOrEmpty(assetBundle))
312 {
313 m_AssetBundlesNeedingUpgrade.Add(assetBundle);
314 m_IndicesWithAssetBundleConnection.Add(pair.Key);
315 m_Logger.Add($"{sourceAsset.name} is connected with the following AssetBundle: {assetBundle}.");
316 }
317
318 SpriteLibrarySourceAssetImporter.SaveSpriteLibrarySourceAsset(destAsset, convertFileName);
319 newLibraryPaths.Add(new ValueTuple<int, string>(pair.Key, convertFileName));
320 m_Logger.Add($"Created a new SpriteLibrary with the data of {sourceAsset.name}. The new SpriteLibrary is located at: {convertFileName}");
321 }
322
323 return newLibraryPaths;
324 }
325
326 static List<SpriteLibraryAsset> LoadNewLibraries(List<(int, string)> sourceAssetPaths)
327 {
328 var newLibraries = new List<SpriteLibraryAsset>();
329 foreach (var path in sourceAssetPaths)
330 {
331 var newLibraryAsset = AssetDatabase.LoadAssetAtPath<SpriteLibraryAsset>(path.Item2);
332 newLibraries.Add(newLibraryAsset);
333 }
334
335 return newLibraries;
336 }
337
338 void ReassignAssets(string[] assetPaths, List<SpriteLibraryAsset> oldLibraries, List<SpriteLibraryAsset> newLibraries)
339 {
340 var index = 0;
341 foreach (var assetPath in assetPaths)
342 {
343 m_Logger.Add($"Scanning {assetPath} for components with SpriteLibraryAsset references in need of reassignment.");
344 var ext = Path.GetExtension(assetPath);
345 if (ext == ".prefab")
346 UpgradePrefab(assetPath, oldLibraries, newLibraries, UpgradeGameObject);
347 else if (ext == ".unity")
348 UpgradeScene(assetPath, oldLibraries, newLibraries, UpgradeGameObject);
349
350 var assetBundle = AssetDatabase.GetImplicitAssetBundleName(assetPath);
351 if (!string.IsNullOrEmpty(assetBundle))
352 {
353 m_AssetBundlesNeedingUpgrade.Add(assetBundle);
354 m_IndicesWithAssetBundleConnection.Add(index);
355 }
356
357 index++;
358 }
359 }
360
361 static void UpgradePrefab(string path, List<SpriteLibraryAsset> oldLibraries, List<SpriteLibraryAsset> newLibraries,
362 Action<GameObject, List<SpriteLibraryAsset>, List<SpriteLibraryAsset>> objectUpgrader)
363 {
364 var objects = AssetDatabase.LoadAllAssetsAtPath(path);
365
366 var firstIndex = 0;
367 for (var i = 0; i < objects.Length; i++)
368 {
369 if (objects[i] as GameObject)
370 {
371 firstIndex = i;
372 break;
373 }
374 }
375
376 if (!PrefabUtility.IsPartOfImmutablePrefab(objects[firstIndex]))
377 {
378 foreach (var obj in objects)
379 {
380 var go = obj as GameObject;
381 if (go != null)
382 {
383 objectUpgrader(go, oldLibraries, newLibraries);
384 }
385 }
386
387 var asset = objects[firstIndex] as GameObject;
388 PrefabUtility.SavePrefabAsset(asset.transform.root.gameObject);
389 }
390 }
391
392 static void UpgradeScene(string path, List<SpriteLibraryAsset> oldLibraries, List<SpriteLibraryAsset> newLibraries,
393 Action<GameObject, List<SpriteLibraryAsset>, List<SpriteLibraryAsset>> objectUpgrader)
394 {
395 var scene = default(Scene);
396 var openedByUser = false;
397 for (var i = 0; i < SceneManager.sceneCount && !openedByUser; i++)
398 {
399 scene = SceneManager.GetSceneAt(i);
400 if (path == scene.path)
401 openedByUser = true;
402 }
403
404 if (!openedByUser)
405 scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Additive);
406
407 var gameObjects = scene.GetRootGameObjects();
408 foreach (var go in gameObjects)
409 objectUpgrader(go, oldLibraries, newLibraries);
410
411 EditorSceneManager.SaveScene(scene);
412 if (!openedByUser)
413 EditorSceneManager.CloseScene(scene, true);
414 }
415
416 void UpgradeGameObject(GameObject go, List<SpriteLibraryAsset> oldLibraries, List<SpriteLibraryAsset> newLibraries)
417 {
418 var types = k_SpriteLibraryReferenceLookup.Keys;
419 foreach (var referenceType in types)
420 {
421 var components = go.GetComponentsInChildren(referenceType);
422 foreach (var component in components)
423 {
424 if (PrefabUtility.IsPartOfPrefabInstance(component))
425 continue;
426
427 var fieldInfos = k_SpriteLibraryReferenceLookup[referenceType];
428 foreach (var field in fieldInfos)
429 {
430 var asset = field.GetValue(component);
431 if (asset is SpriteLibraryAsset spriteLibAsset)
432 {
433 var index = oldLibraries.FindIndex(x => x.GetHashCode() == spriteLibAsset.GetHashCode());
434 if (index == -1)
435 continue;
436
437 field.SetValue(component, newLibraries[index]);
438 m_Logger.Add($"Updated the SpriteLibraryAsset reference in {component.GetType()} on the GameObject {component.name}");
439 }
440 else if (asset is SpriteLibraryAsset[] spriteLibArray)
441 {
442 for (var i = 0; i < spriteLibArray.Length; ++i)
443 {
444 var index = oldLibraries.FindIndex(x => x.GetHashCode() == spriteLibArray[i].GetHashCode());
445 if (index == -1)
446 continue;
447
448 spriteLibArray[i] = newLibraries[index];
449 }
450
451 field.SetValue(component, spriteLibArray);
452 m_Logger.Add($"Updated the SpriteLibraryAsset[] reference in {component.GetType()} on the GameObject {component.name}");
453 }
454 else if (asset is List<SpriteLibraryAsset> spriteLibList)
455 {
456 for (var i = 0; i < spriteLibList.Count; ++i)
457 {
458 var index = oldLibraries.FindIndex(x => x.GetHashCode() == spriteLibList[i].GetHashCode());
459 if (index == -1)
460 continue;
461
462 spriteLibList[i] = newLibraries[index];
463 }
464
465 field.SetValue(component, spriteLibList);
466 m_Logger.Add($"Updated the List<SpriteLibraryAsset> reference in {component.GetType()} on the GameObject {component.name}");
467 }
468 }
469 }
470 }
471 }
472
473 void RemoveOldLibraries(List<SpriteLibraryAsset> oldLibraries)
474 {
475 foreach (var library in oldLibraries)
476 {
477 var path = AssetDatabase.GetAssetPath(library);
478 var isPsbFile = UpgradeUtilities.IsPsbImportedFile(path);
479 if (!string.IsNullOrEmpty(path) && !isPsbFile)
480 {
481 m_Logger.Add($"Deleting {path} from project");
482 AssetDatabase.DeleteAsset(path);
483 }
484 }
485 }
486
487 // Leaving this in if we want to cleanup .psbs in the future
488 void RemoveSpriteLibFromPsb(string path)
489 {
490 var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(path);
491
492 var factory = new SpriteDataProviderFactories();
493 factory.Init();
494 var dataProvider = factory.GetSpriteEditorDataProviderFromObject(texture);
495 dataProvider.InitSpriteEditorDataProvider();
496 if (dataProvider.targetObject == null)
497 {
498 m_Logger.Add($"Could not load the PSDImporter from the path: {path}. Aborting the Sprite Library cleanup inside the .psb.");
499 return;
500 }
501
502 var so = new SerializedObject(dataProvider.targetObject);
503 var property = so.FindProperty(k_PsbImporterCategoriesId);
504 if (property != null && property.isArray)
505 {
506 property.arraySize = 0;
507 so.ApplyModifiedPropertiesWithoutUndo();
508 dataProvider.Apply();
509 m_Logger.Add($"Removed the Sprite Library asset inside {path}");
510
511 var assetImporter = dataProvider.targetObject as AssetImporter;
512 assetImporter.SaveAndReimport();
513 m_Logger.Add($"Saved and re-imported the file.");
514 }
515 else
516 {
517 m_Logger.Add($"Could not find any Sprite Library asset inside the .psb to cleanup.");
518 }
519 }
520
521 void AddAssetBundlesToLog()
522 {
523 m_Logger.AddLineBreak();
524 m_Logger.Add("[NOTE] The following AssetBundles need to be rebuilt:");
525 foreach(var assetBundle in m_AssetBundlesNeedingUpgrade)
526 m_Logger.Add(assetBundle);
527 }
528 }
529}