1 /* Mednafen - Multi-system Emulator
2 *
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 */
17
18 #include "mednafen.h"
19
20 #include <zlib.h>
21 #include <trio/trio.h>
22
23 #include <map>
24
25 #include "netplay.h"
26 #include "netplay-driver.h"
27 #include "general.h"
28 #include <mednafen/string/string.h>
29 #include "state.h"
30 #include "movie.h"
31 #include <mednafen/hash/md5.h>
32 #include <mednafen/Time.h>
33 #include <mednafen/net/Net.h>
34 #include "mempatcher.h"
35
36 #include "MemoryStream.h"
37
38 #include "driver.h"
39
40 namespace Mednafen
41 {
42
43 static std::unique_ptr<Net::Connection> Connection;
44
45 int MDFNnetplay=0;
46
47 static std::map<std::string, uint32> PlayersList;
48 static std::string OurNick;
49
50 static bool Joined = false;
51 static uint32 LocalPlayersMask = 0;
52 static uint32 LocalInputStateSize = 0;
53 static uint32 TotalInputStateSize = 0;
54 static uint8 PortVtoLVMap[16];
55 static uint8 PortLVtoVMap[16];
56 static std::vector<uint8> PreNPPortDataPortData[16];
57 static std::vector<uint8> PostEmulatePortData[16];
58 static bool StateLoaded; // Set to true/false in Netplay_Update() call paths, used in Netplay_PostProcess()
59 // to determine where to pull switch data from.
60
61 static std::unique_ptr<uint8[]> incoming_buffer; // TotalInputStateSize + 1
62 static std::unique_ptr<uint8[]> outgoing_buffer; // 1 + LocalInputStateSize + 4
63
RebuildPortVtoVMap(const uint32 PortDevIdx[])64 static void RebuildPortVtoVMap(const uint32 PortDevIdx[])
65 {
66 const unsigned NumPorts = MDFNGameInfo->PortInfo.size();
67
68 memset(PortVtoLVMap, 0xFF, sizeof(PortVtoLVMap));
69 memset(PortLVtoVMap, 0xFF, sizeof(PortLVtoVMap));
70
71 for(unsigned x = 0; x < NumPorts; x++)
72 {
73 if(LocalPlayersMask & (1U << x))
74 {
75 for(unsigned n = 0; n <= x; n++)
76 {
77 auto const* IDII_N = &MDFNGameInfo->PortInfo[n].DeviceInfo[PortDevIdx[n]].IDII;
78 auto const* IDII_X = &MDFNGameInfo->PortInfo[x].DeviceInfo[PortDevIdx[x]].IDII;
79
80 if(PortLVtoVMap[n] == 0xFF && IDII_N == IDII_X)
81 {
82 PortLVtoVMap[n] = x;
83 PortVtoLVMap[x] = n;
84 break;
85 }
86 }
87 }
88 }
89 }
90
SetLPM(const uint32 v,const uint32 PortDevIdx[],const uint32 PortLen[])91 static void SetLPM(const uint32 v, const uint32 PortDevIdx[], const uint32 PortLen[])
92 {
93 LocalPlayersMask = v;
94 LocalInputStateSize = 0;
95
96 for(unsigned x = 0; x < MDFNGameInfo->PortInfo.size(); x++)
97 {
98 if(LocalPlayersMask & (1U << x))
99 LocalInputStateSize += PortLen[x];
100 }
101
102 outgoing_buffer.reset(nullptr);
103 outgoing_buffer.reset(new uint8[1 + LocalInputStateSize + 4]);
104
105 RebuildPortVtoVMap(PortDevIdx);
106 //
107 MDFND_NetplaySetHints(true, Connection->CanReceive(), LocalPlayersMask);
108 }
109
110
NetError(const char * format,...)111 static void NetError(const char *format, ...)
112 {
113 char *temp = NULL;
114 va_list ap;
115
116 va_start(ap, format);
117 temp = trio_vaprintf(format, ap);
118 va_end(ap);
119
120 MDFND_NetplayText(temp, false);
121 MDFNI_NetplayDisconnect();
122 free(temp);
123 }
124
125 static void NetPrintText(const char* format, ...) MDFN_FORMATSTR(gnu_printf, 1, 2);
NetPrintText(const char * format,...)126 static void NetPrintText(const char* format, ...)
127 {
128 char *temp = NULL;
129 va_list ap;
130
131 va_start(ap, format);
132 temp = trio_vaprintf(format, ap);
133 va_end(ap);
134
135 MDFND_NetplayText(temp, false);
136 free(temp);
137 }
138
SendData(const void * data,uint32 len)139 static void SendData(const void *data, uint32 len)
140 {
141 do
142 {
143 int32 sent = Connection->Send(data, len);
144 assert(sent >= 0);
145
146 data = (uint8*)data + sent;
147 len -= sent;
148
149 if(len)
150 {
151 if(MDFND_CheckNeedExit())
152 throw MDFN_Error(0, _("Mednafen exit pending."));
153
154 Connection->CanSend(50000);
155 }
156 } while(len);
157 }
158
RecvData(void * data,uint32 len)159 static void RecvData(void *data, uint32 len)
160 {
161 do
162 {
163 int32 received = Connection->Receive(data, len);
164 assert(received >= 0);
165
166 data = (uint8*)data + received;
167 len -= received;
168
169 if(len)
170 {
171 if(MDFND_CheckNeedExit())
172 throw MDFN_Error(0, _("Mednafen exit pending."));
173
174 Connection->CanReceive(50000);
175 }
176 } while(len);
177
178 MDFND_NetplaySetHints(true, Connection->CanReceive(), LocalPlayersMask);
179 }
180
181
182 struct login_data_t
183 {
184 uint8 gameid[16];
185 uint8 password[16];
186
187 uint8 protocol_version;
188 uint8 total_controllers;
189 uint8 padding0[2];
190
191 uint8 emu_name_len[4]; // Length of emulator name and version string(up to 64 bytes) - (note that any non-0
192 // bytes < 0x20 in this string will be replaced by 0x20).
193
194 uint8 padding1[8];
195
196 uint8 controller_data_size[16];
197
198 uint8 padding3[16];
199
200 uint8 controller_type[16];
201
202 uint8 local_players;
203 };
204
205 #if 0
206 // WIP, mostly just ideas now, nothing to justify implementing it.
207 struct p4_login_data_t
208 {
209 uint8 magic[16]; // MEDNAFEN_NETPLAY
210 uint8 protocol_version[4]; // uint32
211
212 //
213 //
214 //
215 uint8 password[16]; //
216 uint8 nickname[256]; //
217
218 //
219 //
220 //
221 uint8 game_id[32];
222 uint8 emu_id[256];
223
224 uint8 total_controllers; // Total number of controllers(max 32)
225 uint8 controller_type[32];
226 uint8 controller_data_size[32];
227 uint8 local_controllers; // Number of local controllers.
228 };
229 #endif
230
NetplayStart(const uint32 PortDeviceCache[16],const uint32 PortDataLenCache[16])231 static void NetplayStart(const uint32 PortDeviceCache[16], const uint32 PortDataLenCache[16])
232 {
233 const char *emu_id = PACKAGE " " MEDNAFEN_VERSION;
234 const uint32 local_players = MDFN_GetSettingUI("netplay.localplayers");
235 const std::string nickname = MDFN_GetSettingS("netplay.nick");
236 const std::string game_key = MDFN_GetSettingS("netplay.gamekey");
237 const std::string connect_password = MDFN_GetSettingS("netplay.password");
238 login_data_t *ld = NULL;
239 std::vector<uint8> sendbuf;
240
241 NetPrintText(_("*** Sending initialization data to server."));
242
243 PlayersList.clear();
244 MDFNnetplay = true;
245
246 sendbuf.resize(4 + sizeof(login_data_t) + nickname.size() + strlen(emu_id));
247
248 MDFN_en32lsb(&sendbuf[0], sendbuf.size() - 4);
249 ld = (login_data_t*)&sendbuf[4];
250
251 if(game_key.size())
252 {
253 md5_context md5;
254 uint8 md5out[16];
255
256 md5.starts();
257 md5.update(MDFNGameInfo->MD5, 16);
258 md5.update((uint8 *)game_key.c_str(), game_key.size());
259 md5.finish(md5out);
260 memcpy(ld->gameid, md5out, 16);
261 }
262 else
263 memcpy(ld->gameid, MDFNGameInfo->MD5, 16);
264
265 if(connect_password != "")
266 {
267 md5_context md5;
268 uint8 md5out[16];
269
270 md5.starts();
271 md5.update((uint8*)connect_password.c_str(), connect_password.size());
272 md5.finish(md5out);
273 memcpy(ld->password, md5out, 16);
274 }
275
276 assert(MDFNGameInfo->PortInfo.size() <= 16);
277
278 ld->protocol_version = 3;
279
280 // Set input device number thingies here.
281 ld->total_controllers = MDFNGameInfo->PortInfo.size(); // Total number of ports
282
283 MDFN_en32lsb(ld->emu_name_len, strlen(emu_id));
284
285 // Controller data sizes.
286 for(unsigned x = 0; x < MDFNGameInfo->PortInfo.size(); x++)
287 ld->controller_data_size[x] = PortDataLenCache[x];
288
289 // Controller types
290 for(unsigned x = 0; x < MDFNGameInfo->PortInfo.size(); x++)
291 ld->controller_type[x] = PortDeviceCache[x]; // FIXME: expand controller_type from 8-bit -> 32-bit
292
293 ld->local_players = local_players;
294
295 if(nickname != "")
296 memcpy(&sendbuf[4 + sizeof(login_data_t)], nickname.c_str(), nickname.size());
297
298 memcpy(&sendbuf[4 + sizeof(login_data_t) + nickname.size()], emu_id, strlen(emu_id));
299
300 SendData(&sendbuf[0], sendbuf.size());
301
302 TotalInputStateSize = 0;
303 for(unsigned x = 0; x < MDFNGameInfo->PortInfo.size(); x++)
304 TotalInputStateSize += PortDataLenCache[x];
305
306 // Hack so the server can always encode its command data length properly(a matching "hack" exists in the server).
307 if(TotalInputStateSize < 4)
308 TotalInputStateSize = 4;
309
310 incoming_buffer.reset(nullptr);
311 incoming_buffer.reset(new uint8[TotalInputStateSize + 1]);
312
313 SetLPM(0, PortDeviceCache, PortDataLenCache);
314 Joined = false;
315
316 //
317 //
318 //
319 for(unsigned x = 0; x < MDFNGameInfo->PortInfo.size(); x++)
320 {
321 PreNPPortDataPortData[x].assign(PortDataLenCache[x], 0);
322 PostEmulatePortData[x].assign(PortDataLenCache[x], 0);
323 }
324
325 MDFN_FlushGameCheats(0); /* Save our pre-netplay cheats. */
326
327 if(MDFNMOV_IsPlaying()) /* Recording's ok during netplay, playback is not. */
328 MDFNMOV_Stop();
329
330 NetPrintText(_("*** Connection established."));
331
332 if(game_key.size())
333 NetPrintText(_("** Using game key: %s"), game_key.c_str());
334 //printf("%d\n", TotalInputStateSize);
335 //
336 //
337 MDFND_NetplaySetHints(true, Connection->CanReceive(), LocalPlayersMask);
338 }
339
SendCommand(uint8 cmd,uint32 len,const void * data=NULL)340 static void SendCommand(uint8 cmd, uint32 len, const void* data = NULL)
341 {
342 outgoing_buffer[0] = cmd;
343 memset(&outgoing_buffer[1], 0, LocalInputStateSize);
344 MDFN_en32lsb(&outgoing_buffer[1 + LocalInputStateSize], len);
345 SendData(&outgoing_buffer[0], LocalInputStateSize + 1 + 4);
346
347 if(data != NULL)
348 {
349 SendData(data, len);
350 }
351 }
352
NetplaySendCommand(uint8 cmd,uint32 len,const void * data)353 bool NetplaySendCommand(uint8 cmd, uint32 len, const void* data)
354 {
355 try
356 {
357 SendCommand(cmd, len, data);
358 }
359 catch(std::exception &e)
360 {
361 NetError("%s", e.what());
362 return(false);
363 }
364 return(true);
365 }
366
GenerateMPSString(uint32 mps,bool ctlr_string=false)367 static std::string GenerateMPSString(uint32 mps, bool ctlr_string = false)
368 {
369 std::string ret;
370
371 if(!mps)
372 {
373 if(!ctlr_string)
374 ret = _("a lurker");
375 }
376 else
377 {
378 ret = ctlr_string ? ((mps == round_up_pow2(mps)) ? _("controller") : _("controllers")) : ((mps == round_up_pow2(mps)) ? _("player") : _("players"));
379
380 for(unsigned i = 0; i < 16; i++)
381 {
382 if(mps & (1U << i))
383 {
384 char tmp[16];
385 trio_snprintf(tmp, sizeof(tmp), " %u", i + 1);
386 ret += tmp;
387 }
388 }
389 }
390
391 return ret;
392 }
393
MDFNI_NetplaySwap(uint8 a,uint8 b)394 static void MDFNI_NetplaySwap(uint8 a, uint8 b)
395 {
396 try
397 {
398 SendCommand(MDFNNPCMD_CTRLR_SWAP, (a << 0) | (b << 8));
399 }
400 catch(std::exception &e)
401 {
402 NetError("%s", e.what());
403 }
404 }
405
MDFNI_NetplayTake(uint32 mask)406 static void MDFNI_NetplayTake(uint32 mask)
407 {
408 try
409 {
410 SendCommand(MDFNNPCMD_CTRLR_TAKE, mask);
411 }
412 catch(std::exception &e)
413 {
414 NetError("%s", e.what());
415 }
416 }
417
MDFNI_NetplayDrop(uint32 mask)418 static void MDFNI_NetplayDrop(uint32 mask)
419 {
420 try
421 {
422 SendCommand(MDFNNPCMD_CTRLR_DROP, mask);
423 }
424 catch(std::exception &e)
425 {
426 NetError("%s", e.what());
427 }
428 }
429
MDFNI_NetplayDupe(uint32 mask)430 static void MDFNI_NetplayDupe(uint32 mask)
431 {
432 try
433 {
434 SendCommand(MDFNNPCMD_CTRLR_DUPE, mask);
435 }
436 catch(std::exception &e)
437 {
438 NetError("%s", e.what());
439 }
440 }
441
MDFNI_NetplayList(void)442 static void MDFNI_NetplayList(void)
443 {
444 try
445 {
446 for(auto& it : PlayersList)
447 {
448 NetPrintText(_("** <%s> is %s"), it.first.c_str(), GenerateMPSString(it.second).c_str());
449 }
450 }
451 catch(std::exception &e)
452 {
453 NetError("%s", e.what());
454 }
455 }
456
457
MDFNI_NetplayPing(void)458 static void MDFNI_NetplayPing(void)
459 {
460 try
461 {
462 uint64 now_time;
463
464 now_time = Time::MonoMS();
465
466 // Endianness doesn't matter, since it will be echoed back only to us.
467 SendCommand(MDFNNPCMD_ECHO, sizeof(now_time), &now_time);
468 }
469 catch(std::exception &e)
470 {
471 NetError("%s", e.what());
472 }
473 }
474 #if 0
475 static void MDFNI_NetplayIntegrity(void)
476 {
477 try
478 {
479 SendCommand(MDFNNPCMD_INTEGRITY, 0);
480 }
481 catch(std::exception &e)
482 {
483 NetError("%s", e.what());
484 }
485 }
486 #endif
MDFNI_NetplayText(const char * text)487 static void MDFNI_NetplayText(const char *text)
488 {
489 try
490 {
491 uint32 len;
492
493 if(!Joined) return;
494
495 len = strlen(text);
496
497 SendCommand(MDFNNPCMD_TEXT, len, text);
498 }
499 catch(std::exception &e)
500 {
501 NetError("%s", e.what());
502 }
503 }
504
MDFNI_NetplayChangeNick(const char * newnick)505 static void MDFNI_NetplayChangeNick(const char* newnick)
506 {
507 try
508 {
509 uint32 len;
510
511 if(!Joined) return;
512
513 len = strlen(newnick);
514
515 SendCommand(MDFNNPCMD_SETNICK, len, newnick);
516 }
517 catch(std::exception &e)
518 {
519 NetError("%s", e.what());
520 }
521 }
522
MDFNI_NetplayQuit(const char * quit_message)523 static void MDFNI_NetplayQuit(const char *quit_message)
524 {
525 try
526 {
527 SendCommand(MDFNNPCMD_QUIT, strlen(quit_message), quit_message);
528 }
529 catch(std::exception &e)
530 {
531 NetError("%s", e.what());
532 }
533 }
534
535
536 //
537 // Integrity checking is experimental, and needs work to function properly(in the emulator cores).
538 //
SendIntegrity(void)539 static int SendIntegrity(void)
540 {
541 MemoryStream sm(65536);
542 md5_context md5;
543 uint8 digest[16];
544
545 // Do not do a raw/data-only state for speed, due to lack of endian and bool conversion.
546 MDFNSS_SaveSM(&sm, false);
547
548 md5.starts();
549 md5.update(sm.map(), sm.size());
550 md5.finish(digest);
551
552 SendCommand(MDFNNPCMD_INTEGRITY_RES, 16, digest);
553
554 return(1);
555 }
556
557
SendState(void)558 static void SendState(void)
559 {
560 std::vector<uint8> cbuf;
561 uLongf clen;
562
563 {
564 MemoryStream sm(65536);
565
566 MDFNSS_SaveSM(&sm, false);
567
568 clen = sm.size() + sm.size() / 1000 + 12;
569 cbuf.resize(4 + clen);
570 MDFN_en32lsb(&cbuf[0], sm.size());
571 compress2((Bytef *)&cbuf[0] + 4, &clen, (Bytef *)sm.map(), sm.size(), 7);
572 }
573
574 SendCommand(MDFNNPCMD_LOADSTATE, clen + 4, &cbuf[0]);
575 }
576
RecvState(const uint32 clen)577 static void RecvState(const uint32 clen)
578 {
579 std::vector<uint8> cbuf;
580
581 if(clen < 4)
582 {
583 throw MDFN_Error(0, _("Compressed save state data is too small: %u"), clen);
584 }
585
586 if(clen > 8 * 1024 * 1024) // Compressed length sanity check - 8 MiB max.
587 {
588 throw MDFN_Error(0, _("Compressed save state data is too large: %u"), clen);
589 }
590
591 cbuf.resize(clen);
592
593 RecvData(&cbuf[0], clen);
594
595 uLongf len = MDFN_de32lsb(&cbuf[0]);
596 if(len > 12 * 1024 * 1024) // Uncompressed length sanity check - 12 MiB max.
597 {
598 throw MDFN_Error(0, _("Uncompressed save state data is too large: %llu"), (unsigned long long)len);
599 }
600
601 MemoryStream sm(len, -1);
602
603 uncompress((Bytef *)sm.map(), &len, (Bytef *)&cbuf[0] + 4, clen - 4);
604
605 MDFNSS_LoadSM(&sm, false);
606
607 if(MDFNMOV_IsRecording())
608 MDFNMOV_RecordState();
609 }
610
NetplaySendState(void)611 void NetplaySendState(void)
612 {
613 try
614 {
615 SendState();
616 }
617 catch(std::exception &e)
618 {
619 NetError("%s", e.what());
620 }
621 }
622
ProcessCommand(const uint8 cmd,const uint32 raw_len,const uint32 PortDevIdx[],uint8 * const PortData[],const uint32 PortLen[],int NumPorts)623 static void ProcessCommand(const uint8 cmd, const uint32 raw_len, const uint32 PortDevIdx[], uint8* const PortData[], const uint32 PortLen[], int NumPorts)
624 {
625 switch(cmd)
626 {
627 case 0: break; // No command
628
629 default: MDFN_DoSimpleCommand(cmd);
630 break;
631
632 case MDFNNPCMD_INTEGRITY:
633 SendIntegrity();
634 break;
635
636 case MDFNNPCMD_REQUEST_STATE:
637 SendState();
638 break;
639
640 case MDFNNPCMD_LOADSTATE:
641 RecvState(raw_len);
642 StateLoaded = true;
643 MDFN_Notify(MDFN_NOTICE_STATUS, _("Remote state loaded."));
644 break;
645
646 case MDFNNPCMD_SET_MEDIA:
647 {
648 uint8 buf[4 * 4];
649
650 RecvData(buf, sizeof(buf));
651
652 MDFN_UntrustedSetMedia(MDFN_de32lsb(&buf[0]), MDFN_de32lsb(&buf[4]), MDFN_de32lsb(&buf[8]), MDFN_de32lsb(&buf[12]));
653 }
654 break;
655
656 case MDFNNPCMD_SERVERTEXT:
657 {
658 static const uint32 MaxLength = 2000;
659 uint8 neobuf[MaxLength + 1];
660 char *textbuf = NULL;
661 const uint32 totallen = raw_len;
662
663 if(totallen > MaxLength) // Sanity check
664 {
665 throw MDFN_Error(0, _("Text length is too long: %u"), totallen);
666 }
667
668 RecvData(neobuf, totallen);
669
670 neobuf[totallen] = 0;
671 trio_asprintf(&textbuf, "** %s", neobuf);
672 MDFND_NetplayText(textbuf, false);
673 free(textbuf);
674 }
675 break;
676
677 case MDFNNPCMD_ECHO:
678 {
679 uint32 totallen = raw_len;
680 uint64 then_time;
681 uint64 now_time;
682
683 if(totallen != sizeof(then_time))
684 {
685 throw MDFN_Error(0, _("Echo response length is incorrect size: %u"), totallen);
686 }
687
688 RecvData(&then_time, sizeof(then_time));
689
690 now_time = Time::MonoMS();
691
692 char *textbuf = NULL;
693 trio_asprintf(&textbuf, _("*** Round-trip time: %llu ms"), (unsigned long long)(now_time - then_time));
694 MDFND_NetplayText(textbuf, false);
695 free(textbuf);
696 }
697 break;
698
699 case MDFNNPCMD_TEXT:
700 {
701 static const uint32 MaxLength = 2000;
702 char neobuf[MaxLength + 1];
703 const uint32 totallen = raw_len;
704 uint32 nicklen;
705 bool NetEcho = false;
706 char *textbuf = NULL;
707
708 if(totallen < 4)
709 {
710 throw MDFN_Error(0, _("Text command length is too short: %u"), totallen);
711 }
712
713 if(totallen > MaxLength) // Sanity check
714 {
715 throw MDFN_Error(0, _("Text command length is too long: %u"), totallen);
716 }
717
718 RecvData(neobuf, totallen);
719
720 nicklen = MDFN_de32lsb(neobuf);
721 if(nicklen > (totallen - 4)) // Sanity check
722 {
723 throw MDFN_Error(0, _("Received nickname length is too long: %u"), nicklen);
724 }
725
726 neobuf[totallen] = 0;
727
728 if(nicklen)
729 {
730 memmove(neobuf, neobuf + 4, nicklen);
731 neobuf[nicklen] = 0;
732
733 if(OurNick == neobuf)
734 {
735 trio_asprintf(&textbuf, "> %s", &neobuf[4 + nicklen]);
736 NetEcho = true;
737 }
738 else
739 trio_asprintf(&textbuf, "<%s> %s", neobuf, &neobuf[4 + nicklen]);
740 }
741 else
742 {
743 trio_asprintf(&textbuf, "* %s", &neobuf[4]);
744 }
745 MDFND_NetplayText(textbuf, NetEcho);
746 free(textbuf);
747 }
748 break;
749
750 case MDFNNPCMD_NICKCHANGED:
751 {
752 static const uint32 MaxLength = 2000;
753 char neobuf[MaxLength + 1];
754 char* newnick;
755 char* textbuf = NULL;
756 const uint32 len = raw_len;
757
758 if(len > MaxLength) // Sanity check
759 {
760 throw MDFN_Error(0, _("Nickname change length is too long: %u"), len);
761 }
762
763 RecvData(neobuf, len);
764
765 neobuf[len] = 0;
766
767 newnick = strchr(neobuf, '\n');
768
769 if(newnick)
770 {
771 bool IsMeow = false;
772
773 *newnick = 0;
774 newnick++;
775
776 if(OurNick == neobuf)
777 {
778 OurNick = newnick;
779 textbuf = trio_aprintf(_("* You are now known as <%s>."), newnick);
780 IsMeow = true;
781 }
782
783 if(!textbuf)
784 textbuf = trio_aprintf(_("* <%s> is now known as <%s>"), neobuf, newnick);
785
786 MDFND_NetplayText(textbuf, IsMeow);
787 free(textbuf);
788
789 // Update players list.
790 {
791 const std::string ons(neobuf);
792 const std::string nns(newnick);
793
794 if(ons != nns)
795 {
796 auto ons_it = PlayersList.find(ons);
797 auto nns_it = PlayersList.find(nns);
798
799 if(ons_it == PlayersList.end() || nns_it != PlayersList.end())
800 MDFND_NetplayText(_("[BUG] Players list state out of sync."), false);
801 else
802 {
803 PlayersList[nns] = ons_it->second;
804 PlayersList.erase(ons_it);
805 }
806 }
807 }
808 }
809 }
810 break;
811
812 case MDFNNPCMD_CTRL_CHANGE:
813 {
814 const uint32 len = raw_len;
815
816 //
817 // Joined = true;
818 SendCommand(MDFNNPCMD_CTRL_CHANGE_ACK, len);
819 //
820 //
821 LocalInputStateSize = 0;
822 SetLPM(len, PortDevIdx, PortLen);
823 }
824 break;
825
826 case MDFNNPCMD_CTRLR_SWAP_NOTIF:
827 {
828 const uint32 cm = raw_len;
829 char textbuf[512];
830 const uint8 c0 = cm & 0xFF;
831 const uint8 c1 = (cm >> 8) & 0xFF;
832
833 trio_snprintf(textbuf, sizeof(textbuf), _("* All instances of controllers %u and %u have been swapped."), c0 + 1, c1 + 1);
834 MDFND_NetplayText(textbuf, false);
835
836 for(auto& it : PlayersList)
837 {
838 uint32 mps = it.second;
839 bool c0b, c1b;
840
841 // Grab bits first before any clearing.
842 c0b = ((c0 < sizeof(mps) * 8) ? ((mps >> c0) & 1) : false);
843 c1b = ((c1 < sizeof(mps) * 8) ? ((mps >> c1) & 1) : false);
844
845 if(c0 < sizeof(mps) * 8)
846 {
847 mps &= ~((uint32)1 << c0);
848 mps |= (uint32)c1b << c0;
849 }
850
851 if(c1 < sizeof(mps) * 8)
852 {
853 mps &= ~((uint32)1 << c1);
854 mps |= (uint32)c0b << c1;
855 }
856
857 it.second = mps;
858 }
859 }
860 break;
861
862 case MDFNNPCMD_CTRLR_TAKE_NOTIF:
863 case MDFNNPCMD_CTRLR_DROP_NOTIF:
864 case MDFNNPCMD_CTRLR_DUPE_NOTIF:
865 {
866 static const uint32 MaxNicknameLength = 1000;
867 static const uint32 MaxLength = 12 + MaxNicknameLength;
868 const char *fstr = NULL;
869 const uint32 len = raw_len;
870 uint8 ntf_buf[MaxLength + 1];
871 char *textbuf = NULL;
872
873 if(len < 12)
874 throw MDFN_Error(0, _("Take/drop/dupe notification is too short: %u"), len);
875
876 if(len > MaxLength)
877 throw MDFN_Error(0, _("Take/drop/dupe notification is too long: %u"), len);
878
879 RecvData(ntf_buf, len);
880 ntf_buf[len] = 0;
881
882 switch(cmd)
883 {
884 case MDFNNPCMD_CTRLR_TAKE_NOTIF:
885 fstr = _("* <%s> took all instances of %s, and is now %s.");
886 break;
887
888 case MDFNNPCMD_CTRLR_DUPE_NOTIF:
889 fstr = _("* <%s> took copies of %s, and is now %s.");
890 break;
891
892 case MDFNNPCMD_CTRLR_DROP_NOTIF:
893 fstr = _("* <%s> dropped %s, and is now %s.");
894 break;
895 }
896 trio_asprintf(&textbuf, fstr, ntf_buf + 12, GenerateMPSString(MDFN_de32lsb(&ntf_buf[0]), true).c_str(), GenerateMPSString(MDFN_de32lsb(&ntf_buf[4]), false).c_str());
897 MDFND_NetplayText(textbuf, false);
898 free(textbuf);
899
900 // Update players list.
901 {
902 auto it = PlayersList.find(std::string((const char*)ntf_buf + 12));
903
904 if(it == PlayersList.end())
905 MDFND_NetplayText(_("[BUG] Players list state out of sync."), false);
906 else
907 it->second = MDFN_de32lsb(&ntf_buf[4]);
908 }
909 }
910 break;
911
912 case MDFNNPCMD_YOUJOINED:
913 case MDFNNPCMD_YOULEFT:
914 case MDFNNPCMD_PLAYERLEFT:
915 case MDFNNPCMD_PLAYERJOINED:
916 {
917 static const uint32 MaxLength = 2000;
918 uint8 neobuf[MaxLength + 1];
919 char *textbuf = NULL;
920 uint32 mps;
921 std::string mps_string;
922 const uint32 len = raw_len;
923
924 if(len < 8)
925 {
926 throw MDFN_Error(0, _("Join/Left length is too short: %u"), len);
927 }
928
929 if(len > MaxLength) // Sanity check
930 {
931 throw MDFN_Error(0, _("Join/Left length is too long: %u"), len);
932 }
933
934 RecvData(neobuf, len);
935 neobuf[len] = 0; // NULL-terminate the string
936
937 mps = MDFN_de32lsb(&neobuf[0]);
938
939 mps_string = GenerateMPSString(mps);
940
941 if(cmd == MDFNNPCMD_YOULEFT)
942 {
943 // Uhm, not supported yet!
944 SetLPM(0, PortDevIdx, PortLen);
945 Joined = false;
946 }
947 else if(cmd == MDFNNPCMD_YOUJOINED)
948 {
949 OurNick = (char*)neobuf + 8;
950
951 trio_asprintf(&textbuf, _("* You, %s, have connected as: %s"), neobuf + 8, mps_string.c_str());
952
953 SetLPM(mps, PortDevIdx, PortLen);
954 Joined = true;
955
956 SendCommand(MDFNNPCMD_SETFPS, MDFNGameInfo->fps);
957 }
958 else if(cmd == MDFNNPCMD_PLAYERLEFT)
959 {
960 trio_asprintf(&textbuf, _("* %s(%s) has left"), neobuf + 8, mps_string.c_str());
961 }
962 else
963 {
964 trio_asprintf(&textbuf, _("* %s has connected as: %s"), neobuf + 8, mps_string.c_str());
965 }
966
967 MDFND_NetplayText(textbuf, false);
968 free(textbuf);
969
970 // Update players list.
971 if(cmd == MDFNNPCMD_YOUJOINED || cmd == MDFNNPCMD_PLAYERJOINED)
972 {
973 PlayersList[std::string((const char*)neobuf + 8)] = mps;
974 }
975 else
976 {
977 auto it = PlayersList.find(std::string((const char*)neobuf + 8));
978
979 if(it == PlayersList.end())
980 MDFND_NetplayText(_("[BUG] Players list state out of sync."), false);
981 else
982 PlayersList.erase(it);
983 }
984 }
985 break;
986 }
987 }
988
989 #if 0
990 for(auto const& idii : *IDII_N)
991 {
992 if(idii.Type == IDIT_SWITCH)
993 {
994 const uint32 cur = BitsExtract(&PortData[n][0], idii.BitOffset, idii.BitSize);
995 const uint32 prev = BitsExtract(&PrevPortData[n][0], idii.BitOffset, idii.BitSize);
996 const uint32 delta = cur - prev;
997
998 printf("%d\n", delta);
999
1000 // We mustn't modify PortData with this filter, only outgoing_buffer(because we can load state in the middle of this function,
1001 // which will load new port data that doesn't have this filter applied, and things go boom-boom)!
1002 BitsIntract(&outgoing_buffer[wpos], idii.BitOffset, idii.BitSize, delta);
1003 }
1004 }
1005 #endif
1006
1007 #if 0
1008 if(LocalPlayersMask & (1 << x))
1009 {
1010 for(unsigned n = 0; n <= x; n++)
1011 {
1012 auto const* IDII_N = &MDFNGameInfo->PortInfo[n].DeviceInfo[PortDevIdx[n]].IDII;
1013 auto const* IDII_X = &MDFNGameInfo->PortInfo[x].DeviceInfo[PortDevIdx[x]].IDII;
1014
1015 if(!Taken[n] && IDII_N == IDII_X)
1016 {
1017 memcpy(outgoing_buffer + wpos, PortData[n], PortLen[n]);
1018 Taken[n] = true;
1019 wpos += PortLen[n];
1020 break;
1021 }
1022 }
1023 }
1024 #endif
1025
Netplay_Update(const uint32 PortDevIdx[],uint8 * const PortData[],const uint32 PortLen[])1026 void Netplay_Update(const uint32 PortDevIdx[], uint8* const PortData[], const uint32 PortLen[])
1027 {
1028 const unsigned NumPorts = MDFNGameInfo->PortInfo.size();
1029
1030 StateLoaded = false;
1031
1032 try
1033 {
1034 if(!Connection)
1035 return;
1036
1037 if(!MDFNnetplay)
1038 {
1039 try
1040 {
1041 if(!Connection->Established())
1042 return;
1043 }
1044 catch(...)
1045 {
1046 Connection.reset(nullptr);
1047 throw;
1048 }
1049
1050 NetplayStart(PortDevIdx, PortLen);
1051 }
1052 //
1053 //
1054 //
1055 for(unsigned x = 0; x < NumPorts; x++)
1056 {
1057 if(!PortLen[x])
1058 continue;
1059
1060 memcpy(PreNPPortDataPortData[x].data(), PortData[x], PortLen[x]);
1061 }
1062
1063 if(Joined)
1064 {
1065 outgoing_buffer[0] = 0; // Not a command
1066
1067 for(unsigned x = 0, wpos = 1; x < NumPorts; x++)
1068 {
1069 if(!PortLen[x])
1070 continue;
1071
1072 auto n = PortVtoLVMap[x];
1073 if(n != 0xFF)
1074 {
1075 memcpy(&outgoing_buffer[wpos], PortData[n], PortLen[n]);
1076 wpos += PortLen[n];
1077 }
1078 }
1079 SendData(&outgoing_buffer[0], 1 + LocalInputStateSize);
1080 }
1081 //
1082 //
1083 //
1084 uint8 cmd;
1085 uint32 cmd_raw_len;
1086
1087 do
1088 {
1089 RecvData(&incoming_buffer[0], TotalInputStateSize + 1);
1090
1091 cmd = incoming_buffer[TotalInputStateSize];
1092 cmd_raw_len = MDFN_de32lsb(&incoming_buffer[0]);
1093
1094 if(cmd != 0)
1095 ProcessCommand(cmd, cmd_raw_len, PortDevIdx, PortData, PortLen, NumPorts);
1096 } while(cmd != 0);
1097
1098 //
1099 // Update local port data buffers with data received.
1100 //
1101 for(unsigned x = 0, rpos = 0; x < NumPorts; x++)
1102 {
1103 memcpy(PortData[x], &incoming_buffer[rpos], PortLen[x]);
1104 rpos += PortLen[x];
1105 }
1106 }
1107 catch(std::exception &e)
1108 {
1109 NetError("%s", e.what());
1110 }
1111 }
1112
Netplay_PostProcess(const uint32 PortDevIdx[],uint8 * const PortData[],const uint32 PortLen[])1113 void Netplay_PostProcess(const uint32 PortDevIdx[], uint8* const PortData[], const uint32 PortLen[])
1114 {
1115 const unsigned NumPorts = MDFNGameInfo->PortInfo.size();
1116
1117 //
1118 // Make a backup copy of the current port data, then zero the port data(specifically for rumble and status bits).
1119 //
1120 for(unsigned x = 0; x < NumPorts; x++)
1121 {
1122 if(!PortLen[x])
1123 continue;
1124
1125 assert(PostEmulatePortData[x].size() == PortLen[x]);
1126 assert(PreNPPortDataPortData[x].size() == PortLen[x]);
1127
1128 memcpy(PostEmulatePortData[x].data(), PortData[x], PortLen[x]);
1129 memset(PortData[x], 0, PortLen[x]);
1130 }
1131
1132 //
1133 // Remap rumble and status(along with other data that doesn't matter), and copy switch state bits
1134 // into the current port data from a backup copy saved BEFORE all netplay shenanigans(including load remote save state),
1135 // as a kludgey way of keeping switches from getting into a delay/feedback loop and going bonkers.
1136 //
1137 for(unsigned x = 0; x < NumPorts; x++)
1138 {
1139 if(PortLVtoVMap[x] != 0xFF)
1140 memcpy(PortData[x], PostEmulatePortData[PortLVtoVMap[x]].data(), PortLen[x]);
1141
1142 for(auto const& idii : MDFNGameInfo->PortInfo[x].DeviceInfo[PortDevIdx[x]].IDII)
1143 {
1144 switch(idii.Type)
1145 {
1146 default:
1147 break;
1148 case IDIT_SWITCH:
1149 {
1150 uint32 tmp;
1151
1152 tmp = BitsExtract(PreNPPortDataPortData[x].data(), idii.BitOffset, idii.BitSize);
1153 BitsIntract(PortData[x], idii.BitOffset, idii.BitSize, tmp);
1154 }
1155 break;
1156 }
1157 }
1158 }
1159 }
1160
1161 //
1162 //
1163 //
1164 //
1165
1166 struct CommandEntry
1167 {
1168 const char *name;
1169 bool (*func)(const char* arg);
1170 const char *help_args;
1171 const char *help_desc;
1172 };
1173
1174 static bool CC_server(const char *arg);
1175 static bool CC_quit(const char *arg);
1176 static bool CC_help(const char *arg);
1177 static bool CC_nick(const char *arg);
1178 static bool CC_ping(const char *arg);
1179 //static bool CC_integrity(const char *arg);
1180 static bool CC_gamekey(const char *arg);
1181 static bool CC_swap(const char *arg);
1182 static bool CC_dupe(const char *arg);
1183 static bool CC_drop(const char *arg);
1184 static bool CC_take(const char *arg);
1185 static bool CC_list(const char *arg);
1186
1187 static CommandEntry ConsoleCommands[] =
1188 {
1189 { "/server", CC_server, gettext_noop("[REMOTE_HOST] [PORT]"), "Connects to REMOTE_HOST(IP address or FQDN), on PORT." },
1190
1191 { "/connect", CC_server, NULL, NULL },
1192
1193 { "/gamekey", CC_gamekey, gettext_noop("[GAMEKEY]"), gettext_noop("Changes the game key to the specified GAMEKEY.") },
1194
1195 { "/quit", CC_quit, gettext_noop("[MESSAGE]"), gettext_noop("Disconnects from the netplay server.") },
1196
1197 { "/help", CC_help, "", gettext_noop("Help, I'm drowning in a sea of cliche metaphors!") },
1198
1199 { "/nick", CC_nick, gettext_noop("NICKNAME"), gettext_noop("Changes your nickname to the specified NICKNAME.") },
1200
1201 { "/swap", CC_swap, gettext_noop("A B"), gettext_noop("Swap/Exchange all instances of controllers A and B(numbered from 1).") },
1202
1203 { "/dupe", CC_dupe, gettext_noop("[A] [...]"), gettext_noop("Duplicate and take instances of specified controller(s).") },
1204 { "/drop", CC_drop, gettext_noop("[A] [...]"), gettext_noop("Drop all instances of specified controller(s).") },
1205 { "/take", CC_take, gettext_noop("[A] [...]"), gettext_noop("Take all instances of specified controller(s).") },
1206
1207 { "/list", CC_list, "", "List players in game." },
1208
1209 { "/ping", CC_ping, "", "Pings the server." },
1210
1211 //{ "/integrity", CC_integrity, "", "Starts netplay integrity check sequence." },
1212
1213 { NULL, NULL },
1214 };
1215
MDFNI_NetplayDisconnect(void)1216 void MDFNI_NetplayDisconnect(void)
1217 {
1218 const bool had_connection = Connection != nullptr;
1219 Connection.reset(nullptr);
1220
1221 if(MDFNnetplay)
1222 {
1223 Joined = false;
1224 MDFNnetplay = 0;
1225 MDFN_FlushGameCheats(1); /* Don't save netplay cheats. */
1226 MDFN_LoadGameCheats(0); /* Reload our original cheats. */
1227 OurNick.clear();
1228 PlayersList.clear();
1229 incoming_buffer.reset(nullptr);
1230 outgoing_buffer.reset(nullptr);
1231
1232 NetPrintText(_("*** Disconnected"));
1233 }
1234 else if(had_connection)
1235 {
1236 NetPrintText(_("*** In-progress connection attempt aborted"));
1237 }
1238
1239 MDFND_NetplaySetHints(false, false, 0);
1240 }
1241
1242
MDFNI_NetplayConnect(void)1243 void MDFNI_NetplayConnect(void)
1244 {
1245 MDFNI_NetplayDisconnect();
1246 //
1247 //
1248 try
1249 {
1250 std::string remote_host = MDFN_GetSettingS("netplay.host");
1251 unsigned int remote_port = MDFN_GetSettingUI("netplay.port");
1252
1253 NetPrintText(_("*** Connecting to %s port %u..."), remote_host.c_str(), remote_port);
1254 Connection = Net::Connect(remote_host.c_str(), remote_port);
1255 }
1256 catch(std::exception &e)
1257 {
1258 NetError("%s", e.what());
1259 }
1260 }
1261
CC_server(const char * arg)1262 static bool CC_server(const char *arg)
1263 {
1264 char server[300];
1265 unsigned int port = 0;
1266
1267 server[0] = 0;
1268
1269 switch(trio_sscanf(arg, "%299s %u", server, &port))
1270 {
1271 default:
1272 case 0:
1273 break;
1274
1275 case 1:
1276 MDFNI_SetSetting("netplay.host", server);
1277 break;
1278
1279 case 2:
1280 MDFNI_SetSetting("netplay.host", server);
1281 MDFNI_SetSettingUI("netplay.port", port);
1282 break;
1283 }
1284
1285 MDFNI_NetplayConnect();
1286
1287 return(false);
1288 }
1289
CC_gamekey(const char * arg)1290 static bool CC_gamekey(const char *arg)
1291 {
1292 MDFNI_SetSetting("netplay.gamekey", arg);
1293
1294 if(arg[0] == 0)
1295 NetPrintText(_("** Game key cleared."));
1296 else
1297 {
1298 NetPrintText(_("** Game key changed to: %s"), arg);
1299 }
1300
1301 if(MDFNnetplay)
1302 {
1303 NetPrintText(_("** Caution: Changing the game key will not affect the current netplay session."));
1304 }
1305
1306 return(true);
1307 }
1308
CC_quit(const char * arg)1309 static bool CC_quit(const char *arg)
1310 {
1311 if(!MDFNnetplay && !Connection)
1312 {
1313 NetPrintText(_("*** Not connected!"));
1314 return true;
1315 }
1316
1317 if(MDFNnetplay)
1318 MDFNI_NetplayQuit(arg);
1319
1320 MDFNI_NetplayDisconnect();
1321
1322 return false;
1323 }
1324
CC_list(const char * arg)1325 static bool CC_list(const char *arg)
1326 {
1327 if(MDFNnetplay)
1328 MDFNI_NetplayList();
1329 else
1330 {
1331 NetPrintText(_("*** Not connected!"));
1332 return(true);
1333 }
1334
1335 return(true);
1336 }
1337
CC_swap(const char * arg)1338 static bool CC_swap(const char *arg)
1339 {
1340 int a = 0, b = 0;
1341
1342 if(sscanf(arg, "%u %u", &a, &b) == 2 && a && b)
1343 {
1344 uint32 sc = ((a - 1) & 0xFF) | (((b - 1) & 0xFF) << 8);
1345
1346 if(MDFNnetplay)
1347 MDFNI_NetplaySwap((sc >> 0) & 0xFF, (sc >> 8) & 0xFF);
1348 else
1349 {
1350 NetPrintText(_("*** Not connected!"));
1351 return(true);
1352 }
1353 }
1354 else
1355 {
1356 NetPrintText(_("*** %s command requires at least %u non-zero integer argument(s)."), "SWAP", 2);
1357 return(true);
1358 }
1359
1360 return(false);
1361 }
1362
CC_dupe(const char * arg)1363 static bool CC_dupe(const char *arg)
1364 {
1365 int tmp[32];
1366 int count;
1367
1368
1369 memset(tmp, 0, sizeof(tmp));
1370 count = sscanf(arg, "%u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
1371 &tmp[0x00], &tmp[0x01], &tmp[0x02], &tmp[0x03], &tmp[0x04], &tmp[0x05], &tmp[0x06], &tmp[0x07],
1372 &tmp[0x08], &tmp[0x09], &tmp[0x0A], &tmp[0x0B], &tmp[0x0C], &tmp[0x0D], &tmp[0x0E], &tmp[0x0F],
1373 &tmp[0x00], &tmp[0x01], &tmp[0x02], &tmp[0x03], &tmp[0x04], &tmp[0x05], &tmp[0x06], &tmp[0x07],
1374 &tmp[0x08], &tmp[0x09], &tmp[0x0A], &tmp[0x0B], &tmp[0x0C], &tmp[0x0D], &tmp[0x0E], &tmp[0x0F]);
1375
1376 if(count > 0)
1377 {
1378 uint32 mask = 0;
1379
1380 for(int i = 0; i < 32; i++)
1381 {
1382 if(tmp[i] > 0)
1383 mask |= 1U << (unsigned)(tmp[i] - 1);
1384 }
1385
1386 if(MDFNnetplay)
1387 MDFNI_NetplayDupe(mask);
1388 else
1389 {
1390 NetPrintText(_("*** Not connected!"));
1391 return(true);
1392 }
1393 }
1394 else
1395 {
1396 NetPrintText(_("*** %s command requires at least %u non-zero integer argument(s)."), "DUPE", 1);
1397 return(true);
1398 }
1399
1400 return(false);
1401 }
1402
CC_drop(const char * arg)1403 static bool CC_drop(const char *arg)
1404 {
1405 int tmp[32];
1406 int count;
1407
1408
1409 memset(tmp, 0, sizeof(tmp));
1410 count = sscanf(arg, "%u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
1411 &tmp[0x00], &tmp[0x01], &tmp[0x02], &tmp[0x03], &tmp[0x04], &tmp[0x05], &tmp[0x06], &tmp[0x07],
1412 &tmp[0x08], &tmp[0x09], &tmp[0x0A], &tmp[0x0B], &tmp[0x0C], &tmp[0x0D], &tmp[0x0E], &tmp[0x0F],
1413 &tmp[0x00], &tmp[0x01], &tmp[0x02], &tmp[0x03], &tmp[0x04], &tmp[0x05], &tmp[0x06], &tmp[0x07],
1414 &tmp[0x08], &tmp[0x09], &tmp[0x0A], &tmp[0x0B], &tmp[0x0C], &tmp[0x0D], &tmp[0x0E], &tmp[0x0F]);
1415
1416 if(count > 0)
1417 {
1418 uint32 mask = 0;
1419
1420 for(int i = 0; i < 32; i++)
1421 {
1422 if(tmp[i] > 0)
1423 mask |= 1U << (unsigned)(tmp[i] - 1);
1424 }
1425
1426 if(MDFNnetplay)
1427 MDFNI_NetplayDrop(mask);
1428 else
1429 {
1430 NetPrintText(_("*** Not connected!"));
1431 return(true);
1432 }
1433 }
1434 else
1435 {
1436 NetPrintText(_("*** %s command requires at least %u non-zero integer argument(s)."), "DROP", 1);
1437 return(true);
1438 }
1439
1440 return(false);
1441 }
1442
CC_take(const char * arg)1443 static bool CC_take(const char *arg)
1444 {
1445 int tmp[32];
1446 int count;
1447
1448
1449 memset(tmp, 0, sizeof(tmp));
1450 count = sscanf(arg, "%u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u %u",
1451 &tmp[0x00], &tmp[0x01], &tmp[0x02], &tmp[0x03], &tmp[0x04], &tmp[0x05], &tmp[0x06], &tmp[0x07],
1452 &tmp[0x08], &tmp[0x09], &tmp[0x0A], &tmp[0x0B], &tmp[0x0C], &tmp[0x0D], &tmp[0x0E], &tmp[0x0F],
1453 &tmp[0x00], &tmp[0x01], &tmp[0x02], &tmp[0x03], &tmp[0x04], &tmp[0x05], &tmp[0x06], &tmp[0x07],
1454 &tmp[0x08], &tmp[0x09], &tmp[0x0A], &tmp[0x0B], &tmp[0x0C], &tmp[0x0D], &tmp[0x0E], &tmp[0x0F]);
1455
1456 if(count > 0)
1457 {
1458 uint32 mask = 0;
1459
1460 for(int i = 0; i < 32; i++)
1461 {
1462 if(tmp[i] > 0)
1463 mask |= 1U << (unsigned)(tmp[i] - 1);
1464 }
1465
1466 if(MDFNnetplay)
1467 MDFNI_NetplayTake(mask);
1468 else
1469 {
1470 NetPrintText(_("*** Not connected!"));
1471 return(true);
1472 }
1473 }
1474 else
1475 {
1476 NetPrintText(_("*** %s command requires at least %u non-zero integer argument(s)."), "TAKE", 1);
1477 return(true);
1478 }
1479
1480 return(false);
1481 }
1482
CC_ping(const char * arg)1483 static bool CC_ping(const char *arg)
1484 {
1485 if(MDFNnetplay)
1486 MDFNI_NetplayPing();
1487 else
1488 {
1489 NetPrintText(_("*** Not connected!"));
1490 return(true);
1491 }
1492
1493 return(false);
1494 }
1495
1496 #if 0
1497 static bool CC_integrity(const char *arg)
1498 {
1499 if(MDFNnetplay)
1500 MDFNI_NetplayIntegrity();
1501 else
1502 {
1503 NetPrintText(_("*** Not connected!"));
1504 return(true);
1505 }
1506
1507 return(false);
1508 }
1509 #endif
1510
CC_help(const char * arg)1511 static bool CC_help(const char *arg)
1512 {
1513 for(unsigned int x = 0; ConsoleCommands[x].name; x++)
1514 {
1515 if(ConsoleCommands[x].help_desc)
1516 {
1517 char help_buf[512];
1518 trio_snprintf(help_buf, 512, "%s %s - %s", ConsoleCommands[x].name, _(ConsoleCommands[x].help_args), _(ConsoleCommands[x].help_desc));
1519 MDFND_NetplayText(help_buf, false);
1520 }
1521 }
1522 return(true);
1523 }
1524
CC_nick(const char * arg)1525 static bool CC_nick(const char *arg)
1526 {
1527 MDFNI_SetSetting("netplay.nick", arg);
1528
1529 if(MDFNnetplay)
1530 MDFNI_NetplayChangeNick(arg);
1531
1532 return(true);
1533 }
1534
MDFNI_NetplayLine(const char * text,bool & inputable,bool & viewable)1535 void MDFNI_NetplayLine(const char *text, bool &inputable, bool &viewable)
1536 {
1537 inputable = viewable = false;
1538
1539 for(unsigned int x = 0; ConsoleCommands[x].name; x++)
1540 {
1541 if(!MDFN_strazicmp(ConsoleCommands[x].name, (char*)text, strlen(ConsoleCommands[x].name)) && text[strlen(ConsoleCommands[x].name)] <= 0x20)
1542 {
1543 std::string trim_text(&text[strlen(ConsoleCommands[x].name)]);
1544
1545 MDFN_trim(&trim_text);
1546
1547 inputable = viewable = ConsoleCommands[x].func(trim_text.c_str());
1548
1549 return;
1550 }
1551 }
1552
1553 if(text[0] != 0) // Is non-empty line?
1554 {
1555 MDFNI_NetplayText(text);
1556 viewable = true;
1557 }
1558 }
1559
1560 }
1561