diff --git a/Libraries/SPTarkov.Server.Core/Generators/Bot/BotWeaponGenerator.cs b/Libraries/SPTarkov.Server.Core/Generators/Bot/BotWeaponGenerator.cs index 5ecb9e951..d2add0ed7 100644 --- a/Libraries/SPTarkov.Server.Core/Generators/Bot/BotWeaponGenerator.cs +++ b/Libraries/SPTarkov.Server.Core/Generators/Bot/BotWeaponGenerator.cs @@ -710,7 +710,7 @@ protected HashSet GetCompatibleCartridgesFromMagazineTemplate(TemplateI } var magazineTemplate = itemHelper.GetItem( - magazineSlot.Properties?.Filters.FirstOrDefault()?.Filter?.FirstOrDefault() ?? new MongoId(null) + magazineSlot.Properties?.Filters.FirstOrDefault()?.Filter?.FirstOrDefault() ?? MongoId.Empty() ); if (!magazineTemplate.Key) { diff --git a/Libraries/SPTarkov.Server.Core/Helpers/Bot/BotWeaponGeneratorHelper.cs b/Libraries/SPTarkov.Server.Core/Helpers/Bot/BotWeaponGeneratorHelper.cs index 656fbaaf8..bd1f4e966 100644 --- a/Libraries/SPTarkov.Server.Core/Helpers/Bot/BotWeaponGeneratorHelper.cs +++ b/Libraries/SPTarkov.Server.Core/Helpers/Bot/BotWeaponGeneratorHelper.cs @@ -38,7 +38,7 @@ BotGeneratorHelper botGeneratorHelper { var firstSlotAmmoTpl = magTemplate.Properties?.Cartridges?.FirstOrDefault()?.Properties?.Filters?.First().Filter?.FirstOrDefault() - ?? new MongoId(null); + ?? MongoId.Empty(); var ammoMaxStackSize = itemHelper.GetItem(firstSlotAmmoTpl).Value?.Properties?.StackMaxSize ?? 1; chamberBulletCount = ammoMaxStackSize == 1 diff --git a/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs b/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs index 72d28b223..26423678b 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Common/MongoId.cs @@ -1,5 +1,6 @@ using System.Buffers.Binary; using System.Security.Cryptography; +using System.Text; using SPTarkov.Server.Core.Extensions; namespace SPTarkov.Server.Core.Models.Common; @@ -78,9 +79,9 @@ public MongoId() _pidAndIncrement = BitConverter.ToInt32(bytes[8..]); } - public MongoId(string? hex) + public MongoId(ReadOnlySpan hex) { - if (string.IsNullOrEmpty(hex) || hex == "000000000000000000000000") + if (hex.IsEmpty) { this = default; return; @@ -88,7 +89,10 @@ public MongoId(string? hex) if (hex.Length != 24) { - throw new ArgumentException("ObjectId must be a 24-character hex string.", hex); + throw new ArgumentException( + $"ObjectId must be a 24-character hex string, but got \"{hex}\" (length {hex.Length}).", + nameof(hex) + ); } Span bytes = stackalloc byte[12]; @@ -109,6 +113,43 @@ public MongoId(string? hex) _pidAndIncrement = BitConverter.ToInt32(bytes[8..]); } + public MongoId(ReadOnlySpan hex) + { + if (hex.IsEmpty) + { + this = default; + return; + } + + if (hex.Length != 24) + { + throw new ArgumentException( + $"ObjectId must be a 24-character hex string, but got \"{Encoding.UTF8.GetString(hex)}\" (length {hex.Length}).", + nameof(hex) + ); + } + + Span bytes = stackalloc byte[12]; + for (var i = 0; i < 12; i++) + { + var hi = HexCharToValue((char)hex[2 * i]); + var lo = HexCharToValue((char)hex[(2 * i) + 1]); + + if (hi == -1 || lo == -1) + { + throw new FormatException("ObjectId contains invalid hex characters."); + } + + bytes[i] = (byte)((hi << 4) | lo); + } + + _timestampAndMachine = BitConverter.ToInt64(bytes); + _pidAndIncrement = BitConverter.ToInt32(bytes[8..]); + } + + public MongoId(string? hex) + : this(hex.AsSpan()) { } + /// /// Converts a hexadecimal character into its corresponding integer nibble value. /// diff --git a/Libraries/SPTarkov.Server.Core/Services/Ragfair/RagfairLinkedItemService.cs b/Libraries/SPTarkov.Server.Core/Services/Ragfair/RagfairLinkedItemService.cs index 683c24750..45c7aaf64 100644 --- a/Libraries/SPTarkov.Server.Core/Services/Ragfair/RagfairLinkedItemService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/Ragfair/RagfairLinkedItemService.cs @@ -123,7 +123,7 @@ protected void AddRevolverCylinderAmmoToLinkedItems(TemplateItem cylinder, HashS } // Get the first cylinder filter tpl - var cylinderTpl = cylinderMod.Properties?.Filters?.First().Filter?.FirstOrDefault() ?? new MongoId(null); + var cylinderTpl = cylinderMod.Properties?.Filters?.First().Filter?.FirstOrDefault() ?? MongoId.Empty(); if (!cylinderTpl.IsValidMongoId()) { diff --git a/Libraries/SPTarkov.Server.Core/Utils/Json/Converters/StringToMongoIdConverter.cs b/Libraries/SPTarkov.Server.Core/Utils/Json/Converters/StringToMongoIdConverter.cs index 67bd9b8e9..c4043e5fa 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/Json/Converters/StringToMongoIdConverter.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/Json/Converters/StringToMongoIdConverter.cs @@ -8,12 +8,30 @@ public sealed class StringToMongoIdConverter : JsonConverter { public override MongoId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.String) + if (reader.TokenType is not JsonTokenType.String) { - return new MongoId(reader.GetString()); + throw new JsonException($"The JsonTokenType was not of type string, it was: {reader.TokenType}"); } - throw new JsonException($"The JsonTokenType was not of type string, it was: {reader.TokenType}"); + return Read(ref reader); + } + + // Deserialize MongoId as a dictionary key + public override MongoId ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return Read(ref reader); + } + + private static MongoId Read(ref Utf8JsonReader reader) + { + if (!reader.HasValueSequence && !reader.ValueIsEscaped) + { + return new MongoId(reader.ValueSpan); + } + + Span buffer = stackalloc char[24]; + var written = reader.CopyString(buffer); + return new MongoId(buffer[..written]); } public override void Write(Utf8JsonWriter writer, MongoId mongoId, JsonSerializerOptions options) @@ -29,12 +47,6 @@ public override void Write(Utf8JsonWriter writer, MongoId mongoId, JsonSerialize } } - // Deserialize MongoId as a dictionary key - public override MongoId ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new MongoId(reader.GetString()); - } - // Serialize MongoId as a dictionary key public override void WriteAsPropertyName(Utf8JsonWriter writer, MongoId value, JsonSerializerOptions options) { diff --git a/Testing/UnitTests/Tests/MongoIDTests.cs b/Testing/UnitTests/Tests/MongoIDTests.cs index 4804b8344..cca2e817b 100644 --- a/Testing/UnitTests/Tests/MongoIDTests.cs +++ b/Testing/UnitTests/Tests/MongoIDTests.cs @@ -56,7 +56,7 @@ public void GenerateTest() public void Constructor_EmptyAndNull_ReturnsDefaultInstance() { // Arrange & Act - var fromNull = new MongoId(null); + var fromNull = MongoId.Empty(); var fromEmpty = new MongoId(string.Empty); var fromZeroes = new MongoId(ZeroHexZeroHex); var defaultInstance = default(MongoId);