1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16
17 #include "C4Include.h"
18 #include "C4ForbidLibraryCompilation.h"
19 #include "network/C4Network2.h"
20
21 #include "C4Version.h"
22 #include "control/C4GameControl.h"
23 #include "control/C4GameSave.h"
24 #include "control/C4RoundResults.h"
25 #include "editor/C4Console.h"
26 #include "game/C4Application.h"
27 #include "game/C4GraphicsSystem.h"
28 #include "graphics/C4Draw.h"
29 #include "graphics/C4GraphicsResource.h"
30
31 // lobby
32 #include "gui/C4GameLobby.h"
33
34 #include "network/C4Network2Dialogs.h"
35 #include "network/C4League.h"
36
37 #ifdef _WIN32
38 #include <direct.h>
39 #endif
40 #ifndef HAVE_WINSOCK
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44 #endif
45
46 // compile options
47 #ifdef _MSC_VER
48 #pragma warning (disable: 4355)
49 #endif
50
51 // *** C4Network2Status
52
53 C4Network2Status::C4Network2Status() = default;
54
getStateName() const55 const char *C4Network2Status::getStateName() const
56 {
57 switch (eState)
58 {
59 case GS_None: return "none";
60 case GS_Init: return "init";
61 case GS_Lobby: return "lobby";
62 case GS_Pause: return "pause";
63 case GS_Go: return "go";
64 }
65 return "???";
66 }
67
getDescription() const68 const char *C4Network2Status::getDescription() const
69 {
70 switch (eState)
71 {
72 case GS_None: return LoadResStr("IDS_DESC_NOTINITED");
73 case GS_Init: return LoadResStr("IDS_DESC_WAITFORHOST");
74 case GS_Lobby: return LoadResStr("IDS_DESC_EXPECTING");
75 case GS_Pause: return LoadResStr("IDS_DESC_GAMEPAUSED");
76 case GS_Go: return LoadResStr("IDS_DESC_GAMERUNNING");
77 }
78 return LoadResStr("IDS_DESC_UNKNOWNGAMESTATE");
79 }
80
Set(C4NetGameState enState,int32_t inTargetTick)81 void C4Network2Status::Set(C4NetGameState enState, int32_t inTargetTick)
82 {
83 eState = enState; iTargetCtrlTick = inTargetTick;
84 }
85
SetCtrlMode(int32_t inCtrlMode)86 void C4Network2Status::SetCtrlMode(int32_t inCtrlMode)
87 {
88 iCtrlMode = inCtrlMode;
89 }
90
SetTargetTick(int32_t inTargetCtrlTick)91 void C4Network2Status::SetTargetTick(int32_t inTargetCtrlTick)
92 {
93 iTargetCtrlTick = inTargetCtrlTick;
94 }
95
Clear()96 void C4Network2Status::Clear()
97 {
98 eState = GS_None; iTargetCtrlTick = -1;
99 }
100
CompileFunc(StdCompiler * pComp)101 void C4Network2Status::CompileFunc(StdCompiler *pComp)
102 {
103 CompileFunc(pComp, false);
104 }
105
CompileFunc(StdCompiler * pComp,bool fReference)106 void C4Network2Status::CompileFunc(StdCompiler *pComp, bool fReference)
107 {
108 StdEnumEntry<C4NetGameState> GameStates[] =
109 {
110 { "None", GS_None },
111 { "Init", GS_Init },
112 { "Lobby", GS_Lobby },
113 { "Paused", GS_Pause },
114 { "Running", GS_Go },
115 };
116 pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(eState, GameStates), "State", GS_None));
117 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iCtrlMode), "CtrlMode", -1));
118
119 if (!fReference)
120 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iTargetCtrlTick), "TargetTick", -1));
121 }
122
123 // *** C4Network2
124
C4Network2()125 C4Network2::C4Network2()
126 : Clients(&NetIO),
127 tLastActivateRequest(C4TimeMilliseconds::NegativeInfinity),
128 NetpuncherGameID(C4NetpuncherID())
129 {
130
131 }
132
~C4Network2()133 C4Network2::~C4Network2()
134 {
135 Clear();
136 }
137
InitHost(bool fLobby)138 bool C4Network2::InitHost(bool fLobby)
139 {
140 if (isEnabled()) Clear();
141 // initialize everything
142 Status.Set(fLobby ? GS_Lobby : GS_Go, ::Control.ControlTick);
143 Status.SetCtrlMode(Config.Network.ControlMode);
144 fHost = true;
145 fStatusAck = fStatusReached = true;
146 fChasing = false;
147 fAllowJoin = false;
148 iNextClientID = C4ClientIDStart;
149 NetpuncherGameID = C4NetpuncherID();
150 NetpuncherAddr = ::Config.Network.PuncherAddress;
151 // initialize client list
152 Clients.Init(&Game.Clients, true);
153 // initialize resource list
154 if (!ResList.Init(Game.Clients.getLocalID(), &NetIO))
155 { LogFatal("Network: failed to initialize resource list!"); Clear(); return false; }
156 if (!Game.Parameters.InitNetwork(&ResList))
157 return false;
158 // create initial dynamic
159 if (!CreateDynamic(true))
160 return false;
161 // initialize net i/o
162 if (!InitNetIO(false, true))
163 { Clear(); return false; }
164 // init network control
165 pControl = &::Control.Network;
166 pControl->Init(C4ClientIDHost, true, ::Control.getNextControlTick(), true, this);
167 // init league
168 bool fCancel = true;
169 if (!InitLeague(&fCancel) || !LeagueStart(&fCancel))
170 {
171 // deinit league
172 DeinitLeague();
173 // user cancelled?
174 if (fCancel)
175 return false;
176 // in console mode, bail out
177 #ifdef USE_CONSOLE
178 return false;
179 #endif
180 }
181 // allow connect
182 NetIO.SetAcceptMode(true);
183 // timer
184 Application.Add(this);
185 // ok
186 return true;
187 }
188
189 // Orders connection addresses to optimize joining.
SortAddresses(std::vector<C4Network2Address> & addrs)190 static void SortAddresses(std::vector<C4Network2Address>& addrs)
191 {
192 // TODO: Maybe use addresses from local client to avoid the extra system calls.
193 auto localAddrs = C4NetIO::GetLocalAddresses();
194 bool haveIPv6 = false;
195 for (auto& addr : localAddrs)
196 {
197 if (addr.GetFamily() == C4NetIO::HostAddress::IPv6 && !addr.IsLocal() && !addr.IsPrivate())
198 {
199 haveIPv6 = true;
200 break;
201 }
202 }
203
204 auto rank = [&](const C4Network2Address& Addr)
205 {
206 // Rank addresses. For IPv6-enabled clients, try public IPv6 addresses first, then IPv4,
207 // then link-local IPv6. For IPv4-only clients, skip IPv6.
208 int rank = 0;
209 auto addr = Addr.getAddr();
210 switch (addr.GetFamily())
211 {
212 case C4NetIO::HostAddress::IPv6:
213 if (addr.IsLocal())
214 rank = 100;
215 else if (addr.IsPrivate())
216 rank = 150;
217 else if (haveIPv6)
218 // TODO: Rank public IPv6 addresses by longest matching prefix with local addresses.
219 rank = 300;
220 break;
221 case C4NetIO::HostAddress::IPv4:
222 if (addr.IsPrivate())
223 rank = 150;
224 else
225 rank = 200;
226 break;
227 default:
228 assert(!"Unexpected address family");
229 }
230 return rank;
231 };
232
233 // Sort by decreasing rank. Use stable sort to allow the host to prioritize addresses within a family.
234 std::stable_sort(addrs.begin(), addrs.end(), [&](auto a, auto b) { return rank(a) > rank(b); });
235 }
236
InitClient(const C4Network2Reference & Ref,bool fObserver)237 C4Network2::InitResult C4Network2::InitClient(const C4Network2Reference &Ref, bool fObserver)
238 {
239 if (isEnabled()) Clear();
240 // Get host core
241 const C4ClientCore &HostCore = Ref.Parameters.Clients.getHost()->getCore();
242 // host core revision check
243 if (!SEqualNoCase(HostCore.getRevision(), Application.GetRevision()))
244 {
245 StdStrBuf msg;
246 msg.Format(LoadResStr("IDS_NET_ERR_VERSIONMISMATCH"), HostCore.getRevision(), Application.GetRevision());
247 if (!Application.isEditor)
248 {
249 if (!pGUI->ShowMessageModal(msg.getData(), "[!]Network warning", C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Notify, nullptr /* do not allow to skip this message! */))
250 return IR_Fatal;
251 }
252 else
253 {
254 Log(msg.getData());
255 }
256 }
257 // repeat if wrong password
258 fWrongPassword = Ref.isPasswordNeeded();
259 NetpuncherGameID = Ref.getNetpuncherGameID();
260 NetpuncherAddr = Ref.getNetpuncherAddr();
261 StdStrBuf Password;
262
263 // copy addresses
264 std::vector<C4Network2Address> Addrs;
265 for (int i = 0; i < Ref.getAddrCnt(); i++)
266 {
267 C4Network2Address a = Ref.getAddr(i);
268 a.getAddr().SetScopeId(Ref.GetSourceAddress().GetScopeId());
269 Addrs.push_back(std::move(a));
270 }
271 SortAddresses(Addrs);
272 for (;;)
273 {
274 // ask for password (again)?
275 if (fWrongPassword)
276 {
277 Password.Take(QueryClientPassword());
278 if (!Password.getLength())
279 return IR_Error;
280 fWrongPassword = false;
281 }
282 // Try to connect to host
283 if (InitClient(Addrs, HostCore, Password.getData()) == IR_Fatal)
284 return IR_Fatal;
285 // success?
286 if (isEnabled())
287 break;
288 // Retry only for wrong password
289 if (!fWrongPassword)
290 {
291 LogSilent("Network: Could not connect!");
292 return IR_Error;
293 }
294 }
295 // initialize resources
296 if (!Game.Parameters.InitNetwork(&ResList))
297 return IR_Fatal;
298 // init league
299 if (!InitLeague(nullptr))
300 {
301 // deinit league
302 DeinitLeague();
303 return IR_Fatal;
304 }
305 // allow connect
306 NetIO.SetAcceptMode(true);
307 // timer
308 Application.Add(this);
309 // ok, success
310 return IR_Success;
311 }
312
InitialConnect(const std::vector<C4Network2Address> & Addrs,const C4ClientCore & HostCore,const char * Password)313 C4Network2::InitialConnect::InitialConnect(const std::vector<C4Network2Address>& Addrs, const C4ClientCore& HostCore, const char *Password)
314 : CStdTimerProc(DELAY), Addrs(Addrs), CurrentAddr(this->Addrs.cbegin()),
315 HostCore(HostCore), Password(Password)
316 {
317 Application.Add(this);
318 }
319
~InitialConnect()320 C4Network2::InitialConnect::~InitialConnect()
321 {
322 Done();
323 }
324
Execute(int,pollfd *)325 bool C4Network2::InitialConnect::Execute(int, pollfd *)
326 {
327 if (CheckAndReset())
328 TryNext();
329 return true;
330 }
331
TryNext()332 void C4Network2::InitialConnect::TryNext()
333 {
334 StdStrBuf strAddresses; int Successes = 0;
335 for (; Successes < ADDR_PER_TRY && CurrentAddr != Addrs.cend(); ++CurrentAddr)
336 {
337 if (!CurrentAddr->isIPNull())
338 {
339 auto addr = CurrentAddr->getAddr();
340 std::vector<C4NetIO::addr_t> addrs;
341 if (addr.IsLocal())
342 {
343 // Local IPv6 addresses need a scope id.
344 for (auto& id : Network.Clients.GetLocal()->getInterfaceIDs())
345 {
346 addr.SetScopeId(id);
347 addrs.push_back(addr);
348 }
349 }
350 else
351 addrs.push_back(addr);
352 // connection
353 int cnt = 0;
354 for (auto& a : addrs)
355 if (Network.NetIO.Connect(a, CurrentAddr->getProtocol(), HostCore, Password))
356 cnt++;
357 if (cnt == 0) continue;
358 // format for message
359 if (strAddresses.getLength())
360 strAddresses.Append(", ");
361 strAddresses.Append(CurrentAddr->toString());
362 Successes++;
363 }
364 }
365 if (Successes > 0)
366 {
367 LogF(LoadResStr("IDS_NET_CONNECTHOST"), strAddresses.getData());
368 }
369 else
370 {
371 Done();
372 }
373 }
374
Done()375 void C4Network2::InitialConnect::Done()
376 {
377 Application.Remove(this);
378 }
379
InitClient(const std::vector<class C4Network2Address> & Addrs,const C4ClientCore & HostCore,const char * szPassword)380 C4Network2::InitResult C4Network2::InitClient(const std::vector<class C4Network2Address>& Addrs, const C4ClientCore &HostCore, const char *szPassword)
381 {
382 // initialization
383 Status.Set(GS_Init, -1);
384 fHost = false;
385 fStatusAck = fStatusReached = true;
386 fChasing = true;
387 fAllowJoin = false;
388 // initialize client list
389 Game.Clients.Init(C4ClientIDUnknown);
390 Clients.Init(&Game.Clients, false);
391 // initialize resource list
392 if (!ResList.Init(Game.Clients.getLocalID(), &NetIO))
393 { LogFatal(LoadResStr("IDS_NET_ERR_INITRESLIST")); Clear(); return IR_Fatal; }
394 // initialize net i/o
395 if (!InitNetIO(true, false))
396 { Clear(); return IR_Fatal; }
397 // set network control
398 pControl = &::Control.Network;
399 // set exclusive connection mode
400 NetIO.SetExclusiveConnMode(true);
401 // warm up netpuncher
402 InitPuncher();
403 // try to connect host
404 InitialConnect iconn(Addrs, HostCore, szPassword);
405 // show box
406 std::unique_ptr<C4GUI::MessageDialog> pDlg = nullptr;
407 if (!Application.isEditor)
408 {
409 StdStrBuf strMessage = FormatString(LoadResStr("IDS_NET_JOINGAMEBY"), HostCore.getName());
410 // create & show
411 pDlg = std::make_unique<C4GUI::MessageDialog>(
412 strMessage.getData(), LoadResStr("IDS_NET_JOINGAME"),
413 C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsRegular);
414 if (!pDlg->Show(::pGUI, true)) { Clear(); return IR_Fatal; }
415 }
416 // wait for connect / timeout / abort by user (host will change status on succesful connect)
417 while (Status.getState() == GS_Init)
418 {
419 if (!Application.ScheduleProcs(100))
420 { return IR_Fatal;}
421 if (pDlg && pDlg->IsAborted())
422 { return IR_Fatal; }
423 }
424 // error?
425 if (!isEnabled())
426 return IR_Error;
427 // deactivate exclusive connection mode
428 NetIO.SetExclusiveConnMode(false);
429 return IR_Success;
430 }
431
DoLobby()432 bool C4Network2::DoLobby()
433 {
434 // shouldn't do lobby?
435 if (!isEnabled() || (!isHost() && !isLobbyActive()))
436 return true;
437
438 // lobby runs
439 fLobbyRunning = true;
440 fAllowJoin = true;
441 Log(LoadResStr("IDS_NET_LOBBYWAITING"));
442
443 // client: lobby status reached, message to host
444 if (!isHost())
445 CheckStatusReached();
446 // host: set lobby mode
447 else
448 ChangeGameStatus(GS_Lobby, 0);
449
450 // determine lobby type
451 if (Console.Active)
452 {
453 // console lobby - update console
454 Console.UpdateMenus();
455 // init lobby countdown if specified
456 if (Game.iLobbyTimeout) StartLobbyCountdown(Game.iLobbyTimeout);
457 // do console lobby
458 while (isLobbyActive())
459 if (!Application.ScheduleProcs())
460 { Clear(); return false; }
461 }
462 else
463 {
464 // fullscreen lobby
465
466 // init lobby dialog
467 pLobby = new C4GameLobby::MainDlg(isHost());
468 if (!pLobby->FadeIn(::pGUI)) { delete pLobby; pLobby = nullptr; Clear(); return false; }
469
470 // init lobby countdown if specified
471 if (Game.iLobbyTimeout) StartLobbyCountdown(Game.iLobbyTimeout);
472
473 // while state lobby: keep looping
474 while (isLobbyActive() && pLobby && pLobby->IsShown())
475 if (!Application.ScheduleProcs())
476 { Clear(); return false; }
477
478 // check whether lobby was aborted
479 if (pLobby && pLobby->IsAborted()) { delete pLobby; pLobby = nullptr; Clear(); return false; }
480
481 // deinit lobby
482 if (pLobby && pLobby->IsShown()) pLobby->Close(true);
483 delete pLobby; pLobby = nullptr;
484
485 // close any other dialogs
486 ::pGUI->CloseAllDialogs(false);
487 }
488
489 // lobby end
490 delete pLobbyCountdown; pLobbyCountdown = nullptr;
491 fLobbyRunning = false;
492 fAllowJoin = !Config.Network.NoRuntimeJoin;
493
494 // notify user that the lobby has ended (for people who tasked out)
495 Application.NotifyUserIfInactive();
496
497 // notify lobby end
498 bool fGameGo = isEnabled();
499 if (fGameGo) Log(LoadResStr("IDS_PRC_GAMEGO"));;
500
501 // disabled?
502 return fGameGo;
503 }
504
Start()505 bool C4Network2::Start()
506 {
507 if (!isEnabled() || !isHost()) return false;
508 // change mode: go
509 ChangeGameStatus(GS_Go, ::Control.ControlTick);
510 return true;
511 }
512
Pause()513 bool C4Network2::Pause()
514 {
515 if (!isEnabled() || !isHost()) return false;
516 // change mode: pause
517 return ChangeGameStatus(GS_Pause, ::Control.getNextControlTick());
518 }
519
Sync()520 bool C4Network2::Sync()
521 {
522 // host only
523 if (!isEnabled() || !isHost()) return false;
524 // already syncing the network?
525 if (!fStatusAck)
526 {
527 // maybe we are already sync?
528 if (fStatusReached) CheckStatusAck();
529 return true;
530 }
531 // already sync?
532 if (isFrozen()) return true;
533 // ok, so let's do a sync: change in the same state we are already in
534 return ChangeGameStatus(Status.getState(), ::Control.getNextControlTick());
535 }
536
FinalInit()537 bool C4Network2::FinalInit()
538 {
539 // check reach
540 CheckStatusReached(true);
541 // reached, waiting for ack?
542 if (fStatusReached && !fStatusAck)
543 {
544 // wait for go acknowledgement
545 Log(LoadResStr("IDS_NET_JOINREADY"));
546
547 // any pending keyboard commands should not be routed to cancel the wait dialog - flush the message queue!
548 if (!Application.FlushMessages()) return false;
549
550 // show box
551 C4GUI::Dialog *pDlg = nullptr;
552 if (!Application.isEditor)
553 {
554 // separate dlgs for host/client
555 if (isHost())
556 pDlg = new C4Network2StartWaitDlg();
557 else
558 pDlg = new C4GUI::MessageDialog(LoadResStr("IDS_NET_WAITFORSTART"), LoadResStr("IDS_NET_CAPTION"),
559 C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsSmall);
560 // show it
561 if (!pDlg->Show(::pGUI, true)) return false;
562 }
563
564 // wait for acknowledgement
565 while (fStatusReached && !fStatusAck)
566 {
567 if (pDlg)
568 {
569 // execute
570 if (!pDlg->Execute()) { delete pDlg; Clear(); return false; }
571 // aborted?
572 if (pDlg->IsAborted()) { delete pDlg; Clear(); return false; }
573 }
574 else if (!Application.ScheduleProcs())
575 { Clear(); return false; }
576 }
577 delete pDlg;
578 // log
579 Log(LoadResStr("IDS_NET_START"));
580 }
581 // synchronize
582 Game.SyncClearance();
583 Game.Synchronize(false);
584 // finished
585 return isEnabled();
586 }
587
588
RetrieveScenario(char * szScenario)589 bool C4Network2::RetrieveScenario(char *szScenario)
590 {
591 // client only
592 if (isHost()) return false;
593
594 // wait for scenario
595 C4Network2Res::Ref pScenario = RetrieveRes(*Game.Parameters.Scenario.getResCore(),
596 C4NetResRetrieveTimeout, LoadResStr("IDS_NET_RES_SCENARIO"));
597 if (!pScenario)
598 return false;
599
600 // wait for dynamic data
601 C4Network2Res::Ref pDynamic = RetrieveRes(ResDynamic, C4NetResRetrieveTimeout, LoadResStr("IDS_NET_RES_DYNAMIC"));
602 if (!pDynamic)
603 return false;
604
605 // create unpacked copy of scenario
606 if (!ResList.FindTempResFileName(FormatString("Combined%d.ocs", Game.Clients.getLocalID()).getData(), szScenario) ||
607 !C4Group_CopyItem(pScenario->getFile(), szScenario) ||
608 !C4Group_UnpackDirectory(szScenario))
609 return false;
610
611 // create unpacked copy of dynamic data
612 char szTempDynamic[_MAX_PATH + 1];
613 if (!ResList.FindTempResFileName(pDynamic->getFile(), szTempDynamic) ||
614 !C4Group_CopyItem(pDynamic->getFile(), szTempDynamic) ||
615 !C4Group_UnpackDirectory(szTempDynamic))
616 return false;
617
618 // unpack Material.ocg if materials need to be merged
619 StdStrBuf MaterialScenario, MaterialDynamic;
620 MaterialScenario.Format("%s" DirSep C4CFN_Material, szScenario);
621 MaterialDynamic.Format("%s" DirSep C4CFN_Material, szTempDynamic);
622 if (FileExists(MaterialScenario.getData()) && FileExists(MaterialDynamic.getData()))
623 if (!C4Group_UnpackDirectory(MaterialScenario.getData()) ||
624 !C4Group_UnpackDirectory(MaterialDynamic.getData()))
625 return false;
626
627 // move all dynamic files to scenario
628 C4Group ScenGrp;
629 if (!ScenGrp.Open(szScenario) ||
630 !ScenGrp.Merge(szTempDynamic))
631 return false;
632 ScenGrp.Close();
633
634 // remove dynamic temp file
635 EraseDirectory(szTempDynamic);
636
637 // remove dynamic - isn't needed any more and will soon be out-of-date
638 pDynamic->Remove();
639
640 return true;
641 }
642
OnSec1Timer()643 void C4Network2::OnSec1Timer()
644 {
645 Execute();
646 }
647
Execute()648 void C4Network2::Execute()
649 {
650
651 // client connections
652 Clients.DoConnectAttempts();
653
654 // status reached?
655 CheckStatusReached();
656
657 if (isHost())
658 {
659 // remove dynamic
660 if (!ResDynamic.isNull() && ::Control.ControlTick > iDynamicTick)
661 RemoveDynamic();
662 // Set chase target
663 UpdateChaseTarget();
664 // check for inactive clients and deactivate them
665 DeactivateInactiveClients();
666 // reference
667 if (!iLastReferenceUpdate || time(nullptr) > (time_t) (iLastReferenceUpdate + C4NetReferenceUpdateInterval))
668 if (NetIO.IsReferenceNeeded())
669 {
670 // create
671 C4Network2Reference *pRef = new C4Network2Reference();
672 pRef->InitLocal();
673 // set
674 NetIO.SetReference(pRef);
675 iLastReferenceUpdate = time(nullptr);
676 }
677 // league server reference
678 if (!iLastLeagueUpdate || time(nullptr) > (time_t) (iLastLeagueUpdate + iLeagueUpdateDelay))
679 {
680 LeagueUpdate();
681 }
682 // league update reply receive
683 if (pLeagueClient && fHost && !pLeagueClient->isBusy() && pLeagueClient->getCurrentAction() == C4LA_Update)
684 {
685 LeagueUpdateProcessReply();
686 }
687 // voting timeout
688 if (Votes.firstPkt() && time(nullptr) > (time_t) (iVoteStartTime + C4NetVotingTimeout))
689 {
690 C4ControlVote *pVote = static_cast<C4ControlVote *>(Votes.firstPkt()->getPkt());
691 ::Control.DoInput(
692 CID_VoteEnd,
693 new C4ControlVoteEnd(pVote->getType(), false, pVote->getData()),
694 CDT_Sync);
695 iVoteStartTime = time(nullptr);
696 }
697 // record streaming
698 if (fStreaming)
699 {
700 StreamIn(false);
701 StreamOut();
702 }
703 }
704 else
705 {
706 // request activate, if neccessary
707 if (!tLastActivateRequest.IsInfinite()) RequestActivate();
708 }
709 }
710
Clear()711 void C4Network2::Clear()
712 {
713 // stop timer
714 Application.Remove(this);
715 // stop streaming
716 StopStreaming();
717 // clear league
718 if (pLeagueClient)
719 {
720 LeagueEnd();
721 DeinitLeague();
722 }
723 // stop lobby countdown
724 delete pLobbyCountdown; pLobbyCountdown = nullptr;
725 // cancel lobby
726 delete pLobby; pLobby = nullptr;
727 fLobbyRunning = false;
728 // deactivate
729 Status.Clear();
730 fStatusAck = fStatusReached = true;
731 // if control mode is network: change to local
732 if (::Control.isNetwork())
733 ::Control.ChangeToLocal();
734 // clear all player infos
735 Players.Clear();
736 // remove all clients
737 Clients.Clear();
738 // close net classes
739 NetIO.Clear();
740 // clear resources
741 ResList.Clear();
742 // clear password
743 sPassword.Clear();
744 // stuff
745 fAllowJoin = false;
746 iDynamicTick = -1; fDynamicNeeded = false;
747 tLastActivateRequest = C4TimeMilliseconds::NegativeInfinity;
748 iLastChaseTargetUpdate = iLastReferenceUpdate = iLastLeagueUpdate = 0;
749 fDelayedActivateReq = false;
750 delete pVoteDialog; pVoteDialog = nullptr;
751 fPausedForVote = false;
752 iLastOwnVoting = 0;
753 NetpuncherGameID = C4NetpuncherID();
754 Votes.Clear();
755 // don't clear fPasswordNeeded here, it's needed by InitClient
756 }
757
ToggleAllowJoin()758 bool C4Network2::ToggleAllowJoin()
759 {
760 // just toggle
761 AllowJoin(!fAllowJoin);
762 return true; // toggled
763 }
764
ToggleClientListDlg()765 bool C4Network2::ToggleClientListDlg()
766 {
767 C4Network2ClientListDlg::Toggle();
768 return true;
769 }
770
SetPassword(const char * szToPassword)771 void C4Network2::SetPassword(const char *szToPassword)
772 {
773 bool fHadPassword = isPassworded();
774 // clear password?
775 if (!szToPassword || !*szToPassword)
776 sPassword.Clear();
777 else
778 // no? then set it
779 sPassword.Copy(szToPassword);
780 // if the has-password-state has changed, the reference is invalidated
781 if (fHadPassword != isPassworded()) InvalidateReference();
782 }
783
QueryClientPassword()784 StdStrBuf C4Network2::QueryClientPassword()
785 {
786 // ask client for a password; return nothing if user canceled
787 StdStrBuf sCaption; sCaption.Copy(LoadResStr("IDS_MSG_ENTERPASSWORD"));
788 C4GUI::InputDialog *pInputDlg = new C4GUI::InputDialog(LoadResStr("IDS_MSG_ENTERPASSWORD"), sCaption.getData(), C4GUI::Ico_Ex_Locked, nullptr, false);
789 pInputDlg->SetDelOnClose(false);
790 if (!::pGUI->ShowModalDlg(pInputDlg, false))
791 {
792 delete pInputDlg;
793 return StdStrBuf();
794 }
795 // copy to buffer
796 StdStrBuf Buf; Buf.Copy(pInputDlg->GetInputText());
797 delete pInputDlg;
798 return Buf;
799 }
800
AllowJoin(bool fAllow)801 void C4Network2::AllowJoin(bool fAllow)
802 {
803 if (!isHost()) return;
804 fAllowJoin = fAllow;
805 if (Game.IsRunning)
806 {
807 ::GraphicsSystem.FlashMessage(LoadResStr(fAllowJoin ? "IDS_NET_RUNTIMEJOINFREE" : "IDS_NET_RUNTIMEJOINBARRED"));
808 Config.Network.NoRuntimeJoin = !fAllowJoin;
809 }
810 }
811
SetAllowObserve(bool fAllow)812 void C4Network2::SetAllowObserve(bool fAllow)
813 {
814 if (!isHost()) return;
815 fAllowObserve = fAllow;
816 }
817
SetCtrlMode(int32_t iCtrlMode)818 void C4Network2::SetCtrlMode(int32_t iCtrlMode)
819 {
820 if (!isHost()) return;
821 // no change?
822 if (iCtrlMode == Status.getCtrlMode()) return;
823 // change game status
824 ChangeGameStatus(Status.getState(), ::Control.ControlTick, iCtrlMode);
825 }
826
OnConn(C4Network2IOConnection * pConn)827 void C4Network2::OnConn(C4Network2IOConnection *pConn)
828 {
829 // Nothing to do atm... New pending connections are managed mainly by C4Network2IO
830 // until they are accepted, see PID_Conn/PID_ConnRe handlers in HandlePacket.
831
832 // Note this won't get called anymore because of this (see C4Network2IO::OnConn)
833 }
834
OnDisconn(C4Network2IOConnection * pConn)835 void C4Network2::OnDisconn(C4Network2IOConnection *pConn)
836 {
837 // could not establish host connection?
838 if (Status.getState() == GS_Init && !isHost())
839 {
840 if (!NetIO.getConnectionCount())
841 Clear();
842 return;
843 }
844
845 // connection failed?
846 if (pConn->isFailed())
847 {
848 // call handler
849 OnConnectFail(pConn);
850 return;
851 }
852
853 // search client
854 C4Network2Client *pClient = Clients.GetClient(pConn);
855 // not found? Search by ID (not associated yet, half-accepted connection)
856 if (!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
857 // not found? ignore
858 if (!pClient) return;
859 // remove connection
860 pClient->RemoveConn(pConn);
861
862 // create post-mortem if needed
863 C4PacketPostMortem PostMortem;
864 if (pConn->CreatePostMortem(&PostMortem))
865 {
866 LogSilentF("Network: Sending %d packets for recovery (%d-%d)", PostMortem.getPacketCount(), pConn->getOutPacketCounter() - PostMortem.getPacketCount(), pConn->getOutPacketCounter() - 1);
867 // This might fail because of this disconnect
868 // (If it's the only host connection. We're toast then anyway.)
869 if (!Clients.SendMsgToClient(pConn->getClientID(), MkC4NetIOPacket(PID_PostMortem, PostMortem)))
870 assert(isHost() || !Clients.GetHost()->isConnected());
871 }
872
873 // call handler
874 OnDisconnect(pClient, pConn);
875
876 }
877
HandlePacket(char cStatus,const C4PacketBase * pPacket,C4Network2IOConnection * pConn)878 void C4Network2::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
879 {
880 // find associated client
881 C4Network2Client *pClient = Clients.GetClient(pConn);
882 if (!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
883
884 // local? ignore
885 if (pClient && pClient->isLocal()) { pConn->Close(); return; }
886
887 #define GETPKT(type, name) \
888 assert(pPacket); const type &name = \
889 static_cast<const type &>(*pPacket);
890
891 switch (cStatus)
892 {
893 case PID_Conn: // connection request
894 {
895 if (!pConn->isOpen()) break;
896 GETPKT(C4PacketConn, rPkt);
897 HandleConn(rPkt, pConn, pClient);
898 }
899 break;
900
901 case PID_ConnRe: // connection request reply
902 {
903 GETPKT(C4PacketConnRe, rPkt);
904 HandleConnRe(rPkt, pConn, pClient);
905 }
906 break;
907
908 case PID_JoinData:
909 {
910 // host->client only
911 if (isHost() || !pClient || !pClient->isHost()) break;
912 if (!pConn->isOpen()) break;
913 // handle
914 GETPKT(C4PacketJoinData, rPkt)
915 HandleJoinData(rPkt);
916 }
917 break;
918
919 case PID_Status: // status change
920 {
921 // by host only
922 if (isHost() || !pClient || !pClient->isHost()) break;
923 if (!pConn->isOpen()) break;
924 // must be initialized
925 if (Status.getState() == GS_Init) break;
926 // handle
927 GETPKT(C4Network2Status, rPkt);
928 HandleStatus(rPkt);
929 }
930 break;
931
932 case PID_StatusAck: // status change acknowledgement
933 {
934 // host->client / client->host only
935 if (!pClient) break;
936 if (!isHost() && !pClient->isHost()) break;
937 // must be initialized
938 if (Status.getState() == GS_Init) break;
939 // handle
940 GETPKT(C4Network2Status, rPkt);
941 HandleStatusAck(rPkt, pClient);
942 }
943 break;
944
945 case PID_ClientActReq: // client activation request
946 {
947 // client->host only
948 if (!isHost() || !pClient || pClient->isHost()) break;
949 // must be initialized
950 if (Status.getState() == GS_Init) break;
951 // handle
952 GETPKT(C4PacketActivateReq, rPkt)
953 HandleActivateReq(rPkt.getTick(), pClient);
954 }
955 break;
956
957 }
958
959 #undef GETPKT
960 }
961
HandleLobbyPacket(char cStatus,const C4PacketBase * pBasePkt,C4Network2IOConnection * pConn)962 void C4Network2::HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn)
963 {
964 // find associated client
965 C4Network2Client *pClient = Clients.GetClient(pConn);
966 if (!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
967 // forward directly to lobby
968 if (pLobby) pLobby->HandlePacket(cStatus, pBasePkt, pClient);
969 }
970
HandlePuncherPacket(C4NetpuncherPacket::uptr pkt,C4NetIO::HostAddress::AddressFamily family)971 bool C4Network2::HandlePuncherPacket(C4NetpuncherPacket::uptr pkt, C4NetIO::HostAddress::AddressFamily family)
972 {
973 // TODO: is this all thread-safe?
974 assert(pkt);
975 #define GETPKT(c) dynamic_cast<C4NetpuncherPacket##c*>(pkt.get())
976 switch (pkt->GetType())
977 {
978 case PID_Puncher_CReq:
979 if (isHost())
980 {
981 NetIO.Punch(GETPKT(CReq)->GetAddr());
982 return true;
983 }
984 else
985 {
986 // The IP/Port should be already in the masterserver list, so just keep trying.
987 return Status.getState() == GS_Init;
988 }
989 case PID_Puncher_AssID:
990 if (isHost())
991 {
992 getNetpuncherGameID(family) = GETPKT(AssID)->GetID();
993 InvalidateReference();
994 }
995 else
996 {
997 // The netpuncher hands out IDs for everyone, but clients have no use for them.
998 }
999 return true;
1000 default: return false;
1001 }
1002 }
1003
getNetpuncherGameID(C4NetIO::HostAddress::AddressFamily family)1004 C4NetpuncherID::value& C4Network2::getNetpuncherGameID(C4NetIO::HostAddress::AddressFamily family)
1005 {
1006 switch (family)
1007 {
1008 case C4NetIO::HostAddress::IPv4: return NetpuncherGameID.v4;
1009 case C4NetIO::HostAddress::IPv6: return NetpuncherGameID.v6;
1010 case C4NetIO::HostAddress::UnknownFamily: assert(!"Unexpected address family");
1011 }
1012 // We need to return a valid reference to satisfy the compiler, even though the code here is unreachable.
1013 return NetpuncherGameID.v4;
1014 }
1015
OnPuncherConnect(C4NetIO::addr_t addr)1016 void C4Network2::OnPuncherConnect(C4NetIO::addr_t addr)
1017 {
1018 // NAT punching is only relevant for IPv4, so convert here to show a proper address.
1019 auto maybe_v4 = addr.AsIPv4();
1020 Application.InteractiveThread.ThreadLogS("Adding address from puncher: %s", maybe_v4.ToString().getData());
1021 // Add for local client
1022 C4Network2Client *pLocal = Clients.GetLocal();
1023 if (pLocal)
1024 {
1025 pLocal->AddAddr(C4Network2Address(maybe_v4, P_UDP), true);
1026 // If the outside port matches the inside port, there is no port translation and the
1027 // TCP address will probably work as well.
1028 if (addr.GetPort() == Config.Network.PortUDP && Config.Network.PortTCP > 0)
1029 {
1030 maybe_v4.SetPort(Config.Network.PortTCP);
1031 pLocal->AddAddr(C4Network2Address(maybe_v4, P_TCP), true);
1032 }
1033 // Do not ::Network.InvalidateReference(); yet, we're expecting an ID from the netpuncher
1034 }
1035 auto family = maybe_v4.GetFamily();
1036 if (isHost())
1037 {
1038 // Host connection: request ID from netpuncher
1039 NetIO.SendPuncherPacket(C4NetpuncherPacketIDReq(), family);
1040 }
1041 else
1042 {
1043 // Client connection: request packet from host.
1044 if (Status.getState() == GS_Init && getNetpuncherGameID(family))
1045 NetIO.SendPuncherPacket(C4NetpuncherPacketSReq(getNetpuncherGameID(family)), family);
1046 }
1047 }
1048
1049
InitPuncher()1050 void C4Network2::InitPuncher()
1051 {
1052 // We have an internet connection, so let's punch the puncher server here in order to open an udp port
1053 C4NetIO::addr_t PuncherAddr;
1054 PuncherAddr.SetAddress(getNetpuncherAddr(), C4NetIO::HostAddress::IPv4);
1055 if (!PuncherAddr.IsNull())
1056 {
1057 PuncherAddr.SetDefaultPort(C4NetStdPortPuncher);
1058 NetIO.InitPuncher(PuncherAddr);
1059 }
1060 PuncherAddr.SetAddress(getNetpuncherAddr(), C4NetIO::HostAddress::IPv6);
1061 if (!PuncherAddr.IsNull())
1062 {
1063 PuncherAddr.SetDefaultPort(C4NetStdPortPuncher);
1064 NetIO.InitPuncher(PuncherAddr);
1065 }
1066 }
1067
OnGameSynchronized()1068 void C4Network2::OnGameSynchronized()
1069 {
1070 // savegame needed?
1071 if (fDynamicNeeded)
1072 {
1073 // create dynamic
1074 bool fSuccess = CreateDynamic(false);
1075 // check for clients that still need join-data
1076 C4Network2Client *pClient = nullptr;
1077 while ((pClient = Clients.GetNextClient(pClient)))
1078 if (!pClient->hasJoinData())
1079 {
1080 if (fSuccess)
1081 // now we can provide join data: send it
1082 SendJoinData(pClient);
1083 else
1084 // join data could not be created: emergency kick
1085 Game.Clients.CtrlRemove(pClient->getClient(), LoadResStr("IDS_ERR_ERRORWHILECREATINGJOINDAT"));
1086 }
1087 }
1088 }
1089
DrawStatus(C4TargetFacet & cgo)1090 void C4Network2::DrawStatus(C4TargetFacet &cgo)
1091 {
1092 if (!isEnabled()) return;
1093
1094 C4Network2Client *pLocal = Clients.GetLocal();
1095
1096 StdStrBuf Stat;
1097
1098 // local client status
1099 Stat.AppendFormat("Local: %s %s %s (ID %d)",
1100 pLocal->isObserver() ? "Observing" : pLocal->isActivated() ? "Active" : "Inactive", pLocal->isHost() ? "host" : "client",
1101 pLocal->getName(), pLocal->getID());
1102
1103 // game status
1104 Stat.AppendFormat( "|Game Status: %s (tick %d)%s%s",
1105 Status.getStateName(), Status.getTargetCtrlTick(),
1106 fStatusReached ? " reached" : "", fStatusAck ? " ack" : "");
1107
1108 // available protocols
1109 C4NetIO *pMsgIO = NetIO.MsgIO(), *pDataIO = NetIO.DataIO();
1110 if (pMsgIO && pDataIO)
1111 {
1112 C4Network2IOProtocol eMsgProt = NetIO.getNetIOProt(pMsgIO),
1113 eDataProt = NetIO.getNetIOProt(pDataIO);
1114 int32_t iMsgPort = 0, iDataPort = 0;
1115 switch (eMsgProt)
1116 {
1117 case P_TCP: iMsgPort = Config.Network.PortTCP; break;
1118 case P_UDP: iMsgPort = Config.Network.PortUDP; break;
1119 case P_NONE: assert(eMsgProt != P_NONE); break;
1120 }
1121 switch (eDataProt)
1122 {
1123 case P_TCP: iDataPort = Config.Network.PortTCP; break;
1124 case P_UDP: iDataPort = Config.Network.PortUDP; break;
1125 case P_NONE: assert(eMsgProt != P_NONE); break;
1126 }
1127 Stat.AppendFormat( "|Protocols: %s: %s (%d i%d o%d bc%d)",
1128 pMsgIO != pDataIO ? "Msg" : "Msg/Data",
1129 NetIO.getNetIOName(pMsgIO), iMsgPort,
1130 NetIO.getProtIRate(eMsgProt), NetIO.getProtORate(eMsgProt), NetIO.getProtBCRate(eMsgProt));
1131 if (pMsgIO != pDataIO)
1132 Stat.AppendFormat( ", Data: %s (%d i%d o%d bc%d)",
1133 NetIO.getNetIOName(pDataIO), iDataPort,
1134 NetIO.getProtIRate(eDataProt), NetIO.getProtORate(eDataProt), NetIO.getProtBCRate(eDataProt));
1135 }
1136 else
1137 Stat.Append("|Protocols: none");
1138
1139 // some control statistics
1140 Stat.AppendFormat( "|Control: %s, Tick %d, Behind %d, Rate %d, PreSend %d, ACT: %d",
1141 Status.getCtrlMode() == CNM_Decentral ? "Decentral" : Status.getCtrlMode() == CNM_Central ? "Central" : "Async",
1142 ::Control.ControlTick, pControl->GetBehind(::Control.ControlTick),
1143 ::Control.ControlRate, pControl->getControlPreSend(), pControl->getAvgControlSendTime());
1144
1145 // Streaming statistics
1146 if (fStreaming)
1147 Stat.AppendFormat( "|Streaming: %lu waiting, %u in, %lu out, %lu sent",
1148 static_cast<unsigned long>(pStreamedRecord ? pStreamedRecord->GetStreamingBuf().getSize() : 0),
1149 pStreamedRecord ? pStreamedRecord->GetStreamingPos() : 0,
1150 static_cast<unsigned long>(getPendingStreamData()),
1151 static_cast<unsigned long>(iCurrentStreamPosition));
1152
1153 // clients
1154 Stat.Append("|Clients:");
1155 for (C4Network2Client *pClient = Clients.GetNextClient(nullptr); pClient; pClient = Clients.GetNextClient(pClient))
1156 {
1157 // ignore local
1158 if (pClient->isLocal()) continue;
1159 // client status
1160 const C4ClientCore &Core = pClient->getCore();
1161 const char *szClientStatus;
1162 switch (pClient->getStatus())
1163 {
1164 case NCS_Joining: szClientStatus = " (joining)"; break;
1165 case NCS_Chasing: szClientStatus = " (chasing)"; break;
1166 case NCS_NotReady: szClientStatus = " (!rdy)"; break;
1167 case NCS_Remove: szClientStatus = " (removed)"; break;
1168 default: szClientStatus = ""; break;
1169 }
1170 Stat.AppendFormat( "|- %s %s %s (ID %d) (wait %d ms, behind %d)%s%s",
1171 Core.isObserver() ? "Observing" : Core.isActivated() ? "Active" : "Inactive", Core.isHost() ? "host" : "client",
1172 Core.getName(), Core.getID(),
1173 pControl->ClientPerfStat(pClient->getID()),
1174 ::Control.ControlTick - pControl->ClientNextControl(pClient->getID()),
1175 szClientStatus,
1176 pClient->isActivated() && !pControl->ClientReady(pClient->getID(), ::Control.ControlTick) ? " (!ctrl)" : "");
1177 // connections
1178 if (pClient->isConnected())
1179 {
1180 Stat.AppendFormat( "| Connections: %s: %s (%s p%d l%d)",
1181 pClient->getMsgConn() == pClient->getDataConn() ? "Msg/Data" : "Msg",
1182 NetIO.getNetIOName(pClient->getMsgConn()->getNetClass()),
1183 pClient->getMsgConn()->getPeerAddr().ToString().getData(),
1184 pClient->getMsgConn()->getPingTime(),
1185 pClient->getMsgConn()->getPacketLoss());
1186 if (pClient->getMsgConn() != pClient->getDataConn())
1187 Stat.AppendFormat( ", Data: %s (%s:%d p%d l%d)",
1188 NetIO.getNetIOName(pClient->getDataConn()->getNetClass()),
1189 pClient->getDataConn()->getPeerAddr().ToString().getData(),
1190 pClient->getDataConn()->getPingTime(),
1191 pClient->getDataConn()->getPacketLoss());
1192 }
1193 else
1194 Stat.Append("| Not connected");
1195 }
1196 if (!Clients.GetNextClient(nullptr))
1197 Stat.Append("| - none -");
1198
1199 // draw
1200 pDraw->TextOut(Stat.getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,cgo.X + 20,cgo.Y + 50);
1201 }
1202
InitNetIO(bool fNoClientID,bool fHost)1203 bool C4Network2::InitNetIO(bool fNoClientID, bool fHost)
1204 {
1205 // clear
1206 NetIO.Clear();
1207 Config.Network.CheckPortsForCollisions();
1208 // discovery: disable for client
1209 int16_t iPortDiscovery = fHost ? Config.Network.PortDiscovery : -1;
1210 int16_t iPortRefServer = fHost ? Config.Network.PortRefServer : -1;
1211 // init subclass
1212 if (!NetIO.Init(Config.Network.PortTCP, Config.Network.PortUDP, iPortDiscovery, iPortRefServer, fHost, !!Config.Network.EnableUPnP))
1213 return false;
1214 // set core (unset ID if sepecified, has to be set later)
1215 C4ClientCore Core = Game.Clients.getLocalCore();
1216 if (fNoClientID) Core.SetID(C4ClientIDUnknown);
1217 NetIO.SetLocalCCore(Core);
1218 // safe addresses of local client
1219 Clients.GetLocal()->AddLocalAddrs(
1220 NetIO.hasTCP() ? Config.Network.PortTCP : -1,
1221 NetIO.hasUDP() ? Config.Network.PortUDP : -1);
1222 // ok
1223 return true;
1224 }
1225
HandleConn(const C4PacketConn & Pkt,C4Network2IOConnection * pConn,C4Network2Client * pClient)1226 void C4Network2::HandleConn(const C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient)
1227 {
1228 // security
1229 if (!pConn) return;
1230
1231 // Handles a connect request (packet PID_Conn).
1232 // Check if this peer should be allowed to connect, make space for the new connection.
1233
1234 // connection is closed?
1235 if (pConn->isClosed())
1236 return;
1237
1238 // set up core
1239 const C4ClientCore &CCore = Pkt.getCCore();
1240 C4ClientCore NewCCore = CCore;
1241
1242 // accept connection?
1243 StdStrBuf reply;
1244 bool fOK = false;
1245
1246 // search client
1247 if (!pClient && Pkt.getCCore().getID() != C4ClientIDUnknown)
1248 pClient = Clients.GetClient(Pkt.getCCore());
1249
1250 // check engine version
1251 bool fWrongPassword = false;
1252 if (Pkt.getVer() != C4XVER1*100 + C4XVER2)
1253 {
1254 reply.Format("wrong engine (%d.%d, I have %d.%d)", Pkt.getVer()/100, Pkt.getVer()%100, C4XVER1, C4XVER2);
1255 fOK = false;
1256 }
1257 else
1258 {
1259 if (pClient)
1260 if (CheckConn(NewCCore, pConn, pClient, &reply))
1261 {
1262 // accept
1263 if (!reply) reply = "connection accepted";
1264 fOK = true;
1265 }
1266 // client: host connection?
1267 if (!fOK && !isHost() && Status.getState() == GS_Init && !Clients.GetHost())
1268 if (HostConnect(NewCCore, pConn, &reply))
1269 {
1270 // accept
1271 if (!reply) reply = "host connection accepted";
1272 fOK = true;
1273 }
1274 // host: client join? (NewCCore will be changed by Join()!)
1275 if (!fOK && isHost() && !pClient)
1276 {
1277 // check password
1278 if (!sPassword.isNull() && !SEqual(Pkt.getPassword(), sPassword.getData()))
1279 {
1280 reply = "wrong password";
1281 fWrongPassword = true;
1282 }
1283 // accept join
1284 else if (Join(NewCCore, pConn, &reply))
1285 {
1286 // save core
1287 pConn->SetCCore(NewCCore);
1288 // accept
1289 if (!reply) reply = "join accepted";
1290 fOK = true;
1291 }
1292 }
1293 }
1294
1295 // denied? set default reason
1296 if (!fOK && !reply) reply = "connection denied";
1297
1298 // OK and already half accepted? Skip (double-checked: ok).
1299 if (fOK && pConn->isHalfAccepted())
1300 return;
1301
1302 // send answer
1303 C4PacketConnRe pcr(fOK, fWrongPassword, reply.getData());
1304 if (!pConn->Send(MkC4NetIOPacket(PID_ConnRe, pcr)))
1305 return;
1306
1307 // accepted?
1308 if (fOK)
1309 {
1310 // set status
1311 if (!pConn->isClosed())
1312 pConn->SetHalfAccepted();
1313 }
1314 // denied? close
1315 else
1316 {
1317 // log & close
1318 LogSilentF("Network: connection by %s (%s) blocked: %s", CCore.getName(), pConn->getPeerAddr().ToString().getData(), reply.getData());
1319 pConn->Close();
1320 }
1321 }
1322
CheckConn(const C4ClientCore & CCore,C4Network2IOConnection * pConn,C4Network2Client * pClient,StdStrBuf * szReply)1323 bool C4Network2::CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pConn, C4Network2Client *pClient, StdStrBuf * szReply)
1324 {
1325 if (!pConn || !pClient) return false;
1326 // already connected? (shouldn't happen really)
1327 if (pClient->hasConn(pConn))
1328 { *szReply = "already connected"; return true; }
1329 // check core
1330 if (CCore.getDiffLevel(pClient->getCore()) > C4ClientCoreDL_IDMatch)
1331 { *szReply = "wrong client core"; return false; }
1332 // accept
1333 return true;
1334 }
1335
HostConnect(const C4ClientCore & CCore,C4Network2IOConnection * pConn,StdStrBuf * szReply)1336 bool C4Network2::HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply)
1337 {
1338 if (!pConn) return false;
1339 if (!CCore.isHost()) { *szReply = "not host"; return false; }
1340 // create client class for host
1341 // (core is unofficial, see InitClient() - will be overwritten later in HandleJoinData)
1342 C4Client *pClient = Game.Clients.Add(CCore);
1343 if (!pClient) return false;
1344 // accept
1345 return true;
1346 }
1347
Join(C4ClientCore & CCore,C4Network2IOConnection * pConn,StdStrBuf * szReply)1348 bool C4Network2::Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply)
1349 {
1350 if (!pConn) return false;
1351 // security
1352 if (!isHost()) { *szReply = "not host"; return false; }
1353 if (!fAllowJoin && !fAllowObserve) { *szReply = "join denied"; return false; }
1354 if (CCore.getID() != C4ClientIDUnknown) { *szReply = "join with set id not allowed"; return false; }
1355 // find free client id
1356 CCore.SetID(iNextClientID++);
1357 // observer?
1358 if (!fAllowJoin) CCore.SetObserver(true);
1359 // deactivate - client will have to ask for activation.
1360 CCore.SetActivated(false);
1361 // Name already in use? Find unused one
1362 if (Clients.GetClient(CCore.getName()))
1363 {
1364 char szNameTmpl[256+1], szNewName[256+1];
1365 SCopy(CCore.getName(), szNameTmpl, 254); SAppend("%d", szNameTmpl, 256);
1366 int32_t i = 1;
1367 do
1368 sprintf(szNewName, szNameTmpl, ++i);
1369 while (Clients.GetClient(szNewName));
1370 CCore.SetName(szNewName);
1371 }
1372 // join client
1373 ::Control.DoInput(CID_ClientJoin, new C4ControlClientJoin(CCore), CDT_Direct);
1374 // get client, set status
1375 C4Network2Client *pClient = Clients.GetClient(CCore);
1376 if (pClient) pClient->SetStatus(NCS_Joining);
1377 // warn if client revision doesn't match our host revision
1378 if (!SEqualNoCase(CCore.getRevision(), Application.GetRevision()))
1379 {
1380 LogF("[!]WARNING! Client %s engine revision (%s) differs from local revision (%s). Client might run out of sync.", CCore.getName(), CCore.getRevision(), Application.GetRevision());
1381 }
1382 // ok, client joined.
1383 return true;
1384 // Note that the connection isn't fully accepted at this point and won't be
1385 // associated with the client. The new-created client is waiting for connect.
1386 // Somewhat ironically, the connection may still timeout (resulting in an instant
1387 // removal and maybe some funny message sequences).
1388 // The final client initialization will be done at OnClientConnect.
1389 }
1390
HandleConnRe(const C4PacketConnRe & Pkt,C4Network2IOConnection * pConn,C4Network2Client * pClient)1391 void C4Network2::HandleConnRe(const C4PacketConnRe &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient)
1392 {
1393 // Handle the connection request reply. After this handling, the connection should
1394 // be either fully associated with a client (fully accepted) or closed.
1395 // Note that auto-accepted connection have to processed here once, too, as the
1396 // client must get associated with the connection. After doing so, the connection
1397 // auto-accept flag will be reset to mark the connection fully accepted.
1398
1399 // security
1400 if (!pConn) return;
1401 if (!pClient) { pConn->Close(); return; }
1402
1403 // negative reply?
1404 if (!Pkt.isOK())
1405 {
1406 // wrong password?
1407 fWrongPassword = Pkt.isPasswordWrong();
1408 // show message
1409 LogSilentF("Network: connection to %s (%s) refused: %s", pClient->getName(), pConn->getPeerAddr().ToString().getData(), Pkt.getMsg());
1410 // close connection
1411 pConn->Close();
1412 return;
1413 }
1414
1415 // connection is closed?
1416 if (!pConn->isOpen())
1417 return;
1418
1419 // already accepted? ignore
1420 if (pConn->isAccepted() && !pConn->isAutoAccepted()) return;
1421
1422 // first connection?
1423 bool fFirstConnection = !pClient->isConnected();
1424
1425 // accept connection
1426 pConn->SetAccepted(); pConn->ResetAutoAccepted();
1427
1428 // add connection
1429 pConn->SetCCore(pClient->getCore());
1430 if (pConn->getNetClass() == NetIO.MsgIO()) pClient->SetMsgConn(pConn);
1431 if (pConn->getNetClass() == NetIO.DataIO()) pClient->SetDataConn(pConn);
1432
1433 // add peer connect address to client address list
1434 if (!pConn->getConnectAddr().IsNull())
1435 {
1436 C4Network2Address Addr(pConn->getConnectAddr(), pConn->getProtocol());
1437 pClient->AddAddr(Addr, Status.getState() != GS_Init);
1438 }
1439
1440 // handle
1441 OnConnect(pClient, pConn, Pkt.getMsg(), fFirstConnection);
1442 }
1443
HandleStatus(const C4Network2Status & nStatus)1444 void C4Network2::HandleStatus(const C4Network2Status &nStatus)
1445 {
1446 // set
1447 Status = nStatus;
1448 // log
1449 LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), nStatus.getTargetCtrlTick());
1450 // reset flags
1451 fStatusReached = fStatusAck = false;
1452 // check: reached?
1453 CheckStatusReached();
1454 }
1455
HandleStatusAck(const C4Network2Status & nStatus,C4Network2Client * pClient)1456 void C4Network2::HandleStatusAck(const C4Network2Status &nStatus, C4Network2Client *pClient)
1457 {
1458 // security
1459 if (!pClient->hasJoinData() || pClient->isRemoved()) return;
1460 // status doesn't match?
1461 if (nStatus.getState() != Status.getState() || nStatus.getTargetCtrlTick() < Status.getTargetCtrlTick())
1462 return;
1463 // host: wait until all clients are ready
1464 if (isHost())
1465 {
1466 // check: target tick change?
1467 if (!fStatusAck && nStatus.getTargetCtrlTick() > Status.getTargetCtrlTick())
1468 // take the new status
1469 ChangeGameStatus(nStatus.getState(), nStatus.getTargetCtrlTick());
1470 // already acknowledged? Send another ack
1471 if (fStatusAck)
1472 pClient->SendMsg(MkC4NetIOPacket(PID_StatusAck, nStatus));
1473 // mark as ready (will clear chase-flag)
1474 pClient->SetStatus(NCS_Ready);
1475 // check: everyone ready?
1476 if (!fStatusAck && fStatusReached)
1477 CheckStatusAck();
1478 }
1479 else
1480 {
1481 // target tick doesn't match? ignore
1482 if (nStatus.getTargetCtrlTick() != Status.getTargetCtrlTick())
1483 return;
1484 // reached?
1485 // can be ignored safely otherwise - when the status is reached, we will send
1486 // status ack on which the host should generate another status ack (see above)
1487 if (fStatusReached)
1488 {
1489 // client: set flags, call handler
1490 fStatusAck = true; fChasing = false;
1491 OnStatusAck();
1492 }
1493
1494 }
1495 }
1496
HandleActivateReq(int32_t iTick,C4Network2Client * pByClient)1497 void C4Network2::HandleActivateReq(int32_t iTick, C4Network2Client *pByClient)
1498 {
1499 if (!isHost()) return;
1500 // not allowed or already activated? ignore
1501 if (pByClient->isObserver() || pByClient->isActivated()) return;
1502 // not joined completely yet? ignore
1503 if (!pByClient->isWaitedFor()) return;
1504 // check behind limit
1505 if (isRunning())
1506 {
1507 // make a guess how much the client lags.
1508 int32_t iLagFrames = Clamp(pByClient->getMsgConn()->getPingTime() * Game.FPS / 500, 0, 100);
1509 if (iTick < Game.FrameCounter - iLagFrames - C4NetMaxBehind4Activation)
1510 return;
1511 }
1512 // activate him
1513 ::Control.DoInput(CID_ClientUpdate,
1514 new C4ControlClientUpdate(pByClient->getID(), CUT_Activate, true),
1515 CDT_Sync);
1516 }
1517
HandleJoinData(const C4PacketJoinData & rPkt)1518 void C4Network2::HandleJoinData(const C4PacketJoinData &rPkt)
1519 {
1520 // init only
1521 if (Status.getState() != GS_Init)
1522 { LogSilentF("Network: unexpected join data received!"); return; }
1523 // get client ID
1524 if (rPkt.getClientID() == C4ClientIDUnknown)
1525 { LogSilentF("Network: host didn't set client ID!"); Clear(); return; }
1526 // set local ID
1527 ResList.SetLocalID(rPkt.getClientID());
1528 Game.Parameters.Clients.SetLocalID(rPkt.getClientID());
1529 // read and validate status
1530 HandleStatus(rPkt.getStatus());
1531 if (Status.getState() != GS_Lobby && Status.getState() != GS_Pause && Status.getState() != GS_Go)
1532 { LogSilentF("Network: join data has bad game status: %s", Status.getStateName()); Clear(); return; }
1533 // copy scenario parameter defs for lobby display
1534 ::Game.ScenarioParameterDefs = rPkt.ScenarioParameterDefs;
1535 // copy parameters
1536 ::Game.Parameters = rPkt.Parameters;
1537 // set local client
1538 C4Client *pLocalClient = Game.Clients.getClientByID(rPkt.getClientID());
1539 if (!pLocalClient)
1540 { LogSilentF("Network: Could not find local client in join data!"); Clear(); return; }
1541 // save back dynamic data
1542 ResDynamic = rPkt.getDynamicCore();
1543 iDynamicTick = rPkt.getStartCtrlTick();
1544 // initialize control
1545 ::Control.ControlRate = rPkt.Parameters.ControlRate;
1546 pControl->Init(rPkt.getClientID(), false, rPkt.getStartCtrlTick(), pLocalClient->isActivated(), this);
1547 pControl->CopyClientList(Game.Parameters.Clients);
1548 // set local core
1549 NetIO.SetLocalCCore(pLocalClient->getCore());
1550 // add the resources to the network resource list
1551 Game.Parameters.GameRes.InitNetwork(&ResList);
1552 // load dynamic
1553 if (!ResList.AddByCore(ResDynamic))
1554 { LogFatal("Network: can not not retrieve dynamic!"); Clear(); return; }
1555 // load player resources
1556 Game.Parameters.PlayerInfos.LoadResources();
1557 // send additional addresses
1558 Clients.SendAddresses(nullptr);
1559 }
1560
OnConnect(C4Network2Client * pClient,C4Network2IOConnection * pConn,const char * szMsg,bool fFirstConnection)1561 void C4Network2::OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection)
1562 {
1563 // log
1564 LogSilentF("Network: %s %s connected (%s/%s) (%s)", pClient->isHost() ? "host" : "client",
1565 pClient->getName(), pConn->getPeerAddr().ToString().getData(),
1566 NetIO.getNetIOName(pConn->getNetClass()), szMsg ? szMsg : "");
1567
1568 // first connection for this peer? call special handler
1569 if (fFirstConnection) OnClientConnect(pClient, pConn);
1570 }
1571
OnConnectFail(C4Network2IOConnection * pConn)1572 void C4Network2::OnConnectFail(C4Network2IOConnection *pConn)
1573 {
1574 LogSilentF("Network: %s connection to %s failed!", NetIO.getNetIOName(pConn->getNetClass()),
1575 pConn->getPeerAddr().ToString().getData());
1576
1577 // maybe client connection failure
1578 // (happens if the connection is not fully accepted and the client disconnects.
1579 // See C4Network2::Join)
1580 C4Network2Client *pClient = Clients.GetClientByID(pConn->getClientID());
1581 if (pClient && !pClient->isConnected())
1582 OnClientDisconnect(pClient);
1583 }
1584
OnDisconnect(C4Network2Client * pClient,C4Network2IOConnection * pConn)1585 void C4Network2::OnDisconnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
1586 {
1587 LogSilentF("Network: %s connection to %s (%s) lost!", NetIO.getNetIOName(pConn->getNetClass()),
1588 pClient->getName(), pConn->getPeerAddr().ToString().getData());
1589
1590 // connection lost?
1591 if (!pClient->isConnected())
1592 OnClientDisconnect(pClient);
1593 }
1594
OnClientConnect(C4Network2Client * pClient,C4Network2IOConnection * pConn)1595 void C4Network2::OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
1596 {
1597 // host: new client?
1598 if (isHost())
1599 {
1600 // dynamic available?
1601 if (!pClient->hasJoinData())
1602 SendJoinData(pClient);
1603
1604 // notice lobby (doesn't do anything atm?)
1605 C4GameLobby::MainDlg *pDlg = GetLobby();
1606 if (isLobbyActive()) pDlg->OnClientConnect(pClient->getClient(), pConn);
1607
1608 }
1609
1610 // discover resources
1611 ResList.OnClientConnect(pConn);
1612
1613 }
1614
OnClientDisconnect(C4Network2Client * pClient)1615 void C4Network2::OnClientDisconnect(C4Network2Client *pClient)
1616 {
1617 // league: Notify regular client disconnect within the game
1618 if (pLeagueClient && (isHost() || pClient->isHost())) LeagueNotifyDisconnect(pClient->getID(), C4LDR_ConnectionFailed);
1619 // host? Remove this client from the game.
1620 if (isHost())
1621 {
1622 // log
1623 LogSilentF(LoadResStr("IDS_NET_CLIENTDISCONNECTED"), pClient->getName()); // silent, because a duplicate message with disconnect reason will follow
1624 // remove the client
1625 Game.Clients.CtrlRemove(pClient->getClient(), LoadResStr("IDS_MSG_DISCONNECTED"));
1626 // check status ack (disconnected client might be the last that was waited for)
1627 CheckStatusAck();
1628 // unreached pause/go? retry setting the state with current control tick
1629 // (client might be the only one claiming to have the given control)
1630 if (!fStatusReached)
1631 if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
1632 ChangeGameStatus(Status.getState(), ::Control.ControlTick);
1633 #ifdef USE_CONSOLE
1634 // Dedicated server: stop hosting if there is only one client left we're hosting for.
1635 // TODO: Find a better place to do this.
1636 if (Game.IsRunning && Clients.Count() <= 3) Application.Quit(); // Off-by-1 error
1637 #endif // USE_CONSOLE
1638 }
1639 // host disconnected? Clear up
1640 if (!isHost() && pClient->isHost())
1641 {
1642 StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_NET_HOSTDISCONNECTED"), pClient->getName());
1643 Log(sMsg.getData());
1644 // host connection lost: clear up everything
1645 Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, sMsg.getData());
1646 Clear();
1647 }
1648 }
1649
SendJoinData(C4Network2Client * pClient)1650 void C4Network2::SendJoinData(C4Network2Client *pClient)
1651 {
1652 if (pClient->hasJoinData()) return;
1653 // host only, scenario must be available
1654 assert(isHost());
1655 // dynamic available?
1656 if (ResDynamic.isNull() || iDynamicTick < ::Control.ControlTick)
1657 {
1658 fDynamicNeeded = true;
1659 // add synchronization control (will callback, see C4Game::Synchronize)
1660 ::Control.DoInput(CID_Synchronize, new C4ControlSynchronize(false, true), CDT_Sync);
1661 return;
1662 }
1663 // save his client ID
1664 C4PacketJoinData JoinData;
1665 JoinData.SetClientID(pClient->getID());
1666 // save status into packet
1667 JoinData.SetGameStatus(Status);
1668 // scenario parameter defs for lobby display (localized in host language)
1669 JoinData.ScenarioParameterDefs = ::Game.ScenarioParameterDefs;
1670 // parameters
1671 JoinData.Parameters = Game.Parameters;
1672 // core join data
1673 JoinData.SetStartCtrlTick(iDynamicTick);
1674 JoinData.SetDynamicCore(ResDynamic);
1675 // send
1676 pClient->SendMsg(MkC4NetIOPacket(PID_JoinData, JoinData));
1677 // send addresses
1678 Clients.SendAddresses(pClient->getMsgConn());
1679 // flag client (he will have to accept the network status sent next)
1680 pClient->SetStatus(NCS_Chasing);
1681 if (!iLastChaseTargetUpdate) iLastChaseTargetUpdate = time(nullptr);
1682 }
1683
RetrieveRes(const C4Network2ResCore & Core,int32_t iTimeoutLen,const char * szResName,bool fWaitForCore)1684 C4Network2Res::Ref C4Network2::RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeoutLen, const char *szResName, bool fWaitForCore)
1685 {
1686 C4GUI::ProgressDialog *pDlg = nullptr;
1687 bool fLog = false;
1688 int32_t iProcess = -1;
1689 C4TimeMilliseconds tTimeout = C4TimeMilliseconds::Now() + iTimeoutLen;
1690 // wait for resource
1691 while (isEnabled())
1692 {
1693 // find resource
1694 C4Network2Res::Ref pRes = ResList.getRefRes(Core.getID());
1695 // res not found?
1696 if (!pRes)
1697 {
1698 if (Core.isNull())
1699 {
1700 // should wait for core?
1701 if (!fWaitForCore) return nullptr;
1702 }
1703 else
1704 {
1705 // start loading
1706 pRes = ResList.AddByCore(Core);
1707 }
1708 }
1709 // res found and loaded completely
1710 else if (!pRes->isLoading())
1711 {
1712 // log
1713 if (fLog) LogF(LoadResStr("IDS_NET_RECEIVED"), szResName, pRes->getCore().getFileName());
1714 // return
1715 if (pDlg) delete pDlg;
1716 return pRes;
1717 }
1718
1719 // check: progress?
1720 if (pRes && pRes->getPresentPercent() != iProcess)
1721 {
1722 iProcess = pRes->getPresentPercent();
1723 tTimeout = C4TimeMilliseconds::Now() + iTimeoutLen;
1724 }
1725 else
1726 {
1727 // if not: check timeout
1728 if (C4TimeMilliseconds::Now() > tTimeout)
1729 {
1730 LogFatal(FormatString(LoadResStr("IDS_NET_ERR_RESTIMEOUT"), szResName).getData());
1731 if (pDlg) delete pDlg;
1732 return nullptr;
1733 }
1734 }
1735
1736 // log
1737 if (!fLog)
1738 {
1739 LogF(LoadResStr("IDS_NET_WAITFORRES"), szResName);
1740 fLog = true;
1741 }
1742 // show progress dialog
1743 if (!pDlg && !Console.Active && ::pGUI)
1744 {
1745 // create
1746 pDlg = new C4GUI::ProgressDialog(FormatString(LoadResStr("IDS_NET_WAITFORRES"), szResName).getData(),
1747 LoadResStr("IDS_NET_CAPTION"), 100, 0, C4GUI::Ico_NetWait);
1748 // show dialog
1749 if (!pDlg->Show(::pGUI, true)) { delete pDlg; return nullptr; }
1750 }
1751
1752 // wait
1753 if (pDlg)
1754 {
1755 // set progress bar
1756 pDlg->SetProgress(iProcess);
1757 // execute (will do message handling)
1758 if (!pDlg->Execute())
1759 { if (pDlg) delete pDlg; return nullptr; }
1760 // aborted?
1761 if (pDlg->IsAborted()) break;
1762 }
1763 else
1764 {
1765 if (!Application.ScheduleProcs(tTimeout - C4TimeMilliseconds::Now()))
1766 { return nullptr; }
1767 }
1768
1769 }
1770 // aborted
1771 delete pDlg;
1772 return nullptr;
1773 }
1774
1775
CreateDynamic(bool fInit)1776 bool C4Network2::CreateDynamic(bool fInit)
1777 {
1778 if (!isHost()) return false;
1779 // remove all existing dynamic data
1780 RemoveDynamic();
1781 // log
1782 Log(LoadResStr("IDS_NET_SAVING"));
1783 // compose file name
1784 char szDynamicBase[_MAX_PATH+1], szDynamicFilename[_MAX_PATH+1];
1785 sprintf(szDynamicBase, Config.AtNetworkPath("Dyn%s"), GetFilename(Game.ScenarioFilename), _MAX_PATH);
1786 if (!ResList.FindTempResFileName(szDynamicBase, szDynamicFilename))
1787 Log(LoadResStr("IDS_NET_SAVE_ERR_CREATEDYNFILE"));
1788 // save dynamic data
1789 C4GameSaveNetwork SaveGame(fInit);
1790 if (!SaveGame.Save(szDynamicFilename) || !SaveGame.Close())
1791 { Log(LoadResStr("IDS_NET_SAVE_ERR_SAVEDYNFILE")); return false; }
1792 // add resource
1793 C4Network2Res::Ref pRes = ResList.AddByFile(szDynamicFilename, true, NRT_Dynamic);
1794 if (!pRes) { Log(LoadResStr("IDS_NET_SAVE_ERR_ADDDYNDATARES")); return false; }
1795 // save
1796 ResDynamic = pRes->getCore();
1797 iDynamicTick = ::Control.getNextControlTick();
1798 fDynamicNeeded = false;
1799 // ok
1800 return true;
1801 }
1802
RemoveDynamic()1803 void C4Network2::RemoveDynamic()
1804 {
1805 C4Network2Res::Ref pRes = ResList.getRefRes(ResDynamic.getID());
1806 if (pRes) pRes->Remove();
1807 ResDynamic.Clear();
1808 iDynamicTick = -1;
1809 }
1810
isFrozen() const1811 bool C4Network2::isFrozen() const
1812 {
1813 // "frozen" means all clients are garantueed to be in the same tick.
1814 // This is only the case if the game is not started yet (lobby) or the
1815 // tick has been ensured (pause) and acknowledged by all joined clients.
1816 // Note unjoined clients must be ignored here - they can't be faster than
1817 // the host, anyway.
1818 if (Status.getState() == GS_Lobby) return true;
1819 if (Status.getState() == GS_Pause && fStatusAck) return true;
1820 return false;
1821 }
1822
ChangeGameStatus(C4NetGameState enState,int32_t iTargetCtrlTick,int32_t iCtrlMode)1823 bool C4Network2::ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode)
1824 {
1825 // change game status, announce. Can only be done by host.
1826 if (!isHost()) return false;
1827 // set status
1828 Status.Set(enState, iTargetCtrlTick);
1829 // update reference
1830 InvalidateReference();
1831 // control mode change?
1832 if (iCtrlMode >= 0) Status.SetCtrlMode(iCtrlMode);
1833 // log
1834 LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), iTargetCtrlTick);
1835 // set flags
1836 Clients.ResetReady();
1837 fStatusReached = fStatusAck = false;
1838 // send new status to all clients
1839 Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_Status, Status));
1840 // check reach/ack
1841 CheckStatusReached();
1842 // ok
1843 return true;
1844 }
1845
CheckStatusReached(bool fFromFinalInit)1846 void C4Network2::CheckStatusReached(bool fFromFinalInit)
1847 {
1848 // already reached?
1849 if (fStatusReached) return;
1850 if (Status.getState() == GS_Lobby)
1851 fStatusReached = fLobbyRunning;
1852 // game go / pause: control must be initialized and target tick reached
1853 else if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
1854 {
1855 if (Game.IsRunning || fFromFinalInit)
1856 {
1857 // Make sure we have reached the tick and the control queue is empty (except for chasing)
1858 if (::Control.CtrlTickReached(Status.getTargetCtrlTick()) &&
1859 (fChasing || !pControl->CtrlReady(::Control.ControlTick)))
1860 fStatusReached = true;
1861 else
1862 {
1863 // run ctrl so the tick can be reached
1864 pControl->SetRunning(true, Status.getTargetCtrlTick());
1865 Game.HaltCount = 0;
1866 Console.UpdateHaltCtrls(!! Game.HaltCount);
1867 }
1868 }
1869 }
1870 if (!fStatusReached) return;
1871 // call handler
1872 OnStatusReached();
1873 // host?
1874 if (isHost())
1875 // all clients ready?
1876 CheckStatusAck();
1877 else
1878 {
1879 Status.SetTargetTick(::Control.ControlTick);
1880 // send response to host
1881 Clients.SendMsgToHost(MkC4NetIOPacket(PID_StatusAck, Status));
1882 // do delayed activation request
1883 if (fDelayedActivateReq)
1884 {
1885 fDelayedActivateReq = false;
1886 RequestActivate();
1887 }
1888 }
1889 }
1890
CheckStatusAck()1891 void C4Network2::CheckStatusAck()
1892 {
1893 // host only
1894 if (!isHost()) return;
1895 // status must be reached and not yet acknowledged
1896 if (!fStatusReached || fStatusAck) return;
1897 // all clients ready?
1898 if ((fStatusAck = Clients.AllClientsReady()))
1899 {
1900 // pause/go: check for sync control that can be executed
1901 if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
1902 pControl->ExecSyncControl();
1903 // broadcast ack
1904 Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_StatusAck, Status));
1905 // handle
1906 OnStatusAck();
1907 }
1908 }
1909
OnStatusReached()1910 void C4Network2::OnStatusReached()
1911 {
1912 // stop ctrl, wait for ack
1913 if (pControl->IsEnabled())
1914 {
1915 Console.UpdateHaltCtrls(!!++Game.HaltCount);
1916 pControl->SetRunning(false);
1917 }
1918 }
1919
OnStatusAck()1920 void C4Network2::OnStatusAck()
1921 {
1922 // log it
1923 LogSilentF("Network: status %s (tick %d) reached", Status.getStateName(), Status.getTargetCtrlTick());
1924 // pause?
1925 if (Status.getState() == GS_Pause)
1926 {
1927 // set halt-flag (show hold message)
1928 Console.UpdateHaltCtrls(!!++Game.HaltCount);
1929 }
1930 // go?
1931 if (Status.getState() == GS_Go)
1932 {
1933 // set mode
1934 pControl->SetCtrlMode(static_cast<C4GameControlNetworkMode>(Status.getCtrlMode()));
1935 // notify player list of reached status - will add some input to the queue
1936 Players.OnStatusGoReached();
1937 // start ctrl
1938 pControl->SetRunning(true);
1939 // reset halt-flag
1940 Game.HaltCount = 0;
1941 Console.UpdateHaltCtrls(!!Game.HaltCount);
1942 }
1943 }
1944
RequestActivate()1945 void C4Network2::RequestActivate()
1946 {
1947 // neither observer nor activated?
1948 if (Game.Clients.getLocal()->isObserver() || Game.Clients.getLocal()->isActivated())
1949 {
1950 tLastActivateRequest = C4TimeMilliseconds::NegativeInfinity;
1951 return;
1952 }
1953 // host? just do it
1954 if (fHost)
1955 {
1956 // activate him
1957 ::Control.DoInput(CID_ClientUpdate,
1958 new C4ControlClientUpdate(C4ClientIDHost, CUT_Activate, true),
1959 CDT_Sync);
1960 return;
1961 }
1962 // ensure interval
1963 if(C4TimeMilliseconds::Now() < tLastActivateRequest + C4NetActivationReqInterval)
1964 return;
1965 // status not reached yet? May be chasing, let's delay this.
1966 if (!fStatusReached)
1967 {
1968 fDelayedActivateReq = true;
1969 return;
1970 }
1971 // request
1972 Clients.SendMsgToHost(MkC4NetIOPacket(PID_ClientActReq, C4PacketActivateReq(Game.FrameCounter)));
1973 // store time
1974 tLastActivateRequest = C4TimeMilliseconds::Now();
1975 }
1976
DeactivateInactiveClients()1977 void C4Network2::DeactivateInactiveClients()
1978 {
1979 // host only and not in editor
1980 if (!isHost() || ::Application.isEditor) return;
1981 // update activity
1982 Clients.UpdateClientActivity();
1983 // find clients to deactivate
1984 for (C4Network2Client *pClient = Clients.GetNextClient(nullptr); pClient; pClient = Clients.GetNextClient(pClient))
1985 if (!pClient->isLocal() && pClient->isActivated())
1986 if (pClient->getLastActivity() + C4NetDeactivationDelay < Game.FrameCounter)
1987 ::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(pClient->getID(), CUT_Activate, false), CDT_Sync);
1988 }
1989
UpdateChaseTarget()1990 void C4Network2::UpdateChaseTarget()
1991 {
1992 // no chasing clients?
1993 C4Network2Client *pClient;
1994 for (pClient = Clients.GetNextClient(nullptr); pClient; pClient = Clients.GetNextClient(pClient))
1995 if (pClient->isChasing())
1996 break;
1997 if (!pClient)
1998 {
1999 iLastChaseTargetUpdate = 0;
2000 return;
2001 }
2002 // not time for an update?
2003 if (!iLastChaseTargetUpdate || long(iLastChaseTargetUpdate + C4NetChaseTargetUpdateInterval) > time(nullptr))
2004 return;
2005 // copy status, set current tick
2006 C4Network2Status ChaseTarget = Status;
2007 ChaseTarget.SetTargetTick(::Control.ControlTick);
2008 // send to everyone involved
2009 for (pClient = Clients.GetNextClient(nullptr); pClient; pClient = Clients.GetNextClient(pClient))
2010 if (pClient->isChasing())
2011 pClient->SendMsg(MkC4NetIOPacket(PID_Status, ChaseTarget));
2012 iLastChaseTargetUpdate = time(nullptr);
2013 }
2014
LeagueGameEvaluate(const char * szRecordName,const BYTE * pRecordSHA)2015 void C4Network2::LeagueGameEvaluate(const char *szRecordName, const BYTE *pRecordSHA)
2016 {
2017 // already off?
2018 if (!pLeagueClient) return;
2019 // already evaluated?
2020 if (fLeagueEndSent) return;
2021 // end
2022 LeagueEnd(szRecordName, pRecordSHA);
2023 }
2024
LeagueSignupDisable()2025 void C4Network2::LeagueSignupDisable()
2026 {
2027 // already off?
2028 if (!pLeagueClient) return;
2029 // no post-disable if league is active
2030 if (pLeagueClient && Game.Parameters.isLeague()) return;
2031 // signup end
2032 LeagueEnd(); DeinitLeague();
2033 }
2034
LeagueSignupEnable()2035 bool C4Network2::LeagueSignupEnable()
2036 {
2037 // already running?
2038 if (pLeagueClient) return true;
2039 // Start it!
2040 if (InitLeague(nullptr) && LeagueStart(nullptr)) return true;
2041 // Failure :'(
2042 DeinitLeague();
2043 return false;
2044 }
2045
InvalidateReference()2046 void C4Network2::InvalidateReference()
2047 {
2048 // Update both local and league reference as soon as possible
2049 iLastReferenceUpdate = 0;
2050 iLeagueUpdateDelay = C4NetMinLeagueUpdateInterval;
2051 }
2052
InitLeague(bool * pCancel)2053 bool C4Network2::InitLeague(bool *pCancel)
2054 {
2055
2056 if (fHost)
2057 {
2058
2059 // Clear parameters
2060 MasterServerAddress.Clear();
2061 Game.Parameters.League.Clear();
2062 Game.Parameters.LeagueAddress.Clear();
2063 if (pLeagueClient) delete pLeagueClient; pLeagueClient = nullptr;
2064
2065 // Not needed?
2066 if (!Config.Network.MasterServerSignUp && !Config.Network.LeagueServerSignUp)
2067 return true;
2068
2069 // Save address
2070 MasterServerAddress = Config.Network.GetLeagueServerAddress();
2071 if (Config.Network.LeagueServerSignUp)
2072 {
2073 Game.Parameters.LeagueAddress = MasterServerAddress;
2074 // enforce some league rules
2075 Game.Parameters.EnforceLeagueRules(&Game.C4S);
2076 }
2077
2078 }
2079 else
2080 {
2081
2082 // Get league server from parameters
2083 MasterServerAddress = Game.Parameters.LeagueAddress;
2084
2085 // Not needed?
2086 if (!MasterServerAddress.getLength())
2087 return true;
2088
2089 }
2090
2091 // Init
2092 pLeagueClient = new C4LeagueClient();
2093 if (!pLeagueClient->Init() ||
2094 !pLeagueClient->SetServer(MasterServerAddress.getData()))
2095 {
2096 // Log message
2097 StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUEINIT"), pLeagueClient->GetError());
2098 LogFatal(Message.getData());
2099 // Clear league
2100 delete pLeagueClient; pLeagueClient = nullptr;
2101 if (fHost)
2102 Game.Parameters.LeagueAddress.Clear();
2103 // Show message, allow abort
2104 bool fResult = true;
2105 if (!Application.isEditor)
2106 fResult = ::pGUI->ShowMessageModal(Message.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2107 (pCancel ? C4GUI::MessageDialog::btnOK : 0) | C4GUI::MessageDialog::btnAbort,
2108 C4GUI::Ico_Error);
2109 if (pCancel) *pCancel = fResult;
2110 return false;
2111 }
2112
2113 // Add to message loop
2114 Application.Add(pLeagueClient);
2115
2116 // OK
2117 return true;
2118 }
2119
DeinitLeague()2120 void C4Network2::DeinitLeague()
2121 {
2122 // league clear
2123 MasterServerAddress.Clear();
2124 Game.Parameters.League.Clear();
2125 Game.Parameters.LeagueAddress.Clear();
2126 if (pLeagueClient)
2127 {
2128 Application.Remove(pLeagueClient);
2129 delete pLeagueClient; pLeagueClient = nullptr;
2130 }
2131 }
2132
LeagueStart(bool * pCancel)2133 bool C4Network2::LeagueStart(bool *pCancel)
2134 {
2135 // Not needed?
2136 if (!pLeagueClient || !fHost)
2137 return true;
2138
2139 // Default
2140 if (pCancel) *pCancel = true;
2141
2142 // Do update
2143 C4Network2Reference Ref;
2144 Ref.InitLocal();
2145 if (!pLeagueClient->Start(Ref))
2146 {
2147 // Log message
2148 StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_STARTGAME"), pLeagueClient->GetError());
2149 LogFatal(Message.getData());
2150 // Show message
2151 if (!Application.isEditor)
2152 {
2153 // Show option to cancel, if possible
2154 bool fResult = ::pGUI->ShowMessageModal(
2155 Message.getData(),
2156 LoadResStr("IDS_NET_ERR_LEAGUE"),
2157 pCancel ? (C4GUI::MessageDialog::btnOK | C4GUI::MessageDialog::btnAbort) : C4GUI::MessageDialog::btnOK,
2158 C4GUI::Ico_Error);
2159 if (pCancel)
2160 *pCancel = !fResult;
2161 }
2162 // Failed
2163 return false;
2164 }
2165
2166 InitPuncher();
2167
2168 // Let's wait for response
2169 StdStrBuf Message = FormatString(LoadResStr("IDS_NET_LEAGUE_REGGAME"), pLeagueClient->getServerName());
2170 Log(Message.getData());
2171 // Set up a dialog
2172 C4GUI::MessageDialog *pDlg = nullptr;
2173 if (!Application.isEditor)
2174 {
2175 // create & show
2176 pDlg = new C4GUI::MessageDialog(Message.getData(), LoadResStr("IDS_NET_LEAGUE_STARTGAME"),
2177 C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsRegular);
2178 if (!pDlg || !pDlg->Show(::pGUI, true)) return false;
2179 }
2180 // Wait for response
2181 while (pLeagueClient->isBusy())
2182 {
2183 // Execute GUI
2184 if (!Application.ScheduleProcs() ||
2185 (pDlg && pDlg->IsAborted()))
2186 {
2187 // Clear up
2188 if (pDlg) delete pDlg;
2189 return false;
2190 }
2191 // Check if league server has responded
2192 if (!pLeagueClient->Execute(100))
2193 break;
2194 }
2195 // Close dialog
2196 if (pDlg)
2197 {
2198 pDlg->Close(true);
2199 delete pDlg;
2200 }
2201 // Error?
2202 StdStrBuf LeagueServerMessage, League, StreamingAddr;
2203 int32_t Seed = Game.RandomSeed, MaxPlayersLeague = 0;
2204 if (!pLeagueClient->isSuccess() ||
2205 !pLeagueClient->GetStartReply(&LeagueServerMessage, &League, &StreamingAddr, &Seed, &MaxPlayersLeague))
2206 {
2207 const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
2208 LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2209 LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
2210 StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_REGGAME"), pError);
2211 // Log message
2212 Log(Message.getData());
2213 // Show message
2214 if (!Application.isEditor)
2215 {
2216 // Show option to cancel, if possible
2217 bool fResult = ::pGUI->ShowMessageModal(
2218 Message.getData(),
2219 LoadResStr("IDS_NET_ERR_LEAGUE"),
2220 pCancel ? (C4GUI::MessageDialog::btnOK | C4GUI::MessageDialog::btnAbort) : C4GUI::MessageDialog::btnOK,
2221 C4GUI::Ico_Error);
2222 if (pCancel)
2223 *pCancel = !fResult;
2224 }
2225 // Failed
2226 return false;
2227 }
2228
2229 // Show message
2230 if (LeagueServerMessage.getLength())
2231 {
2232 StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_LEAGUEGAMESIGNUP"), pLeagueClient->getServerName(), LeagueServerMessage.getData());
2233 // Log message
2234 Log(Message.getData());
2235 // Show message
2236 if (!Application.isEditor)
2237 {
2238 // Show option to cancel, if possible
2239 bool fResult = ::pGUI->ShowMessageModal(
2240 Message.getData(),
2241 LoadResStr("IDS_NET_ERR_LEAGUE"),
2242 pCancel ? (C4GUI::MessageDialog::btnOK | C4GUI::MessageDialog::btnAbort) : C4GUI::MessageDialog::btnOK,
2243 C4GUI::Ico_Error);
2244 if (pCancel)
2245 *pCancel = !fResult;
2246 if (!fResult)
2247 {
2248 LeagueEnd(); DeinitLeague();
2249 return false;
2250 }
2251 }
2252 }
2253
2254 // Set game parameters for league game
2255 Game.Parameters.League = League;
2256 Game.RandomSeed = Seed;
2257 if (MaxPlayersLeague)
2258 Game.Parameters.MaxPlayers = MaxPlayersLeague;
2259 if (!League.getLength())
2260 {
2261 Game.Parameters.LeagueAddress.Clear();
2262 Game.Parameters.StreamAddress.Clear();
2263 }
2264 else
2265 {
2266 Game.Parameters.StreamAddress = StreamingAddr;
2267 }
2268
2269 // All ok
2270 fLeagueEndSent = false;
2271 return true;
2272 }
2273
LeagueUpdate()2274 bool C4Network2::LeagueUpdate()
2275 {
2276 // Not needed?
2277 if (!pLeagueClient || !fHost)
2278 return true;
2279
2280 // League client currently busy?
2281 if (pLeagueClient->isBusy())
2282 return true;
2283
2284 // Create reference
2285 C4Network2Reference Ref;
2286 Ref.InitLocal();
2287
2288 // Do update
2289 if (!pLeagueClient->Update(Ref))
2290 {
2291 // Log
2292 LogF(LoadResStr("IDS_NET_ERR_LEAGUE_UPDATEGAME"), pLeagueClient->GetError());
2293 return false;
2294 }
2295
2296 // Timing
2297 iLastLeagueUpdate = time(nullptr);
2298 iLeagueUpdateDelay = Config.Network.MasterReferencePeriod;
2299
2300 return true;
2301 }
2302
LeagueUpdateProcessReply()2303 bool C4Network2::LeagueUpdateProcessReply()
2304 {
2305 // safety: A reply must be present
2306 assert(pLeagueClient);
2307 assert(fHost);
2308 assert(!pLeagueClient->isBusy());
2309 assert(pLeagueClient->getCurrentAction() == C4LA_Update);
2310 // check reply success
2311 C4ClientPlayerInfos PlayerLeagueInfos;
2312 StdStrBuf LeagueServerMessage;
2313 bool fSucc = pLeagueClient->isSuccess() && pLeagueClient->GetUpdateReply(&LeagueServerMessage, &PlayerLeagueInfos);
2314 pLeagueClient->ResetCurrentAction();
2315 if (!fSucc)
2316 {
2317 const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
2318 LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2319 LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
2320 StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_UPDATEGAME"), pError);
2321 // Show message - no dialog, because it's not really fatal and might happen in the running game
2322 Log(Message.getData());
2323 return false;
2324 }
2325 // evaluate reply: Transfer data to players
2326 // Take round results
2327 C4PlayerInfoList &TargetList = Game.PlayerInfos;
2328 C4ClientPlayerInfos *pInfos; C4PlayerInfo *pInfo, *pResultInfo;
2329 for (int iClient = 0; (pInfos = TargetList.GetIndexedInfo(iClient)); iClient++)
2330 for (int iInfo = 0; (pInfo = pInfos->GetPlayerInfo(iInfo)); iInfo++)
2331 if ((pResultInfo = PlayerLeagueInfos.GetPlayerInfoByID(pInfo->GetID())))
2332 {
2333 int32_t iLeagueProjectedGain = pResultInfo->GetLeagueProjectedGain();
2334 if (iLeagueProjectedGain != pInfo->GetLeagueProjectedGain())
2335 {
2336 pInfo->SetLeagueProjectedGain(iLeagueProjectedGain);
2337 pInfos->SetUpdated();
2338 }
2339 }
2340 // transfer info update to other clients
2341 Players.SendUpdatedPlayers();
2342 // if lobby is open, notify lobby of updated players
2343 if (pLobby) pLobby->OnPlayersChange();
2344 // OMFG SUCCESS!
2345 return true;
2346 }
2347
LeagueEnd(const char * szRecordName,const BYTE * pRecordSHA)2348 bool C4Network2::LeagueEnd(const char *szRecordName, const BYTE *pRecordSHA)
2349 {
2350 C4RoundResultsPlayers RoundResults;
2351 StdStrBuf sResultMessage;
2352 bool fIsError = true;
2353
2354 // Not needed?
2355 if (!pLeagueClient || !fHost || fLeagueEndSent)
2356 return true;
2357
2358 // Make sure league client is available
2359 LeagueWaitNotBusy();
2360
2361 // Try until either aborted or successful
2362 const int MAX_RETRIES = 10;
2363 for (int iRetry = 0; iRetry < MAX_RETRIES; iRetry++)
2364 {
2365
2366 // Do update
2367 C4Network2Reference Ref;
2368 Ref.InitLocal();
2369 if (!pLeagueClient->End(Ref, szRecordName, pRecordSHA))
2370 {
2371 // Log message
2372 sResultMessage = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_FINISHGAME"), pLeagueClient->GetError());
2373 Log(sResultMessage.getData());
2374 // Show message, allow retry
2375 if (Application.isEditor) break;
2376 bool fRetry = ::pGUI->ShowMessageModal(sResultMessage.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2377 C4GUI::MessageDialog::btnRetryAbort, C4GUI::Ico_Error);
2378 if (fRetry) continue;
2379 break;
2380 }
2381 // Let's wait for response
2382 StdStrBuf Message = FormatString(LoadResStr("IDS_NET_LEAGUE_SENDRESULT"), pLeagueClient->getServerName());
2383 Log(Message.getData());
2384 // Wait for response
2385 while (pLeagueClient->isBusy())
2386 {
2387 // Check if league server has responded
2388 if (!pLeagueClient->Execute(100))
2389 break;
2390 }
2391 // Error?
2392 StdStrBuf LeagueServerMessage;
2393 if (!pLeagueClient->isSuccess() || !pLeagueClient->GetEndReply(&LeagueServerMessage, &RoundResults))
2394 {
2395 const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
2396 LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2397 LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
2398 sResultMessage.Take(FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_SENDRESULT"), pError));
2399 if (Application.isEditor) continue;
2400 // Only retry if we didn't get an answer from the league server
2401 bool fRetry = !pLeagueClient->isSuccess();
2402 fRetry = ::pGUI->ShowMessageModal(sResultMessage.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2403 fRetry ? C4GUI::MessageDialog::btnRetryAbort : C4GUI::MessageDialog::btnAbort,
2404 C4GUI::Ico_Error);
2405 if (fRetry) continue;
2406 }
2407 else
2408 {
2409 // All OK!
2410 sResultMessage.Copy(LoadResStr(Game.Parameters.isLeague() ? "IDS_MSG_LEAGUEEVALUATIONSUCCESSFU" : "IDS_MSG_INTERNETGAMEEVALUATED"));
2411 fIsError = false;
2412 }
2413 // Done
2414 break;
2415 }
2416
2417 // Show message
2418 Log(sResultMessage.getData());
2419
2420 // Take round results
2421 Game.RoundResults.EvaluateLeague(sResultMessage.getData(), !fIsError, RoundResults);
2422
2423 // Send round results to other clients
2424 C4PacketLeagueRoundResults LeagueUpdatePacket(sResultMessage.getData(), !fIsError, RoundResults);
2425 Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_LeagueRoundResults, LeagueUpdatePacket));
2426
2427 // All done
2428 fLeagueEndSent = true;
2429 return true;
2430 }
2431
LeaguePlrAuth(C4PlayerInfo * pInfo)2432 bool C4Network2::LeaguePlrAuth(C4PlayerInfo *pInfo)
2433 {
2434
2435 // Not possible?
2436 if (!pLeagueClient)
2437 return false;
2438
2439 // Make sure league client is avilable
2440 LeagueWaitNotBusy();
2441
2442 // Official league?
2443 bool fOfficialLeague = SEqual(pLeagueClient->getServerName(), "league.openclonk.org");
2444
2445 StdStrBuf Account, Password;
2446 bool fRememberLogin = false;
2447
2448 // Default password from login token if present
2449 if (Config.Network.GetLeagueLoginData(pLeagueClient->getServerName(), pInfo->GetName(), &Account, &Password))
2450 {
2451 fRememberLogin = (Password.getLength()>0);
2452 }
2453 else
2454 {
2455 Account.Copy(pInfo->GetName());
2456 }
2457
2458 for (;;)
2459 {
2460 // ask for account name and password
2461 if (!C4LeagueSignupDialog::ShowModal(pInfo->GetName(), Account.getData(), pLeagueClient->getServerName(), &Account, &Password, !fOfficialLeague, false, &fRememberLogin))
2462 return false;
2463
2464 // safety (modal dlg may have deleted network)
2465 if (!pLeagueClient) return false;
2466
2467 // Send authentication request
2468 if (!pLeagueClient->Auth(*pInfo, Account.getData(), Password.getData(), nullptr, nullptr, fRememberLogin))
2469 return false;
2470
2471 // safety (modal dlg may have deleted network)
2472 if (!pLeagueClient) return false;
2473
2474 // Wait for a response
2475 StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_TRYLEAGUESIGNUP"), pInfo->GetName(), Account.getData(), pLeagueClient->getServerName());
2476 Log(Message.getData());
2477 // Set up a dialog
2478 C4GUI::MessageDialog *pDlg = nullptr;
2479 if (!Application.isEditor)
2480 {
2481 // create & show
2482 pDlg = new C4GUI::MessageDialog(Message.getData(), LoadResStr("IDS_DLG_LEAGUESIGNUP"), C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsRegular);
2483 if (!pDlg || !pDlg->Show(::pGUI, true)) return false;
2484 }
2485 // Wait for response
2486 while (pLeagueClient->isBusy())
2487 {
2488 // Execute GUI
2489 if (!Application.ScheduleProcs() ||
2490 (pDlg && pDlg->IsAborted()))
2491 {
2492 // Clear up
2493 if (pDlg) delete pDlg;
2494 return false;
2495 }
2496 // Check if league server has responded
2497 if (!pLeagueClient->Execute(0))
2498 break;
2499 }
2500 // Close dialog
2501 if (pDlg)
2502 {
2503 pDlg->Close(true);
2504 delete pDlg;
2505 }
2506
2507 // Success?
2508 StdStrBuf AUID, AccountMaster, LoginToken; bool fUnregistered = false;
2509 if (pLeagueClient->GetAuthReply(&Message, &AUID, &AccountMaster, &fUnregistered, &LoginToken))
2510 {
2511
2512 // Set AUID
2513 pInfo->SetAuthID(AUID.getData());
2514
2515 // Remember login data; set or clear login token
2516 Config.Network.SetLeagueLoginData(pLeagueClient->getServerName(), pInfo->GetName(), Account.getData(), fRememberLogin ? LoginToken.getData() : "");
2517
2518 // Show welcome message, if any
2519 bool fSuccess;
2520 if (Message.getLength())
2521 fSuccess = ::pGUI->ShowMessageModal(
2522 Message.getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
2523 C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Ex_League);
2524 else if (AccountMaster.getLength())
2525 fSuccess = ::pGUI->ShowMessageModal(
2526 FormatString(LoadResStr("IDS_MSG_LEAGUEPLAYERSIGNUPAS"), pInfo->GetName(), AccountMaster.getData(), pLeagueClient->getServerName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
2527 C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Ex_League);
2528 else
2529 fSuccess = ::pGUI->ShowMessageModal(
2530 FormatString(LoadResStr("IDS_MSG_LEAGUEPLAYERSIGNUP"), pInfo->GetName(), pLeagueClient->getServerName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
2531 C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Ex_League);
2532
2533 // Approved?
2534 if (fSuccess)
2535 // Done
2536 return true;
2537 else
2538 // Sign-up was cancelled by user
2539 ::pGUI->ShowMessageModal(FormatString(LoadResStr("IDS_MSG_LEAGUESIGNUPCANCELLED"), pInfo->GetName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUP"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Notify);
2540
2541 }
2542 else
2543 {
2544
2545 // Authentification error
2546 LogF(LoadResStr("IDS_MSG_LEAGUESIGNUPERROR"), Message.getData());
2547 ::pGUI->ShowMessageModal(FormatString(LoadResStr("IDS_MSG_LEAGUESERVERMSG"), Message.getData()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPFAILED"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
2548 // after a league server error message, always fall-through to try again
2549 }
2550
2551 // Try given account name as default next time
2552 if (AccountMaster.getLength())
2553 Account.Take(std::move(AccountMaster));
2554
2555 // safety (modal dlg may have deleted network)
2556 if (!pLeagueClient) return false;
2557 }
2558
2559 }
2560
LeaguePlrAuthCheck(C4PlayerInfo * pInfo)2561 bool C4Network2::LeaguePlrAuthCheck(C4PlayerInfo *pInfo)
2562 {
2563
2564 // Not possible?
2565 if (!pLeagueClient)
2566 return false;
2567
2568 // Make sure league client is available
2569 LeagueWaitNotBusy();
2570
2571 // Ask league server to check the code
2572 if (!pLeagueClient->AuthCheck(*pInfo))
2573 return false;
2574
2575 // Log
2576 StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_LEAGUEJOINING"), pInfo->GetName());
2577 Log(Message.getData());
2578
2579 // Wait for response
2580 while (pLeagueClient->isBusy())
2581 if (!pLeagueClient->Execute(100))
2582 break;
2583
2584 // Check response validity
2585 if (!pLeagueClient->isSuccess())
2586 {
2587 LeagueShowError(pLeagueClient->GetError());
2588 return false;
2589 }
2590
2591 // Check if league server approves. pInfo will have league info if this call is successful.
2592 if (!pLeagueClient->GetAuthCheckReply(&Message, Game.Parameters.League.getData(), pInfo))
2593 {
2594 LeagueShowError(FormatString(LoadResStr("IDS_MSG_LEAGUEJOINREFUSED"), pInfo->GetName(), Message.getData()).getData());
2595 return false;
2596 }
2597
2598 return true;
2599 }
2600
LeagueNotifyDisconnect(int32_t iClientID,C4LeagueDisconnectReason eReason)2601 void C4Network2::LeagueNotifyDisconnect(int32_t iClientID, C4LeagueDisconnectReason eReason)
2602 {
2603 // league active?
2604 if (!pLeagueClient || !Game.Parameters.isLeague()) return;
2605 // only in running game
2606 if (!Game.IsRunning || Game.GameOver) return;
2607 // clients send notifications for their own players; host sends for the affected client players
2608 if (!isHost()) { if (!Clients.GetLocal()) return; iClientID = Clients.GetLocal()->getID(); }
2609 // clients only need notifications if they have players in the game
2610 const C4ClientPlayerInfos *pInfos = Game.PlayerInfos.GetInfoByClientID(iClientID);
2611 if (!pInfos) return;
2612 int32_t i=0; C4PlayerInfo *pInfo;
2613 while ((pInfo = pInfos->GetPlayerInfo(i++))) if (pInfo->IsJoined() && !pInfo->IsRemoved()) break;
2614 if (!pInfo) return;
2615 // Make sure league client is avilable
2616 LeagueWaitNotBusy();
2617 // report the disconnect!
2618 LogF(LoadResStr("IDS_LEAGUE_LEAGUEREPORTINGUNEXPECTED"), (int) eReason);
2619 pLeagueClient->ReportDisconnect(*pInfos, eReason);
2620 // wait for the reply
2621 LeagueWaitNotBusy();
2622 // display it
2623 const char *szMsg;
2624 StdStrBuf sMessage;
2625 if (pLeagueClient->GetReportDisconnectReply(&sMessage))
2626 szMsg = LoadResStr("IDS_MSG_LEAGUEUNEXPECTEDDISCONNEC");
2627 else
2628 szMsg = LoadResStr("IDS_ERR_LEAGUEERRORREPORTINGUNEXP");
2629 LogF(szMsg, sMessage.getData());
2630 }
2631
LeagueWaitNotBusy()2632 void C4Network2::LeagueWaitNotBusy()
2633 {
2634 // league client busy?
2635 if (!pLeagueClient || !pLeagueClient->isBusy()) return;
2636 // wait for it
2637 Log(LoadResStr("IDS_LEAGUE_WAITINGFORLASTLEAGUESERVE"));
2638 while (pLeagueClient->isBusy())
2639 if (!pLeagueClient->Execute(100))
2640 break;
2641 // if last request was an update request, process it
2642 if (pLeagueClient->getCurrentAction() == C4LA_Update)
2643 LeagueUpdateProcessReply();
2644 }
2645
LeagueSurrender()2646 void C4Network2::LeagueSurrender()
2647 {
2648 // there's currently no functionality to surrender in the league
2649 // just stop responding so other clients will notify the disconnect
2650 DeinitLeague();
2651 }
2652
LeagueShowError(const char * szMsg)2653 void C4Network2::LeagueShowError(const char *szMsg)
2654 {
2655 if (!Application.isEditor)
2656 {
2657 ::pGUI->ShowErrorMessage(szMsg);
2658 }
2659 else
2660 {
2661 LogF(LoadResStr("IDS_LGA_SERVERFAILURE"), szMsg);
2662 }
2663 }
2664
Vote(C4ControlVoteType eType,bool fApprove,int32_t iData)2665 void C4Network2::Vote(C4ControlVoteType eType, bool fApprove, int32_t iData)
2666 {
2667 // Original vote?
2668 if (!GetVote(C4ClientIDUnknown, eType, iData))
2669 {
2670 // Too fast?
2671 if (time(nullptr) < (time_t) (iLastOwnVoting + C4NetMinVotingInterval))
2672 {
2673 Log(LoadResStr("IDS_TEXT_YOUCANONLYSTARTONEVOTINGE"));
2674 if ((eType == VT_Kick && iData == Game.Clients.getLocalID()) || eType == VT_Cancel)
2675 OpenSurrenderDialog(eType, iData);
2676 return;
2677 }
2678 // Save timestamp
2679 iLastOwnVoting = time(nullptr);
2680 }
2681 // Already voted? Ignore
2682 if (GetVote(::Control.ClientID(), eType, iData))
2683 return;
2684 // Set pause mode if this is the host
2685 if (isHost() && isRunning())
2686 {
2687 Pause();
2688 fPausedForVote = true;
2689 }
2690 // send vote control
2691 ::Control.DoInput(CID_Vote, new C4ControlVote(eType, fApprove, iData), CDT_Direct);
2692 }
2693
AddVote(const C4ControlVote & Vote)2694 void C4Network2::AddVote(const C4ControlVote &Vote)
2695 {
2696 // Save back timestamp
2697 if (!Votes.firstPkt())
2698 iVoteStartTime = time(nullptr);
2699 // Save vote back
2700 Votes.Add(CID_Vote, new C4ControlVote(Vote));
2701 // Set pause mode if this is the host
2702 if (isHost() && isRunning())
2703 {
2704 Pause();
2705 fPausedForVote = true;
2706 }
2707 // Check if the dialog should be opened
2708 OpenVoteDialog();
2709 }
2710
GetVote(int32_t iClientID,C4ControlVoteType eType,int32_t iData)2711 C4IDPacket *C4Network2::GetVote(int32_t iClientID, C4ControlVoteType eType, int32_t iData)
2712 {
2713 C4ControlVote *pVote;
2714 for (C4IDPacket *pPkt = Votes.firstPkt(); pPkt; pPkt = Votes.nextPkt(pPkt))
2715 if (pPkt->getPktType() == CID_Vote)
2716 if ((pVote = static_cast<C4ControlVote *>(pPkt->getPkt())))
2717 if (iClientID == C4ClientIDUnknown || pVote->getByClient() == iClientID)
2718 if (pVote->getType() == eType && pVote->getData() == iData)
2719 return pPkt;
2720 return nullptr;
2721 }
2722
EndVote(C4ControlVoteType eType,bool fApprove,int32_t iData)2723 void C4Network2::EndVote(C4ControlVoteType eType, bool fApprove, int32_t iData)
2724 {
2725 // Remove all vote packets
2726 C4IDPacket *pPkt; int32_t iOrigin = C4ClientIDUnknown;
2727 while ((pPkt = GetVote(C4ClientIDAll, eType, iData)))
2728 {
2729 if (iOrigin == C4ClientIDUnknown)
2730 iOrigin = static_cast<C4ControlVote *>(pPkt->getPkt())->getByClient();
2731 Votes.Delete(pPkt);
2732 }
2733 // Reset timestamp
2734 iVoteStartTime = time(nullptr);
2735 // Approved own voting? Reset voting block
2736 if (fApprove && iOrigin == Game.Clients.getLocalID())
2737 iLastOwnVoting = 0;
2738 // Dialog open?
2739 if (pVoteDialog)
2740 if (pVoteDialog->getVoteType() == eType && pVoteDialog->getVoteData() == iData)
2741 {
2742 // close
2743 delete pVoteDialog;
2744 pVoteDialog = nullptr;
2745 }
2746 // Did we try to kick ourself? Ask if we'd like to surrender
2747 bool fCancelVote = (eType == VT_Kick && iData == Game.Clients.getLocalID()) || eType == VT_Cancel;
2748 if (!fApprove && fCancelVote && iOrigin == Game.Clients.getLocalID())
2749 OpenSurrenderDialog(eType, iData);
2750 // Check if the dialog should be opened
2751 OpenVoteDialog();
2752 // Pause/unpause voting?
2753 if (fApprove && eType == VT_Pause)
2754 fPausedForVote = !iData;
2755 // No voting left? Reset pause.
2756 if (!Votes.firstPkt())
2757 if (fPausedForVote)
2758 {
2759 Start();
2760 fPausedForVote = false;
2761 }
2762 }
2763
OpenVoteDialog()2764 void C4Network2::OpenVoteDialog()
2765 {
2766 // Dialog already open?
2767 if (pVoteDialog) return;
2768 // No vote available?
2769 if (!Votes.firstPkt()) return;
2770 // Can't vote?
2771 C4ClientPlayerInfos *pPlayerInfos = Game.PlayerInfos.GetInfoByClientID(Game.Clients.getLocalID());
2772 if (!pPlayerInfos || !pPlayerInfos->GetPlayerCount() || !pPlayerInfos->GetJoinedPlayerCount())
2773 return;
2774 // Search a voting we have to vote on
2775 for (C4IDPacket *pPkt = Votes.firstPkt(); pPkt; pPkt = Votes.nextPkt(pPkt))
2776 {
2777 // Already voted on this matter?
2778 C4ControlVote *pVote = static_cast<C4ControlVote *>(pPkt->getPkt());
2779 if (!GetVote(::Control.ClientID(), pVote->getType(), pVote->getData()))
2780 {
2781 // Compose message
2782 C4Client *pSrcClient = Game.Clients.getClientByID(pVote->getByClient());
2783 StdStrBuf Msg; Msg.Format(LoadResStr("IDS_VOTE_WANTSTOALLOW"), pSrcClient ? pSrcClient->getName() : "???", pVote->getDesc().getData());
2784 Msg.AppendChar('|');
2785 Msg.Append(pVote->getDescWarning());
2786
2787 // Open dialog
2788 pVoteDialog = new C4VoteDialog(Msg.getData(), pVote->getType(), pVote->getData(), false);
2789 pVoteDialog->SetDelOnClose();
2790 pVoteDialog->Show(::pGUI, true);
2791
2792 break;
2793 }
2794 }
2795 }
2796
OpenSurrenderDialog(C4ControlVoteType eType,int32_t iData)2797 void C4Network2::OpenSurrenderDialog(C4ControlVoteType eType, int32_t iData)
2798 {
2799 if (!pVoteDialog)
2800 {
2801 pVoteDialog = new C4VoteDialog(
2802 LoadResStr("IDS_VOTE_SURRENDERWARNING"), eType, iData, true);
2803 pVoteDialog->SetDelOnClose();
2804 pVoteDialog->Show(::pGUI, true);
2805 }
2806 }
2807
2808
OnVoteDialogClosed()2809 void C4Network2::OnVoteDialogClosed()
2810 {
2811 pVoteDialog = nullptr;
2812 }
2813
2814
2815 // *** C4VoteDialog
2816
C4VoteDialog(const char * szText,C4ControlVoteType eVoteType,int32_t iVoteData,bool fSurrender)2817 C4VoteDialog::C4VoteDialog(const char *szText, C4ControlVoteType eVoteType, int32_t iVoteData, bool fSurrender)
2818 : MessageDialog(szText, LoadResStr("IDS_DLG_VOTING"), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_Confirm, C4GUI::MessageDialog::dsRegular, nullptr, true),
2819 eVoteType(eVoteType), iVoteData(iVoteData), fSurrender(fSurrender)
2820 {
2821
2822 }
2823
OnClosed(bool fOK)2824 void C4VoteDialog::OnClosed(bool fOK)
2825 {
2826 bool fAbortGame = false;
2827 // notify that this object will be deleted shortly
2828 ::Network.OnVoteDialogClosed();
2829 // Was league surrender dialog
2830 if (fSurrender)
2831 {
2832 // League surrender accepted
2833 if (fOK)
2834 {
2835 // set game leave reason, although round results dialog isn't showing it ATM
2836 Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, LoadResStr("IDS_ERR_YOUSURRENDEREDTHELEAGUEGA"));
2837 // leave game
2838 ::Network.LeagueSurrender();
2839 ::Network.Clear();
2840 // We have just league-surrendered. Abort the game - that is what we originally wanted.
2841 // Note: as we are losing league points and this is a relevant game, it would actually be
2842 // nice to show an evaluation dialog which tells us that we have lost and how many league
2843 // points we have lost. But until the evaluation dialog can actually do that, it is better
2844 // to abort completely.
2845 // Note2: The league dialog will never know that, because the game will usually not be over yet.
2846 // Scores are not calculated until after the game.
2847 fAbortGame = true;
2848 }
2849 }
2850 // Was normal vote dialog
2851 else
2852 {
2853 // Vote still active? Then vote.
2854 if (::Network.GetVote(C4ClientIDUnknown, eVoteType, iVoteData))
2855 ::Network.Vote(eVoteType, fOK, iVoteData);
2856 }
2857 // notify base class
2858 MessageDialog::OnClosed(fOK);
2859 // Abort game
2860 if (fAbortGame)
2861 Game.Abort(true);
2862 }
2863
2864
2865 /* Lobby countdown */
2866
StartLobbyCountdown(int32_t iCountdownTime)2867 void C4Network2::StartLobbyCountdown(int32_t iCountdownTime)
2868 {
2869 // abort previous
2870 if (pLobbyCountdown) AbortLobbyCountdown();
2871 // start new
2872 pLobbyCountdown = new C4GameLobby::Countdown(iCountdownTime);
2873 }
2874
AbortLobbyCountdown()2875 void C4Network2::AbortLobbyCountdown()
2876 {
2877 // aboert lobby countdown
2878 if (pLobbyCountdown)
2879 {
2880 pLobbyCountdown->Abort();
2881 delete pLobbyCountdown;
2882 pLobbyCountdown = nullptr;
2883 }
2884 }
2885
2886 /* Streaming */
2887
StartStreaming(C4Record * pRecord)2888 bool C4Network2::StartStreaming(C4Record *pRecord)
2889 {
2890 // Save back
2891 fStreaming = true;
2892 pStreamedRecord = pRecord;
2893 iLastStreamAttempt = time(nullptr);
2894
2895 // Initialize compressor
2896 ZeroMem(&StreamCompressor, sizeof(StreamCompressor));
2897 if (deflateInit(&StreamCompressor, 9) != Z_OK)
2898 return false;
2899
2900 // Create stream buffer
2901 StreamingBuf.New(C4NetStreamingMaxBlockSize);
2902 StreamCompressor.next_out = reinterpret_cast<BYTE*>(StreamingBuf.getMData());
2903 StreamCompressor.avail_out = C4NetStreamingMaxBlockSize;
2904
2905 // Initialize HTTP client
2906 pStreamer = new C4Network2HTTPClient();
2907 if (!pStreamer->Init())
2908 return false;
2909 Application.Add(pStreamer);
2910
2911 return true;
2912 }
2913
FinishStreaming()2914 bool C4Network2::FinishStreaming()
2915 {
2916 if (!fStreaming) return false;
2917
2918 // Stream
2919 StreamIn(true);
2920
2921 // Reset record pointer
2922 pStreamedRecord = nullptr;
2923
2924 // Try to get rid of remaining data immediately
2925 iLastStreamAttempt = 0;
2926 StreamOut();
2927
2928 return true;
2929 }
2930
StopStreaming()2931 bool C4Network2::StopStreaming()
2932 {
2933 if (!fStreaming) return false;
2934
2935 // Clear
2936 Application.Remove(pStreamer);
2937 fStreaming = false;
2938 pStreamedRecord = nullptr;
2939 deflateEnd(&StreamCompressor);
2940 StreamingBuf.Clear();
2941 delete pStreamer;
2942 pStreamer = nullptr;
2943
2944 // ... finalization?
2945 return true;
2946 }
2947
StreamIn(bool fFinish)2948 bool C4Network2::StreamIn(bool fFinish)
2949 {
2950 if (!pStreamedRecord) return false;
2951
2952 // Get data from record
2953 const StdBuf &Data = pStreamedRecord->GetStreamingBuf();
2954 if (!fFinish)
2955 if (!Data.getSize() || !StreamCompressor.avail_out)
2956 return false;
2957
2958 do
2959 {
2960
2961 // Compress
2962 StreamCompressor.next_in = const_cast<BYTE *>(getBufPtr<BYTE>(Data));
2963 StreamCompressor.avail_in = Data.getSize();
2964 int ret = deflate(&StreamCompressor, fFinish ? Z_FINISH : Z_NO_FLUSH);
2965
2966 // Anything consumed?
2967 unsigned int iInAmount = Data.getSize() - StreamCompressor.avail_in;
2968 if (iInAmount > 0)
2969 pStreamedRecord->ClearStreamingBuf(iInAmount);
2970
2971 // Done?
2972 if (!fFinish || ret == Z_STREAM_END)
2973 break;
2974
2975 // Error while finishing?
2976 if (ret != Z_OK)
2977 return false;
2978
2979 // Enlarge buffer, if neccessary
2980 size_t iPending = getPendingStreamData();
2981 size_t iGrow = StreamingBuf.getSize();
2982 StreamingBuf.Grow(iGrow);
2983 StreamCompressor.avail_out += iGrow;
2984 StreamCompressor.next_out = getMBufPtr<BYTE>(StreamingBuf, iPending);
2985
2986 }
2987 while (true);
2988
2989 return true;
2990 }
2991
StreamOut()2992 bool C4Network2::StreamOut()
2993 {
2994 // Streamer busy?
2995 if (!pStreamer || pStreamer->isBusy())
2996 return false;
2997
2998 // Streamer done?
2999 if (pStreamer->isSuccess())
3000 {
3001
3002 // Move new data to front of buffer
3003 if (getPendingStreamData() != iCurrentStreamAmount)
3004 StreamingBuf.Move(iCurrentStreamAmount, getPendingStreamData() - iCurrentStreamAmount);
3005
3006 // Free buffer space
3007 StreamCompressor.next_out -= iCurrentStreamAmount;
3008 StreamCompressor.avail_out += iCurrentStreamAmount;
3009
3010 // Advance stream
3011 iCurrentStreamPosition += iCurrentStreamAmount;
3012
3013 // Get input
3014 StreamIn(false);
3015 }
3016
3017 // Clear streamer
3018 pStreamer->Clear();
3019
3020 // Record is still running?
3021 if (pStreamedRecord)
3022 {
3023
3024 // Enough available to send?
3025 if (getPendingStreamData() < C4NetStreamingMinBlockSize)
3026 return false;
3027
3028 // Overflow protection
3029 if (iLastStreamAttempt && iLastStreamAttempt + C4NetStreamingInterval >= time(nullptr))
3030 return false;
3031
3032 }
3033 // All data finished?
3034 else if (!getPendingStreamData())
3035 {
3036 // Then we're done.
3037 StopStreaming();
3038 return false;
3039 }
3040
3041 // Set stream address
3042 StdStrBuf StreamAddr;
3043 StreamAddr.Copy(Game.Parameters.StreamAddress);
3044 StreamAddr.AppendFormat("pos=%d&end=%d", iCurrentStreamPosition, !pStreamedRecord);
3045 pStreamer->SetServer(StreamAddr.getData());
3046
3047 // Send data
3048 size_t iStreamAmount = getPendingStreamData();
3049 iCurrentStreamAmount = iStreamAmount;
3050 iLastStreamAttempt = time(nullptr);
3051 return pStreamer->Query(StdBuf(StreamingBuf.getData(), iStreamAmount), false);
3052 }
3053
isStreaming() const3054 bool C4Network2::isStreaming() const
3055 {
3056 // Streaming must be active and there must still be anything to stream
3057 return fStreaming;
3058 }
3059