diff --git a/Libraries/SPTarkov.Reflection/Patching/AbstractPrepatch.cs b/Libraries/SPTarkov.Reflection/Patching/AbstractPrepatch.cs
deleted file mode 100644
index ff444633a..000000000
--- a/Libraries/SPTarkov.Reflection/Patching/AbstractPrepatch.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using Mono.Cecil;
-using Mono.Cecil.Rocks;
-
-namespace SPTarkov.Reflection.Patching;
-
-public abstract class AbstractPrepatch(ModuleDefinition serverCoreModule) : IPrepatch
-{
- ///
- ///
- ///
- public abstract string ModGuid { get; }
-
- ///
- ///
- ///
- public abstract bool IsActive { get; }
-
- ///
- ///
- ///
- public abstract void Patch();
-
- ///
- /// Gets a type definition from the server core module
- ///
- /// Type to get the typedef for
- /// The typedef for the provided runtime type
- /// Thrown if the type does not exist in the module definition
- protected TypeDefinition GetTypeDefinition()
- {
- var result = serverCoreModule.GetAllTypes().FirstOrDefault(t => t.FullName == typeof(T).FullName);
- return result ?? throw new PatchException($"Could not a TypeDefinition for type: {typeof(T).FullName}");
- }
-
- ///
- /// Gets a field definition from the server core module
- ///
- /// Name of the field
- /// Declaring type of the field
- /// The field definition for the provided type and name
- /// Thrown if the field does not exist in the type definition
- protected FieldDefinition GetField(string name)
- {
- var typeDef = GetTypeDefinition();
- return typeDef.Fields.FirstOrDefault(f => f.Name == name)
- ?? throw new PatchException($"Could not locate a FieldDefinition for type: `{typeof(T).FullName}` and name `{name}`");
- }
-
- ///
- /// Gets a property definition from the server core module
- ///
- /// Name of the property
- /// Declaring type of the property
- /// The property definition for the provided type and name
- /// Thrown if the property does not exist in the type definition
- protected PropertyDefinition GetProperty(string name)
- {
- var typeDef = GetTypeDefinition();
- return typeDef.Properties.FirstOrDefault(p => p.Name == name)
- ?? throw new PatchException($"Could not locate a PropertyDefinition for type: `{typeof(T).FullName}` and name `{name}`");
- }
-
- ///
- /// Gets a method definition from the server core module
- ///
- /// Name of the method
- /// Declaring type of the method
- /// The method definition for the provided type and name
- /// Thrown if the method does not exist in the type definition
- protected MethodDefinition GetMethod(string name)
- {
- var typeDef = GetTypeDefinition();
- return typeDef.Methods.FirstOrDefault(m => m.Name == name)
- ?? throw new PatchException($"Could not locate a MethodDefinition for type: `{typeof(T).FullName}` and name `{name}`");
- }
-
- ///
- /// Adds a new constant to an enum
- ///
- /// Name of the constant field, must be unique
- /// Number of the constant, must be unique
- /// Type of the enum to add the constant to
- protected void AddNewEnumConstant(string name, int constant)
- where T : Enum
- {
- var typeDef = GetTypeDefinition();
-
- var newEnum = new FieldDefinition(
- name,
- Mono.Cecil.FieldAttributes.Public
- | Mono.Cecil.FieldAttributes.Static
- | Mono.Cecil.FieldAttributes.Literal
- | Mono.Cecil.FieldAttributes.HasDefault,
- typeDef
- )
- {
- Constant = constant,
- };
-
- typeDef.Fields.Add(newEnum);
- }
-}
diff --git a/Libraries/SPTarkov.Reflection/Patching/IPrepatch.cs b/Libraries/SPTarkov.Reflection/Patching/IPrepatch.cs
deleted file mode 100644
index 82805c76d..000000000
--- a/Libraries/SPTarkov.Reflection/Patching/IPrepatch.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace SPTarkov.Reflection.Patching;
-
-public interface IPrepatch
-{
- ///
- /// Guid of the mod this prepatch belongs to
- ///
- public string ModGuid { get; }
-
- ///
- /// Is this patch active?
- ///
- public bool IsActive { get; }
-
- ///
- /// prepatch method called by the mod loader
- ///
- void Patch();
-}
diff --git a/Libraries/SPTarkov.Server.Core/Callbacks/ModdedTraderCustomizationCallbacks.cs b/Libraries/SPTarkov.Server.Core/Callbacks/ModLoaderCallbacks.cs
similarity index 60%
rename from Libraries/SPTarkov.Server.Core/Callbacks/ModdedTraderCustomizationCallbacks.cs
rename to Libraries/SPTarkov.Server.Core/Callbacks/ModLoaderCallbacks.cs
index 682c2cb0b..e0fbe4dd8 100644
--- a/Libraries/SPTarkov.Server.Core/Callbacks/ModdedTraderCustomizationCallbacks.cs
+++ b/Libraries/SPTarkov.Server.Core/Callbacks/ModLoaderCallbacks.cs
@@ -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
)
{
+ ///
+ /// Handle /singleplayer/customEnumEntries
+ ///
+ ///
+ public ValueTask GetCustomEnumEntries(string url, EmptyRequestData _, MongoId sessionID)
+ {
+ return new ValueTask(httpResponseUtil.NoBody(clientEnumDefinitions.Entries.Values.SelectMany(e => e)));
+ }
+
///
/// Handle /singleplayer/moddedTraders
///
diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/EnumEntryDefinition.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/EnumEntryDefinition.cs
new file mode 100644
index 000000000..cbab9e44e
--- /dev/null
+++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/EnumEntryDefinition.cs
@@ -0,0 +1,81 @@
+using System.Text.Json.Serialization;
+
+namespace SPTarkov.Server.Core.Models.Spt.Mod;
+
+///
+/// Provides enum prepatch data needed to apply custom enum constants
+///
+public sealed record EnumEntryDefinition
+{
+ ///
+ /// Type of enum, full namespace must be provided. E.g. `EFT.EBuffId`
+ /// When targeting nested types they should be denoted with `+` E.g. `EFT.InventoryLogic.Weapon+EFireMode`
+ ///
+ [JsonPropertyName("enumType")]
+ public required string EnumType { get; set; }
+
+ ///
+ /// The name to give your new constant.
+ /// This will be validated and pre-patcher will provide feedback if this name is already taken.
+ ///
+ [JsonPropertyName("constantName")]
+ public required string ConstantName { get; set; }
+
+ ///
+ /// 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
+ ///
+ [JsonPropertyName("constantValue")]
+ public required long ConstantValue { get; set; }
+
+ ///
+ /// CLIENT ONLY
+ /// Denotes the string to give the `JsonEnumName` attribute constructor should one exist.
+ /// E.g. `EFT.EBuffId`
+ ///
+ [JsonPropertyName("jsonEnumName")]
+ public string? JsonEnumName { get; set; }
+}
+
+public sealed class ClientEnumDefinitions
+{
+ public IReadOnlyDictionary> Entries
+ {
+ get { return _entries; }
+ }
+
+ private readonly Dictionary> _entries = [];
+
+ ///
+ /// Adds a singular definition
+ ///
+ /// Mod guid that is adding this definition
+ /// Definition to add
+ public void Add(string modGuid, EnumEntryDefinition definition)
+ {
+ if (!_entries.TryGetValue(modGuid, out var value))
+ {
+ _entries[modGuid] = [definition];
+ return;
+ }
+
+ value.Add(definition);
+ }
+
+ ///
+ /// Adds multiple definitions
+ ///
+ /// Mod guid that is adding these definitions
+ /// Definitions to add
+ public void AddRange(string modGuid, IEnumerable definitions)
+ {
+ if (!_entries.TryGetValue(modGuid, out var value))
+ {
+ _entries[modGuid] = [.. definitions];
+ return;
+ }
+
+ value.AddRange(definitions);
+ }
+}
diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/IModMetadata.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/IModMetadata.cs
index fe74dc9a1..28f8416d0 100644
--- a/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/IModMetadata.cs
+++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/IModMetadata.cs
@@ -73,7 +73,8 @@ public interface IModMetadata
Range SptVersion { get; init; }
///
- /// 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
+ /// user/patchers/{ModGuid}. If you don't know what this means, leave it false.
///
bool HasPrepatcher { get; init; }
diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/SptMod.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/SptMod.cs
index 73e390a98..92c0edcf2 100644
--- a/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/SptMod.cs
+++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Mod/SptMod.cs
@@ -13,7 +13,4 @@ public class SptMod
[JsonPropertyName("assemblies")]
public required IEnumerable Assemblies { get; init; }
-
- [JsonPropertyName("patcherAssembly")]
- public Assembly? PatcherAssembly { get; set; }
}
diff --git a/Libraries/SPTarkov.Server.Core/Routers/Static/ModdedTraderCustomizationRouter.cs b/Libraries/SPTarkov.Server.Core/Routers/Static/ModLoaderRouter.cs
similarity index 53%
rename from Libraries/SPTarkov.Server.Core/Routers/Static/ModdedTraderCustomizationRouter.cs
rename to Libraries/SPTarkov.Server.Core/Routers/Static/ModLoaderRouter.cs
index 4349426f3..dd1e6b8e9 100644
--- a/Libraries/SPTarkov.Server.Core/Routers/Static/ModdedTraderCustomizationRouter.cs
+++ b/Libraries/SPTarkov.Server.Core/Routers/Static/ModLoaderRouter.cs
@@ -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(
+ "/singleplayer/customEnumEntries",
+ async (url, info, sessionID, output, cancellationToken) =>
+ await modLoaderCallbacks.GetCustomEnumEntries(url, info, sessionID)
+ ),
new RouteAction(
"/singleplayer/moddedTraders",
async (url, info, sessionID, output, cancellationToken) =>
- await moddedTraderCustomizationCallbacks.GetCustomizationTraders(url, info, sessionID)
+ await modLoaderCallbacks.GetCustomizationTraders(url, info, sessionID)
),
]
) { }
diff --git a/SPTarkov.Server/Modding/EnumPatcher.cs b/SPTarkov.Server/Modding/EnumPatcher.cs
new file mode 100644
index 000000000..c6f3c9626
--- /dev/null
+++ b/SPTarkov.Server/Modding/EnumPatcher.cs
@@ -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 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
+ );
+ }
+ }
+}
diff --git a/SPTarkov.Server/Modding/ModLoader.cs b/SPTarkov.Server/Modding/ModLoader.cs
index 8f83700ae..788e6d454 100644
--- a/SPTarkov.Server/Modding/ModLoader.cs
+++ b/SPTarkov.Server/Modding/ModLoader.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Runtime.Loader;
+using System.Text.Json;
using Mono.Cecil;
using Mono.Cecil.Cil;
using SPTarkov.Common.Models.Logging;
@@ -12,7 +13,7 @@ namespace SPTarkov.Server.Modding;
public sealed class ModLoader(ISptLogger logger, ModValidator modValidator)
{
private List _loadedMods = [];
- private readonly List _prepatches = [];
+ private readonly List _enumPrepatches = [];
private ModuleDefinition? _serverCoreModule;
private MemoryStream? _serverCoreModuleStream;
@@ -39,7 +40,7 @@ public async Task RunModLoader(string[] args)
// Load all prepatches without loading metadata, preventing a stale copy of the patched assembly
await LoadPrepatchesForPrepatchPass();
- if (_prepatches.Count > 0)
+ if (_enumPrepatches.Count > 0)
{
// Clean the console a bit
ClearConsole();
@@ -85,7 +86,7 @@ private async Task LoadPrepatchesForPrepatchPass()
foreach (var patcherDirectory in Directory.GetDirectories(PatcherPath))
{
- LoadModPatchers(Path.GetFileName(patcherDirectory), patcherDirectory);
+ LoadEnumPrepatch(Path.GetFileName(patcherDirectory), patcherDirectory);
}
}
@@ -144,7 +145,7 @@ private async Task LoadMods(bool isPrepatchedProcess)
/// Patched assembly and it's symbols, or null if any prepatch failed.
private async Task ApplyPrepatchesInMemory(IReadOnlyCollection? validRuntimeMods = null)
{
- var success = RunActivePrepatches(validRuntimeMods);
+ var success = RunEnumPrepatches(validRuntimeMods);
try
{
@@ -190,10 +191,10 @@ private async Task LoadMods(bool isPrepatchedProcess)
}
///
- /// Runs every active, valid prepatch against the loaded Core module in a deterministic order.
+ /// Runs every valid enum prepatch against the loaded Core module in a deterministic order.
///
/// True if all prepatches succeeded.
- private bool RunActivePrepatches(IReadOnlyCollection? validRuntimeMods)
+ private bool RunEnumPrepatches(IReadOnlyCollection? validRuntimeMods)
{
if (_serverCoreModule is null)
{
@@ -201,18 +202,18 @@ private bool RunActivePrepatches(IReadOnlyCollection? validRuntimeMods)
}
var validModGuids = validRuntimeMods?.Select(mod => mod.ModMetadata.ModGuid).ToHashSet(StringComparer.OrdinalIgnoreCase);
- var activePrepatches = _prepatches
- .Where(prepatch => prepatch.IsActive && (validModGuids is null || validModGuids.Contains(prepatch.ModGuid)))
+ var activePrepatches = _enumPrepatches
+ .Where(prepatch => validModGuids is null || validModGuids.Contains(prepatch.ModGuid))
.OrderBy(prepatch => prepatch.ModGuid, StringComparer.OrdinalIgnoreCase)
- .ThenBy(prepatch => prepatch.GetType().FullName, StringComparer.OrdinalIgnoreCase);
+ .ThenBy(prepatch => prepatch.DefinitionPath, StringComparer.OrdinalIgnoreCase);
foreach (var prepatch in activePrepatches)
{
- logger.Info($"Applying prepatch: {prepatch.GetType().FullName}");
+ logger.Info($"Applying enum prepatch definitions: {prepatch.DefinitionPath}");
var succeeded = false;
try
{
- prepatch.Patch();
+ EnumPatcher.Patch(_serverCoreModule, prepatch.Entries);
succeeded = true;
}
catch (Exception e)
@@ -304,7 +305,7 @@ private SptMod LoadMod(string path)
if (result.ModMetadata.HasPrepatcher)
{
- LoadModPatchers(result.ModMetadata.ModGuid, Path.Combine(PatcherPath, result.ModMetadata.ModGuid), result);
+ LoadEnumPrepatch(result.ModMetadata.ModGuid, Path.Combine(PatcherPath, result.ModMetadata.ModGuid));
}
return result;
@@ -357,7 +358,7 @@ private IModMetadata LoadModMetadata(IEnumerable assemblies, string pa
return result;
}
- private void LoadModPatchers(string modGuid, string path, SptMod? mod = null)
+ private void LoadEnumPrepatch(string modGuid, string path)
{
if (!Directory.Exists(path))
{
@@ -366,32 +367,45 @@ private void LoadModPatchers(string modGuid, string path, SptMod? mod = null)
);
}
- var patcherPath =
- Directory.GetFiles(path, "*.dll", SearchOption.TopDirectoryOnly).FirstOrDefault()
- ?? throw new ModLoaderException(
- $"Failed to locate a patcher for mod: `{modGuid}`. If you did not intend to ship a patcher. Disable `HasPatcher` in your IModMetadata implementation."
+ var definitionFiles = Directory
+ .EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly)
+ .Where(file => string.Equals(Path.GetExtension(file), ".json", StringComparison.OrdinalIgnoreCase))
+ .OrderBy(file => file, StringComparer.OrdinalIgnoreCase)
+ .ToArray();
+
+ if (definitionFiles.Length == 0)
+ {
+ throw new ModLoaderException(
+ $"Failed to locate an enum prepatch JSON file for mod: `{modGuid}`. If you did not intend to ship a prepatcher, disable `HasPrepatcher` in your IModMetadata implementation."
);
+ }
- // Load into the loader's own context so the patcher's AbstractPrepatch matches ours
- var loadContext = AssemblyLoadContext.GetLoadContext(typeof(ModLoader).Assembly) ?? AssemblyLoadContext.Default;
- var patcherAssembly = loadContext.LoadFromAssemblyPath(Path.GetFullPath(patcherPath));
- if (mod is not null)
+ if (definitionFiles.Length > 1)
{
- mod.PatcherAssembly = patcherAssembly;
+ throw new ModLoaderException(
+ $"Found multiple enum prepatch JSON files for mod: `{modGuid}`. Expected exactly one file in: `{Path.GetFullPath(path)}`"
+ );
}
- var patcherTypes = mod?.PatcherAssembly.GetTypes() ?? patcherAssembly.GetTypes();
- var prepatchTypes = patcherTypes.Where(type => !type.IsAbstract && typeof(AbstractPrepatch).IsAssignableFrom(type));
+ var definitionPath = definitionFiles[0];
+ List? entries;
- if (!prepatchTypes.Any())
+ try
+ {
+ using var definitionStream = File.OpenRead(definitionPath);
+ entries = JsonSerializer.Deserialize>(definitionStream);
+ }
+ catch (Exception exception) when (exception is JsonException or NotSupportedException or IOException or UnauthorizedAccessException)
{
- throw new ModLoaderException($"Patcher at path: `{patcherPath}` has no patcher entry point(s) of type `AbstractPrepatch`");
+ throw new ModLoaderException($"Failed to load enum prepatch definitions from: `{Path.GetFullPath(definitionPath)}`", exception);
}
- foreach (var prepatchType in prepatchTypes)
+ if (entries is null || entries.Count == 0)
{
- _prepatches.Add((AbstractPrepatch)Activator.CreateInstance(prepatchType, args: [_serverCoreModule])!);
+ throw new ModLoaderException($"Enum prepatch file contains no definitions: `{Path.GetFullPath(definitionPath)}`");
}
+
+ _enumPrepatches.Add(new EnumPrepatch(modGuid, Path.GetFullPath(definitionPath), entries));
}
private async Task TryLoadServerCoreBytes()
@@ -431,3 +445,5 @@ private async Task TryLoadServerCoreBytes()
public sealed record ModLoaderRunResult(bool ShouldStartServer, List ValidRuntimeMods);
public sealed record PatchedCoreAssembly(byte[] Assembly, byte[]? Symbols);
+
+internal sealed record EnumPrepatch(string ModGuid, string DefinitionPath, IReadOnlyList Entries);
diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs
index 6caa70994..f75eb46d0 100644
--- a/SPTarkov.Server/Program.cs
+++ b/SPTarkov.Server/Program.cs
@@ -218,6 +218,7 @@ await dbImporter.LoadDatabaseAsync(shouldVerify, cTSource.Token)
if (ProgramStatics.MODS())
{
diHandler.AddInjectableTypesFromAssemblies(loadedMods.SelectMany(a => a.Assemblies));
+ builder.Services.AddSingleton(new ClientEnumDefinitions());
diHandler.AddInjectableTypesFromTypeAssembly(typeof(SPTStartupHostedService));
}
else
diff --git a/Testing/TestMod2/TestMod2.cs b/Testing/TestMod2/TestMod2.cs
index 3baa617d1..9b20ba817 100644
--- a/Testing/TestMod2/TestMod2.cs
+++ b/Testing/TestMod2/TestMod2.cs
@@ -29,7 +29,7 @@ public sealed class TestMod2Metadata : IModMetadata, IModBlazorMetadata
}
[Injectable(TypePriority = OnLoadOrder.PostLoad + 1)]
-public class TestMod2(ISptLogger logger) : IOnLoad
+public class TestMod2(ISptLogger logger, ClientEnumDefinitions clientEnumDefinitions) : IOnLoad
{
private const string InjectedName = "TestSkillEntry";
@@ -49,6 +49,17 @@ public async Task OnLoadAsync(CancellationToken cancellationToken)
logger.Warning($"Prepatch NOT applied: {InjectedName} missing from SkillTypes");
}
+ clientEnumDefinitions.Add(
+ "com.sp-tarkov.test-mod2",
+ new EnumEntryDefinition
+ {
+ EnumType = "EFT.EBuffId",
+ ConstantName = "NewSkill",
+ ConstantValue = 10000,
+ JsonEnumName = "NewSkill",
+ }
+ );
+
await Task.CompletedTask;
}
}
diff --git a/Testing/TestMod2/TestMod2.csproj b/Testing/TestMod2/TestMod2.csproj
index 17e300738..df458ab59 100644
--- a/Testing/TestMod2/TestMod2.csproj
+++ b/Testing/TestMod2/TestMod2.csproj
@@ -15,16 +15,10 @@
-
-
-
+
@@ -33,6 +27,10 @@
SourceFiles="@(OutputDLL);"
DestinationFolder="$(MSBuildProjectDirectory)\..\..\SPTarkov.Server\bin\$(Configuration)\net10.0\user\mods\TestMod2"
/>
+
("TestSkillEntry", 100);
-
- if (serverCoreModule.Assembly.CustomAttributes.Any(IsTestPrepatchMarker))
- {
- return;
- }
-
- var constructor = typeof(AssemblyMetadataAttribute).GetConstructor([typeof(string), typeof(string)]);
- var metadataAttribute = new CustomAttribute(serverCoreModule.ImportReference(constructor));
- metadataAttribute.ConstructorArguments.Add(new CustomAttributeArgument(serverCoreModule.TypeSystem.String, MetadataKey));
- metadataAttribute.ConstructorArguments.Add(new CustomAttributeArgument(serverCoreModule.TypeSystem.String, MetadataValue));
-
- serverCoreModule.Assembly.CustomAttributes.Add(metadataAttribute);
- }
-
- private static bool IsTestPrepatchMarker(CustomAttribute attribute)
- {
- return attribute.AttributeType.FullName == typeof(AssemblyMetadataAttribute).FullName
- && attribute.ConstructorArguments.Count == 2
- && string.Equals(attribute.ConstructorArguments[0].Value as string, MetadataKey, StringComparison.Ordinal);
- }
-}
diff --git a/Testing/TestPrepatch/TestPrepatch.csproj b/Testing/TestPrepatch/TestPrepatch.csproj
deleted file mode 100644
index 03b57435d..000000000
--- a/Testing/TestPrepatch/TestPrepatch.csproj
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- enable
- TestPrepatch
- Library
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/server-csharp.slnx b/server-csharp.slnx
index 1b6a93db8..bd7a0850e 100644
--- a/server-csharp.slnx
+++ b/server-csharp.slnx
@@ -14,10 +14,7 @@
-
-
-
-
+