ue-mcp 0.4.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/bridge.d.ts +25 -0
- package/dist/bridge.js +124 -0
- package/dist/bridge.js.map +1 -0
- package/dist/deployer.d.ts +18 -0
- package/dist/deployer.js +175 -0
- package/dist/deployer.js.map +1 -0
- package/dist/editor-control.d.ts +15 -0
- package/dist/editor-control.js +181 -0
- package/dist/editor-control.js.map +1 -0
- package/dist/github-app.d.ts +4 -0
- package/dist/github-app.js +94 -0
- package/dist/github-app.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +125 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +290 -0
- package/dist/init.js.map +1 -0
- package/dist/instructions.d.ts +1 -0
- package/dist/instructions.js +150 -0
- package/dist/instructions.js.map +1 -0
- package/dist/project.d.ts +32 -0
- package/dist/project.js +175 -0
- package/dist/project.js.map +1 -0
- package/dist/tools/animation.d.ts +2 -0
- package/dist/tools/animation.js +62 -0
- package/dist/tools/animation.js.map +1 -0
- package/dist/tools/asset.d.ts +2 -0
- package/dist/tools/asset.js +128 -0
- package/dist/tools/asset.js.map +1 -0
- package/dist/tools/audio.d.ts +2 -0
- package/dist/tools/audio.js +24 -0
- package/dist/tools/audio.js.map +1 -0
- package/dist/tools/blueprint.d.ts +2 -0
- package/dist/tools/blueprint.js +64 -0
- package/dist/tools/blueprint.js.map +1 -0
- package/dist/tools/demo.d.ts +2 -0
- package/dist/tools/demo.js +10 -0
- package/dist/tools/demo.js.map +1 -0
- package/dist/tools/editor.d.ts +2 -0
- package/dist/tools/editor.js +133 -0
- package/dist/tools/editor.js.map +1 -0
- package/dist/tools/feedback.d.ts +2 -0
- package/dist/tools/feedback.js +44 -0
- package/dist/tools/feedback.js.map +1 -0
- package/dist/tools/foliage.d.ts +2 -0
- package/dist/tools/foliage.js +29 -0
- package/dist/tools/foliage.js.map +1 -0
- package/dist/tools/gameplay.d.ts +2 -0
- package/dist/tools/gameplay.js +101 -0
- package/dist/tools/gameplay.js.map +1 -0
- package/dist/tools/gas.d.ts +2 -0
- package/dist/tools/gas.js +43 -0
- package/dist/tools/gas.js.map +1 -0
- package/dist/tools/landscape.d.ts +2 -0
- package/dist/tools/landscape.js +32 -0
- package/dist/tools/landscape.js.map +1 -0
- package/dist/tools/level.d.ts +2 -0
- package/dist/tools/level.js +66 -0
- package/dist/tools/level.js.map +1 -0
- package/dist/tools/material.d.ts +2 -0
- package/dist/tools/material.js +59 -0
- package/dist/tools/material.js.map +1 -0
- package/dist/tools/networking.d.ts +2 -0
- package/dist/tools/networking.js +42 -0
- package/dist/tools/networking.js.map +1 -0
- package/dist/tools/niagara.d.ts +2 -0
- package/dist/tools/niagara.js +42 -0
- package/dist/tools/niagara.js.map +1 -0
- package/dist/tools/pcg.d.ts +2 -0
- package/dist/tools/pcg.js +39 -0
- package/dist/tools/pcg.js.map +1 -0
- package/dist/tools/project.d.ts +2 -0
- package/dist/tools/project.js +282 -0
- package/dist/tools/project.js.map +1 -0
- package/dist/tools/reflection.d.ts +2 -0
- package/dist/tools/reflection.js +26 -0
- package/dist/tools/reflection.js.map +1 -0
- package/dist/tools/widget.d.ts +2 -0
- package/dist/tools/widget.js +34 -0
- package/dist/tools/widget.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +38 -0
- package/dist/types.js.map +1 -0
- package/package.json +76 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
- package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
#include "BridgeServer.h"
|
|
2
|
+
#include "UE_MCP_BridgeModule.h"
|
|
3
|
+
#include "Dom/JsonObject.h"
|
|
4
|
+
#include "Dom/JsonValue.h"
|
|
5
|
+
#include "Serialization/JsonSerializer.h"
|
|
6
|
+
#include "Serialization/JsonWriter.h"
|
|
7
|
+
#include "HAL/PlatformProcess.h"
|
|
8
|
+
#include "Misc/DateTime.h"
|
|
9
|
+
#include "Misc/Timespan.h"
|
|
10
|
+
#include "Async/Async.h"
|
|
11
|
+
#include "Handlers/EditorHandlers.h"
|
|
12
|
+
#include "Handlers/AssetHandlers.h"
|
|
13
|
+
#include "Handlers/BlueprintHandlers.h"
|
|
14
|
+
#include "Handlers/LevelHandlers.h"
|
|
15
|
+
#include "Handlers/ReflectionHandlers.h"
|
|
16
|
+
#include "Handlers/GasHandlers.h"
|
|
17
|
+
#include "Handlers/GameplayHandlers.h"
|
|
18
|
+
#include "Handlers/DialogHandlers.h"
|
|
19
|
+
#include "Handlers/MaterialHandlers.h"
|
|
20
|
+
#include "Handlers/AnimationHandlers.h"
|
|
21
|
+
#include "Handlers/AudioHandlers.h"
|
|
22
|
+
#include "Handlers/WidgetHandlers.h"
|
|
23
|
+
#include "Handlers/FoliageHandlers.h"
|
|
24
|
+
#include "Handlers/LandscapeHandlers.h"
|
|
25
|
+
#include "Handlers/NetworkingHandlers.h"
|
|
26
|
+
#include "Handlers/NiagaraHandlers.h"
|
|
27
|
+
#include "Handlers/PCGHandlers.h"
|
|
28
|
+
#include "Handlers/SequencerHandlers.h"
|
|
29
|
+
#include "Handlers/SplineHandlers.h"
|
|
30
|
+
#include "Handlers/PhysicsHandlers.h"
|
|
31
|
+
#include "Handlers/DemoHandlers.h"
|
|
32
|
+
|
|
33
|
+
// Platform-specific socket includes
|
|
34
|
+
#if PLATFORM_WINDOWS
|
|
35
|
+
#include "Windows/AllowWindowsPlatformTypes.h"
|
|
36
|
+
#include <winsock2.h>
|
|
37
|
+
#include <ws2tcpip.h>
|
|
38
|
+
#include "Windows/HideWindowsPlatformTypes.h"
|
|
39
|
+
#pragma comment(lib, "ws2_32.lib")
|
|
40
|
+
#elif PLATFORM_LINUX || PLATFORM_MAC
|
|
41
|
+
#include <sys/socket.h>
|
|
42
|
+
#include <netinet/in.h>
|
|
43
|
+
#include <arpa/inet.h>
|
|
44
|
+
#include <unistd.h>
|
|
45
|
+
#include <sys/select.h>
|
|
46
|
+
#endif
|
|
47
|
+
|
|
48
|
+
#include "Misc/Base64.h"
|
|
49
|
+
#if PLATFORM_WINDOWS
|
|
50
|
+
#include "Windows/AllowWindowsPlatformTypes.h"
|
|
51
|
+
#include <wincrypt.h>
|
|
52
|
+
#include "Windows/HideWindowsPlatformTypes.h"
|
|
53
|
+
#pragma comment(lib, "advapi32.lib")
|
|
54
|
+
#endif
|
|
55
|
+
|
|
56
|
+
FMCPBridgeServer::FMCPBridgeServer(int32 Port)
|
|
57
|
+
: ServerPort(Port)
|
|
58
|
+
, ServerThread(nullptr)
|
|
59
|
+
, bShouldStop(false)
|
|
60
|
+
, bIsRunning(false)
|
|
61
|
+
, ServerSocket(nullptr)
|
|
62
|
+
{
|
|
63
|
+
// Register core handlers
|
|
64
|
+
FEditorHandlers::RegisterHandlers(HandlerRegistry);
|
|
65
|
+
FAssetHandlers::RegisterHandlers(HandlerRegistry);
|
|
66
|
+
FBlueprintHandlers::RegisterHandlers(HandlerRegistry);
|
|
67
|
+
FLevelHandlers::RegisterHandlers(HandlerRegistry);
|
|
68
|
+
FReflectionHandlers::RegisterHandlers(HandlerRegistry);
|
|
69
|
+
FGasHandlers::RegisterHandlers(HandlerRegistry);
|
|
70
|
+
FGameplayHandlers::RegisterHandlers(HandlerRegistry);
|
|
71
|
+
FDialogHandlers::RegisterHandlers(HandlerRegistry);
|
|
72
|
+
FMaterialHandlers::RegisterHandlers(HandlerRegistry);
|
|
73
|
+
FAnimationHandlers::RegisterHandlers(HandlerRegistry);
|
|
74
|
+
FAudioHandlers::RegisterHandlers(HandlerRegistry);
|
|
75
|
+
FWidgetHandlers::RegisterHandlers(HandlerRegistry);
|
|
76
|
+
FFoliageHandlers::RegisterHandlers(HandlerRegistry);
|
|
77
|
+
FLandscapeHandlers::RegisterHandlers(HandlerRegistry);
|
|
78
|
+
FNetworkingHandlers::RegisterHandlers(HandlerRegistry);
|
|
79
|
+
FNiagaraHandlers::RegisterHandlers(HandlerRegistry);
|
|
80
|
+
FPCGHandlers::RegisterHandlers(HandlerRegistry);
|
|
81
|
+
FSequencerHandlers::RegisterHandlers(HandlerRegistry);
|
|
82
|
+
FSplineHandlers::RegisterHandlers(HandlerRegistry);
|
|
83
|
+
FPhysicsHandlers::RegisterHandlers(HandlerRegistry);
|
|
84
|
+
FDemoHandlers::RegisterHandlers(HandlerRegistry);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
FMCPBridgeServer::~FMCPBridgeServer()
|
|
88
|
+
{
|
|
89
|
+
Shutdown();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
bool FMCPBridgeServer::Start()
|
|
93
|
+
{
|
|
94
|
+
if (bIsRunning)
|
|
95
|
+
{
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
bShouldStop = false;
|
|
100
|
+
ServerThread = FRunnableThread::Create(this, TEXT("MCPBridgeServer"), 0, TPri_Normal);
|
|
101
|
+
return ServerThread != nullptr;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
void FMCPBridgeServer::Shutdown()
|
|
105
|
+
{
|
|
106
|
+
if (!bIsRunning)
|
|
107
|
+
{
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
bShouldStop = true;
|
|
112
|
+
|
|
113
|
+
if (ServerThread)
|
|
114
|
+
{
|
|
115
|
+
ServerThread->WaitForCompletion();
|
|
116
|
+
delete ServerThread;
|
|
117
|
+
ServerThread = nullptr;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
bIsRunning = false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
bool FMCPBridgeServer::Init()
|
|
124
|
+
{
|
|
125
|
+
bIsRunning = true;
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
uint32 FMCPBridgeServer::Run()
|
|
130
|
+
{
|
|
131
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Bridge server thread started on port %d"), ServerPort);
|
|
132
|
+
|
|
133
|
+
// Initialize platform sockets
|
|
134
|
+
#if PLATFORM_WINDOWS
|
|
135
|
+
WSADATA WsaData;
|
|
136
|
+
if (WSAStartup(MAKEWORD(2, 2), &WsaData) != 0)
|
|
137
|
+
{
|
|
138
|
+
UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to initialize Winsock"));
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
#endif
|
|
142
|
+
|
|
143
|
+
// Create server socket
|
|
144
|
+
#if PLATFORM_WINDOWS
|
|
145
|
+
SOCKET ServerSocketFD = socket(AF_INET, SOCK_STREAM, 0);
|
|
146
|
+
if (ServerSocketFD == INVALID_SOCKET)
|
|
147
|
+
#else
|
|
148
|
+
int32 ServerSocketFD = socket(AF_INET, SOCK_STREAM, 0);
|
|
149
|
+
if (ServerSocketFD < 0)
|
|
150
|
+
#endif
|
|
151
|
+
{
|
|
152
|
+
UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to create socket"));
|
|
153
|
+
#if PLATFORM_WINDOWS
|
|
154
|
+
WSACleanup();
|
|
155
|
+
#endif
|
|
156
|
+
return 1;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Set socket options
|
|
160
|
+
int32 ReuseAddr = 1;
|
|
161
|
+
setsockopt(ServerSocketFD, SOL_SOCKET, SO_REUSEADDR, (char*)&ReuseAddr, sizeof(ReuseAddr));
|
|
162
|
+
|
|
163
|
+
// Set TCP_NODELAY for immediate send (disable Nagle's algorithm)
|
|
164
|
+
int32 NoDelay = 1;
|
|
165
|
+
setsockopt(ServerSocketFD, IPPROTO_TCP, TCP_NODELAY, (char*)&NoDelay, sizeof(NoDelay));
|
|
166
|
+
|
|
167
|
+
// Bind socket
|
|
168
|
+
sockaddr_in ServerAddr;
|
|
169
|
+
FMemory::Memset(&ServerAddr, 0, sizeof(ServerAddr));
|
|
170
|
+
ServerAddr.sin_family = AF_INET;
|
|
171
|
+
ServerAddr.sin_addr.s_addr = INADDR_ANY;
|
|
172
|
+
ServerAddr.sin_port = htons(ServerPort);
|
|
173
|
+
|
|
174
|
+
if (bind(ServerSocketFD, (sockaddr*)&ServerAddr, sizeof(ServerAddr)) < 0)
|
|
175
|
+
{
|
|
176
|
+
int32 ErrorCode = 0;
|
|
177
|
+
#if PLATFORM_WINDOWS
|
|
178
|
+
ErrorCode = WSAGetLastError();
|
|
179
|
+
UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to bind socket to port %d, error: %d"), ServerPort, ErrorCode);
|
|
180
|
+
closesocket(ServerSocketFD);
|
|
181
|
+
WSACleanup();
|
|
182
|
+
#else
|
|
183
|
+
UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to bind socket to port %d"), ServerPort);
|
|
184
|
+
close(ServerSocketFD);
|
|
185
|
+
#endif
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Listen
|
|
190
|
+
if (listen(ServerSocketFD, 5) < 0)
|
|
191
|
+
{
|
|
192
|
+
int32 ErrorCode = 0;
|
|
193
|
+
#if PLATFORM_WINDOWS
|
|
194
|
+
ErrorCode = WSAGetLastError();
|
|
195
|
+
UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to listen on socket, error: %d"), ErrorCode);
|
|
196
|
+
closesocket(ServerSocketFD);
|
|
197
|
+
WSACleanup();
|
|
198
|
+
#else
|
|
199
|
+
UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to listen on socket"));
|
|
200
|
+
close(ServerSocketFD);
|
|
201
|
+
#endif
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Bridge listening on ws://localhost:%d"), ServerPort);
|
|
206
|
+
bIsRunning = true;
|
|
207
|
+
|
|
208
|
+
// Accept connections
|
|
209
|
+
while (!bShouldStop)
|
|
210
|
+
{
|
|
211
|
+
fd_set ReadSet;
|
|
212
|
+
FD_ZERO(&ReadSet);
|
|
213
|
+
FD_SET(ServerSocketFD, &ReadSet);
|
|
214
|
+
|
|
215
|
+
timeval Timeout;
|
|
216
|
+
Timeout.tv_sec = 1;
|
|
217
|
+
Timeout.tv_usec = 0;
|
|
218
|
+
|
|
219
|
+
int32 SelectResult = select(ServerSocketFD + 1, &ReadSet, nullptr, nullptr, &Timeout);
|
|
220
|
+
#if PLATFORM_WINDOWS
|
|
221
|
+
if (SelectResult > 0 && FD_ISSET(ServerSocketFD, &ReadSet))
|
|
222
|
+
#else
|
|
223
|
+
if (SelectResult > 0 && FD_ISSET(ServerSocketFD, &ReadSet))
|
|
224
|
+
#endif
|
|
225
|
+
{
|
|
226
|
+
sockaddr_in ClientAddr;
|
|
227
|
+
socklen_t ClientAddrLen = sizeof(ClientAddr);
|
|
228
|
+
#if PLATFORM_WINDOWS
|
|
229
|
+
SOCKET ClientSocketFD = accept(ServerSocketFD, (sockaddr*)&ClientAddr, &ClientAddrLen);
|
|
230
|
+
if (ClientSocketFD != INVALID_SOCKET)
|
|
231
|
+
{
|
|
232
|
+
#else
|
|
233
|
+
int32 ClientSocketFD = accept(ServerSocketFD, (sockaddr*)&ClientAddr, &ClientAddrLen);
|
|
234
|
+
if (ClientSocketFD >= 0)
|
|
235
|
+
{
|
|
236
|
+
#endif
|
|
237
|
+
char AddrStr[INET_ADDRSTRLEN];
|
|
238
|
+
inet_ntop(AF_INET, &ClientAddr.sin_addr, AddrStr, INET_ADDRSTRLEN);
|
|
239
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Client connected from %s:%d"),
|
|
240
|
+
ANSI_TO_TCHAR(AddrStr), ntohs(ClientAddr.sin_port));
|
|
241
|
+
|
|
242
|
+
// Handle each WebSocket connection in its own thread
|
|
243
|
+
Async(EAsyncExecution::Thread, [this, ClientSocketFD]() {
|
|
244
|
+
HandleWebSocketConnection(ClientSocketFD);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Cleanup
|
|
251
|
+
#if PLATFORM_WINDOWS
|
|
252
|
+
closesocket(ServerSocketFD);
|
|
253
|
+
WSACleanup();
|
|
254
|
+
#else
|
|
255
|
+
close(ServerSocketFD);
|
|
256
|
+
#endif
|
|
257
|
+
|
|
258
|
+
bIsRunning = false;
|
|
259
|
+
return 0;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
void FMCPBridgeServer::Stop()
|
|
263
|
+
{
|
|
264
|
+
bShouldStop = true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
void FMCPBridgeServer::Exit()
|
|
268
|
+
{
|
|
269
|
+
bIsRunning = false;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
TSharedPtr<FJsonObject> FMCPBridgeServer::ParseJsonRpcRequest(const FString& Message)
|
|
273
|
+
{
|
|
274
|
+
TSharedPtr<FJsonObject> JsonObject;
|
|
275
|
+
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Message);
|
|
276
|
+
|
|
277
|
+
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
|
|
278
|
+
{
|
|
279
|
+
return JsonObject;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return nullptr;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
FString FMCPBridgeServer::CreateJsonRpcResponse(const TSharedPtr<FJsonObject>& Request, const TSharedPtr<FJsonValue>& Result)
|
|
286
|
+
{
|
|
287
|
+
TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
|
|
288
|
+
Response->SetStringField(TEXT("jsonrpc"), TEXT("2.0"));
|
|
289
|
+
|
|
290
|
+
if (Request.IsValid() && Request->HasField(TEXT("id")))
|
|
291
|
+
{
|
|
292
|
+
Response->SetField(TEXT("id"), Request->TryGetField(TEXT("id")));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
Response->SetField(TEXT("result"), Result);
|
|
296
|
+
|
|
297
|
+
FString OutputString;
|
|
298
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
|
|
299
|
+
FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
|
|
300
|
+
return OutputString;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
FString FMCPBridgeServer::CreateJsonRpcError(const TSharedPtr<FJsonObject>& Request, int32 ErrorCode, const FString& ErrorMessage)
|
|
304
|
+
{
|
|
305
|
+
TSharedPtr<FJsonObject> Response = MakeShared<FJsonObject>();
|
|
306
|
+
Response->SetStringField(TEXT("jsonrpc"), TEXT("2.0"));
|
|
307
|
+
|
|
308
|
+
if (Request.IsValid() && Request->HasField(TEXT("id")))
|
|
309
|
+
{
|
|
310
|
+
Response->SetField(TEXT("id"), Request->TryGetField(TEXT("id")));
|
|
311
|
+
}
|
|
312
|
+
else
|
|
313
|
+
{
|
|
314
|
+
Response->SetField(TEXT("id"), MakeShared<FJsonValueNull>());
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
TSharedPtr<FJsonObject> ErrorObject = MakeShared<FJsonObject>();
|
|
318
|
+
ErrorObject->SetNumberField(TEXT("code"), ErrorCode);
|
|
319
|
+
ErrorObject->SetStringField(TEXT("message"), ErrorMessage);
|
|
320
|
+
Response->SetObjectField(TEXT("error"), ErrorObject);
|
|
321
|
+
|
|
322
|
+
FString OutputString;
|
|
323
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
|
|
324
|
+
FJsonSerializer::Serialize(Response.ToSharedRef(), Writer);
|
|
325
|
+
return OutputString;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
FString FMCPBridgeServer::ProcessMessage(const FString& Message)
|
|
329
|
+
{
|
|
330
|
+
TSharedPtr<FJsonObject> Request = ParseJsonRpcRequest(Message);
|
|
331
|
+
if (!Request.IsValid())
|
|
332
|
+
{
|
|
333
|
+
return CreateJsonRpcError(nullptr, -32700, TEXT("Parse error"));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
FString Method;
|
|
337
|
+
if (!Request->TryGetStringField(TEXT("method"), Method))
|
|
338
|
+
{
|
|
339
|
+
return CreateJsonRpcError(Request, -32600, TEXT("Invalid Request"));
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Processing method: %s"), *Method);
|
|
343
|
+
|
|
344
|
+
TSharedPtr<FJsonObject> Params;
|
|
345
|
+
if (Request->HasField(TEXT("params")))
|
|
346
|
+
{
|
|
347
|
+
TSharedPtr<FJsonValue> ParamsValue = Request->TryGetField(TEXT("params"));
|
|
348
|
+
if (ParamsValue.IsValid() && ParamsValue->Type == EJson::Object)
|
|
349
|
+
{
|
|
350
|
+
Params = ParamsValue->AsObject();
|
|
351
|
+
}
|
|
352
|
+
else
|
|
353
|
+
{
|
|
354
|
+
Params = MakeShared<FJsonObject>();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else
|
|
358
|
+
{
|
|
359
|
+
Params = MakeShared<FJsonObject>();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Execute handler on game thread
|
|
363
|
+
FMCPHandlerRegistry::FHandlerFunction Handler = [this, Method](const TSharedPtr<FJsonObject>& HandlerParams) -> TSharedPtr<FJsonValue>
|
|
364
|
+
{
|
|
365
|
+
return HandlerRegistry.ExecuteHandler(Method, HandlerParams);
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
TSharedPtr<FJsonValue> Result = GameThreadExecutor.ExecuteOnGameThread(Handler, Params);
|
|
369
|
+
|
|
370
|
+
if (Result.IsValid())
|
|
371
|
+
{
|
|
372
|
+
return CreateJsonRpcResponse(Request, Result);
|
|
373
|
+
}
|
|
374
|
+
else
|
|
375
|
+
{
|
|
376
|
+
return CreateJsonRpcError(Request, -32601, FString::Printf(TEXT("Unknown method: %s"), *Method));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
#if PLATFORM_WINDOWS
|
|
381
|
+
void FMCPBridgeServer::HandleWebSocketConnection(SOCKET ClientSocketFD)
|
|
382
|
+
#else
|
|
383
|
+
void FMCPBridgeServer::HandleWebSocketConnection(int32 ClientSocketFD)
|
|
384
|
+
#endif
|
|
385
|
+
{
|
|
386
|
+
// Set TCP_NODELAY on client socket for immediate send
|
|
387
|
+
int32 NoDelay = 1;
|
|
388
|
+
setsockopt(ClientSocketFD, IPPROTO_TCP, TCP_NODELAY, (char*)&NoDelay, sizeof(NoDelay));
|
|
389
|
+
|
|
390
|
+
// Perform WebSocket handshake
|
|
391
|
+
FString Response = PerformWebSocketHandshake(ClientSocketFD);
|
|
392
|
+
if (Response.IsEmpty())
|
|
393
|
+
{
|
|
394
|
+
#if PLATFORM_WINDOWS
|
|
395
|
+
closesocket(ClientSocketFD);
|
|
396
|
+
#else
|
|
397
|
+
close(ClientSocketFD);
|
|
398
|
+
#endif
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Send handshake response
|
|
403
|
+
// HTTP headers are ASCII, FString uses TCHAR (which is wchar_t on Windows)
|
|
404
|
+
// Convert to UTF-8 bytes for network transmission
|
|
405
|
+
FTCHARToUTF8 UTF8Response(*Response);
|
|
406
|
+
const char* ResponseBytes = (const char*)UTF8Response.Get();
|
|
407
|
+
int32 TotalBytes = UTF8Response.Length();
|
|
408
|
+
|
|
409
|
+
// Send response - ensure all bytes are sent
|
|
410
|
+
int32 SentBytes = 0;
|
|
411
|
+
while (SentBytes < TotalBytes)
|
|
412
|
+
{
|
|
413
|
+
int32 BytesSent = send(ClientSocketFD, ResponseBytes + SentBytes, TotalBytes - SentBytes, 0);
|
|
414
|
+
if (BytesSent < 0)
|
|
415
|
+
{
|
|
416
|
+
int32 ErrorCode = 0;
|
|
417
|
+
#if PLATFORM_WINDOWS
|
|
418
|
+
ErrorCode = WSAGetLastError();
|
|
419
|
+
UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to send WebSocket handshake response, error: %d"), ErrorCode);
|
|
420
|
+
closesocket(ClientSocketFD);
|
|
421
|
+
#else
|
|
422
|
+
UE_LOG(LogMCPBridge, Error, TEXT("[UE-MCP] Failed to send WebSocket handshake response"));
|
|
423
|
+
close(ClientSocketFD);
|
|
424
|
+
#endif
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
SentBytes += BytesSent;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Sent WebSocket handshake response (%d/%d bytes)"), SentBytes, TotalBytes);
|
|
431
|
+
|
|
432
|
+
// Small delay to ensure response is fully sent and received by client
|
|
433
|
+
FPlatformProcess::Sleep(0.01f); // 10ms
|
|
434
|
+
|
|
435
|
+
// Process WebSocket messages
|
|
436
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Starting WebSocket message processing"));
|
|
437
|
+
ProcessWebSocketMessages(ClientSocketFD);
|
|
438
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] WebSocket message processing ended"));
|
|
439
|
+
|
|
440
|
+
#if PLATFORM_WINDOWS
|
|
441
|
+
closesocket(ClientSocketFD);
|
|
442
|
+
#else
|
|
443
|
+
close(ClientSocketFD);
|
|
444
|
+
#endif
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
#if PLATFORM_WINDOWS
|
|
448
|
+
FString FMCPBridgeServer::PerformWebSocketHandshake(SOCKET ClientSocketFD)
|
|
449
|
+
#else
|
|
450
|
+
FString FMCPBridgeServer::PerformWebSocketHandshake(int32 ClientSocketFD)
|
|
451
|
+
#endif
|
|
452
|
+
{
|
|
453
|
+
FString Request = ReadHttpRequest(ClientSocketFD);
|
|
454
|
+
if (Request.IsEmpty())
|
|
455
|
+
{
|
|
456
|
+
return TEXT("");
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Extract WebSocket-Key from request (case-insensitive search)
|
|
460
|
+
FString WebSocketKey;
|
|
461
|
+
int32 KeyStart = Request.Find(TEXT("Sec-WebSocket-Key:"), ESearchCase::IgnoreCase);
|
|
462
|
+
if (KeyStart != INDEX_NONE)
|
|
463
|
+
{
|
|
464
|
+
// Skip past the header name and any whitespace
|
|
465
|
+
int32 ValueStart = KeyStart + 18; // Length of "Sec-WebSocket-Key:"
|
|
466
|
+
while (ValueStart < Request.Len() && (Request[ValueStart] == TEXT(' ') || Request[ValueStart] == TEXT('\t')))
|
|
467
|
+
{
|
|
468
|
+
ValueStart++;
|
|
469
|
+
}
|
|
470
|
+
int32 KeyEnd = Request.Find(TEXT("\r\n"), ESearchCase::CaseSensitive, ESearchDir::FromStart, ValueStart);
|
|
471
|
+
if (KeyEnd != INDEX_NONE)
|
|
472
|
+
{
|
|
473
|
+
WebSocketKey = Request.Mid(ValueStart, KeyEnd - ValueStart).TrimStartAndEnd();
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Extracted WebSocket-Key: %s"), *WebSocketKey);
|
|
478
|
+
|
|
479
|
+
if (WebSocketKey.IsEmpty())
|
|
480
|
+
{
|
|
481
|
+
return TEXT("");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Create accept key
|
|
485
|
+
FString AcceptKey = CreateWebSocketAcceptKey(WebSocketKey);
|
|
486
|
+
|
|
487
|
+
// Build response (WebSocket spec requires exact format)
|
|
488
|
+
// Must be: HTTP/1.1 101 Switching Protocols\r\n
|
|
489
|
+
// Upgrade: websocket\r\n
|
|
490
|
+
// Connection: Upgrade\r\n
|
|
491
|
+
// Sec-WebSocket-Accept: <key>\r\n
|
|
492
|
+
// \r\n
|
|
493
|
+
FString Response = TEXT("HTTP/1.1 101 Switching Protocols\r\n");
|
|
494
|
+
Response += TEXT("Upgrade: websocket\r\n");
|
|
495
|
+
Response += TEXT("Connection: Upgrade\r\n");
|
|
496
|
+
Response += FString::Printf(TEXT("Sec-WebSocket-Accept: %s\r\n"), *AcceptKey);
|
|
497
|
+
Response += TEXT("\r\n");
|
|
498
|
+
|
|
499
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Accept key: %s"), *AcceptKey);
|
|
500
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Response length: %d chars"), Response.Len());
|
|
501
|
+
|
|
502
|
+
return Response;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
#if PLATFORM_WINDOWS
|
|
506
|
+
FString FMCPBridgeServer::ReadHttpRequest(SOCKET SocketFD)
|
|
507
|
+
#else
|
|
508
|
+
FString FMCPBridgeServer::ReadHttpRequest(int32 SocketFD)
|
|
509
|
+
#endif
|
|
510
|
+
{
|
|
511
|
+
// Read HTTP request headers (until \r\n\r\n)
|
|
512
|
+
FString Request;
|
|
513
|
+
TArray<uint8> Buffer;
|
|
514
|
+
Buffer.SetNum(4096);
|
|
515
|
+
|
|
516
|
+
// Use select to wait for data with timeout
|
|
517
|
+
fd_set ReadSet;
|
|
518
|
+
FD_ZERO(&ReadSet);
|
|
519
|
+
FD_SET(SocketFD, &ReadSet);
|
|
520
|
+
|
|
521
|
+
timeval Timeout;
|
|
522
|
+
Timeout.tv_sec = 5; // 5 second timeout
|
|
523
|
+
Timeout.tv_usec = 0;
|
|
524
|
+
|
|
525
|
+
int32 SelectResult = select(SocketFD + 1, &ReadSet, nullptr, nullptr, &Timeout);
|
|
526
|
+
if (SelectResult <= 0 || !FD_ISSET(SocketFD, &ReadSet))
|
|
527
|
+
{
|
|
528
|
+
UE_LOG(LogMCPBridge, Warning, TEXT("[UE-MCP] Timeout waiting for HTTP request"));
|
|
529
|
+
return TEXT("");
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Read data
|
|
533
|
+
int32 BytesReceived = recv(SocketFD, (char*)Buffer.GetData(), Buffer.Num(), 0);
|
|
534
|
+
if (BytesReceived <= 0)
|
|
535
|
+
{
|
|
536
|
+
UE_LOG(LogMCPBridge, Warning, TEXT("[UE-MCP] Failed to read HTTP request"));
|
|
537
|
+
return TEXT("");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
Buffer.SetNum(BytesReceived);
|
|
541
|
+
Request = FString(ANSI_TO_TCHAR((char*)Buffer.GetData()));
|
|
542
|
+
|
|
543
|
+
UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Read HTTP request (%d bytes):\n%s"), BytesReceived, *Request.Left(200));
|
|
544
|
+
|
|
545
|
+
return Request;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
FString FMCPBridgeServer::CreateWebSocketAcceptKey(const FString& ClientKey)
|
|
549
|
+
{
|
|
550
|
+
// WebSocket accept key = base64(sha1(client_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
|
|
551
|
+
FString MagicString = TEXT("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
|
552
|
+
FString Combined = ClientKey + MagicString;
|
|
553
|
+
|
|
554
|
+
// Compute SHA1 hash (20 bytes) using Windows CryptoAPI
|
|
555
|
+
FTCHARToUTF8 UTF8String(*Combined);
|
|
556
|
+
uint8 HashBytes[20];
|
|
557
|
+
|
|
558
|
+
#if PLATFORM_WINDOWS
|
|
559
|
+
HCRYPTPROV hProv = 0;
|
|
560
|
+
HCRYPTHASH hHash = 0;
|
|
561
|
+
if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
|
|
562
|
+
{
|
|
563
|
+
if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash))
|
|
564
|
+
{
|
|
565
|
+
CryptHashData(hHash, (BYTE*)UTF8String.Get(), UTF8String.Length(), 0);
|
|
566
|
+
DWORD HashLen = 20;
|
|
567
|
+
CryptGetHashParam(hHash, HP_HASHVAL, HashBytes, &HashLen, 0);
|
|
568
|
+
CryptDestroyHash(hHash);
|
|
569
|
+
}
|
|
570
|
+
CryptReleaseContext(hProv, 0);
|
|
571
|
+
}
|
|
572
|
+
#else
|
|
573
|
+
// Fallback: use a simple hash (not secure, but works for WebSocket handshake)
|
|
574
|
+
uint32 Hash = 0;
|
|
575
|
+
for (int32 i = 0; i < UTF8String.Length(); ++i)
|
|
576
|
+
{
|
|
577
|
+
Hash = Hash * 31 + UTF8String.Get()[i];
|
|
578
|
+
}
|
|
579
|
+
FMemory::Memcpy(HashBytes, &Hash, 20);
|
|
580
|
+
#endif
|
|
581
|
+
|
|
582
|
+
// Base64 encode
|
|
583
|
+
FString AcceptKey = FBase64::Encode(HashBytes, 20);
|
|
584
|
+
return AcceptKey;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
#if PLATFORM_WINDOWS
|
|
588
|
+
void FMCPBridgeServer::ProcessWebSocketMessages(SOCKET ClientSocketFD)
|
|
589
|
+
#else
|
|
590
|
+
void FMCPBridgeServer::ProcessWebSocketMessages(int32 ClientSocketFD)
|
|
591
|
+
#endif
|
|
592
|
+
{
|
|
593
|
+
constexpr int32 RecvBufferSize = 65536;
|
|
594
|
+
TArray<uint8> Buffer;
|
|
595
|
+
Buffer.SetNumUninitialized(RecvBufferSize);
|
|
596
|
+
|
|
597
|
+
while (!bShouldStop)
|
|
598
|
+
{
|
|
599
|
+
fd_set ReadSet;
|
|
600
|
+
FD_ZERO(&ReadSet);
|
|
601
|
+
FD_SET(ClientSocketFD, &ReadSet);
|
|
602
|
+
|
|
603
|
+
timeval Timeout;
|
|
604
|
+
Timeout.tv_sec = 1;
|
|
605
|
+
Timeout.tv_usec = 0;
|
|
606
|
+
|
|
607
|
+
int32 SelectResult = select(ClientSocketFD + 1, &ReadSet, nullptr, nullptr, &Timeout);
|
|
608
|
+
|
|
609
|
+
if (SelectResult > 0 && FD_ISSET(ClientSocketFD, &ReadSet))
|
|
610
|
+
{
|
|
611
|
+
int32 BytesReceived = recv(ClientSocketFD, (char*)Buffer.GetData(), RecvBufferSize, 0);
|
|
612
|
+
if (BytesReceived <= 0)
|
|
613
|
+
{
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
TArray<uint8> FrameData(Buffer.GetData(), BytesReceived);
|
|
618
|
+
FString Message = ParseWebSocketFrame(FrameData);
|
|
619
|
+
|
|
620
|
+
if (!Message.IsEmpty())
|
|
621
|
+
{
|
|
622
|
+
FString Response = ProcessMessage(Message);
|
|
623
|
+
TArray<uint8> ResponseFrame = CreateWebSocketFrame(Response);
|
|
624
|
+
int32 TotalToSend = ResponseFrame.Num();
|
|
625
|
+
int32 Sent = 0;
|
|
626
|
+
while (Sent < TotalToSend)
|
|
627
|
+
{
|
|
628
|
+
int32 BytesSent = send(ClientSocketFD, (char*)ResponseFrame.GetData() + Sent, TotalToSend - Sent, 0);
|
|
629
|
+
if (BytesSent <= 0) break;
|
|
630
|
+
Sent += BytesSent;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
else if (SelectResult < 0)
|
|
635
|
+
{
|
|
636
|
+
break;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
TArray<uint8> FMCPBridgeServer::CreateWebSocketFrame(const FString& Message)
|
|
642
|
+
{
|
|
643
|
+
// Simple WebSocket frame creation (text frame, no masking)
|
|
644
|
+
TArray<uint8> Frame;
|
|
645
|
+
|
|
646
|
+
// Convert to UTF-8 first to get correct byte length
|
|
647
|
+
FTCHARToUTF8 UTF8String(*Message);
|
|
648
|
+
int32 MessageLen = UTF8String.Length();
|
|
649
|
+
|
|
650
|
+
// Frame header
|
|
651
|
+
uint8 FirstByte = 0x81; // FIN + text frame
|
|
652
|
+
Frame.Add(FirstByte);
|
|
653
|
+
|
|
654
|
+
if (MessageLen < 126)
|
|
655
|
+
{
|
|
656
|
+
Frame.Add(MessageLen);
|
|
657
|
+
}
|
|
658
|
+
else if (MessageLen < 65536)
|
|
659
|
+
{
|
|
660
|
+
Frame.Add(126);
|
|
661
|
+
Frame.Add((MessageLen >> 8) & 0xFF);
|
|
662
|
+
Frame.Add(MessageLen & 0xFF);
|
|
663
|
+
}
|
|
664
|
+
else
|
|
665
|
+
{
|
|
666
|
+
Frame.Add(127);
|
|
667
|
+
for (int32 i = 7; i >= 0; --i)
|
|
668
|
+
{
|
|
669
|
+
Frame.Add((MessageLen >> (i * 8)) & 0xFF);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Message payload (UTF-8 bytes)
|
|
674
|
+
Frame.Append((uint8*)UTF8String.Get(), MessageLen);
|
|
675
|
+
|
|
676
|
+
return Frame;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
FString FMCPBridgeServer::ParseWebSocketFrame(const TArray<uint8>& Data)
|
|
680
|
+
{
|
|
681
|
+
if (Data.Num() < 2)
|
|
682
|
+
{
|
|
683
|
+
return TEXT("");
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
uint8 FirstByte = Data[0];
|
|
687
|
+
uint8 SecondByte = Data[1];
|
|
688
|
+
|
|
689
|
+
bool bMasked = (SecondByte & 0x80) != 0;
|
|
690
|
+
int32 PayloadLen = SecondByte & 0x7F;
|
|
691
|
+
|
|
692
|
+
int32 HeaderLen = 2;
|
|
693
|
+
if (PayloadLen == 126)
|
|
694
|
+
{
|
|
695
|
+
if (Data.Num() < 4)
|
|
696
|
+
{
|
|
697
|
+
return TEXT("");
|
|
698
|
+
}
|
|
699
|
+
PayloadLen = (Data[2] << 8) | Data[3];
|
|
700
|
+
HeaderLen = 4;
|
|
701
|
+
}
|
|
702
|
+
else if (PayloadLen == 127)
|
|
703
|
+
{
|
|
704
|
+
if (Data.Num() < 10)
|
|
705
|
+
{
|
|
706
|
+
return TEXT("");
|
|
707
|
+
}
|
|
708
|
+
PayloadLen = 0;
|
|
709
|
+
for (int32 i = 0; i < 8; ++i)
|
|
710
|
+
{
|
|
711
|
+
PayloadLen = (PayloadLen << 8) | Data[2 + i];
|
|
712
|
+
}
|
|
713
|
+
HeaderLen = 10;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
if (bMasked)
|
|
717
|
+
{
|
|
718
|
+
HeaderLen += 4; // Masking key
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
if (Data.Num() < HeaderLen + PayloadLen)
|
|
722
|
+
{
|
|
723
|
+
return TEXT("");
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
TArray<uint8> Payload;
|
|
727
|
+
Payload.Append(Data.GetData() + HeaderLen, PayloadLen);
|
|
728
|
+
|
|
729
|
+
if (bMasked)
|
|
730
|
+
{
|
|
731
|
+
// Unmask payload
|
|
732
|
+
uint8 MaskKey[4];
|
|
733
|
+
MaskKey[0] = Data[HeaderLen - 4];
|
|
734
|
+
MaskKey[1] = Data[HeaderLen - 3];
|
|
735
|
+
MaskKey[2] = Data[HeaderLen - 2];
|
|
736
|
+
MaskKey[3] = Data[HeaderLen - 1];
|
|
737
|
+
|
|
738
|
+
for (int32 i = 0; i < Payload.Num(); ++i)
|
|
739
|
+
{
|
|
740
|
+
Payload[i] ^= MaskKey[i % 4];
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
FUTF8ToTCHAR UTF8ToTCHAR((char*)Payload.GetData(), Payload.Num());
|
|
745
|
+
return FString(UTF8ToTCHAR.Length(), UTF8ToTCHAR.Get());
|
|
746
|
+
}
|