diff --git a/src/Prospect.Unreal/Core/Names/EFindName.cs b/src/Prospect.Unreal/Core/Names/EFindName.cs new file mode 100644 index 0000000..0e47305 --- /dev/null +++ b/src/Prospect.Unreal/Core/Names/EFindName.cs @@ -0,0 +1,15 @@ +namespace Prospect.Unreal.Core.Names; + +public enum EFindName +{ + /** Find a name; return 0 if it doesn't exist. */ + FNAME_Find, + + /** Find a name or add it if it doesn't exist. */ + FNAME_Add, + + /** Finds a name and replaces it. Adds it if missing. This is only used by UHT and is generally not safe for threading. + * All this really is used for is correcting the case of names. In MT conditions you might get a half-changed name. + */ + FNAME_Replace_Not_Safe_For_Threading, +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Names/UnrealNameKey.cs b/src/Prospect.Unreal/Core/Names/EName.cs similarity index 99% rename from src/Prospect.Unreal/Core/Names/UnrealNameKey.cs rename to src/Prospect.Unreal/Core/Names/EName.cs index 6ee4777..7709e24 100644 --- a/src/Prospect.Unreal/Core/Names/UnrealNameKey.cs +++ b/src/Prospect.Unreal/Core/Names/EName.cs @@ -1,6 +1,6 @@ namespace Prospect.Unreal.Core.Names { - public enum UnrealNameKey + public enum EName { None = 0, diff --git a/src/Prospect.Unreal/Core/Names/FName.cs b/src/Prospect.Unreal/Core/Names/FName.cs index ee5e24d..c5c38fd 100644 --- a/src/Prospect.Unreal/Core/Names/FName.cs +++ b/src/Prospect.Unreal/Core/Names/FName.cs @@ -1,36 +1,150 @@ -namespace Prospect.Unreal.Core.Names; +using System.Runtime.CompilerServices; -public class FName +namespace Prospect.Unreal.Core.Names; + +public readonly struct FName { - public FName() + public FName() : this(EName.None, FNameHelper.NAME_NO_NUMBER_INTERNAL) { - NameIndex = -1; // ? - Number = -1; // ? - Str = "None"; + } + + /// + /// Create an FName with a hardcoded string index. + /// + /// The hardcoded value the string portion of the name will have + public FName(EName hardcodedName) : this(hardcodedName, FNameHelper.NAME_NO_NUMBER_INTERNAL) + { + + } + + /// + /// Create an FName with a hardcoded string index and (instance). + /// + /// The hardcoded value the string portion of the name will have + /// The hardcoded value for the number portion of the name + public FName(EName hardcodedName, int number) + { + Index = FNamePool.Find(hardcodedName); + Number = (uint)number; } - public FName(string str) + /// + /// Create an FName. If FindType is FNAME_Find, and the string part of the name + /// doesn't already exist, then the name will be NAME_None. + /// + /// Value for the string portion of the name + /// Action to take + public FName(string value, EFindName findType = EFindName.FNAME_Add) { - NameIndex = -1; - Number = -1; - Str = str; + var name = FNameHelper.MakeDetectNumber(value, findType); + + Index = name.Index; + Number = name.Number; } - public FName(string str, int number) + /// + /// Create an FName. If FindType is FNAME_Find, and the string part of the name + /// doesn't already exist, then the name will be NAME_None. + /// + /// Value for the string portion of the name + /// Value for the number portion of the name + /// Action to take + public FName(string value, int number, EFindName findType = EFindName.FNAME_Add) { - NameIndex = -1; - Number = number; - Str = str; + var name = FNameHelper.MakeWithNumber(value, findType, number); + + Index = name.Index; + Number = name.Number; } - public Int32 NameIndex { get; set; } + /// + /// Only use this if you know what you are doing (: + /// + public FName(FNameEntryId index, int number) + { + Index = index; + Number = (uint)number; + } - public Int32 Number { get; set; } + /// + /// Index of the name + /// + public FNameEntryId Index { get; } + + /// + /// Number portion of the string/number pair + /// (stored internally as 1 more than actual, so zero'd memory will be the default, no-instance case) + /// + public uint Number { get; } + + public static implicit operator FName(EName name) + { + return new FName(name); + } - public string Str { get; set; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetNumber() + { + return (int)Number; + } + + public string GetPlainNameString() + { + return FNamePool.Resolve(Index); + } + + public EName? ToEName() + { + return FNamePool.FindEName(Index); + } public override string ToString() { - return Str; + var name = GetPlainNameString(); + + if (Number == FNameHelper.NAME_NO_NUMBER_INTERNAL) + { + return name; + } + + return $"{name}_{FNameHelper.NAME_INTERNAL_TO_EXTERNAL(GetNumber())}"; + } + + public static bool operator ==(FName left, EName right) + { + return (left.Index == right) & (left.GetNumber() == 0); + } + + public static bool operator !=(FName left, EName right) + { + return (left.Index != right) | (left.GetNumber() != 0); + } + + public static bool operator ==(FName left, FName right) + { + return (left.Index == right.Index) & (left.GetNumber() == right.GetNumber()); + } + + public static bool operator !=(FName left, FName right) + { + return !(left == right); + } + + public bool Equals(FName other) + { + return this == other; + } + + public override bool Equals(object? obj) + { + return obj is FName other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return (Index.GetHashCode() * 397) ^ (int)Number; + } } } \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Names/FNameEntryId.cs b/src/Prospect.Unreal/Core/Names/FNameEntryId.cs new file mode 100644 index 0000000..2b31609 --- /dev/null +++ b/src/Prospect.Unreal/Core/Names/FNameEntryId.cs @@ -0,0 +1,51 @@ +namespace Prospect.Unreal.Core.Names; + +public readonly struct FNameEntryId +{ + public FNameEntryId() + { + Value = 0; + } + + public FNameEntryId(uint value) + { + Value = value; + } + + public readonly uint Value; + + public static bool operator ==(FNameEntryId left, EName right) + { + return left == FNamePool.Find(right); + } + + public static bool operator !=(FNameEntryId left, EName right) + { + return !(left == right); + } + + public static bool operator ==(FNameEntryId left, FNameEntryId right) + { + return left.Value == right.Value; + } + + public static bool operator !=(FNameEntryId left, FNameEntryId right) + { + return left.Value != right.Value; + } + + public bool Equals(FNameEntryId other) + { + return this == other; + } + + public override bool Equals(object? obj) + { + return obj is FNameEntryId other && Equals(other); + } + + public override int GetHashCode() + { + return (int)Value; + } +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Names/FNameHelper.cs b/src/Prospect.Unreal/Core/Names/FNameHelper.cs new file mode 100644 index 0000000..4a2b066 --- /dev/null +++ b/src/Prospect.Unreal/Core/Names/FNameHelper.cs @@ -0,0 +1,125 @@ +using System.Runtime.CompilerServices; + +namespace Prospect.Unreal.Core.Names; + +internal static class FNameHelper +{ + /// + /// Externally, the instance number to represent no instance number is NAME_NO_NUMBER, + /// but internally, we add 1 to indices, so we use this #define internally for + /// zero'd memory initialization will still make NAME_None as expected + /// + public const int NAME_NO_NUMBER_INTERNAL = 0; + + /// + /// Special value for an FName with no number + /// + public const int NAME_NO_NUMBER = NAME_NO_NUMBER_INTERNAL - 1; + + /// + /// Maximum size of name. + /// + public const int NAME_SIZE = 1024; + + public static FName MakeDetectNumber(string name, EFindName findType) + { + if (string.IsNullOrEmpty(name)) + { + return new FName(); + } + + var nameLen = name.Length; + var internalNumber = ParseNumber(name, ref nameLen); + return MakeWithNumber(name.Substring(0, nameLen), findType, (int)internalNumber); + } + + private static uint ParseNumber(ReadOnlySpan name, ref int nameLength) + { + var len = nameLength; + var digits = 0; + + for (var i = len - 1; i >= 0 && name[i] >= '0' && name[i] <= '9'; --i) + { + ++digits; + } + + var firstDigit = len - digits; + const int maxDigitsInt32 = 10; + if (digits != 0 && digits < len && name[firstDigit - 1] == '_' && digits <= maxDigitsInt32) + { + // check for the case where there are multiple digits after the _ and the first one + // is a 0 ("Rocket_04"). Can't split this case. (So, we check if the first char + // is not 0 or the length of the number is 1 (since ROcket_0 is valid) + if (digits == 1 || name[firstDigit] != '0') + { + var number = long.Parse(name.Slice(len - digits, digits)); + if (number < int.MaxValue) + { + nameLength -= 1 + digits; + return NAME_EXTERNAL_TO_INTERNAL((uint)number); + } + } + } + + return NAME_NO_NUMBER_INTERNAL; + } + + public static FName MakeWithNumber(string name, EFindName findType, int internalNumber) + { + if (name.Length == 0) + { + return new FName(); + } + + return Make(name, findType, internalNumber); + } + + private static FName Make(string name, EFindName findType, int internalNumber) + { + if (name.Length >= NAME_SIZE) + { + return new FName("ERROR_NAME_SIZE_EXCEEDED"); + } + + FNameEntryId index; + + if (findType == EFindName.FNAME_Add) + { + index = FNamePool.Store(name); + } + else if (findType == EFindName.FNAME_Find) + { + index = FNamePool.Find(name); + } + else + { + throw new NotImplementedException(); + } + + return new FName(index, internalNumber); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint NAME_EXTERNAL_TO_INTERNAL(uint number) + { + return number + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int NAME_EXTERNAL_TO_INTERNAL(int number) + { + return number + 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint NAME_INTERNAL_TO_EXTERNAL(uint number) + { + return number - 1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int NAME_INTERNAL_TO_EXTERNAL(int number) + { + return number - 1; + } +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Names/FNamePool.cs b/src/Prospect.Unreal/Core/Names/FNamePool.cs new file mode 100644 index 0000000..fa38da3 --- /dev/null +++ b/src/Prospect.Unreal/Core/Names/FNamePool.cs @@ -0,0 +1,89 @@ +namespace Prospect.Unreal.Core.Names; + +public static class FNamePool +{ + private static readonly object NamesLock = new object(); + + /// + /// Map hardcoded names in . + /// + private static readonly Dictionary HardcodedNames = new Dictionary(); + private static readonly Dictionary HardcodedNamesReverse = new Dictionary(); + + /// + /// Map existing strings to a . + /// + private static readonly Dictionary Names = new Dictionary(); + private static readonly Dictionary NamesReverse = new Dictionary(); + + private static uint _counter; + + static FNamePool() + { + // Initialize hardcoded names. + foreach (var (key, value) in UnrealNames.Names) + { + var index = Store(value); + HardcodedNames[key] = index; + HardcodedNamesReverse[index] = key; + } + } + + public static EName? FindEName(FNameEntryId index) + { + if (HardcodedNamesReverse.TryGetValue(index, out var result)) + { + return result; + } + + return null; + } + + public static FNameEntryId Find(string name) + { + if (Names.TryGetValue(name, out var result)) + { + return result; + } + + return new FNameEntryId((uint)EName.None); + } + + public static FNameEntryId Find(EName name) + { + return HardcodedNames[name]; + } + + public static string Resolve(FNameEntryId index) + { + return NamesReverse[index]; + } + + public static FNameEntryId Store(ReadOnlySpan valueSpan) + { + var value = valueSpan.ToString(); + + if (Names.TryGetValue(value, out var result)) + { + return result; + } + + lock (NamesLock) + { + // Check again incase a previous lock added it. + if (Names.TryGetValue(value, out result)) + { + return result; + } + + // Create new FName. + result = new FNameEntryId(_counter++); + + // Store FName. + Names[value] = result; + NamesReverse[result] = value; + } + + return result; + } +} \ No newline at end of file diff --git a/src/Prospect.Unreal/Core/Names/UnrealNames.cs b/src/Prospect.Unreal/Core/Names/UnrealNames.cs index 22cac42..c2f1e1c 100644 --- a/src/Prospect.Unreal/Core/Names/UnrealNames.cs +++ b/src/Prospect.Unreal/Core/Names/UnrealNames.cs @@ -1,5 +1,8 @@ namespace Prospect.Unreal.Core.Names { + /// + /// Hardcoded names in Unreal Engine 4.26.2. See "UnrealNames.inl" + /// public static class UnrealNames { public const int MaxNetworkedHardcodedName = 410; @@ -217,15 +220,11 @@ namespace Prospect.Unreal.Core.Names names.Add(702, "Root"); // Save. - Names = names; - FNames = Names.ToDictionary(x => (UnrealNameKey) x.Key, y => new FName(y.Value, y.Key)); - MaxHardcodedNameIndex = Names.Last().Key + 1; + Names = names.ToDictionary(x => (EName) x.Key, y => y.Value); + MaxHardcodedNameIndex = (int)Names.Last().Key + 1; } - public static IReadOnlyDictionary Names { get; } - - public static IReadOnlyDictionary FNames { get; } - + public static IReadOnlyDictionary Names { get; } public static int MaxHardcodedNameIndex { get; } } } \ No newline at end of file diff --git a/src/Prospect.Unreal/Net/Channels/Actor/UActorChannel.cs b/src/Prospect.Unreal/Net/Channels/Actor/UActorChannel.cs index 39b1f8d..575ea79 100644 --- a/src/Prospect.Unreal/Net/Channels/Actor/UActorChannel.cs +++ b/src/Prospect.Unreal/Net/Channels/Actor/UActorChannel.cs @@ -8,7 +8,7 @@ public class UActorChannel : UChannel public UActorChannel() { ChType = EChannelType.CHTYPE_Actor; - ChName = UnrealNames.FNames[UnrealNameKey.Actor]; + ChName = EName.Actor; // bClearRecentActorRefs = true; // bHoldQueuedExportBunchesAndGUIDs = false; // QueuedCloseReason = EChannelCloseReason::Destroyed; diff --git a/src/Prospect.Unreal/Net/Channels/Control/UControlChannel.cs b/src/Prospect.Unreal/Net/Channels/Control/UControlChannel.cs index 0e24e2f..40b6deb 100644 --- a/src/Prospect.Unreal/Net/Channels/Control/UControlChannel.cs +++ b/src/Prospect.Unreal/Net/Channels/Control/UControlChannel.cs @@ -12,7 +12,7 @@ public class UControlChannel : UChannel public UControlChannel() { ChType = EChannelType.CHTYPE_Control; - ChName = UnrealNames.FNames[UnrealNameKey.Control]; + ChName = EName.Control; } protected override void ReceivedBunch(FInBunch bunch) diff --git a/src/Prospect.Unreal/Net/Channels/Voice/UVoiceChannel.cs b/src/Prospect.Unreal/Net/Channels/Voice/UVoiceChannel.cs index 27d92ee..6ed7395 100644 --- a/src/Prospect.Unreal/Net/Channels/Voice/UVoiceChannel.cs +++ b/src/Prospect.Unreal/Net/Channels/Voice/UVoiceChannel.cs @@ -8,7 +8,7 @@ public class UVoiceChannel : UChannel public UVoiceChannel() { ChType = EChannelType.CHTYPE_Voice; - ChName = UnrealNames.FNames[UnrealNameKey.Control]; + ChName = EName.Voice; } protected override void ReceivedBunch(FInBunch bunch) diff --git a/src/Prospect.Unreal/Net/FUniqueNetIdRepl.cs b/src/Prospect.Unreal/Net/FUniqueNetIdRepl.cs index c48f39d..ced8358 100644 --- a/src/Prospect.Unreal/Net/FUniqueNetIdRepl.cs +++ b/src/Prospect.Unreal/Net/FUniqueNetIdRepl.cs @@ -71,12 +71,7 @@ public class FUniqueNetIdRepl { var typeString = ar.ReadString(); var type = new FName(typeString); - // TODO: Add FName into FNamePool throw new NotImplementedException(); - if (ar.IsError() || type.Number == (int)UnrealNameKey.None) - { - bValidTypeHash = false; - } } else { @@ -110,7 +105,7 @@ public class FUniqueNetIdRepl } else { - type = UnrealNames.FNames[UnrealNameKey.None]; + type = new FName(EName.None); // TODO: Type = UOnlineEngineInterface::Get()->GetSubsystemFromReplicationHash(TypeHash); } diff --git a/src/Prospect.Unreal/Net/Packets/Bunch/FInBunch.cs b/src/Prospect.Unreal/Net/Packets/Bunch/FInBunch.cs index db0bfbc..41767a8 100644 --- a/src/Prospect.Unreal/Net/Packets/Bunch/FInBunch.cs +++ b/src/Prospect.Unreal/Net/Packets/Bunch/FInBunch.cs @@ -45,7 +45,7 @@ namespace Prospect.Unreal.Net.Packets.Bunch Connection = inConnection; ChIndex = 0; ChType = EChannelType.CHTYPE_None; - ChName = UnrealNames.FNames[UnrealNameKey.None]; + ChName = EName.None; ChSequence = 0; bOpen = false; bClose = false; diff --git a/src/Prospect.Unreal/Net/Packets/Bunch/FOutBunch.cs b/src/Prospect.Unreal/Net/Packets/Bunch/FOutBunch.cs index 7c59ec0..d789162 100644 --- a/src/Prospect.Unreal/Net/Packets/Bunch/FOutBunch.cs +++ b/src/Prospect.Unreal/Net/Packets/Bunch/FOutBunch.cs @@ -8,7 +8,7 @@ public class FOutBunch : FNetBitWriter { public FOutBunch() : base(0) { - ChName = UnrealNames.FNames[UnrealNameKey.None]; + ChName = EName.None; } public FOutBunch(UChannel inChannel, bool bInClose) : base( @@ -53,7 +53,7 @@ public class FOutBunch : FNetBitWriter Time = 0; ChIndex = 0; ChType = EChannelType.CHTYPE_None; - ChName = UnrealNames.FNames[UnrealNameKey.None]; + ChName = EName.None; ChSequence = 0; PacketId = 0; ReceivedAck = false; diff --git a/src/Prospect.Unreal/Net/UNetConnection.cs b/src/Prospect.Unreal/Net/UNetConnection.cs index 910ceff..95468e1 100644 --- a/src/Prospect.Unreal/Net/UNetConnection.cs +++ b/src/Prospect.Unreal/Net/UNetConnection.cs @@ -90,7 +90,7 @@ public abstract class UNetConnection : UPlayer public UNetConnection() { _channelsToTick = new HashSet(); - _playerOnlinePlatformName = UnrealNames.FNames[UnrealNameKey.None]; + _playerOnlinePlatformName = EName.None; _packetOrderCache = null; _packetOrderCacheStartIdx = 0; _packetOrderCacheCount = 0; @@ -649,13 +649,13 @@ public abstract class UNetConnection : UPlayer switch (bunch.ChType) { case EChannelType.CHTYPE_Control: - bunch.ChName = UnrealNames.FNames[UnrealNameKey.Control]; + bunch.ChName = EName.Control; break; case EChannelType.CHTYPE_Voice: - bunch.ChName = UnrealNames.FNames[UnrealNameKey.Voice]; + bunch.ChName = EName.Voice; break; case EChannelType.CHTYPE_Actor: - bunch.ChName = UnrealNames.FNames[UnrealNameKey.Actor]; + bunch.ChName = EName.Actor; break; } } @@ -667,32 +667,30 @@ public abstract class UNetConnection : UPlayer if (!UPackageMap.StaticSerializeName(reader, ref chName) || reader.IsError()) { - // TODO: Close connection + Close(); Logger.Fatal("Channel name serialization failed"); return; } - bunch.ChName = chName; - - switch ((UnrealNameKey) bunch.ChName.Number) + bunch.ChName = chName!.Value; + + if (bunch.ChName == EName.Control) { - case UnrealNameKey.Control: - bunch.ChType = EChannelType.CHTYPE_Control; - break; - - case UnrealNameKey.Voice: - bunch.ChType = EChannelType.CHTYPE_Voice; - break; - - case UnrealNameKey.Actor: - bunch.ChType = EChannelType.CHTYPE_Actor; - break; + bunch.ChType = EChannelType.CHTYPE_Control; + } + else if (bunch.ChName == EName.Voice) + { + bunch.ChType = EChannelType.CHTYPE_Voice; + } + else if (bunch.ChName == EName.Actor) + { + bunch.ChType = EChannelType.CHTYPE_Actor; } } else { bunch.ChType = EChannelType.CHTYPE_None; - bunch.ChName = UnrealNames.FNames[UnrealNameKey.None]; + bunch.ChName = EName.None; } } @@ -700,12 +698,11 @@ public abstract class UNetConnection : UPlayer // If there's an existing channel and the bunch specified it's channel type, make sure they match. if (channel != null && - (bunch.ChName.Number != (int)UnrealNameKey.None) && - (bunch.ChName.Number != channel.ChName.Number)) + (bunch.ChName != EName.None) && + (bunch.ChName != channel.ChName)) { - Logger.Error("Existing channel at index {ChIndex} with type \"{ChName}\" differs from the incoming bunch's expected channel type, \"{BunchChName}\"", - bunch.ChIndex, channel.ChName.Str, bunch.ChName.Str); - // TODO: Close(); + Logger.Error("Existing channel at index {ChIndex} with type \"{ChName}\" differs from the incoming bunch's expected channel type, \"{BunchChName}\"", bunch.ChIndex, channel.ChName, bunch.ChName); + Close(); return; } @@ -714,7 +711,7 @@ public abstract class UNetConnection : UPlayer if (reader.IsError()) { Logger.Error("Bunch header overflow"); - // TODO: Close(); + Close(); return; } @@ -746,27 +743,27 @@ public abstract class UNetConnection : UPlayer Logger.Verbose(" bOpen Bunch, Channel {Ch} Sequence {Seq}: Size {A:###.0}+{B:###.0}", bunch.ChIndex, bunch.ChSequence, (headerPos - incomingStartPos)/8.0f, (reader.GetPosBits()-headerPos)/8.0f); } - if (Channels[bunch.ChIndex] == null && (bunch.ChIndex != 0 || bunch.ChName != UnrealNames.FNames[UnrealNameKey.Control])) + if (Channels[bunch.ChIndex] == null && (bunch.ChIndex != 0 || bunch.ChName != EName.Control)) { if (Channels[0] == null) { Logger.Fatal(" Received non-control bunch before control channel was created. ChIndex: {Ch}, ChName: {Name}", bunch.ChIndex, bunch.ChName); - // TODO: Close(); + Close(); return; } else if (PlayerController == null && Driver.ClientConnections.Contains(this)) { Logger.Fatal(" Received non-control bunch before player controller was assigned. ChIndex: {Ch}, ChName: {Name}", bunch.ChIndex, bunch.ChName); - // TODO: Close(); + Close(); return; } } // ignore control channel close if it hasn't been opened yet - if (bunch.ChIndex == 0 && Channels[0] == null && bunch.bClose && bunch.ChName == UnrealNames.FNames[UnrealNameKey.Control]) + if (bunch.ChIndex == 0 && Channels[0] == null && bunch.bClose && bunch.ChName == EName.Control) { Logger.Fatal("Received control channel close before open"); - // Close(); + Close(); return; } @@ -827,7 +824,7 @@ public abstract class UNetConnection : UPlayer { // Unknown type. Logger.Fatal("Connection unknown channel type ({Name})", bunch.ChName); - // TODO: Close() + Close(); return; } @@ -883,7 +880,7 @@ public abstract class UNetConnection : UPlayer if (Driver.IsServer() && (bunch.IsCriticalError() || bunch.IsError())) { Logger.Error("Received corrupted packet data from client {RemoteAddress}. Disconnecting", LowLevelGetRemoteAddress()); - // TODO: Close() + Close(); return; } } @@ -1218,7 +1215,7 @@ public abstract class UNetConnection : UPlayer if (bIsOpenOrReliable) { - var name = bunch.ChName; + var name = (FName?) bunch.ChName; UPackageMap.StaticSerializeName(sendBunchHeader, ref name); } diff --git a/src/Prospect.Unreal/Net/UNetDriver.cs b/src/Prospect.Unreal/Net/UNetDriver.cs index 5cbfdd0..6eac997 100644 --- a/src/Prospect.Unreal/Net/UNetDriver.cs +++ b/src/Prospect.Unreal/Net/UNetDriver.cs @@ -21,9 +21,9 @@ public abstract class UNetDriver : IAsyncDisposable ChannelDefinitionMap = new Dictionary(); ChannelDefinitions = new List { - new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Control], typeof(string), 0, true, false, true, false, true), - new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Voice], typeof(string), 1, true, true, true, true, true), - new FChannelDefinition(UnrealNames.FNames[UnrealNameKey.Actor], typeof(string), -1, false, true, false, false, false) + new FChannelDefinition(EName.Control, typeof(string), 0, true, false, true, false, true), + new FChannelDefinition(EName.Voice, typeof(string), 1, true, true, true, true, true), + new FChannelDefinition(EName.Actor, typeof(string), -1, false, true, false, false, false) }; ClientConnections = new List(); MappedClientConnections = new ConcurrentDictionary(); @@ -179,11 +179,17 @@ public abstract class UNetDriver : IAsyncDisposable { // TODO: Pool actor channels (?) - return (UnrealNameKey)chName.Number switch + var name = chName.ToEName(); + if (name == null) { - UnrealNameKey.Actor => new UActorChannel(), - UnrealNameKey.Control => new UControlChannel(), - UnrealNameKey.Voice => new UVoiceChannel(), + throw new UnrealNetException($"Unsupported channel name specified {chName}"); + } + + return name switch + { + EName.Actor => new UActorChannel(), + EName.Control => new UControlChannel(), + EName.Voice => new UVoiceChannel(), _ => throw new UnrealNetException($"Attempted to create unknown channel {chName}") }; } diff --git a/src/Prospect.Unreal/Net/UPackageMap.cs b/src/Prospect.Unreal/Net/UPackageMap.cs index 46f6db8..d59d3bf 100644 --- a/src/Prospect.Unreal/Net/UPackageMap.cs +++ b/src/Prospect.Unreal/Net/UPackageMap.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using Prospect.Unreal.Core; using Prospect.Unreal.Core.Names; +using Prospect.Unreal.Exceptions; using Prospect.Unreal.Net.Packets.Bunch; using Prospect.Unreal.Serialization; @@ -8,7 +9,7 @@ namespace Prospect.Unreal.Net; public class UPackageMap { - public static unsafe bool StaticSerializeName(FArchive ar, [MaybeNullWhen(false)] ref FName name) + public static unsafe bool StaticSerializeName(FArchive ar, ref FName? name) { if (ar.IsLoading()) { @@ -34,7 +35,7 @@ public class UPackageMap if (nameIndex < UnrealNames.MaxHardcodedNameIndex) { // hardcoded names never have a Number - name = UnrealNames.FNames[(UnrealNameKey) nameIndex]; + name = new FName((EName) nameIndex); } else { @@ -51,17 +52,23 @@ public class UPackageMap } else { - var bHardcoded = (byte)(ShouldReplicateAsInteger(name) ? 1 : 0); + if (name == null) + { + throw new UnrealException("Name should not be null when saving"); + } + + var inEName = name.Value.ToEName(); + var bHardcoded = inEName.HasValue && ShouldReplicateAsInteger(inEName.Value) ? 1 : 0; ar.SerializeBits(&bHardcoded, 1); // 25 if (bHardcoded != 0) { - ar.SerializeIntPacked((uint)name.Number); + ar.SerializeIntPacked((uint)inEName!.Value); } else { // send by string - var outString = name.Str; - var outNumber = name.Number; + var outString = name.Value.GetPlainNameString(); + var outNumber = name.Value.GetNumber(); ar.WriteString(outString); ar.WriteInt32(outNumber); @@ -71,9 +78,9 @@ public class UPackageMap return true; } - private static bool ShouldReplicateAsInteger(FName name) + private static bool ShouldReplicateAsInteger(EName name) { - return name.Number <= UnrealNames.MaxNetworkedHardcodedName; + return (int)name <= UnrealNames.MaxNetworkedHardcodedName; } public void NotifyBunchCommit(int bunchPacketId, FOutBunch bunch)