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