Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 0 additions & 102 deletions Libraries/SPTarkov.Reflection/Patching/AbstractPrepatch.cs

This file was deleted.

19 changes: 0 additions & 19 deletions Libraries/SPTarkov.Reflection/Patching/IPrepatch.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,27 @@
using SPTarkov.Server.Core.Controllers;
using SPTarkov.Server.Core.Models.Common;
using SPTarkov.Server.Core.Models.Eft.Common;
using SPTarkov.Server.Core.Models.Spt.Mod;
using SPTarkov.Server.Core.Utils;

namespace SPTarkov.Server.Core.Callbacks;

[Injectable]
public class ModdedTraderCustomizationCallbacks(
public class ModLoaderCallbacks(
ClientEnumDefinitions clientEnumDefinitions,
ModdedTraderCustomizationController moddedTraderCustomizationController,
HttpResponseUtil httpResponseUtil
)
{
/// <summary>
/// Handle /singleplayer/customEnumEntries
/// </summary>
/// <returns></returns>
public ValueTask<string> GetCustomEnumEntries(string url, EmptyRequestData _, MongoId sessionID)
{
return new ValueTask<string>(httpResponseUtil.NoBody(clientEnumDefinitions.Entries.Values.SelectMany(e => e)));
}

/// <summary>
/// Handle /singleplayer/moddedTraders
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Text.Json.Serialization;

namespace SPTarkov.Server.Core.Models.Spt.Mod;

/// <summary>
/// Provides enum prepatch data needed to apply custom enum constants
/// </summary>
public sealed record EnumEntryDefinition
{
/// <summary>
/// Type of enum, full namespace must be provided. E.g. `EFT.EBuffId` <br/><br/>
/// When targeting nested types they should be denoted with `+` E.g. `EFT.InventoryLogic.Weapon+EFireMode`
/// </summary>
[JsonPropertyName("enumType")]
public required string EnumType { get; set; }

/// <summary>
/// The name to give your new constant.
/// This will be validated and pre-patcher will provide feedback if this name is already taken.
/// </summary>
[JsonPropertyName("constantName")]
public required string ConstantName { get; set; }

/// <summary>
/// Value to give your new constant.
/// This will be validated and pre-patcher will provide feedback if this constant is already taken.
/// This value is a long, however it will be converted to the proper type on application. E.g. int, sbyte, etc
/// </summary>
[JsonPropertyName("constantValue")]
public required long ConstantValue { get; set; }

/// <summary>
/// CLIENT ONLY <br/><br/>
/// Denotes the string to give the `JsonEnumName` attribute constructor should one exist.
/// E.g. `EFT.EBuffId`
/// </summary>
[JsonPropertyName("jsonEnumName")]
public string? JsonEnumName { get; set; }
}

public sealed class ClientEnumDefinitions
{
public IReadOnlyDictionary<string, List<EnumEntryDefinition>> Entries
{
get { return _entries; }
}

private readonly Dictionary<string, List<EnumEntryDefinition>> _entries = [];

/// <summary>
/// Adds a singular definition
/// </summary>
/// <param name="modGuid">Mod guid that is adding this definition</param>
/// <param name="definition">Definition to add</param>
public void Add(string modGuid, EnumEntryDefinition definition)
{
if (!_entries.TryGetValue(modGuid, out var value))
{
_entries[modGuid] = [definition];
return;
}

value.Add(definition);
}

/// <summary>
/// Adds multiple definitions
/// </summary>
/// <param name="modGuid">Mod guid that is adding these definitions</param>
/// <param name="definitions">Definitions to add</param>
public void AddRange(string modGuid, IEnumerable<EnumEntryDefinition> definitions)
{
if (!_entries.TryGetValue(modGuid, out var value))
{
_entries[modGuid] = [.. definitions];
return;
}

value.AddRange(definitions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ public interface IModMetadata
Range SptVersion { get; init; }

/// <summary>
/// Indicates whether this mod includes a pre-patcher. If you don't know what this means, leave it false.
/// Indicates whether this mod includes enum prepatch definitions. Definitions must be stored as one JSON array in
/// <c>user/patchers/{ModGuid}</c>. If you don't know what this means, leave it false.
/// </summary>
bool HasPrepatcher { get; init; }

Expand Down
3 changes: 0 additions & 3 deletions Libraries/SPTarkov.Server.Core/Models/Spt/Mod/SptMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,4 @@ public class SptMod

[JsonPropertyName("assemblies")]
public required IEnumerable<Assembly> Assemblies { get; init; }

[JsonPropertyName("patcherAssembly")]
public Assembly? PatcherAssembly { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
namespace SPTarkov.Server.Core.Routers.Static;

[Injectable(TypePriority = OnLoadOrder.Routers)]
public class ModdedTraderCustomizationRouter(JsonUtil jsonUtil, ModdedTraderCustomizationCallbacks moddedTraderCustomizationCallbacks)
public class ModLoaderRouter(JsonUtil jsonUtil, ModLoaderCallbacks modLoaderCallbacks)
: StaticRouter(
jsonUtil,
[
new RouteAction<EmptyRequestData>(
"/singleplayer/customEnumEntries",
async (url, info, sessionID, output, cancellationToken) =>
await modLoaderCallbacks.GetCustomEnumEntries(url, info, sessionID)
),
new RouteAction<EmptyRequestData>(
"/singleplayer/moddedTraders",
async (url, info, sessionID, output, cancellationToken) =>
await moddedTraderCustomizationCallbacks.GetCustomizationTraders(url, info, sessionID)
await modLoaderCallbacks.GetCustomizationTraders(url, info, sessionID)
),
]
) { }
83 changes: 83 additions & 0 deletions SPTarkov.Server/Modding/EnumPatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Mono.Cecil;
using Mono.Cecil.Rocks;
using SPTarkov.Reflection.Patching;
using SPTarkov.Server.Core.Models.Spt.Mod;
using SPTarkov.Server.Exceptions;

namespace SPTarkov.Server.Modding;

internal static class EnumPatcher
{
public static void Patch(ModuleDefinition module, IReadOnlyCollection<EnumEntryDefinition> entries)
{
foreach (var entry in entries)
{
ApplyEntry(module, entry);
}
}

private static void ApplyEntry(ModuleDefinition module, EnumEntryDefinition entry)
{
if (string.IsNullOrWhiteSpace(entry.EnumType))
{
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));

if (enumType is null || !enumType.IsEnum)
{
throw new ModLoaderException($"Could not find enum type `{entry.EnumType}` in SPTarkov.Server.Core.dll.");
}

if (enumType.Fields.Any(field => string.Equals(field.Name, entry.ConstantName, StringComparison.Ordinal)))
{
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))
{
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),
}
);
}

private static object ConvertConstant(TypeDefinition enumType, EnumEntryDefinition entry)
{
var underlyingType = enumType.Fields.First(field => field.Name == "value__").FieldType.MetadataType;

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),
_ => throw new ModLoaderException($"Enum `{entry.EnumType}` has an unsupported underlying type `{underlyingType}`."),
};
}
catch (OverflowException exception)
{
throw new ModLoaderException(
$"Value {entry.ConstantValue} does not fit enum `{entry.EnumType}` underlying type `{underlyingType}`.",
exception
);
}
}
}
Loading
Loading