chatpipe.cpp
1 //------------------------------------------------------------------------- 2 /* 3 Copyright (C) 2010-2019 EDuke32 developers and contributors 4 Copyright (C) 2023, 2023 - Jordon Moss (Aka. StrikerTheHedgefox) 5 6 This file is part of NBlood. 7 8 NBlood is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License version 2 10 as published by the Free Software Foundation. 11 12 This program is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 15 16 See the GNU General Public License for more details. 17 18 You should have received a copy of the GNU General Public License 19 along with this program; if not, write to the Free Software 20 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 */ 22 //------------------------------------------------------------------------- 23 #include <stdlib.h> 24 #include <string.h> 25 #include "compat.h" 26 #include "build.h" 27 #include "common.h" 28 #include "common_game.h" 29 #include "sjson.h" 30 31 #include "chatpipe.h" 32 #include "view.h" 33 34 #ifdef NETCODE_DISABLE // non-netcode build 35 void ChatPipe_Create(void) 36 { 37 return; 38 } 39 40 void ChatPipe_SendMessage(const char* message) 41 { 42 UNREFERENCED_PARAMETER(message); 43 } 44 45 void ChatPipe_Poll(void) 46 { 47 return; 48 } 49 #else 50 51 #define CHATPIPE_BUFSIZE 4096 52 #define CHATPIPE_TIMEOUT 5000 53 54 // Global vars 55 char cpTempBuf[CHATPIPE_BUFSIZE] = ""; 56 char bPipeActive = 0; 57 58 // Platform-agnostic functions. 59 static void ChatPipe_WriteToPipe(const char* message); 60 61 static bool ChatPipe_ParseJSON(const char* data) 62 { 63 sjson_context* jsonCtx = sjson_create_context(0, 0, NULL); 64 sjson_node* jRoot = sjson_decode(jsonCtx, data); 65 bool success = false; 66 67 if (jRoot) 68 { 69 const char* strEvent = sjson_get_string(jRoot, "event", NULL); 70 if (!Bstrcasecmp(strEvent, "message")) 71 { 72 const char* strUser = sjson_get_string(jRoot, "user", NULL); 73 const char* strText = sjson_get_string(jRoot, "text", NULL); 74 75 snprintf(cpTempBuf, kMaxMessageTextLength, "\r[LOBBY]\r \r%s\r: %s\n", strUser, strText); 76 success = true; 77 } 78 else if (!Bstrcasecmp(strEvent, "join")) 79 { 80 const char* strUser = sjson_get_string(jRoot, "user", NULL); 81 82 snprintf(cpTempBuf, kMaxMessageTextLength, "\r[LOBBY]\r \r%s\r has joined the lobby.\n", strUser); 83 success = true; 84 } 85 else if (!Bstrcasecmp(strEvent, "leave")) 86 { 87 const char* strUser = sjson_get_string(jRoot, "user", NULL); 88 89 snprintf(cpTempBuf, kMaxMessageTextLength, "\r[LOBBY]\r \r%s\r has left the lobby.\n", strUser); 90 success = true; 91 } 92 93 if (success) 94 viewSetMessageColor(cpTempBuf, 0, MESSAGE_PRIORITY_NORMAL, gColorMsg ? 9 : 0, gColorMsg ? kFlagBluePal : 0); 95 sjson_delete_node(jsonCtx, jRoot); 96 } 97 98 #ifdef DEBUGGINGAIDS 99 if (success) 100 LOG_F(INFO, "%s: JSON parse succeded!", __func__); 101 else 102 LOG_F(INFO, "%s: JSON parse failed, or unknown message!", __func__); 103 #endif 104 105 sjson_destroy_context(jsonCtx); 106 return success; 107 } 108 109 void ChatPipe_SendMessage(const char* message) 110 { 111 if (!bPipeActive) 112 return; 113 114 sjson_context* jsonCtx = sjson_create_context(0, 0, NULL); 115 sjson_node* jRoot = sjson_mkobject(jsonCtx); 116 117 sjson_put_string(jsonCtx, jRoot, "event", "message"); 118 //sjson_put_string(jsonCtx, jRoot, "user", g_player[myconnectindex].user_name); 119 OSD_StripColors(cpTempBuf, message); 120 sjson_put_string(jsonCtx, jRoot, "text", cpTempBuf); 121 122 char* jEncoded = sjson_stringify(jsonCtx, jRoot, NULL); 123 ChatPipe_WriteToPipe(jEncoded); 124 125 sjson_free_string(jsonCtx, jEncoded); 126 sjson_delete_node(jsonCtx, jRoot); 127 sjson_destroy_context(jsonCtx); 128 } 129 130 #if defined(_WIN32) // Windows implementation 131 struct { 132 HANDLE handle; 133 OVERLAPPED overlapRead, overlapWrite; 134 DWORD state; 135 DWORD bytesRead; 136 DWORD bytesToWrite; 137 char readBuffer[CHATPIPE_BUFSIZE]; 138 char writeBuffer[CHATPIPE_BUFSIZE]; 139 int readFlag; 140 int writeFlag; 141 } Pipe; 142 143 HANDLE waitEvent; 144 145 enum chatPipeState_t 146 { 147 PIPESTATE_CONNECTING, 148 PIPESTATE_READY, 149 }; 150 151 static bool ChatPipe_Connect(void) 152 { 153 bool pipeConnected = ConnectNamedPipe(Pipe.handle, &Pipe.overlapRead); 154 155 DWORD error = GetLastError(); 156 if (pipeConnected) // Overlapped ConnectNamedPipe should return zero 157 { 158 LOG_F(ERROR, "%s: Chat pipe connection failed with %ld", __func__, error); 159 return false; 160 } 161 162 if (error == ERROR_IO_PENDING) 163 { 164 LOG_F(INFO, "%s: Chat pipe connection pending...", __func__); 165 return true; 166 } 167 else if (error == ERROR_PIPE_CONNECTED) 168 { 169 //a client has already connected between creating the pipe and 170 //waiting for a client to connect. This is rare but can happen. 171 //When it does, the overlapped wait does not begin, so the overlapped 172 //event would not be signalled. We trigger it manually so that the normal 173 //workflow continues 174 SetEvent(Pipe.overlapRead.hEvent); 175 return false; 176 } 177 else 178 { 179 LOG_F(ERROR, "%s: Chat pipe connection failed with %ld", __func__, error); 180 return false; 181 } 182 183 return false; 184 } 185 186 static void ChatPipe_Reconnect() 187 { 188 // Disconnect the pipe instance. 189 190 if (!DisconnectNamedPipe(Pipe.handle)) 191 { 192 LOG_F(ERROR, "%s: DisconnectNamedPipe failed with %ld.", __func__, GetLastError()); 193 } 194 195 // Call a subroutine to connect to the new client. 196 Pipe.state = ChatPipe_Connect() ? PIPESTATE_CONNECTING : PIPESTATE_READY; 197 Pipe.readFlag = 0; 198 Pipe.writeFlag = 0; 199 } 200 201 void ChatPipe_Create(void) 202 { 203 bPipeActive = 0; 204 205 if (Pipe.handle != NULL) 206 { 207 LOG_F(ERROR, "%s: Already have a chat pipe!", __func__); 208 return; 209 } 210 211 Pipe.handle = CreateNamedPipe("\\\\.\\pipe\\NotBlood", 212 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 213 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, 214 1, 215 CHATPIPE_BUFSIZE, 216 CHATPIPE_BUFSIZE, 217 CHATPIPE_TIMEOUT, 218 NULL); 219 220 if ((Pipe.handle == NULL) || (Pipe.handle == INVALID_HANDLE_VALUE)) 221 { 222 LOG_F(ERROR, "%s: Chat pipe creation failed", __func__); 223 return; 224 } 225 226 waitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 227 Pipe.overlapRead.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 228 Pipe.overlapWrite.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); 229 230 Pipe.state = ChatPipe_Connect() ? PIPESTATE_CONNECTING : PIPESTATE_READY; 231 Pipe.readFlag = 0; 232 Pipe.writeFlag = 0; 233 234 bPipeActive = 1; 235 } 236 237 static void ChatPipe_WriteToPipe(const char* message) 238 { 239 DWORD byteCnt; 240 241 snprintf(Pipe.writeBuffer, sizeof(Pipe.writeBuffer), "%s", message); 242 Pipe.bytesToWrite = (strlen(Pipe.writeBuffer)) * sizeof(char); 243 244 bool success = WriteFile( 245 Pipe.handle, 246 Pipe.writeBuffer, 247 Pipe.bytesToWrite, 248 &byteCnt, 249 &Pipe.overlapWrite); 250 251 DWORD error_code = GetLastError(); 252 253 if (!success && error_code != ERROR_IO_PENDING) 254 { 255 LOG_F(ERROR, "%s: WriteFile failed, error code: %ld", __func__, error_code); 256 ChatPipe_Reconnect(); // An error occurred; disconnect from the client. 257 return; 258 } 259 260 Pipe.writeFlag++; 261 } 262 263 static void ChatPipe_ReadFromPipe() 264 { 265 bool success = ReadFile( 266 Pipe.handle, 267 Pipe.readBuffer, 268 CHATPIPE_BUFSIZE, 269 &Pipe.bytesRead, 270 &Pipe.overlapRead); 271 272 DWORD error_code = ::GetLastError(); 273 if (!success && error_code != ERROR_IO_PENDING) 274 { 275 LOG_F(ERROR, "%s: ReadFile failed, error code: %ld", __func__, error_code); 276 ChatPipe_Reconnect(); // An error occurred; disconnect from the client. 277 return; 278 } 279 280 Pipe.readFlag++; 281 282 return; 283 } 284 285 void ChatPipe_OnWriteComplete() 286 { 287 Pipe.writeFlag--; 288 289 #ifdef DEBUGGINGAIDS 290 LOG_F(INFO, "%s: Write completed. (%ld bytes)", __func__, Pipe.overlapWrite.InternalHigh); 291 #endif 292 } 293 294 void ChatPipe_OnReadComplete() 295 { 296 Pipe.readFlag--; 297 DWORD byteCnt; 298 BOOL ol_result = ::GetOverlappedResult(Pipe.handle, &Pipe.overlapRead, &byteCnt, FALSE); 299 if (!ol_result) { 300 DWORD code = ::GetLastError(); 301 if (code != ERROR_MORE_DATA) { 302 LOG_F(ERROR, "GetOverlappedResult failed, last error: %ld", code); 303 ChatPipe_Reconnect(); 304 return; 305 } 306 307 LOG_F(INFO, "%s: Recieved messaged too large! (%ld > %d bytes)", __func__, byteCnt, CHATPIPE_BUFSIZE); 308 ChatPipe_ReadFromPipe(); 309 return; 310 } 311 312 Pipe.readBuffer[byteCnt] = '\0'; 313 314 if (byteCnt > 0) 315 { 316 char* p = Pipe.readBuffer; 317 318 #ifdef DEBUGGINGAIDS 319 LOG_F(INFO, "%s: Got messages! (%ld bytes)", __func__, byteCnt); 320 #endif 321 322 for (DWORD i = 0; i < byteCnt;) // Bullshit hack because for some reason simultaneous messages get concatenated... 323 { 324 strcpy(cpTempBuf, p); 325 326 #ifdef DEBUGGINGAIDS 327 LOG_F(INFO, "DATA: %s", cpTempBuf); 328 #endif 329 330 if (!ChatPipe_ParseJSON(cpTempBuf)) 331 break; 332 333 i += strlen(p) + 1; 334 p += strlen(p) + 1; 335 } 336 } 337 338 if(Pipe.state == PIPESTATE_CONNECTING) 339 { 340 LOG_F(INFO, "%s: Connection established.", __func__); 341 Pipe.state = PIPESTATE_READY; 342 } 343 344 ChatPipe_ReadFromPipe(); 345 } 346 347 void ChatPipe_Poll(void) // Called once per game loop 348 { 349 if (!bPipeActive || (Pipe.handle == NULL) || (Pipe.handle == INVALID_HANDLE_VALUE)) 350 return; 351 352 HANDLE handles[3] = { waitEvent, Pipe.overlapRead.hEvent, Pipe.overlapWrite.hEvent }; 353 354 DWORD waitResult; 355 356 //if (!(waitResult > 0 && waitResult != WAIT_TIMEOUT)) 357 //return; 358 359 do 360 { 361 waitResult = WaitForMultipleObjects(3, handles, FALSE, 0); 362 //LOG_F(INFO, "%s: waitResult: %ld", __func__, waitResult); 363 switch (waitResult) 364 { 365 case WAIT_OBJECT_0: 366 break; 367 case WAIT_OBJECT_0 + 1: 368 ChatPipe_OnReadComplete(); 369 break; 370 case WAIT_OBJECT_0 + 2: 371 ChatPipe_OnWriteComplete(); 372 break; 373 case WAIT_TIMEOUT: 374 break; 375 default: 376 LOG_F(ERROR, "WaitForMultipleObjects failed, error code: %ld", GetLastError()); 377 break; 378 } 379 } while ((waitResult > 0) && (waitResult != WAIT_TIMEOUT)); 380 381 return; 382 } 383 #else // Unix/Linux stuff 384 385 static void ChatPipe_WriteToPipe(const char* message) 386 { 387 UNREFERENCED_PARAMETER(message); 388 } 389 390 void ChatPipe_Create(void) 391 { 392 return; 393 } 394 395 void ChatPipe_Poll(void) // Called once per game loop 396 { 397 return; 398 } 399 400 #endif 401 #endif