bool FObjectReplicator::SerializeCustomDeltaProperty( UNetConnection * Connection, void * Src, UProperty * Property, int32 ArrayDim, FNetBitWriter & OutBunch, TSharedPtr<INetDeltaBaseState> &NewFullState, TSharedPtr<INetDeltaBaseState> & OldState ) { check( NewFullState.IsValid() == false ); // NewState is passed in as NULL and instantiated within this function if necessary SCOPE_CYCLE_COUNTER( STAT_NetSerializeItemDeltaTime ); UStructProperty * StructProperty = CastChecked< UStructProperty >( Property ); //------------------------------------------------ // Custom NetDeltaSerialization //------------------------------------------------ if ( !ensure( ( StructProperty->Struct->StructFlags & STRUCT_NetDeltaSerializeNative ) != 0 ) ) { return false; } FNetDeltaSerializeInfo Parms; FNetSerializeCB NetSerializeCB( Connection->Driver ); Parms.OutBunch = &OutBunch; Parms.Map = Connection->PackageMap; Parms.OldState = OldState.Get(); Parms.NewState = &NewFullState; Parms.NetSerializeCB = &NetSerializeCB; UScriptStruct::ICppStructOps * CppStructOps = StructProperty->Struct->GetCppStructOps(); check(CppStructOps); // else should not have STRUCT_NetSerializeNative check(!StructProperty->Struct->InheritedCppStructOps()); // else should not have STRUCT_NetSerializeNative Parms.Struct = StructProperty->Struct; return CppStructOps->NetDeltaSerialize( Parms, Property->ContainerPtrToValuePtr<void>( Src, ArrayDim ) ); }
bool UStructProperty::ConvertFromType(const FPropertyTag& Tag, FArchive& Ar, uint8* Data, UStruct* DefaultsStruct, bool& bOutAdvanceProperty) { bOutAdvanceProperty = false; auto CanSerializeFromStructWithDifferentName = [](const FArchive& InAr, const FPropertyTag& PropertyTag, const UStructProperty* StructProperty) { if (InAr.UE4Ver() < VER_UE4_STRUCT_GUID_IN_PROPERTY_TAG) { // Old Implementation return StructProperty && !StructProperty->UseBinaryOrNativeSerialization(InAr); } return PropertyTag.StructGuid.IsValid() && StructProperty && StructProperty->Struct && (PropertyTag.StructGuid == StructProperty->Struct->GetCustomGuid()); }; if (Struct) { if ((Struct->StructFlags & STRUCT_SerializeFromMismatchedTag) && (Tag.Type != NAME_StructProperty || (Tag.StructName != Struct->GetFName())) && Struct->StructFlags & STRUCT_SerializeFromMismatchedTag) { UScriptStruct::ICppStructOps* CppStructOps = Struct->GetCppStructOps(); check(CppStructOps && CppStructOps->HasSerializeFromMismatchedTag()); // else should not have STRUCT_SerializeFromMismatchedTag void* DestAddress = ContainerPtrToValuePtr<void>(Data, Tag.ArrayIndex); if (CppStructOps->SerializeFromMismatchedTag(Tag, Ar, DestAddress)) { bOutAdvanceProperty = true; } else { UE_LOG(LogClass, Warning, TEXT("SerializeFromMismatchedTag failed: Type mismatch in %s of %s - Previous (%s) Current(StructProperty) for package: %s"), *Tag.Name.ToString(), *GetName(), *Tag.Type.ToString(), *Ar.GetArchiveName()); } return true; } else if (Tag.Type == NAME_StructProperty && Tag.StructName != Struct->GetFName() && !CanSerializeFromStructWithDifferentName(Ar, Tag, this)) { //handle Vector -> Vector4 upgrades here because using the SerializeFromMismatchedTag system would cause a dependency from Core -> CoreUObject if (Tag.StructName == NAME_Vector && Struct->GetFName() == NAME_Vector4) { void* DestAddress = ContainerPtrToValuePtr<void>(Data, Tag.ArrayIndex); FVector OldValue; Ar << OldValue; //only set X/Y/Z. The W should already have been set to the property specific default and we don't want to trash it by forcing 0 or 1. FVector4* DestValue = (FVector4*)DestAddress; DestValue->X = OldValue.X; DestValue->Y = OldValue.Y; DestValue->Z = OldValue.Z; } else { UE_LOG(LogClass, Warning, TEXT("Property %s of %s has a struct type mismatch (tag %s != prop %s) in package: %s. If that struct got renamed, add an entry to ActiveStructRedirects."), *Tag.Name.ToString(), *GetName(), *Tag.StructName.ToString(), *Struct->GetName(), *Ar.GetArchiveName()); } return true; } } return false; }
bool UStructProperty::HasNoOpConstructor() const { Struct->PrepareCppStructOps(); UScriptStruct::ICppStructOps* CppStructOps = Struct->GetCppStructOps(); if (CppStructOps && CppStructOps->HasNoopConstructor()) { return true; } return false; }
void UStructProperty::ExportTextItem( FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope ) const { if (Struct->StructFlags & STRUCT_ExportTextItemNative) { UScriptStruct::ICppStructOps* CppStructOps = Struct->GetCppStructOps(); check(CppStructOps); // else should not have STRUCT_ExportTextItemNative check(!Struct->InheritedCppStructOps()); // else should not have STRUCT_ExportTextItemNative if (CppStructOps->ExportTextItem(ValueStr, PropertyValue, DefaultValue, Parent, PortFlags, ExportRootScope)) { return; } } UStructProperty_ExportTextItem(Struct, ValueStr, PropertyValue, DefaultValue, Parent, PortFlags, ExportRootScope); }
void UStructProperty::SerializeItem( FArchive& Ar, void* Value, int32 MaxReadBytes, void const* Defaults ) const { const bool bUseBinarySerialization = UseBinarySerialization(Ar); const bool bUseNativeSerialization = UseNativeSerialization(); // Preload struct before serialization tracking to not double count time. if ( bUseBinarySerialization || bUseNativeSerialization) { Ar.Preload( Struct ); } bool bItemSerialized = false; if (bUseNativeSerialization) { UScriptStruct::ICppStructOps* CppStructOps = Struct->GetCppStructOps(); check(CppStructOps); // else should not have STRUCT_SerializeNative check(!Struct->InheritedCppStructOps()); // else should not have STRUCT_SerializeNative bItemSerialized = CppStructOps->Serialize(Ar, Value); } if (!bItemSerialized) { if( bUseBinarySerialization ) { // Struct is already preloaded above. if ( !Ar.IsPersistent() && Ar.GetPortFlags() != 0 && !Struct->ShouldSerializeAtomically(Ar) ) { Struct->SerializeBinEx( Ar, Value, Defaults, Struct ); } else { Struct->SerializeBin( Ar, Value, MaxReadBytes ); } } else { Struct->SerializeTaggedProperties( Ar, (uint8*)Value, Struct, (uint8*)Defaults ); } } if (Struct->StructFlags & STRUCT_PostSerializeNative) { UScriptStruct::ICppStructOps* CppStructOps = Struct->GetCppStructOps(); check(CppStructOps); // else should not have STRUCT_PostSerializeNative check(!Struct->InheritedCppStructOps()); // else should not have STRUCT_PostSerializeNative CppStructOps->PostSerialize(Ar, Value); } }
bool UStructProperty::NetSerializeItem( FArchive& Ar, UPackageMap* Map, void* Data, TArray<uint8> * MetaData ) const { //------------------------------------------------ // Custom NetSerialization //------------------------------------------------ if (Struct->StructFlags & STRUCT_NetSerializeNative) { UScriptStruct::ICppStructOps* CppStructOps = Struct->GetCppStructOps(); check(CppStructOps); // else should not have STRUCT_NetSerializeNative bool bSuccess = true; bool bMapped = CppStructOps->NetSerialize(Ar, Map, bSuccess, Data); if (!bSuccess) { UE_LOG(LogProperty, Warning, TEXT("Native NetSerialize %s (%s) failed."), *GetFullName(), *Struct->GetFullName() ); } return bMapped; } UE_LOG( LogProperty, Fatal, TEXT( "Deprecated code path" ) ); return 1; }
virtual void NetSerializeStruct( UScriptStruct* Struct, FArchive& Ar, UPackageMap* Map, void* Data, bool& bHasUnmapped ) { if (Struct->StructFlags & STRUCT_NetSerializeNative) { UScriptStruct::ICppStructOps* CppStructOps = Struct->GetCppStructOps(); check(CppStructOps); // else should not have STRUCT_NetSerializeNative check(!Struct->InheritedCppStructOps()); // else should not have STRUCT_NetSerializeNative bool bSuccess = true; if (!CppStructOps->NetSerialize(Ar, Map, bSuccess, Data)) { bHasUnmapped = true; } if (!bSuccess) { UE_LOG(LogRep, Warning, TEXT("NetSerializeStruct: Native NetSerialize %s failed."), *Struct->GetFullName()); } } else { TSharedPtr<FRepLayout> RepLayout = Driver->GetStructRepLayout(Struct); RepLayout->SerializePropertiesForStruct(Struct, Ar, Map, Data, bHasUnmapped); } }
bool FObjectReplicator::ReceivedBunch( FInBunch &Bunch, const FReplicationFlags & RepFlags ) { UObject * Object = GetObject(); UPackageMap * PackageMap = OwningChannel->Connection->PackageMap; if ( Object == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Object == NULL" ) ); return false; } const bool bIsServer = ( OwningChannel->Connection->Driver->ServerConnection == NULL ); FClassNetCache * ClassCache = PackageMap->GetClassNetCache( ObjectClass ); if ( ClassCache == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: ClassCache == NULL: %s" ), *Object->GetFullName() ); return false; } bool bThisBunchReplicatedProperties = false; // First RepIndex. int32 RepIndex = Bunch.ReadInt( ClassCache->GetMaxIndex() + 1 ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Error reading bunch 1: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex == ClassCache->GetMaxIndex() ) { // There are no actual replicated properties or functions in this bunch. That is ok - we may have gotten this // actor/subobject because we want the client to spawn one (but we arent actually replicating properties on it) return true; } if ( RepIndex > ClassCache->GetMaxIndex() ) { // We shouldn't be receiving this bunch of this object has no properties or RPC functions to process UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: RepIndex too large: %s" ), *Object->GetFullName() ); return false; } FFieldNetCache * FieldCache = ClassCache->GetFromIndex( RepIndex ); if ( FieldCache == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: FieldCache == NULL: %s" ), *Object->GetFullName() ); return false; } while ( FieldCache ) { // Receive properties from the net. UProperty * ReplicatedProp = NULL; int32 LastIndex = 0; while ( FieldCache && ( ReplicatedProp = Cast< UProperty >( FieldCache->Field ) ) != NULL ) { NET_CHECKSUM( Bunch ); // Server shouldn't receive properties. if ( bIsServer ) { UE_LOG( LogNet, Error, TEXT( "Server received unwanted property value %s in %s" ), *ReplicatedProp->GetName(), *Object->GetFullName() ); return false; } bThisBunchReplicatedProperties = true; if ( !bHasReplicatedProperties ) { bHasReplicatedProperties = true; // Persistent, not reset until PostNetReceive is called PreNetReceive(); } bool DebugProperty = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("net.Replication.DebugProperty")); if (CVar && !CVar->GetString().IsEmpty() && ReplicatedProp->GetName().Contains(CVar->GetString()) ) { UE_LOG(LogNet, Log, TEXT("Replicating Property[%d] %s on %s"), RepIndex, *ReplicatedProp->GetName(), *Object->GetName()); DebugProperty = true; } } #endif if ( !Retirement[ ReplicatedProp->RepIndex ].CustomDelta ) { // We hijack a non custom delta property to signify we are using FRepLayout to read the entire property block FPropertyRetirement & Retire = Retirement[ ReplicatedProp->RepIndex ]; bool bDiscardLayout = false; if ( Bunch.PacketId >= Retire.InPacketId ) //!! problem with reliable pkts containing dynamic references, being retransmitted, and overriding newer versions. Want "OriginalPacketId" for retransmissions? { // Receive this new property. Retire.InPacketId = Bunch.PacketId; } else { bDiscardLayout = true; } if ( !RepLayout->ReceiveProperties( ObjectClass, RepState, (void*)Object, Bunch, bDiscardLayout ) ) { UE_LOG( LogNet, Error, TEXT( "ReceiveProperties FAILED %s in %s" ), *ReplicatedProp->GetName(), *Object->GetFullName() ); return false; } } else { // Receive array index. int32 Element = 0; if ( ReplicatedProp->ArrayDim != 1 ) { // Serialize index as delta from previous index to increase chance we'll only use 1 byte uint32 idx; Bunch.SerializeIntPacked( idx ); Element = static_cast< int32 >( idx ) + LastIndex; LastIndex = Element; if ( Element >= ReplicatedProp->ArrayDim ) { UE_LOG( LogNet, Error, TEXT( "Element index too large %s in %s" ), *ReplicatedProp->GetName(), *Object->GetFullName() ); return false; } } // Pointer to destination. uint8 * DestObj = (uint8*)Object; uint8 * DestRecent = RepState->StaticBuffer.Num() ? RepState->StaticBuffer.GetTypedData() : NULL; // Check property ordering. FPropertyRetirement & Retire = Retirement[ ReplicatedProp->RepIndex + Element ]; if ( Bunch.PacketId >= Retire.InPacketId ) //!! problem with reliable pkts containing dynamic references, being retransmitted, and overriding newer versions. Want "OriginalPacketId" for retransmissions? { // Receive this new property. Retire.InPacketId = Bunch.PacketId; } else { // Skip this property, because it's out-of-date. UE_LOG( LogNetTraffic, Log, TEXT( "Received out-of-date %s" ), *ReplicatedProp->GetName() ); DestObj = NULL; DestRecent = NULL; } FMemMark Mark(FMemStack::Get()); uint8 * Data = DestObj ? ReplicatedProp->ContainerPtrToValuePtr<uint8>(DestObj, Element) : NewZeroed<uint8>(FMemStack::Get(),ReplicatedProp->ElementSize); TArray<uint8> MetaData; PTRINT Offset = 0; // Copy current value over to Recent for comparison if ( DestRecent ) { Offset = ReplicatedProp->ContainerPtrToValuePtr<uint8>(DestRecent, Element) - DestRecent; check( Offset >= 0 && Offset < RepState->StaticBuffer.Num() ); //@todo if we move properties outside of the memory block, then this will not work anyway ReplicatedProp->CopySingleValue( DestRecent + Offset, Data ); } // Receive custom delta property. UStructProperty * StructProperty = Cast< UStructProperty >( ReplicatedProp ); if ( StructProperty == NULL ) { // This property isn't custom delta UE_LOG( LogNetTraffic, Error, TEXT( "Property isn't custom delta %s" ), *ReplicatedProp->GetName() ); return false; } UScriptStruct * InnerStruct = StructProperty->Struct; if ( !( InnerStruct->StructFlags & STRUCT_NetDeltaSerializeNative ) ) { // This property isn't custom delta UE_LOG( LogNetTraffic, Error, TEXT( "Property isn't custom delta %s" ), *ReplicatedProp->GetName() ); return false; } UScriptStruct::ICppStructOps * CppStructOps = InnerStruct->GetCppStructOps(); check( CppStructOps ); check( !InnerStruct->InheritedCppStructOps() ); FNetDeltaSerializeInfo Parms; FNetSerializeCB NetSerializeCB( OwningChannel->Connection->Driver ); Parms.DebugName = StructProperty->GetName(); Parms.Struct = InnerStruct; Parms.Map = PackageMap; Parms.InArchive = &Bunch; Parms.NetSerializeCB = &NetSerializeCB; // Call the custom delta serialize function to handle it CppStructOps->NetDeltaSerialize( Parms, Data ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: NetDeltaSerialize - Bunch.IsError() == true: %s" ), *Object->GetFullName() ); return false; } // See if it changed from our local value bool PropertyChanged = true; if ( DestRecent ) { // POD types can do a memcmp with a call to Identical if ( ReplicatedProp->Identical( DestRecent + Offset, Data ) ) { PropertyChanged = false; } } Mark.Pop(); // Successfully received it. UE_LOG( LogNetTraffic, Log, TEXT( " %s - %s - Change: %d" ), *Object->GetName(), *ReplicatedProp->GetName(), PropertyChanged ); // Notify the Object if this var is RepNotify if ( PropertyChanged ) { QueuePropertyRepNotify( Object, ReplicatedProp, Element, MetaData ); } } // Next. RepIndex = Bunch.ReadInt( ClassCache->GetMaxIndex() + 1 ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Error reading bunch 2: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex > ClassCache->GetMaxIndex() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: RepIndex too large: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex == ClassCache->GetMaxIndex() ) { // We're done FieldCache = NULL; } else { FieldCache = ClassCache->GetFromIndex( RepIndex ); if ( FieldCache == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: FieldCache == NULL: %s" ), *Object->GetFullName() ); return false; } } } // Handle function calls. if ( FieldCache && Cast< UFunction >( FieldCache->Field ) ) { FName Message = FieldCache->Field->GetFName(); UFunction * Function = Object->FindFunction( Message ); if ( Function == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Function == NULL: %s" ), *Object->GetFullName() ); return false; } if ( ( Function->FunctionFlags & FUNC_Net ) == 0 ) { UE_LOG( LogNet, Error, TEXT( "Rejected non RPC function %s in %s" ), *Message.ToString(), *Object->GetFullName() ); return false; } if ( ( Function->FunctionFlags & ( bIsServer ? FUNC_NetServer : ( FUNC_NetClient | FUNC_NetMulticast ) ) ) == 0 ) { UE_LOG( LogNet, Error, TEXT( "Rejected RPC function due to access rights %s in %s" ), *Message.ToString(), *Object->GetFullName() ); return false; } UE_LOG( LogNetTraffic, Log, TEXT( " Received RPC: %s" ), *Message.ToString() ); // Get the parameters. FMemMark Mark(FMemStack::Get()); uint8* Parms = new(FMemStack::Get(),MEM_Zeroed,Function->ParmsSize)uint8; // Use the replication layout to receive the rpc parameter values TSharedPtr<FRepLayout> FuncRepLayout = OwningChannel->Connection->Driver->GetFunctionRepLayout( Function ); FuncRepLayout->ReceivePropertiesForRPC( Object, Function, OwningChannel, Bunch, Parms ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: ReceivePropertiesForRPC - Bunch.IsError() == true: %s" ), *Object->GetFullName() ); return false; } // validate that the function is callable here const bool bCanExecute = ( ( !bIsServer || RepFlags.bNetOwner ) ); // we are client or net owner if ( bCanExecute ) { // Call the function. RPC_ResetLastFailedReason(); Object->ProcessEvent( Function, Parms ); if ( RPC_GetLastFailedReason() != NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: RPC_GetLastFailedReason: %s" ), RPC_GetLastFailedReason() ); return false; } } else { UE_LOG( LogNet, Warning, TEXT( "Rejected unwanted function %s in %s" ), *Message.ToString(), *Object->GetFullName() ); if ( !OwningChannel->Connection->TrackLogsPerSecond() ) // This will disconnect the client if we get her too often { return false; } } // Destroy the parameters. //warning: highly dependent on UObject::ProcessEvent freeing of parms! for ( UProperty * Destruct=Function->DestructorLink; Destruct; Destruct=Destruct->DestructorLinkNext ) { if( Destruct->IsInContainer(Function->ParmsSize) ) { Destruct->DestroyValue_InContainer(Parms); } } Mark.Pop(); if ( Object == NULL || Object->IsPendingKill() ) { // replicated function destroyed Object return true; // FIXME: Should this return false to kick connection? Seems we'll cause a read misalignment here if we don't } // Next. RepIndex = Bunch.ReadInt( ClassCache->GetMaxIndex() + 1 ); if ( Bunch.IsError() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Error reading bunch 2: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex > ClassCache->GetMaxIndex() ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: RepIndex too large: %s" ), *Object->GetFullName() ); return false; } if ( RepIndex == ClassCache->GetMaxIndex() ) { // We're done FieldCache = NULL; } else { FieldCache = ClassCache->GetFromIndex( RepIndex ); if ( FieldCache == NULL ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: FieldCache == NULL: %s" ), *Object->GetFullName() ); return false; } } } else if ( FieldCache ) { UE_LOG( LogNet, Error, TEXT( "ReceivedBunch: Invalid replicated field %i in %s" ), RepIndex, *Object->GetFullName() ); return false; } } return true; }
const TCHAR* UStructProperty::ImportText_Static(UScriptStruct* InStruct, const FString& Name, const TCHAR* InBuffer, void* Data, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText) { auto Struct = InStruct; if (Struct->StructFlags & STRUCT_ImportTextItemNative) { UScriptStruct::ICppStructOps* CppStructOps = Struct->GetCppStructOps(); check(CppStructOps); // else should not have STRUCT_ImportTextItemNative if (CppStructOps->ImportTextItem(InBuffer, Data, PortFlags, Parent, ErrorText)) { return InBuffer; } } TArray<FDefinedProperty> DefinedProperties; // this keeps track of the number of errors we've logged, so that we can add new lines when logging more than one error int32 ErrorCount = 0; const TCHAR* Buffer = InBuffer; if (*Buffer++ == TCHAR('(')) { // Parse all properties. while (*Buffer != TCHAR(')')) { // parse and import the value Buffer = ImportSingleProperty(Buffer, Data, Struct, Parent, PortFlags | PPF_Delimited, ErrorText, DefinedProperties); // skip any remaining text before the next property value SkipWhitespace(Buffer); int32 SubCount = 0; while ( *Buffer && *Buffer != TCHAR('\r') && *Buffer != TCHAR('\n') && (SubCount > 0 || *Buffer != TCHAR(')')) && (SubCount > 0 || *Buffer != TCHAR(',')) ) { SkipWhitespace(Buffer); if (*Buffer == TCHAR('\"')) { do { Buffer++; } while (*Buffer && *Buffer != TCHAR('\"') && *Buffer != TCHAR('\n') && *Buffer != TCHAR('\r')); if (*Buffer != TCHAR('\"')) { ErrorText->Logf(TEXT("%sImportText (%s): Bad quoted string at: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *Name, Buffer); return NULL; } } else if( *Buffer == TCHAR('(') ) { SubCount++; } else if( *Buffer == TCHAR(')') ) { SubCount--; if( SubCount < 0 ) { ErrorText->Logf(TEXT("%sImportText (%s): Too many closing parenthesis in: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *Name, InBuffer); return NULL; } } Buffer++; } if( SubCount > 0 ) { ErrorText->Logf(TEXT("%sImportText(%s): Not enough closing parenthesis in: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *Name, InBuffer); return NULL; } // Skip comma. if( *Buffer==TCHAR(',') ) { // Skip comma. Buffer++; } else if( *Buffer!=TCHAR(')') ) { ErrorText->Logf(TEXT("%sImportText (%s): Missing closing parenthesis: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *Name, InBuffer); return NULL; } SkipWhitespace(Buffer); } // Skip trailing ')'. Buffer++; } else { ErrorText->Logf(TEXT("%sImportText (%s): Missing opening parenthesis: %s"), ErrorCount++ > 0 ? LINE_TERMINATOR : TEXT(""), *Name, InBuffer); return NULL; } return Buffer; }
bool FObjectReplicator::ReceivedBunch( FInBunch& Bunch, const FReplicationFlags& RepFlags, bool& bOutHasUnmapped ) { UObject* Object = GetObject(); if ( Object == NULL ) { UE_LOG(LogNet, Verbose, TEXT("ReceivedBunch: Object == NULL")); return false; } UPackageMap * PackageMap = OwningChannel->Connection->PackageMap; const bool bIsServer = ( OwningChannel->Connection->Driver->ServerConnection == NULL ); const FClassNetCache * ClassCache = OwningChannel->Connection->Driver->NetCache->GetClassNetCache( ObjectClass ); if ( ClassCache == NULL ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: ClassCache == NULL: %s"), *Object->GetFullName()); return false; } bool bThisBunchReplicatedProperties = false; // Read first field const FFieldNetCache * FieldCache = ReadField( ClassCache, Bunch ); if ( Bunch.IsError() ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: Error reading field 1: %s"), *Object->GetFullName()); return false; } if ( FieldCache == NULL ) { // There are no actual replicated properties or functions in this bunch. That is ok - we may have gotten this // actor/sub-object because we want the client to spawn one (but we aren't actually replicating properties on it) return true; } while ( FieldCache ) { // Receive properties from the net. UProperty* ReplicatedProp = NULL; while ( FieldCache && ( ReplicatedProp = Cast< UProperty >( FieldCache->Field ) ) != NULL ) { NET_CHECKSUM( Bunch ); // Server shouldn't receive properties. if ( bIsServer ) { UE_LOG(LogNet, Error, TEXT("Server received unwanted property value %s in %s"), *ReplicatedProp->GetName(), *Object->GetFullName()); return false; } bThisBunchReplicatedProperties = true; if ( !bHasReplicatedProperties ) { bHasReplicatedProperties = true; // Persistent, not reset until PostNetReceive is called PreNetReceive(); } bool DebugProperty = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) { static IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("net.Replication.DebugProperty")); if (CVar && !CVar->GetString().IsEmpty() && ReplicatedProp->GetName().Contains(CVar->GetString()) ) { UE_LOG(LogRep, Log, TEXT("Replicating Property[%d] %s on %s"), ReplicatedProp->RepIndex, *ReplicatedProp->GetName(), *Object->GetName()); DebugProperty = true; } } #endif if ( !Retirement[ ReplicatedProp->RepIndex ].CustomDelta ) { bool bLocalHasUnmapped = false; // We hijack a non custom delta property to signify we are using FRepLayout to read the entire property block if ( !RepLayout->ReceiveProperties( ObjectClass, RepState, (void*)Object, Bunch, bLocalHasUnmapped ) ) { UE_LOG(LogRep, Error, TEXT("ReceiveProperties FAILED %s in %s"), *ReplicatedProp->GetName(), *Object->GetFullName()); return false; } if ( bLocalHasUnmapped ) { bOutHasUnmapped = true; } } else { // Receive array index. uint32 Element = 0; if ( ReplicatedProp->ArrayDim != 1 ) { check( ReplicatedProp->ArrayDim >= 2 ); Bunch.SerializeIntPacked( Element ); if ( Element >= (uint32)ReplicatedProp->ArrayDim ) { UE_LOG(LogRep, Error, TEXT("Element index too large %s in %s"), *ReplicatedProp->GetName(), *Object->GetFullName()); return false; } } // Pointer to destination. uint8* Data = ReplicatedProp->ContainerPtrToValuePtr<uint8>((uint8*)Object, Element); TArray<uint8> MetaData; const PTRINT DataOffset = Data - (uint8*)Object; // Receive custom delta property. UStructProperty * StructProperty = Cast< UStructProperty >( ReplicatedProp ); if ( StructProperty == NULL ) { // This property isn't custom delta UE_LOG(LogRepTraffic, Error, TEXT("Property isn't custom delta %s"), *ReplicatedProp->GetName()); return false; } UScriptStruct * InnerStruct = StructProperty->Struct; if ( !( InnerStruct->StructFlags & STRUCT_NetDeltaSerializeNative ) ) { // This property isn't custom delta UE_LOG(LogRepTraffic, Error, TEXT("Property isn't custom delta %s"), *ReplicatedProp->GetName()); return false; } UScriptStruct::ICppStructOps * CppStructOps = InnerStruct->GetCppStructOps(); check( CppStructOps ); check( !InnerStruct->InheritedCppStructOps() ); FNetDeltaSerializeInfo Parms; FNetSerializeCB NetSerializeCB( OwningChannel->Connection->Driver ); Parms.DebugName = StructProperty->GetName(); Parms.Struct = InnerStruct; Parms.Map = PackageMap; Parms.Reader = &Bunch; Parms.NetSerializeCB = &NetSerializeCB; // Call the custom delta serialize function to handle it CppStructOps->NetDeltaSerialize( Parms, Data ); if ( Bunch.IsError() ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: NetDeltaSerialize - Bunch.IsError() == true: %s"), *Object->GetFullName()); return false; } if ( Parms.bOutHasMoreUnmapped ) { UnmappedCustomProperties.Add( DataOffset, StructProperty ); bOutHasUnmapped = true; } // Successfully received it. UE_LOG(LogRepTraffic, Log, TEXT(" %s - %s"), *Object->GetName(), *ReplicatedProp->GetName()); // Notify the Object if this var is RepNotify QueuePropertyRepNotify( Object, ReplicatedProp, Element, MetaData ); } // Read next field FieldCache = ReadField( ClassCache, Bunch ); if ( Bunch.IsError() ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: Error reading field 2: %s"), *Object->GetFullName()); return false; } } // Handle function calls. if ( FieldCache && Cast< UFunction >( FieldCache->Field ) ) { FName Message = FieldCache->Field->GetFName(); UFunction * Function = Object->FindFunction( Message ); if ( Function == NULL ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: Function == NULL: %s"), *Object->GetFullName()); return false; } if ( ( Function->FunctionFlags & FUNC_Net ) == 0 ) { UE_LOG(LogRep, Error, TEXT("Rejected non RPC function %s in %s"), *Message.ToString(), *Object->GetFullName()); return false; } if ( ( Function->FunctionFlags & ( bIsServer ? FUNC_NetServer : ( FUNC_NetClient | FUNC_NetMulticast ) ) ) == 0 ) { UE_LOG(LogRep, Error, TEXT("Rejected RPC function due to access rights %s in %s"), *Message.ToString(), *Object->GetFullName()); return false; } UE_LOG(LogRepTraffic, Log, TEXT(" Received RPC: %s"), *Message.ToString()); // Get the parameters. FMemMark Mark(FMemStack::Get()); uint8* Parms = new(FMemStack::Get(),MEM_Zeroed,Function->ParmsSize)uint8; // Use the replication layout to receive the rpc parameter values TSharedPtr<FRepLayout> FuncRepLayout = OwningChannel->Connection->Driver->GetFunctionRepLayout( Function ); FuncRepLayout->ReceivePropertiesForRPC( Object, Function, OwningChannel, Bunch, Parms ); if ( Bunch.IsError() ) { UE_LOG(LogRep, Error, TEXT("ReceivedBunch: ReceivePropertiesForRPC - Bunch.IsError() == true: Function: %s, Object: %s"), *Message.ToString(), *Object->GetFullName()); return false; } // validate that the function is callable here const bool bCanExecute = ( !bIsServer || RepFlags.bNetOwner ); // we are client or net owner if ( bCanExecute ) { // Call the function. RPC_ResetLastFailedReason(); Object->ProcessEvent( Function, Parms ); if ( RPC_GetLastFailedReason() != NULL ) { UE_LOG(LogRep, Error, TEXT("ReceivedBunch: RPC_GetLastFailedReason: %s"), RPC_GetLastFailedReason()); return false; } } else { UE_LOG(LogRep, Verbose, TEXT("Rejected unwanted function %s in %s"), *Message.ToString(), *Object->GetFullName()); if ( !OwningChannel->Connection->TrackLogsPerSecond() ) // This will disconnect the client if we get here too often { UE_LOG(LogRep, Error, TEXT("Rejected too many unwanted functions %s in %s"), *Message.ToString(), *Object->GetFullName()); return false; } } // Destroy the parameters. //warning: highly dependent on UObject::ProcessEvent freeing of parms! for ( UProperty * Destruct=Function->DestructorLink; Destruct; Destruct=Destruct->DestructorLinkNext ) { if( Destruct->IsInContainer(Function->ParmsSize) ) { Destruct->DestroyValue_InContainer(Parms); } } Mark.Pop(); if ( Object == NULL || Object->IsPendingKill() ) { // replicated function destroyed Object return true; // FIXME: Should this return false to kick connection? Seems we'll cause a read misalignment here if we don't } // Next. FieldCache = ReadField( ClassCache, Bunch ); if ( Bunch.IsError() ) { UE_LOG(LogNet, Error, TEXT("ReceivedBunch: Error reading field 3: %s"), *Object->GetFullName()); return false; } } else if ( FieldCache ) { UE_LOG(LogRep, Error, TEXT("ReceivedBunch: Invalid replicated field %i in %s"), FieldCache->FieldNetIndex, *Object->GetFullName()); return false; } } return true; }
void FObjectReplicator::UpdateUnmappedObjects( bool & bOutHasMoreUnmapped ) { UObject* Object = GetObject(); if ( Object == NULL || Object->IsPendingKill() ) { bOutHasMoreUnmapped = false; return; } if ( Connection->State == USOCK_Closed ) { UE_LOG(LogNet, Warning, TEXT("FObjectReplicator::UpdateUnmappedObjects: Connection->State == USOCK_Closed")); return; } checkf( RepState->RepNotifies.Num() == 0, TEXT("Failed RepState RepNotifies check. Num=%d. Object=%s"), RepState->RepNotifies.Num(), *Object->GetFullName() ); checkf( RepNotifies.Num() == 0, TEXT("Failed replicator RepNotifies check. Num=%d. Object=%s."), RepNotifies.Num(), *Object->GetFullName() ); bool bSomeObjectsWereMapped = false; // Let the rep layout update any unmapped properties RepLayout->UpdateUnmappedObjects( RepState, Connection->PackageMap, Object, bSomeObjectsWereMapped, bOutHasMoreUnmapped ); // Update unmapped objects for custom properties (currently just fast tarray) for ( auto It = UnmappedCustomProperties.CreateIterator(); It; ++It ) { const int32 Offset = It.Key(); UStructProperty* StructProperty = It.Value(); UScriptStruct* InnerStruct = StructProperty->Struct; check( InnerStruct->StructFlags & STRUCT_NetDeltaSerializeNative ); UScriptStruct::ICppStructOps* CppStructOps = InnerStruct->GetCppStructOps(); check( CppStructOps ); check( !InnerStruct->InheritedCppStructOps() ); FNetDeltaSerializeInfo Parms; FNetSerializeCB NetSerializeCB( OwningChannel->Connection->Driver ); Parms.DebugName = StructProperty->GetName(); Parms.Struct = InnerStruct; Parms.Map = Connection->PackageMap; Parms.NetSerializeCB = &NetSerializeCB; Parms.bUpdateUnmappedObjects = true; Parms.bCalledPreNetReceive = bSomeObjectsWereMapped; // RepLayout used this to flag whether PreNetReceive was called Parms.Object = Object; // Call the custom delta serialize function to handle it CppStructOps->NetDeltaSerialize( Parms, (uint8*)Object + Offset ); // Merge in results bSomeObjectsWereMapped |= Parms.bOutSomeObjectsWereMapped; bOutHasMoreUnmapped |= Parms.bOutHasMoreUnmapped; if ( Parms.bOutSomeObjectsWereMapped ) { // If we mapped a property, call the rep notify TArray<uint8> MetaData; QueuePropertyRepNotify( Object, StructProperty, 0, MetaData ); } // If this property no longer has unmapped objects, we can stop checking it if ( !Parms.bOutHasMoreUnmapped ) { It.RemoveCurrent(); } } // Call any rep notifies that need to happen when object pointers change // Pass in false to override the check for queued bunches. Otherwise, if the owning channel has queued bunches, // the RepNotifies will remain in the list and the check for 0 RepNotifies above will fail next time. CallRepNotifies(false); if ( bSomeObjectsWereMapped ) { // If we mapped some objects, make sure to call PostNetReceive (some game code will need to think this was actually replicated to work) PostNetReceive(); } }