#if UNITY_6000_0_OR_NEWER using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Xml; using UnityEditor.Build.Content; using UnityEngine; using UnityEngine.ResourceManagement.ResourceProviders; namespace UnityEditor.AddressableAssets.Build { /// /// Handles serialization and deserialization of AddressablesContentState using DataContractSerializer. /// Uses Binary XML format for compact storage with JSON extraction for debugging. /// internal static class ContentStateSerializer { private static DataContractSerializer s_Serializer; private static DataContractSerializer Serializer { get { if (s_Serializer == null) { var settings = new DataContractSerializerSettings { KnownTypes = new[] { typeof(AssetBundleRequestOptions), typeof(SerializableGUID), typeof(SerializableHash128) }, PreserveObjectReferences = true }; var serializer = new DataContractSerializer(typeof(AddressablesContentState), settings); serializer.SetSerializationSurrogateProvider(new ContentStateSurrogate()); s_Serializer = serializer; } return s_Serializer; } } /// /// Checks if the file at the given path is in the legacy BinaryFormatter format. /// /// Path to the content state file. /// True if the file is in legacy format, false if it's the new format. public static bool IsLegacyFormat(string path) { return LegacyFormatConverter.IsLegacyFormat(path, LegacyFormatConverter.ContentStateVersionMarker); } /// /// Checks if the stream contains legacy BinaryFormatter format data. /// /// The stream to check. Position will be reset after checking. /// True if the stream contains legacy format data. public static bool IsLegacyFormat(Stream stream) { return LegacyFormatConverter.IsLegacyFormat(stream, LegacyFormatConverter.ContentStateVersionMarker); } /// /// Serializes the content state to a file using Binary XML format. /// /// The content state to serialize. /// The file path to write to. public static void Serialize(AddressablesContentState contentState, string path) { var dir = Path.GetDirectoryName(path); if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) Directory.CreateDirectory(dir); if (File.Exists(path)) File.Delete(path); using (var stream = new FileStream(path, FileMode.CreateNew, FileAccess.Write)) { Serialize(contentState, stream); } } /// /// Serializes the content state to a stream using Binary XML format. /// /// The content state to serialize. /// The stream to write to. public static void Serialize(AddressablesContentState contentState, Stream stream) { // Write version marker first var marker = LegacyFormatConverter.ContentStateVersionMarker; stream.Write(marker, 0, marker.Length); // Write using Binary XML format using (var writer = XmlDictionaryWriter.CreateBinaryWriter(stream, null, null, false)) { Serializer.WriteObject(writer, contentState); writer.Flush(); } } /// /// Deserializes the content state from a file. /// /// The file path to read from. /// The deserialized content state, or null if deserialization fails. public static AddressablesContentState Deserialize(string path) { using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read)) { return Deserialize(stream); } } /// /// Deserializes the content state from a stream. /// /// The stream to read from. /// The deserialized content state, or null if deserialization fails. public static AddressablesContentState Deserialize(Stream stream) { // Skip version marker stream.Position = LegacyFormatConverter.ContentStateVersionMarker.Length; // Read using Binary XML format using (var reader = XmlDictionaryReader.CreateBinaryReader(stream, XmlDictionaryReaderQuotas.Max)) { return Serializer.ReadObject(reader) as AddressablesContentState; } } /// /// Extracts the content state to a human-readable JSON file for debugging. /// /// Path to the binary content state file. /// Path for the output JSON file. public static void ExtractToJson(string inputPath, string outputPath) { var contentState = Deserialize(inputPath); if (contentState == null) { Debug.LogError($"Failed to deserialize content state from {inputPath}"); return; } var jsonSettings = new DataContractJsonSerializerSettings { KnownTypes = new[] { typeof(AssetBundleRequestOptions), typeof(SerializableGUID), typeof(SerializableHash128) }, UseSimpleDictionaryFormat = true }; var jsonSerializer = new DataContractJsonSerializer(typeof(AddressablesContentState), jsonSettings); using (var stream = new FileStream(outputPath, FileMode.Create, FileAccess.Write)) using (var writer = JsonReaderWriterFactory.CreateJsonWriter(stream, System.Text.Encoding.UTF8, true, true, " ")) { jsonSerializer.WriteObject(writer, contentState); } } [MenuItem("Window/Asset Management/Addressables/Extract Binary Content State", priority = 2053)] private static void ExtractBinaryContentStateMenuCommand() { var contentStatePath = EditorUtility.OpenFilePanelWithFilters( "Select Binary Content State", Path.GetDirectoryName(Application.dataPath), new string[] { "Content State", "bin" }); if (string.IsNullOrEmpty(contentStatePath)) return; if (IsLegacyFormat(contentStatePath)) { EditorUtility.DisplayDialog( "Legacy Format Detected", "The selected file is in the legacy BinaryFormatter format. Please convert it first using the migration tool.", "OK"); return; } var outputPath = contentStatePath.Replace(".bin", ".extracted.json"); ExtractToJson(contentStatePath, outputPath); Debug.Log($"Content state extracted to: {outputPath}"); EditorUtility.RevealInFinder(outputPath); } } /// /// Serializable wrapper for Unity's GUID struct. /// [DataContract] internal struct SerializableGUID { [DataMember] public string Value; public SerializableGUID(GUID guid) { Value = guid.ToString(); } public GUID ToGUID() { return string.IsNullOrEmpty(Value) ? new GUID() : new GUID(Value); } public static implicit operator SerializableGUID(GUID guid) => new SerializableGUID(guid); public static implicit operator GUID(SerializableGUID serializable) => serializable.ToGUID(); } /// /// Serializable wrapper for Unity's Hash128 struct. /// [DataContract] internal struct SerializableHash128 { [DataMember] public string Value; public SerializableHash128(Hash128 hash) { Value = hash.ToString(); } public Hash128 ToHash128() { return string.IsNullOrEmpty(Value) ? new Hash128() : Hash128.Parse(Value); } public static implicit operator SerializableHash128(Hash128 hash) => new SerializableHash128(hash); public static implicit operator Hash128(SerializableHash128 serializable) => serializable.ToHash128(); } /// /// Data contract surrogate to handle Unity types during serialization. /// internal class ContentStateSurrogate : ISerializationSurrogateProvider { public Type GetSurrogateType(Type type) { if (type == typeof(GUID)) return typeof(SerializableGUID); if (type == typeof(Hash128)) return typeof(SerializableHash128); return type; } public object GetDeserializedObject(object obj, Type targetType) { if (targetType == typeof(GUID) && obj is SerializableGUID serializableGuid) return serializableGuid.ToGUID(); if (targetType == typeof(Hash128) && obj is SerializableHash128 serializableHash) return serializableHash.ToHash128(); return obj; } public object GetObjectToSerialize(object obj, Type targetType) { if (obj is GUID guid) return new SerializableGUID(guid); if (obj is Hash128 hash) return new SerializableHash128(hash); return obj; } public object GetCustomDataToExport(System.Reflection.MemberInfo memberInfo, Type dataContractType) { return null; } public object GetCustomDataToExport(Type clrType, Type dataContractType) { return null; } public void GetKnownCustomDataTypes(System.Collections.ObjectModel.Collection customDataTypes) { } public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData) { return null; } public System.CodeDom.CodeTypeDeclaration ProcessImportedType( System.CodeDom.CodeTypeDeclaration typeDeclaration, System.CodeDom.CodeCompileUnit compileUnit) { return typeDeclaration; } } } #endif