diff --git a/Project Reboot 3.0/Actor.cpp b/Project Reboot 3.0/Actor.cpp index cdcaf41..f0d5b5c 100644 --- a/Project Reboot 3.0/Actor.cpp +++ b/Project Reboot 3.0/Actor.cpp @@ -4,6 +4,25 @@ #include "reboot.h" +bool AActor::IsTearOff() +{ + static auto bTearOffOffset = GetOffset("bTearOff"); + static auto bTearOffFieldMask = GetFieldMask(GetProperty("bTearOff")); + return ReadBitfieldValue(bTearOffOffset, bTearOffFieldMask); +} + +/* FORCEINLINE */ ENetDormancy& AActor::GetNetDormancy() +{ + static auto NetDormancyOffset = GetOffset("NetDormancy"); + return Get(NetDormancyOffset); +} + +int32& AActor::GetNetTag() +{ + static auto NetTagOffset = GetOffset("NetTag"); + return Get(NetTagOffset); +} + FTransform AActor::GetTransform() { FTransform Ret; @@ -148,6 +167,13 @@ float& AActor::GetMinNetUpdateFrequency() return Get(MinNetUpdateFrequencyOffset); } +const AActor* AActor::GetNetOwner() const +{ + static int GetNetOwnerOffset = 0x448; // 1.11 + const AActor* (*GetNetOwnerOriginal)(const AActor*) = decltype(GetNetOwnerOriginal)(this->VFTable[GetNetOwnerOffset / 8]); + return GetNetOwnerOriginal(this); +} + bool AActor::IsAlwaysRelevant() { static auto bAlwaysRelevantOffset = GetOffset("bAlwaysRelevant"); diff --git a/Project Reboot 3.0/Actor.h b/Project Reboot 3.0/Actor.h index ee6b5c1..0c73713 100644 --- a/Project Reboot 3.0/Actor.h +++ b/Project Reboot 3.0/Actor.h @@ -2,11 +2,25 @@ #include "Object.h" +enum class ENetDormancy : uint8_t +{ + DORM_Never = 0, + DORM_Awake = 1, + DORM_DormantAll = 2, + DORM_DormantPartial = 3, + DORM_Initial = 4, + DORN_MAX = 5, + ENetDormancy_MAX = 6 +}; + class AActor : public UObject { public: struct FTransform GetTransform(); + bool IsTearOff(); + /* FORCEINLINE */ ENetDormancy& GetNetDormancy(); + int32& GetNetTag(); AActor* GetOwner(); struct FVector GetActorLocation(); struct FVector GetActorRightVector(); @@ -29,6 +43,13 @@ public: bool IsPendingKillPending(); float& GetNetUpdateFrequency(); float& GetMinNetUpdateFrequency(); + const AActor* GetNetOwner() const; + + bool IsRelevancyOwnerFor(const AActor* ReplicatedActor, const AActor* ActorOwner, const AActor* ConnectionActor) const + { + // we should call virtual function but eh + return (ActorOwner == this); + } static class UClass* StaticClass(); }; \ No newline at end of file diff --git a/Project Reboot 3.0/ActorChannel.h b/Project Reboot 3.0/ActorChannel.h new file mode 100644 index 0000000..6603d78 --- /dev/null +++ b/Project Reboot 3.0/ActorChannel.h @@ -0,0 +1,32 @@ +#pragma once + +#include "Channel.h" +#include "NetworkGuid.h" + +class UActorChannel : public UChannel +{ +public: + double& GetLastUpdateTime() + { + static auto LastUpdateTimeOffset = GetOffset("Actor") + 8 + 4 + 4 + 8; // checked on 4.19 + return Get(LastUpdateTimeOffset); + } + + double& GetRelevantTime() + { + static auto RelevantTimeOffset = GetOffset("Actor") + 8 + 4 + 4; // checked on 4.19 + return Get(RelevantTimeOffset); + } + + AActor*& GetActor() + { + static auto ActorOffset = GetOffset("Actor"); + return Get(ActorOffset); + } + + void Close() + { + static void (*ActorChannelClose)(UActorChannel*) = decltype(ActorChannelClose)(Addresses::ActorChannelClose); + ActorChannelClose(this); + } +}; \ No newline at end of file diff --git a/Project Reboot 3.0/Channel.h b/Project Reboot 3.0/Channel.h new file mode 100644 index 0000000..7d7be52 --- /dev/null +++ b/Project Reboot 3.0/Channel.h @@ -0,0 +1,25 @@ +#pragma once + +#include "Object.h" + +class UChannel : public UObject +{ +public: + void StartBecomingDormant() + { + void (*StartBecomingDormantOriginal)(UChannel* Channel) = decltype(StartBecomingDormantOriginal)(this->VFTable[0x298 / 8]); + StartBecomingDormantOriginal(this); + } + + bool IsPendingDormancy() + { + static auto BitfieldOffset = GetOffset("Connection") + 8; + return ((PlaceholderBitfield*)(__int64(this) + BitfieldOffset))->Seventh; + } + + bool IsDormant() + { + static auto BitfieldOffset = GetOffset("Connection") + 8; + return ((PlaceholderBitfield*)(__int64(this) + BitfieldOffset))->Third; + } +}; \ No newline at end of file diff --git a/Project Reboot 3.0/GenericPlatformMath.h b/Project Reboot 3.0/GenericPlatformMath.h new file mode 100644 index 0000000..c8725a9 --- /dev/null +++ b/Project Reboot 3.0/GenericPlatformMath.h @@ -0,0 +1,64 @@ +#pragma once + +#include "inc.h" + +class FPlatformMath +{ +public: + static constexpr FORCEINLINE int32 TruncToInt(float F) + { + return (int32)F; + } + + static constexpr FORCEINLINE float TruncToFloat(float F) + { + return (float)TruncToInt(F); + } + + static FORCEINLINE int32 FloorToInt(float F) + { + return TruncToInt(floorf(F)); + } + + template< class T, class U > + static FORCEINLINE T Lerp(const T& A, const T& B, const U& Alpha) + { + return (T)(A + Alpha * (B - A)); + } + + template< class T > + static constexpr FORCEINLINE T Max(const T A, const T B) + { + return (A >= B) ? A : B; + } + + static FORCEINLINE float FloorToFloat(float F) + { + return floorf(F); + } + + static FORCEINLINE double FloorToDouble(double F) + { + return floor(F); + } + + static FORCEINLINE int32 RoundToInt(float F) + { + return FloorToInt(F + 0.5f); + } + + static FORCEINLINE float Fractional(float Value) + { + return Value - TruncToFloat(Value); + } + + static FORCEINLINE double TruncToDouble(double F) + { + return trunc(F); + } + + static FORCEINLINE double Fractional(double Value) + { + return Value - TruncToDouble(Value); + } +}; \ No newline at end of file diff --git a/Project Reboot 3.0/GenericPlatformTime.h b/Project Reboot 3.0/GenericPlatformTime.h new file mode 100644 index 0000000..d412385 --- /dev/null +++ b/Project Reboot 3.0/GenericPlatformTime.h @@ -0,0 +1,25 @@ +#pragma once + +#include "inc.h" + +struct FGenericPlatformTime +{ + static FORCEINLINE uint32 Cycles() + { + struct timeval tv{}; + FILETIME ft; + ULARGE_INTEGER uli{}; + + GetSystemTimeAsFileTime(&ft); // Get current time + uli.LowPart = ft.dwLowDateTime; + uli.HighPart = ft.dwHighDateTime; + + // Convert to microseconds + uli.QuadPart /= 10; + uli.QuadPart -= 11644473600000000ULL; + + tv.tv_sec = (long)(uli.QuadPart / 1000000); + tv.tv_usec = (long)(uli.QuadPart % 1000000); + return (uint32)((((uint64)tv.tv_sec) * 1000000ULL) + (((uint64)tv.tv_usec))); + } +}; \ No newline at end of file diff --git a/Project Reboot 3.0/Map.h b/Project Reboot 3.0/Map.h index 5b94545..a86b063 100644 --- a/Project Reboot 3.0/Map.h +++ b/Project Reboot 3.0/Map.h @@ -30,35 +30,122 @@ public: } }; -template //, typename SetAllocator, typename KeyFuncs> -class TMapBase +template +class TMap { public: - typedef TPair ElementType; + typedef TPair ElementType; - typedef TSet ElementSetType; +public: + TSet Pairs; - ElementSetType Pairs; +public: + class FBaseIterator + { + private: + TMap& IteratedMap; + TSet::FBaseIterator SetIt; + public: + FBaseIterator(TMap& InMap, TSet::FBaseIterator InSet) + : IteratedMap(InMap) + , SetIt(InSet) + { + } + FORCEINLINE TMap::FBaseIterator operator++() + { + ++SetIt; + return *this; + } + FORCEINLINE TMap::ElementType& operator*() + { + return *SetIt; + } + FORCEINLINE const TMap::ElementType& operator*() const + { + return *SetIt; + } + FORCEINLINE bool operator==(const TMap::FBaseIterator& Other) const + { + return SetIt == Other.SetIt; + } + FORCEINLINE bool operator!=(const TMap::FBaseIterator& Other) const + { + return SetIt != Other.SetIt; + } + FORCEINLINE bool IsElementValid() const + { + return SetIt.IsElementValid(); + } + }; + + FORCEINLINE TMap::FBaseIterator begin() + { + return TMap::FBaseIterator(*this, Pairs.begin()); + } + FORCEINLINE const TMap::FBaseIterator begin() const + { + return TMap::FBaseIterator(*this, Pairs.begin()); + } + FORCEINLINE TMap::FBaseIterator end() + { + return TMap::FBaseIterator(*this, Pairs.end()); + } + FORCEINLINE const TMap::FBaseIterator end() const + { + return TMap::FBaseIterator(*this, Pairs.end()); + } + FORCEINLINE ValueType& operator[](const KeyType& Key) + { + return this->GetByKey(Key); + } + FORCEINLINE const ValueType& operator[](const KeyType& Key) const + { + return this->GetByKey(Key); + } + FORCEINLINE int32 Num() const + { + return Pairs.Num(); + } + FORCEINLINE bool IsValid() const + { + return Pairs.IsValid(); + } + FORCEINLINE bool IsIndexValid(int32 IndexToCheck) const + { + return Pairs.IsIndexValid(IndexToCheck); + } + FORCEINLINE bool Contains(const KeyType& ElementToLookFor) const + { + for (auto Element : *this) + { + if (Element.Key() == ElementToLookFor) + return true; + } + return false; + } + FORCEINLINE ValueType& GetByKey(const KeyType& Key) + { + for (auto Pair : *this) + { + if (Pair.Key() == Key) + { + return Pair.Value(); + } + } + } FORCEINLINE ValueType& Find(const KeyType& Key) { - for (int j = 0; j < this->Pairs.Elements.Num(); j++) + return GetByKey(Key); + } + FORCEINLINE ValueType GetByKeyNoRef(const KeyType& Key) + { + for (auto Pair : *this) { - ElementType& Pair = this->Pairs.Elements.operator[](j).ElementData.Value; - - if (Key == Pair.Key()) + if (Pair.Key() == Key) + { return Pair.Value(); + } } - } -}; - -template //, typename SetAllocator, typename KeyFuncs> -class TSortableMapBase : public TMapBase //, SetAllocator, KeyFuncs> -{ -}; - -template //,typename SetAllocator /*= FDefaultSetAllocator*/, typename KeyFuncs /*= TDefaultMapHashableKeyFuncs*/> -class TMap : public TSortableMapBase //, SetAllocator, KeyFuncs> -{ - + } }; \ No newline at end of file diff --git a/Project Reboot 3.0/NetConnection.h b/Project Reboot 3.0/NetConnection.h index 0ce7905..3996c4b 100644 --- a/Project Reboot 3.0/NetConnection.h +++ b/Project Reboot 3.0/NetConnection.h @@ -1,6 +1,10 @@ #pragma once #include "Player.h" +#include "Array.h" +#include "Map.h" +#include "WeakObjectPtrTemplates.h" +#include "ActorChannel.h" class UNetConnection : public UPlayer { @@ -11,9 +15,51 @@ public: return Get(OwningActorOffset); } + FName& GetClientWorldPackageName() const + { + static auto ClientWorldPackageNameOffset = 0x337B8; + return *(FName*)(__int64(this) + ClientWorldPackageNameOffset); + } + AActor*& GetViewTarget() { static auto ViewTargetOffset = GetOffset("ViewTarget"); return Get(ViewTargetOffset); } + + TSet& GetClientVisibleLevelNames() + { + static auto ClientVisibleLevelNamesOffset = 0x336C8; + return *(TSet*)(__int64(this) + ClientVisibleLevelNamesOffset); + } + + class UNetDriver*& GetDriver() + { + static auto DriverOffset = GetOffset("Driver"); + return Get(DriverOffset); + } + + int32& GetTickCount() + { + static auto TickCountOffset = GetOffset("LastReceiveTime") + 8 + 8 + 8 + 8 + 8 + 4; + return Get(TickCountOffset); + } + + TMap, UActorChannel*>& GetActorChannels() + { + static auto ActorChannelsOffset = 0x33588; + return *(TMap, UActorChannel*>*)(__int64(this) + ActorChannelsOffset); + } + + TArray& GetSentTemporaries() + { + static auto SentTemporariesOffset = GetOffset("SentTemporaries"); + return Get>(SentTemporariesOffset); + } + + bool ClientHasInitializedLevelFor(const AActor* TestActor) const + { + bool (*ClientHasInitializedLevelForOriginal)(const UNetConnection* Connection, const AActor * TestActor) = decltype(ClientHasInitializedLevelForOriginal)(this->VFTable[0x300 / 8]); + return ClientHasInitializedLevelForOriginal(this, TestActor); + } }; \ No newline at end of file diff --git a/Project Reboot 3.0/NetDriver.cpp b/Project Reboot 3.0/NetDriver.cpp index 36e6900..d4dfe1a 100644 --- a/Project Reboot 3.0/NetDriver.cpp +++ b/Project Reboot 3.0/NetDriver.cpp @@ -7,15 +7,135 @@ #include "GameplayStatics.h" #include "KismetMathLibrary.h" #include +#include "GenericPlatformMath.h" +#include "ActorChannel.h" +#include "KismetSystemLibrary.h" +#include "UnrealMathUtility.h" FNetworkObjectList& UNetDriver::GetNetworkObjectList() { return *(*(TSharedPtr*)(__int64(this) + Offsets::NetworkObjectList)); } +struct FPacketIdRange +{ + FPacketIdRange(int32 _First, int32 _Last) : First(_First), Last(_Last) { } + FPacketIdRange(int32 PacketId) : First(PacketId), Last(PacketId) { } + // FPacketIdRange() : First(INDEX_NONE), Last(INDEX_NONE) { } + int32 First; + int32 Last; + + bool InRange(int32 PacketId) + { + return (First <= PacketId && PacketId <= Last); + } +}; + +#define CLOSEPROXIMITY 500.f +#define NEARSIGHTTHRESHOLD 2000.f +#define MEDSIGHTTHRESHOLD 3162.f +#define FARSIGHTTHRESHOLD 8000.f +#define CLOSEPROXIMITYSQUARED (CLOSEPROXIMITY*CLOSEPROXIMITY) +#define NEARSIGHTTHRESHOLDSQUARED (NEARSIGHTTHRESHOLD*NEARSIGHTTHRESHOLD) +#define MEDSIGHTTHRESHOLDSQUARED (MEDSIGHTTHRESHOLD*MEDSIGHTTHRESHOLD) +#define FARSIGHTTHRESHOLDSQUARED (FARSIGHTTHRESHOLD*FARSIGHTTHRESHOLD) + +FActorPriority::FActorPriority(UNetConnection* InConnection, UActorChannel* InChannel, FNetworkObjectInfo* InActorInfo, const std::vector& Viewers, bool bLowBandwidth) + : ActorInfo(InActorInfo), Channel(InChannel), DestructionInfo(NULL) +{ + float Time = Channel ? (InConnection->GetDriver()->GetTime() - Channel->GetLastUpdateTime()) : InConnection->GetDriver()->GetSpawnPrioritySeconds(); + // take the highest priority of the viewers on this connection + Priority = 0; + + for (int32 i = 0; i < Viewers.size(); i++) + { + static auto GetNetPriorityOffset = 0x380; + float (*GetNetPriorityOriginal)(AActor* Actor, const FVector& ViewPos, const FVector& ViewDir, AActor* Viewer, AActor* ViewTarget, UActorChannel* InChannel, float Time, bool bLowBandwidth) + = decltype(GetNetPriorityOriginal)(ActorInfo->Actor->VFTable[GetNetPriorityOffset / 8]); + + Priority = FMath::Max(Priority, FMath::RoundToInt(65536.0f * GetNetPriorityOriginal(ActorInfo->Actor, Viewers[i].ViewLocation, Viewers[i].ViewDir, Viewers[i].InViewer, Viewers[i].ViewTarget, InChannel, Time, bLowBandwidth))); + } +} + +FActorPriority::FActorPriority(UNetConnection* InConnection, FActorDestructionInfo* Info, const std::vector& Viewers) + : ActorInfo(NULL), Channel(NULL), DestructionInfo(Info) +{ + Priority = 0; + + for (int32 i = 0; i < Viewers.size(); i++) + { + float Time = InConnection->GetDriver()->GetSpawnPrioritySeconds(); + + FVector Dir = DestructionInfo->DestroyedPosition - Viewers[i].ViewLocation; + float DistSq = Dir.SizeSquared(); + + // adjust priority based on distance and whether actor is in front of viewer + if ((Viewers.at(i).ViewDir | Dir) < 0.f) + { + if (DistSq > NEARSIGHTTHRESHOLDSQUARED) + Time *= 0.2f; + else if (DistSq > CLOSEPROXIMITYSQUARED) + Time *= 0.4f; + } + else if (DistSq > MEDSIGHTTHRESHOLDSQUARED) + Time *= 0.4f; + + Priority = FMath::Max(Priority, 65536.0f * Time); + } +} + +void SetChannelActorForDestroy(UActorChannel* ActorChannel, FActorDestructionInfo* DestructInfo) +{ + static auto ConnectionOffset = ActorChannel->GetOffset("Connection"); + UNetConnection* Connection = ActorChannel->Get(ConnectionOffset); + + auto State = *(uint8_t*)(__int64(Connection) + 0x12C); + + if (!(State - 2 <= 1)) // this will make sure that it is USOCK_Open or USOCK_Pending + return; + + using FOutBunch = __int64; + + static auto PackageMapOffset = Connection->GetOffset("PackageMap"); + auto PackageMap = Connection->Get(PackageMapOffset); + + FOutBunch* CloseBunch = Alloc(0x200); + + if (!CloseBunch) + return; + + static FOutBunch(*FOutBunchConstructor)(FOutBunch* a1, UActorChannel* a2, bool bInClose) = decltype(FOutBunchConstructor)(__int64(GetModuleHandleW(0)) + 0x194E800); + /* *CloseBunch = */ FOutBunchConstructor(CloseBunch, ActorChannel, true); + + // we could set bDormant but it's set by default to 0. + + SetBitfield((PlaceholderBitfield*)(__int64(CloseBunch) + 0x30), 4, true); // bReliable + + bool (*UPackageMap_WriteObjectOriginal)(UObject* PackageMap, FOutBunch* Ar, UObject* InOuter, FNetworkGUID NetGUID, FString ObjectName) = decltype(UPackageMap_WriteObjectOriginal)(PackageMap->VFTable[0x238 / 8]); + UPackageMap_WriteObjectOriginal(PackageMap, CloseBunch, DestructInfo->ObjOuter.Get(), DestructInfo->NetGUID, DestructInfo->PathName); + + FPacketIdRange (*SendBunchOriginal)(UActorChannel* Channel, FOutBunch* Bunch, bool Merge) = decltype(SendBunchOriginal)(ActorChannel->VFTable[0x288 / 8]); + SendBunchOriginal(ActorChannel, CloseBunch, false); + + static void (*FArchiveDeconstructor)(FOutBunch* Ar) = decltype(FArchiveDeconstructor)(__int64(GetModuleHandleW(0)) + 0xC36500); + FArchiveDeconstructor(CloseBunch); +} + +TSet& GetConnectionDestroyedStartupOrDormantActors(UNetConnection* Connection) +{ + return *(TSet*)(__int64(Connection) + 0x33678); +} + +TMap& GetDriverDestroyedStartupOrDormantActors(UNetDriver* NetDriver) +{ + return *(TMap*)(__int64(NetDriver) + 0x228); +} + void UNetDriver::RemoveNetworkActor(AActor* Actor) { - GetNetworkObjectList().Remove(Actor); + static void (*FNetworkObjectList_Remove)(FNetworkObjectList*, AActor* const a2) = decltype(FNetworkObjectList_Remove)(__int64(GetModuleHandleW(0)) + 0x1AEBB40); + FNetworkObjectList_Remove(&GetNetworkObjectList(), Actor); + // GetNetworkObjectList().Remove(Actor); // RenamedStartupActors.Remove(Actor->GetFName()); } @@ -88,19 +208,10 @@ enum class ENetRole : uint8_t ROLE_MAX = 4 }; -enum class ENetDormancy : uint8_t -{ - DORM_Never = 0, - DORM_Awake = 1, - DORM_DormantAll = 2, - DORM_DormantPartial = 3, - DORM_Initial = 4, - DORN_MAX = 5, - ENetDormancy_MAX = 6 -}; - FORCEINLINE float FRand() { + return ReplicationRandStream.FRand(); + std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> dis(0, 1); @@ -109,44 +220,37 @@ FORCEINLINE float FRand() return random_number; } -#define USEOBJECTLIST +FORCEINLINE float SRand() +{ + GSRandSeed = (GSRandSeed * 196314165) + 907633515; + union { float f; int32 i; } Result; + union { float f; int32 i; } Temp; + const float SRandTemp = 1.0f; + Temp.f = SRandTemp; + Result.i = (Temp.i & 0xff800000) | (GSRandSeed & 0x007fffff); + auto res = FPlatformMath::Fractional(Result.f); + // MILXNOR + // res /= 3; + return res; +} void UNetDriver::ServerReplicateActors_BuildConsiderList(std::vector& OutConsiderList) { std::vector ActorsToRemove; -#ifdef USEOBJECTLIST auto& ActiveObjects = GetNetworkObjectList().ActiveNetworkObjects; -#else - TArray Actors = UGameplayStatics::GetAllActorsOfClass(GetWorld(), AActor::StaticClass()); -#endif auto World = GetWorld(); -#ifdef USEOBJECTLIST - // for (int i = 0; i < ActiveObjects.Elements.Num(); i++) for (const TSharedPtr& ActorInfo : ActiveObjects) { - // auto& ActorInfo = ActiveObjects.Elements.Data.at(i).ElementData.Value; - if (!ActorInfo->bPendingNetUpdate && UGameplayStatics::GetTimeSeconds(GetWorld()) <= ActorInfo->NextUpdateTime) { continue; } - // if (IsBadReadPtr(ActorInfo, 8)) - // continue; - auto Actor = ActorInfo->Actor; -#else - - for (int i = 0; i < Actors.Num(); i++) - { - auto Actor = Actors.at(i); - -#endif - if (!Actor) continue; @@ -171,20 +275,15 @@ void UNetDriver::ServerReplicateActors_BuildConsiderList(std::vectorGetOffset("NetDormancy"); - - if (Actor->Get(NetDormancyOffset) == ENetDormancy::DORM_Initial && Actor->IsNetStartupActor()) // IsDormInitialStartupActor + if (Actor->GetNetDormancy() == ENetDormancy::DORM_Initial && Actor->IsNetStartupActor()) // IsDormInitialStartupActor { + ActorsToRemove.push_back(Actor); continue; } // We should check NeedsLoadForClient here. // We should make sure the actor is in the same world here but I don't believe it is needed. -#ifndef USEOBJECTLIST - FNetworkObjectInfo* ActorInfo = new FNetworkObjectInfo; - ActorInfo->Actor = Actor; -#else auto TimeSeconds = UGameplayStatics::GetTimeSeconds(World); // Can we do this outside of the loop? if (ActorInfo->LastNetReplicateTime == 0) @@ -208,10 +307,10 @@ void UNetDriver::ServerReplicateActors_BuildConsiderList(std::vectorGetNetUpdateFrequency(); // Don't go faster than NetUpdateFrequency - const float MaxOptimalDelta = max(1.0f / Actor->GetNetUpdateFrequency(), MinOptimalDelta); // Don't go slower than MinNetUpdateFrequency (or NetUpdateFrequency if it's slower) + const float MaxOptimalDelta = FMath::Max(1.0f / Actor->GetNetUpdateFrequency(), MinOptimalDelta); // Don't go slower than MinNetUpdateFrequency (or NetUpdateFrequency if it's slower) - const float Alpha = std::clamp((LastReplicateDelta - ScaleDownStartTime) / ScaleDownTimeRange, 0.0f, 1.0f); // should we use fmath? - ActorInfo->OptimalNetUpdateDelta = std::lerp(MinOptimalDelta, MaxOptimalDelta, Alpha); // should we use fmath? + const float Alpha = FMath::Clamp( (LastReplicateDelta - ScaleDownStartTime) / ScaleDownTimeRange, 0.0f, 1.0f); + ActorInfo->OptimalNetUpdateDelta = FMath::Lerp(MinOptimalDelta, MaxOptimalDelta, Alpha); } if (!ActorInfo->bPendingNetUpdate) @@ -221,13 +320,11 @@ void UNetDriver::ServerReplicateActors_BuildConsiderList(std::vectorNextUpdateTime = TimeSeconds + FRand() * ServerTickTime + NextUpdateDelta; - static auto TimeOffset = GetOffset("Time"); - ActorInfo->LastNetUpdateTime = Get(TimeOffset); + ActorInfo->NextUpdateTime = TimeSeconds + SRand() * ServerTickTime + NextUpdateDelta; + ActorInfo->LastNetUpdateTime = GetTime(); } ActorInfo->bPendingNetUpdate = false; -#endif OutConsiderList.push_back(ActorInfo.Get()); @@ -235,69 +332,16 @@ void UNetDriver::ServerReplicateActors_BuildConsiderList(std::vectorGetFullName() : "InvalidObject"); RemoveNetworkActor(Actor); - LOG_INFO(LogDev, "Finished removing actor."); */ } -#endif } -using UChannel = UObject; -using UActorChannel = UObject; - -static UActorChannel* FindChannel(AActor* Actor, UNetConnection* Connection) -{ - static auto OpenChannelsOffset = Connection->GetOffset("OpenChannels"); - auto& OpenChannels = Connection->Get>(OpenChannelsOffset); - - static auto ActorChannelClass = FindObject("/Script/Engine.ActorChannel"); - - // LOG_INFO(LogReplication, "OpenChannels.Num(): {}", OpenChannels.Num()); - - for (int i = 0; i < OpenChannels.Num(); i++) - { - auto Channel = OpenChannels.at(i); - - if (!Channel) - continue; - - // LOG_INFO(LogReplication, "[{}] Class {}", i, Channel->ClassPrivate ? Channel->ClassPrivate->GetFullName() : "InvalidObject"); - - if (!Channel->IsA(ActorChannelClass)) // (Channel->ClassPrivate == ActorChannelClass) - continue; - - static auto ActorOffset = Channel->GetOffset("Actor"); - auto ChannelActor = Channel->Get(ActorOffset); - - // LOG_INFO(LogReplication, "[{}] {}", i, ChannelActor->GetFullName()); - - if (ChannelActor != Actor) - continue; - - return (UActorChannel*)Channel; - } - - return NULL; -} - -struct FNetViewer -{ - UNetConnection* Connection; // 0x0000(0x0008) (ZeroConstructor, IsPlainOldData) - AActor* InViewer; // 0x0008(0x0008) (ZeroConstructor, IsPlainOldData) - AActor* ViewTarget; // 0x0010(0x0008) (ZeroConstructor, IsPlainOldData) - FVector ViewLocation; // 0x0018(0x000C) (IsPlainOldData) - FVector ViewDir; -}; - -static bool IsActorRelevantToConnection(AActor* Actor, std::vector& ConnectionViewers) +static bool IsActorRelevantToConnection(AActor* Actor, const std::vector& ConnectionViewers) { for (int32 viewerIdx = 0; viewerIdx < ConnectionViewers.size(); viewerIdx++) { @@ -310,7 +354,7 @@ static bool IsActorRelevantToConnection(AActor* Actor, std::vector& // if (Actor->IsNetRelevantFor(ConnectionViewers[viewerIdx].InViewer, ConnectionViewers[viewerIdx].ViewTarget, ConnectionViewers[viewerIdx].ViewLocation)) // if (IsNetRelevantFor(Actor, ConnectionViewers[viewerIdx].InViewer, ConnectionViewers[viewerIdx].ViewTarget, ConnectionViewers[viewerIdx].ViewLocation)) - if (reinterpret_cast(Actor->VFTable[index])( + if (reinterpret_cast(Actor->VFTable[index])( Actor, ConnectionViewers[viewerIdx].InViewer, ConnectionViewers[viewerIdx].ViewTarget, ConnectionViewers[viewerIdx].ViewLocation)) { return true; @@ -320,6 +364,236 @@ static bool IsActorRelevantToConnection(AActor* Actor, std::vector& return false; } +bool UNetDriver::IsLevelInitializedForActor(const AActor* InActor, const UNetConnection* InConnection) const +{ + const bool bCorrectWorld = (InConnection->GetClientWorldPackageName() == GetWorldPackage()->NamePrivate && InConnection->ClientHasInitializedLevelFor(InActor)); + const bool bIsConnectionPC = (InActor == InConnection->GetPlayerController()); + return bCorrectWorld || bIsConnectionPC; +} + +static FORCEINLINE bool IsActorDormant(FNetworkObjectInfo* ActorInfo, const TWeakObjectPtr& Connection) +{ + // If actor is already dormant on this channel, then skip replication entirely + return ActorInfo->DormantConnections.Contains(Connection); +} + +static FORCEINLINE UNetConnection* IsActorOwnedByAndRelevantToConnection(const AActor* Actor, const std::vector& ConnectionViewers, bool& bOutHasNullViewTarget) +{ + const AActor* ActorOwner = Actor->GetNetOwner(); + + bOutHasNullViewTarget = false; + + for (int i = 0; i < ConnectionViewers.size(); i++) + { + UNetConnection* ViewerConnection = ConnectionViewers[i].Connection; + + if (ViewerConnection->GetViewTarget() == nullptr) + { + bOutHasNullViewTarget = true; + } + + if (ActorOwner == ViewerConnection->GetPlayerController() || + (ViewerConnection->GetPlayerController() && ActorOwner == ViewerConnection->GetPlayerController()->GetPawn()) || + (ViewerConnection->GetViewTarget() && ViewerConnection->GetViewTarget()->IsRelevancyOwnerFor(Actor, ActorOwner, ViewerConnection->GetOwningActor()))) + { + return ViewerConnection; + } + } + + return nullptr; +} + +struct FCompareFActorPriority +{ + FORCEINLINE bool operator()(const FActorPriority& A, const FActorPriority& B) const + { + return B.Priority < A.Priority; + } +}; + +static FORCEINLINE bool ShouldActorGoDormant(AActor* Actor, const std::vector& ConnectionViewers, UActorChannel* Channel, const float Time, const bool bLowNetBandwidth) +{ + using enum ENetDormancy; + + static auto bPendingDormancyOffset = 0x30; + static auto bPendingDormancyFieldMask = 0x0; + static auto DormantOffset = 0x30; + static auto DormantFieldMask = 0x0; + + if (Actor->GetNetDormancy() <= DORM_Awake || !Channel + // || ReadBitfield((PlaceholderBitfield*)(__int64(Channel) + bPendingDormancyOffset), bPendingDormancyFieldMask) + // || ReadBitfield((PlaceholderBitfield*)(__int64(Channel) + DormantOffset), DormantFieldMask) + || Channel->IsPendingDormancy() + || Channel->IsDormant() + ) + { + // Either shouldn't go dormant, or is already dormant + return false; + } + + if (Actor->GetNetDormancy() == DORM_DormantPartial) + { + for (int32 viewerIdx = 0; viewerIdx < ConnectionViewers.size(); viewerIdx++) + { + // if (!Actor->GetNetDormancy(ConnectionViewers[viewerIdx].ViewLocation, ConnectionViewers[viewerIdx].ViewDir, ConnectionViewers[viewerIdx].InViewer, ConnectionViewers[viewerIdx].ViewTarget, Channel, Time, bLowNetBandwidth)) + if (!false) // ^ this just returns false soo (atleast AActor implementation) + { + return false; + } + } + } + + return true; +} + +int32 UNetDriver::ServerReplicateActors_PrioritizeActors(UNetConnection* Connection, const std::vector& ConnectionViewers, const std::vector ConsiderList, const bool bCPUSaturated, FActorPriority*& OutPriorityList, FActorPriority**& OutPriorityActors) +{ + GetNetTag()++; + Connection->GetTickCount()++; + + for (int32 j = 0; j < Connection->GetSentTemporaries().Num(); j++) // Set up to skip all sent temporary actors + { + Connection->GetSentTemporaries().at(j)->GetNetTag() = GetNetTag(); + } + + // check( World == Connection->OwningActor->GetWorld() ); + + int32 FinalSortedCount = 0; + int32 DeletedCount = 0; + + // Make weak ptr once for IsActorDormant call + TWeakObjectPtr WeakConnection{}; + WeakConnection.ObjectIndex = Connection->InternalIndex; + WeakConnection.ObjectSerialNumber = GetItemByIndex(Connection->InternalIndex)->SerialNumber; + + auto& Connection_DestroyedStartupOrDormantActors = GetConnectionDestroyedStartupOrDormantActors(Connection); + + const int32 MaxSortedActors = ConsiderList.size() + Connection_DestroyedStartupOrDormantActors.Num(); + + if (MaxSortedActors > 0) + { + OutPriorityList = Alloc(MaxSortedActors * sizeof(FActorPriority)); + OutPriorityActors = Alloc(MaxSortedActors * sizeof(FActorPriority*)); + + // check( World == Connection->ViewTarget->GetWorld() ); + + // AGameNetworkManager* const NetworkManager = World->NetworkManager; + const bool bLowNetBandwidth = false; // NetworkManager ? NetworkManager->IsInLowBandwidthMode() : false; + + for (int i = 0; i < ConsiderList.size(); i++) + { + FNetworkObjectInfo* ActorInfo = ConsiderList.at(i); + AActor* Actor = ActorInfo->Actor; + + auto& ActorChannels = Connection->GetActorChannels(); + UActorChannel* Channel = nullptr; + + // Connection->ActorChannels.FindRef(ActorInfo->WeakActor); + + for (int i = 0; i < ActorChannels.Pairs.Num(); i++) + { + if (ActorChannels.Pairs[i].First == ActorInfo->WeakActor) + { + Channel = ActorChannels.Pairs[i].Second; + break; + } + } + + if (!Channel) + { + if (!IsLevelInitializedForActor(Actor, Connection)) + { + // If the level this actor belongs to isn't loaded on client, don't bother sending + continue; + } + + if (!IsActorRelevantToConnection(Actor, ConnectionViewers)) + { + // LOG_INFO(LogDev, "Not relevant!"); + + // If not relevant (and we don't have a channel), skip + continue; + } + } + + // LOG_INFO(LogDev, "Actor->GetNetTag(): {}", Actor->GetNetTag()); + + UNetConnection* PriorityConnection = Connection; + + if (Actor->IsOnlyRelevantToOwner()) + { + // This actor should be owned by a particular connection, see if that connection is the one passed in + bool bHasNullViewTarget = false; + + PriorityConnection = IsActorOwnedByAndRelevantToConnection(Actor, ConnectionViewers, bHasNullViewTarget); + + if (PriorityConnection == nullptr) + { + if (!bHasNullViewTarget && Channel != NULL && GetTime() - Channel->GetRelevantTime() >= GetRelevantTimeout()) + { + Channel->Close(); + } + + continue; + } + } + // else if (CVarSetNetDormancyEnabled.GetValueOnGameThread() != 0) + else + { + // Skip Actor if dormant + if (IsActorDormant(ActorInfo, WeakConnection)) + { + // LOG_INFO(LogDev, "Actor is dormant!"); + continue; + } + + // See of actor wants to try and go dormant + if (ShouldActorGoDormant(Actor, ConnectionViewers, Channel, GetTime(), bLowNetBandwidth)) + { + // LOG_INFO(LogDev, "Actor is going dormant!"); + + // Channel is marked to go dormant now once all properties have been replicated (but is not dormant yet) + Channel->StartBecomingDormant(); + } + } + + // Actor is relevant to this connection, add it to the list + // NOTE - We use NetTag to make sure SentTemporaries didn't already mark this actor to be skipped + + if (Actor->GetNetTag() != GetNetTag()) + { + Actor->GetNetTag() = GetNetTag(); + + OutPriorityList[FinalSortedCount] = FActorPriority(PriorityConnection, Channel, ActorInfo, ConnectionViewers, bLowNetBandwidth); + OutPriorityActors[FinalSortedCount] = OutPriorityList + FinalSortedCount; + + FinalSortedCount++; + } + } + + // Add in deleted actors + + /* + + for (auto& CurrentGuid : Connection_DestroyedStartupOrDormantActors) + { + FActorDestructionInfo& DInfo = GetDriverDestroyedStartupOrDormantActors(this).Find(CurrentGuid); + OutPriorityList[FinalSortedCount] = FActorPriority(Connection, &DInfo, ConnectionViewers); + OutPriorityActors[FinalSortedCount] = OutPriorityList + FinalSortedCount; + FinalSortedCount++; + DeletedCount++; + } + + */ + + // Sort(OutPriorityActors, FinalSortedCount, FCompareFActorPriority()); + } + + return FinalSortedCount; +} + +#define NAME_None 0 + static FNetViewer ConstructNetViewer(UNetConnection* NetConnection) { FNetViewer newViewer{}; @@ -344,6 +618,143 @@ static FNetViewer ConstructNetViewer(UNetConnection* NetConnection) return newViewer; } +int32 UNetDriver::ServerReplicateActors_ProcessPrioritizedActors(UNetConnection* Connection, const std::vector& ConnectionViewers, FActorPriority** PriorityActors, const int32 FinalSortedCount, int32& OutUpdated) +{ + static UChannel* (*CreateChannel)(UNetConnection*, int, bool, int32_t) = decltype(CreateChannel)(Addresses::CreateChannel); + static __int64 (*ReplicateActor)(UActorChannel*) = decltype(ReplicateActor)(Addresses::ReplicateActor); + static __int64 (*SetChannelActor)(UActorChannel*, AActor*) = decltype(SetChannelActor)(Addresses::SetChannelActor); + + int32 ActorUpdatesThisConnection = 0; + int32 ActorUpdatesThisConnectionSent = 0; + int32 FinalRelevantCount = 0; + + for (int32 j = 0; j < FinalSortedCount; j++) + { + FNetworkObjectInfo* ActorInfo = PriorityActors[j]->ActorInfo; + + // Deletion entry + if (ActorInfo == NULL && PriorityActors[j]->DestructionInfo) + { + // Make sure client has streaming level loaded + if (PriorityActors[j]->DestructionInfo->StreamingLevelName.ComparisonIndex.Value != NAME_None && !Connection->GetClientVisibleLevelNames().Contains(PriorityActors[j]->DestructionInfo->StreamingLevelName)) + { + // This deletion entry is for an actor in a streaming level the connection doesn't have loaded, so skip it + continue; + } + + UActorChannel* Channel = (UActorChannel*)CreateChannel(Connection, 2, true, -1); + + if (Channel) + { + FinalRelevantCount++; + + auto& Connection_DestroyedStartupOrDormantActors = GetConnectionDestroyedStartupOrDormantActors(Connection); + + SetChannelActorForDestroy(Channel, PriorityActors[j]->DestructionInfo); // Send a close bunch on the new channel + Connection_DestroyedStartupOrDormantActors.Remove(PriorityActors[j]->DestructionInfo->NetGUID); // Remove from connections to-be-destroyed list (close bunch of reliable, so it will make it there) + } + + continue; + } + + // Normal actor replication + UActorChannel* Channel = PriorityActors[j]->Channel; + if (!Channel || Channel->GetActor()) //make sure didn't just close this channel + { + AActor* Actor = ActorInfo->Actor; + // Test2(Actor, "Before"); + + bool bIsRelevant = false; + + const bool bLevelInitializedForActor = IsLevelInitializedForActor(Actor, Connection); + + // only check visibility on already visible actors every 1.0 + 0.5R seconds + // bTearOff actors should never be checked + if (bLevelInitializedForActor) + { + if (!Actor->IsTearOff() && (!Channel || GetTime() - Channel->GetRelevantTime() > 1.f)) + { + if (IsActorRelevantToConnection(Actor, ConnectionViewers)) + { + bIsRelevant = true; + } + } + } + + // if the actor is now relevant or was recently relevant + const bool bIsRecentlyRelevant = bIsRelevant || (Channel && GetTime() - Channel->GetRelevantTime() < GetRelevantTimeout()) || ActorInfo->bForceRelevantNextUpdate; + + // Test2(Actor, std::format("bIsRecentlyRelevant: {} Channel: {} bIsRelevant: {}", (int)bIsRecentlyRelevant, __int64(Channel), (int)bIsRelevant)); + // Test2(Actor, std::format("bIsRelevant: {} bLevelInitializedForActor: {} Cond: {}", bIsRelevant, bLevelInitializedForActor, !Actor->IsTearOff() && (!Channel || GetTime() - Channel->GetRelevantTime() > 1.f))); + // Test2(Actor, std::format("TearOff: {} GetTime(): {} Channel->GetRelevantTime(): {}", !Actor->IsTearOff(), GetTime(), Channel ? Channel->GetRelevantTime() : 99)); + + ActorInfo->bForceRelevantNextUpdate = false; + + if (bIsRecentlyRelevant) + { + FinalRelevantCount++; + + if (Channel == NULL) // && GuidCache->SupportsObject(Actor->GetClass()) && GuidCache->SupportsObject(Actor->IsNetStartupActor() ? Actor : Actor->GetArchetype())) + { + if (bLevelInitializedForActor) + { + // Create a new channel for this actor. + UActorChannel* Channel = (UActorChannel*)CreateChannel(Connection, 2, true, -1); + + if (Channel) + { + SetChannelActor(Channel, Actor); + } + } + // if we couldn't replicate it for a reason that should be temporary, and this Actor is updated very infrequently, make sure we update it again soon + else if (Actor->GetNetUpdateFrequency() < 1.0f) + { + auto TimeSeconds = UGameplayStatics::GetTimeSeconds(GetWorld()); // Actor->GetWorld()->TimeSeconds + ActorInfo->NextUpdateTime = TimeSeconds + 0.2f * FRand(); + } + } + + if (Channel) + { + // if it is relevant then mark the channel as relevant for a short amount of time + if (bIsRelevant) + { + Channel->GetRelevantTime() = GetTime() + 0.5f * SRand(); + } + + if (ReplicateActor(Channel)) + { + ActorUpdatesThisConnectionSent++; + + // Calculate min delta (max rate actor will upate), and max delta (slowest rate actor will update) + const float MinOptimalDelta = 1.0f / Actor->GetNetUpdateFrequency(); + const float MaxOptimalDelta = FMath::Max(1.0f / Actor->GetMinNetUpdateFrequency(), MinOptimalDelta); + const float DeltaBetweenReplications = (UGameplayStatics::GetTimeSeconds(GetWorld()) - ActorInfo->LastNetReplicateTime); + + // Choose an optimal time, we choose 70% of the actual rate to allow frequency to go up if needed + ActorInfo->OptimalNetUpdateDelta = FMath::Clamp(DeltaBetweenReplications * 0.7f, MinOptimalDelta, MaxOptimalDelta); + ActorInfo->LastNetReplicateTime = UGameplayStatics::GetTimeSeconds(GetWorld()); + ReplicatedActors.emplace(Actor->GetFullName()); + } + + ActorUpdatesThisConnection++; + OutUpdated++; + } + } + + if ((!bIsRecentlyRelevant || Actor->IsTearOff()) && Channel != NULL) + { + if (!bLevelInitializedForActor || !Actor->IsNetStartupActor()) + { + Channel->Close(); + } + } + } + } + + return FinalSortedCount; +} + int32 UNetDriver::ServerReplicateActors() { int32 Updated = 0; @@ -374,9 +785,7 @@ int32 UNetDriver::ServerReplicateActors() std::vector ConsiderList; -#ifdef USEOBJECTLIST ConsiderList.reserve(GetNetworkObjectList().ActiveNetworkObjects.Num()); -#endif // std::cout << "ConsiderList.size(): " << GetNetworkObjectList(NetDriver).ActiveNetworkObjects.Num() << '\n'; @@ -384,97 +793,110 @@ int32 UNetDriver::ServerReplicateActors() ServerReplicateActors_BuildConsiderList(ConsiderList); - for (int32 i = 0; i < this->GetClientConnections().Num(); i++) + bool bCPUSaturated = false; + + auto& ClientConnections = GetClientConnections(); + + for (int32 i = 0; i < ClientConnections.Num(); i++) { - UNetConnection* Connection = this->GetClientConnections().at(i); + UNetConnection* Connection = ClientConnections.at(i); if (!Connection) continue; + // idk some dormancy validate stuff should go here + + // if this client shouldn't be ticked this frame if (i >= NumClientsToTick) - continue; - - if (!Connection->GetViewTarget()) - continue; - - if (Connection->GetPlayerController()) { - static void (*SendClientAdjustment)(APlayerController*) = decltype(SendClientAdjustment)(Addresses::SendClientAdjustment); - SendClientAdjustment(Connection->GetPlayerController()); - } - - for (auto& ActorInfo : ConsiderList) - { - if (!ActorInfo || !ActorInfo->Actor) - continue; - - auto Actor = ActorInfo->Actor; - - auto Channel = FindChannel(Actor, Connection); - - if (Addresses::ActorChannelClose && Offsets::IsNetRelevantFor) + //UE_LOG(LogNet, Log, TEXT("skipping update to %s"),*Connection->GetName()); + // then mark each considered actor as bPendingNetUpdate so that they will be considered again the next frame when the connection is actually ticked + for (int32 ConsiderIdx = 0; ConsiderIdx < ConsiderList.size(); ConsiderIdx++) { - static void (*ActorChannelClose)(UActorChannel*) = decltype(ActorChannelClose)(Addresses::ActorChannelClose); - - std::vector ConnectionViewers; - ConnectionViewers.push_back(ConstructNetViewer(Connection)); - - if (!Actor->IsAlwaysRelevant() && !Actor->UsesOwnerRelevancy() && !Actor->IsOnlyRelevantToOwner()) + AActor* Actor = ConsiderList[ConsiderIdx]->Actor; + // if the actor hasn't already been flagged by another connection, + if (Actor != NULL && !ConsiderList[ConsiderIdx]->bPendingNetUpdate) { - if (Connection && Connection->GetViewTarget()) + // find the channel + + UActorChannel* Channel = nullptr; + + auto& ActorChannels = Connection->GetActorChannels(); + + for (int i = 0; i < ActorChannels.Pairs.Num(); i++) { - auto Viewer = Connection->GetViewTarget(); - auto Loc = Viewer->GetActorLocation(); - - if (!IsActorRelevantToConnection(Actor, ConnectionViewers)) + if (ActorChannels.Pairs[i].First == ConsiderList[ConsiderIdx]->WeakActor) { - if (Channel) - ActorChannelClose(Channel); - - continue; + Channel = ActorChannels.Pairs[i].Second; + break; } } + + // and if the channel last update time doesn't match the last net update time for the actor + if (Channel != NULL && Channel->GetLastUpdateTime() < ConsiderList[ConsiderIdx]->LastNetUpdateTime) + { + ConsiderList[ConsiderIdx]->bPendingNetUpdate = true; + } } } - static UChannel* (*CreateChannel)(UNetConnection*, int, bool, int32_t) = decltype(CreateChannel)(Addresses::CreateChannel); - static __int64 (*ReplicateActor)(UActorChannel*) = decltype(ReplicateActor)(Addresses::ReplicateActor); - static __int64 (*SetChannelActor)(UActorChannel*, AActor*) = decltype(SetChannelActor)(Addresses::SetChannelActor); + // Connection->TimeSensitive = false; // TODO Milxnor + } + else if (Connection->GetViewTarget()) + { + // Make a list of viewers this connection should consider (this connection and children of this connection) + // TArray& ConnectionViewers = WorldSettings->ReplicationViewers; - if (!Channel) + // ConnectionViewers.Reset(); + std::vector ConnectionViewers; + // new(ConnectionViewers)FNetViewer(Connection, DeltaSeconds); + ConnectionViewers.push_back(ConstructNetViewer(Connection)); + + // send ClientAdjustment if necessary + // we do this here so that we send a maximum of one per packet to that client; there is no value in stacking additional corrections + if (Connection->GetPlayerController()) { - if (Actor->IsA(APlayerController::StaticClass()) && Actor != Connection->GetPlayerController()) // isnetrelevantfor should handle this iirc - continue; - - Channel = (UActorChannel*)CreateChannel(Connection, 2, true, -1); - - if (Channel) - { - SetChannelActor(Channel, Actor); - } - -#ifdef USEOBJECTLIST - if (Actor->GetNetUpdateFrequency() < 1.0f) - { - ActorInfo->NextUpdateTime = UGameplayStatics::GetTimeSeconds(GetWorld()) + 0.2f * FRand(); - } -#endif + static void (*SendClientAdjustment)(APlayerController*) = decltype(SendClientAdjustment)(Addresses::SendClientAdjustment); + SendClientAdjustment(Connection->GetPlayerController()); } - if (Channel) - { - if (ReplicateActor(Channel)) - { -#ifdef USEOBJECTLIST - auto TimeSeconds = UGameplayStatics::GetTimeSeconds(World); - const float MinOptimalDelta = 1.0f / Actor->GetNetUpdateFrequency(); - const float MaxOptimalDelta = max(1.0f / Actor->GetMinNetUpdateFrequency(), MinOptimalDelta); - const float DeltaBetweenReplications = (TimeSeconds - ActorInfo->LastNetReplicateTime); + FActorPriority* PriorityList = NULL; + FActorPriority** PriorityActors = NULL; - // Choose an optimal time, we choose 70% of the actual rate to allow frequency to go up if needed - ActorInfo->OptimalNetUpdateDelta = std::clamp(DeltaBetweenReplications * 0.7f, MinOptimalDelta, MaxOptimalDelta); // should we use fmath? - ActorInfo->LastNetReplicateTime = TimeSeconds; -#endif + // LOG_INFO(LogDev, "ConsiderList.size(): {}", ConsiderList.size()); + + // Get a sorted list of actors for this connection + const int32 FinalSortedCount = ServerReplicateActors_PrioritizeActors(Connection, ConnectionViewers, ConsiderList, bCPUSaturated, PriorityList, PriorityActors); + + // Process the sorted list of actors for this connection + const int32 LastProcessedActor = ServerReplicateActors_ProcessPrioritizedActors(Connection, ConnectionViewers, PriorityActors, FinalSortedCount, Updated); + + // LOG_INFO(LogDev, "LastProcessedActor: {} FinalSortedCount: {} NetTag: {}", LastProcessedActor, FinalSortedCount, GetNetTag()); + + // relevant actors that could not be processed this frame are marked to be considered for next frame + for (int32 k = LastProcessedActor; k < FinalSortedCount; k++) + { + if (!PriorityActors[k]->ActorInfo) + { + // A deletion entry, skip it because we dont have anywhere to store a 'better give higher priority next time' + continue; + } + + AActor* Actor = PriorityActors[k]->ActorInfo->Actor; + + UActorChannel* Channel = PriorityActors[k]->Channel; + + if (Channel != NULL && GetTime() - Channel->GetRelevantTime() <= 1.f) + { + PriorityActors[k]->ActorInfo->bPendingNetUpdate = true; + } + else if (IsActorRelevantToConnection(Actor, ConnectionViewers)) + { + PriorityActors[k]->ActorInfo->bPendingNetUpdate = true; + if (Channel != NULL) + { + Channel->GetRelevantTime() = GetTime() + 0.5f * SRand(); + } } } } diff --git a/Project Reboot 3.0/NetDriver.h b/Project Reboot 3.0/NetDriver.h index 59e4e14..994dd91 100644 --- a/Project Reboot 3.0/NetDriver.h +++ b/Project Reboot 3.0/NetDriver.h @@ -11,15 +11,17 @@ #include "Map.h" #include "SharedPointer.h" #include "Level.h" +#include "ActorChannel.h" +#include "NetworkGuid.h" struct FActorDestructionInfo { TWeakObjectPtr Level; TWeakObjectPtr ObjOuter; - FVector DestroyedPosition; - int32 NetGUID; - FString PathName; - FName StreamingLevelName; + FVector DestroyedPosition; + FNetworkGUID NetGUID; + FString PathName; + FName StreamingLevelName; }; struct FNetworkObjectInfo { @@ -50,14 +52,18 @@ struct FNetworkObjectInfo /** List of connections that this actor is dormant on */ TSet> DormantConnections; - - /** A list of connections that this actor has recently been dormant on, but the actor doesn't have a channel open yet. - * These need to be differentiated from actors that the client doesn't know about, but there's no explicit list for just those actors. - * (this list will be very transient, with connections being moved off the DormantConnections list, onto this list, and then off once the actor has a channel again) - */ TSet> RecentlyDormantConnections; }; +struct FNetViewer +{ + UNetConnection* Connection; // 0x0000(0x0008) (ZeroConstructor, IsPlainOldData) + AActor* InViewer; // 0x0008(0x0008) (ZeroConstructor, IsPlainOldData) + AActor* ViewTarget; // 0x0010(0x0008) (ZeroConstructor, IsPlainOldData) + FVector ViewLocation; // 0x0018(0x000C) (IsPlainOldData) + FVector ViewDir; +}; + class FNetworkObjectList { public: @@ -72,6 +78,23 @@ public: void Remove(AActor* const Actor); }; +struct FActorPriority +{ + int32 Priority; // Update priority, higher = more important. + + FNetworkObjectInfo* ActorInfo; // Actor info. + UActorChannel* Channel; // Actor channel. + + FActorDestructionInfo* DestructionInfo; // Destroy an actor + + FActorPriority() : + Priority(0), ActorInfo(NULL), Channel(NULL), DestructionInfo(NULL) + {} + + FActorPriority(UNetConnection* InConnection, UActorChannel* InChannel, FNetworkObjectInfo* InActorInfo, const std::vector& Viewers, bool bLowBandwidth); + FActorPriority(UNetConnection* InConnection, FActorDestructionInfo* DestructInfo, const std::vector& Viewers); +}; + class UWorld; struct FURL // idk where this actually goes @@ -96,16 +119,49 @@ public: static void TickFlushHook(UNetDriver* NetDriver); + UObject*& GetWorldPackage() const + { + static auto WorldPackageOffset = GetOffset("WorldPackage"); + return Get(WorldPackageOffset); + } + TArray& GetClientConnections() { static auto ClientConnectionsOffset = GetOffset("ClientConnections"); return Get>(ClientConnectionsOffset); } + float& GetTime() + { + static auto TimeOffset = GetOffset("Time"); + return Get(TimeOffset); + } + + float& GetRelevantTimeout() + { + static auto RelevantTimeoutOffset = GetOffset("RelevantTimeout"); + return Get(RelevantTimeoutOffset); + } + + float& GetSpawnPrioritySeconds() + { + static auto SpawnPrioritySecondsOffset = GetOffset("SpawnPrioritySeconds"); + return Get(SpawnPrioritySecondsOffset); + } + + int32& GetNetTag() + { + static auto NetTagOffset = 0x1DC + 4; + return Get(NetTagOffset); + } + + bool IsLevelInitializedForActor(const AActor* InActor, const UNetConnection* InConnection) const; void RemoveNetworkActor(AActor* Actor); bool InitListen(FNetworkNotify* InNotify, FURL& ListenURL, bool bReuseAddressAndPort, FString& Error) { return InitListenOriginal(this, InNotify, ListenURL, bReuseAddressAndPort, Error); } void SetWorld(UWorld* World) { return SetWorldOriginal(this, World); } int32 ServerReplicateActors(); + int32 ServerReplicateActors_ProcessPrioritizedActors(UNetConnection* Connection, const std::vector& ConnectionViewers, FActorPriority** PriorityActors, const int32 FinalSortedCount, int32& OutUpdated); void ServerReplicateActors_BuildConsiderList(std::vector& OutConsiderList); + int32 ServerReplicateActors_PrioritizeActors(UNetConnection* Connection, const std::vector& ConnectionViewers, const std::vector ConsiderList, const bool bCPUSaturated, FActorPriority*& OutPriorityList, FActorPriority**& OutPriorityActors); FNetworkObjectList& GetNetworkObjectList(); }; \ No newline at end of file diff --git a/Project Reboot 3.0/NetworkGuid.h b/Project Reboot 3.0/NetworkGuid.h new file mode 100644 index 0000000..4867c76 --- /dev/null +++ b/Project Reboot 3.0/NetworkGuid.h @@ -0,0 +1,14 @@ +#pragma once + +#include "inc.h" + +class FNetworkGUID +{ +public: + uint32 Value; + + friend bool operator==(const FNetworkGUID& X, const FNetworkGUID& Y) + { + return (X.Value == Y.Value); + } +}; diff --git a/Project Reboot 3.0/Object.cpp b/Project Reboot 3.0/Object.cpp index 5832734..fa7b954 100644 --- a/Project Reboot 3.0/Object.cpp +++ b/Project Reboot 3.0/Object.cpp @@ -18,6 +18,44 @@ FName* getFNameOfProp(void* Property) return NamePrivate; }; +void* UObject::GetProperty(const std::string& ChildName, bool bWarnIfNotFound) const +{ + for (auto CurrentClass = ClassPrivate; CurrentClass; CurrentClass = *(UClass**)(__int64(CurrentClass) + Offsets::SuperStruct)) + { + void* Property = *(void**)(__int64(CurrentClass) + Offsets::Children); + + if (Property) + { + // LOG_INFO(LogDev, "Reading prop name.."); + + std::string PropName = getFNameOfProp(Property)->ToString(); + + // LOG_INFO(LogDev, "PropName: {}", PropName); + + if (PropName == ChildName) + { + return Property; + } + + while (Property) + { + if (PropName == ChildName) + { + return Property; + } + + Property = Engine_Version >= 425 ? *(void**)(__int64(Property) + 0x20) : ((UField*)Property)->Next; + PropName = Property ? getFNameOfProp(Property)->ToString() : ""; + } + } + } + + if (bWarnIfNotFound) + LOG_WARN(LogFinder, "Unable to find0{}", ChildName); + + return nullptr; +} + void* UObject::GetProperty(const std::string& ChildName, bool bWarnIfNotFound) { for (auto CurrentClass = ClassPrivate; CurrentClass; CurrentClass = *(UClass**)(__int64(CurrentClass) + Offsets::SuperStruct)) @@ -66,6 +104,16 @@ int UObject::GetOffset(const std::string& ChildName, bool bWarnIfNotFound) return *(int*)(__int64(Property) + Offsets::Offset_Internal); } +int UObject::GetOffset(const std::string& ChildName, bool bWarnIfNotFound) const +{ + auto Property = GetProperty(ChildName, bWarnIfNotFound); + + if (!Property) + return -1; + + return *(int*)(__int64(Property) + Offsets::Offset_Internal); +} + bool UObject::ReadBitfieldValue(int Offset, uint8_t FieldMask) { return ReadBitfield(this->GetPtr(Offset), FieldMask); diff --git a/Project Reboot 3.0/Object.h b/Project Reboot 3.0/Object.h index ca1419f..0919423 100644 --- a/Project Reboot 3.0/Object.h +++ b/Project Reboot 3.0/Object.h @@ -45,10 +45,12 @@ public: bool IsA(UClass* Other); void* GetProperty(const std::string& ChildName, bool bWarnIfNotFound = true); + void* GetProperty(const std::string& ChildName, bool bWarnIfNotFound = true) const; int GetOffset(const std::string& ChildName, bool bWarnIfNotFound = true); + int GetOffset(const std::string& ChildName, bool bWarnIfNotFound = true) const; template - T& Get(int Offset) { return *(T*)(__int64(this) + Offset); } + T& Get(int Offset) const { return *(T*)(__int64(this) + Offset); } bool ReadBitfieldValue(int Offset, uint8_t FieldMask); bool ReadBitfieldValue(const std::string& ChildName, uint8_t FieldMask) { return ReadBitfieldValue(GetOffset(ChildName), FieldMask); } diff --git a/Project Reboot 3.0/Player.h b/Project Reboot 3.0/Player.h index eac0941..0c23047 100644 --- a/Project Reboot 3.0/Player.h +++ b/Project Reboot 3.0/Player.h @@ -6,7 +6,7 @@ class UPlayer : public UObject { public: - APlayerController*& GetPlayerController() + APlayerController*& GetPlayerController() const { static auto PlayerControllerOffset = GetOffset("PlayerController"); return Get(PlayerControllerOffset); diff --git a/Project Reboot 3.0/Project Reboot 3.0.vcxproj b/Project Reboot 3.0/Project Reboot 3.0.vcxproj index 8a5737f..c26ea1c 100644 --- a/Project Reboot 3.0/Project Reboot 3.0.vcxproj +++ b/Project Reboot 3.0/Project Reboot 3.0.vcxproj @@ -228,6 +228,7 @@ + @@ -242,6 +243,7 @@ + @@ -293,6 +295,8 @@ + + @@ -307,6 +311,7 @@ + @@ -317,6 +322,7 @@ + @@ -329,6 +335,7 @@ + diff --git a/Project Reboot 3.0/Project Reboot 3.0.vcxproj.filters b/Project Reboot 3.0/Project Reboot 3.0.vcxproj.filters index 5e25c34..34abdbe 100644 --- a/Project Reboot 3.0/Project Reboot 3.0.vcxproj.filters +++ b/Project Reboot 3.0/Project Reboot 3.0.vcxproj.filters @@ -506,6 +506,27 @@ FortniteGame\Source\FortniteGame\Public\Pawns + + Engine\Source\Runtime\Core\Public\GenericPlatform + + + Engine\Source\Runtime\Engine\Classes\Engine + + + Engine\Source\Runtime\Engine\Classes\Engine + + + Engine\Source\Runtime\Core\Public\Misc + + + Engine\Source\Runtime\Core\Public\Math + + + Engine\Source\Runtime\Core\Public\GenericPlatform + + + Engine\Source\Runtime\Core\Public\Math + @@ -691,6 +712,9 @@ {31a7f342-8b7c-4594-a24d-c4dd5c9d230d} + + {653d6dbf-b361-41ea-a9b8-b85737412d66} + diff --git a/Project Reboot 3.0/RandomStream.h b/Project Reboot 3.0/RandomStream.h new file mode 100644 index 0000000..dc4b18b --- /dev/null +++ b/Project Reboot 3.0/RandomStream.h @@ -0,0 +1,76 @@ +#pragma once + +#include "inc.h" + +struct FRandomStream +{ +public: + + /** + * Default constructor. + * + * The seed should be set prior to use. + */ + FRandomStream() + : InitialSeed(0) + , Seed(0) + { } + + /** + * Creates and initializes a new random stream from the specified seed value. + * + * @param InSeed The seed value. + */ + FRandomStream(int32 InSeed) + { + Initialize(InSeed); + } + +public: + + /** + * Initializes this random stream with the specified seed value. + * + * @param InSeed The seed value. + */ + void Initialize(int32 InSeed) + { + InitialSeed = InSeed; + Seed = uint32(InSeed); + } + + float GetFraction() const + { + MutateSeed(); + + float Result; + + *(uint32*)&Result = 0x3F800000U | (Seed >> 9); + + return Result - 1.0f; + } + + FORCEINLINE float FRand() const + { + return GetFraction(); + } + +protected: + + /** + * Mutates the current seed into the next seed. + */ + void MutateSeed() const + { + Seed = (Seed * 196314165U) + 907633515U; + } + +private: + + // Holds the initial seed. + int32 InitialSeed; + + // Holds the current seed. This should be an uint32 so that any shift to obtain top bits + // is a logical shift, rather than an arithmetic shift (which smears down the negative bit). + mutable uint32 Seed; +}; \ No newline at end of file diff --git a/Project Reboot 3.0/SparseArray.h b/Project Reboot 3.0/SparseArray.h index 3e9302d..e707854 100644 --- a/Project Reboot 3.0/SparseArray.h +++ b/Project Reboot 3.0/SparseArray.h @@ -166,12 +166,12 @@ public: int32 PreviousFreeIndex = -1; int32 NextFreeIndex = -1; - if (NumFreeIndices == 0) + /* if (NumFreeIndices == 0) { FirstFreeIndex = IndexToRemove; Data.at(IndexToRemove) = { -1, -1 }; } - else + else */ { for (auto It = AllocationFlags.begin(); It != AllocationFlags.end(); ++It) { @@ -189,6 +189,7 @@ public: } } } + AllocationFlags.Set(IndexToRemove, false); NumFreeIndices++; diff --git a/Project Reboot 3.0/UnrealMathUtility.h b/Project Reboot 3.0/UnrealMathUtility.h new file mode 100644 index 0000000..2354654 --- /dev/null +++ b/Project Reboot 3.0/UnrealMathUtility.h @@ -0,0 +1,12 @@ +#pragma once + +#include "GenericPlatformMath.h" + +struct FMath : public FPlatformMath +{ + template< class T > + static FORCEINLINE T Clamp(const T X, const T Min, const T Max) + { + return X < Min ? Min : X < Max ? X : Max; + } +}; \ No newline at end of file diff --git a/Project Reboot 3.0/Vector.h b/Project Reboot 3.0/Vector.h index 3b9092b..6c6ec61 100644 --- a/Project Reboot 3.0/Vector.h +++ b/Project Reboot 3.0/Vector.h @@ -17,6 +17,16 @@ public: return FVector{ this->X - A.X, this->Y - A.Y, this->Z - A.Z }; } + FORCEINLINE float SizeSquared() const + { + return X * X + Y * Y + Z * Z; + } + + FORCEINLINE float operator|(const FVector& V) const + { + return X * V.X + Y * V.Y + Z * V.Z; + } + FVector operator*(const float A) { return FVector{ this->X * A, this->Y * A, this->Z * A }; diff --git a/Project Reboot 3.0/WeakObjectPtr.h b/Project Reboot 3.0/WeakObjectPtr.h index 66dd808..ba3ca84 100644 --- a/Project Reboot 3.0/WeakObjectPtr.h +++ b/Project Reboot 3.0/WeakObjectPtr.h @@ -12,4 +12,9 @@ public: { return ChunkedObjects ? ChunkedObjects->GetObjectByIndex(ObjectIndex) : UnchunkedObjects ? UnchunkedObjects->GetObjectByIndex(ObjectIndex) : nullptr; } + + bool operator==(const FWeakObjectPtr& other) + { + return ObjectIndex == other.ObjectIndex && ObjectSerialNumber == other.ObjectSerialNumber; // i need to check in ue if we check serialnumber + } }; \ No newline at end of file diff --git a/Project Reboot 3.0/WeakObjectPtrTemplates.h b/Project Reboot 3.0/WeakObjectPtrTemplates.h index 0e32554..8807d3e 100644 --- a/Project Reboot 3.0/WeakObjectPtrTemplates.h +++ b/Project Reboot 3.0/WeakObjectPtrTemplates.h @@ -13,4 +13,9 @@ struct TWeakObjectPtr : public TWeakObjectPtrBase { return (T*)TWeakObjectPtrBase::Get(); } + + bool operator==(const TWeakObjectPtr& other) + { + return TWeakObjectPtrBase::operator==(other); + } }; \ No newline at end of file diff --git a/Project Reboot 3.0/dllmain.cpp b/Project Reboot 3.0/dllmain.cpp index 448649a..ff081fe 100644 --- a/Project Reboot 3.0/dllmain.cpp +++ b/Project Reboot 3.0/dllmain.cpp @@ -11,6 +11,8 @@ #include "FortPlayerPawn.h" #include "globals.h" #include "FortInventoryInterface.h" +#include +#include "GenericPlatformTime.h" #include "Map.h" #include "events.h" @@ -150,6 +152,9 @@ DWORD WINAPI Main(LPVOID) Hooking::MinHook::Hook((PVOID)Addresses::GetNetMode, (PVOID)GetNetModeHook, nullptr); } + GSRandSeed = FGenericPlatformTime::Cycles(); + ReplicationRandStream = FRandomStream(FGenericPlatformTime::Cycles()); + Hooking::MinHook::Hook((PVOID)Addresses::KickPlayer, (PVOID)AGameSession::KickPlayerHook, (PVOID*)&AGameSession::KickPlayerOriginal); LOG_INFO(LogDev, "Size: 0x{:x}", sizeof(TMap)); @@ -723,6 +728,16 @@ DWORD WINAPI Main(LPVOID) LOG_ERROR(LogGame, "Restarting is not supported on chapter 2 and above!"); } } + + else if (GetAsyncKeyState(VK_F11) & 1) + { + std::ofstream stream("Test.log"); + + for (auto& Current : ReplicatedActors) + { + stream << Current << '\n'; + } + } Sleep(1000 / 30); } diff --git a/Project Reboot 3.0/inc.h b/Project Reboot 3.0/inc.h index 63880ea..f64fb9b 100644 --- a/Project Reboot 3.0/inc.h +++ b/Project Reboot 3.0/inc.h @@ -16,6 +16,18 @@ extern inline int Engine_Version = 0; // For example, 420, 421, etc. // Prevent extern inline double Fortnite_Version = 0; // For example, 4.1, 6.21, etc. // Prevent using this when possible. extern inline int Fortnite_CL = 0; +struct PlaceholderBitfield +{ + uint8_t First : 1; + uint8_t Second : 1; + uint8_t Third : 1; + uint8_t Fourth : 1; + uint8_t Fifth : 1; + uint8_t Sixth : 1; + uint8_t Seventh : 1; + uint8_t Eighth : 1; +}; + #define MS_ALIGN(n) __declspec(align(n)) #define FORCENOINLINE __declspec(noinline) diff --git a/Project Reboot 3.0/reboot.h b/Project Reboot 3.0/reboot.h index 93433ea..dfd210e 100644 --- a/Project Reboot 3.0/reboot.h +++ b/Project Reboot 3.0/reboot.h @@ -4,8 +4,10 @@ #include "Engine.h" // #include "World.h" +#include "RandomStream.h" #include "Class.h" #include "globals.h" +#include /* enum class REBOOT_ERROR : uint8 { @@ -130,18 +132,9 @@ static __forceinline T* Cast(UObject* Object, bool bCheckType = true) } extern inline int AmountOfRestarts = 0; // DO NOT CHANGE - -struct PlaceholderBitfield -{ - uint8_t First : 1; - uint8_t Second : 1; - uint8_t Third : 1; - uint8_t Fourth : 1; - uint8_t Fifth : 1; - uint8_t Sixth : 1; - uint8_t Seventh : 1; - uint8_t Eighth : 1; -}; +extern inline FRandomStream ReplicationRandStream = (0); +extern inline int32 GSRandSeed = 0; +extern inline std::set ReplicatedActors = {}; inline uint8_t GetFieldMask(void* Property, int additional = 0) {