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