diff --git a/src/FortniteReplayReader.Test/FortniteReplayBuilderTest.cs b/src/FortniteReplayReader.Test/FortniteReplayBuilderTest.cs index 9cde900..aca9777 100644 --- a/src/FortniteReplayReader.Test/FortniteReplayBuilderTest.cs +++ b/src/FortniteReplayReader.Test/FortniteReplayBuilderTest.cs @@ -25,6 +25,7 @@ public void GameStateTest() GameSessionId = "123", WinningTeam = 99, ReplicatedWorldTimeSeconds = 1, + ReplicatedWorldTimeSecondsDouble = 1, }; builder.UpdateGameState(state); diff --git a/src/FortniteReplayReader/FortniteReplayBuilder.cs b/src/FortniteReplayReader/FortniteReplayBuilder.cs index 90314d6..05854ac 100644 --- a/src/FortniteReplayReader/FortniteReplayBuilder.cs +++ b/src/FortniteReplayReader/FortniteReplayBuilder.cs @@ -37,6 +37,7 @@ public class FortniteReplayBuilder private readonly Dictionary _unknownWeapons = new(); private float? ReplicatedWorldTimeSeconds = 0; + private double? ReplicatedWorldTimeSecondsDouble = 0; public void AddActorChannel(uint channelIndex, uint guid) { @@ -132,6 +133,11 @@ public void UpdateGameState(GameState state) ReplicatedWorldTimeSeconds = state.ReplicatedWorldTimeSeconds; } + if (state.ReplicatedWorldTimeSecondsDouble != null) + { + ReplicatedWorldTimeSecondsDouble = state.ReplicatedWorldTimeSecondsDouble; + } + GameData.WinningPlayerIds ??= state.WinningPlayerList; GameData.WinningTeam ??= state.WinningTeam; GameData.RecorderId ??= state.RecorderPlayerState?.Value; @@ -246,6 +252,8 @@ public void UpdatePlayerState(uint channelIndex, FortPlayerState state) if (state.DeathTags != null) { playerData.DeathTime = ReplicatedWorldTimeSeconds; + playerData.DeathTimeDouble = ReplicatedWorldTimeSecondsDouble; + } playerData.Cosmetics.Parts ??= state.Parts?.Name; @@ -262,7 +270,8 @@ public void UpdateKillFeed(uint channelIndex, PlayerData data, FortPlayerState s { KillFeedEntry? entry = new KillFeedEntry() { - ReplicatedWorldTimeSeconds = ReplicatedWorldTimeSeconds + ReplicatedWorldTimeSeconds = ReplicatedWorldTimeSeconds, + ReplicatedWorldTimeSecondsDouble = ReplicatedWorldTimeSecondsDouble, }; if (state.RebootCounter != null) @@ -363,6 +372,7 @@ public void UpdatePlayerPawn(uint channelIndex, PlayerPawn pawn) { ReplicatedMovement = pawn.ReplicatedMovement, ReplicatedWorldTimeSeconds = ReplicatedWorldTimeSeconds, + ReplicatedWorldTimeSecondsDouble = ReplicatedWorldTimeSecondsDouble, LastUpdateTime = pawn.ReplayLastTransformUpdateTimeStamp, bIsCrouched = pawn.bIsCrouched, bIsInAnyStorm = pawn.bIsInAnyStorm, @@ -495,6 +505,7 @@ public void UpdateLlama(uint channelIndex, SupplyDropLlama supplyDropLlama) { llama.Looted = true; llama.LootedTime = ReplicatedWorldTimeSeconds; + llama.LootedTimeDouble = ReplicatedWorldTimeSecondsDouble; } if (supplyDropLlama.bHasSpawnedPickups) @@ -517,12 +528,15 @@ public void UpdateSupplyDrop(uint channelIndex, Models.NetFieldExports.SupplyDro { drop.Looted = true; drop.LootedTime = ReplicatedWorldTimeSeconds; + drop.LootedTimeDouble = ReplicatedWorldTimeSecondsDouble; } if (supplyDrop.BalloonPopped) { drop.BalloonPopped = true; drop.BalloonPoppedTime = ReplicatedWorldTimeSeconds; + drop.BalloonPoppedTimeDouble = ReplicatedWorldTimeSecondsDouble; + } if (supplyDrop.bHasSpawnedPickups) diff --git a/src/FortniteReplayReader/Models/KillFeedEntry.cs b/src/FortniteReplayReader/Models/KillFeedEntry.cs index 854dbb2..1c72a7f 100644 --- a/src/FortniteReplayReader/Models/KillFeedEntry.cs +++ b/src/FortniteReplayReader/Models/KillFeedEntry.cs @@ -14,6 +14,7 @@ public class KillFeedEntry public bool FinisherOrDownerIsBot { get; set; } public float? ReplicatedWorldTimeSeconds { get; set; } + public double? ReplicatedWorldTimeSecondsDouble { get; set; } public float? Distance { get; set; } public int? DeathCause { get; set; } public FVector DeathLocation { get; set; } diff --git a/src/FortniteReplayReader/Models/Llama.cs b/src/FortniteReplayReader/Models/Llama.cs index ddd1a92..08e37ba 100644 --- a/src/FortniteReplayReader/Models/Llama.cs +++ b/src/FortniteReplayReader/Models/Llama.cs @@ -24,6 +24,7 @@ public Llama(uint channelIndex, SupplyDropLlama drop) public bool HasSpawnedPickups { get; set; } public bool Looted { get; set; } public float? LootedTime { get; set; } + public double? LootedTimeDouble { get; set; } public FVector? LandingLocation { get; set; } } } diff --git a/src/FortniteReplayReader/Models/NetFieldExports/GameState.cs b/src/FortniteReplayReader/Models/NetFieldExports/GameState.cs index 594d6a8..b22e0e1 100644 --- a/src/FortniteReplayReader/Models/NetFieldExports/GameState.cs +++ b/src/FortniteReplayReader/Models/NetFieldExports/GameState.cs @@ -108,6 +108,9 @@ public class GameState : INetFieldExportGroup [NetFieldExport("ReplicatedWorldTimeSeconds", RepLayoutCmdType.PropertyFloat)] public float? ReplicatedWorldTimeSeconds { get; set; } + [NetFieldExport("ReplicatedWorldTimeSecondsDouble", RepLayoutCmdType.PropertyDouble)] + public double? ReplicatedWorldTimeSecondsDouble { get; set; } + [NetFieldExport("MatchState", RepLayoutCmdType.Property)] public FName MatchState { get; set; } diff --git a/src/FortniteReplayReader/Models/PlayerData.cs b/src/FortniteReplayReader/Models/PlayerData.cs index 97fd8f4..6bd01e1 100644 --- a/src/FortniteReplayReader/Models/PlayerData.cs +++ b/src/FortniteReplayReader/Models/PlayerData.cs @@ -71,7 +71,7 @@ public PlayerData(FortPlayerState playerState) public IEnumerable DeathTags { get; set; } public FVector DeathLocation { get; set; } public float? DeathTime { get; set; } - + public double? DeathTimeDouble { get; set; } public Cosmetics Cosmetics { get; set; } public uint? CurrentWeapon { get; internal set; } @@ -104,6 +104,9 @@ public class PlayerMovement { public FRepMovement? ReplicatedMovement { get; set; } public float? ReplicatedWorldTimeSeconds { get; set; } + + public double? ReplicatedWorldTimeSecondsDouble { get; set; } + public float? LastUpdateTime { get; set; } public bool? bIsCrouched { get; set; } diff --git a/src/FortniteReplayReader/Models/SupplyDrop.cs b/src/FortniteReplayReader/Models/SupplyDrop.cs index ad52764..9819fad 100644 --- a/src/FortniteReplayReader/Models/SupplyDrop.cs +++ b/src/FortniteReplayReader/Models/SupplyDrop.cs @@ -21,8 +21,10 @@ public SupplyDrop(uint channelIndex, NetFieldExports.SupplyDrop drop) public bool HasSpawnedPickups { get; set; } public bool Looted { get; set; } public float? LootedTime { get; set; } + public double? LootedTimeDouble { get; set; } public bool BalloonPopped { get; set; } public float? BalloonPoppedTime { get; set; } + public double? BalloonPoppedTimeDouble { get; set; } public float FallSpeed { get; set; } public FVector LandingLocation { get; set; } public float FallHeight { get; set; } diff --git a/src/Unreal.Core/Contracts/ITelemetryEvent.cs b/src/Unreal.Core/Contracts/ITelemetryEvent.cs index d4ac4a2..bdc4be5 100644 --- a/src/Unreal.Core/Contracts/ITelemetryEvent.cs +++ b/src/Unreal.Core/Contracts/ITelemetryEvent.cs @@ -6,5 +6,7 @@ public interface ITelemetryEvent { public float? ReplicatedWorldTimeSeconds { get; set; } + + public double? ReplicatedWorldTimeSecondsDouble { get; set; } } } diff --git a/src/Unreal.Core/Models/Enums/EngineNetworkVersionHistory.cs b/src/Unreal.Core/Models/Enums/EngineNetworkVersionHistory.cs index b4cd62e..2d16a71 100644 --- a/src/Unreal.Core/Models/Enums/EngineNetworkVersionHistory.cs +++ b/src/Unreal.Core/Models/Enums/EngineNetworkVersionHistory.cs @@ -35,6 +35,7 @@ public enum EngineNetworkVersionHistory HISTORY_RUNTIME_FEATURES_COMPATIBILITY = 28, // Bump version to add network runtime feature compatibility test to handshake (hello/upgrade) control messages HISTORY_SOFTOBJECTPTR_NETGUIDS = 29, // Bump version to support replicating SoftObjectPtrs by NetGuid instead of raw strings. HISTORY_SUBOBJECT_DESTROY_FLAG = 30, // Bump version to support subobject destruction message flags + HISTORY_GAMESTATE_REPLCIATED_TIME_AS_DOUBLE = 31, // Bump version to support AGameStateBase::ReplicatedWorldTimeSeconds as double instead of float. HISTORY_ENGINENETVERSION_PLUS_ONE, LATEST = HISTORY_ENGINENETVERSION_PLUS_ONE - 1, diff --git a/src/Unreal.Core/Models/Enums/RepLayoutCmdType.cs b/src/Unreal.Core/Models/Enums/RepLayoutCmdType.cs index a818965..08eb72b 100644 --- a/src/Unreal.Core/Models/Enums/RepLayoutCmdType.cs +++ b/src/Unreal.Core/Models/Enums/RepLayoutCmdType.cs @@ -32,6 +32,7 @@ public enum RepLayoutCmdType PropertySoftObject = 22, PropertyWeakObject = 23, + PropertyDouble = 94, PropertyVector2D = 95, PropertyInt16 = 96, PropertyUInt16 = 97, diff --git a/src/Unreal.Core/Models/Enums/ReplayVersionHistory.cs b/src/Unreal.Core/Models/Enums/ReplayVersionHistory.cs index eb3c2ba..3107a92 100644 --- a/src/Unreal.Core/Models/Enums/ReplayVersionHistory.cs +++ b/src/Unreal.Core/Models/Enums/ReplayVersionHistory.cs @@ -1,7 +1,7 @@ namespace Unreal.Core.Models { /// - /// see https://github.com/EpicGames/UnrealEngine/blob/70bc980c6361d9a7d23f6d23ffe322a2d6ef16fb/Engine/Source/Runtime/NetworkReplayStreaming/LocalFileNetworkReplayStreaming/Private/LocalFileNetworkReplayStreaming.cpp#L45 + /// see https://github.com/EpicGames/UnrealEngine/blob/996ef9a9f4ad5a899abf70fb292d2914a46d0876/Engine/Source/Runtime/NetworkReplayStreaming/LocalFileNetworkReplayStreaming/Public/LocalFileNetworkReplayStreaming.h#L34 /// [System.Flags] public enum ReplayVersionHistory : uint @@ -13,7 +13,7 @@ public enum ReplayVersionHistory : uint HISTORY_STREAM_CHUNK_TIMES = 4, HISTORY_FRIENDLY_NAME_ENCODING = 5, HISTORY_ENCRYPTION = 6, - HISTORY_2500 = 7, // TODO, FIND THE NAME OF THIS + HISTORY_CUSTOM_VERSIONS = 7, HISTORY_PLUS_ONE, LATEST = HISTORY_PLUS_ONE - 1 diff --git a/src/Unreal.Core/NetBitReader.cs b/src/Unreal.Core/NetBitReader.cs index 6ec7ff8..ac80c94 100644 --- a/src/Unreal.Core/NetBitReader.cs +++ b/src/Unreal.Core/NetBitReader.cs @@ -41,6 +41,11 @@ public float SerializePropertyFloat() return ReadSingle(); } + public double SerializePropertyDouble() + { + return ReadDouble(); + } + public string SerializePropertyName() { return ReadFName(); diff --git a/src/Unreal.Core/NetFieldParser.cs b/src/Unreal.Core/NetFieldParser.cs index a140aa3..7cfad59 100644 --- a/src/Unreal.Core/NetFieldParser.cs +++ b/src/Unreal.Core/NetFieldParser.cs @@ -379,6 +379,9 @@ private void SetType(INetFieldExportGroup obj, NetFieldInfo netFieldInfo, NetFie case RepLayoutCmdType.PropertyFloat: data = netBitReader.SerializePropertyFloat(); break; + case RepLayoutCmdType.PropertyDouble: + data = netBitReader.SerializePropertyDouble(); + break; case RepLayoutCmdType.PropertyNativeBool: data = netBitReader.SerializePropertyNativeBool(); break; diff --git a/src/Unreal.Core/ReplayReader.cs b/src/Unreal.Core/ReplayReader.cs index ae3664f..8483fdc 100644 --- a/src/Unreal.Core/ReplayReader.cs +++ b/src/Unreal.Core/ReplayReader.cs @@ -531,7 +531,7 @@ public virtual void ReadReplayInfo(FArchive archive) { _logger?.LogWarning("Found unexpected ReplayVersionHistory: {}", fileVersion); } - if (archive.ReplayVersion >= ReplayVersionHistory.HISTORY_2500) + if (archive.ReplayVersion >= ReplayVersionHistory.HISTORY_CUSTOM_VERSIONS) { var customVersionCount = archive.ReadInt32();