Files
RobustToolbox/Robust.Shared/Serialization/NetBitArraySerializer.cs
PJB3005 03f5efc4ec Fix NetBitArraySerializer compatibility.
Apparently NetSerializer treats IDynamicTypeSerializer and IStaticTypeSerializer differently for sealed types??

(cherry-picked from 6bbeaeeba6, without the test changes)

(cherry picked from commit d187018834dfa1cdead9ce5a7b72c38b07f8c81f)
2025-12-02 00:57:03 +01:00

101 lines
3.2 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.Serialization;
using JetBrains.Annotations;
using NetSerializer;
namespace Robust.Shared.Serialization;
/// <summary>
/// Custom serializer implementation for <see cref="BitArray"/>.
/// </summary>
/// <remarks>
/// <para>
/// This type is necessary as, since .NET 10, the internal layout of <see cref="BitArray"/> was changed.
/// The type now (internally) implements <see cref="ISerializable"/> for backwards compatibility with existing
/// <c>BinaryFormatter</c> code, but NetSerializer does not support <see cref="ISerializable"/>.
/// </para>
/// <para>
/// This code is designed to be backportable &amp; network compatible with the previous behavior on .NET 9.
/// </para>
/// </remarks>
internal sealed class NetBitArraySerializer : IDynamicTypeSerializer
{
// NOTE: MUST be a IDynamicTypeSerializer for compatibility!
// Can be changed in the future.
// For reference, the layout of BitArray before .NET 10 was:
// private int[] m_array;
// private int m_length;
// private int _version;
// NetSerializer serialized these in the following order (sorted by name):
// _version, m_array, m_length
public bool Handles(Type type)
{
return type == typeof(BitArray);
}
public IEnumerable<Type> GetSubtypes(Type type)
{
return [typeof(int[]), typeof(int)];
}
public void GenerateWriterMethod(Serializer serializer, Type type, ILGenerator il)
{
var method = typeof(NetBitArraySerializer).GetMethod("Write", BindingFlags.Static | BindingFlags.NonPublic)!;
// arg0: Serializer, arg1: Stream, arg2: value
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.EmitCall(OpCodes.Call, method, null);
il.Emit(OpCodes.Ret);
}
public void GenerateReaderMethod(Serializer serializer, Type type, ILGenerator il)
{
var method = typeof(NetBitArraySerializer).GetMethod("Read", BindingFlags.Static | BindingFlags.NonPublic)!;
// arg0: Serializer, arg1: stream, arg2: out value
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.EmitCall(OpCodes.Call, method, null);
il.Emit(OpCodes.Ret);
}
[UsedImplicitly]
private static void Write(Serializer serializer, Stream stream, BitArray value)
{
var intCount = (31 + value.Length) >> 5;
var ints = new int[intCount];
value.CopyTo(ints, 0);
serializer.SerializeDirect(stream, 0); // _version
serializer.SerializeDirect(stream, ints); // m_array
serializer.SerializeDirect(stream, value.Length); // m_length
}
[UsedImplicitly]
private static void Read(Serializer serializer, Stream stream, out BitArray value)
{
serializer.DeserializeDirect<int>(stream, out _); // _version
serializer.DeserializeDirect<int[]>(stream, out var array); // m_array
serializer.DeserializeDirect<int>(stream, out var length); // m_length
value = new BitArray(array)
{
Length = length
};
}
}