207 lines
5.6 KiB
C#

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<ITexture2D>())
{
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<string> 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<byte> 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;
}
}
}
}