using AssetStudio; using AssetStudioCLI.Options; using Newtonsoft.Json; using System.IO; using System.Linq; namespace AssetStudioCLI { internal static class Exporter { public static AssemblyLoader assemblyLoader = new AssemblyLoader(); public static bool ExportTexture2D(AssetItem item, string exportPath, CLIOptions options) { var m_Texture2D = (Texture2D)item.Asset; if (options.convertTexture) { var type = options.o_imageFormat.Value; if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) return false; var image = m_Texture2D.ConvertToImage(flip: true); if (image == null) { Logger.Error($"Failed to convert texture \"{m_Texture2D.m_Name}\" into image"); return false; } using (image) { using (var file = File.OpenWrite(exportFullPath)) { image.WriteToStream(file, type); } Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } } else { if (!TryExportFile(exportPath, item, ".tex", out var exportFullPath)) return false; File.WriteAllBytes(exportFullPath, m_Texture2D.image_data.GetData()); Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } } public static bool ExportAudioClip(AssetItem item, string exportPath, CLIOptions options) { string exportFullPath; var m_AudioClip = (AudioClip)item.Asset; var m_AudioData = m_AudioClip.m_AudioData.GetData(); if (m_AudioData == null || m_AudioData.Length == 0) { Logger.Error($"[{item.Text}]: AudioData was not found"); return false; } var converter = new AudioClipConverter(m_AudioClip); if (options.o_audioFormat.Value != AudioFormat.None && converter.IsSupport) { if (!TryExportFile(exportPath, item, ".wav", out exportFullPath)) return false; Logger.Debug($"Converting \"{m_AudioClip.m_Name}\" to wav..\n" + $"AudioClip sound compression: {m_AudioClip.m_CompressionFormat}\n" + $"AudioClip channel count: {m_AudioClip.m_Channels}\n" + $"AudioClip sample rate: {m_AudioClip.m_Frequency}\n" + $"AudioClip bit depth: {m_AudioClip.m_BitsPerSample}"); var buffer = converter.ConvertToWav(m_AudioData); if (buffer == null) { Logger.Error($"[{item.Text}]: Failed to convert to Wav"); return false; } File.WriteAllBytes(exportFullPath, buffer); } else { if (!TryExportFile(exportPath, item, converter.GetExtensionName(), out exportFullPath)) return false; File.WriteAllBytes(exportFullPath, m_AudioData); } Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } public static bool ExportVideoClip(AssetItem item, string exportPath) { var m_VideoClip = (VideoClip)item.Asset; if (m_VideoClip.m_ExternalResources.m_Size > 0) { if (!TryExportFile(exportPath, item, Path.GetExtension(m_VideoClip.m_OriginalPath), out var exportFullPath)) return false; m_VideoClip.m_VideoData.WriteData(exportFullPath); Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } return false; } public static bool ExportShader(AssetItem item, string exportPath) { if (!TryExportFile(exportPath, item, ".shader", out var exportFullPath)) return false; var m_Shader = (Shader)item.Asset; var str = m_Shader.Convert(); File.WriteAllText(exportFullPath, str); Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } public static bool ExportTextAsset(AssetItem item, string exportPath, CLIOptions options) { var m_TextAsset = (TextAsset)item.Asset; var extension = ".txt"; if (!options.f_notRestoreExtensionName.Value) { if (!string.IsNullOrEmpty(item.Container)) { extension = Path.GetExtension(item.Container); } } if (!TryExportFile(exportPath, item, extension, out var exportFullPath)) return false; File.WriteAllBytes(exportFullPath, m_TextAsset.m_Script); Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } public static bool ExportMonoBehaviour(AssetItem item, string exportPath, CLIOptions options) { if (!TryExportFile(exportPath, item, ".json", out var exportFullPath)) return false; var m_MonoBehaviour = (MonoBehaviour)item.Asset; var type = m_MonoBehaviour.ToType(); if (type == null) { var m_Type = MonoBehaviourToTypeTree(m_MonoBehaviour, options); type = m_MonoBehaviour.ToType(m_Type); } var str = JsonConvert.SerializeObject(type, Formatting.Indented); File.WriteAllText(exportFullPath, str); Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } public static bool ExportFont(AssetItem item, string exportPath) { var m_Font = (Font)item.Asset; if (m_Font.m_FontData != null) { var extension = ".ttf"; if (m_Font.m_FontData[0] == 79 && m_Font.m_FontData[1] == 84 && m_Font.m_FontData[2] == 84 && m_Font.m_FontData[3] == 79) { extension = ".otf"; } if (!TryExportFile(exportPath, item, extension, out var exportFullPath)) return false; File.WriteAllBytes(exportFullPath, m_Font.m_FontData); Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } return false; } public static bool ExportSprite(AssetItem item, string exportPath, CLIOptions options) { var type = options.o_imageFormat.Value; var alphaMask = SpriteMaskMode.On; if (!TryExportFile(exportPath, item, "." + type.ToString().ToLower(), out var exportFullPath)) return false; var image = ((Sprite)item.Asset).GetImage(alphaMask); if (image != null) { using (image) { using (var file = File.OpenWrite(exportFullPath)) { image.WriteToStream(file, type); } Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } } return false; } public static bool ExportRawFile(AssetItem item, string exportPath) { if (!TryExportFile(exportPath, item, ".dat", out var exportFullPath)) return false; File.WriteAllBytes(exportFullPath, item.Asset.GetRawData()); Logger.Debug($"{item.TypeString}: \"{item.Text}\" exported to \"{exportFullPath}\""); return true; } public static bool ExportDumpFile(AssetItem item, string exportPath, CLIOptions options) { if (!TryExportFile(exportPath, item, ".txt", out var exportFullPath)) return false; var str = item.Asset.Dump(); if (str == null && item.Asset is MonoBehaviour m_MonoBehaviour) { var m_Type = MonoBehaviourToTypeTree(m_MonoBehaviour, options); str = m_MonoBehaviour.Dump(m_Type); } if (str != null) { File.WriteAllText(exportFullPath, str); Logger.Debug($"{item.TypeString}: \"{item.Text}\" saved to \"{exportFullPath}\""); return true; } return false; } private static bool TryExportFile(string dir, AssetItem item, string extension, out string fullPath) { var fileName = FixFileName(item.Text); fullPath = Path.Combine(dir, fileName + extension); if (!File.Exists(fullPath)) { Directory.CreateDirectory(dir); return true; } fullPath = Path.Combine(dir, fileName + item.UniqueID + extension); if (!File.Exists(fullPath)) { Directory.CreateDirectory(dir); return true; } Logger.Error($"Export error. File \"{fullPath.Color(CLIAnsiColors.BrightRed)}\" already exist"); return false; } public static bool ExportConvertFile(AssetItem item, string exportPath, CLIOptions options) { switch (item.Type) { case ClassIDType.Texture2D: return ExportTexture2D(item, exportPath, options); case ClassIDType.AudioClip: return ExportAudioClip(item, exportPath, options); case ClassIDType.VideoClip: return ExportVideoClip(item, exportPath); case ClassIDType.Shader: return ExportShader(item, exportPath); case ClassIDType.TextAsset: return ExportTextAsset(item, exportPath, options); case ClassIDType.MonoBehaviour: return ExportMonoBehaviour(item, exportPath, options); case ClassIDType.Font: return ExportFont(item, exportPath); case ClassIDType.Sprite: return ExportSprite(item, exportPath, options); default: return ExportRawFile(item, exportPath); } } public static TypeTree MonoBehaviourToTypeTree(MonoBehaviour m_MonoBehaviour, CLIOptions options) { if (!assemblyLoader.Loaded) { var assemblyFolder = options.o_assemblyPath.Value; if (assemblyFolder != "") { assemblyLoader.Load(assemblyFolder); } else { assemblyLoader.Loaded = true; } } return m_MonoBehaviour.ConvertToTypeTree(assemblyLoader); } public static string FixFileName(string str) { if (str.Length >= 260) return Path.GetRandomFileName(); return Path.GetInvalidFileNameChars().Aggregate(str, (current, c) => current.Replace(c, '_')); } } }