diff --git a/SPTarkov.Server/Modding/EnumPatcher.cs b/SPTarkov.Server/Modding/EnumPatcher.cs index c6f3c9626..474de5e36 100644 --- a/SPTarkov.Server/Modding/EnumPatcher.cs +++ b/SPTarkov.Server/Modding/EnumPatcher.cs @@ -1,6 +1,5 @@ -using Mono.Cecil; -using Mono.Cecil.Rocks; -using SPTarkov.Reflection.Patching; +using AsmResolver.DotNet; +using AsmResolver.PE.DotNet.Metadata.Tables; using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Exceptions; @@ -23,8 +22,7 @@ private static void ApplyEntry(ModuleDefinition module, EnumEntryDefinition entr throw new ModLoaderException("An enum prepatch entry has no enumType."); } - var cecilTypeName = entry.EnumType.Replace('+', '/'); - var enumType = module.GetAllTypes().FirstOrDefault(type => string.Equals(type.FullName, cecilTypeName, StringComparison.Ordinal)); + var enumType = module.GetAllTypes().FirstOrDefault(type => string.Equals(type.FullName, entry.EnumType, StringComparison.Ordinal)); if (enumType is null || !enumType.IsEnum) { @@ -36,39 +34,39 @@ private static void ApplyEntry(ModuleDefinition module, EnumEntryDefinition entr throw new ModLoaderException($"Enum `{entry.EnumType}` already contains an entry named `{entry.ConstantName}`."); } - if (enumType.Fields.Any(field => field.HasConstant && Convert.ToDecimal(field.Constant) == entry.ConstantValue)) + if (enumType.Fields.Any(field => field.Constant?.InterpretData() is { } value && Convert.ToDecimal(value) == entry.ConstantValue)) { throw new ModLoaderException($"Enum `{entry.EnumType}` already contains the value {entry.ConstantValue}."); } - enumType.Fields.Add( - new FieldDefinition( - entry.ConstantName, - FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault, - enumType - ) - { - Constant = ConvertConstant(enumType, entry), - } + var field = new FieldDefinition( + entry.ConstantName, + FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal | FieldAttributes.HasDefault, + enumType.ToTypeSignature() ); + field.Constant = ConvertConstant(enumType, entry); + enumType.Fields.Add(field); + module.TokenAllocator.AssignNextAvailableToken(field); } - private static object ConvertConstant(TypeDefinition enumType, EnumEntryDefinition entry) + private static Constant ConvertConstant(TypeDefinition enumType, EnumEntryDefinition entry) { - var underlyingType = enumType.Fields.First(field => field.Name == "value__").FieldType.MetadataType; + var underlyingType = + enumType.GetEnumUnderlyingType()?.ElementType + ?? throw new ModLoaderException($"Enum `{entry.EnumType}` has no underlying type."); try { return underlyingType switch { - MetadataType.SByte => checked((sbyte)entry.ConstantValue), - MetadataType.Byte => checked((byte)entry.ConstantValue), - MetadataType.Int16 => checked((short)entry.ConstantValue), - MetadataType.UInt16 => checked((ushort)entry.ConstantValue), - MetadataType.Int32 => checked((int)entry.ConstantValue), - MetadataType.UInt32 => checked((uint)entry.ConstantValue), - MetadataType.Int64 => entry.ConstantValue, - MetadataType.UInt64 => checked((ulong)entry.ConstantValue), + ElementType.I1 => Constant.FromValue(checked((sbyte)entry.ConstantValue)), + ElementType.U1 => Constant.FromValue(checked((byte)entry.ConstantValue)), + ElementType.I2 => Constant.FromValue(checked((short)entry.ConstantValue)), + ElementType.U2 => Constant.FromValue(checked((ushort)entry.ConstantValue)), + ElementType.I4 => Constant.FromValue(checked((int)entry.ConstantValue)), + ElementType.U4 => Constant.FromValue(checked((uint)entry.ConstantValue)), + ElementType.I8 => Constant.FromValue(entry.ConstantValue), + ElementType.U8 => Constant.FromValue(checked((ulong)entry.ConstantValue)), _ => throw new ModLoaderException($"Enum `{entry.EnumType}` has an unsupported underlying type `{underlyingType}`."), }; } diff --git a/SPTarkov.Server/Modding/ModLoader.cs b/SPTarkov.Server/Modding/ModLoader.cs index b624202ad..9a5d7f52f 100644 --- a/SPTarkov.Server/Modding/ModLoader.cs +++ b/SPTarkov.Server/Modding/ModLoader.cs @@ -1,10 +1,8 @@ using System.Reflection; using System.Runtime.Loader; using System.Text.Json; -using Mono.Cecil; -using Mono.Cecil.Cil; +using AsmResolver.DotNet; using SPTarkov.Common.Models.Logging; -using SPTarkov.Reflection.Patching; using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Exceptions; @@ -16,9 +14,7 @@ public sealed class ModLoader(ISptLogger logger, ModValidator modVali private readonly List _enumPrepatches = []; private ModuleDefinition? _serverCoreModule; - private MemoryStream? _serverCoreModuleStream; - private MemoryStream? _serverCoreSymbolStream; - private bool _serverCoreHasSymbols; + private byte[]? _serverCoreSymbols; private readonly List _prepatchResults = []; private const string ModPath = "./user/mods/"; @@ -161,21 +157,8 @@ private async Task LoadMods(bool isPrepatchedProcess, CancellationToken cancella return null; } - using var patchedStream = new MemoryStream(); - using var symbolStream = new MemoryStream(); - - var writerParameters = new WriterParameters(); - if (_serverCoreHasSymbols) - { - writerParameters.WriteSymbols = true; - writerParameters.SymbolWriterProvider = new PortablePdbWriterProvider(); - writerParameters.SymbolStream = symbolStream; - } - - _serverCoreModule!.Write(patchedStream, writerParameters); - - var assemblyBytes = patchedStream.ToArray(); - var symbolBytes = _serverCoreHasSymbols ? symbolStream.ToArray() : null; + var assemblyBytes = _serverCoreModule!.Write(); + var symbolBytes = _serverCoreSymbols; await File.WriteAllBytesAsync(PatchedAssemblyName, assemblyBytes, cancellationToken); @@ -189,11 +172,8 @@ private async Task LoadMods(bool isPrepatchedProcess, CancellationToken cancella } finally { - DisposeServerCoreModule(); - _serverCoreModuleStream?.Dispose(); - _serverCoreModuleStream = null; - _serverCoreSymbolStream?.Dispose(); - _serverCoreSymbolStream = null; + _serverCoreModule = null; + _serverCoreSymbols = null; } } @@ -234,12 +214,6 @@ private bool RunEnumPrepatches(IReadOnlyCollection? validRuntimeMods) return _prepatchResults.All(r => r.Succeeded); } - private void DisposeServerCoreModule() - { - _serverCoreModule?.Dispose(); - _serverCoreModule = null; - } - private static void ClearConsole() { if (Console.IsOutputRedirected) @@ -357,9 +331,7 @@ private IModMetadata LoadModMetadata(IEnumerable assemblies, string pa if (result == null) { - throw new ModLoaderException( - $"Failed to load mod metadata for: {Path.GetFullPath(path)} \ndid you implement `IModMetadata`?" - ); + throw new ModLoaderException($"Failed to load mod metadata for: {Path.GetFullPath(path)} \ndid you implement `IModMetadata`?"); } return result; @@ -422,22 +394,13 @@ private async Task TryLoadServerCoreBytes() var serverCorePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "SPTarkov.Server.Core.dll"); var symbolPath = Path.ChangeExtension(serverCorePath, ".pdb"); - // Don't dispose the streams, keep them open, it will cause cecil to have a stroke if they're disposed of - _serverCoreModuleStream = new MemoryStream(await File.ReadAllBytesAsync(serverCorePath), writable: false); - - var readerParameters = new ReaderParameters { ReadingMode = ReadingMode.Immediate, InMemory = true }; + _serverCoreModule = ModuleDefinition.FromBytes(await File.ReadAllBytesAsync(serverCorePath)); - // Read the symbols so the patched Core can emit a matching pdb and stay breakpointable + // Existing metadata tokens are preserved, so the original PDB remains valid for the patched assembly. if (File.Exists(symbolPath)) { - _serverCoreSymbolStream = new MemoryStream(await File.ReadAllBytesAsync(symbolPath), writable: false); - readerParameters.ReadSymbols = true; - readerParameters.SymbolReaderProvider = new PortablePdbReaderProvider(); - readerParameters.SymbolStream = _serverCoreSymbolStream; - _serverCoreHasSymbols = true; + _serverCoreSymbols = await File.ReadAllBytesAsync(symbolPath); } - - _serverCoreModule = ModuleDefinition.ReadModule(_serverCoreModuleStream, readerParameters); } catch (Exception e) { diff --git a/SPTarkov.Server/Modding/PrepatchAssemblyWriter.cs b/SPTarkov.Server/Modding/PrepatchAssemblyWriter.cs new file mode 100644 index 000000000..6dcdf3d74 --- /dev/null +++ b/SPTarkov.Server/Modding/PrepatchAssemblyWriter.cs @@ -0,0 +1,14 @@ +using AsmResolver.DotNet; +using AsmResolver.DotNet.Builder; + +namespace SPTarkov.Server.Modding; + +internal static class PrepatchAssemblyWriter +{ + public static byte[] Write(this ModuleDefinition module) + { + using var stream = new MemoryStream(); + module.Write(stream, new ManagedPEImageBuilder(MetadataBuilderFlags.PreserveAll)); + return stream.ToArray(); + } +} diff --git a/SPTarkov.Server/SPTarkov.Server.csproj b/SPTarkov.Server/SPTarkov.Server.csproj index 7f352a648..9ebdb7a7c 100644 --- a/SPTarkov.Server/SPTarkov.Server.csproj +++ b/SPTarkov.Server/SPTarkov.Server.csproj @@ -36,6 +36,12 @@ + + + + + + Always