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.IO.Endian; using AssetRipper.IO.Files; using AssetRipper.IO.Files.SerializedFiles; using AssetRipper.IO.Files.SerializedFiles.Parser; using AssetRipper.SourceGenerated; using AssetRipper.SourceGenerated.Classes.ClassID_28; using AssetRipper.SourceGenerated.Classes.ClassID_89; using AssetRipper.SourceGenerated.Extensions; using System.Diagnostics; namespace AssetRipper.Tools.RawTextureExtractor; internal static class Program { private static readonly string outputDirectory = Path.Join(AppContext.BaseDirectory, "Output"); static void Main(string[] args) { if (Directory.Exists(outputDirectory)) { Directory.Delete(outputDirectory, true); } Directory.CreateDirectory(outputDirectory); if (args.Length == 0) { Console.WriteLine("No arguments"); } else { LoadFiles(args); Console.WriteLine("Done!"); } Console.ReadKey(); return; } private static void LoadFiles(string[] files) { foreach (string file in files) { LoadFile(file); } } private static void LoadFile(string fullName) { Console.WriteLine(fullName); #if !DEBUG try #endif { FileBase file = SchemeReader.LoadFile(fullName, LocalFileSystem.Instance); if (file is SerializedFile serializedFile) { GameBundle bundle = new(); SerializedAssetCollection collection = bundle.AddCollectionFromSerializedFile(serializedFile, new TextureAssetFactory()); bundle.InitializeAllDependencyLists(); Extract(collection); } else if (file is FileContainer container) { file.ReadContents(); SerializedBundle serializedBundle = SerializedBundle.FromFileContainer(container, new TextureAssetFactory()); foreach (AssetCollection collection in serializedBundle.FetchAssetCollections()) { Extract(collection); } } else { Console.WriteLine($"File is {file.GetType()}"); } } #if !DEBUG catch (Exception ex) { Console.WriteLine(ex.ToString()); } #endif } private static void Extract(AssetCollection collection) { const string jsonExtension = ".json"; string collectionOutputPath = Path.Join(GetReversedName(collection).Reverse().ToArray()); Directory.CreateDirectory(collectionOutputPath); foreach (ITexture2D texture in collection.OfType()) { byte[] data = texture.GetImageData(); if (data.Length > 0) { string originalName = texture.Name; string name = originalName.Length > 0 ? FileSystem.FixInvalidFileNameCharacters(originalName) : $"{texture.ClassName}_{ToValidString(texture.PathID)}"; Debug.Assert(name.Length > 0); string uniqueName = LocalFileSystem.Instance.GetUniqueName(collectionOutputPath, name, FileSystem.MaxFileNameLength - jsonExtension.Length); string dataFilePath = Path.Join(collectionOutputPath, uniqueName); string infoFilePath = dataFilePath + jsonExtension; File.WriteAllBytes(dataFilePath, data); string text = $$""" { "Type" : "{{texture.ClassName}}", "Format" : "{{texture.Format_C28E}}", "FileSize" : {{data.Length}}, "ImageSize" : {{texture.CompleteImageSize}}, "ImageCount" : {{texture.ImageCount_C28}}, "Mips" : {{texture.Mips.ToJson()}}, "Width" : {{texture.Width_C28}}, "Height" : {{texture.Height_C28}} } """; File.WriteAllText(infoFilePath, text); } } } private static IEnumerable GetReversedName(AssetCollection collection) { yield return collection.Name; Bundle? bundle = collection.Bundle; while (bundle is not null and not GameBundle) { yield return bundle.Name; bundle = bundle.Parent; } yield return outputDirectory; } private static string ToValidString(long value) { if (value >= 0) { return value.ToString(); } else if (value == long.MinValue) { return $"N{value.ToString().AsSpan(1)}"; } else { return (-value).ToString(); } } private static string ToJson(this bool value) { return value ? "true" : "false"; } private sealed class TextureAssetFactory : AssetFactoryBase { public override IUnityObjectBase? ReadAsset(AssetInfo assetInfo, ReadOnlyArraySegment assetData, SerializedType? assetType) { IUnityObjectBase? asset = CreateAsset(assetInfo); if (asset is not null) { EndianSpanReader reader = new EndianSpanReader(assetData, asset.Collection.EndianType); return TryReadAsset(ref reader, asset.Collection.Flags, asset); } else { return null; } } private static IUnityObjectBase? CreateAsset(AssetInfo assetInfo) { return (ClassIDType)assetInfo.ClassID switch { ClassIDType.Texture2D => Texture2D.Create(assetInfo), ClassIDType.Cubemap => Cubemap.Create(assetInfo), _ => null }; } private static IUnityObjectBase? TryReadAsset(ref EndianSpanReader reader, TransferInstructionFlags flags, IUnityObjectBase asset) { try { asset.Read(ref reader, flags); if (reader.Position != reader.Length) { Console.WriteLine($"Read {reader.Position} but expected {reader.Length} for asset type {(ClassIDType)asset.ClassID}. V: {asset.Collection.Version} P: {asset.Collection.Platform} N: {asset.Collection.Name} Path: {asset.Collection.FilePath}"); return null; } else { return asset; } } catch (Exception ex) { Console.WriteLine($"Error during reading of asset type {(ClassIDType)asset.ClassID}. V: {asset.Collection.Version} P: {asset.Collection.Platform} N: {asset.Collection.Name} Path: {asset.Collection.FilePath}\n{ex}"); return null; } } } }