├── Resources └── Icon128.png ├── README.md ├── Binaries └── Win64 │ └── UE4Editor-UnrealJSONRPC.pdb ├── Source └── UnrealEdRemote │ ├── Private │ ├── UnrealEdRemoteSettings.cpp │ ├── UnrealEdRemotePrivatePCH.h │ ├── UnrealEdRemote.cpp │ └── UnrealEdRemoteServer.cpp │ ├── Public │ ├── UnrealEdRemoteSettings.h │ ├── IUnrealEdRemote.h │ └── UnrealEdRemoteServer.h │ └── UnrealEdRemote.Build.cs ├── .gitignore ├── UnrealEdRemote.uplugin └── LICENSE /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritz-wundke/UnrealRemote/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UnrealEdRemote 2 | ================ 3 | 4 | Very simple TCP Server for the Unreal Editor to execute consoles commands remotely. 5 | -------------------------------------------------------------------------------- /Binaries/Win64/UE4Editor-UnrealJSONRPC.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moritz-wundke/UnrealRemote/HEAD/Binaries/Win64/UE4Editor-UnrealJSONRPC.pdb -------------------------------------------------------------------------------- /Source/UnrealEdRemote/Private/UnrealEdRemoteSettings.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Moritz Wundke 2 | 3 | #include "UnrealEdRemotePrivatePCH.h" 4 | #include "UnrealEdRemoteSettings.h" 5 | 6 | UUnrealEdRemoteSettings::UUnrealEdRemoteSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer), 7 | ListenPort(8080) 8 | { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | 19 | # Compiled Static libraries 20 | *.lai 21 | *.la 22 | *.a 23 | *.lib 24 | 25 | # Executables 26 | *.exe 27 | *.out 28 | *.app 29 | -------------------------------------------------------------------------------- /Source/UnrealEdRemote/Private/UnrealEdRemotePrivatePCH.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Moritz Wundke 2 | 3 | #pragma once 4 | 5 | #include "IUnrealEdRemote.h" 6 | 7 | // You should place include statements to your module's private header files here. You only need to 8 | // add includes for headers that are used in most of your module's source files though. 9 | 10 | #include "CoreUObject.h" 11 | #include "Engine.h" 12 | //#include "Settings.h" 13 | 14 | DECLARE_LOG_CATEGORY_EXTERN(LogUnrealEdRemote, Log, All); 15 | -------------------------------------------------------------------------------- /UnrealEdRemote.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | 4 | "FriendlyName" : "Unreal Ed Remote", 5 | "Version" : 1, 6 | "VersionName" : "1.0", 7 | "CreatedBy" : "Moritz Wundke", 8 | "CreatedByURL" : "http://www.moritzwundke.com", 9 | "EngineVersion" : "4.7.6", 10 | "Description" : "Very simple TCP Server for the Unreal Editor to execute consoles commands remotely.", 11 | "Category" : "Messaging", 12 | "EnabledByDefault" : false, 13 | "CanContainContent" : false, 14 | 15 | "Modules" : 16 | [ 17 | { 18 | "Name" : "UnrealEdRemote", 19 | "Type" : "Runtime", 20 | "LoadingPhase" : "PreDefault" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /Source/UnrealEdRemote/Public/UnrealEdRemoteSettings.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Moritz Wundke 2 | 3 | #pragma once 4 | 5 | #include "UnrealEdRemoteSettings.generated.h" 6 | 7 | /** 8 | * Setting object used to hold both config settings and editable ones in one place 9 | */ 10 | UCLASS(config = Engine) 11 | class UUnrealEdRemoteSettings : public UObject 12 | { 13 | GENERATED_BODY() 14 | 15 | public: 16 | UUnrealEdRemoteSettings(const FObjectInitializer& ObjectInitializer); 17 | 18 | UPROPERTY(Config, EditAnywhere, Category = Transport) 19 | int32 ListenPort; 20 | 21 | UPROPERTY(Config, EditAnywhere, Category = General) 22 | bool EnableInGame; 23 | 24 | UPROPERTY(Config, EditAnywhere, Category = General) 25 | bool Enabled; 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Moritz Wundke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Source/UnrealEdRemote/Public/IUnrealEdRemote.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Moritz Wundke 2 | 3 | #pragma once 4 | 5 | #include "ModuleManager.h" 6 | 7 | /** 8 | * The public interface to this module. In most cases, this interface is only public to sibling modules 9 | * within this plugin. 10 | */ 11 | class IUnrealEdRemote : public IModuleInterface 12 | { 13 | 14 | public: 15 | 16 | /** 17 | * Singleton-like access to this module's interface. This is just for convenience! 18 | * Beware of calling this during the shutdown phase, though. Your module might have been unloaded already. 19 | * 20 | * @return Returns singleton instance, loading the module on demand if needed 21 | */ 22 | static inline IUnrealEdRemote& Get() 23 | { 24 | return FModuleManager::LoadModuleChecked< IUnrealEdRemote >("UnrealEdRemote"); 25 | } 26 | 27 | /** 28 | * Checks to see if this module is loaded and ready. It is only valid to call Get() if IsAvailable() returns true. 29 | * 30 | * @return True if the module is loaded and ready to use 31 | */ 32 | static inline bool IsAvailable() 33 | { 34 | return FModuleManager::Get().IsModuleLoaded( "UnrealEdRemote" ); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Source/UnrealEdRemote/UnrealEdRemote.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Moritz Wundke 2 | 3 | namespace UnrealBuildTool.Rules 4 | { 5 | public class UnrealEdRemote : ModuleRules 6 | { 7 | public UnrealEdRemote(TargetInfo Target) 8 | { 9 | PublicIncludePaths.AddRange( 10 | new string[] { 11 | // ... add public include paths required here ... 12 | } 13 | ); 14 | 15 | PrivateIncludePaths.AddRange( 16 | new string[] { 17 | "UnrealEdRemote/Private" 18 | // ... add other private include paths required here ... 19 | } 20 | ); 21 | 22 | PublicIncludePathModuleNames.AddRange( 23 | new string[] 24 | { 25 | "Settings" 26 | }); 27 | 28 | PublicDependencyModuleNames.AddRange( 29 | new string[] 30 | { 31 | "Core", 32 | "CoreUObject", 33 | "Engine", 34 | // ... add other public dependencies that you statically link with here ... 35 | } 36 | ); 37 | 38 | PrivateDependencyModuleNames.AddRange( 39 | new string[] 40 | { 41 | "Networking", 42 | "Sockets", 43 | // ... add private dependencies that you statically link with here ... 44 | } 45 | ); 46 | 47 | DynamicallyLoadedModuleNames.AddRange( 48 | new string[] 49 | { 50 | // ... add any modules that your module loads dynamically here ... 51 | } 52 | ); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Source/UnrealEdRemote/Public/UnrealEdRemoteServer.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2015 Moritz Wundke 2 | 3 | #pragma once 4 | 5 | #include "Networking.h" 6 | #include "TcpListener.h" 7 | #include "UnrealEdRemoteSettings.h" 8 | 9 | // Default endpoint for the server 10 | #define DEFAULT_ENDPOINT FIPv4Endpoint(FIPv4Address(127, 0, 0, 1), 5005) 11 | 12 | /** 13 | * Server listening on a TCP socket 14 | */ 15 | class FUnrealEdRemoteServer 16 | : public FRunnable 17 | { 18 | public: 19 | FUnrealEdRemoteServer(); 20 | 21 | ~FUnrealEdRemoteServer(); 22 | 23 | void OnSettingsChanged(const UUnrealEdRemoteSettings& Settings); 24 | 25 | /** FRunnable init */ 26 | virtual bool Init() override; 27 | 28 | /** FRunnable loop */ 29 | virtual uint32 Run() override; 30 | 31 | bool HandleListenerConnectionAccepted(class FSocket* ClientSocket, const FIPv4Endpoint& ClientEndpoint); 32 | 33 | /** 34 | * Checks whether the listener is listening for incoming connections. 35 | * 36 | * @return true if it is listening, false otherwise. 37 | */ 38 | bool IsActive() const 39 | { 40 | return (!Stopping); 41 | } 42 | 43 | virtual void Stop() override 44 | { 45 | Stopping = true; 46 | } 47 | 48 | virtual void Exit() override { } 49 | 50 | private: 51 | class FTcpListener *Listener = NULL; 52 | 53 | /** Current clients and pending to get accepted clients */ 54 | TQueue PendingClients; 55 | TArray Clients; 56 | 57 | /** Used to tell that the thread is stopping */ 58 | bool Stopping; 59 | 60 | /** Connection thread, used to not block the editor when waiting for connections */ 61 | FRunnableThread* Thread = NULL; 62 | 63 | /** Basic message handling, will get extended */ 64 | FString HandleClientMessage(const FSocket *Socket, const FString& Message); 65 | bool HandleExecCommand(const FSocket *Socket, const FString& Command); 66 | 67 | }; 68 | -------------------------------------------------------------------------------- /Source/UnrealEdRemote/Private/UnrealEdRemote.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2015 Moritz Wundke 2 | 3 | #include "UnrealEdRemotePrivatePCH.h" 4 | 5 | // Settings 6 | #include "UnrealEdRemoteSettings.h" 7 | #include "ISettingsModule.h" 8 | #include "ISettingsSection.h" 9 | 10 | // The server 11 | #include "UnrealEdRemoteServer.h" 12 | 13 | DEFINE_LOG_CATEGORY(LogUnrealEdRemote); 14 | 15 | #define LOCTEXT_NAMESPACE "UnrealEdRemote" 16 | 17 | class FUnrealEdRemote: public IUnrealEdRemote 18 | { 19 | /** IModuleInterface implementation */ 20 | virtual void StartupModule() override; 21 | virtual void ShutdownModule() override; 22 | 23 | virtual bool SupportsDynamicReloading() override 24 | { 25 | return true; 26 | } 27 | private: 28 | 29 | // Callback for when the settings were saved. 30 | bool HandleSettingsSaved(); 31 | 32 | void RegisterSettings() 33 | { 34 | if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) 35 | { 36 | // Register the settings 37 | ISettingsSectionPtr SettingsSection = SettingsModule->RegisterSettings("Project", "Plugins", "UnrealEdRemote", 38 | LOCTEXT("RuntimeSettingsName", "Unreal Ed Remote"), 39 | LOCTEXT("RuntimeSettingsDescription", "Configure the Unreal Ed Remote plugin"), 40 | GetMutableDefault() 41 | ); 42 | 43 | // Set the save handle, used to restart the server in case of a server change 44 | if (SettingsSection.IsValid()) 45 | { 46 | SettingsSection->OnModified().BindRaw(this, &FUnrealEdRemote::HandleSettingsSaved); 47 | } 48 | } 49 | } 50 | 51 | void UnregisterSettings() 52 | { 53 | // unregister settings 54 | if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) 55 | { 56 | SettingsModule->UnregisterSettings("Project", "Plugins", "UnrealEdRemote"); 57 | } 58 | } 59 | 60 | /** Callback for when an has been reactivated (i.e. return from sleep on iOS). */ 61 | void HandleApplicationHasReactivated() 62 | { 63 | RestartServer(); 64 | } 65 | 66 | /** Callback for when the application will be deactivated (i.e. sleep on iOS).*/ 67 | void HandleApplicationWillDeactivate() 68 | { 69 | StopServer(); 70 | } 71 | 72 | /** Services life-cycle */ 73 | void InitServer(); 74 | void RestartServer(); 75 | void StopServer(); 76 | 77 | private: 78 | 79 | /** The server that is currently lifted-up */ 80 | FUnrealEdRemoteServer* ServerInstance = NULL; 81 | }; 82 | 83 | IMPLEMENT_MODULE( FUnrealEdRemote, UnrealEdRemote ) 84 | 85 | void FUnrealEdRemote::StartupModule() 86 | { 87 | RegisterSettings(); 88 | 89 | // register application events 90 | FCoreDelegates::ApplicationHasReactivatedDelegate.AddRaw(this, &FUnrealEdRemote::HandleApplicationHasReactivated); 91 | FCoreDelegates::ApplicationWillDeactivateDelegate.AddRaw(this, &FUnrealEdRemote::HandleApplicationWillDeactivate); 92 | 93 | // Start it all! 94 | RestartServer(); 95 | 96 | UE_LOG(LogUnrealEdRemote, Display, TEXT("Unreal Ed Remote running!")); 97 | } 98 | 99 | void FUnrealEdRemote::ShutdownModule() 100 | { 101 | // unregister application events 102 | FCoreDelegates::ApplicationHasReactivatedDelegate.RemoveAll(this); 103 | FCoreDelegates::ApplicationWillDeactivateDelegate.RemoveAll(this); 104 | 105 | UnregisterSettings(); 106 | StopServer(); 107 | } 108 | 109 | bool FUnrealEdRemote::HandleSettingsSaved() 110 | { 111 | RestartServer(); 112 | return true; 113 | } 114 | 115 | void FUnrealEdRemote::InitServer() 116 | { 117 | StopServer(); 118 | 119 | // Get settings and start server 120 | //const UUnrealEdRemoteSettings& Settings = *GetDefault(); 121 | ServerInstance = new FUnrealEdRemoteServer(); 122 | } 123 | 124 | void FUnrealEdRemote::RestartServer() 125 | { 126 | const UUnrealEdRemoteSettings& Settings = *GetDefault(); 127 | if (Settings.Enabled) 128 | { 129 | if (ServerInstance == NULL || !ServerInstance->IsActive()) 130 | { 131 | InitServer(); 132 | } 133 | } 134 | else 135 | { 136 | StopServer(); 137 | } 138 | } 139 | 140 | void FUnrealEdRemote::StopServer() 141 | { 142 | if (ServerInstance != NULL) 143 | { 144 | delete ServerInstance; 145 | ServerInstance = NULL; 146 | } 147 | } -------------------------------------------------------------------------------- /Source/UnrealEdRemote/Private/UnrealEdRemoteServer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 Moritz Wundke 2 | 3 | #include "UnrealEdRemotePrivatePCH.h" 4 | #include "UnrealEdRemoteServer.h" 5 | #include "Runtime/Core/Public/Misc/CString.h" 6 | 7 | FUnrealEdRemoteServer::FUnrealEdRemoteServer() 8 | : Listener(NULL), Thread(NULL) 9 | { 10 | Thread = FRunnableThread::Create(this, TEXT("FUnrealEdRemoteServer"), 8 * 1024, TPri_Normal); 11 | } 12 | 13 | FUnrealEdRemoteServer::~FUnrealEdRemoteServer() 14 | { 15 | // Stop the runnable 16 | Stop(); 17 | 18 | // Stop accepting clients first 19 | if (Listener != NULL) 20 | { 21 | Listener->Stop(); 22 | delete Listener; 23 | Listener = NULL; 24 | } 25 | 26 | // Kill all pending connections and current connections 27 | if (!PendingClients.IsEmpty()) 28 | { 29 | FSocket *Client = NULL; 30 | while (PendingClients.Dequeue(Client)) 31 | { 32 | Client->Close(); 33 | } 34 | } 35 | for (TArray::TIterator ClientIt(Clients); ClientIt; ++ClientIt) 36 | { 37 | (*ClientIt)->Close(); 38 | } 39 | 40 | // And last but not least stop the main thread 41 | if (Thread != NULL) 42 | { 43 | Thread->Kill(true); 44 | delete Thread; 45 | } 46 | } 47 | 48 | void FUnrealEdRemoteServer::OnSettingsChanged(const UUnrealEdRemoteSettings& Settings) 49 | { 50 | 51 | } 52 | 53 | bool FUnrealEdRemoteServer::HandleListenerConnectionAccepted(class FSocket* ClientSocket, const FIPv4Endpoint& ClientEndpoint) 54 | { 55 | PendingClients.Enqueue(ClientSocket); 56 | return true; 57 | } 58 | 59 | bool FUnrealEdRemoteServer::Init() 60 | { 61 | if (Listener == NULL) 62 | { 63 | Listener = new FTcpListener(DEFAULT_ENDPOINT); 64 | Listener->OnConnectionAccepted().BindRaw(this, &FUnrealEdRemoteServer::HandleListenerConnectionAccepted); 65 | Stopping = false; 66 | } 67 | return (Listener != NULL); 68 | } 69 | 70 | /** Send a string message over to a socket */ 71 | bool SendMessage(FSocket *Socket, const FString& Message) 72 | { 73 | check(Socket); 74 | int32 BytesSent = 0; 75 | return Socket->Send((uint8*)TCHAR_TO_UTF8(*Message), Message.Len(), BytesSent); 76 | } 77 | 78 | /** Receive a string message from a socket */ 79 | bool RecvMessage(FSocket *Socket, uint32 DataSize, FString& Message) 80 | { 81 | check(Socket); 82 | 83 | FArrayReaderPtr Datagram = MakeShareable(new FArrayReader(true)); 84 | Datagram->Init(FMath::Min(DataSize, 65507u)); 85 | 86 | int32 BytesRead = 0; 87 | if (Socket->Recv(Datagram->GetData(), Datagram->Num(), BytesRead)) 88 | { 89 | char* Data = (char*)Datagram->GetData(); 90 | Data[BytesRead] = '\0'; 91 | Message = UTF8_TO_TCHAR(Data); 92 | return true; 93 | } 94 | return false; 95 | } 96 | 97 | uint32 FUnrealEdRemoteServer::Run() 98 | { 99 | while (!Stopping) 100 | { 101 | if (!PendingClients.IsEmpty()) 102 | { 103 | FSocket *Client = NULL; 104 | while (PendingClients.Dequeue(Client)) 105 | { 106 | Clients.Add(Client); 107 | } 108 | } 109 | 110 | // remove closed connections 111 | for (int32 ClientIndex = Clients.Num() - 1; ClientIndex >= 0; --ClientIndex) 112 | { 113 | if (Clients[ClientIndex]->GetConnectionState() != SCS_Connected) 114 | { 115 | Clients.RemoveAtSwap(ClientIndex); 116 | } 117 | } 118 | 119 | // Poll data from every connected client 120 | for (TArray::TIterator ClientIt(Clients); ClientIt; ++ClientIt) 121 | { 122 | FSocket *Client = *ClientIt; 123 | uint32 DataSize = 0; 124 | while (Client->HasPendingData(DataSize)) 125 | { 126 | FString Request; 127 | if (RecvMessage(Client, DataSize, Request)) 128 | { 129 | FString Response = HandleClientMessage(Client, Request); 130 | SendMessage(Client, Response); 131 | } 132 | } 133 | } 134 | 135 | FPlatformProcess::Sleep(0.2f); 136 | } 137 | 138 | return 0; 139 | } 140 | 141 | FString FUnrealEdRemoteServer::HandleClientMessage(const FSocket *Socket, const FString& Message) 142 | { 143 | bool bProcessed = false; 144 | FString Response; 145 | 146 | const TCHAR* Cmd = *Message; 147 | if (FParse::Command(&Cmd, TEXT("EXEC")) && HandleExecCommand(Socket, Message.Mid(FCString::Strlen(TEXT("CMD"))))) 148 | { 149 | bProcessed = true; 150 | } 151 | 152 | return bProcessed ? TEXT("OK") : TEXT("FAILED"); 153 | } 154 | 155 | bool FUnrealEdRemoteServer::HandleExecCommand(const FSocket *Socket, const FString& Command) 156 | { 157 | if (GEngine != NULL) 158 | { 159 | GEngine->DeferredCommands.Add(Command); 160 | return true; 161 | } 162 | return false; 163 | } --------------------------------------------------------------------------------