From fded7b9a51aada1f720f0dd87c9c3b1e48755964 Mon Sep 17 00:00:00 2001 From: liuyunhui Date: Mon, 15 Sep 2025 17:17:30 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=BB=BA3d=E5=AE=A2=E6=88=B7=E7=AB=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpaceStudioClient/Config/DefaultEditor.ini | 0 SpaceStudioClient/Config/DefaultEngine.ini | 78 + SpaceStudioClient/Config/DefaultGame.ini | 4 + SpaceStudioClient/Config/DefaultInput.ini | 85 ++ .../SocketServer/Resources/Icon128.png | Bin 0 -> 38794 bytes .../Plugins/SocketServer/SocketServer.uplugin | 30 + .../Private/DNSClientSocketServer.cpp | 125 ++ .../Source/SocketServer/Private/EventBean.cpp | 14 + .../Private/FileFunctionsSocketServer.cpp | 428 ++++++ .../SocketServer/Private/RCONServer.cpp | 207 +++ .../SocketServer/Private/SocketServer.cpp | 22 + .../Private/SocketServerBPLibrary.cpp | 1268 +++++++++++++++++ .../Private/SocketServerCleanerThread.cpp | 66 + .../SocketServer/Private/SocketServerTCP.cpp | 259 ++++ ...SocketServerTCPClientReceiveDataThread.cpp | 245 ++++ .../SocketServerTCPClientSendDataThread.cpp | 205 +++ .../SocketServerTCPFileHandlerThread.cpp | 515 +++++++ .../Private/SocketServerTCPThread.cpp | 126 ++ .../SocketServer/Private/SocketServerUDP.cpp | 367 +++++ .../SocketServerUDPClientSendDataThread.cpp | 108 ++ .../Private/SocketServerUDPThread.cpp | 193 +++ .../Public/DNSClientSocketServer.h | 36 + .../Source/SocketServer/Public/EventBean.h | 24 + .../Public/FileFunctionsSocketServer.h | 243 ++++ .../Source/SocketServer/Public/RCONServer.h | 32 + .../Source/SocketServer/Public/SocketServer.h | 267 ++++ .../Public/SocketServerBPLibrary.h | 499 +++++++ .../Public/SocketServerCleanerThread.h | 25 + .../SocketServer/Public/SocketServerTCP.h | 80 ++ .../SocketServerTCPClientReceiveDataThread.h | 29 + .../SocketServerTCPClientSendDataThread.h | 34 + .../Public/SocketServerTCPFileHandlerThread.h | 48 + .../Public/SocketServerTCPThread.h | 23 + .../SocketServer/Public/SocketServerUDP.h | 59 + .../SocketServerUDPClientSendDataThread.h | 30 + .../Public/SocketServerUDPThread.h | 28 + .../Source/SocketServer/SocketServer.Build.cs | 57 + SpaceStudioClient/SpaceStudioClient.uproject | 15 + 38 files changed, 5874 insertions(+) create mode 100644 SpaceStudioClient/Config/DefaultEditor.ini create mode 100644 SpaceStudioClient/Config/DefaultEngine.ini create mode 100644 SpaceStudioClient/Config/DefaultGame.ini create mode 100644 SpaceStudioClient/Config/DefaultInput.ini create mode 100644 SpaceStudioClient/Plugins/SocketServer/Resources/Icon128.png create mode 100644 SpaceStudioClient/Plugins/SocketServer/SocketServer.uplugin create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/DNSClientSocketServer.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/EventBean.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/FileFunctionsSocketServer.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/RCONServer.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServer.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerBPLibrary.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerCleanerThread.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCP.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPClientReceiveDataThread.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPClientSendDataThread.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPFileHandlerThread.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPThread.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDP.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDPClientSendDataThread.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDPThread.cpp create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/DNSClientSocketServer.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/EventBean.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/FileFunctionsSocketServer.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/RCONServer.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServer.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerBPLibrary.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerCleanerThread.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCP.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPClientReceiveDataThread.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPClientSendDataThread.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPFileHandlerThread.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPThread.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDP.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDPClientSendDataThread.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDPThread.h create mode 100644 SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/SocketServer.Build.cs create mode 100644 SpaceStudioClient/SpaceStudioClient.uproject diff --git a/SpaceStudioClient/Config/DefaultEditor.ini b/SpaceStudioClient/Config/DefaultEditor.ini new file mode 100644 index 0000000..e69de29 diff --git a/SpaceStudioClient/Config/DefaultEngine.ini b/SpaceStudioClient/Config/DefaultEngine.ini new file mode 100644 index 0000000..fa2cd8d --- /dev/null +++ b/SpaceStudioClient/Config/DefaultEngine.ini @@ -0,0 +1,78 @@ + + +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/Engine/Maps/Templates/OpenWorld + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +-D3D12TargetedShaderFormats=PCD3D_SM5 ++D3D12TargetedShaderFormats=PCD3D_SM6 +-D3D11TargetedShaderFormats=PCD3D_SM5 ++D3D11TargetedShaderFormats=PCD3D_SM5 +Compiler=Default +AudioSampleRate=48000 +AudioCallbackBufferFrameSize=1024 +AudioNumBuffersToEnqueue=1 +AudioMaxChannels=0 +AudioNumSourceWorkers=4 +SpatializationPlugin= +SourceDataOverridePlugin= +ReverbPlugin= +OcclusionPlugin= +CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) +CacheSizeKB=65536 +MaxChunkSizeOverrideKB=0 +bResampleForDevice=False +MaxSampleRate=48000.000000 +HighSampleRate=32000.000000 +MedSampleRate=24000.000000 +LowSampleRate=12000.000000 +MinSampleRate=8000.000000 +CompressionQualityModifier=1.000000 +AutoStreamingThreshold=0.000000 +SoundCueCookQualityIndex=-1 + + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/Engine.RendererSettings] +r.GenerateMeshDistanceFields=True +r.DynamicGlobalIlluminationMethod=1 +r.ReflectionMethod=1 +r.SkinCache.CompileShaders=True +r.RayTracing=True +r.Shadow.Virtual.Enable=1 +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True +r.DefaultFeature.LocalExposure.HighlightContrastScale=0.8 +r.DefaultFeature.LocalExposure.ShadowContrastScale=0.8 + +[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] +CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' + +[/Script/Engine.UserInterfaceSettings] +bAuthorizeAutomaticWidgetVariableCreation=False +FontDPIPreset=Standard +FontDPI=72 + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/SpaceStudioClient") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/SpaceStudioClient") + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=55D0C7F04C701EB3CC127F9AE4347B4F +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + diff --git a/SpaceStudioClient/Config/DefaultGame.ini b/SpaceStudioClient/Config/DefaultGame.ini new file mode 100644 index 0000000..059fca6 --- /dev/null +++ b/SpaceStudioClient/Config/DefaultGame.ini @@ -0,0 +1,4 @@ + + +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=C5CBDA014AD60DA47558CEB13DA4AD3C diff --git a/SpaceStudioClient/Config/DefaultInput.ini b/SpaceStudioClient/Config/DefaultInput.ini new file mode 100644 index 0000000..4cc3605 --- /dev/null +++ b/SpaceStudioClient/Config/DefaultInput.ini @@ -0,0 +1,85 @@ +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +bCaptureMouseOnLaunch=True +bEnableLegacyInputScales=True +bEnableMotionControls=True +bFilterInputByPlatformUser=False +bShouldFlushPressedKeysOnViewportFocusLost=True +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +bEnableGestureRecognizer=False +bUseAutocorrect=False +DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 +DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput +DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent +DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde ++ConsoleKeys=Caret + diff --git a/SpaceStudioClient/Plugins/SocketServer/Resources/Icon128.png b/SpaceStudioClient/Plugins/SocketServer/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..1c4ef96ef8773587ee59aad9321f2f63a9c20a1b GIT binary patch literal 38794 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

hX96q`D+`k|D*5luLWqNw% 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 WEI+~B0~B%q0000OnDataReceived().BindUObject(this, &UDNSClientSocketServer::UDPReceiver); + udpSocketReceiver->Start(); + + TSharedRef addr = socketSubSystem->CreateInternetAddr(); + bool bIsValid; + addr->SetIp(*dnsIP, bIsValid); + addr->SetPort(53); + if (bIsValid) { + TArray dnsRequest; + //id (ramdom choose) + dnsRequest.Add(0x22); + dnsRequest.Add(0x76); + //head + dnsRequest.Add(0x1); + dnsRequest.Add(0x0); + dnsRequest.Add(0x0); + dnsRequest.Add(0x1); + dnsRequest.Add(0x0); + dnsRequest.Add(0x0); + dnsRequest.Add(0x0); + dnsRequest.Add(0x0); + dnsRequest.Add(0x0); + dnsRequest.Add(0x0); + + //domain + //split in domain parts than add the length as byte and the part as bytes + TArray domainParts; + domain.ParseIntoArray(domainParts, TEXT("."), true); + for (int i = 0; i < domainParts.Num(); i++) { + FTCHARToUTF8 Convert(*domainParts[i]); + dnsRequest.Add(Convert.Length()); + dnsRequest.Append((uint8*)Convert.Get(), Convert.Length()); + } + //foot + dnsRequest.Add(0x0); + dnsRequest.Add(0x0); + dnsRequest.Add(0x1); + dnsRequest.Add(0x0); + dnsRequest.Add(0x1); + int32 sent; + socket->SendTo((uint8*)dnsRequest.GetData(), dnsRequest.Num(), sent, *addr); + } + } + +} + +void UDNSClientSocketServer::UDPReceiver(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt) { + + //TSharedPtr peerAddr = EndPt.ToInternetAddr(); + //FString ip = peerAddr->ToString(false); + //int32 port = peerAddr->GetPort(); + ////UE_LOG(LogTemp, Error, TEXT("peerAddr:%s port:%i"), *peerAddr->ToString(false), peerAddr->GetPort()); + + + + TArray byteArray; + byteArray.Append(ArrayReaderPtr->GetData(), ArrayReaderPtr->Num()); + + FString ipAdress; + if (byteArray.Num() > 10) { + for (int32 i = (byteArray.Num() - 4); i < byteArray.Num(); i++) { + uint32 ipTmp = byteArray.GetData()[i] << 0; + ipAdress += FString::FromInt(ipTmp)+"."; + } + } + + ipAdress.RemoveFromEnd("."); + + //UE_LOG(LogTemp, Display, TEXT("DNS Resolved IP:%s"), *recvMessage); + + ip = ipAdress; + dnsCache.Add(domain, ip); + + resolving = false; + + UDNSClientSocketServer* self = this; + + AsyncTask(ENamedThreads::GameThread, [ipAdress,self]() { + self->onresolveDomainEventDelegate.Broadcast(ipAdress); + }); +} + +bool UDNSClientSocketServer::isResloving(){ + return resolving; +} + +FString UDNSClientSocketServer::getIP(){ + return ip; +} diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/EventBean.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/EventBean.cpp new file mode 100644 index 0000000..79a2fac --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/EventBean.cpp @@ -0,0 +1,14 @@ +// Copyright 2018-2020 David Romanski (Socke). All Rights Reserved. + +#include "EventBean.h" + + +UEventBean::UEventBean(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + +} + +//void UEventBean::JavascriptEventTag(const FString ID, const FString ClassName, const FString Value, const TArray& args) {} + +void UEventBean::registeredEventDelegate(const FString message, const TArray& byteArray) +{ +} diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/FileFunctionsSocketServer.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/FileFunctionsSocketServer.cpp new file mode 100644 index 0000000..f317d1d --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/FileFunctionsSocketServer.cpp @@ -0,0 +1,428 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#include "FileFunctionsSocketServer.h" + +UFileFunctionsSocketServer* UFileFunctionsSocketServer::fileFunctionsSocketServer; + +UFileFunctionsSocketServer::UFileFunctionsSocketServer(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + fileFunctionsSocketServer = this; +} + +UFileFunctionsSocketServer* UFileFunctionsSocketServer::getFileFunctionsSocketServerTarget() { + return fileFunctionsSocketServer; +} + +FString UFileFunctionsSocketServer::getCleanDirectory(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + if (directoryType == EFileFunctionsSocketServerDirectoryType::E_ad) { + return FPaths::ConvertRelativePathToFull(filePath); + } + else { + FString ProjectDir = FPaths::ProjectDir(); + return FPaths::ConvertRelativePathToFull(ProjectDir + filePath); + } +} + +void UFileFunctionsSocketServer::writeBytesToFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, TArray bytes, bool& success) { + success = FFileHelper::SaveArrayToFile(bytes, *getCleanDirectory(directoryType, filePath)); +} + +void UFileFunctionsSocketServer::addBytesToFileAndCloseIt(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, TArray bytes, bool& success) { + FArchive* writer = IFileManager::Get().CreateFileWriter(*getCleanDirectory(directoryType, filePath), EFileWrite::FILEWRITE_Append); + if (!writer) { + success = false; + return; + } + writer->Seek(writer->TotalSize()); + writer->Serialize(bytes.GetData(), bytes.Num()); + writer->Close(); + delete writer; + + success = true; +} + +//void UFileFunctionsSocketServer::splittFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, int32 parts, bool& success){ +// if (parts <= 0) +// parts = 1; +// FArchive* reader = IFileManager::Get().CreateFileReader(*getCleanDirectory(directoryType, filePath)); +// if (!reader) { +// success = false; +// return; +// } +// +// int64 splittAfterBytes = reader->TotalSize()/ ((int64)parts); +// TArray bytes; +// +// for (int32 i = 0; i < parts; i++){ +// bytes.AddUninitialized(splittAfterBytes); +// reader->Serialize(bytes.GetData(), splittAfterBytes); +// if (FFileHelper::SaveArrayToFile(bytes, *getCleanDirectory(directoryType, filePath)) == false) { +// success = false; +// return; +// } +// splittAfterBytes = +// reader->Seek(); +// } +// +//} + +TArray UFileFunctionsSocketServer::readBytesFromFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool& success) { + TArray result; + success = FFileHelper::LoadFileToArray(result, *getCleanDirectory(directoryType, filePath)); + return result; +} + +void UFileFunctionsSocketServer::readStringFromFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool& success, FString& data) { + data.Empty(); + success = FFileHelper::LoadFileToString(data, *getCleanDirectory(directoryType, filePath)); +} + +void UFileFunctionsSocketServer::writeStringToFile(EFileFunctionsSocketServerDirectoryType directoryType, FString data, FString filePath, EFileFunctionsSocketServerEncodingOptions fileEncoding, bool& success) { + success = FFileHelper::SaveStringToFile(data, *getCleanDirectory(directoryType, filePath), (FFileHelper::EEncodingOptions)fileEncoding); +} + + + +void UFileFunctionsSocketServer::getMD5FromFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool& success, FString& MD5) { + getMD5FromFileAbsolutePath(getCleanDirectory(directoryType, filePath), success, MD5); +} + +void UFileFunctionsSocketServer::getMD5FromFileAbsolutePath(FString filePath, bool& success, FString& MD5) { + MD5.Empty(); + FArchive* reader = IFileManager::Get().CreateFileReader(*filePath); + if (!reader) { + success = false; + return; + } + + TArray byteArrayTmp; + int64 totalSize = reader->TotalSize(); + int64 loadedBytes = 0; + int64 leftUploadBytes = 1024; + + + if (totalSize < leftUploadBytes) + leftUploadBytes = totalSize; + + + uint8 Digest[16]; + FMD5 Md5Gen; + + while ((loadedBytes + leftUploadBytes) <= totalSize) { + byteArrayTmp.Reset(leftUploadBytes); + byteArrayTmp.AddUninitialized(leftUploadBytes); + reader->Serialize(byteArrayTmp.GetData(), byteArrayTmp.Num()); + loadedBytes += leftUploadBytes; + reader->Seek(loadedBytes); + + Md5Gen.Update(byteArrayTmp.GetData(), byteArrayTmp.Num()); + } + + leftUploadBytes = totalSize - loadedBytes; + if (leftUploadBytes > 0) { + byteArrayTmp.Reset(leftUploadBytes); + byteArrayTmp.AddUninitialized(leftUploadBytes); + reader->Serialize(byteArrayTmp.GetData(), byteArrayTmp.Num()); + loadedBytes += leftUploadBytes; + Md5Gen.Update(byteArrayTmp.GetData(), byteArrayTmp.Num()); + } + + if (reader != nullptr) { + reader->Close(); + delete reader; + } + + if (totalSize != loadedBytes) { + success = false; + return; + } + + Md5Gen.Final(Digest); + for (int32 i = 0; i < 16; i++) { + MD5 += FString::Printf(TEXT("%02x"), Digest[i]); + } + + success = true; +} + +void UFileFunctionsSocketServer::stringToBase64String(FString string, FString& base64String) { + base64String.Empty(); + FTCHARToUTF8 Convert(*string); + TArray bytes; + bytes.Append(((uint8*)((ANSICHAR*)Convert.Get())), Convert.Length()); + base64String = FBase64::Encode(bytes); +} + +void UFileFunctionsSocketServer::base64StringToString(FString& string, FString base64String) { + string.Empty(); + TArray bytes; + if (FBase64::Decode(*base64String, bytes)) { + bytes.Add(0x00);// null-terminator + char* Data = (char*)bytes.GetData(); + string = FString(UTF8_TO_TCHAR(Data)); + } +} + + +void UFileFunctionsSocketServer::bytesToBase64String(TArray bytes, FString& base64String) { + base64String.Empty(); + base64String = FBase64::Encode(bytes); +} + +TArray UFileFunctionsSocketServer::base64StringToBytes(FString base64String, bool& success) { + TArray fileData; + if (base64String.Len() % 2 != 0 || base64String.Len() < 4) { + success = false; + return fileData; + } + success = FBase64::Decode(*base64String, fileData); + return fileData; +} + +void UFileFunctionsSocketServer::fileToBase64String(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool& success, FString& base64String, FString& fileName) { + base64String.Empty(); + fileName.Empty(); + TArray fileData; + if (!FFileHelper::LoadFileToArray(fileData, *getCleanDirectory(directoryType, filePath))) { + success = false; + return; + } + base64String = FBase64::Encode(fileData); + success = true; +} + +bool UFileFunctionsSocketServer::fileExists(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + return FPaths::FileExists(*getCleanDirectory(directoryType, filePath)); +} + +bool UFileFunctionsSocketServer::fileExistsAbsolutePath(FString filePath) { + return FPaths::FileExists(*filePath); +} + +bool UFileFunctionsSocketServer::directoryExists(EFileFunctionsSocketServerDirectoryType directoryType, FString path) { + return FPaths::DirectoryExists(*getCleanDirectory(directoryType, path)); +} + +int64 UFileFunctionsSocketServer::fileSize(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().FileSize(*getCleanDirectory(directoryType, filePath)); +} + +int64 UFileFunctionsSocketServer::fileSizeAbsolutePath(FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().FileSize(*filePath); +} + +bool UFileFunctionsSocketServer::deleteFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*getCleanDirectory(directoryType, filePath)); +} + +bool UFileFunctionsSocketServer::deleteFileAbsolutePath(FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*filePath); +} + +bool UFileFunctionsSocketServer::deleteDirectory(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().DeleteDirectory(*getCleanDirectory(directoryType, filePath)); +} + +bool UFileFunctionsSocketServer::isReadOnly(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().IsReadOnly(*getCleanDirectory(directoryType, filePath)); +} + +bool UFileFunctionsSocketServer::moveFile(EFileFunctionsSocketServerDirectoryType directoryTypeTo, FString filePathTo, EFileFunctionsSocketServerDirectoryType directoryTypeFrom, FString filePathFrom) { + return FPlatformFileManager::Get().GetPlatformFile().MoveFile(*getCleanDirectory(directoryTypeTo, filePathTo), *getCleanDirectory(directoryTypeFrom, filePathFrom)); +} + +bool UFileFunctionsSocketServer::setReadOnly(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool bNewReadOnlyValue) { + return FPlatformFileManager::Get().GetPlatformFile().SetReadOnly(*getCleanDirectory(directoryType, filePath), bNewReadOnlyValue); +} + +FDateTime UFileFunctionsSocketServer::getTimeStamp(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().GetTimeStamp(*getCleanDirectory(directoryType, filePath)); +} + +void UFileFunctionsSocketServer::setTimeStamp(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, FDateTime DateTime) { + FPlatformFileManager::Get().GetPlatformFile().SetTimeStamp(*getCleanDirectory(directoryType, filePath), DateTime); +} + +FDateTime UFileFunctionsSocketServer::getAccessTimeStamp(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().GetAccessTimeStamp(*getCleanDirectory(directoryType, filePath)); +} + +FString UFileFunctionsSocketServer::getFilenameOnDisk(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + return FPlatformFileManager::Get().GetPlatformFile().GetFilenameOnDisk(*getCleanDirectory(directoryType, filePath)); +} + +bool UFileFunctionsSocketServer::createDirectory(EFileFunctionsSocketServerDirectoryType directoryType, FString path) { + return FPlatformFileManager::Get().GetPlatformFile().CreateDirectory(*getCleanDirectory(directoryType, path)); +} + +void UFileFunctionsSocketServer::getAllFilesFromDirectory(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, int32& count, TArray& files, TArray& filePaths, FString fileType) { + if (filePath.Len() < 1) { + return; + } + files.Empty(); + filePaths.Empty(); + + FString dir = getCleanDirectory(directoryType, filePath); + FPaths::NormalizeDirectoryName(filePath); + + if (!FPaths::DirectoryExists(dir)) { + return; + } + IFileManager& FileManager = IFileManager::Get(); + + dir += "/" + fileType; + + FileManager.FindFiles(files, *dir, true, false); + + + filePath += "/"; + for (int32 i = 0; i < files.Num(); i++) { + filePaths.Add((filePath + files[i])); + //UE_LOG(LogTemp, Display, TEXT("->%s"), *files[i]); + } + count = files.Num(); +} + +FFileFunctionsSocketServerOpenFile UFileFunctionsSocketServer::openFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + + FArchive* writer = IFileManager::Get().CreateFileWriter(*getCleanDirectory(directoryType, filePath), EFileWrite::FILEWRITE_Append); + FFileFunctionsSocketServerOpenFile file; + file.writer = writer; + return file; +} + +int64 UFileFunctionsSocketServer::addBytesToFile(FFileFunctionsSocketServerOpenFile openFile, TArray bytes) { + //UE_LOG(LogTemp, Warning, TEXT("xxxxx WRITE: %i"), bytes.Num()); + if (openFile.writer != nullptr) { + openFile.writer->Seek(openFile.writer->TotalSize()); + openFile.writer->Serialize(bytes.GetData(), bytes.Num()); + //openFile.writer->Flush(); + return openFile.writer->TotalSize(); + } + return 0; +} + +void UFileFunctionsSocketServer::closeFile(FFileFunctionsSocketServerOpenFile openFile) { + if (openFile.writer != nullptr) { + openFile.writer->Close(); + openFile.writer = nullptr; + } +} + + +FString UFileFunctionsSocketServer::encryptMessageWithAES(FString message, FString key) { + + if (message.IsEmpty() || key.Len() != 32) { + return FString(); + } + + TArray data = FStringToByteArray(message); + data.Add(0x00);// null-terminator + const int64 encryptedFileSize = Align(data.Num(), FAES::AESBlockSize); + + if (data.Num() < encryptedFileSize) { + data.AddUninitialized(encryptedFileSize - data.Num()); + } + + FAES::EncryptData(data.GetData(), encryptedFileSize, TCHAR_TO_ANSI(*key)); + + FString encryptedBase64String = FString(); + bytesToBase64String(data, encryptedBase64String); + + return encryptedBase64String; +} + +FString UFileFunctionsSocketServer::decryptMessageWithAES(FString message, FString key) { + if (message.IsEmpty() || key.Len() != 32) { + UE_LOG(LogTemp, Error, TEXT("decryptMessageFromAES: Wrong key length.")); + return FString(); + } + + bool success = false; + TArray data = base64StringToBytes(message, success); + + + const int64 encryptedFileSize = Align(data.Num(), FAES::AESBlockSize); + if (data.Num() < encryptedFileSize) { + UE_LOG(LogTemp, Error, TEXT("decryptMessageFromAES: Wrong string length. This is not an AES encrypted string.")); + return FString(); + } + + FAES::DecryptData(data.GetData(), encryptedFileSize, TCHAR_TO_ANSI(*key)); + + return FString(UTF8_TO_TCHAR((char*)data.GetData())); + +} + + +TArray UFileFunctionsSocketServer::FStringToByteArray(FString s) { + FTCHARToUTF8 Convert(*s); + TArray data; + data.Append((uint8*)Convert.Get(), Convert.Length()); + return data; +} + + +FString UFileFunctionsSocketServer::int64ToString(int64 Num) { + FString str = FString(); + const TCHAR* DigitToChar = TEXT("9876543210123456789"); + constexpr int64 ZeroDigitIndex = 9; + bool bIsNumberNegative = Num < 0; + const int64 TempBufferSize = 32; // 32 is big enough + TCHAR TempNum[TempBufferSize]; + int64 TempAt = TempBufferSize; // fill the temp string from the top down. + + // Convert to string assuming base ten. + do + { + TempNum[--TempAt] = DigitToChar[ZeroDigitIndex + (Num % 10)]; + Num /= 10; + } while (Num); + + if (bIsNumberNegative) + { + TempNum[--TempAt] = TEXT('-'); + } + + const TCHAR* CharPtr = TempNum + TempAt; + const int64 NumChars = TempBufferSize - TempAt; + str.Append(CharPtr, NumChars); + + return str; +} + + + +void UFileFunctionsSocketServer::readBytesFromFileInPartsAsync(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, int32 bufferSize, float delayBetweenReadsInSeconds) { + UFileFunctionsSocketServer::getFileFunctionsSocketServerTarget()->readBytesFromFileInPartsAsyncInternal(directoryType, filePath, bufferSize, delayBetweenReadsInSeconds); +} + +void UFileFunctionsSocketServer::readBytesFromFileInPartsAsyncInternal(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, int32 bufferSize, float delayBetweenReadsInSeconds) { + + FString dir = UFileFunctionsSocketServer::getCleanDirectory(directoryType, filePath); + + if (readFileInPartsThreads.Find(*dir) != nullptr) { + UE_LOG(LogTemp, Warning, TEXT("ReadBytesFromFileInPartsAsync: %s is being read already. Operation canceled."), *dir); + return; + } + + FReadFileInPartsSocketServerThread* readThread = new FReadFileInPartsSocketServerThread(dir, bufferSize, delayBetweenReadsInSeconds); + readFileInPartsThreads.Add(dir, readThread); +} + +void UFileFunctionsSocketServer::cancelReadBytesFromFileInParts(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + UFileFunctionsSocketServer::getFileFunctionsSocketServerTarget()->cancelReadBytesFromFileInPartsInternal(directoryType, filePath); +} + +void UFileFunctionsSocketServer::cancelReadBytesFromFileInPartsInternal(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath) { + FString dir = UFileFunctionsSocketServer::getCleanDirectory(directoryType, filePath); + + if (readFileInPartsThreads.Find(*dir) != nullptr) { + (*readFileInPartsThreads.Find(*dir))->stopThread(); + } +} + +void UFileFunctionsSocketServer::cleanReadBytesFromFileInParts(FString cleanDir) { + if (readFileInPartsThreads.Find(*cleanDir) != nullptr) { + readFileInPartsThreads.Remove(*cleanDir); + } +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/RCONServer.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/RCONServer.cpp new file mode 100644 index 0000000..c1c5f6b --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/RCONServer.cpp @@ -0,0 +1,207 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#include "RCONServer.h" + +URCONServer::URCONServer(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { +} + +void URCONServer::startRCONServer(FString serverID, ERCONPasswordType passwordTypeP, FString passwordOrFileP + , bool& success, FString& errorMessage) { + + success = false; + errorMessage = ""; + + if (serverID.IsEmpty() || USocketServerBPLibrary::getSocketServerTarget()->getTcpServerMap().Find(serverID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): ServerID not found: %s"), *serverID); + success = false; + errorMessage = "ServerID not found: "+serverID; + return; + } + passwordType = passwordTypeP; + + if (passwordType != ERCONPasswordType::E_parameter) { + + EFileFunctionsSocketServerDirectoryType passwordDirectoryType = EFileFunctionsSocketServerDirectoryType::E_gd; + if (passwordType == ERCONPasswordType::E_ad) { + passwordDirectoryType = EFileFunctionsSocketServerDirectoryType::E_ad; + } + + passwordOrFile = UFileFunctionsSocketServer::getCleanDirectory(passwordDirectoryType, passwordOrFileP); + if (FPaths::DirectoryExists(FPaths::GetPath(passwordOrFile)) == false) { + UFileFunctionsSocketServer::createDirectory(passwordDirectoryType, FPaths::GetPath(passwordOrFile)); + } + + + if (FPaths::FileExists(passwordOrFile) == false) { + UFileFunctionsSocketServer::writeStringToFile(passwordDirectoryType, "", passwordOrFile, EFileFunctionsSocketServerEncodingOptions::E_AutoDetect, success); + } + + if (FPaths::FileExists(passwordOrFile) == false) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): Can't read the password file: %s"), *passwordOrFile); + success = false; + errorMessage = "Can't read the password file: " + passwordOrFile; + return; + } + } + else { + passwordOrFile = passwordOrFileP; + } + + success = true; +} + + +void URCONServer::receiveTCPMessageEvent(const FString sessionID, const FString message, const TArray& byteArray, const FString serverID) { + + USocketServerTCP* tcpServer = *USocketServerBPLibrary::getSocketServerTarget()->getTcpServerMap().Find(serverID); + if (tcpServer == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): Server not found: %s"), *serverID); + return; + } + + //https://developer.valvesoftware.com/wiki/Source_RCON_Protocol + + //packet to small + if (byteArray.Num() < 14) { + tcpServer->removeClientSession(sessionID); + return; + } + + //packet to big + if (byteArray.Num() > 4096) { + tcpServer->removeClientSession(sessionID); + return; + } + + int32 size = 0; + FMemory::Memcpy(&size, byteArray.GetData(), 4); + + //packet to small + if (size < 10) { + tcpServer->removeClientSession(sessionID); + return; + } + + int32 id = 0; + FMemory::Memcpy(&id, byteArray.GetData() + 4, 4); + + int32 type = 0; + FMemory::Memcpy(&type, byteArray.GetData() + 8, 4); + + TArray bodyTextArray; + bodyTextArray.AddUninitialized(byteArray.Num()-12); + FMemory::Memcpy(bodyTextArray.GetData(), byteArray.GetData() + 12, byteArray.Num() - 12); + + FString bodyText = FString(UTF8_TO_TCHAR(bodyTextArray.GetData())); + bodyTextArray.Empty(); + + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): size:%i id:%i type:%i body:%s"),size, id,type,*bodyText); + + + + switch (type) + { + case 0: + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): Server response sent to the server? Request will be ignored.")); + break; + case 2: + USocketServerBPLibrary::getSocketServerTarget()->onreceiveRCONRequestEventDelegate.Broadcast(sessionID,serverID,id, bodyText); + break; + case 3: + authResponse(sessionID, serverID, bodyText, id); + break; + default: + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): Unsupported request. Connection is closed.")); + tcpServer->removeClientSession(sessionID); + return; + } + + +} + + + +void URCONServer::authResponse(FString sessionID, FString serverID, FString password, int32 rconID){ + + //check password + if (password.IsEmpty() || passwordOrFile.IsEmpty()) { + rconID = -1; + sendResponse(sessionID, serverID, -1, 2, FString()); + return; + } + + bool found = false; + + if (passwordType == ERCONPasswordType::E_parameter) { + if (password.Equals(passwordOrFile)) { + found = true; + } + } + else { + FString passwordData = FString(); + FFileHelper::LoadFileToString(passwordData, *passwordOrFile); + + TArray passwords; + passwordData.ParseIntoArray(passwords, TEXT("\n"), true); + + for (int32 i = 0; i < passwords.Num(); i++) + { + if (passwords[i].TrimEnd().Equals(password)) { + found = true; + break; + } + } + } + + + + if (found) { + sendResponse(sessionID, serverID, rconID, 2, FString()); + } + else { + sendResponse(sessionID, serverID, -1, 2, FString()); + USocketServerTCP* tcpServer = *USocketServerBPLibrary::getSocketServerTarget()->getTcpServerMap().Find(serverID); + if (tcpServer != nullptr) { + tcpServer->removeClientSession(sessionID); + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): Wrong password. Connection is closed.")); + } + } + +} + +bool URCONServer::sendResponse(FString sessionID, FString serverID, int32 id, int32 type, FString body){ + USocketServerTCP* tcpServer = *USocketServerBPLibrary::getSocketServerTarget()->getTcpServerMap().Find(serverID); + if (tcpServer == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): Server not found. Can't send response: %s"), *serverID); + return false; + } + + + + FTCHARToUTF8 Convert(*body); + + //4 (id) + 4 (type) + 1 (minimum body) + 1 (null terminator) = 10 + int32 size = 10; + + TArray response; + size += Convert.Length(); + + response.AddZeroed(size + 4); // 4 = the size itself as int32 + + FMemory::Memcpy(response.GetData(), &size, 4); + FMemory::Memcpy(response.GetData() + 4, &id, 4); + FMemory::Memcpy(response.GetData() + 8, &type, 4); + if (Convert.Length() > 0) { + FMemory::Memcpy(response.GetData() + 12, Convert.Get(), Convert.Length()); + } + + //packet to big + if (response.Num() > 4096) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): Answer too big. A data packet may have a maximum size of 4096 bytes. Size is %i"), response.Num()); + return false; + } + + tcpServer->sendTCPMessageToClient(sessionID, FString(), response, false); + + return true; +} diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServer.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServer.cpp new file mode 100644 index 0000000..093b994 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServer.cpp @@ -0,0 +1,22 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#include "SocketServer.h" + +#define LOCTEXT_NAMESPACE "FSocketServerModule" + +void FSocketServerModule::StartupModule() +{ + // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + +} + +void FSocketServerModule::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. + +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FSocketServerModule, SocketServer) \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerBPLibrary.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerBPLibrary.cpp new file mode 100644 index 0000000..1d6f307 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerBPLibrary.cpp @@ -0,0 +1,1268 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerBPLibrary.h" + +USocketServerBPLibrary* USocketServerBPLibrary::socketServerBPLibrary; + +USocketServerBPLibrary::USocketServerBPLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) { + socketServerBPLibrary = this; + + if (socketServerPluginCleanerThread == nullptr) + socketServerPluginCleanerThread = new FSocketServerCleanerThread(); + + //Delegates examples + /*onsocketServerConnectionEventDelegate.AddDynamic(this, &USocketServerBPLibrary::socketServerConnectionEventDelegate); + onserverReceiveTCPMessageEventDelegate.AddDynamic(this, &USocketServerBPLibrary::serverReceiveTCPMessageEventDelegate); + onsocketServerUDPConnectionEventDelegate.AddDynamic(this, &USocketServerBPLibrary::socketServerUDPConnectionEventDelegate); + onserverReceiveUDPMessageEventDelegate.AddDynamic(this, &USocketServerBPLibrary::serverReceiveUDPMessageEventDelegate);*/ +} + + +/*Delegate functions*/ +void USocketServerBPLibrary::socketServerConnectionEventDelegate(const EServerSocketConnectionEventType type, const bool success, const FString message, const FString sessionID, const FString serverID) {} +void USocketServerBPLibrary::serverReceiveTCPMessageEventDelegate(const FString sessionID, const FString message, const TArray& byteArray, const FString serverID) {} +void USocketServerBPLibrary::fileTransferOverTCPProgressEventDelegate(const FString sessionID, const FString filePathP, const float percent, const float mbit, const int64 bytesTransferred, const int64 fileSize){} +void USocketServerBPLibrary::fileTransferOverTCPInfoEventDelegate(const FString message, const FString sessionID, const FString filePathP, const bool success){} +void USocketServerBPLibrary::socketServerUDPConnectionEventDelegate(const bool success, const FString message, const FString serverID) {} +void USocketServerBPLibrary::serverReceiveUDPMessageEventDelegate(const FString sessionID, const FString message, const TArray& byteArray, const FString serverID) {} +void USocketServerBPLibrary::readBytesFromFileInPartsEventDelegate(const int64 fileSize, const int64 position, const bool end, const TArray& byteArray) {} +void USocketServerBPLibrary::receiveRCONRequestEventDelegate(const FString sessionID, const FString serverID, const int32 requestID, const FString request) {} + + + + +USocketServerBPLibrary * USocketServerBPLibrary::getSocketServerTarget() { + return socketServerBPLibrary; +} + + + + + +//void USocketServerBPLibrary::breakDownloadFileInfo(FSocketServerDownloadFileInfo downloadFileInfo, float& size, float& megaBytesLeft, float& megaBytesReceived, float& percentDownload, float& megaBit, FString& fileName, FString& serverID){ +// size = downloadFileInfo.size; +// megaBytesLeft = downloadFileInfo.megaBytesLeft; +// megaBytesReceived = downloadFileInfo.megaBytesReceived; +// percentDownload = downloadFileInfo.percentDownload; +// megaBit = downloadFileInfo.megaBit; +// fileName = downloadFileInfo.fileName; +// serverID = downloadFileInfo.serverID; +//} +// +//void USocketServerBPLibrary::breakUploadFileInfo(FSocketServerUploadFileInfo fileUploadInfo, float& size, float& megaBytesLeft, float& megabytesTransferred, float& percentUpload, float& megaBit, FString& fileName, FString& serverID) { +// size = fileUploadInfo.size; +// megaBytesLeft = fileUploadInfo.megaBytesLeft; +// megaBytesSend = fileUploadInfo.megaBytesSend; +// percentUpload = fileUploadInfo.percentUpload; +// megaBit = fileUploadInfo.megaBit; +// fileName = fileUploadInfo.fileName; +// serverID = fileUploadInfo.serverID; +//} + +void USocketServerBPLibrary::serverPluginGetSocketSessionIds(const FString serverID, TArray& sessionIDs) { + USocketServerBPLibrary::getSocketServerTarget()->serverPluginGetSocketSessionIdsNonStatic(serverID, sessionIDs); +} + +void USocketServerBPLibrary::serverPluginGetSocketSessionIdsNonStatic(const FString optionalServerID, TArray& sessionIDs){ + if (optionalServerID.IsEmpty()) { + if (udpServers.Find(lastUDPServerID) != nullptr) { + (*udpServers.Find(lastUDPServerID))->getClientSessions().GetKeys(sessionIDs); + return; + } + } + else { + if (udpServers.Find(optionalServerID) != nullptr) { + (*udpServers.Find(optionalServerID))->getClientSessions().GetKeys(sessionIDs); + return; + } + } + + if (optionalServerID.IsEmpty()) { + if (tcpServers.Find(lastTCPServerID) != nullptr) { + (*tcpServers.Find(lastTCPServerID))->getClientSessions().GetKeys(sessionIDs); + } + } + else { + if (tcpServers.Find(optionalServerID) != nullptr) { + (*tcpServers.Find(optionalServerID))->getClientSessions().GetKeys(sessionIDs); + } + } +} + +void USocketServerBPLibrary::serverPluginGetSocketSessionInfoByServerID(const FString serverID, const FString sessionID, bool& sessionFound, FString& IP, int32& port, EServerSocketConnectionProtocol& connectionProtocol) { + USocketServerBPLibrary::getSocketServerTarget()->serverPluginGetSocketSessionInfoByServerIDNonStatic(serverID, sessionID,sessionFound, IP, port, connectionProtocol); +} + +void USocketServerBPLibrary::serverPluginGetSocketSessionInfoByServerIDNonStatic(const FString serverID, const FString sessionID, bool& sessionFound, FString& IP, int32& port, EServerSocketConnectionProtocol& connectionProtocol){ + if (sessionID.IsEmpty()) { + sessionFound = false; + return; + } + + FSocketServerPluginSession* sessionPointer = nullptr; + + if (udpServers.Find(serverID) != nullptr) { + sessionPointer = (*udpServers.Find(serverID))->getClientSession(sessionID); + } + else { + sessionPointer = (*tcpServers.Find(serverID))->clientSessions.Find(sessionID); + } + + if (sessionPointer != nullptr) { + FSocketServerPluginSession& session = *sessionPointer; + IP = session.ip; + port = session.port; + connectionProtocol = session.protocol; + sessionFound = true; + } + else { + sessionFound = false; + IP = ""; + port = -1; + connectionProtocol = EServerSocketConnectionProtocol::E_NotSet; + } +} + + +void USocketServerBPLibrary::serverPluginGetSocketSessionInfo(const FString sessionID, bool &sessionFound, FString &IP, int32 &port, EServerSocketConnectionProtocol &connectionProtocol, FString& serverID) { + USocketServerBPLibrary::getSocketServerTarget()->serverPluginGetSocketSessionInfoNonStatic(sessionID, sessionFound, IP, port, connectionProtocol, serverID); +} + +void USocketServerBPLibrary::serverPluginGetSocketSessionInfoNonStatic(const FString sessionID, bool& sessionFound, FString& IP, int32& port, EServerSocketConnectionProtocol& connectionProtocol, FString& serverID){ + if (sessionID.IsEmpty()) { + sessionFound = false; + return; + } + + sessionFound = false; + IP = ""; + port = -1; + connectionProtocol = EServerSocketConnectionProtocol::E_NotSet; + + //udp + for (auto& element : udpServers) { + serverPluginGetSocketSessionInfoByServerID(element.Key, sessionID, sessionFound, IP, port, connectionProtocol); + if (sessionFound) { + serverID = element.Key; + break; + } + } + + //tcp + if (sessionFound == false) { + for (auto& element : tcpServers) { + serverPluginGetSocketSessionInfoByServerID(element.Key, sessionID, sessionFound, IP, port, connectionProtocol); + if (sessionFound) { + serverID = element.Key; + break; + } + } + } +} + +void USocketServerBPLibrary::removeSessionAndCloseConnection(FString sessionId, FString optionalServerID) { + USocketServerBPLibrary::getSocketServerTarget()->removeSessionAndCloseConnectionNonStatic(sessionId, optionalServerID); +} + +void USocketServerBPLibrary::removeSessionAndCloseConnectionNonStatic(FString sessionId, FString optionalServerID){ + if (optionalServerID.IsEmpty()) { + if (lastUDPServerID.IsEmpty() == false && udpServers.Find(lastUDPServerID) != nullptr) { + (*udpServers.Find(lastUDPServerID))->removeClientSession(sessionId); + } + if (lastTCPServerID.IsEmpty() == false && tcpServers.Find(lastTCPServerID) != nullptr) { + (*tcpServers.Find(lastTCPServerID))->removeClientSession(sessionId); + } + } + else { + if (udpServers.Find(optionalServerID) != nullptr) { + (*udpServers.Find(optionalServerID))->removeClientSession(sessionId); + } + if (optionalServerID.IsEmpty() == false && tcpServers.Find(optionalServerID) != nullptr) { + (*tcpServers.Find(optionalServerID))->removeClientSession(sessionId); + } + } +} + +FIPandPortStruct USocketServerBPLibrary::checkIpAndPort(FString IP, int32 port) { + + FIPandPortStruct ipAndPortStruct; + ipAndPortStruct.ip = IP; + ipAndPortStruct.port = port; + + ISocketSubsystem* socketSubSystem = getSocketSubSystem(); + if (socketSubSystem != nullptr) { + TSharedRef internetAddr = socketSubSystem->CreateInternetAddr(); + internetAddr->SetIp(*IP, ipAndPortStruct.success); + if (ipAndPortStruct.success == false) { + UE_LOG(LogTemp, Error, TEXT("Invalid IP:%s"), *IP); + ipAndPortStruct.errorMessage = "Invalid IP: " + IP; + return ipAndPortStruct; + } + } + + //simple port check + if (port < 0 || port > 65535) { + UE_LOG(LogTemp, Error, TEXT("Invalid port:%i"), port); + ipAndPortStruct.errorMessage = "Invalid port: " + FString::FromInt(port); + ipAndPortStruct.success = false; + return ipAndPortStruct; + } + + + + return ipAndPortStruct; +} + + + + +void USocketServerBPLibrary::stopUDPServer(FString optionalServerID) { + USocketServerBPLibrary::getSocketServerTarget()->stopUDPServerNonStatic(optionalServerID); +} + +void USocketServerBPLibrary::stopUDPServerNonStatic(FString optionalServerID){ + if (optionalServerID.IsEmpty()) { + optionalServerID = lastUDPServerID; + } + + if (optionalServerID.IsEmpty() || udpServers.Find(optionalServerID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("ServerID not found: %s"), *optionalServerID); + return; + } + + USocketServerUDP* udpServer = *udpServers.Find(optionalServerID); + if (udpServer != nullptr) { + udpServer->stopUDPServer(); + } + udpServers.Remove(optionalServerID); + + AsyncTask(ENamedThreads::GameThread, [optionalServerID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(false, "UDP Server stopped.", optionalServerID); + }); +} + +void USocketServerBPLibrary::stopAllUDPServers() { + USocketServerBPLibrary::getSocketServerTarget()->stopAllUDPServersNonStatic(); +} + +void USocketServerBPLibrary::stopAllUDPServersNonStatic(){ + TArray toRemoveServerKeys; + for (auto& element : udpServers) { + toRemoveServerKeys.Add(element.Key); + } + + for (auto& element : toRemoveServerKeys) { + stopUDPServer(element); + } +} + +void USocketServerBPLibrary::startUDPServer(FString& serverID, FString IP, int32 port ,bool multicast, EReceiveFilterServer receiveFilter, FString optionalServerID, int32 maxPacketSize) { + USocketServerBPLibrary::getSocketServerTarget()->startUDPServerNonStatic(serverID, IP, port, multicast, receiveFilter, optionalServerID, maxPacketSize); +} + +void USocketServerBPLibrary::startUDPServerNonStatic(FString& serverID, FString IP, int32 port, bool multicast, EReceiveFilterServer receiveFilter, FString optionalServerID, int32 maxPacketSize){ + serverID = optionalServerID; + + if (serverID.IsEmpty()) { + serverID = FGuid::NewGuid().ToString(); + UE_LOG(LogTemp, Display, TEXT("SimpleSocketServer Plugin: ServerID not set. Generate automatically one :%s"), *serverID); + } + + if (udpServers.Find(serverID) != nullptr) { + AsyncTask(ENamedThreads::GameThread, [serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(false, "An server with this serverID is already started.", serverID); + }); + return; + } + + FIPandPortStruct ipStruct = checkIpAndPort(IP, port); + if (ipStruct.success == false) { + AsyncTask(ENamedThreads::GameThread, [IP, port, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(false, "Can't start server on: " + IP + ":" + FString::FromInt(port), serverID); + }); + return; + } + + //check used port + for (auto& element : udpServers) { + USocketServerUDP* udpServerTmp = element.Value; + if (udpServerTmp != nullptr) { + if (udpServerTmp->getIP().Equals(IP) && udpServerTmp->getPort() == port) { + FString tmpServerID = udpServerTmp->getServerID(); + AsyncTask(ENamedThreads::GameThread, [IP, port, serverID, tmpServerID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(false, "Can't start server. Address is already used by: " + tmpServerID, serverID); + }); + return; + } + } + } + + USocketServerUDP* udpServer = NewObject(USocketServerUDP::StaticClass()); + udpServers.Add(serverID, udpServer); + udpServer->startUDPServer(ipStruct, IP, port, multicast, receiveFilter, serverID, maxPacketSize); + + lastUDPServerID = serverID; +} + + +void USocketServerBPLibrary::socketServerSendUDPMessage(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, ESocketServerUDPSocketType socketType, FString optionalServerID) { + USocketServerBPLibrary::getSocketServerTarget()->socketServerSendUDPMessageNonStatic(clientSessionIDs, message, byteArray, addLineBreak, asynchronous, socketType, optionalServerID); +} + +void USocketServerBPLibrary::socketServerSendUDPMessageNonStatic(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, ESocketServerUDPSocketType socketType, FString optionalServerID){ + if (message.Len() == 0 && byteArray.Num() == 0) + return; + + if (optionalServerID.IsEmpty()) { + optionalServerID = lastUDPServerID; + } + + if (optionalServerID.IsEmpty() || udpServers.Find(optionalServerID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: ServerID not found: %s"), *optionalServerID); + return; + } + + if (message.Len() > 0 && addLineBreak) { + message.Append("\r\n"); + } + + USocketServerUDP* udpServer = *udpServers.Find(optionalServerID); + if (udpServer != nullptr) { + udpServer->sendUDPMessage(clientSessionIDs, message, byteArray, asynchronous, socketType); + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Server not found or not started: %s"), *optionalServerID); + return; + } +} + + +void USocketServerBPLibrary::socketServerSendUDPMessageToClient(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, ESocketServerUDPSocketType socketType, FString optionalServerID) { + USocketServerBPLibrary::getSocketServerTarget()->socketServerSendUDPMessageToClientNonStatic(clientSessionID, message, byteArray, addLineBreak, asynchronous, socketType, optionalServerID); +} + +void USocketServerBPLibrary::socketServerSendUDPMessageToClientNonStatic(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, ESocketServerUDPSocketType socketType, FString optionalServerID){ + if (message.Len() == 0 && byteArray.Num() == 0) + return; + + if (optionalServerID.IsEmpty()) { + optionalServerID = lastUDPServerID; + } + + if (optionalServerID.IsEmpty() || udpServers.Find(optionalServerID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: ServerID not found: %s"), *optionalServerID); + return; + } + + if (message.Len() > 0 && addLineBreak) { + message.Append("\r\n"); + } + + USocketServerUDP* udpServer = *udpServers.Find(optionalServerID); + if (udpServer != nullptr) { + udpServer->sendUDPMessageToClient(clientSessionID, message, byteArray, asynchronous, socketType); + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Server not found or not started: %s"), *optionalServerID); + return; + } +} + +void USocketServerBPLibrary::socketServerSendUDPMessageTo(FString ip, int32 port, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, FString optionalServerID) { + USocketServerBPLibrary::getSocketServerTarget()->socketServerSendUDPMessageToNonStatic(ip, port, message, byteArray, addLineBreak, asynchronous, optionalServerID); +} + +void USocketServerBPLibrary::socketServerSendUDPMessageToNonStatic(FString ip, int32 port, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, FString optionalServerID){ + if (message.Len() == 0 && byteArray.Num() == 0) + return; + + if (optionalServerID.IsEmpty()) { + optionalServerID = lastUDPServerID; + } + + if (optionalServerID.IsEmpty() || udpServers.Find(optionalServerID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: ServerID not found: %s"), *optionalServerID); + return; + } + + if (message.Len() > 0 && addLineBreak) { + message.Append("\r\n"); + } + + USocketServerUDP* udpServer = *udpServers.Find(optionalServerID); + if (udpServer != nullptr) { + udpServer->sendUDPMessageTo(ip, port, message, byteArray, asynchronous); + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Server not found or not started: %s"), *optionalServerID); + return; + } +} + + + +//TCP +void USocketServerBPLibrary::startTCPServerNontStatic(FString& serverID, FString IP, int32 port, EReceiveFilterServer receiveFilter, + ESocketServerTCPSeparator messageSeparatorP, + FString optionalServerID, bool isFileServer, FString Aes256bitKey, bool resumeFiles, bool writeHandShakeToLogEditorOnly) { + + serverID = optionalServerID; + + if (serverID.IsEmpty()) { + serverID = FGuid::NewGuid().ToString(); + UE_LOG(LogTemp, Display, TEXT("SimpleSocketServer Plugin: ServerID not set. Generate automatically one :%s"), *serverID); + } + + if (tcpServers.Find(serverID) != nullptr) { + AsyncTask(ENamedThreads::GameThread, [serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Server, false, "An server with this serverID is already started.","",serverID); + }); + return; + } + + FIPandPortStruct ipStruct = checkIpAndPort(IP, port); + if (ipStruct.success == false) { + AsyncTask(ENamedThreads::GameThread, [IP, port, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Server, false, "Can't start server on: " + IP + ":" + FString::FromInt(port), "", serverID); + }); + return; + } + + //check used port + for (auto& element : tcpServers) { + USocketServerTCP* tcpServerTmp = element.Value; + if (tcpServerTmp != nullptr) { + if (tcpServerTmp->getIP().Equals(IP) && tcpServerTmp->getPort() == port) { + FString tmpServerID = tcpServerTmp->getServerID(); + AsyncTask(ENamedThreads::GameThread, [IP, port, serverID, tmpServerID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Server, false, "Can't start server. Address is already used by: " + tmpServerID, "", serverID); + }); + return; + } + } + } + + USocketServerTCP* tcpServer = NewObject(USocketServerTCP::StaticClass()); + if (serverID.IsEmpty()) { + serverID = FGuid::NewGuid().ToString(); + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: ServerID not set. Generate automatically one :%s"), *serverID); + } + tcpServers.Add(serverID, tcpServer); + tcpServer->startTCPServer(ipStruct, IP, port, receiveFilter, messageSeparatorP, serverID,isFileServer,Aes256bitKey,resumeFiles,writeHandShakeToLogEditorOnly); + + lastTCPServerID = serverID; +} + +void USocketServerBPLibrary::startTCPServer(FString& serverID, FString IP, int32 port, EReceiveFilterServer receiveFilter, ESocketServerTCPSeparator messageSeparatorP, + FString optionalServerID) { + USocketServerBPLibrary::getSocketServerTarget()->startTCPServerNontStatic(serverID, IP, port, receiveFilter, messageSeparatorP, optionalServerID, false, "", false,false); +} + +void USocketServerBPLibrary::startTCPFileServer(FString& serverID, FString IP, int32 port, FString customServerID, FString Aes256bitKey, bool resumeFiles, bool writeHandShakeToLogEditorOnly){ + USocketServerBPLibrary::getSocketServerTarget()->startTCPServerNontStatic(serverID, IP, port, EReceiveFilterServer::E_SAB, ESocketServerTCPSeparator::E_LengthSeparator, + customServerID, true, Aes256bitKey, resumeFiles, writeHandShakeToLogEditorOnly); +} + + +void USocketServerBPLibrary::addFileToken(FString token, bool deleteAfterUse, EFileFunctionsSocketServerDirectoryType directoryType, FString fileDirectory){ + if (token.IsEmpty()) + return; + struct FSocketServerToken fst; + fst.token = token; + fst.deleteAfterUse = deleteAfterUse; + fst.directoryType = directoryType; + fst.fileDirectory = fileDirectory; + + + USocketServerBPLibrary::getSocketServerTarget()->fileTokenMap.Add(token, fst); +} + +void USocketServerBPLibrary::addFileTokens(TMap fileTokens, bool deleteAfterUse, EFileFunctionsSocketServerDirectoryType directoryType){ + for (auto& element : fileTokens) { + addFileToken(element.Key, deleteAfterUse, directoryType, element.Value); + } +} + +void USocketServerBPLibrary::removeFileToken(FString token){ + USocketServerBPLibrary::getSocketServerTarget()->fileTokenMap.Remove(token); +} + + +void USocketServerBPLibrary::stopTCPServer(FString serverID) { + USocketServerBPLibrary::getSocketServerTarget()->stopTCPServerNonSTatic(serverID); +} + +void USocketServerBPLibrary::stopTCPServerNonSTatic(FString serverID){ + if (serverID.IsEmpty()) { + serverID = lastTCPServerID; + } + + if (rconServers.Find(serverID) != nullptr) { + unregiserRCONServer(serverID); + } + + if (serverID.IsEmpty() || tcpServers.Find(serverID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("ServerID not found: %s"), *serverID); + return; + } + + USocketServerTCP* tcpServer = *tcpServers.Find(serverID); + if (tcpServer != nullptr) { + tcpServer->stopTCPServer(); + } + tcpServers.Remove(serverID); +} + +void USocketServerBPLibrary::stopAllTCPServers() { + USocketServerBPLibrary::getSocketServerTarget()->stopAllTCPServersNonStatic(); +} + +void USocketServerBPLibrary::stopAllTCPServersNonStatic(){ + TArray toRemoveServerKeys; + for (auto& element : tcpServers) { + toRemoveServerKeys.Add(element.Key); + } + + for (auto& element : toRemoveServerKeys) { + stopTCPServer(element); + } +} + +void USocketServerBPLibrary::socketServerSendTCPMessage(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak,FString optionalServerID) { + USocketServerBPLibrary::getSocketServerTarget()->socketServerSendTCPMessageNonStatic( clientSessionIDs, message, byteArray, addLineBreak, optionalServerID); +} + +void USocketServerBPLibrary::socketServerSendTCPMessageNonStatic(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak, FString optionalServerID){ + if (message.Len() == 0 && byteArray.Num() == 0) + return; + + if (optionalServerID.IsEmpty()) { + optionalServerID = lastTCPServerID; + } + + if (optionalServerID.IsEmpty() || tcpServers.Find(optionalServerID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: ServerID not found: %s"), *optionalServerID); + return; + } + + USocketServerTCP* tcpServer = *tcpServers.Find(optionalServerID); + if (tcpServer != nullptr) { + tcpServer->sendTCPMessage(clientSessionIDs, message, byteArray, addLineBreak); + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Server not found or not started: %s"), *optionalServerID); + return; + } +} + +void USocketServerBPLibrary::socketServerSendTCPMessageToClient(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak, FString optionalServerID) { + USocketServerBPLibrary::getSocketServerTarget()->socketServerSendTCPMessageToClientNonStatic(clientSessionID, message, byteArray, addLineBreak, optionalServerID); +} + +void USocketServerBPLibrary::socketServerSendTCPMessageToClientNonStatic(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak, FString optionalServerID){ + if (message.Len() == 0 && byteArray.Num() == 0) + return; + + if (optionalServerID.IsEmpty()) { + optionalServerID = lastTCPServerID; + } + + if (optionalServerID.IsEmpty() || tcpServers.Find(optionalServerID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: ServerID not found: %s"), *optionalServerID); + return; + } + + USocketServerTCP* tcpServer = *tcpServers.Find(optionalServerID); + if (tcpServer != nullptr) { + tcpServer->sendTCPMessageToClient(clientSessionID, message, byteArray, addLineBreak); + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Server not found or not started: %s"), *optionalServerID); + return; + } +} + +void USocketServerBPLibrary::changeTCPSeparatorStringOnServer(FString separator) { + USocketServerBPLibrary::getSocketServerTarget()->changeTCPSeparatorStringOnServerNonStatic(separator); +} + +void USocketServerBPLibrary::changeTCPSeparatorStringOnServerNonStatic(FString separator){ + tcpStringSeparator = separator; +} + +void USocketServerBPLibrary::changeTCPSeparatorByteOnServer(uint8 separator) { + USocketServerBPLibrary::getSocketServerTarget()->changeTCPSeparatorByteOnServerNonStatic(separator); +} + +void USocketServerBPLibrary::changeTCPSeparatorByteOnServerNonStatic(uint8 separator) { + tcpByteSeparator = separator; +} + + +void USocketServerBPLibrary::getTcpSeparator(uint8& byteSeparator, FString& stringSeparator) { + stringSeparator = tcpStringSeparator; + byteSeparator = tcpByteSeparator; +} + +void USocketServerBPLibrary::cleanConnection(FSocketServerPluginSession& session) { + socketServerPluginCleanerThread->addSession(session); +} + +TMap USocketServerBPLibrary::getTcpServerMap(){ + return tcpServers; +} + + + +FString USocketServerBPLibrary::generateUniqueID(){ + return FGuid::NewGuid().ToString(); +} + +UDNSClientSocketServer* USocketServerBPLibrary::resolveDomain(FString domain, bool useDNSCache, FString dnsIP){ + return USocketServerBPLibrary::getSocketServerTarget()->resolveDomainNonStatic(domain, useDNSCache, dnsIP); +} + +UDNSClientSocketServer* USocketServerBPLibrary::resolveDomainNonStatic(FString domain, bool useDNSCache, FString dnsIP){ + UDNSClientSocketServer* dnsClient = NewObject(UDNSClientSocketServer::StaticClass()); + dnsClient->resolveDomain(USocketServerBPLibrary::getSocketSubSystem(), domain, useDNSCache, dnsIP); + return dnsClient; +} + +void USocketServerBPLibrary::changeSocketPlatform(ESocketPlatformServer platform){ + USocketServerBPLibrary::getSocketServerTarget()->systemSocketPlatform = platform; +} + +bool USocketServerBPLibrary::checkPort(EServerSocketConnectionCheckPortType type, FString ip, int32 port){ + return USocketServerBPLibrary::getSocketServerTarget()->checkPortNonStatic(type, ip, port); +} + +bool USocketServerBPLibrary::checkPortNonStatic(EServerSocketConnectionCheckPortType type, FString ip, int32 port){ + if (type == EServerSocketConnectionCheckPortType::E_TCP) { + FIPandPortStruct ipAndPort = checkIpAndPort(ip, port); + if (!ipAndPort.success) { + return false; + } + FSocket* listenerSocket = nullptr; + ISocketSubsystem* socketSubSystem = USocketServerBPLibrary::getSocketSubSystem(); + if (socketSubSystem != nullptr) { + + if (socketSubSystem != nullptr) { + TSharedRef internetAddr = socketSubSystem->CreateInternetAddr(); + bool validIP = false; + internetAddr->SetIp(*ip, validIP); + internetAddr->SetPort(port); + + if (validIP) { + listenerSocket = socketSubSystem->CreateSocket(NAME_Stream, *FString("USocketServerBPLibraryListenerCheckTCPPort"), internetAddr->GetProtocolType()); + } + + if (listenerSocket != nullptr) { + listenerSocket->SetLinger(false, 0); + + if (!listenerSocket->Bind(*internetAddr)) { + if (listenerSocket != nullptr) { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + } + return false; + } + + + if (!listenerSocket->Listen(1)) { + if (listenerSocket != nullptr) { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + } + return false; + } + } + } + } + if (listenerSocket == nullptr) { + return false; + } + else { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + return true; + } + } + else { + ISocketSubsystem* socketSubSystem = USocketServerBPLibrary::getSocketSubSystem(); + FSocket* listenerSocket = nullptr; + if (socketSubSystem != nullptr) { + TSharedPtr addr = socketSubSystem->CreateInternetAddr(); + bool validIP = true; + addr->SetPort(port); + addr->SetIp(*ip, validIP); + + if (!validIP) { + return false; + } + FString socketName; + listenerSocket = socketSubSystem->CreateSocket(NAME_DGram, *socketName, addr->GetProtocolType()); + if (listenerSocket == nullptr) { + return false; + } + listenerSocket->SetReuseAddr(); + listenerSocket->SetNonBlocking(); + if (!listenerSocket->Bind(*addr)) { + return false; + } + } + + if (listenerSocket == nullptr) { + return false; + } + else { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + return true; + } + } + return false; +} + +int32 USocketServerBPLibrary::getRandomPort(EServerSocketConnectionCheckPortType type, FString ip){ + return USocketServerBPLibrary::getSocketServerTarget()->getRandomPortNonStatic(type, ip); +} + +int32 USocketServerBPLibrary::getRandomPortNonStatic(EServerSocketConnectionCheckPortType type, FString ip){ + int32 port = 0; + if (type == EServerSocketConnectionCheckPortType::E_TCP) { + FIPandPortStruct ipAndPort = checkIpAndPort(ip, port); + if (!ipAndPort.success) { + return 0; + } + FSocket* listenerSocket = nullptr; + ISocketSubsystem* socketSubSystem = USocketServerBPLibrary::getSocketSubSystem(); + if (socketSubSystem != nullptr) { + + if (socketSubSystem != nullptr) { + TSharedRef internetAddr = socketSubSystem->CreateInternetAddr(); + bool validIP = false; + internetAddr->SetIp(*ip, validIP); + internetAddr->SetPort(port); + + if (validIP) { + listenerSocket = socketSubSystem->CreateSocket(NAME_Stream, *FString("USocketServerBPLibraryListenerCheckTCPPort"), internetAddr->GetProtocolType()); + } + + if (listenerSocket != nullptr) { + listenerSocket->SetLinger(false, 0); + + if (!listenerSocket->Bind(*internetAddr)) { + if (listenerSocket != nullptr) { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + } + return 0; + } + + + if (!listenerSocket->Listen(1)) { + if (listenerSocket != nullptr) { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + } + return 0; + } + } + } + } + if (listenerSocket == nullptr) { + return 0; + } + else { + port = listenerSocket->GetPortNo(); + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + return port; + } + } + else { + ISocketSubsystem* socketSubSystem = USocketServerBPLibrary::getSocketSubSystem(); + FSocket* listenerSocket = nullptr; + if (socketSubSystem != nullptr) { + TSharedPtr addr = socketSubSystem->CreateInternetAddr(); + bool validIP = true; + addr->SetPort(port); + addr->SetIp(*ip, validIP); + + if (!validIP) { + return 0; + } + FString socketName; + listenerSocket = socketSubSystem->CreateSocket(NAME_DGram, *socketName, addr->GetProtocolType()); + if (listenerSocket == nullptr) { + return 0; + } + listenerSocket->SetReuseAddr(); + listenerSocket->SetNonBlocking(); + if (!listenerSocket->Bind(*addr)) { + return 0; + } + } + + if (listenerSocket == nullptr) { + return 0; + } + else { + port = listenerSocket->GetPortNo(); + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + return port; + } + } + return 0; +} + +void USocketServerBPLibrary::registerClientEvent(FString sessionID, UEventBean*& event){ + USocketServerBPLibrary::getSocketServerTarget()->registerClientEventNonStatic(sessionID, event); +} + +void USocketServerBPLibrary::registerClientEventNonStatic(FString sessionID, UEventBean*& event){ + UPROPERTY() + UEventBean* eventBean = NewObject(UEventBean::StaticClass()); + eventBean->AddToRoot(); + messageEvents.Add(sessionID, eventBean); + event = eventBean; +} + +void USocketServerBPLibrary::unregisterClientEvent(FString sessionID){ + USocketServerBPLibrary::getSocketServerTarget()->unregisterClientEventNonStatic(sessionID); +} + +void USocketServerBPLibrary::unregisterClientEventNonStatic(FString sessionID){ + if (sessionID.IsEmpty()) + return; + if (messageEvents.Find(sessionID) != nullptr) { + UEventBean* event = *messageEvents.Find(sessionID); + if (event != nullptr) { + event->RemoveFromRoot(); + } + messageEvents.Remove(sessionID); + } +} + +UEventBean* USocketServerBPLibrary::getResiteredClientEvent(FString sessionID){ + if (sessionID.IsEmpty()) + return nullptr; + if (messageEvents.Find(sessionID) != nullptr) { + return *messageEvents.Find(sessionID); + } + return nullptr; +} + +void USocketServerBPLibrary::registerRCONServer(FString serverID, ERCONPasswordType passwordType, FString passwordOrFile, bool& success, FString& errorMessage){ + URCONServer* rconServer = NewObject(URCONServer::StaticClass()); + rconServer->AddToRoot(); + rconServer->startRCONServer(serverID, passwordType, passwordOrFile, success, errorMessage); + + if (success) { + USocketServerBPLibrary::getSocketServerTarget()->onserverReceiveTCPMessageEventDelegate.AddDynamic(rconServer, &URCONServer::receiveTCPMessageEvent); + USocketServerBPLibrary::getSocketServerTarget()->rconServers.Add(serverID, rconServer); + } +} + +void USocketServerBPLibrary::unregiserRCONServer(FString serverID){ + if (USocketServerBPLibrary::getSocketServerTarget()->rconServers.Find(serverID) != nullptr) { + URCONServer* rconServer = *USocketServerBPLibrary::getSocketServerTarget()->rconServers.Find(serverID); + USocketServerBPLibrary::getSocketServerTarget()->onserverReceiveTCPMessageEventDelegate.RemoveDynamic(rconServer, &URCONServer::receiveTCPMessageEvent); + USocketServerBPLibrary::getSocketServerTarget()->rconServers.Remove(serverID); + rconServer->RemoveFromRoot(); + rconServer = nullptr; + } +} + +void USocketServerBPLibrary::sendRCONResponse(FString sessionID, FString serverID, int32 requestID, FString response){ + if (USocketServerBPLibrary::getSocketServerTarget()->rconServers.Find(serverID) == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin (RCON): Server noch registered. %s"),*serverID); + return; + } + URCONServer* rconServer = *USocketServerBPLibrary::getSocketServerTarget()->rconServers.Find(serverID); + rconServer->sendResponse(sessionID, serverID,requestID, 0, response); +} + + +void USocketServerBPLibrary::changeCleanerThreadSettingsOnServer(bool showLogs, int32 minLiveTimeInSeconds) { + if (USocketServerBPLibrary::getSocketServerTarget()->socketServerPluginCleanerThread != nullptr) { + USocketServerBPLibrary::getSocketServerTarget()->socketServerPluginCleanerThread->changeSettings(showLogs, minLiveTimeInSeconds); + } +} + +void USocketServerBPLibrary::parseBytesToFloat(TArray bytes, float& value) { + + if (bytes.Num() != 4) { + UE_LOG(LogTemp, Error, TEXT("ParseBytesToFloat: Cannot convert bytes to float. Array must contain 4 bytes but has %i bytes."), bytes.Num()); + return; + } + + FMemory::Memcpy(&value, bytes.GetData(), 4); + +} + +void USocketServerBPLibrary::parseBytesToInteger(TArray bytes, int32& value) { + + + if (bytes.Num() != 4) { + UE_LOG(LogTemp, Error, TEXT("ParseBytesToInteger: Cannot convert bytes to integer. Array must contain 4 bytes but has %i bytes."), bytes.Num()); + return; + } + + FMemory::Memcpy(&value, bytes.GetData(), 4); +} + +void USocketServerBPLibrary::parseBytesToInteger64(TArray bytes, int64& value) { + + + if (bytes.Num() != 8) { + UE_LOG(LogTemp, Error, TEXT("ParseBytesToInteger64: Cannot convert bytes to integer. Array must contain 8 bytes but has %i bytes."), bytes.Num()); + return; + } + + FMemory::Memcpy(&value, bytes.GetData(), 8); +} + +void USocketServerBPLibrary::parseBytesToFloatPure(TArray bytes, float& value){ + parseBytesToFloat(bytes, value); +} + +void USocketServerBPLibrary::parseBytesToIntegerPure(TArray bytes, int32& value){ + parseBytesToInteger(bytes, value); +} + +void USocketServerBPLibrary::parseBytesToInteger64Pure(TArray bytes, int64& value){ + parseBytesToInteger64(bytes, value); +} + +void USocketServerBPLibrary::parseBytesToFloatEndian(TArray bytes, float& littleEndian, float& bigEndian) { + littleEndian = 0.f; + bigEndian = 0.f; + + if (bytes.Num() != 4) { + UE_LOG(LogTemp, Error, TEXT("ParseBytesToFloat: Cannot convert bytes to float. Array must contain 4 bytes but has %i bytes."), bytes.Num()); + return; + } + + uint8 littleEndianChar[] = { bytes[0], bytes[1], bytes[2], bytes[3] }; + FMemory::Memcpy(&littleEndian, &littleEndianChar, sizeof(littleEndian)); + + uint8 bigEndianChar[] = { bytes[3], bytes[2], bytes[1], bytes[0] }; + FMemory::Memcpy(&bigEndian, &bigEndianChar, sizeof(bigEndian)); +} + +void USocketServerBPLibrary::parseBytesToIntegerEndian(TArray bytes, int32& littleEndian, int32& bigEndian){ + littleEndian = 0; + bigEndian = 0; + + if (bytes.Num() != 4){ + UE_LOG(LogTemp, Error, TEXT("ParseBytesToInteger: Cannot convert bytes to integer. Array must contain 4 bytes but has %i bytes."), bytes.Num()); + return; + } + + + uint8 littleEndianChar[] = { bytes[0], bytes[1], bytes[2], bytes[3] }; + FMemory::Memcpy(&littleEndian, &littleEndianChar, sizeof(littleEndian)); + + uint8 bigEndianChar[] = { bytes[3], bytes[2], bytes[1], bytes[0] }; + FMemory::Memcpy(&bigEndian, &bigEndianChar, sizeof(bigEndian)); +} + +void USocketServerBPLibrary::parseBytesToInteger64Endian(TArray bytes, int64& littleEndian, int64& bigEndian) { + littleEndian = 0; + bigEndian = 0; + + if (bytes.Num() != 8) { + UE_LOG(LogTemp, Error, TEXT("ParseBytesToInteger64: Cannot convert bytes to integer. Array must contain 8 bytes but has %i bytes."), bytes.Num()); + return; + }return; + + uint8 littleEndianChar[] = { bytes[0], bytes[1], bytes[2], bytes[3],bytes[4], bytes[5], bytes[6], bytes[7] }; + FMemory::Memcpy(&littleEndian, &littleEndianChar, sizeof(littleEndian)); + + uint8 bigEndianChar[] = { bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0] }; + FMemory::Memcpy(&bigEndian, &bigEndianChar, sizeof(bigEndian)); +} + +void USocketServerBPLibrary::parseFloatToBytes(TArray& byteArray, float value, bool switchByteOrder){ + byteArray.Empty(); + union { + float tmpVal; + uint8 tmpArray[4]; + } u; + u.tmpVal = value; + byteArray.AddUninitialized(4); + + FMemory::Memcpy(byteArray.GetData(), u.tmpArray, 4); + + if (switchByteOrder) { + byteArray.SwapMemory(3, 0); + byteArray.SwapMemory(2, 1); + } +} + +void USocketServerBPLibrary::parseIntegerToBytes(TArray& byteArray, int32 value, bool switchByteOrder){ + byteArray.Empty(); + union { + int32 tmpVal; + uint8 tmpArray[4]; + } u; + u.tmpVal = value; + byteArray.AddUninitialized(4); + + FMemory::Memcpy(byteArray.GetData(), u.tmpArray, 4); + + if (switchByteOrder) { + byteArray.SwapMemory(3, 0); + byteArray.SwapMemory(2, 1); + } +} + +void USocketServerBPLibrary::parseInteger64ToBytes(TArray& byteArray, int64 value, bool switchByteOrder){ + byteArray.Empty(); + union { + int64 tmpVal; + uint8 tmpArray[8]; + } u; + u.tmpVal = value; + byteArray.AddUninitialized(8); + + FMemory::Memcpy(byteArray.GetData(), u.tmpArray, 8); + + if (switchByteOrder) { + byteArray.SwapMemory(7, 0); + byteArray.SwapMemory(6, 1); + byteArray.SwapMemory(5, 2); + byteArray.SwapMemory(4, 3); + } +} + +void USocketServerBPLibrary::parseFloatToBytesPure(TArray& byteArray, float value, bool switchByteOrder){ + byteArray.Empty(); + parseFloatToBytes(byteArray, value, switchByteOrder); +} + +void USocketServerBPLibrary::parseIntegerToBytesPure(TArray& byteArray, int32 value, bool switchByteOrder){ + byteArray.Empty(); + parseIntegerToBytes(byteArray, value, switchByteOrder); +} + +void USocketServerBPLibrary::parseInteger64ToBytesPure(TArray& byteArray, int64 value, bool switchByteOrder){ + byteArray.Empty(); + parseInteger64ToBytes(byteArray, value, switchByteOrder); +} + +void USocketServerBPLibrary::parseBytesToFloatArrayPure(TArray& value, TArray bytes){ + if (bytes.Num() == 0 && bytes.Num()%4 != 0) + return; + value.Empty(); + value.AddUninitialized(bytes.Num()/4); + FMemory::Memcpy(value.GetData(), bytes.GetData(), bytes.Num()); +} + +void USocketServerBPLibrary::parseBytesToIntegerArrayPure(TArray& value, TArray bytes){ + if (bytes.Num() == 0 && bytes.Num() % 4 != 0) + return; + value.Empty(); + value.AddUninitialized(bytes.Num() / 4); + FMemory::Memcpy(value.GetData(), bytes.GetData(), bytes.Num()); +} + +void USocketServerBPLibrary::parseBytesToInteger64ArrayPure(TArray& value, TArray bytes){ + if (bytes.Num() == 0 && bytes.Num() % 8 != 0) + return; + value.Empty(); + value.AddUninitialized(bytes.Num() / 8); + FMemory::Memcpy(value.GetData(), bytes.GetData(), bytes.Num()); +} + +void USocketServerBPLibrary::parseFloatArrayToBytesPure(TArray& byteArray, TArray value){ + byteArray.Empty(); + byteArray.AddUninitialized(value.Num() * 4); + FMemory::Memcpy(byteArray.GetData(), value.GetData(), value.Num()*4); +} + +void USocketServerBPLibrary::parseIntegerArrayToBytesPure(TArray& byteArray, TArray value){ + byteArray.Empty(); + byteArray.AddUninitialized(value.Num() * 4); + FMemory::Memcpy(byteArray.GetData(), value.GetData(), value.Num() * 4); +} + +void USocketServerBPLibrary::parseInteger64ArrayToBytesPure(TArray& byteArray, TArray value){ + byteArray.Empty(); + byteArray.AddUninitialized(value.Num() * 8); + FMemory::Memcpy(byteArray.GetData(), value.GetData(), value.Num() * 8); +} + +TArray USocketServerBPLibrary::parseHexToBytes(FString hex) { + TArray bytes; + + if (hex.Contains(" ")) { + hex = hex.Replace(TEXT(" "), TEXT("")); + } + + if (hex.Len() % 2 != 0) { + UE_LOG(LogTemp, Error, TEXT("This is not a valid hex string: %s"), *hex); + return bytes; + } + + + TArray charArray = hex.GetCharArray(); + for (int32 i = 0; i < (charArray.Num() - 1); i++) { + if (CheckTCharIsHex(charArray[i]) == false) { + UE_LOG(LogTemp, Error, TEXT("This is not a valid hex string: %s"), *hex); + return bytes; + } + } + + + bytes.AddZeroed(hex.Len() / 2); + HexToBytes(hex, bytes.GetData()); + + return bytes; +} + +FString USocketServerBPLibrary::parseHexToString(FString hex) { + TArray bytes = parseHexToBytes(hex); + bytes.Add(0x00);// null-terminator + char* Data = (char*)bytes.GetData(); + return FString(UTF8_TO_TCHAR(Data)); +} + +FString USocketServerBPLibrary::parseBytesToHex(TArray bytes) { + FString hex; + hex = BytesToHex(bytes.GetData(), bytes.Num()); + return hex; +} + +TArray USocketServerBPLibrary::parseHexToBytesPure(FString hex) { + return parseHexToBytes(hex); +} + +FString USocketServerBPLibrary::parseHexToStringPure(FString hex) { + return parseHexToString(hex); +} + +FString USocketServerBPLibrary::parseBytesToHexPure(TArray bytes) { + return parseBytesToHex(bytes); +} + + +bool USocketServerBPLibrary::startUEGameHost(UObject* worldContextObject, FString Protocol, FString Host, FString Map, FString RedirectURL, TArray Op, FString Portal, int32 Port) { + if (GEngine) { + FURL url; + url.Protocol = Protocol; + url.Host = Host; + url.Port = Port; + url.Map = Map; + url.RedirectURL = RedirectURL; + url.Op = Op; + url.Portal = Portal; + UWorld* world = GEngine->GetWorldFromContextObjectChecked(worldContextObject); + if (world) { + return world->Listen(url); + } + } + return false; +} + +//void USocketServerBPLibrary::stopUEGameHost(UObject* worldContextObject) { +// //It looks like the listen function checks if a server is already running and then closes it if it is running. +// //If you now call listen again with port 0 then the server is stopped but new one can't be created on port 0. +// startUEGameHost(worldContextObject, 0); +//} + +ISocketSubsystem * USocketServerBPLibrary::getSocketSubSystem(){ + switch (USocketServerBPLibrary::getSocketServerTarget()->systemSocketPlatform) + { + case ESocketPlatformServer::E_SSS_SYSTEM: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_SYSTEM")); + return ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); + case ESocketPlatformServer::E_SSS_WINDOWS: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_WINDOWS")); + return ISocketSubsystem::Get(FName(TEXT("WINDOWS"))); + case ESocketPlatformServer::E_SSS_MAC: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_MAC")); + return ISocketSubsystem::Get(FName(TEXT("MAC"))); + case ESocketPlatformServer::E_SSS_IOS: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_IOS")); + return ISocketSubsystem::Get(FName(TEXT("IOS"))); + case ESocketPlatformServer::E_SSS_UNIX: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_UNIX")); + return ISocketSubsystem::Get(FName(TEXT("UNIX"))); + case ESocketPlatformServer::E_SSS_ANDROID: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_ANDROID")); + return ISocketSubsystem::Get(FName(TEXT("ANDROID"))); + case ESocketPlatformServer::E_SSS_PS4: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_PS4")); + return ISocketSubsystem::Get(FName(TEXT("PS4"))); + case ESocketPlatformServer::E_SSS_XBOXONE: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_XBOXONE")); + return ISocketSubsystem::Get(FName(TEXT("XBOXONE"))); + case ESocketPlatformServer::E_SSS_HTML5: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_HTML5")); + return ISocketSubsystem::Get(FName(TEXT("HTML5"))); + case ESocketPlatformServer::E_SSS_SWITCH: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_SWITCH")); + return ISocketSubsystem::Get(FName(TEXT("SWITCH"))); + case ESocketPlatformServer::E_SSS_DEFAULT: + //UE_LOG(LogTemp, Display, TEXT("E_SSS_DEFAULT")); + return ISocketSubsystem::Get(); + default: + //pUE_LOG(LogTemp, Display, TEXT("DEFAULT")); + return ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); + } +} + +//void USocketServerBPLibrary::addClientSession(FString key, FClientSocketSession& session) { +// clientSessions.Add(key, session); +//} +// +//void USocketServerBPLibrary::removeClientSession(FString key) { +// FClientSocketSession* sessionPointer = clientSessions.Find(key); +// if (sessionPointer != nullptr) { +// FClientSocketSession& session = *sessionPointer; +// +// if (session.protocol == EServerSocketConnectionProtocol::E_TCP) { +// if (session.TCPClientSendDataToServerThread != nullptr && session.TCPClientSendDataToServerThread->isRun()) { +// session.TCPClientSendDataToServerThread->stopThread(); +// } +// FSocket * socket = session.socket; +// if (socket != nullptr) { +// socket->Close(); +// } +// } +// clientSessions.Remove(key); +// } +// +//} + +//void USocketServerBPLibrary::startTCPClientHandler(FClientSocketSession& session, EReceiveFilterServer receiveFilter) { +// new FClientConnectionThread(session, receiveFilter); +//} +// +//bool USocketServerBPLibrary::isTCPServerRun() { +// return tcpServerRun; +//} +// +//void USocketServerBPLibrary::setTCPServerRun(bool run) { +// tcpServerRun = run; +//} diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerCleanerThread.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerCleanerThread.cpp new file mode 100644 index 0000000..a2df6a2 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerCleanerThread.cpp @@ -0,0 +1,66 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerCleanerThread.h" + +FSocketServerCleanerThread::FSocketServerCleanerThread() { + FString threadName = "FSocketServerPluginCleanerThread_" + FGuid::NewGuid().ToString(); + thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Lowest); +} + + +void FSocketServerCleanerThread::addSession(FSocketServerPluginSession& session) { + session.addToCleanerTime = FDateTime::Now().GetTicks(); + sessionQueue.Enqueue(session); +} + +void FSocketServerCleanerThread::changeSettings(bool showLogsP, int32 minLiveTimeInSecondsP){ + showLogs = showLogsP; + minLiveTimeInSeconds = minLiveTimeInSecondsP; +} + +uint32 FSocketServerCleanerThread::Run() { + while (true) { + TArray tryItAgain; + + while (sessionQueue.IsEmpty() == false) { + FSocketServerPluginSession session; + sessionQueue.Dequeue(session); + + //if (session.recieverThread == nullptr && session.sendThread == nullptr) { + // continue; + //} + + + //one second = 10000000 ticks + if ((FDateTime::Now().GetTicks() - session.addToCleanerTime) < (10000000 * minLiveTimeInSeconds)) { + tryItAgain.Add(session); + continue; + } + + if (showLogs) { + UE_LOG(LogTemp, Display, TEXT("SocketServer: Clean Session: %s"), *session.sessionID); + } + + + delete session.tcpSendThread; + delete session.tcpRecieverThread; + delete session.tcpFileHandlerThread; + + delete session.udpServerThread; + delete session.udpSocketReceiver; + delete session.udpSendThread; + + delete session.socket; + + } + + for (int32 i = 0; i < tryItAgain.Num(); i++) { + sessionQueue.Enqueue(tryItAgain[i]); + } + tryItAgain.Empty(); + + + FPlatformProcess::Sleep(minLiveTimeInSeconds); + } + return 0; +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCP.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCP.cpp new file mode 100644 index 0000000..44b5711 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCP.cpp @@ -0,0 +1,259 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerTCP.h" + + +USocketServerTCP::USocketServerTCP(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + this->AddToRoot(); + /*UE_LOG(LogTemp, Warning, TEXT("USocketServerPluginTCPServer 1"));*/ +} + + +void USocketServerTCP::startTCPServer(FIPandPortStruct ipStructP,FString IPPP, int32 portP, EReceiveFilterServer receiveFilterP, + ESocketServerTCPSeparator messageWrappingP, FString serverIDP, bool isFileServer, FString Aes256bitKeyP, bool resumeFilesP, bool writeHandShakeToLogEditorOnlyP) { + ipAndPortStruct = ipStructP; + serverPort = portP; + receiveFilter = receiveFilterP; + serverID = serverIDP; + fileServer = isFileServer; + aesKey = Aes256bitKeyP; + resumeFiles = resumeFilesP; + messageWrapping = messageWrappingP; + writeHandShakeToLogEditorOnly = writeHandShakeToLogEditorOnlyP; + USocketServerBPLibrary::socketServerBPLibrary->getTcpSeparator(tcpByteSeparator, tcpStringSeparator); + socketServerTCPThread = new FSocketServerTCPThread(this, receiveFilter,run); +} + + +void USocketServerTCP::stopTCPServer() { + + TArray toRemoveSessionKeys; + for (auto& element : getClientSessions()) { + toRemoveSessionKeys.Add(element.Key); + } + for (int32 i = 0; i < toRemoveSessionKeys.Num(); i++) { + removeClientSession(toRemoveSessionKeys[i]); + } + toRemoveSessionKeys.Empty(); + + run = false; + if (socketServerTCPThread != nullptr) { + socketServerTCPThread->stopThread(); + delete socketServerTCPThread; + socketServerTCPThread = nullptr; + } +} + +void USocketServerTCP::sendTCPMessage(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak){ + + if (message.Len() > 0) { + //if (messageWrapping == ESocketServerTCPSeparator::E_StringSeparator) { + // message = tcpMessageHeader + message + tcpMessageFooter; + //} + if (addLineBreak) { + message.Append("\r\n"); + } + } + + + for (auto& sessionID : clientSessionIDs) { + if (clientSessions.Find(sessionID) != nullptr) { + FSocketServerPluginSession& session = *clientSessions.Find(sessionID); + if (session.protocol == EServerSocketConnectionProtocol::E_TCP) { + + if (session.tcpSendThread == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: The thread for sending data has not yet been initialized. Data is not sent.")); + } + else { + session.tcpSendThread->sendMessage(message, byteArray); + } + } + } + } +} + +void USocketServerTCP::sendTCPMessageToClient(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak) { + + if (message.Len() > 0) { + //if (messageWrapping == ESocketServerTCPSeparator::E_StringSeparator) { + // message = tcpMessageHeader + message + tcpMessageFooter; + //} + if (addLineBreak) { + message.Append("\r\n"); + } + } + + if (clientSessions.Find(clientSessionID) != nullptr) { + FSocketServerPluginSession& session = *clientSessions.Find(clientSessionID); + if (session.protocol == EServerSocketConnectionProtocol::E_TCP) { + + if (session.tcpSendThread == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: The thread for sending data has not yet been initialized. Data is not sent.")); + } + else { + session.tcpSendThread->sendMessage(message, byteArray); + } + } + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Session not found: %s"),*clientSessionID); + } +} + + +FIPandPortStruct USocketServerTCP::getServerIpAndPortStruct() { + return ipAndPortStruct; +} + +FString USocketServerTCP::getIP() { + return serverIP; +} + +int32 USocketServerTCP::getPort() { + return serverPort; +} + +FString USocketServerTCP::getServerID() { + return serverID; +} + +bool USocketServerTCP::hasResume() { + return resumeFiles; +} + + +void USocketServerTCP::initTCPClientThreads(FSocketServerPluginSession& sessionP, EReceiveFilterServer receiveFilterP){ + if (fileServer) { + sessionP.tcpFileHandlerThread = new FSocketServerTCPFileHandlerThread(this, sessionP); + //tcpFileHandlerThread = new FSocketServerTCPFileHandlerThread(this, sessionP); + } + else { + sessionP.tcpRecieverThread = new FSocketServerTCPClientReceiveDataThread(this, sessionP, receiveFilterP); + sessionP.tcpSendThread = new FSocketServerTCPClientSendDataThread(this, sessionP); + } + +} + +void USocketServerTCP::addClientSession(FSocketServerPluginSession& session) { + if (session.sessionID.IsEmpty() == false) { + //UE_LOG(LogTemp, Warning, TEXT("ADD Session:%s"), *session.sessionID); + clientSessions.Add(session.sessionID, session); + } +} + +//FClientSocketSession USocketServerPluginTCPServer::getClientSession(FString key) { +// if (clientSessions.Find(key) == nullptr) +// return FClientSocketSession(); +// return *clientSessions.Find(key); +//} + +void USocketServerTCP::removeClientSession(FString key) { + //close client socket + if (clientSessions.Find(key) != nullptr) { + FSocketServerPluginSession& session = *clientSessions.Find(key); + + if (session.tcpRecieverThread != nullptr) { + session.tcpRecieverThread->stopThread(); + } + + if (session.tcpSendThread != nullptr) { + session.tcpSendThread->stopThread(); + } + + if (session.tcpFileHandlerThread != nullptr) { + session.tcpFileHandlerThread->stopThread(); + } + + + USocketServerBPLibrary::socketServerBPLibrary->cleanConnection(session); + } + if (clientSessions.Remove(key)) { + //UE_LOG(LogTemp, Warning, TEXT("Remove3 Session:%s"), *key); + USocketServerBPLibrary::socketServerBPLibrary->unregisterClientEvent(key); + } +} + +TMap USocketServerTCP::getClientSessions(){ + return clientSessions; +} + +//EHTTPSocketServerFileDownloadResumeType USocketServerPluginTCPServer::getifFileExistThen(){ +// return ifFileExistThen; +//} + + + +FString USocketServerTCP::encryptMessage(FString message) { +//#if WITH_EDITOR +// if (writeHandShakeToLogEditorOnly) { + UE_LOG(LogTemp, Warning, TEXT("TCP File Server Handshake: %s"),*message); +// } +//#endif + return UFileFunctionsSocketServer::encryptMessageWithAES(message, aesKey); +} + +FString USocketServerTCP::decryptMessage(FString message) { + message = UFileFunctionsSocketServer::decryptMessageWithAES(message, aesKey); +//#if WITH_EDITOR +// if (writeHandShakeToLogEditorOnly) { + UE_LOG(LogTemp, Warning, TEXT("TCP File Server Handshake: %s"),*message); +// } +//#endif + return message; +} + +struct FSocketServerToken USocketServerTCP::getTokenStruct(FString token){ + FSocketServerToken sst; + if (USocketServerBPLibrary::socketServerBPLibrary->fileTokenMap.Find(token) != nullptr) { + sst = *USocketServerBPLibrary::socketServerBPLibrary->fileTokenMap.Find(token); + } + return sst; +} + +void USocketServerTCP::removeTokenFromStruct(FString token) { + if (USocketServerBPLibrary::socketServerBPLibrary->fileTokenMap.Find(token) != nullptr) { + USocketServerBPLibrary::socketServerBPLibrary->fileTokenMap.Remove(token); + } +} + +FString USocketServerTCP::getCleanDir(EFileFunctionsSocketServerDirectoryType directoryType, FString fileDirectory) { + return UFileFunctionsSocketServer::getCleanDirectory(directoryType, fileDirectory); +} + +void USocketServerTCP::getMD5FromFile(FString filePathP, bool& success, FString& MD5) { + UFileFunctionsSocketServer::getMD5FromFileAbsolutePath(filePathP, success, MD5); +} + +void USocketServerTCP::deleteFile(FString filePathP) { + UFileFunctionsSocketServer::deleteFileAbsolutePath(filePathP); +} + +bool USocketServerTCP::isRun(){ + return run; +} + +int64 USocketServerTCP::fileSize(FString filePathP) { + return UFileFunctionsSocketServer::fileSizeAbsolutePath(filePathP); +} + +FString USocketServerTCP::int64ToString(int64 num) { + return UFileFunctionsSocketServer::int64ToString(num); +} + +void USocketServerTCP::getTcpSeparator(FString& stringSeparator, uint8& byteSeparator, ESocketServerTCPSeparator& messageWrappingP) { + messageWrappingP = messageWrapping; + stringSeparator = tcpStringSeparator; + tcpByteSeparator = byteSeparator; +} + +void USocketServerTCP::readDataLength(TArray& byteDataArray, int32& byteLenght) { + if (FGenericPlatformProperties::IsLittleEndian() && byteDataArray[0] == 0x00) { + FMemory::Memcpy(&byteLenght, byteDataArray.GetData() + 1, 4); + } + else { + //endian fits not. swap bytes that contains the length + byteDataArray.SwapMemory(1, 4); + byteDataArray.SwapMemory(2, 3); + FMemory::Memcpy(&byteLenght, byteDataArray.GetData() + 1, 4); + } +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPClientReceiveDataThread.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPClientReceiveDataThread.cpp new file mode 100644 index 0000000..3a13602 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPClientReceiveDataThread.cpp @@ -0,0 +1,245 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerTCPClientReceiveDataThread.h" + + +FSocketServerTCPClientReceiveDataThread::FSocketServerTCPClientReceiveDataThread(USocketServerTCP* tcpServerP, + FSocketServerPluginSession& sessionP, + EReceiveFilterServer receiveFilterP) : + tcpServer(tcpServerP), + session(sessionP), + receiveFilter(receiveFilterP){ + FString threadName = "FTCPClientReceiveDataFromServerThread" + FGuid::NewGuid().ToString(); + thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_BelowNormal); +} + +FSocketServerTCPClientReceiveDataThread::~FSocketServerTCPClientReceiveDataThread() { + delete thread; + //thread = nullptr; +} + +uint32 FSocketServerTCPClientReceiveDataThread::Run() { + + //FPlatformProcess::Sleep(0.1); + //tcpServer->removeClientSession(session.sessionID); + + FString serverID = tcpServer->getServerID(); + FSocket* clientSocket = session.socket; + FString sessionID = session.sessionID; + + //message wrapping + FString stringSeparator = FString(); + uint8 byteSeparator = 0x00; + ESocketServerTCPSeparator messageWrapping = ESocketServerTCPSeparator::E_None; + tcpServer->getTcpSeparator(stringSeparator, byteSeparator, messageWrapping); + + TArray stringSeparatorArray = stringSeparator.GetCharArray(); + if (stringSeparatorArray.Num() > 0 && stringSeparatorArray.Last() == 0x00) { + stringSeparatorArray.RemoveAt(stringSeparatorArray.Num() - 1, 1, true); + } + + if (messageWrapping == ESocketServerTCPSeparator::E_LengthSeparator && stringSeparatorArray.Num() == 0) { + messageWrapping = ESocketServerTCPSeparator::E_None; + UE_LOG(LogTemp, Warning, TEXT("Socket Sever Plugin: Separator mode is set to String but there is no String Separator. Mode changed to none.")); + } + + + //switch to gamethread + AsyncTask(ENamedThreads::GameThread, [sessionID, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Client, true, "Client connected", sessionID, serverID); + }); + + uint32 DataSize = 0; + //FArrayReaderPtr Datagram = MakeShareable(new FArrayReader(true)); + TArray dataFromSocket; + int64 ticks1; + int64 ticks2; + TArray byteDataArray; + TArray byteDataArrayCache; + bool hasData = false; + int32 lastDataLengthFromHeader = 0; + + while (run && clientSocket != nullptr && tcpServer->isRun()) { + //ESocketConnectionState::SCS_Connected does not work https://issues.unrealengine.com/issue/UE-27542 + //Compare ticks is a workaround to get a disconnect. clientSocket->Wait() stop working after disconnect. (Another bug?) + //If it doesn't wait any longer, ticks1 and ticks2 should be the same == disconnect. + ticks1 = FDateTime::Now().GetTicks(); + clientSocket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(1)); + ticks2 = FDateTime::Now().GetTicks(); + + + hasData = clientSocket->HasPendingData(DataSize);; + + /* if (tcpServer->isRun()) { + hasData = clientSocket->HasPendingData(DataSize); + } + else { + deathConnection = true; + }*/ + + if (!hasData && ticks1 == ticks2) { + deathConnection = true; + //UE_LOG(LogTemp, Display, TEXT("TCP End xxx: %s:%i"), *session.ip, session.port); + break; + } + if (hasData) { + + dataFromSocket.SetNumUninitialized(DataSize); + int32 BytesRead = 0; + if (clientSocket->Recv(dataFromSocket.GetData(), dataFromSocket.Num(), BytesRead)) { + + switch (messageWrapping) + { + case ESocketServerTCPSeparator::E_None: + triggerMessageEvent(dataFromSocket, sessionID, serverID); + break; + + case ESocketServerTCPSeparator::E_ByteSeparator: + + for (int32 i = 0; i < dataFromSocket.Num(); i++) { + byteDataArrayCache.Add(dataFromSocket[i]); + if (dataFromSocket[i] == byteSeparator) { + triggerMessageEvent(byteDataArrayCache, sessionID, serverID, false); + byteDataArrayCache.Empty(); + } + } + + break; + + case ESocketServerTCPSeparator::E_StringSeparator: + for (int32 i = 0; i < dataFromSocket.Num(); i++) { + ; + if ((TCHAR)dataFromSocket[i] == stringSeparatorArray[0]) { + bool found = true; + for (int32 j = 1; j < stringSeparatorArray.Num(); j++) { + i++; + if (dataFromSocket.Num() <= (i)) { + found = false; + break; + } + if ((TCHAR)dataFromSocket[i] != stringSeparatorArray[j]) { + found = false; + break; + } + } + if (found) { + triggerMessageEvent(byteDataArrayCache, sessionID, serverID, true); + byteDataArrayCache.Empty(); + } + } + else { + byteDataArrayCache.Add(dataFromSocket[i]); + } + } + break; + + case ESocketServerTCPSeparator::E_LengthSeparator: + if (lastDataLengthFromHeader == 0 && dataFromSocket.Num() >= 5) { + + tcpServer->readDataLength(dataFromSocket, lastDataLengthFromHeader); + + if (dataFromSocket.Num() == 5) { + dataFromSocket.Empty(); + continue; + } + + byteDataArrayCache.Append(dataFromSocket.GetData() + 5, dataFromSocket.Num() - 5); + dataFromSocket.Empty(); + } + else { + byteDataArrayCache.Append(dataFromSocket.GetData(), dataFromSocket.Num()); + } + + int32 maxLoops = 1000;//to prevent endless loop + + while (byteDataArrayCache.Num() > 0 && byteDataArrayCache.Num() >= lastDataLengthFromHeader && maxLoops > 0) { + maxLoops--; + + byteDataArray.Append(byteDataArrayCache.GetData(), lastDataLengthFromHeader); + byteDataArrayCache.RemoveAt(0, lastDataLengthFromHeader, true); + + + triggerMessageEvent(byteDataArray, sessionID, serverID); + //UE_LOG(LogTemp, Display, TEXT("%s"), *mainMessage); + byteDataArray.Empty(); + + if (byteDataArrayCache.Num() == 0) { + lastDataLengthFromHeader = 0; + break; + } + + if (byteDataArrayCache.Num() > 5) { + tcpServer->readDataLength(byteDataArrayCache, lastDataLengthFromHeader); + byteDataArrayCache.RemoveAt(0, 5, true); + } + + } + break; + } + } + dataFromSocket.Empty(); + } + } + + if (!deathConnection && clientSocket != nullptr) { + //UE_LOG(LogTemp, Display, TEXT("TCP Close")); + clientSocket->Close(); + } + //else { + // if (deathConnection) { + // UE_LOG(LogTemp, Display, TEXT("TCP2 Close true")); + // } + // else { + // UE_LOG(LogTemp, Display, TEXT("TCP2 Close false")); + // } + //} + + //UE_LOG(LogTemp, Display, TEXT("TCP Connected: %s:%i"), *session.ip, session.port); + + //switch to gamethread + USocketServerTCP* tcpServerGlobal = tcpServer; + AsyncTask(ENamedThreads::GameThread, [sessionID, serverID, tcpServerGlobal]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Client, false, "Client disconnected", sessionID, serverID); + //clean up socket in main thread because race condition + tcpServerGlobal->removeClientSession(sessionID); + }); + + dataFromSocket.Empty(); + byteDataArray.Empty(); + byteDataArrayCache.Empty(); + + return 0; +} + +void FSocketServerTCPClientReceiveDataThread::triggerMessageEvent(TArray& byteDataArray, FString& sessionID, FString& serverID, bool addNullTerminator) { + //if (receiveFilter == EReceiveFilterServer::E_SAB || receiveFilter == EReceiveFilterServer::E_B) { + // byteDataArray.Append(dataFromSocket.GetData(), dataFromSocket.Num()); + //} + + FString mainMessage = FString(); + if (receiveFilter == EReceiveFilterServer::E_SAB || receiveFilter == EReceiveFilterServer::E_S) { + if (addNullTerminator) + byteDataArray.Add(0x00);// null-terminator + mainMessage = FString(UTF8_TO_TCHAR((char*)byteDataArray.GetData())); + if (receiveFilter == EReceiveFilterServer::E_S) { + byteDataArray.Empty(); + } + } + + + + //switch to gamethread + AsyncTask(ENamedThreads::GameThread, [mainMessage, sessionID, byteDataArray, serverID]() { + //UE_LOG(LogTemp, Display, TEXT("TCP:%s"), *recvMessage); + USocketServerBPLibrary::socketServerBPLibrary->onserverReceiveTCPMessageEventDelegate.Broadcast(sessionID, mainMessage, byteDataArray, serverID); + if (USocketServerBPLibrary::socketServerBPLibrary->getResiteredClientEvent(sessionID) != nullptr) { + USocketServerBPLibrary::socketServerBPLibrary->getResiteredClientEvent(sessionID)->onregisteredEventDelegate.Broadcast(mainMessage, byteDataArray); + } + }); + mainMessage.Empty(); +} + +void FSocketServerTCPClientReceiveDataThread::stopThread() { + run = false; + Stop(); +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPClientSendDataThread.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPClientSendDataThread.cpp new file mode 100644 index 0000000..48cd45c --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPClientSendDataThread.cpp @@ -0,0 +1,205 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerTCPClientSendDataThread.h" + + +FSocketServerTCPClientSendDataThread::FSocketServerTCPClientSendDataThread(USocketServerTCP* tcpServerP, FSocketServerPluginSession& sessionP) : + tcpServer(tcpServerP), + session(sessionP) { + FString threadName = "FTCPClientSendDataToServerThread_" + FGuid::NewGuid().ToString(); + thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_BelowNormal); +} + +FSocketServerTCPClientSendDataThread::~FSocketServerTCPClientSendDataThread() { + delete thread; +} + +uint32 FSocketServerTCPClientSendDataThread::Run() { + FString serverID = tcpServer->getServerID(); + socket = session.socket; + FString sessionID = session.sessionID; + + //message wrapping + FString stringSeparator = FString(); + uint8 byteSeparator = 0x00; + ESocketServerTCPSeparator messageWrapping = ESocketServerTCPSeparator::E_None; + + tcpServer->getTcpSeparator(stringSeparator, byteSeparator, messageWrapping); + + TArray stringSeparatorArray = stringSeparator.GetCharArray(); + if (stringSeparatorArray.Num() > 0 && stringSeparatorArray.Last() == 0x00) { + stringSeparatorArray.RemoveAt(stringSeparatorArray.Num() - 1, 1, true); + } + + if (messageWrapping == ESocketServerTCPSeparator::E_LengthSeparator && stringSeparatorArray.Num() == 0) { + messageWrapping = ESocketServerTCPSeparator::E_None; + UE_LOG(LogTemp, Warning, TEXT("Socket Sever Plugin: Separator mode is set to String but there is no String Separator. Mode changed to none.")); + } + + while (run) { + + //Wait a bit in case someone tries to send something right after the connection is established to avoid hitting a dead connection. + if (waitForInit) { + waitForInit = false; + FPlatformProcess::Sleep(0.5); + } + + if (socket == nullptr || tcpServer->clientSessions.Find(sessionID) == nullptr) { + //UE_LOG(LogTemp, Error, TEXT("Socket not found.")); + return 0; + } + + + // try to connect to the server + if (socket == nullptr || run == false) { + //UE_LOG(LogTemp, Error, TEXT("Connection not exist.")); + //switch to gamethread + if (messageQueue.IsEmpty() == false || byteArrayQueue.IsEmpty() == false) { + AsyncTask(ENamedThreads::GameThread, [sessionID, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Client, false, "Connection not exist", sessionID, serverID); + }); + } + return 0; + } + + + int32 sent = 0; + if (socket != nullptr) { + + + + while (messageQueue.IsEmpty() == false) { + FString m; + messageQueue.Dequeue(m); + FTCHARToUTF8 Convert(*m); + sent = 0; + + TArray byteCache; + + switch (messageWrapping) + { + case ESocketServerTCPSeparator::E_None: + byteCache.Append((uint8*)Convert.Get(), Convert.Length()); + break; + case ESocketServerTCPSeparator::E_ByteSeparator: + byteCache.Append((uint8*)Convert.Get(), Convert.Length()); + byteCache.Add(byteSeparator); + break; + case ESocketServerTCPSeparator::E_StringSeparator: + { + m += stringSeparator; + FTCHARToUTF8 ConvertWithSeparator(*m); + byteCache.Append((uint8*)ConvertWithSeparator.Get(), ConvertWithSeparator.Length()); + } + break; + case ESocketServerTCPSeparator::E_LengthSeparator: + if (FGenericPlatformProperties::IsLittleEndian()) { + byteCache.Add(0x00); + } + else { + byteCache.Add(0x01); + } + byteCache.SetNum(5); + int32 dataLength = Convert.Length(); + FMemory::Memcpy(byteCache.GetData() + 1, &dataLength, 4); + byteCache.Append((uint8*)Convert.Get(), Convert.Length()); + break; + } + + socket->Send(byteCache.GetData(), byteCache.Num(), sent); + } + + while (byteArrayQueue.IsEmpty() == false) { + + TArray byteCache; + byteArrayQueue.Dequeue(byteCache); + sent = 0; + + switch (messageWrapping) + { + case ESocketServerTCPSeparator::E_ByteSeparator: + byteCache.Add(byteSeparator); + break; + case ESocketServerTCPSeparator::E_StringSeparator: + { + FTCHARToUTF8 ConvertWithSeparator(*stringSeparator); + byteCache.Append((uint8*)ConvertWithSeparator.Get(), ConvertWithSeparator.Length()); + } + break; + case ESocketServerTCPSeparator::E_LengthSeparator: + byteCache.InsertZeroed(0, 5); + if (FGenericPlatformProperties::IsLittleEndian() == false) { + uint8 a = 0x01; + FMemory::Memcpy(byteCache.GetData(), &a, 1); + } + int32 dataLength = byteCache.Num() - 5; + FMemory::Memcpy(byteCache.GetData() + 1, &dataLength, 4); + break; + } + + socket->Send(byteCache.GetData(), byteCache.Num(), sent); + + } + + + } + else { + //UE_LOG(LogTemp, Error, TEXT("Connection lost")); + AsyncTask(ENamedThreads::GameThread, [sessionID, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Client, false, "Connection lost", sessionID, serverID); + }); + } + + if (run) { + pauseThread(true); + //workaround. suspend do not work on all platforms. lets sleep + while (paused && run) { + FPlatformProcess::Sleep(0.01); + } + } + } + + run = false; + + return 0; +} + +FRunnableThread* FSocketServerTCPClientSendDataThread::getThread() { + return thread; +} + +void FSocketServerTCPClientSendDataThread::setThread(FRunnableThread* threadP) { + thread = threadP; +} + +void FSocketServerTCPClientSendDataThread::stopThread() { + run = false; + if (thread != nullptr) { + pauseThread(false); + } + Stop(); +} + +bool FSocketServerTCPClientSendDataThread::isRun() { + return run; +} + +void FSocketServerTCPClientSendDataThread::setMessage(FString messageP, TArray byteArrayP) { + if (messageP.Len() > 0) + messageQueue.Enqueue(messageP); + if (byteArrayP.Num() > 0) + byteArrayQueue.Enqueue(byteArrayP); +} + +void FSocketServerTCPClientSendDataThread::sendMessage(FString messageP, TArray byteArrayP) { + if (messageP.Len() > 0) + messageQueue.Enqueue(messageP); + if (byteArrayP.Num() > 0) + byteArrayQueue.Enqueue(byteArrayP); + pauseThread(false); +} + +void FSocketServerTCPClientSendDataThread::pauseThread(bool pause) { + paused = pause; + thread->Suspend(pause); +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPFileHandlerThread.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPFileHandlerThread.cpp new file mode 100644 index 0000000..4ad043a --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPFileHandlerThread.cpp @@ -0,0 +1,515 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerTCPFileHandlerThread.h" + +FSocketServerTCPFileHandlerThread::FSocketServerTCPFileHandlerThread(USocketServerTCP* tcpServerP, FSocketServerPluginSession& sessionP) : + tcpServer(tcpServerP), + session(sessionP) { + FString threadName = "FTCPFileHandlerThread" + FGuid::NewGuid().ToString(); + thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_BelowNormal); +} + +FSocketServerTCPFileHandlerThread::~FSocketServerTCPFileHandlerThread() { + delete thread; +} + + +uint32 FSocketServerTCPFileHandlerThread::Run() { + FString message = readMessageFromClient(); + + //send file to client + if (message.StartsWith("REQUEST_FILE_FROM_SERVER_|_", ESearchCase::CaseSensitive)) { + message.RemoveFromStart("REQUEST_FILE_FROM_SERVER_|_", ESearchCase::CaseSensitive); + + FSocketServerToken tokenStruct = tcpServer->getTokenStruct(message); + + if (tokenStruct.token.IsEmpty() && tokenStruct.token.Equals(message) == false) { + triggerFileTransferOverTCPInfoEvent("Token not found.", session.sessionID, FString(), false); + } + else { + doRequestFileFromServer(tokenStruct); + } + } + else { + //get file from client + if (message.StartsWith("SEND_FILE_TO_SERVER_|_", ESearchCase::CaseSensitive)) { + doSendFileToClient(message); + } + } + + + run = false; + + + FPlatformProcess::Sleep(3); + if (session.socket != nullptr) { + session.socket->Close(); + } + + USocketServerTCP* tcpServerGlobal = tcpServer; + FString sessionID = session.serverID; + FString serverID = session.serverID; + AsyncTask(ENamedThreads::GameThread, [sessionID, serverID, tcpServerGlobal]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Client, false, "Client disconnected", sessionID, serverID); + //clean up socket in main thread because race condition + tcpServerGlobal->removeClientSession(sessionID); + }); + + return 0; +} + +void FSocketServerTCPFileHandlerThread::doRequestFileFromServer(FSocketServerToken tokenStruct){ + FString fullFilePath = tcpServer->getCleanDir(tokenStruct.directoryType, tokenStruct.fileDirectory); + + if (FPaths::FileExists(fullFilePath) == false) { + triggerFileTransferOverTCPInfoEvent("File not found,", session.sessionID, fullFilePath, false); + return; + } + + bool md5okay = false; + FString md5Server = FString(); + tcpServer->getMD5FromFile(fullFilePath, md5okay, md5Server); + + FString response = "REQUEST_FILE_FROM_SERVER_ACCEPTED_|_" + tokenStruct.token + "_|_" + md5Server + "_|_" + tcpServer->int64ToString(tcpServer->fileSize(fullFilePath)) + "_|_" + FPaths::GetCleanFilename(fullFilePath); + sendMessageToClient(response); + + FString message = readMessageFromClient(); + + TArray lines; + message.ParseIntoArray(lines, TEXT("_|_"), true); + if (lines.Num() == 3) { + if (lines[0].Equals("REQUEST_FILE_FROM_SERVER_ACCEPTED") && lines[1].Equals(tokenStruct.token) && lines[2].Len() > 0) { + int64 startPosition = FCString::Atoi64(*lines[2]); + + //download + FArchive* reader = IFileManager::Get().CreateFileReader(*fullFilePath); + if (reader == nullptr || reader->TotalSize() == 0) { + if (reader != nullptr) { + reader->Close(); + } + delete reader; + return; + } + + fileSize = reader->TotalSize(); + int64 readSize = 0; + int64 lastPosition = startPosition; + int64 bytesSentSinceLastTick = 0; + + int32 bufferSize = 1024 * 64; + int32 dataSendBySocket = 0; + + float percent = 0.f; + float mbit = 0.f; + + TArray buffer; + + int64 lastTimeTicks = FDateTime::Now().GetTicks(); + + if (bufferSize > fileSize) { + bufferSize = fileSize; + } + + if (lastPosition > 0) { + reader->Seek(lastPosition); + } + + while (run && lastPosition < fileSize) { + + if ((lastPosition + bufferSize) > fileSize) { + bufferSize = fileSize - lastPosition; + } + + //buffer.Reset(bufferSize); + buffer.Empty(); + buffer.AddUninitialized(bufferSize); + + reader->Serialize(buffer.GetData(), buffer.Num()); + lastPosition += buffer.Num(); + + //socket->Send((uint8*)((ANSICHAR*)Convert.Get()), Convert.Length(), dataSendBySocket); + session.socket->Send(buffer.GetData(), buffer.Num(), dataSendBySocket); + + //slowdown for tests + //FPlatformProcess::Sleep(0.01f); + + //fire event every second + //one second = 10000000 ticks + if (((FDateTime::Now().GetTicks()) - lastTimeTicks) >= 10000000) { + mbit = ((float)lastPosition - (float)bytesSentSinceLastTick) / 1024 / 1024 * 8; + lastTimeTicks = FDateTime::Now().GetTicks(); + bytesSentSinceLastTick = lastPosition; + + percent = ((float)lastPosition / fileSize) * 100; + triggerFileOverTCPProgress(session.sessionID, fullFilePath, percent, mbit, lastPosition, fileSize); + } + + } + + if (lastPosition == fileSize) { + + mbit = ((float)lastPosition - (float)bytesSentSinceLastTick) / 1024 / 1024 * 8; + percent = ((float)lastPosition / fileSize) * 100; + + triggerFileOverTCPProgress(session.sessionID, fullFilePath, percent, mbit, lastPosition, fileSize); + } + else { + triggerFileTransferOverTCPInfoEvent("Error while sending the file.", session.sessionID, fullFilePath, false); + } + + buffer.Empty(); + if (reader != nullptr) { + reader->Close(); + reader = nullptr; + } + + + message = readMessageFromClient(); + lines.Empty(); + message.ParseIntoArray(lines, TEXT("_|_"), true); + + + if (lines.Num() == 3) { + if (lines[0].Equals("REQUEST_FILE_FROM_SERVER_END") && lines[1].Equals(tokenStruct.token) && lines[2].Equals("OKAY")) { + triggerFileTransferOverTCPInfoEvent("File transfer successful.", session.sessionID, fullFilePath, true); + } + else { + triggerFileTransferOverTCPInfoEvent("File transfer failed.", session.sessionID, fullFilePath, false); + } + } + } + } +} + +void FSocketServerTCPFileHandlerThread::doSendFileToClient(FString message){ + + + TArray lines; + message.ParseIntoArray(lines, TEXT("_|_"), true); + + + if (lines.Num() == 5) { + if (lines[0].Equals("SEND_FILE_TO_SERVER") && lines[1].Len() > 0 && lines[2].Len() > 0 && lines[3].Len() > 0 && lines[4].Len() > 0) { + + FArchive* writer = nullptr; + int64 bytesDownloaded = 0; + FString fullFilePath = FString(); + FString md5Client = lines[2]; + FString token = lines[1]; + struct FSocketServerToken tokenStruct = tcpServer->getTokenStruct(token); + + if (tokenStruct.token.IsEmpty() && tokenStruct.token.Equals(token) == false) { + triggerFileTransferOverTCPInfoEvent("Token not found.", session.sessionID, fullFilePath, false); + run = false; + return; + } + + if (tokenStruct.deleteAfterUse) { + tcpServer->removeTokenFromStruct(token); + } + + FString downloadDir = tcpServer->getCleanDir(tokenStruct.directoryType, tokenStruct.fileDirectory); + FString fileName = lines[3]; + if (downloadDir.EndsWith("/")) { + fullFilePath = downloadDir + fileName; + } + else { + fullFilePath = downloadDir + "/" + fileName; + } + + + if (md5Client.IsEmpty()) { + triggerFileTransferOverTCPInfoEvent("MD5 string from client is missing.", session.sessionID, fullFilePath, false); + run = false; + return; + } + + + if (FPaths::DirectoryExists(downloadDir) == false) { + triggerFileTransferOverTCPInfoEvent("Directory not found.", session.sessionID, fullFilePath, false); + run = false; + return; + } + + + fileSize = FCString::Atoi64(*lines[4]); + + + if (fileSize <= 0) { + triggerFileTransferOverTCPInfoEvent("Client has reported a file size of 0 or lower.", session.sessionID, fullFilePath, false); + run = false; + return; + } + + /* if (tcpServer->hasResume() && lines[4].Equals("0") == false) { + if (FPaths::FileExists(fullFilePath)) { + lastByte = FCString::Atoi64(*lines[4]); + } + }*/ + + if (tcpServer->hasResume()) { + writer = IFileManager::Get().CreateFileWriter(*fullFilePath, EFileWrite::FILEWRITE_Append); + } + else { + writer = IFileManager::Get().CreateFileWriter(*fullFilePath); + } + + + if (!writer) { + triggerFileTransferOverTCPInfoEvent("Can't create file.", session.sessionID, fullFilePath, false); + writer->Close(); + delete writer; + run = false; + return; + } + + bytesDownloaded = writer->TotalSize(); + + + if (bytesDownloaded > fileSize) { + triggerFileTransferOverTCPInfoEvent("File on server bigger than on client. Cancel.", session.sessionID, fullFilePath, false); + run = false; + return; + } + + if (bytesDownloaded == fileSize) { + writer->Close(); + sendEndMessage(fullFilePath, token, md5Client); + run = false; + return; + } + + FString response = "SEND_FILE_TO_SERVER_ACCEPTED_|_" + token + "_|_" + FString::FromInt(bytesDownloaded); + sendMessageToClient(response); + + //send file to client + + int64 ticks1 = 0; + int64 ticks2 = 0; + int64 ticksDownload = FDateTime::Now().GetTicks(); + int64 lastByte = 0; + int32 bytesRead = 0; + uint32 dataSize = 0; + TArray recvDataArray; + + while (run && session.socket != nullptr) { + + ticks1 = FDateTime::Now().GetTicks(); + session.socket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(WaitForRead)); + ticks2 = FDateTime::Now().GetTicks(); + + bool hasData = session.socket->HasPendingData(dataSize); + + if (!hasData && ticks1 == ticks2) { + //fireConnectionEvent(false, 0, "TCP connection broken."); + run = false; + return; + } + + if (!hasData) { + run = false; + return; + } + + + if (hasData) { + recvDataArray.SetNumUninitialized(dataSize); + if (session.socket->Recv(recvDataArray.GetData(), recvDataArray.Num(), bytesRead)) { + writer->Serialize(recvDataArray.GetData(), recvDataArray.Num()); + //show progress each second + if ((ticksDownload + 10000000) <= FDateTime::Now().GetTicks()) { + writer->Flush(); + int64 bytesSendLastSecond = bytesDownloaded - lastByte; + //float speed = ((float)bytesSendLastSecond) / 125000; + float mbit = ((float)bytesSendLastSecond) / 1024 / 1024 * 8; + float sent = ((float)bytesDownloaded) / 1048576; + float left = 0; + float percent = 0; + if (fileSize > 0) { + left = ((float)(fileSize - bytesDownloaded)) / 1048576; + percent = ((float)bytesDownloaded / (float)fileSize * 100); + } + + + triggerFileOverTCPProgress(session.sessionID, fullFilePath, percent, mbit, lastByte, fileSize); + + ticksDownload = FDateTime::Now().GetTicks(); + lastByte = bytesDownloaded; + } + bytesDownloaded += bytesRead; + } + } + if (bytesDownloaded >= fileSize) { + //receive file finish + triggerFileOverTCPProgress(session.sessionID, fullFilePath, 100, 0, bytesDownloaded, fileSize); + + if (writer != nullptr) { + writer->Close(); + delete writer; + writer = nullptr; + } + + + sendEndMessage(fullFilePath, token, md5Client); + + run = false; + } + } + if (writer != nullptr) { + writer->Close(); + delete writer; + } + } + else { + triggerFileTransferOverTCPInfoEvent("File request incorrect (0).", session.sessionID, "", false); + + run = false; + } + } +} + + +void FSocketServerTCPFileHandlerThread::triggerFileOverTCPProgress(FString sessionIDP, FString filePathP, float percentP, float mbitP, int64 bytesReceivedP, int64 fileSizeP) { + AsyncTask(ENamedThreads::GameThread, [sessionIDP, filePathP, percentP, mbitP, bytesReceivedP, fileSizeP]() { + USocketServerBPLibrary::socketServerBPLibrary->onfileTransferOverTCPProgressEventDelegate.Broadcast(sessionIDP, filePathP, percentP, mbitP, bytesReceivedP, fileSizeP); + }); +} + +void FSocketServerTCPFileHandlerThread::triggerFileTransferOverTCPInfoEvent(FString messageP, FString sessionIDP, FString filePathP, bool successP) { + AsyncTask(ENamedThreads::GameThread, [messageP, sessionIDP, filePathP, successP]() { + USocketServerBPLibrary::socketServerBPLibrary->onfileTransferOverTCPInfoEventDelegate.Broadcast(messageP, sessionIDP, filePathP, successP); + }); +} + +void FSocketServerTCPFileHandlerThread::sendEndMessage(FString fullFilePathP, FString tokenP, FString md5ClientP) { + bool md5okay = false; + FString md5Server = FString(); + tcpServer->getMD5FromFile(fullFilePathP, md5okay, md5Server); + + FString response = "SEND_FILE_TO_SERVER_END_|_" + tokenP + "_|_"; + + if (md5okay && md5ClientP.Equals(md5Server)) { + triggerFileTransferOverTCPInfoEvent("File successfully received.", session.sessionID, fullFilePathP, true); + + response += "OKAY"; + } + else { + triggerFileTransferOverTCPInfoEvent("File received but MD5 does not match. Corrupted file will be deleted if resume is not disabled.", session.sessionID, fullFilePathP, false); + response += "MD5ERROR"; + if (tcpServer->hasResume() == false) { + tcpServer->deleteFile(fullFilePathP); + } + } + + sendMessageToClient(response); +} + +FString FSocketServerTCPFileHandlerThread::readMessageFromClient(){ + int64 ticks1 = 0; + int64 ticks2 = 0; + int32 lastDataLengthFromHeader = 0; + FString message = FString(); + TArray byteDataArrayCache; + TArray byteDataArray; + + uint32 dataSize = 0; + TArray lines; + while (run && session.socket != nullptr) { + + ticks1 = FDateTime::Now().GetTicks(); + session.socket->Wait(ESocketWaitConditions::WaitForRead, FTimespan::FromSeconds(WaitForRead)); + ticks2 = FDateTime::Now().GetTicks(); + + bool hasData = session.socket->HasPendingData(dataSize); + + if (!hasData && ticks1 == ticks2) { + //fireConnectionEvent(false, 0, "TCP connection broken."); + return ""; + } + + if (!hasData) { + return message; + } + + + if (hasData) { + TArray dataFromSocket; + dataFromSocket.SetNumUninitialized(dataSize); + int32 BytesRead = 0; + if (session.socket->Recv(dataFromSocket.GetData(), dataFromSocket.Num(), BytesRead)) { + if (lastDataLengthFromHeader == 0 && dataFromSocket.Num() >= 5) { + + tcpServer->readDataLength(dataFromSocket, lastDataLengthFromHeader); + + if (dataFromSocket.Num() == 5) { + dataFromSocket.Empty(); + continue; + } + + byteDataArrayCache.Append(dataFromSocket.GetData() + 5, dataFromSocket.Num() - 5); + dataFromSocket.Empty(); + } + else { + byteDataArrayCache.Append(dataFromSocket.GetData(), dataFromSocket.Num()); + } + + int32 maxLoops = 1000;//to prevent endless loop + + while (byteDataArrayCache.Num() > 0 && byteDataArrayCache.Num() >= lastDataLengthFromHeader && maxLoops > 0) { + maxLoops--; + + byteDataArray.Append(byteDataArrayCache.GetData(), lastDataLengthFromHeader); + byteDataArrayCache.RemoveAt(0, lastDataLengthFromHeader, true); + + if (byteDataArrayCache.Num() == 0) { + lastDataLengthFromHeader = 0; + break; + } + + if (byteDataArrayCache.Num() > 5) { + tcpServer->readDataLength(byteDataArrayCache, lastDataLengthFromHeader); + byteDataArrayCache.RemoveAt(0, 5, true); + } + } + + byteDataArray.Add(0x00);// null-terminator + message = FString(UTF8_TO_TCHAR((char*)byteDataArray.GetData())); + byteDataArray.Empty(); + if (message.IsEmpty() == false) { + message = tcpServer->decryptMessage(message); + } + return message; + } + } + } + if (message.IsEmpty() == false) { + message = tcpServer->decryptMessage(message); + } + return message; +} + +void FSocketServerTCPFileHandlerThread::sendMessageToClient(FString message){ + + message = tcpServer->encryptMessage(message); + + TArray byteCache; + FTCHARToUTF8 Convert(*message); + int32 sent = 0; + if (FGenericPlatformProperties::IsLittleEndian()) { + byteCache.Add(0x00); + } + else { + byteCache.Add(0x01); + } + byteCache.SetNum(5); + int32 dataLength = Convert.Length(); + FMemory::Memcpy(byteCache.GetData() + 1, &dataLength, 4); + byteCache.Append((uint8*)Convert.Get(), Convert.Length()); + + session.socket->Send(byteCache.GetData(), byteCache.Num(), sent); + byteCache.Empty(); +} + +void FSocketServerTCPFileHandlerThread::stopThread() { + run = false; +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPThread.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPThread.cpp new file mode 100644 index 0000000..38c2089 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerTCPThread.cpp @@ -0,0 +1,126 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerTCPThread.h" + + +FSocketServerTCPThread::FSocketServerTCPThread(USocketServerTCP* tcpServerP, EReceiveFilterServer receiveFilterP, bool& runP) : + tcpServer(tcpServerP), + receiveFilter(receiveFilterP), + run(runP) { + FString threadName = "FSocketServerTCPThread_" + FGuid::NewGuid().ToString(); + thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_BelowNormal); +} + +FSocketServerTCPThread::~FSocketServerTCPThread(){ + delete thread; +} + +uint32 FSocketServerTCPThread::Run() { + FIPandPortStruct ipAndPortStruct = tcpServer->getServerIpAndPortStruct(); + int32 port = tcpServer->getPort(); + FString serverID = tcpServer->getServerID(); + + bool createServer = true; + FSocket* listenerSocket = nullptr; + ISocketSubsystem* socketSubSystem = USocketServerBPLibrary::getSocketSubSystem(); + if (socketSubSystem != nullptr) { + + TSharedRef internetAddr = socketSubSystem->CreateInternetAddr(); + internetAddr->SetIp(*ipAndPortStruct.ip, ipAndPortStruct.success); + internetAddr->SetPort(ipAndPortStruct.port); + + if (ipAndPortStruct.success) { + listenerSocket = socketSubSystem->CreateSocket(NAME_Stream, *FString("USocketServerBPLibraryListenerSocket"), internetAddr->GetProtocolType()); + } + + if (listenerSocket != nullptr) { + listenerSocket->SetLinger(false, 0); + + if (!listenerSocket->Bind(*internetAddr)) { + if (listenerSocket != nullptr) { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + UE_LOG(LogTemp, Error, TEXT("(211) TCP Server not started. Can't bind %s:%i. Please check IP,Port or your firewall."), *ipAndPortStruct.ip, port); + } + createServer = false; + } + + + if (createServer && !listenerSocket->Listen(8)) { + if (listenerSocket != nullptr) { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + UE_LOG(LogTemp, Error, TEXT("(212) TCP Server not started. Can't listen on %s:%i. Please check IP,Port or your firewall."), *ipAndPortStruct.ip, port); + } + createServer = false; + } + } + + } + + + + if (!createServer || listenerSocket == nullptr) { + UE_LOG(LogTemp, Error, TEXT("(210) TCP Server not started on %s:%i. Please check IP,Port or your firewall."), *ipAndPortStruct.ip, port); + AsyncTask(ENamedThreads::GameThread, [serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Server, false, "TCP Server not started. Please check IP,Port or your firewall.", TEXT(""), serverID); + USocketServerBPLibrary::getSocketServerTarget()->stopTCPServer(serverID); + }); + } + else { + //switch to gamethread + AsyncTask(ENamedThreads::GameThread, [serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Server, true, "TCP Server started.", TEXT(""), serverID); + }); + + while (run) { + bool pending; + listenerSocket->WaitForPendingConnection(pending, FTimespan::FromSeconds(1)); + if (pending) { + //UE_LOG(LogTemp, Display, TEXT("TCP Client: Pending connection")); + + FSocketServerPluginSession session = FSocketServerPluginSession(); + session.sessionID = FGuid::NewGuid().ToString(); + session.serverID = serverID; + + TSharedPtr remoteAddress = USocketServerBPLibrary::getSocketSubSystem()->CreateInternetAddr(); + session.socket = listenerSocket->Accept(*remoteAddress, session.sessionID); + + /*FPlatformProcess::Sleep(0.1); + session.socket->Close(); + USocketServerBPLibrary::getSocketSubSystem()->DestroySocket(session.socket);*/ + + session.ip = remoteAddress->ToString(false); + session.port = remoteAddress->GetPort(); + session.protocol = EServerSocketConnectionProtocol::E_TCP; + tcpServer->initTCPClientThreads(session, receiveFilter); + tcpServer->addClientSession(session); + } + } + } + + ////wait for client disconnects + //FPlatformProcess::Sleep(2); + + if (listenerSocket && listenerSocket != nullptr) { + listenerSocket->Close(); + if (socketSubSystem != nullptr) + socketSubSystem->DestroySocket(listenerSocket); + } + + listenerSocket = nullptr; + + //switch to gamethread + AsyncTask(ENamedThreads::GameThread, [serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerConnectionEventDelegate.Broadcast(EServerSocketConnectionEventType::E_Server, false, "TCP Server stopped. Depending on the operating system it can take some time until the port is free again.", TEXT(""), serverID); + }); + return 0; +} + +void FSocketServerTCPThread::stopThread() { + run = false; +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDP.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDP.cpp new file mode 100644 index 0000000..f762ed1 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDP.cpp @@ -0,0 +1,367 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerUDP.h" + + +USocketServerUDP::USocketServerUDP(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { + this->AddToRoot(); +} + + +void USocketServerUDP::startUDPServer(FIPandPortStruct ipStructP,FString IPP, int32 portP, bool multicastP, + EReceiveFilterServer receiveFilterP, FString serverIDP, int32 maxPacketSizeP) { + + ipAndPortStruct = ipStructP; + serverIP = IPP; + serverPort = portP; + receiveFilter = receiveFilterP; + serverID = serverIDP; + maxPacketSize = maxPacketSizeP; + if (maxPacketSize < 1 || maxPacketSize > 65507) + maxPacketSize = 65507; + serverThread = new FSocketServerUDPThread(this, multicastP); + sendThread = new FSocketServerUDPClientSendDataThread(this); + +} + +void USocketServerUDP::stopUDPServer() { + + TArray toRemoveSessionKeys; + for (auto& element : getClientSessions()) { + toRemoveSessionKeys.Add(element.Key); + } + for (int32 i = 0; i < toRemoveSessionKeys.Num(); i++) { + removeClientSession(toRemoveSessionKeys[i]); + } + toRemoveSessionKeys.Empty(); + + FSocketServerPluginSession session; + + if (sendThread != nullptr) { + sendThread->stopThread(); + session.udpSendThread = sendThread; + } + + if (serverThread != nullptr) { + serverThread->stopThread(); + session.udpServerThread = serverThread; + } + + if (socketReceiver != nullptr) { + socketReceiver->Stop(); + socketReceiver->Exit(); + session.udpSocketReceiver = socketReceiver; + } + + if (socket && socket != nullptr) { + socket->Close(); + } + USocketServerBPLibrary::socketServerBPLibrary->cleanConnection(session); +} + +void USocketServerUDP::sendUDPMessage(TArray clientSessionIDs, FString message, TArray byteArray, bool asynchronous, ESocketServerUDPSocketType socketType) { + for (auto& sessionID : clientSessionIDs) { + FSocketServerPluginSession* sessionPointer = clientSessions.Find(sessionID); + if (sessionPointer != nullptr) { + FSocketServerPluginSession& session = *sessionPointer; + if (session.protocol == EServerSocketConnectionProtocol::E_UDP) { + + + FSocket* socketUDP = socket; + if (socketUDP == nullptr || socketType == ESocketServerUDPSocketType::E_SSS_CLIENT) + socketUDP = session.socket; + + if (asynchronous) { + sendThread->sendMessage(session.ip, session.port, message, byteArray, socketUDP); + return; + } + + int32 sent = 0; + + TSharedRef addr = USocketServerBPLibrary::getSocketSubSystem()->CreateInternetAddr(); + bool bIsValid; + addr->SetIp(*session.ip, bIsValid); + addr->SetPort(session.port); + if (bIsValid) { + if (byteArray.Num() > 0) { + sendBytes(socketUDP, byteArray, sent, addr); + } + + if (message.Len() > 0) { + FTCHARToUTF8 Convert(*message); + byteArray.Append((uint8*)Convert.Get(), Convert.Length()); + sendBytes(socketUDP, byteArray, sent, addr); + } + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Can't send data. Wrong adress.")); + } + + } + } + else { + FString serverIDGlobal = serverID; + AsyncTask(ENamedThreads::GameThread, [sessionID, serverIDGlobal]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(false, "Can't send message. SessionID not found on this server: " + sessionID, serverIDGlobal); + }); + } + } +} + + +void USocketServerUDP::sendUDPMessageToClient(FString clientSessionID, FString message, TArray byteArray, bool asynchronous, ESocketServerUDPSocketType socketType) { + + FSocketServerPluginSession* sessionPointer = clientSessions.Find(clientSessionID); + if (sessionPointer != nullptr) { + FSocketServerPluginSession& session = *sessionPointer; + if (session.protocol == EServerSocketConnectionProtocol::E_UDP) { + + FSocket* socketUDP = socket; + if (socketUDP == nullptr || socketType == ESocketServerUDPSocketType::E_SSS_CLIENT) + socketUDP = session.socket; + + if (asynchronous) { + sendThread->sendMessage(session.ip, session.port, message, byteArray, socketUDP); + return; + } + + int32 sent = 0; + + TSharedRef addr = USocketServerBPLibrary::getSocketSubSystem()->CreateInternetAddr(); + bool bIsValid; + addr->SetIp(*session.ip, bIsValid); + addr->SetPort(session.port); + if (bIsValid) { + if (byteArray.Num() > 0) { + sendBytes(socketUDP, byteArray, sent, addr); + } + + if (message.Len() > 0) { + FTCHARToUTF8 Convert(*message); + byteArray.Append((uint8*)Convert.Get(), Convert.Length()); + sendBytes(socketUDP, byteArray, sent, addr); + } + + + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Can't send data. Wrong adress.")); + } + + } + } + else { + FString serverIDGlobal = serverID; + AsyncTask(ENamedThreads::GameThread, [clientSessionID, serverIDGlobal]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(false, "Can't send message. SessionID not found on this server: " + clientSessionID, serverIDGlobal); + }); + } + +} + + +void USocketServerUDP::sendUDPMessageTo(FString ip, int32 port, FString message, TArray byteArray, bool asynchronous) { + int32 sent = 0; + + FSocket* socketUDP = socket; + if (socketUDP == nullptr) { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Can't send data. Connection broken. Restart your UDP server %s"),*serverID); + return; + } + + if (asynchronous) { + sendThread->sendMessage(ip,port,message, byteArray,socketUDP); + return; + } + + TSharedRef addr = USocketServerBPLibrary::getSocketSubSystem()->CreateInternetAddr(); + bool bIsValid; + addr->SetIp(*ip, bIsValid); + addr->SetPort(port); + if (bIsValid) { + + if (byteArray.Num() > 0) { + sendBytes(socketUDP, byteArray, sent, addr); + } + + if (message.Len() > 0) { + FTCHARToUTF8 Convert(*message); + byteArray.Append((uint8*)Convert.Get(), Convert.Length()); + sendBytes(socketUDP, byteArray, sent, addr); + } + + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Can't send data. Wrong adress.")); + } +} + + + +void USocketServerUDP::UDPReceiverSocketServerPlugin(FArrayReaderPtr& ArrayReaderPtr, TSharedRef remoteAddress) { + + FString sessionID = remoteAddress.Get().ToString(true); + FSocketServerPluginSession* sessionPointer = clientSessions.Find(sessionID); + if (sessionPointer == nullptr) { + + //FString socketName; + //FSocket* receiverSocket = FUdpSocketBuilder(*socketName); + ISocketSubsystem* socketSubsystem = USocketServerBPLibrary::getSocketSubSystem(); + FSocket* receiverSocket = socketSubsystem->CreateSocket(NAME_DGram, *sessionID, remoteAddress->GetProtocolType()); + + + //create and save session + FSocketServerPluginSession session; + session.sessionID = sessionID; + session.serverID = serverID; + session.ip = remoteAddress.Get().ToString(false); + session.port = remoteAddress.Get().GetPort(); + session.socket = receiverSocket; + session.protocol = EServerSocketConnectionProtocol::E_UDP; + addClientSession(session); + } + TArray byteArray; + if (receiveFilter == EReceiveFilterServer::E_SAB || receiveFilter == EReceiveFilterServer::E_B) { + byteArray.Append(ArrayReaderPtr->GetData(), ArrayReaderPtr->Num()); + } + + FString recvMessage; + if (receiveFilter == EReceiveFilterServer::E_SAB || receiveFilter == EReceiveFilterServer::E_S) { + ArrayReaderPtr->Add(0x00);// null-terminator + char* Data = (char*)ArrayReaderPtr->GetData(); + recvMessage = FString(UTF8_TO_TCHAR(Data)); + } + + FString serverIDGlobal = serverID; + //switch to gamethread + AsyncTask(ENamedThreads::GameThread, [recvMessage, sessionID, byteArray, serverIDGlobal]() { + USocketServerBPLibrary::socketServerBPLibrary->onserverReceiveUDPMessageEventDelegate.Broadcast(sessionID, recvMessage, byteArray, serverIDGlobal); + if (USocketServerBPLibrary::socketServerBPLibrary->getResiteredClientEvent(sessionID) != nullptr) { + USocketServerBPLibrary::socketServerBPLibrary->getResiteredClientEvent(sessionID)->onregisteredEventDelegate.Broadcast(recvMessage, byteArray); + } + }); + byteArray.Empty(); + recvMessage.Empty(); +} + +//do not work with ipv6 +//void USocketServerPluginUDPServer::UDPReceiver(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt) { +// +// FString sessionID = EndPt.ToString(); +// FClientSocketSession* sessionPointer = clientSessions.Find(sessionID); +// if (sessionPointer == nullptr) { +// TSharedRef remoteAddress = EndPt.ToInternetAddr(); +// +// FString socketName; +// FSocket* receiverSocket = FUdpSocketBuilder(*socketName); +// +// //create and save session +// FClientSocketSession session; +// session.sessionID = sessionID; +// session.serverID = serverID; +// session.ip = remoteAddress.Get().ToString(false); +// session.port = remoteAddress.Get().GetPort(); +// session.socket = receiverSocket; +// session.protocol = EServerSocketConnectionProtocol::E_UDP; +// addClientSession(session); +// } +// TArray byteArray; +// if (receiveFilter == EReceiveFilterServer::E_SAB || receiveFilter == EReceiveFilterServer::E_B) { +// byteArray.Append(ArrayReaderPtr->GetData(), ArrayReaderPtr->Num()); +// } +// +// FString recvMessage; +// if (receiveFilter == EReceiveFilterServer::E_SAB || receiveFilter == EReceiveFilterServer::E_S) { +// ArrayReaderPtr->Add(0x00);// null-terminator +// char* Data = (char*)ArrayReaderPtr->GetData(); +// recvMessage = FString(UTF8_TO_TCHAR(Data)); +// } +// +// FString serverIDGlobal = serverID; +// //switch to gamethread +// AsyncTask(ENamedThreads::GameThread, [recvMessage, sessionID, byteArray, serverIDGlobal]() { +// USocketServerBPLibrary::socketServerBPLibrary->onserverReceiveUDPMessageEventDelegate.Broadcast(sessionID, recvMessage, byteArray, serverIDGlobal); +// if (USocketServerBPLibrary::socketServerBPLibrary->getResiteredClientEvent(sessionID) != nullptr) { +// USocketServerBPLibrary::socketServerBPLibrary->getResiteredClientEvent(sessionID)->onregisteredEventDelegate.Broadcast(recvMessage, byteArray); +// } +// }); +// byteArray.Empty(); +// recvMessage.Empty(); +//} + + +FIPandPortStruct USocketServerUDP::getServerIpAndPortStruct() { + return ipAndPortStruct; +} + +FString USocketServerUDP::getIP(){ + return serverIP; +} + +int32 USocketServerUDP::getPort() { + return serverPort; +} + +void USocketServerUDP::setSocketReceiver(FUdpSocketReceiver* socketReceiverP, FSocket* socketP) { + socketReceiver = socketReceiverP; + socket = socketP; +} + +FUdpSocketReceiver* USocketServerUDP::getSocketReceiver() { + return socketReceiver; +} + +FSocket* USocketServerUDP::getSocket() { + return socket; +} + +FString USocketServerUDP::getServerID(){ + return serverID; +} + +void USocketServerUDP::addClientSession(FSocketServerPluginSession& session){ + if (session.sessionID.IsEmpty() == false) { + //UE_LOG(LogTemp, Warning, TEXT("ADD Session:%s"), *session.sessionID); + clientSessions.Add(session.sessionID, session); + } +} + +FSocketServerPluginSession* USocketServerUDP::getClientSession(FString key){ + return clientSessions.Find(key); +} + +void USocketServerUDP::removeClientSession(FString key){ + if (clientSessions.Remove(key)) { + //UE_LOG(LogTemp, Warning, TEXT("Remove Session:%s"), *key); + USocketServerBPLibrary::socketServerBPLibrary->unregisterClientEvent(key); + } +} + +TMap USocketServerUDP::getClientSessions(){ + return clientSessions; +} + +void USocketServerUDP::sendBytes(FSocket*& socketP, TArray& byteArray, int32& sent, TSharedRef& addr){ + if (byteArray.Num() > maxPacketSize) { + TArray byteArrayTemp; + for (int32 i = 0; i < byteArray.Num(); i++) { + byteArrayTemp.Add(byteArray[i]); + if (byteArrayTemp.Num() == maxPacketSize) { + sent = 0; + socketP->SendTo(byteArrayTemp.GetData(), byteArrayTemp.Num(), sent, *addr); + byteArrayTemp.Empty(); + } + } + if (byteArrayTemp.Num() > 0) { + sent = 0; + socketP->SendTo(byteArrayTemp.GetData(), byteArrayTemp.Num(), sent, *addr); + byteArrayTemp.Empty(); + } + } + else { + sent = 0; + socketP->SendTo(byteArray.GetData(), byteArray.Num(), sent, *addr); + } + + byteArray.Empty(); +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDPClientSendDataThread.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDPClientSendDataThread.cpp new file mode 100644 index 0000000..2fc7af3 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDPClientSendDataThread.cpp @@ -0,0 +1,108 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerUDPClientSendDataThread.h" + +FSocketServerUDPClientSendDataThread::FSocketServerUDPClientSendDataThread(USocketServerUDP* udpServerP) : + udpServer(udpServerP) { + FString threadName = "FUDPClientSendDataToServerThread_" + FGuid::NewGuid().ToString(); + thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_BelowNormal); +} + +FSocketServerUDPClientSendDataThread::~FSocketServerUDPClientSendDataThread() { + delete thread; +} + + +uint32 FSocketServerUDPClientSendDataThread::Run() { + //FString serverID = tcpServer->getServerID(); + //socket = session.socket; + //FString sessionID = session.sessionID; + + while (run && thread == nullptr) { + FPlatformProcess::Sleep(0.1); + } + + TSharedRef addr = USocketServerBPLibrary::getSocketSubSystem()->CreateInternetAddr(); + int32 sent = 0; + bool bIsValid = false; + + while (run) { + + while (messageQueue.IsEmpty() == false) { + FSendUDPMessageStruct messageStuct; + messageQueue.Dequeue(messageStuct); + + + addr->SetIp(*messageStuct.ip, bIsValid); + addr->SetPort(messageStuct.port); + + if (bIsValid && messageStuct.socketUDP != nullptr) { + + if (messageStuct.bytes.Num() > 0) { + udpServer->sendBytes(messageStuct.socketUDP, messageStuct.bytes, sent, addr); + } + + if (messageStuct.message.Len() > 0) { + FTCHARToUTF8 Convert(*messageStuct.message); + messageStuct.bytes.Append((uint8*)Convert.Get(), Convert.Length()); + udpServer->sendBytes(messageStuct.socketUDP, messageStuct.bytes, sent, addr); + } + + } + else { + UE_LOG(LogTemp, Warning, TEXT("SimpleSocketServer Plugin: Can't send data. Wrong adress.")); + } + + } + + + if (run) { + pauseThread(true); + //workaround. suspend do not work on all platforms. lets sleep + while (paused && run) { + FPlatformProcess::Sleep(0.01); + } + } + } + + run = false; + + return 0; +} + +FRunnableThread* FSocketServerUDPClientSendDataThread::getThread() { + return thread; +} + +void FSocketServerUDPClientSendDataThread::setThread(FRunnableThread* threadP) { + thread = threadP; +} + +void FSocketServerUDPClientSendDataThread::stopThread() { + run = false; + if (thread != nullptr) { + pauseThread(false); + } +} + +bool FSocketServerUDPClientSendDataThread::isRun() { + return run; +} + + +void FSocketServerUDPClientSendDataThread::sendMessage(FString ip, int32 port, FString message, TArray bytes, FSocket* socketUDP) { + FSendUDPMessageStruct messageStuct; + messageStuct.ip = ip; + messageStuct.port = port; + messageStuct.message = message; + messageStuct.bytes = bytes; + messageStuct.socketUDP = socketUDP; + messageQueue.Enqueue(messageStuct); + pauseThread(false); +} + + +void FSocketServerUDPClientSendDataThread::pauseThread(bool pause) { + paused = pause; + thread->Suspend(pause); +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDPThread.cpp b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDPThread.cpp new file mode 100644 index 0000000..1f13bd6 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Private/SocketServerUDPThread.cpp @@ -0,0 +1,193 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#include "SocketServerUDPThread.h" + +FSocketServerUDPThread::FSocketServerUDPThread(USocketServerUDP* udpServerP, bool multicastP) : + udpServer(udpServerP), + multicast(multicastP) { + FString threadName = "FServerUDPThread_" + FGuid::NewGuid().ToString(); + thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_BelowNormal); +} + +FSocketServerUDPThread::~FSocketServerUDPThread() { + delete thread; +} + +uint32 FSocketServerUDPThread::Run() { + + FIPandPortStruct ipAndPortStruct = udpServer->getServerIpAndPortStruct(); + FSocket* listenerSocket = udpServer->getSocket(); + FUdpSocketReceiver* udpSocketReceiver = nullptr; + + + FString ip = ipAndPortStruct.ip; + FString adress = ip + ":" + FString::FromInt(udpServer->getPort()); + FString serverID = udpServer->getServerID(); + + // create the socket + FString socketName; + ISocketSubsystem* socketSubsystem = USocketServerBPLibrary::getSocketSubSystem(); + + + if (multicast) { + TSharedPtr addr = socketSubsystem->CreateInternetAddr(); + addr->SetAnyAddress(); + addr->SetPort(udpServer->getPort()); + + + listenerSocket = socketSubsystem->CreateSocket(NAME_DGram, *socketName, addr->GetProtocolType()); + if (listenerSocket == nullptr || listenerSocket == NULL) { + const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE); + AsyncTask(ENamedThreads::GameThread, [adress, SocketErr, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to open UDP Server: " + adress + " | " + SocketErr, serverID); + }); + thread = nullptr; + return 0; + } + + + if (!listenerSocket->Bind(*addr)) { + UE_LOG(LogTemp, Error, TEXT("Unable to open UDP Server")); + const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE); + AsyncTask(ENamedThreads::GameThread, [adress, SocketErr, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to open UDP Server: " + adress + " | " + SocketErr, serverID); + }); + thread = nullptr; + return 0; + } + + if (!listenerSocket->SetBroadcast(true)) { + UE_LOG(LogTemp, Error, TEXT("Unable to set Broadcast")); + const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE); + AsyncTask(ENamedThreads::GameThread, [adress, SocketErr, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to set Broadcast: " + adress + " | " + SocketErr, serverID); + }); + thread = nullptr; + return 0; + } + + if (!listenerSocket->SetMulticastLoopback(true)) { + UE_LOG(LogTemp, Error, TEXT("Unable to set Multicast Loopback")); + const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE); + AsyncTask(ENamedThreads::GameThread, [adress, SocketErr, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to set Multicast Loopback: " + adress + " | " + SocketErr, serverID); + }); + thread = nullptr; + return 0; + } + bool validIP = true; + addr->SetIp(*ip, validIP); + + if (!validIP) { + UE_LOG(LogTemp, Error, TEXT("SocketServer UDP. Can't set ip")); + AsyncTask(ENamedThreads::GameThread, [adress, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to open UDP Server: " + adress + " | Can't set ip.", serverID); + }); + thread = nullptr; + return 0; + } + + + if (!listenerSocket->JoinMulticastGroup(*addr)) { + UE_LOG(LogTemp, Error, TEXT("Unable to join Multicast Group")); + const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE); + AsyncTask(ENamedThreads::GameThread, [adress, SocketErr, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to join Multicast Group: " + adress + " | " + SocketErr, serverID); + }); + thread = nullptr; + return 0; + } + } + else { + + TSharedPtr addr = socketSubsystem->CreateInternetAddr(); + bool validIP = true; + addr->SetPort(udpServer->getPort()); + addr->SetIp(*ip, validIP); + + + listenerSocket = socketSubsystem->CreateSocket(NAME_DGram, *socketName, addr->GetProtocolType()); + if (listenerSocket == nullptr || listenerSocket == NULL) { + const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE); + AsyncTask(ENamedThreads::GameThread, [adress, SocketErr, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to open UDP Server: " + adress + " | " + SocketErr, serverID); + }); + thread = nullptr; + return 0; + } + + if (!validIP) { + UE_LOG(LogTemp, Error, TEXT("SocketServer UDP. Can't set ip")); + AsyncTask(ENamedThreads::GameThread, [adress, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to open UDP Server: " + adress + " | Can't set ip.", serverID); + }); + thread = nullptr; + return 0; + } + + listenerSocket->SetReuseAddr(); + listenerSocket->SetNonBlocking(); + if (!listenerSocket->Bind(*addr)) { + UE_LOG(LogTemp, Error, TEXT("Unable to open UDP Server")); + const TCHAR* SocketErr = socketSubsystem->GetSocketError(SE_GET_LAST_ERROR_CODE); + AsyncTask(ENamedThreads::GameThread, [adress, SocketErr, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "Unable to open UDP Server: " + adress + " | " + SocketErr, serverID); + }); + thread = nullptr; + return 0; + } + } + + //do not work with ipv6 + FTimespan ThreadWaitTime = FTimespan::FromMilliseconds(100); + //FString threadName = "SocketServerBPLibUDPReceiverThread_" + FString::FromInt(FDateTime::Now().GetTicks()); + + //udpSocketReceiver = new FUdpSocketReceiver(listenerSocket, ThreadWaitTime, *threadName); + //udpSocketReceiver->OnDataReceived().BindUObject(udpServer, &USocketServerPluginUDPServer::UDPReceiver); + //udpSocketReceiver->Start(); + udpServer->setSocketReceiver(udpSocketReceiver, listenerSocket); + + //udpServer->initUDPClientThreads(listenerSocket); + + AsyncTask(ENamedThreads::GameThread, [adress, serverID]() { + USocketServerBPLibrary::socketServerBPLibrary->onsocketServerUDPConnectionEventDelegate.Broadcast(true, "UDP Server started: " + adress, serverID); + }); + + + //copy of FUdpSocketReceiver.h to get IPv6 working + while (run) { + if (!listenerSocket->Wait(ESocketWaitConditions::WaitForRead, ThreadWaitTime)) { + continue; + } + + TSharedRef Sender = socketSubsystem->CreateInternetAddr(); + uint32 Size; + + while (listenerSocket->HasPendingData(Size)) { + FArrayReaderPtr Reader = MakeShared(true); + Reader->SetNumUninitialized(FMath::Min(Size, 65507u)); + + int32 Read = 0; + + if (listenerSocket->RecvFrom(Reader->GetData(), Reader->Num(), Read, *Sender)) + { + Reader->RemoveAt(Read, Reader->Num() - Read, false); + udpServer->UDPReceiverSocketServerPlugin(Reader, Sender); + // UE_LOG(LogTemp, Error, TEXT("%s_%s"), *Sender.Get().ToString(true), *Sender->GetProtocolType().ToString()); + + } + } + } + + if (listenerSocket != nullptr) { + listenerSocket->Close(); + socketSubsystem->DestroySocket(listenerSocket); + listenerSocket = nullptr; + } + + return 0; +} + +void FSocketServerUDPThread::stopThread() { + run = false; +} \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/DNSClientSocketServer.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/DNSClientSocketServer.h new file mode 100644 index 0000000..3134e1f --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/DNSClientSocketServer.h @@ -0,0 +1,36 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" +#include "SocketServerBPLibrary.h" +#include "DNSClientSocketServer.generated.h" + +UCLASS() +class SOCKETSERVER_API UDNSClientSocketServer : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FresolveDomainEventDelegate, FString, IP); + UFUNCTION() + void resolveDomainEventDelegate(const FString IP); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|Events|ResolveDomain") + FresolveDomainEventDelegate onresolveDomainEventDelegate; + + void resolveDomain(ISocketSubsystem * socketSubSystem, FString domain, bool useDNSCache = true, FString dnsIP = FString("8.8.8.8")); + + void UDPReceiver(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt); + + FSocket* socket = nullptr; + + bool isResloving(); + FString getIP(); + +private: + bool resolving; + FString ip; + FString domain; + TMap dnsCache; +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/EventBean.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/EventBean.h new file mode 100644 index 0000000..f4f090c --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/EventBean.h @@ -0,0 +1,24 @@ +// Copyright 2018-2020 David Romanski (Socke). All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "EventBean.generated.h" + + + +UCLASS(Blueprintable, BlueprintType) +class UEventBean : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FregisteredEventDelegate, const FString, message, const TArray&, byteArray); + UFUNCTION() + void registeredEventDelegate(const FString message, const TArray& byteArray); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|Register|Events") + FregisteredEventDelegate onregisteredEventDelegate; + +private: + +}; diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/FileFunctionsSocketServer.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/FileFunctionsSocketServer.h new file mode 100644 index 0000000..b959153 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/FileFunctionsSocketServer.h @@ -0,0 +1,243 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. +#pragma once + +#include "SocketServer.h" +#include "SocketServerBPLibrary.h" + +#include "FileFunctionsSocketServer.generated.h" + +class FReadFileInPartsSocketServerThread; + + +UCLASS(Blueprintable, BlueprintType) +class SOCKETSERVER_API UFileFunctionsSocketServer : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + UFUNCTION() + static UFileFunctionsSocketServer* getFileFunctionsSocketServerTarget(); + static UFileFunctionsSocketServer* fileFunctionsSocketServer; + + + static FString getCleanDirectory(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void writeBytesToFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, TArray bytes, bool& success); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void addBytesToFileAndCloseIt(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, TArray bytes, bool& success); + //UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + // static void splittFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, int32 parts, bool& success); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static TArray readBytesFromFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool& success); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void readStringFromFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool& success, FString& data); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void writeStringToFile(EFileFunctionsSocketServerDirectoryType directoryType, FString data, FString filePath, EFileFunctionsSocketServerEncodingOptions fileEncoding, bool& success); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void getMD5FromFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool& success, FString& MD5); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void getMD5FromFileAbsolutePath(FString filePath, bool& success, FString& MD5); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void stringToBase64String(FString string, FString& base64String); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void base64StringToString(FString& string, FString base64String); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void bytesToBase64String(TArray bytes, FString& base64String); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static TArray base64StringToBytes(FString base64String, bool& success); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void fileToBase64String(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool& success, FString& base64String, FString& fileName); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool fileExists(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool fileExistsAbsolutePath(FString filePath); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool directoryExists(EFileFunctionsSocketServerDirectoryType directoryType, FString path); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static int64 fileSize(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static int64 fileSizeAbsolutePath(FString filePath); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool deleteFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool deleteFileAbsolutePath(FString filePath); + /** Delete a directory and return true if the directory was deleted or otherwise does not exist. **/ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool deleteDirectory(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + /** Return true if the file is read only. **/ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool isReadOnly(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + /** Attempt to move a file. Return true if successful. Will not overwrite existing files. **/ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool moveFile(EFileFunctionsSocketServerDirectoryType directoryTypeTo, FString filePathTo, EFileFunctionsSocketServerDirectoryType directoryTypeFrom, FString filePathFrom); + /** Attempt to change the read only status of a file. Return true if successful. **/ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool setReadOnly(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, bool bNewReadOnlyValue); + /** Return the modification time of a file. Returns FDateTime::MinValue() on failure **/ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static FDateTime getTimeStamp(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + /** Sets the modification time of a file **/ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void setTimeStamp(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, FDateTime DateTime); + /** Return the last access time of a file. Returns FDateTime::MinValue() on failure **/ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static FDateTime getAccessTimeStamp(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + /** For case insensitive filesystems, returns the full path of the file with the same case as in the filesystem */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static FString getFilenameOnDisk(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + /** Create a directory and return true if the directory was created or already existed. **/ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static bool createDirectory(EFileFunctionsSocketServerDirectoryType directoryType, FString path); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void getAllFilesFromDirectory(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, int32& count, TArray& files, TArray& filePaths, FString fileType = "*.*"); + + + + //AES + /** + * Encrypts a string with AES in 256bit and returns the encrypted string as Base64 string. + * @param message The string to be encrypted + * @param keyIn256Bit The key must be a string with 32 characters. Please use ASCII characters only! + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|AES") + static FString encryptMessageWithAES(FString message, FString keyIn256Bit); + + /** + * Decrypts a Base64 string that has been encrypted in AES with 256bit and returns the string. + * @param message The string to be decrypted + * @param keyIn256Bit The key must be a string with 32 characters. Please use ASCII characters only! + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|AES") + static FString decryptMessageWithAES(FString encryptedBase64Message, FString keyIn256Bit); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|String") + static FString int64ToString(int64 num); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static struct FFileFunctionsSocketServerOpenFile openFile(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static int64 addBytesToFile(struct FFileFunctionsSocketServerOpenFile openFile, TArraybytes); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void closeFile(struct FFileFunctionsSocketServerOpenFile openFile); + + /** + * With this function you can read a file piece by piece. This reduces the RAM consumption to almost zero and files can be read in infinite size. + *@param bufferSize In bytes. This is the size of the file pieces that are being read. + *@param delayBetweenReadsInSeconds Specified in seconds. The higher the value, the slower the file is read (0.0001 minimum). When sending data over the network/internet, please make sure not to send data too fast. SSDs can read data much faster than you can send it over a network. This means that the data ends up in some buffers (RAM) and can also cause them to overflow. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File", meta = (AdvancedDisplay = 2)) + static void readBytesFromFileInPartsAsync(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, int32 bufferSize = 65536, float delayBetweenReadsInSeconds = 0.01f); + void readBytesFromFileInPartsAsyncInternal(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath, int32 bufferSize = 65536, float delayBetweenReadsInSeconds = 0.01f); + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|File") + static void cancelReadBytesFromFileInParts(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + void cancelReadBytesFromFileInPartsInternal(EFileFunctionsSocketServerDirectoryType directoryType, FString filePath); + void cleanReadBytesFromFileInParts(FString cleanDir); + + TMap readFileInPartsThreads; + +private: + static TArray FStringToByteArray(FString s); + +}; + +/* asynchronous Thread*/ +class SOCKETSERVER_API FReadFileInPartsSocketServerThread : public FRunnable { + +public: + + FReadFileInPartsSocketServerThread(FString cleanDirP, int32 bufferSizeP, float delayBetweenReadsInSecondsP) : + cleanDir(cleanDirP), + bufferSize(bufferSizeP), + delayBetweenReadsInSeconds(delayBetweenReadsInSecondsP) + { + FString threadName = "FReadFileInPartsSocketServerThread" + FGuid::NewGuid().ToString(); + thread = FRunnableThread::Create(this, *threadName, 0, EThreadPriority::TPri_Normal); + } + + virtual uint32 Run() override { + + FArchive* reader = IFileManager::Get().CreateFileReader(*cleanDir); + if (reader == nullptr || reader->TotalSize() == 0) { + AsyncTask(ENamedThreads::GameThread, []() { + USocketServerBPLibrary::getSocketServerTarget()->onreadBytesFromFileInPartsEventDelegate.Broadcast(0, 0, true, TArray()); + }); + if (reader != nullptr) { + reader->Close(); + } + delete reader; + return 0; + } + + if (delayBetweenReadsInSeconds <= 0) { + delayBetweenReadsInSeconds = 0.0001f; + } + + int64 fileSize = reader->TotalSize(); + int64 readSize = 0; + int64 lastPosition = 0; + TArray buffer; + + if (bufferSize > fileSize) { + bufferSize = fileSize; + } + + while (run && lastPosition < fileSize) { + if ((lastPosition + bufferSize) > fileSize) { + bufferSize = fileSize - lastPosition; + } + + //buffer.Reset(bufferSize); + buffer.Empty(); + buffer.AddUninitialized(bufferSize); + + reader->Serialize(buffer.GetData(), buffer.Num()); + lastPosition += buffer.Num(); + + //UE_LOG(LogTemp, Warning, TEXT("xxxxx READ: %i"), buffer.Num()); + + + AsyncTask(ENamedThreads::GameThread, [fileSize, lastPosition, buffer]() { + USocketServerBPLibrary::getSocketServerTarget()->onreadBytesFromFileInPartsEventDelegate.Broadcast(fileSize, lastPosition, false, buffer); + }); + + FPlatformProcess::Sleep(delayBetweenReadsInSeconds); + + } + + AsyncTask(ENamedThreads::GameThread, [fileSize, lastPosition]() { + USocketServerBPLibrary::getSocketServerTarget()->onreadBytesFromFileInPartsEventDelegate.Broadcast(fileSize, lastPosition, true, TArray()); + }); + + UFileFunctionsSocketServer::getFileFunctionsSocketServerTarget()->cleanReadBytesFromFileInParts(cleanDir); + //buffer.Empty(); + if (reader != nullptr) { + reader->Close(); + } + delete reader; + thread = nullptr; + return 0; + } + + void stopThread() { + run = false; + } + + void setDelayBetweenReadsInSeconds(float d) { + delayBetweenReadsInSeconds = d; + if (delayBetweenReadsInSeconds <= 0) { + delayBetweenReadsInSeconds = 0.001f; + } + } + + +protected: + bool run = true; + FString cleanDir; + int32 bufferSize; + float delayBetweenReadsInSeconds; + //USocketServerBPLibrary* mainLib = USocketServerBPLibrary::getSocketServerTarget(); + + FRunnableThread* thread = nullptr; +}; diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/RCONServer.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/RCONServer.h new file mode 100644 index 0000000..95255bc --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/RCONServer.h @@ -0,0 +1,32 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" +#include "RCONServer.generated.h" + + + +UCLASS(Blueprintable, BlueprintType) +class SOCKETSERVER_API URCONServer : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + UFUNCTION() + void receiveTCPMessageEvent(const FString sessionID, const FString message, const TArray& byteArray, const FString serverID); + + void startRCONServer(FString serverID, ERCONPasswordType passwordType, FString passwordOrFile, + bool& success, FString& errorMessage); + + bool sendResponse(FString sessionID, FString serverID, int32 id, int32 type, FString body); + +private: + FString passwordOrFile = FString(); + ERCONPasswordType passwordType = ERCONPasswordType::E_parameter; + TArray commandList; + + void authResponse(FString sessionID, FString serverID, FString password, int32 rconID); + +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServer.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServer.h new file mode 100644 index 0000000..8f39734 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServer.h @@ -0,0 +1,267 @@ +// Copyright 2017-2022 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "EngineGlobals.h" +#include "Engine/Engine.h" +#include "EventBean.h" +#include "Sockets.h" +#include "SocketSubsystem.h" +#include "Interfaces/IPv4/IPv4Endpoint.h" +#include "Common/UdpSocketReceiver.h" +#include "Common/UdpSocketBuilder.h" +#include "Async/Async.h" +#include "Containers/Queue.h" +#include "Misc/DateTime.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" +#include "HAL/PlatformFileManager.h" +#include "HAL/FileManager.h" +#include "Misc/Base64.h" +#include "Misc/SecureHash.h" +#include "Misc/AES.h" +#include "Modules/ModuleManager.h" +#include "SocketServer.generated.h" + +class FSocketServerTCPClientSendDataThread; +class FSocketServerTCPClientReceiveDataThread; +class FSocketServerTCPFileHandlerThread; + +class FUdpSocketReceiver; +class FSocketServerUDPThread; +class FSocketServerUDPClientSendDataThread; + + +UENUM(BlueprintType) +enum class EFileFunctionsSocketServerEncodingOptions : uint8 +{ + E_AutoDetect UMETA(DisplayName = "AutoDetect"), + E_ForceAnsi UMETA(DisplayName = "ForceAnsi"), + E_ForceUnicode UMETA(DisplayName = "ForceUnicode"), + E_ForceUTF8 UMETA(DisplayName = "ForceUTF8"), + E_ForceUTF8WithoutBOM UMETA(DisplayName = "ForceUTF8WithoutBOM") +}; + +UENUM(BlueprintType) +enum class EFileFunctionsSocketServerDirectoryType : uint8 +{ + E_gd UMETA(DisplayName = "Game directory"), + E_ad UMETA(DisplayName = "Absolute directory") +}; + +UENUM(BlueprintType) +enum class EServerSocketConnectionEventType : uint8 +{ + E_Server UMETA(DisplayName = "Server"), + E_Client UMETA(DisplayName = "Client") + +}; + +UENUM(BlueprintType) +enum class EServerSocketConnectionProtocol : uint8 +{ + E_NotSet UMETA(DisplayName = "NotSet"), + E_TCP UMETA(DisplayName = "TCP"), + E_UDP UMETA(DisplayName = "UDP") + +}; + +UENUM(BlueprintType) +enum class EServerSocketConnectionCheckPortType : uint8 +{ + E_TCP UMETA(DisplayName = "TCP"), + E_UDP UMETA(DisplayName = "UDP") + +}; + +UENUM(BlueprintType) +enum class EReceiveFilterServer : uint8 +{ + E_SAB UMETA(DisplayName = "Message And Bytes"), + E_S UMETA(DisplayName = "Message"), + E_B UMETA(DisplayName = "Bytes") + +}; + +UENUM(BlueprintType) +enum class ESocketPlatformServer : uint8 +{ + E_SSS_SYSTEM UMETA(DisplayName = "System"), + E_SSS_DEFAULT UMETA(DisplayName = "Auto"), + E_SSS_WINDOWS UMETA(DisplayName = "WINDOWS"), + E_SSS_MAC UMETA(DisplayName = "MAC"), + E_SSS_IOS UMETA(DisplayName = "IOS"), + E_SSS_UNIX UMETA(DisplayName = "UNIX"), + E_SSS_ANDROID UMETA(DisplayName = "ANDROID"), + E_SSS_PS4 UMETA(DisplayName = "PS4"), + E_SSS_XBOXONE UMETA(DisplayName = "XBOXONE"), + E_SSS_HTML5 UMETA(DisplayName = "HTML5"), + E_SSS_SWITCH UMETA(DisplayName = "SWITCH") + +}; + +UENUM(BlueprintType) +enum class ESocketServerUDPSocketType : uint8 +{ + E_SSS_SERVER UMETA(DisplayName = "Use Server Socket"), + E_SSS_CLIENT UMETA(DisplayName = "Use Client Socket") + +}; + +UENUM(BlueprintType) +enum class ESocketServerTCPSeparator : uint8 +{ + E_None UMETA(DisplayName = "None"), + E_ByteSeparator UMETA(DisplayName = "Separate via one Byte"), + E_StringSeparator UMETA(DisplayName = "Separate via String"), + E_LengthSeparator UMETA(DisplayName = "Separate by Length") + +}; + + +UENUM(BlueprintType) +enum class ERCONPasswordType : uint8 +{ + E_parameter UMETA(DisplayName = "As String Parameter"), + E_gd UMETA(DisplayName = "As File in Game directory"), + E_ad UMETA(DisplayName = "As File in Absolute directory") +}; + +USTRUCT() +struct FSendUDPMessageStruct { + + GENERATED_USTRUCT_BODY() + + FString ip; + int32 port; + FString message; + TArray bytes; + FSocket* socketUDP = nullptr; +}; + +USTRUCT(BlueprintType) +struct FFileFunctionsSocketServerOpenFile +{ + GENERATED_USTRUCT_BODY() + + FArchive* writer = nullptr; + +}; + +USTRUCT() +struct FIPandPortStruct { + + GENERATED_USTRUCT_BODY() + + bool success = false; + FString ip = FString(); + FString errorMessage = FString(); + int32 port = 0; +}; + +USTRUCT(BlueprintType) +struct FSocketServerPluginSession +{ + GENERATED_USTRUCT_BODY() + + FString ip = FString(); + int32 port = 0; + int64 addToCleanerTime = 0; + FString sessionID = FString(); + FString serverID = FString(); + FSocket* socket = nullptr; + + FSocketServerTCPClientSendDataThread* tcpSendThread = nullptr; + FSocketServerTCPClientReceiveDataThread* tcpRecieverThread = nullptr; + FSocketServerTCPFileHandlerThread* tcpFileHandlerThread = nullptr; + + FUdpSocketReceiver* udpSocketReceiver = nullptr; + FSocketServerUDPThread* udpServerThread = nullptr; + FSocketServerUDPClientSendDataThread* udpSendThread = nullptr; + + EServerSocketConnectionProtocol protocol; +}; + + +USTRUCT(BlueprintType) +struct FSocketServerDownloadFileInfo +{ + GENERATED_USTRUCT_BODY() + + float size; + float megaBytesReceived; + float megaBytesLeft; + float percentDownload; + float megaBit; + FString fileName; + FString serverID; +}; + +//USTRUCT(BlueprintType) +//struct FSocketServerUploadFileInfo +//{ +// GENERATED_USTRUCT_BODY() +// +// float size; +// float megaBytesSend; +// float megaBytesLeft; +// float percentUpload; +// float megaBit; +// FString fileName; +// FString serverID; +//}; + +USTRUCT(BlueprintType) +struct FSocketServerToken +{ + GENERATED_USTRUCT_BODY() + + FString token = FString(); + bool deleteAfterUse = false; + EFileFunctionsSocketServerDirectoryType directoryType; + FString fileDirectory = FString(); +}; + +#ifndef __FileFunctionsSocketServer +#define __FileFunctionsSocketServer +#include "FileFunctionsSocketServer.h" +#endif + +#ifndef __RCONServer +#define __RCONServer +#include "RCONServer.h" +#endif + +#ifndef __SocketServerCleanerThread +#define __SocketServerCleanerThread +#include "SocketServerCleanerThread.h" +#endif + + +#ifndef __SocketServerBPLibrary +#define __SocketServerBPLibrary +#include "SocketServerBPLibrary.h" +#endif + +#ifndef __SocketServerUDP +#define __SocketServerUDP +#include "SocketServerUDP.h" +#endif + +#ifndef __SocketServerTCP +#define __SocketServerTCP +#include "SocketServerTCP.h" +#endif + + + +class FSocketServerModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerBPLibrary.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerBPLibrary.h new file mode 100644 index 0000000..1e1e2f7 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerBPLibrary.h @@ -0,0 +1,499 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" +#include "SocketServerBPLibrary.generated.h" + +class FSocketServerCleanerThread; +class USocketServerUDP; +class USocketServerTCP; +class URCONServer; + + +UCLASS() +class SOCKETSERVER_API USocketServerBPLibrary : public UObject +{ + GENERATED_UCLASS_BODY() + + +public: + + static USocketServerBPLibrary *socketServerBPLibrary; + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer") + static USocketServerBPLibrary* getSocketServerTarget(); + + //Delegates + DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams(FsocketServerConnectionEventDelegate, EServerSocketConnectionEventType, type, bool, success, FString, message, FString, sessionID, FString, serverID); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FserverReceiveTCPMessageEventDelegate, FString, sessionID, FString, message, const TArray&, byteArray, FString, serverID); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FreadBytesFromFileInPartsEventDelegate, int64, fileSize, int64, position, bool, end, const TArray&, byteArray); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FsocketServerUDPConnectionEventDelegate, bool, success, FString, message, FString, serverID); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FserverReceiveUDPMessageEventDelegate, FString, sessionID, FString, message, const TArray&, byteArray,FString, serverID); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_SixParams(FfileTransferOverTCPProgressEventDelegate, FString, sessionID, FString, filePath, float, percent, float, mbit, int64, bytesTransferred, int64, fileSize); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FfileTransferOverTCPInfoEventDelegate, FString, message, FString, sessionID, FString, filePath, bool, success); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FreceiveRCONRequestEventDelegate, FString, sessionID, FString, serverID, int32, requestID, FString, request); + + + UFUNCTION() + void socketServerConnectionEventDelegate(const EServerSocketConnectionEventType type, const bool success, const FString message, const FString sessionID, const FString serverID); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|TCP|Events|ConnectionInfo") + FsocketServerConnectionEventDelegate onsocketServerConnectionEventDelegate; + UFUNCTION() + void serverReceiveTCPMessageEventDelegate(const FString sessionID, const FString message, const TArray& byteArray, const FString serverID); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|TCP|Events|ReceiveMessage") + FserverReceiveTCPMessageEventDelegate onserverReceiveTCPMessageEventDelegate; + UFUNCTION() + void socketServerUDPConnectionEventDelegate(const bool success, const FString message, const FString serverID); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|UDP|Events|ConnectionInfo") + FsocketServerUDPConnectionEventDelegate onsocketServerUDPConnectionEventDelegate; + UFUNCTION() + void serverReceiveUDPMessageEventDelegate(const FString sessionID, const FString message, const TArray& byteArray, const FString serverID); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|UDP|Events|ReceiveMessage") + FserverReceiveUDPMessageEventDelegate onserverReceiveUDPMessageEventDelegate; + UFUNCTION() + void fileTransferOverTCPProgressEventDelegate(const FString sessionID, const FString filePath, const float percent, const float mbit, const int64 bytesTransferred, const int64 fileSize); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|TCP|Events|File|FileTransferOverTCPProgress") + FfileTransferOverTCPProgressEventDelegate onfileTransferOverTCPProgressEventDelegate; + UFUNCTION() + void fileTransferOverTCPInfoEventDelegate(const FString message, const FString sessionID, const FString filePath, const bool success); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|TCP|Events|File|FileTransferOverTCPInfo") + FfileTransferOverTCPInfoEventDelegate onfileTransferOverTCPInfoEventDelegate; + UFUNCTION() + void readBytesFromFileInPartsEventDelegate(const int64 fileSize, const int64 position, const bool end, const TArray& byteArray); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|SpecialFunctions|File|Events|ReadBytesFromFileInPartsAsync") + FreadBytesFromFileInPartsEventDelegate onreadBytesFromFileInPartsEventDelegate; + UFUNCTION() + void receiveRCONRequestEventDelegate(const FString sessionID, const FString serverID, const int32 requestID, const FString request); + UPROPERTY(BlueprintAssignable, Category = "SocketServer|TCP|Events|RCON|ReceiveRCONRequest") + FreceiveRCONRequestEventDelegate onreceiveRCONRequestEventDelegate; + + + + /** + *Get all Session IDs + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static void serverPluginGetSocketSessionIds(const FString optionalServerID, TArray& sessionIDs); + void serverPluginGetSocketSessionIdsNonStatic(const FString optionalServerID, TArray& sessionIDs); + + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static void serverPluginGetSocketSessionInfo(const FString sessionID, bool &sessionFound, FString &IP, int32 &port, EServerSocketConnectionProtocol &connectionProtocol, FString& serverID); + void serverPluginGetSocketSessionInfoNonStatic(const FString sessionID, bool& sessionFound, FString& IP, int32& port, EServerSocketConnectionProtocol& connectionProtocol, FString& serverID); + + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static void serverPluginGetSocketSessionInfoByServerID(const FString serverID, const FString sessionID, bool& sessionFound, FString& IP, int32& port, EServerSocketConnectionProtocol& connectionProtocol); + void serverPluginGetSocketSessionInfoByServerIDNonStatic(const FString serverID, const FString sessionID, bool& sessionFound, FString& IP, int32& port, EServerSocketConnectionProtocol& connectionProtocol); + + /** + *Close a connection and remove the session + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static void removeSessionAndCloseConnection(FString sessionId, FString optionalServerID); + void removeSessionAndCloseConnectionNonStatic(FString sessionId, FString optionalServerID); + + + + //UDP + /** + *Start UDP Server + *@param domainOrIP IP or Domain to listen + *@param port port to listen + *@param multicast This allows several servers to be started on different computers in the LAN with the same IP. + *@param receiveFilter This allows you to decide which data type you want to receive. If you receive files it makes no sense to convert them into a string. + *@param customServerID Optionally you can assign your own ServerID like "myAuthentificationServer" or "fileServer" + *@param maxPacketSize sets the maximum UDP packet size. More than 65507 is not possible. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|UDP", meta = (AdvancedDisplay = 7)) + static void startUDPServer(FString& serverID, FString IP = FString("0.0.0.0"), int32 port = 8888, bool multicast = false, EReceiveFilterServer receiveFilter = EReceiveFilterServer::E_SAB, FString customServerID = FString(""), int32 maxPacketSize = 65507); + void startUDPServerNonStatic(FString& serverID, FString IP = FString("0.0.0.0"), int32 port = 8888, bool multicast = false, EReceiveFilterServer receiveFilter = EReceiveFilterServer::E_SAB, FString customServerID = FString(""), int32 maxPacketSize = 65507); + + /** + *Stop UDP Server + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|UDP") + static void stopUDPServer(FString optionalServerID); + void stopUDPServerNonStatic(FString optionalServerID); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|UDP") + static void stopAllUDPServers(); + void stopAllUDPServersNonStatic(); + + /** + *Sends data back to a client. + *@param clientSessionIDs + *@param message + *@param byteArray + *@param addLineBreak + *@param socketType Some Thirdparty software expects the data to be sent back over the same socket, others expects a new socket. + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|UDP", meta = (AutoCreateRefTerm = "byteArray")) + static void socketServerSendUDPMessage(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, ESocketServerUDPSocketType socketType, FString optionalServerID); + void socketServerSendUDPMessageNonStatic(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, ESocketServerUDPSocketType socketType, FString optionalServerID); + + /** + *Sends data back to a client. + *@param clientSessionIDs + *@param message + *@param byteArray + *@param addLineBreak + *@param socketType Some Thirdparty software expects the data to be sent back over the same socket, others expects a new socket. + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|UDP", meta = (AutoCreateRefTerm = "byteArray")) + static void socketServerSendUDPMessageToClient(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, ESocketServerUDPSocketType socketType, FString optionalServerID); + void socketServerSendUDPMessageToClientNonStatic(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, ESocketServerUDPSocketType socketType, FString optionalServerID); + + /** + *If you want to send data directly to a specific destination without getting data back. + *@param ip + *@param port + *@param message + *@param byteArray + *@param addLineBreak + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|UDP", meta = (AutoCreateRefTerm = "byteArray")) + static void socketServerSendUDPMessageTo(FString ip, int32 port, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, FString optionalServerID); + void socketServerSendUDPMessageToNonStatic(FString ip, int32 port, FString message, TArray byteArray, bool addLineBreak, bool asynchronous, FString optionalServerID); + + + + //TCP + + + /** + * Start TCP Server + * @param domainOrIP IP or Domain to listen + * @param port port to listen + * @param receiveFilter This allows you to decide which data type you want to receive. If you receive files it makes no sense to convert them into a string. + * @param messageSeparator It may be that data packets are split or merged when transmitted over TCP in order to optimize the transmission. For example, if you send "Hello" two times in a row very quickly, it can happen that "HelloHa" and "llo" arrive. To counteract this circumstance there are options to separate the data packets. + * @param customServerID Optionally you can assign your own ServerID like "myAuthentificationServer" or "fileServer" + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void startTCPServer(FString& serverID, FString IP = FString("0.0.0.0"), int32 port = 8888, EReceiveFilterServer receiveFilter = EReceiveFilterServer::E_SAB, + ESocketServerTCPSeparator messageSeparator = ESocketServerTCPSeparator::E_None, FString customServerID = FString("")); + void startTCPServerNontStatic(FString& serverID, FString IP, int32 port, EReceiveFilterServer receiveFilter, ESocketServerTCPSeparator messageSeparator, + FString customServerID, bool isFileServer, FString Aes256bitKey, bool resumeFiles, bool writeHandShakeToLogEditorOnly); + + + /** + * Starts a file server that can receive and send files in response to requests from clients. The files are streamed and do not consume RAM. To determine what can be uploaded or downloaded and how, the tokens are used. + * @param domainOrIP IP or Domain to listen + * @param port port to listen + * @param receiveFilter This allows you to decide which data type you want to receive. If you receive files it makes no sense to convert them into a string. + * @param customServerID Optionally you can assign your own ServerID like "myAuthentificationServer" or "fileServer" + * @param Aes256bitKey The AES key must consist of 32 ASCII characters. The communication between client and server is encrypted via AES in 256bit. Therefore a key must be entered. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void startTCPFileServer(FString& serverID, FString IP = FString("0.0.0.0"), int32 port = 8899, FString customServerID = FString(""), FString Aes256bitKey = FString(""), bool resumeFiles = false, bool writeHandShakeToLogEditorOnly = false); + + + /** + * Tokens are used to determine what can be uploaded or downloaded and how. It can be used to specify a directory in which a file is stored or to specify a file that can be sent to a client. + * @param deleteTokenAfterUse If true, the token will be deleted after one use. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void addFileToken(FString token, bool deleteTokenAfterUse, EFileFunctionsSocketServerDirectoryType directoryType, FString filePathOrDirectory); + + /** + * Tokens are used to determine what can be uploaded or downloaded and how. It can be used to specify a directory in which a file is stored or to specify a file that can be sent to a client. + * @param fileTokens Key= token, value = file path or directory + * @param deleteTokenAfterUse If true, the token will be deleted after one use. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void addFileTokens(TMap fileTokens, bool deleteAfterUse, EFileFunctionsSocketServerDirectoryType directoryType); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void removeFileToken(FString token); + + + /** + *Stop TCP Server + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void stopTCPServer(FString optionalServerID); + void stopTCPServerNonSTatic(FString optionalServerID); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void stopAllTCPServers(); + void stopAllTCPServersNonStatic(); + + //UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP", meta = (AutoCreateRefTerm = "userIdsToDirectoriesMap")) + // void startTCPFileserver(FString& serverID, TMap userIdsToDirectoriesMap, FString IP = FString("0.0.0.0"), int32 port = 9999, FString downloadDirectory = FString("Content/"), EFileFunctionsSocketServerDirectoryType directoryType = EFileFunctionsSocketServerDirectoryType::E_gd, EHTTPSocketServerFileDownloadResumeType ifFileExistThen = EHTTPSocketServerFileDownloadResumeType::E_RESUME, FString customServerID = FString(""), bool onlyWithToken = true); + //UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + // void addFileserverToken(FString token, int32 lifeTimeSeconds,bool reusable); + //UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + // void removeFileserverToken(FString token); + + /** + *Send data to clients + *@param clientSessionIDs array with client sessionIDs + *@param message data as string + *@param byteArray data as bytes + *@param addLineBreak add linebreak to message + *@param serverID Id of the server you want to send data from + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP", meta = (AutoCreateRefTerm = "byteArray")) + static void socketServerSendTCPMessage(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak = true, FString optionalServerID = FString("")); + void socketServerSendTCPMessageNonStatic(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak = true, FString optionalServerID = FString("")); + + /** + *Send data to client + *@param clientSessionID array with client sessionIDs + *@param message data as string + *@param byteArray data as bytes + *@param addLineBreak add linebreak to message + *@param serverID Id of the server you want to send data from + *@param optionalServerID With one server the field can remain empty. If there are several servers, the ServerID should be entered here or the newest server is automatically taken. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP", meta = (AutoCreateRefTerm = "byteArray")) + static void socketServerSendTCPMessageToClient(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak = true, FString optionalServerID = FString("")); + void socketServerSendTCPMessageToClientNonStatic(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak = true, FString optionalServerID = FString("")); + + + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void changeTCPSeparatorStringOnServer(FString separator = "(~{"); + void changeTCPSeparatorStringOnServerNonStatic(FString separator); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP") + static void changeTCPSeparatorByteOnServer(uint8 separator = 0x00); + void changeTCPSeparatorByteOnServerNonStatic(uint8 separator); + + + /** + *Send file to client + *@param clientSessionID client sessionID + *@param directoryWithFileName + *@param directoryType + *@param serverID Id of the server you want to send file from + */ + /*UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP", meta = (AutoCreateRefTerm = "byteArray")) + void socketServerSendTCPFile(FString clientSessionID, FString directoryWithFileName = FString("Content/image.png"), EFileFunctionsSocketServerDirectoryType directoryType = EFileFunctionsSocketServerDirectoryType::E_gd, int64 resumeFileSize =0,FString serverID = FString(""));*/ + + /** + *Creates a unique ID + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static FString generateUniqueID(); + + /** + *Resolve Domain. Only Domains. Hostnames do not work. + *@param domain + *@param useDNSCache Domain and IP are stored in RAM. Starting from the second time the IP is taken from the RAM. + *@param dnsIP + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static UDNSClientSocketServer* resolveDomain(FString domain, bool useDNSCache = true, FString dnsIP = FString("8.8.8.8")); + UDNSClientSocketServer* resolveDomainNonStatic(FString domain, bool useDNSCache = true, FString dnsIP = FString("8.8.8.8")); + + + /** + *UE4 uses different socket connections. When Steam is active, Steam Sockets are used for all connections. This leads to problems if you want to use Steam but not Steam Sockets. Therefore you can change the sockets. + *@param platform Auto = UE4 decides. Thirdparty platforms (Steam, Playstore etc.) will be used if configured. System = UE4 determines the OS and selects a socket type accordingly. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static void changeSocketPlatform(ESocketPlatformServer platform); + + + /** + * Checks if a server can listen on IP x and port y. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static bool checkPort(EServerSocketConnectionCheckPortType type, FString ip = FString("0.0.0.0"), int32 port = 8888); + bool checkPortNonStatic(EServerSocketConnectionCheckPortType type, FString ip = FString("0.0.0.0"), int32 port = 8888); + + /** + * Returns a random port on which the server can listen. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer") + static int32 getRandomPort(EServerSocketConnectionCheckPortType type, FString ip = FString("0.0.0.0")); + int32 getRandomPortNonStatic(EServerSocketConnectionCheckPortType type, FString ip = FString("0.0.0.0")); + + + UFUNCTION(BlueprintCallable, Category = "SocketServer|Register") + static void registerClientEvent(FString sessionID, UEventBean*& event); + void registerClientEventNonStatic(FString sessionID, UEventBean*& event); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|Register") + static void unregisterClientEvent(FString sessionID); + void unregisterClientEventNonStatic(FString sessionID); + + UEventBean* getResiteredClientEvent(FString sessionID); + + //RCON + + /** + * Turns a TCP server into an RCON server. + * @param passwordType If the type "As String Parameter" has been selected, the password can simply be entered as a parameter. Otherwise please select a directory incl. file. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP|RCON") + static void registerRCONServer(FString serverID, ERCONPasswordType passwordType, FString passwordOrFile, bool& success, FString& errorMessage); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP|RCON") + static void unregiserRCONServer(FString serverID); + + /** + * Sends an RCON response to an RCON client. sessionID, serverID and requestID must be specified. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|TCP|RCON") + static void sendRCONResponse(FString sessionID, FString serverID, int32 requestID, FString response); + + /** + *The cleaner thread is a thread that runs endlessly and deletes data remnants from closed/broken connections from RAM. + *@param showLogs Writes to the logs when data remnants are deleted. + *@param minLiveTimeInSeconds When a connection is closed it is passed to the cleaner thread. The thread ignores the connection for "minLiveTimeInSeconds" until it clears the data. This is necessary because sometimes connections need some time to be closed completely. + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions") + static void changeCleanerThreadSettingsOnServer(bool showLogs, int32 minLiveTimeInSeconds = 10); + + + //number stuff + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToFloat(TArray bytes, float& value); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToInteger(TArray bytes, int32& value); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToInteger64(TArray bytes, int64& value); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToFloatPure(TArray bytes, float& value); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToIntegerPure(TArray bytes, int32& value); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToInteger64Pure(TArray bytes, int64& value); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToFloatEndian(TArray bytes, float& littleEndian, float& bigEndian); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToIntegerEndian(TArray bytes, int32& littleEndian, int32& bigEndian); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number") + static void parseBytesToInteger64Endian(TArray bytes, int64& littleEndian, int64& bigEndian); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseFloatToBytes(TArray& byteArray, float value, bool switchByteOrder = false); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseIntegerToBytes(TArray& byteArray, int32 value, bool switchByteOrder = false); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseInteger64ToBytes(TArray& byteArray, int64 value, bool switchByteOrder = false); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseFloatToBytesPure(TArray& byteArray, float value, bool switchByteOrder = false); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseIntegerToBytesPure(TArray& byteArray, int32 value, bool switchByteOrder = false); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseInteger64ToBytesPure(TArray& byteArray, int64 value, bool switchByteOrder = false); + + + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "value")) + static void parseBytesToFloatArrayPure(TArray& value, TArray bytes); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "value")) + static void parseBytesToIntegerArrayPure(TArray& value, TArray bytes); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "value")) + static void parseBytesToInteger64ArrayPure(TArray& value, TArray bytes); + + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseFloatArrayToBytesPure(TArray& byteArray, TArray value); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseIntegerArrayToBytesPure(TArray& byteArray, TArray value); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Number", meta = (AutoCreateRefTerm = "byteArray")) + static void parseInteger64ArrayToBytesPure(TArray& byteArray, TArray value); + + + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Hex") + static TArray parseHexToBytes(FString hex); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Hex") + static FString parseHexToString(FString hex); + + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Hex") + static FString parseBytesToHex(TArray bytes); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Hex") + static TArray parseHexToBytesPure(FString hex); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Hex") + static FString parseHexToStringPure(FString hex); + + UFUNCTION(BlueprintCallable, BlueprintPure, Category = "SocketServer|SpecialFunctions|Hex") + static FString parseBytesToHexPure(TArray bytes); + + + /** + *With this function you can start a UE game server at runtime. To close the server, you can simply start the server again on the same port. However, the default map will then be loaded. + *@param Protocol i.e. "unreal" or "http" + *@param Host Optional hostname, i.e. "204.157.115.40" or "unreal.epicgames.com", blank if local. + *@param Port Optional host port + *@param Map + * @param name, i.e. "SkyCity", default is "Entry". + * @param RedirectURL Optional place to download Map if client does not possess it + * @param Portal Portal to enter through, default is "" + */ + UFUNCTION(BlueprintCallable, Category = "SocketServer|SpecialFunctions|Multiplayer", meta = (WorldContext = worldContextObject, AutoCreateRefTerm = "Options")) + static bool startUEGameHost(UObject* worldContextObject, FString Protocol, FString Host, FString Map, FString RedirectURL, TArray Options, FString Portal, int32 Port = 7777); + + static ISocketSubsystem* getSocketSubSystem(); + + + + + //void addClientSession(FString key, FClientSocketSession& session); + //void removeClientSession(FString key); + //TMap getClientSessions(); + + //void startTCPClientHandler(FClientSocketSession& session, EReceiveFilterServer receiveFilter); + //bool isTCPServerRun(); + //void setTCPServerRun(bool run); + + void getTcpSeparator(uint8& byteSeparator, FString& stringSeparator); + + void cleanConnection(FSocketServerPluginSession& session); + + TMap getTcpServerMap(); + + TMap fileTokenMap; + + FSocketServerCleanerThread* socketServerPluginCleanerThread = nullptr; + +private: + + ESocketPlatformServer systemSocketPlatform; + TMap messageEvents; + + TMap udpServers; + TMap tcpServers; + TMap rconServers; + + + FString lastUDPServerID; + FString lastTCPServerID; + + FString tcpStringSeparator = "(~{"; + uint8 tcpByteSeparator = 0x00; + + FIPandPortStruct checkIpAndPort(FString IP, int32 port); + +}; + diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerCleanerThread.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerCleanerThread.h new file mode 100644 index 0000000..c8b1cae --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerCleanerThread.h @@ -0,0 +1,25 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" + + +class SOCKETSERVER_API FSocketServerCleanerThread : public FRunnable { + +public: + + FSocketServerCleanerThread(); + + + virtual uint32 Run() override; + + void addSession(FSocketServerPluginSession& session); + void changeSettings(bool showLogs, int32 minLiveTimeInSeconds); + +private: + bool showLogs = false; + int32 minLiveTimeInSeconds = 10; + FRunnableThread* thread = nullptr; + TQueue sessionQueue; +}; diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCP.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCP.h new file mode 100644 index 0000000..a680ab7 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCP.h @@ -0,0 +1,80 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. +#pragma once + +#include "SocketServer.h" +#include "SocketServerTCPThread.h" +#include "SocketServerTCPClientSendDataThread.h" +#include "SocketServerTCPClientReceiveDataThread.h" +#include "SocketServerTCPFileHandlerThread.h" +#include "SocketServerTCP.generated.h" + + +class USocketServerBPLibrary; + + +UCLASS(Blueprintable, BlueprintType) +class SOCKETSERVER_API USocketServerTCP : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + void startTCPServer(FIPandPortStruct ipStructP,FString IP, int32 port, EReceiveFilterServer receiveFilter, ESocketServerTCPSeparator messageWrapping, + FString serverID, bool isFileServer, FString Aes256bitKey, bool resumeFiles, bool writeHandShakeToLogEditorOnly); + void stopTCPServer(); + void sendTCPMessage(TArray clientSessionIDs, FString message, TArray byteArray, bool addLineBreak); + void sendTCPMessageToClient(FString clientSessionID, FString message, TArray byteArray, bool addLineBreak); + + FIPandPortStruct getServerIpAndPortStruct(); + FString getIP(); + int32 getPort(); + FString getServerID(); + bool hasResume(); + + void initTCPClientThreads(FSocketServerPluginSession& session, EReceiveFilterServer receiveFilter); + + void addClientSession(FSocketServerPluginSession& session); + FSocketServerPluginSession getClientSession(FString key); + void removeClientSession(FString key); + TMap getClientSessions(); + //EHTTPSocketServerFileDownloadResumeType getifFileExistThen(); + + + FString encryptMessage(FString message); + FString decryptMessage(FString message); + + struct FSocketServerToken getTokenStruct(FString token); + void removeTokenFromStruct(FString token); + FString getCleanDir(EFileFunctionsSocketServerDirectoryType directoryType, FString fileDirectory); + void getMD5FromFile(FString filePathP, bool& success, FString& MD5); + void deleteFile(FString filePathP); + int64 fileSize(FString filePathP); + FString int64ToString(int64 num); + void getTcpSeparator(FString& stringSeparator, uint8& byteSeparator, ESocketServerTCPSeparator& messageWrapping); + bool isRun(); + void readDataLength(TArray& byteDataArray, int32& byteLenght); + TMap clientSessions; + +private: + + FSocketServerTCPThread* socketServerTCPThread = nullptr; + FSocketServerTCPFileHandlerThread* tcpFileHandlerThread = nullptr; + + bool run = true; + FString serverID = FString(); + FIPandPortStruct ipAndPortStruct; + FString serverIP = FString(); + int32 serverPort = -1; + EReceiveFilterServer receiveFilter; + bool fileServer = false; + FString aesKey = FString(); + bool resumeFiles = false; + bool writeHandShakeToLogEditorOnly = false; + //FString downloadDir; + + ESocketServerTCPSeparator messageWrapping; + FString tcpStringSeparator = "(~{"; + uint8 tcpByteSeparator = 0x00; + +}; + diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPClientReceiveDataThread.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPClientReceiveDataThread.h new file mode 100644 index 0000000..5a5777c --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPClientReceiveDataThread.h @@ -0,0 +1,29 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" + + +class SOCKETSERVER_API FSocketServerTCPClientReceiveDataThread : public FRunnable { + +public: + + FSocketServerTCPClientReceiveDataThread(USocketServerTCP* tcpServerP, + FSocketServerPluginSession& sessionP, + EReceiveFilterServer receiveFilterP); + + ~FSocketServerTCPClientReceiveDataThread(); + + virtual uint32 Run() override; + void triggerMessageEvent(TArray& byteDataArray, FString& sessionID, FString& serverID, bool addNullTerminator = true); + void stopThread(); + +private: + USocketServerTCP* tcpServer = nullptr; + FSocketServerPluginSession session; + EReceiveFilterServer receiveFilter; + FRunnableThread* thread = nullptr; + bool run = true; + bool deathConnection = false; +}; diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPClientSendDataThread.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPClientSendDataThread.h new file mode 100644 index 0000000..ac82313 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPClientSendDataThread.h @@ -0,0 +1,34 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" + + +class SOCKETSERVER_API FSocketServerTCPClientSendDataThread : public FRunnable { + +public: + + FSocketServerTCPClientSendDataThread(USocketServerTCP* tcpServerP, FSocketServerPluginSession& sessionP); + ~FSocketServerTCPClientSendDataThread(); + + virtual uint32 Run() override; + FRunnableThread* getThread(); + void setThread(FRunnableThread* threadP); + void stopThread(); + bool isRun(); + void setMessage(FString messageP, TArray byteArrayP); + void sendMessage(FString messageP, TArray byteArrayP); + void pauseThread(bool pause); + +private: + USocketServerTCP* tcpServer = nullptr; + FSocketServerPluginSession session; + FRunnableThread* thread = nullptr; + FSocket* socket = nullptr; + bool run = true; + bool paused; + bool waitForInit = true; + TQueue messageQueue; + TQueue> byteArrayQueue; +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPFileHandlerThread.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPFileHandlerThread.h new file mode 100644 index 0000000..9170a84 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPFileHandlerThread.h @@ -0,0 +1,48 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" +//#include "SocketServerTCPFileHandlerThread.generated.h" + +class SOCKETSERVER_API FSocketServerTCPFileHandlerThread : public FRunnable { + +public: + + FSocketServerTCPFileHandlerThread(USocketServerTCP* tcpServerP, FSocketServerPluginSession& sessionP); + ~FSocketServerTCPFileHandlerThread(); + + virtual uint32 Run() override; + + void doRequestFileFromServer(FSocketServerToken tokenStruct); + void doSendFileToClient(FString message); + void triggerFileOverTCPProgress(FString sessionIDP, FString filePathP, float percentP, float mbitP, int64 bytesReceivedP, int64 fileSizeP); + void triggerFileTransferOverTCPInfoEvent(FString messageP, FString sessionIDP, FString filePathP, bool successP); + void sendEndMessage(FString fullFilePathP, FString tokenP, FString md5ClientP); + FString readMessageFromClient(); + void sendMessageToClient(FString message); + void stopThread(); + + + +private: + USocketServerTCP* tcpServer = nullptr; + FSocketServerPluginSession session; + bool fileServer = false; + FRunnableThread* thread = nullptr; + bool run = true; + //int32 commandProgress = 0; + int64 fileSize = 0; + double WaitForRead = 30; + //FSocketServerTCPClientSendFileToThread* sendFileThread = nullptr; +}; + + +//UCLASS(Blueprintable, BlueprintType) +//class USocketServerTCPFileHandler : public UObject +//{ +// GENERATED_UCLASS_BODY() +// +// UFUNCTION() +// void receiveData(const FString sessionID, const FString message, const TArray& byteArray, const FString serverID); +//}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPThread.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPThread.h new file mode 100644 index 0000000..d20c3d9 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerTCPThread.h @@ -0,0 +1,23 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" + +class SOCKETSERVER_API FSocketServerTCPThread : public FRunnable { + +public: + + FSocketServerTCPThread(USocketServerTCP* tcpServerP, EReceiveFilterServer receiveFilterP, bool& runP); + ~FSocketServerTCPThread(); + + virtual uint32 Run() override; + + void stopThread(); + +private: + USocketServerTCP* tcpServer; + EReceiveFilterServer receiveFilter; + bool& run; + FRunnableThread* thread = nullptr; +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDP.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDP.h new file mode 100644 index 0000000..e6b47ff --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDP.h @@ -0,0 +1,59 @@ +// Copyright 2017 David Romanski (Socke). All Rights Reserved. +#pragma once + +#include "SocketServer.h" +#include "SocketServerUDPThread.h" +#include "SocketServerUDPClientSendDataThread.h" +#include "SocketServerUDP.generated.h" + + +class USocketServerBPLibrary; +class FSocketServerUDPThread; +class FSocketServerUDPClientSendDataThread; + + +UCLASS(Blueprintable, BlueprintType) +class SOCKETSERVER_API USocketServerUDP : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + void startUDPServer(FIPandPortStruct ipStruct,FString IP, int32 port, bool multicast, EReceiveFilterServer receiveFilter, FString serverID, int32 maxPacketSize); + void stopUDPServer(); + void sendUDPMessage(TArray clientSessionIDs, FString message, TArray byteArray, bool asynchronous, ESocketServerUDPSocketType socketType); + void sendUDPMessageToClient(FString clientSessionID, FString message, TArray byteArray, bool asynchronous, ESocketServerUDPSocketType socketType); + void sendUDPMessageTo(FString ip, int32 port, FString message, TArray byteArray, bool asynchronous); + //do not work with ipv6 + //void UDPReceiver(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt); + void UDPReceiverSocketServerPlugin(FArrayReaderPtr& ArrayReaderPtr, TSharedRef remoteAddress); + + FIPandPortStruct getServerIpAndPortStruct(); + FString getIP(); + int32 getPort(); + void setSocketReceiver(FUdpSocketReceiver* socketReceiverP, FSocket* socket); + FUdpSocketReceiver* getSocketReceiver(); + FSocket* getSocket(); + FString getServerID(); + + void addClientSession(FSocketServerPluginSession& session); + FSocketServerPluginSession* getClientSession(FString key); + void removeClientSession(FString key); + TMap getClientSessions(); + void sendBytes(FSocket*& socket, TArray& bytes, int32& sent, TSharedRef& addr); + +private: + + FString serverID; + FIPandPortStruct ipAndPortStruct; + FString serverIP; + int32 serverPort = -1; + int32 maxPacketSize = 65507; + FSocket* socket= nullptr; + FUdpSocketReceiver* socketReceiver = nullptr; + EReceiveFilterServer receiveFilter; + FSocketServerUDPThread* serverThread = nullptr; + FSocketServerUDPClientSendDataThread* sendThread = nullptr; + + TMap clientSessions; +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDPClientSendDataThread.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDPClientSendDataThread.h new file mode 100644 index 0000000..51bdbe0 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDPClientSendDataThread.h @@ -0,0 +1,30 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" + + +class SOCKETSERVER_API FSocketServerUDPClientSendDataThread : public FRunnable { + +public: + + FSocketServerUDPClientSendDataThread(USocketServerUDP* udpServerP); + + ~FSocketServerUDPClientSendDataThread(); + + virtual uint32 Run() override; + FRunnableThread* getThread(); + void setThread(FRunnableThread* threadP); + void stopThread(); + bool isRun(); + void sendMessage(FString ip, int32 port, FString message, TArray bytes, FSocket* socketUDP); + void pauseThread(bool pause); + +private: + USocketServerUDP* udpServer; + FRunnableThread* thread = nullptr; + bool run = true; + bool paused; + TQueue messageQueue; +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDPThread.h b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDPThread.h new file mode 100644 index 0000000..b879596 --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/Public/SocketServerUDPThread.h @@ -0,0 +1,28 @@ +// Copyright 2022 David Romanski (Socke). All Rights Reserved. + +#pragma once + +#include "SocketServer.h" + + +class SOCKETSERVER_API FSocketServerUDPThread : public FRunnable { + +public: + + FSocketServerUDPThread(USocketServerUDP* udpServerP, bool multicastP); + + ~FSocketServerUDPThread(); + + virtual uint32 Run() override; + + void stopThread(); + + +private: + FString message; + USocketServerUDP* udpServer; + bool multicast; + FRunnableThread* thread = nullptr; + bool run = true; + +}; \ No newline at end of file diff --git a/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/SocketServer.Build.cs b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/SocketServer.Build.cs new file mode 100644 index 0000000..da95b1d --- /dev/null +++ b/SpaceStudioClient/Plugins/SocketServer/Source/SocketServer/SocketServer.Build.cs @@ -0,0 +1,57 @@ +// Copyright 2017-2020 David Romanski (Socke). All Rights Reserved. + +using UnrealBuildTool; + +public class SocketServer : ModuleRules +{ + public SocketServer(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "Sockets", + "Networking", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Sockets", + "Networking", + "Slate", + "SlateCore" + // ... add private dependencies that you statically link with here ... + } + ); + + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + } +} diff --git a/SpaceStudioClient/SpaceStudioClient.uproject b/SpaceStudioClient/SpaceStudioClient.uproject new file mode 100644 index 0000000..420d4d0 --- /dev/null +++ b/SpaceStudioClient/SpaceStudioClient.uproject @@ -0,0 +1,15 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.3", + "Category": "", + "Description": "", + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true, + "TargetAllowList": [ + "Editor" + ] + } + ] +} \ No newline at end of file