#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