Files
Nitrox/NitroxServer/Serialization/ServerProtoBufSerializer.cs
2025-07-06 00:23:46 +02:00

123 lines
3.8 KiB
C#

using System;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using NitroxModel.Platforms.OS.Shared;
using ProtoBufNet;
using ProtoBufNet.Meta;
namespace NitroxServer.Serialization;
public class ServerProtoBufSerializer : IServerSerializer
{
public const string FILE_ENDING = ".nitrox";
protected RuntimeTypeModel Model { get; } = TypeModel.Create();
public ServerProtoBufSerializer(params string[] assemblies)
{
foreach (string assembly in assemblies)
{
RegisterAssemblyClasses(assembly);
}
}
public string FileEnding => FILE_ENDING;
public void Serialize(Stream stream, object o)
{
Model.SerializeWithLengthPrefix(stream, o, o.GetType(), PrefixStyle.Base128, 0);
}
public void Serialize(string filePath, object o)
{
string tmpPath = Path.ChangeExtension(filePath, ".tmp");
using (Stream stream = File.OpenWrite(tmpPath))
{
Serialize(stream, o);
}
FileSystem.Instance.ReplaceFile(tmpPath, filePath);
}
public T Deserialize<T>(Stream stream)
{
T t = Activator.CreateInstance<T>();
Model.DeserializeWithLengthPrefix(stream, t, typeof(T), PrefixStyle.Base128, 0);
return t;
}
public T Deserialize<T>(string filePath)
{
using Stream stream = File.OpenRead(filePath);
return Deserialize<T>(stream);
}
public void Deserialize(Stream stream, object o, Type t)
{
Model.DeserializeWithLengthPrefix(stream, o, t, PrefixStyle.Base128, 0);
}
private void RegisterAssemblyClasses(string assemblyName)
{
bool HasNitroxDataContract(Type type) => type.GetCustomAttributes(typeof(DataContractAttribute), false).Length > 0;
bool HasNitroxProtoBuf(Type type) => type.GetCustomAttributes(typeof(ProtoContractAttribute), false).Length > 0;
foreach (Type type in Assembly.Load(assemblyName).GetTypes())
{
try
{
if (HasNitroxDataContract(type) || HasNitroxProtoBuf(type))
{
// As of the latest protobuf update they will automatically register detected attributes.
Model.Add(type, true);
}
else if (HasUweProtoContract(type))
{
Model.Add(type, true);
ManuallyRegisterUweProtoMembers(type.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), type);
}
}
catch (Exception ex)
{
Log.Error(ex, $"ServerProtoBufSerializer has thrown an error registering the type: {type} from {assemblyName}");
}
}
}
private static bool HasUweProtoContract(Type type)
{
foreach (object o in type.GetCustomAttributes(false))
{
if (o.GetType().ToString().Contains(nameof(ProtoContractAttribute)))
{
return true;
}
}
return false;
}
private void ManuallyRegisterUweProtoMembers(MemberInfo[] info, Type type)
{
foreach (MemberInfo property in info)
{
if (property.DeclaringType != type)
{
continue;
}
foreach (object customAttribute in property.GetCustomAttributes(false))
{
Type attributeType = customAttribute.GetType();
if (attributeType.ToString().Contains(nameof(ProtoMemberAttribute)))
{
int tag = (int)attributeType.GetProperty(nameof(ProtoMemberAttribute.Tag), BindingFlags.Public | BindingFlags.Instance).GetValue(customAttribute, Array.Empty<object>());
Model[type].Add(tag, property.Name);
}
}
}
}
}