using AssetRipper.Assets; using AssetRipper.Assets.Bundles; using AssetRipper.Assets.Collections; using AssetRipper.Assets.Generics; using AssetRipper.Assets.IO; using AssetRipper.Assets.Metadata; using AssetRipper.Import.AssetCreation; using AssetRipper.Import.Structure.Assembly.Serializable; using AssetRipper.Import.Structure.Assembly.TypeTrees; using AssetRipper.IO.Endian; using AssetRipper.IO.Files.SerializedFiles.Parser; using AssetRipper.Mining.PredefinedAssets; using Object = AssetRipper.Mining.PredefinedAssets.Object; namespace AssetRipper.Mining.EngineFileExtractor; internal static class Program { private const string DefaultResourcesName = "unity default resources"; private const string ExtraResourcesName = "unity_builtin_extra"; private const string ResourcesFolderPath = @"Editor/Data/Resources"; public static void Main(string[] args) { string unityFolderPath = args[0]; bool silent = args.Length > 1 && args[1] == "--silent"; Dictionary defaultDictionary = ReadDictionary(Path.Combine(unityFolderPath, ResourcesFolderPath, DefaultResourcesName)); Dictionary extraDictionary = ReadDictionary(Path.Combine(unityFolderPath, ResourcesFolderPath, ExtraResourcesName)); File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "engineassets.json"), new EngineResourceData(defaultDictionary, extraDictionary).ToJson()); if (!silent) { foreach ((string name, Dictionary dictionary) in new[] { ("Default", defaultDictionary), ("Extra", extraDictionary) }) { Dictionary typeIDs = dictionary.Values .Select(a => a.TypeID).Distinct().Order() .ToDictionary(id => id, id => dictionary.Values.Count(a => a.TypeID == id)); Console.WriteLine(name); foreach ((int typeID, int count) in typeIDs) { Console.WriteLine($"\t{typeID,4} : {count,3}"); } } Console.WriteLine("Done!"); } } private static Dictionary ReadDictionary(string path) { if (!File.Exists(path)) { return []; } return LoadAllAssetInfo(path).OrderBy(pair => pair.Key).ToDictionary(pair => pair.Key, pair => pair.Value); } private static AssetCollection LoadAllAssets(string path) { return GameBundle.FromPaths([path], TypeTreeAssetFactory.Instance).FetchAssetCollections().Single(); } private sealed class TypeTreeAssetFactory : AssetFactoryBase { public static TypeTreeAssetFactory Instance { get; } = new(); public override IUnityObjectBase? ReadAsset(AssetInfo assetInfo, ReadOnlyArraySegment assetData, SerializedType? assetType) { ArgumentNullException.ThrowIfNull(assetType); ArgumentOutOfRangeException.ThrowIfZero(assetType.OldType.Nodes.Count); if (!TypeTreeNodeStruct.TryMakeFromTypeTree(assetType.OldType, out TypeTreeNodeStruct root)) { throw new NotSupportedException("Type tree node struct creation failed"); } else { TypeTreeObject asset = TypeTreeObject.Create(assetInfo, root); EndianSpanReader reader = new(assetData, assetInfo.Collection.EndianType); asset.Read(ref reader); return asset; } } } private static IEnumerable> LoadAllAssetInfo(string path) { foreach (TypeTreeObject asset in LoadAllAssets(path).Cast()) { Object obj; switch (asset.ClassID) { case 21://Material { obj = new Material() { Name = asset.Name(), Shader = asset.TryGetAsset("m_Shader")?.Name() }; } break; case 28://Texture2D { obj = new Texture2D() { Name = asset.Name(), Height = asset.GetInt32("m_Height"), Width = asset.GetInt32("m_Width") }; } break; case 43://Mesh { obj = new Mesh() { Name = asset.Name(), VertexCount = asset.ReleaseFields.ContainsField("m_VertexData") ? asset.ReleaseFields["m_VertexData"]["m_VertexCount"].AsInt32 : asset.ReleaseFields["m_CompressedMesh"]["m_Vertices"]["m_NumItems"].AsInt32, SubMeshCount = asset.ReleaseFields["m_SubMeshes"].AsAssetArray.Length, LocalAABB = asset.ReleaseFields["m_LocalAABB"].AsAxisAlignedBoundingBox(), }; } break; case 48://Shader { string[] propertyNames; SerializableStructure? serializedShader = asset.ReleaseFields.TryGetField("m_ParsedForm")?.AsStructure; if (serializedShader is null) { propertyNames = Array.Empty(); } else { IUnityAssetBase[] propsList = serializedShader["m_PropInfo"]["m_Props"].AsAssetArray; if (propsList.Length == 0) { propertyNames = Array.Empty(); } else { propertyNames = new string[propsList.Length]; for (int i = 0; i < propsList.Length; i++) { propertyNames[i] = ((SerializableStructure)propsList[i])["m_Name"].AsString; } } } obj = new Shader() { Name = asset.Name(), PropertyNames = propertyNames }; } break; case 72://ComputeShader goto default; case 89://Cubemap { obj = new Cubemap() { Name = asset.Name(), Height = asset.GetInt32("m_Height"), Width = asset.GetInt32("m_Width") }; } break; case 114://MonoBehaviour { TypeTreeObject? script = asset.TryGetAsset("m_Script"); obj = new MonoBehaviour() { Name = asset.Name(), AssemblyName = script?.TryGetString("m_AssemblyName") ?? "", Namespace = script?.GetString("m_Namespace") ?? "", ClassName = script?.GetString("m_ClassName") ?? "", GameObject = asset.TryGetAsset("m_GameObject")?.Name(), Enabled = asset.GetBoolean("m_Enabled") }; } break; case 115://MonoScript { obj = new MonoScript() { AssemblyName = asset.TryGetString("m_AssemblyName") ?? "", Namespace = asset.GetString("m_Namespace"), ClassName = asset.GetString("m_ClassName") }; } break; case 128://Font goto default; case 213://Sprite { obj = new Sprite() { Name = asset.Name(), Texture = asset.ResolveAsset(asset.ReleaseFields["m_RD"]["texture"].AsPPtr)?.Name(), }; } break; case 1113://LightmapParameters goto default; default: obj = new GenericNamedObject() { TypeID = asset.ClassID, Name = asset.Name() }; break; } yield return new KeyValuePair(asset.PathID, obj); } } }