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)