OpenClonk
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->AddAddrFromPuncher(maybe_v4);
1026  // Do not ::Network.InvalidateReference(); yet, we're expecting an ID from the netpuncher
1027  }
1028  auto family = maybe_v4.GetFamily();
1029  if (isHost())
1030  {
1031  // Host connection: request ID from netpuncher
1033  }
1034  else
1035  {
1036  // Client connection: request packet from host.
1037  if (Status.getState() == GS_Init && getNetpuncherGameID(family))
1038  NetIO.SendPuncherPacket(C4NetpuncherPacketSReq(getNetpuncherGameID(family)), family);
1039  }
1040 }
1041 
1042 
1044 {
1045  // We have an internet connection, so let's punch the puncher server here in order to open an udp port
1046  C4NetIO::addr_t PuncherAddr;
1048  if (!PuncherAddr.IsNull())
1049  {
1050  PuncherAddr.SetDefaultPort(C4NetStdPortPuncher);
1051  NetIO.InitPuncher(PuncherAddr);
1052  }
1054  if (!PuncherAddr.IsNull())
1055  {
1056  PuncherAddr.SetDefaultPort(C4NetStdPortPuncher);
1057  NetIO.InitPuncher(PuncherAddr);
1058  }
1059 }
1060 
1062 {
1063  // savegame needed?
1064  if (fDynamicNeeded)
1065  {
1066  // create dynamic
1067  bool fSuccess = CreateDynamic(false);
1068  // check for clients that still need join-data
1069  C4Network2Client *pClient = nullptr;
1070  while ((pClient = Clients.GetNextClient(pClient)))
1071  if (!pClient->hasJoinData())
1072  {
1073  if (fSuccess)
1074  // now we can provide join data: send it
1075  SendJoinData(pClient);
1076  else
1077  // join data could not be created: emergency kick
1078  Game.Clients.CtrlRemove(pClient->getClient(), LoadResStr("IDS_ERR_ERRORWHILECREATINGJOINDAT"));
1079  }
1080  }
1081 }
1082 
1084 {
1085  if (!isEnabled()) return;
1086 
1087  C4Network2Client *pLocal = Clients.GetLocal();
1088 
1089  StdStrBuf Stat;
1090 
1091  // local client status
1092  Stat.AppendFormat("Local: %s %s %s (ID %d)",
1093  pLocal->isObserver() ? "Observing" : pLocal->isActivated() ? "Active" : "Inactive", pLocal->isHost() ? "host" : "client",
1094  pLocal->getName(), pLocal->getID());
1095 
1096  // game status
1097  Stat.AppendFormat( "|Game Status: %s (tick %d)%s%s",
1099  fStatusReached ? " reached" : "", fStatusAck ? " ack" : "");
1100 
1101  // available protocols
1102  C4NetIO *pMsgIO = NetIO.MsgIO(), *pDataIO = NetIO.DataIO();
1103  if (pMsgIO && pDataIO)
1104  {
1105  C4Network2IOProtocol eMsgProt = NetIO.getNetIOProt(pMsgIO),
1106  eDataProt = NetIO.getNetIOProt(pDataIO);
1107  int32_t iMsgPort = 0, iDataPort = 0;
1108  switch (eMsgProt)
1109  {
1110  case P_TCP: iMsgPort = Config.Network.PortTCP; break;
1111  case P_UDP: iMsgPort = Config.Network.PortUDP; break;
1112  case P_NONE: assert(eMsgProt != P_NONE); break;
1113  }
1114  switch (eDataProt)
1115  {
1116  case P_TCP: iDataPort = Config.Network.PortTCP; break;
1117  case P_UDP: iDataPort = Config.Network.PortUDP; break;
1118  case P_NONE: assert(eMsgProt != P_NONE); break;
1119  }
1120  Stat.AppendFormat( "|Protocols: %s: %s (%d i%d o%d bc%d)",
1121  pMsgIO != pDataIO ? "Msg" : "Msg/Data",
1122  NetIO.getNetIOName(pMsgIO), iMsgPort,
1123  NetIO.getProtIRate(eMsgProt), NetIO.getProtORate(eMsgProt), NetIO.getProtBCRate(eMsgProt));
1124  if (pMsgIO != pDataIO)
1125  Stat.AppendFormat( ", Data: %s (%d i%d o%d bc%d)",
1126  NetIO.getNetIOName(pDataIO), iDataPort,
1127  NetIO.getProtIRate(eDataProt), NetIO.getProtORate(eDataProt), NetIO.getProtBCRate(eDataProt));
1128  }
1129  else
1130  Stat.Append("|Protocols: none");
1131 
1132  // some control statistics
1133  Stat.AppendFormat( "|Control: %s, Tick %d, Behind %d, Rate %d, PreSend %d, ACT: %d",
1134  Status.getCtrlMode() == CNM_Decentral ? "Decentral" : Status.getCtrlMode() == CNM_Central ? "Central" : "Async",
1137 
1138  // Streaming statistics
1139  if (fStreaming)
1140  Stat.AppendFormat( "|Streaming: %lu waiting, %u in, %lu out, %lu sent",
1141  static_cast<unsigned long>(pStreamedRecord ? pStreamedRecord->GetStreamingBuf().getSize() : 0),
1143  static_cast<unsigned long>(getPendingStreamData()),
1144  static_cast<unsigned long>(iCurrentStreamPosition));
1145 
1146  // clients
1147  Stat.Append("|Clients:");
1148  for (C4Network2Client *pClient = Clients.GetNextClient(nullptr); pClient; pClient = Clients.GetNextClient(pClient))
1149  {
1150  // ignore local
1151  if (pClient->isLocal()) continue;
1152  // client status
1153  const C4ClientCore &Core = pClient->getCore();
1154  const char *szClientStatus;
1155  switch (pClient->getStatus())
1156  {
1157  case NCS_Joining: szClientStatus = " (joining)"; break;
1158  case NCS_Chasing: szClientStatus = " (chasing)"; break;
1159  case NCS_NotReady: szClientStatus = " (!rdy)"; break;
1160  case NCS_Remove: szClientStatus = " (removed)"; break;
1161  default: szClientStatus = ""; break;
1162  }
1163  Stat.AppendFormat( "|- %s %s %s (ID %d) (wait %d ms, behind %d)%s%s",
1164  Core.isObserver() ? "Observing" : Core.isActivated() ? "Active" : "Inactive", Core.isHost() ? "host" : "client",
1165  Core.getName(), Core.getID(),
1166  pControl->ClientPerfStat(pClient->getID()),
1167  ::Control.ControlTick - pControl->ClientNextControl(pClient->getID()),
1168  szClientStatus,
1169  pClient->isActivated() && !pControl->ClientReady(pClient->getID(), ::Control.ControlTick) ? " (!ctrl)" : "");
1170  // connections
1171  if (pClient->isConnected())
1172  {
1173  Stat.AppendFormat( "| Connections: %s: %s (%s p%d l%d)",
1174  pClient->getMsgConn() == pClient->getDataConn() ? "Msg/Data" : "Msg",
1175  NetIO.getNetIOName(pClient->getMsgConn()->getNetClass()),
1176  pClient->getMsgConn()->getPeerAddr().ToString().getData(),
1177  pClient->getMsgConn()->getPingTime(),
1178  pClient->getMsgConn()->getPacketLoss());
1179  if (pClient->getMsgConn() != pClient->getDataConn())
1180  Stat.AppendFormat( ", Data: %s (%s:%d p%d l%d)",
1181  NetIO.getNetIOName(pClient->getDataConn()->getNetClass()),
1182  pClient->getDataConn()->getPeerAddr().ToString().getData(),
1183  pClient->getDataConn()->getPingTime(),
1184  pClient->getDataConn()->getPacketLoss());
1185  }
1186  else
1187  Stat.Append("| Not connected");
1188  }
1189  if (!Clients.GetNextClient(nullptr))
1190  Stat.Append("| - none -");
1191 
1192  // draw
1193  pDraw->TextOut(Stat.getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,cgo.X + 20,cgo.Y + 50);
1194 }
1195 
1196 bool C4Network2::InitNetIO(bool fNoClientID, bool fHost)
1197 {
1198  // clear
1199  NetIO.Clear();
1201  // discovery: disable for client
1202  int16_t iPortDiscovery = fHost ? Config.Network.PortDiscovery : -1;
1203  int16_t iPortRefServer = fHost ? Config.Network.PortRefServer : -1;
1204  // init subclass
1205  if (!NetIO.Init(Config.Network.PortTCP, Config.Network.PortUDP, iPortDiscovery, iPortRefServer, fHost, !!Config.Network.EnableUPnP))
1206  return false;
1207  // set core (unset ID if sepecified, has to be set later)
1209  if (fNoClientID) Core.SetID(C4ClientIDUnknown);
1210  NetIO.SetLocalCCore(Core);
1211  // safe addresses of local client
1213  NetIO.hasTCP() ? Config.Network.PortTCP : -1,
1214  NetIO.hasUDP() ? Config.Network.PortUDP : -1);
1215  // ok
1216  return true;
1217 }
1218 
1220 {
1221  // security
1222  if (!pConn) return;
1223 
1224  // Handles a connect request (packet PID_Conn).
1225  // Check if this peer should be allowed to connect, make space for the new connection.
1226 
1227  // connection is closed?
1228  if (pConn->isClosed())
1229  return;
1230 
1231  // set up core
1232  const C4ClientCore &CCore = Pkt.getCCore();
1233  C4ClientCore NewCCore = CCore;
1234 
1235  // accept connection?
1236  StdStrBuf reply;
1237  bool fOK = false;
1238 
1239  // search client
1240  if (!pClient && Pkt.getCCore().getID() != C4ClientIDUnknown)
1241  pClient = Clients.GetClient(Pkt.getCCore());
1242 
1243  // check engine version
1244  bool fWrongPassword = false;
1245  if (Pkt.getVer() != C4XVER1*100 + C4XVER2)
1246  {
1247  reply.Format("wrong engine (%d.%d, I have %d.%d)", Pkt.getVer()/100, Pkt.getVer()%100, C4XVER1, C4XVER2);
1248  fOK = false;
1249  }
1250  else
1251  {
1252  if (pClient)
1253  if (CheckConn(NewCCore, pConn, pClient, &reply))
1254  {
1255  // accept
1256  if (!reply) reply = "connection accepted";
1257  fOK = true;
1258  }
1259  // client: host connection?
1260  if (!fOK && !isHost() && Status.getState() == GS_Init && !Clients.GetHost())
1261  if (HostConnect(NewCCore, pConn, &reply))
1262  {
1263  // accept
1264  if (!reply) reply = "host connection accepted";
1265  fOK = true;
1266  }
1267  // host: client join? (NewCCore will be changed by Join()!)
1268  if (!fOK && isHost() && !pClient)
1269  {
1270  // check password
1271  if (!sPassword.isNull() && !SEqual(Pkt.getPassword(), sPassword.getData()))
1272  {
1273  reply = "wrong password";
1274  fWrongPassword = true;
1275  }
1276  // accept join
1277  else if (Join(NewCCore, pConn, &reply))
1278  {
1279  // save core
1280  pConn->SetCCore(NewCCore);
1281  // accept
1282  if (!reply) reply = "join accepted";
1283  fOK = true;
1284  }
1285  }
1286  }
1287 
1288  // denied? set default reason
1289  if (!fOK && !reply) reply = "connection denied";
1290 
1291  // OK and already half accepted? Skip (double-checked: ok).
1292  if (fOK && pConn->isHalfAccepted())
1293  return;
1294 
1295  // send answer
1296  C4PacketConnRe pcr(fOK, fWrongPassword, reply.getData());
1297  if (!pConn->Send(MkC4NetIOPacket(PID_ConnRe, pcr)))
1298  return;
1299 
1300  // accepted?
1301  if (fOK)
1302  {
1303  // set status
1304  if (!pConn->isClosed())
1305  pConn->SetHalfAccepted();
1306  }
1307  // denied? close
1308  else
1309  {
1310  // log & close
1311  LogSilentF("Network: connection by %s (%s) blocked: %s", CCore.getName(), pConn->getPeerAddr().ToString().getData(), reply.getData());
1312  pConn->Close();
1313  }
1314 }
1315 
1317 {
1318  if (!pConn || !pClient) return false;
1319  // already connected? (shouldn't happen really)
1320  if (pClient->hasConn(pConn))
1321  { *szReply = "already connected"; return true; }
1322  // check core
1323  if (CCore.getDiffLevel(pClient->getCore()) > C4ClientCoreDL_IDMatch)
1324  { *szReply = "wrong client core"; return false; }
1325  // accept
1326  return true;
1327 }
1328 
1330 {
1331  if (!pConn) return false;
1332  if (!CCore.isHost()) { *szReply = "not host"; return false; }
1333  // create client class for host
1334  // (core is unofficial, see InitClient() - will be overwritten later in HandleJoinData)
1335  C4Client *pClient = Game.Clients.Add(CCore);
1336  if (!pClient) return false;
1337  // accept
1338  return true;
1339 }
1340 
1342 {
1343  if (!pConn) return false;
1344  // security
1345  if (!isHost()) { *szReply = "not host"; return false; }
1346  if (!fAllowJoin && !fAllowObserve) { *szReply = "join denied"; return false; }
1347  if (CCore.getID() != C4ClientIDUnknown) { *szReply = "join with set id not allowed"; return false; }
1348  // find free client id
1349  CCore.SetID(iNextClientID++);
1350  // observer?
1351  if (!fAllowJoin) CCore.SetObserver(true);
1352  // deactivate - client will have to ask for activation.
1353  CCore.SetActivated(false);
1354  // Name already in use? Find unused one
1355  if (Clients.GetClient(CCore.getName()))
1356  {
1357  char szNameTmpl[256+1], szNewName[256+1];
1358  SCopy(CCore.getName(), szNameTmpl, 254); SAppend("%d", szNameTmpl, 256);
1359  int32_t i = 1;
1360  do
1361  sprintf(szNewName, szNameTmpl, ++i);
1362  while (Clients.GetClient(szNewName));
1363  CCore.SetName(szNewName);
1364  }
1365  // join client
1367  // get client, set status
1368  C4Network2Client *pClient = Clients.GetClient(CCore);
1369  if (pClient) pClient->SetStatus(NCS_Joining);
1370  // warn if client revision doesn't match our host revision
1372  {
1373  LogF("[!]WARNING! Client %s engine revision (%s) differs from local revision (%s). Client might run out of sync.", CCore.getName(), CCore.getRevision(), Application.GetRevision());
1374  }
1375  // ok, client joined.
1376  return true;
1377  // Note that the connection isn't fully accepted at this point and won't be
1378  // associated with the client. The new-created client is waiting for connect.
1379  // Somewhat ironically, the connection may still timeout (resulting in an instant
1380  // removal and maybe some funny message sequences).
1381  // The final client initialization will be done at OnClientConnect.
1382 }
1383 
1385 {
1386  // Handle the connection request reply. After this handling, the connection should
1387  // be either fully associated with a client (fully accepted) or closed.
1388  // Note that auto-accepted connection have to processed here once, too, as the
1389  // client must get associated with the connection. After doing so, the connection
1390  // auto-accept flag will be reset to mark the connection fully accepted.
1391 
1392  // security
1393  if (!pConn) return;
1394  if (!pClient) { pConn->Close(); return; }
1395 
1396  // negative reply?
1397  if (!Pkt.isOK())
1398  {
1399  // wrong password?
1401  // show message
1402  LogSilentF("Network: connection to %s (%s) refused: %s", pClient->getName(), pConn->getPeerAddr().ToString().getData(), Pkt.getMsg());
1403  // close connection
1404  pConn->Close();
1405  return;
1406  }
1407 
1408  // connection is closed?
1409  if (!pConn->isOpen())
1410  return;
1411 
1412  // already accepted? ignore
1413  if (pConn->isAccepted() && !pConn->isAutoAccepted()) return;
1414 
1415  // first connection?
1416  bool fFirstConnection = !pClient->isConnected();
1417 
1418  // accept connection
1419  pConn->SetAccepted(); pConn->ResetAutoAccepted();
1420 
1421  // add connection
1422  pConn->SetCCore(pClient->getCore());
1423  if (pConn->getNetClass() == NetIO.MsgIO()) pClient->SetMsgConn(pConn);
1424  if (pConn->getNetClass() == NetIO.DataIO()) pClient->SetDataConn(pConn);
1425 
1426  // add peer connect address to client address list
1427  if (!pConn->getConnectAddr().IsNull())
1428  {
1429  C4Network2Address Addr(pConn->getConnectAddr(), pConn->getProtocol());
1430  pClient->AddAddr(Addr, Status.getState() != GS_Init);
1431  }
1432 
1433  // handle
1434  OnConnect(pClient, pConn, Pkt.getMsg(), fFirstConnection);
1435 }
1436 
1438 {
1439  // set
1440  Status = nStatus;
1441  // log
1442  LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), nStatus.getTargetCtrlTick());
1443  // reset flags
1444  fStatusReached = fStatusAck = false;
1445  // check: reached?
1447 }
1448 
1450 {
1451  // security
1452  if (!pClient->hasJoinData() || pClient->isRemoved()) return;
1453  // status doesn't match?
1454  if (nStatus.getState() != Status.getState() || nStatus.getTargetCtrlTick() < Status.getTargetCtrlTick())
1455  return;
1456  // host: wait until all clients are ready
1457  if (isHost())
1458  {
1459  // check: target tick change?
1460  if (!fStatusAck && nStatus.getTargetCtrlTick() > Status.getTargetCtrlTick())
1461  // take the new status
1462  ChangeGameStatus(nStatus.getState(), nStatus.getTargetCtrlTick());
1463  // already acknowledged? Send another ack
1464  if (fStatusAck)
1465  pClient->SendMsg(MkC4NetIOPacket(PID_StatusAck, nStatus));
1466  // mark as ready (will clear chase-flag)
1467  pClient->SetStatus(NCS_Ready);
1468  // check: everyone ready?
1469  if (!fStatusAck && fStatusReached)
1470  CheckStatusAck();
1471  }
1472  else
1473  {
1474  // target tick doesn't match? ignore
1475  if (nStatus.getTargetCtrlTick() != Status.getTargetCtrlTick())
1476  return;
1477  // reached?
1478  // can be ignored safely otherwise - when the status is reached, we will send
1479  // status ack on which the host should generate another status ack (see above)
1480  if (fStatusReached)
1481  {
1482  // client: set flags, call handler
1483  fStatusAck = true; fChasing = false;
1484  OnStatusAck();
1485  }
1486 
1487  }
1488 }
1489 
1490 void C4Network2::HandleActivateReq(int32_t iTick, C4Network2Client *pByClient)
1491 {
1492  if (!isHost()) return;
1493  // not allowed or already activated? ignore
1494  if (pByClient->isObserver() || pByClient->isActivated()) return;
1495  // not joined completely yet? ignore
1496  if (!pByClient->isWaitedFor()) return;
1497  // check behind limit
1498  if (isRunning())
1499  {
1500  // make a guess how much the client lags.
1501  int32_t iLagFrames = Clamp(pByClient->getMsgConn()->getPingTime() * Game.FPS / 500, 0, 100);
1502  if (iTick < Game.FrameCounter - iLagFrames - C4NetMaxBehind4Activation)
1503  return;
1504  }
1505  // activate him
1507  new C4ControlClientUpdate(pByClient->getID(), CUT_Activate, true),
1508  CDT_Sync);
1509 }
1510 
1512 {
1513  // init only
1514  if (Status.getState() != GS_Init)
1515  { LogSilentF("Network: unexpected join data received!"); return; }
1516  // get client ID
1517  if (rPkt.getClientID() == C4ClientIDUnknown)
1518  { LogSilentF("Network: host didn't set client ID!"); Clear(); return; }
1519  // set local ID
1520  ResList.SetLocalID(rPkt.getClientID());
1522  // read and validate status
1523  HandleStatus(rPkt.getStatus());
1524  if (Status.getState() != GS_Lobby && Status.getState() != GS_Pause && Status.getState() != GS_Go)
1525  { LogSilentF("Network: join data has bad game status: %s", Status.getStateName()); Clear(); return; }
1526  // copy scenario parameter defs for lobby display
1528  // copy parameters
1529  ::Game.Parameters = rPkt.Parameters;
1530  // set local client
1531  C4Client *pLocalClient = Game.Clients.getClientByID(rPkt.getClientID());
1532  if (!pLocalClient)
1533  { LogSilentF("Network: Could not find local client in join data!"); Clear(); return; }
1534  // save back dynamic data
1535  ResDynamic = rPkt.getDynamicCore();
1536  iDynamicTick = rPkt.getStartCtrlTick();
1537  // initialize control
1539  pControl->Init(rPkt.getClientID(), false, rPkt.getStartCtrlTick(), pLocalClient->isActivated(), this);
1541  // set local core
1542  NetIO.SetLocalCCore(pLocalClient->getCore());
1543  // add the resources to the network resource list
1545  // load dynamic
1547  { LogFatal("Network: can not not retrieve dynamic!"); Clear(); return; }
1548  // load player resources
1550  // send additional addresses
1551  Clients.SendAddresses(nullptr);
1552 }
1553 
1554 void C4Network2::OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection)
1555 {
1556  // log
1557  LogSilentF("Network: %s %s connected (%s/%s) (%s)", pClient->isHost() ? "host" : "client",
1558  pClient->getName(), pConn->getPeerAddr().ToString().getData(),
1559  NetIO.getNetIOName(pConn->getNetClass()), szMsg ? szMsg : "");
1560 
1561  // first connection for this peer? call special handler
1562  if (fFirstConnection) OnClientConnect(pClient, pConn);
1563 }
1564 
1566 {
1567  LogSilentF("Network: %s connection to %s failed!", NetIO.getNetIOName(pConn->getNetClass()),
1568  pConn->getPeerAddr().ToString().getData());
1569 
1570  // maybe client connection failure
1571  // (happens if the connection is not fully accepted and the client disconnects.
1572  // See C4Network2::Join)
1573  C4Network2Client *pClient = Clients.GetClientByID(pConn->getClientID());
1574  if (pClient && !pClient->isConnected())
1575  OnClientDisconnect(pClient);
1576 }
1577 
1579 {
1580  LogSilentF("Network: %s connection to %s (%s) lost!", NetIO.getNetIOName(pConn->getNetClass()),
1581  pClient->getName(), pConn->getPeerAddr().ToString().getData());
1582 
1583  // connection lost?
1584  if (!pClient->isConnected())
1585  OnClientDisconnect(pClient);
1586 }
1587 
1589 {
1590  // host: new client?
1591  if (isHost())
1592  {
1593  // dynamic available?
1594  if (!pClient->hasJoinData())
1595  SendJoinData(pClient);
1596 
1597  // notice lobby (doesn't do anything atm?)
1598  C4GameLobby::MainDlg *pDlg = GetLobby();
1599  if (isLobbyActive()) pDlg->OnClientConnect(pClient->getClient(), pConn);
1600 
1601  }
1602 
1603  // discover resources
1604  ResList.OnClientConnect(pConn);
1605 
1606 }
1607 
1609 {
1610  // league: Notify regular client disconnect within the game
1611  if (pLeagueClient && (isHost() || pClient->isHost())) LeagueNotifyDisconnect(pClient->getID(), C4LDR_ConnectionFailed);
1612  // host? Remove this client from the game.
1613  if (isHost())
1614  {
1615  // log
1616  LogSilentF(LoadResStr("IDS_NET_CLIENTDISCONNECTED"), pClient->getName()); // silent, because a duplicate message with disconnect reason will follow
1617  // remove the client
1618  Game.Clients.CtrlRemove(pClient->getClient(), LoadResStr("IDS_MSG_DISCONNECTED"));
1619  // check status ack (disconnected client might be the last that was waited for)
1620  CheckStatusAck();
1621  // unreached pause/go? retry setting the state with current control tick
1622  // (client might be the only one claiming to have the given control)
1623  if (!fStatusReached)
1624  if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
1626 #ifdef USE_CONSOLE
1627  // Dedicated server: stop hosting if there is only one client left we're hosting for.
1628  // TODO: Find a better place to do this.
1629  if (Game.IsRunning && Clients.Count() <= 3) Application.Quit(); // Off-by-1 error
1630 #endif // USE_CONSOLE
1631  }
1632  // host disconnected? Clear up
1633  if (!isHost() && pClient->isHost())
1634  {
1635  StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_NET_HOSTDISCONNECTED"), pClient->getName());
1636  Log(sMsg.getData());
1637  // host connection lost: clear up everything
1639  Clear();
1640  }
1641 }
1642 
1644 {
1645  if (pClient->hasJoinData()) return;
1646  // host only, scenario must be available
1647  assert(isHost());
1648  // dynamic available?
1650  {
1651  fDynamicNeeded = true;
1652  // add synchronization control (will callback, see C4Game::Synchronize)
1654  return;
1655  }
1656  // save his client ID
1657  C4PacketJoinData JoinData;
1658  JoinData.SetClientID(pClient->getID());
1659  // save status into packet
1660  JoinData.SetGameStatus(Status);
1661  // scenario parameter defs for lobby display (localized in host language)
1663  // parameters
1664  JoinData.Parameters = Game.Parameters;
1665  // core join data
1666  JoinData.SetStartCtrlTick(iDynamicTick);
1667  JoinData.SetDynamicCore(ResDynamic);
1668  // send
1669  pClient->SendMsg(MkC4NetIOPacket(PID_JoinData, JoinData));
1670  // send addresses
1671  Clients.SendAddresses(pClient->getMsgConn());
1672  // flag client (he will have to accept the network status sent next)
1673  pClient->SetStatus(NCS_Chasing);
1674  if (!iLastChaseTargetUpdate) iLastChaseTargetUpdate = time(nullptr);
1675 }
1676 
1677 C4Network2Res::Ref C4Network2::RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeoutLen, const char *szResName, bool fWaitForCore)
1678 {
1679  C4GUI::ProgressDialog *pDlg = nullptr;
1680  bool fLog = false;
1681  int32_t iProcess = -1;
1682  C4TimeMilliseconds tTimeout = C4TimeMilliseconds::Now() + iTimeoutLen;
1683  // wait for resource
1684  while (isEnabled())
1685  {
1686  // find resource
1687  C4Network2Res::Ref pRes = ResList.getRefRes(Core.getID());
1688  // res not found?
1689  if (!pRes)
1690  {
1691  if (Core.isNull())
1692  {
1693  // should wait for core?
1694  if (!fWaitForCore) return nullptr;
1695  }
1696  else
1697  {
1698  // start loading
1699  pRes = ResList.AddByCore(Core);
1700  }
1701  }
1702  // res found and loaded completely
1703  else if (!pRes->isLoading())
1704  {
1705  // log
1706  if (fLog) LogF(LoadResStr("IDS_NET_RECEIVED"), szResName, pRes->getCore().getFileName());
1707  // return
1708  if (pDlg) delete pDlg;
1709  return pRes;
1710  }
1711 
1712  // check: progress?
1713  if (pRes && pRes->getPresentPercent() != iProcess)
1714  {
1715  iProcess = pRes->getPresentPercent();
1716  tTimeout = C4TimeMilliseconds::Now() + iTimeoutLen;
1717  }
1718  else
1719  {
1720  // if not: check timeout
1721  if (C4TimeMilliseconds::Now() > tTimeout)
1722  {
1723  LogFatal(FormatString(LoadResStr("IDS_NET_ERR_RESTIMEOUT"), szResName).getData());
1724  if (pDlg) delete pDlg;
1725  return nullptr;
1726  }
1727  }
1728 
1729  // log
1730  if (!fLog)
1731  {
1732  LogF(LoadResStr("IDS_NET_WAITFORRES"), szResName);
1733  fLog = true;
1734  }
1735  // show progress dialog
1736  if (!pDlg && !Console.Active && ::pGUI)
1737  {
1738  // create
1739  pDlg = new C4GUI::ProgressDialog(FormatString(LoadResStr("IDS_NET_WAITFORRES"), szResName).getData(),
1740  LoadResStr("IDS_NET_CAPTION"), 100, 0, C4GUI::Ico_NetWait);
1741  // show dialog
1742  if (!pDlg->Show(::pGUI, true)) { delete pDlg; return nullptr; }
1743  }
1744 
1745  // wait
1746  if (pDlg)
1747  {
1748  // set progress bar
1749  pDlg->SetProgress(iProcess);
1750  // execute (will do message handling)
1751  if (!pDlg->Execute())
1752  { if (pDlg) delete pDlg; return nullptr; }
1753  // aborted?
1754  if (pDlg->IsAborted()) break;
1755  }
1756  else
1757  {
1759  { return nullptr; }
1760  }
1761 
1762  }
1763  // aborted
1764  delete pDlg;
1765  return nullptr;
1766 }
1767 
1768 
1770 {
1771  if (!isHost()) return false;
1772  // remove all existing dynamic data
1773  RemoveDynamic();
1774  // log
1775  Log(LoadResStr("IDS_NET_SAVING"));
1776  // compose file name
1777  char szDynamicBase[_MAX_PATH+1], szDynamicFilename[_MAX_PATH+1];
1779  if (!ResList.FindTempResFileName(szDynamicBase, szDynamicFilename))
1780  Log(LoadResStr("IDS_NET_SAVE_ERR_CREATEDYNFILE"));
1781  // save dynamic data
1782  C4GameSaveNetwork SaveGame(fInit);
1783  if (!SaveGame.Save(szDynamicFilename) || !SaveGame.Close())
1784  { Log(LoadResStr("IDS_NET_SAVE_ERR_SAVEDYNFILE")); return false; }
1785  // add resource
1786  C4Network2Res::Ref pRes = ResList.AddByFile(szDynamicFilename, true, NRT_Dynamic);
1787  if (!pRes) { Log(LoadResStr("IDS_NET_SAVE_ERR_ADDDYNDATARES")); return false; }
1788  // save
1789  ResDynamic = pRes->getCore();
1791  fDynamicNeeded = false;
1792  // ok
1793  return true;
1794 }
1795 
1797 {
1799  if (pRes) pRes->Remove();
1800  ResDynamic.Clear();
1801  iDynamicTick = -1;
1802 }
1803 
1805 {
1806  // "frozen" means all clients are garantueed to be in the same tick.
1807  // This is only the case if the game is not started yet (lobby) or the
1808  // tick has been ensured (pause) and acknowledged by all joined clients.
1809  // Note unjoined clients must be ignored here - they can't be faster than
1810  // the host, anyway.
1811  if (Status.getState() == GS_Lobby) return true;
1812  if (Status.getState() == GS_Pause && fStatusAck) return true;
1813  return false;
1814 }
1815 
1816 bool C4Network2::ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode)
1817 {
1818  // change game status, announce. Can only be done by host.
1819  if (!isHost()) return false;
1820  // set status
1821  Status.Set(enState, iTargetCtrlTick);
1822  // update reference
1824  // control mode change?
1825  if (iCtrlMode >= 0) Status.SetCtrlMode(iCtrlMode);
1826  // log
1827  LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), iTargetCtrlTick);
1828  // set flags
1829  Clients.ResetReady();
1830  fStatusReached = fStatusAck = false;
1831  // send new status to all clients
1833  // check reach/ack
1835  // ok
1836  return true;
1837 }
1838 
1839 void C4Network2::CheckStatusReached(bool fFromFinalInit)
1840 {
1841  // already reached?
1842  if (fStatusReached) return;
1843  if (Status.getState() == GS_Lobby)
1845  // game go / pause: control must be initialized and target tick reached
1846  else if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
1847  {
1848  if (Game.IsRunning || fFromFinalInit)
1849  {
1850  // Make sure we have reached the tick and the control queue is empty (except for chasing)
1853  fStatusReached = true;
1854  else
1855  {
1856  // run ctrl so the tick can be reached
1858  Game.HaltCount = 0;
1860  }
1861  }
1862  }
1863  if (!fStatusReached) return;
1864  // call handler
1865  OnStatusReached();
1866  // host?
1867  if (isHost())
1868  // all clients ready?
1869  CheckStatusAck();
1870  else
1871  {
1873  // send response to host
1875  // do delayed activation request
1876  if (fDelayedActivateReq)
1877  {
1878  fDelayedActivateReq = false;
1879  RequestActivate();
1880  }
1881  }
1882 }
1883 
1885 {
1886  // host only
1887  if (!isHost()) return;
1888  // status must be reached and not yet acknowledged
1889  if (!fStatusReached || fStatusAck) return;
1890  // all clients ready?
1892  {
1893  // pause/go: check for sync control that can be executed
1894  if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
1896  // broadcast ack
1898  // handle
1899  OnStatusAck();
1900  }
1901 }
1902 
1904 {
1905  // stop ctrl, wait for ack
1906  if (pControl->IsEnabled())
1907  {
1909  pControl->SetRunning(false);
1910  }
1911 }
1912 
1914 {
1915  // log it
1916  LogSilentF("Network: status %s (tick %d) reached", Status.getStateName(), Status.getTargetCtrlTick());
1917  // pause?
1918  if (Status.getState() == GS_Pause)
1919  {
1920  // set halt-flag (show hold message)
1922  }
1923  // go?
1924  if (Status.getState() == GS_Go)
1925  {
1926  // set mode
1927  pControl->SetCtrlMode(static_cast<C4GameControlNetworkMode>(Status.getCtrlMode()));
1928  // notify player list of reached status - will add some input to the queue
1930  // start ctrl
1931  pControl->SetRunning(true);
1932  // reset halt-flag
1933  Game.HaltCount = 0;
1935  }
1936 }
1937 
1939 {
1940  // neither observer nor activated?
1942  {
1944  return;
1945  }
1946  // host? just do it
1947  if (fHost)
1948  {
1949  // activate him
1952  CDT_Sync);
1953  return;
1954  }
1955  // ensure interval
1957  return;
1958  // status not reached yet? May be chasing, let's delay this.
1959  if (!fStatusReached)
1960  {
1961  fDelayedActivateReq = true;
1962  return;
1963  }
1964  // request
1966  // store time
1968 }
1969 
1971 {
1972  // host only and not in editor
1973  if (!isHost() || ::Application.isEditor) return;
1974  // update activity
1976  // find clients to deactivate
1977  for (C4Network2Client *pClient = Clients.GetNextClient(nullptr); pClient; pClient = Clients.GetNextClient(pClient))
1978  if (!pClient->isLocal() && pClient->isActivated())
1979  if (pClient->getLastActivity() + C4NetDeactivationDelay < Game.FrameCounter)
1980  ::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(pClient->getID(), CUT_Activate, false), CDT_Sync);
1981 }
1982 
1984 {
1985  // no chasing clients?
1986  C4Network2Client *pClient;
1987  for (pClient = Clients.GetNextClient(nullptr); pClient; pClient = Clients.GetNextClient(pClient))
1988  if (pClient->isChasing())
1989  break;
1990  if (!pClient)
1991  {
1993  return;
1994  }
1995  // not time for an update?
1997  return;
1998  // copy status, set current tick
1999  C4Network2Status ChaseTarget = Status;
2000  ChaseTarget.SetTargetTick(::Control.ControlTick);
2001  // send to everyone involved
2002  for (pClient = Clients.GetNextClient(nullptr); pClient; pClient = Clients.GetNextClient(pClient))
2003  if (pClient->isChasing())
2004  pClient->SendMsg(MkC4NetIOPacket(PID_Status, ChaseTarget));
2005  iLastChaseTargetUpdate = time(nullptr);
2006 }
2007 
2008 void C4Network2::LeagueGameEvaluate(const char *szRecordName, const BYTE *pRecordSHA)
2009 {
2010  // already off?
2011  if (!pLeagueClient) return;
2012  // already evaluated?
2013  if (fLeagueEndSent) return;
2014  // end
2015  LeagueEnd(szRecordName, pRecordSHA);
2016 }
2017 
2019 {
2020  // already off?
2021  if (!pLeagueClient) return;
2022  // no post-disable if league is active
2023  if (pLeagueClient && Game.Parameters.isLeague()) return;
2024  // signup end
2025  LeagueEnd(); DeinitLeague();
2026 }
2027 
2029 {
2030  // already running?
2031  if (pLeagueClient) return true;
2032  // Start it!
2033  if (InitLeague(nullptr) && LeagueStart(nullptr)) return true;
2034  // Failure :'(
2035  DeinitLeague();
2036  return false;
2037 }
2038 
2040 {
2041  // Update both local and league reference as soon as possible
2044 }
2045 
2046 bool C4Network2::InitLeague(bool *pCancel)
2047 {
2048 
2049  if (fHost)
2050  {
2051 
2052  // Clear parameters
2056  if (pLeagueClient) delete pLeagueClient; pLeagueClient = nullptr;
2057 
2058  // Not needed?
2060  return true;
2061 
2062  // Save address
2065  {
2067  // enforce some league rules
2069  }
2070 
2071  }
2072  else
2073  {
2074 
2075  // Get league server from parameters
2077 
2078  // Not needed?
2080  return true;
2081 
2082  }
2083 
2084  // Init
2085  pLeagueClient = new C4LeagueClient();
2086  if (!pLeagueClient->Init() ||
2088  {
2089  // Log message
2090  StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUEINIT"), pLeagueClient->GetError());
2091  LogFatal(Message.getData());
2092  // Clear league
2093  delete pLeagueClient; pLeagueClient = nullptr;
2094  if (fHost)
2096  // Show message, allow abort
2097  bool fResult = true;
2098  if (!Application.isEditor)
2099  fResult = ::pGUI->ShowMessageModal(Message.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2102  if (pCancel) *pCancel = fResult;
2103  return false;
2104  }
2105 
2106  // Add to message loop
2108 
2109  // OK
2110  return true;
2111 }
2112 
2114 {
2115  // league clear
2119  if (pLeagueClient)
2120  {
2122  delete pLeagueClient; pLeagueClient = nullptr;
2123  }
2124 }
2125 
2126 bool C4Network2::LeagueStart(bool *pCancel)
2127 {
2128  // Not needed?
2129  if (!pLeagueClient || !fHost)
2130  return true;
2131 
2132  // Default
2133  if (pCancel) *pCancel = true;
2134 
2135  // Do update
2136  C4Network2Reference Ref;
2137  Ref.InitLocal();
2138  if (!pLeagueClient->Start(Ref))
2139  {
2140  // Log message
2141  StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_STARTGAME"), pLeagueClient->GetError());
2142  LogFatal(Message.getData());
2143  // Show message
2144  if (!Application.isEditor)
2145  {
2146  // Show option to cancel, if possible
2147  bool fResult = ::pGUI->ShowMessageModal(
2148  Message.getData(),
2149  LoadResStr("IDS_NET_ERR_LEAGUE"),
2152  if (pCancel)
2153  *pCancel = !fResult;
2154  }
2155  // Failed
2156  return false;
2157  }
2158 
2159  InitPuncher();
2160 
2161  // Let's wait for response
2162  StdStrBuf Message = FormatString(LoadResStr("IDS_NET_LEAGUE_REGGAME"), pLeagueClient->getServerName());
2163  Log(Message.getData());
2164  // Set up a dialog
2165  C4GUI::MessageDialog *pDlg = nullptr;
2166  if (!Application.isEditor)
2167  {
2168  // create & show
2169  pDlg = new C4GUI::MessageDialog(Message.getData(), LoadResStr("IDS_NET_LEAGUE_STARTGAME"),
2171  if (!pDlg || !pDlg->Show(::pGUI, true)) return false;
2172  }
2173  // Wait for response
2174  while (pLeagueClient->isBusy())
2175  {
2176  // Execute GUI
2177  if (!Application.ScheduleProcs() ||
2178  (pDlg && pDlg->IsAborted()))
2179  {
2180  // Clear up
2181  if (pDlg) delete pDlg;
2182  return false;
2183  }
2184  // Check if league server has responded
2185  if (!pLeagueClient->Execute(100))
2186  break;
2187  }
2188  // Close dialog
2189  if (pDlg)
2190  {
2191  pDlg->Close(true);
2192  delete pDlg;
2193  }
2194  // Error?
2195  StdStrBuf LeagueServerMessage, League, StreamingAddr;
2196  int32_t Seed = Game.RandomSeed, MaxPlayersLeague = 0;
2197  if (!pLeagueClient->isSuccess() ||
2198  !pLeagueClient->GetStartReply(&LeagueServerMessage, &League, &StreamingAddr, &Seed, &MaxPlayersLeague))
2199  {
2200  const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
2201  LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2202  LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
2203  StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_REGGAME"), pError);
2204  // Log message
2205  Log(Message.getData());
2206  // Show message
2207  if (!Application.isEditor)
2208  {
2209  // Show option to cancel, if possible
2210  bool fResult = ::pGUI->ShowMessageModal(
2211  Message.getData(),
2212  LoadResStr("IDS_NET_ERR_LEAGUE"),
2215  if (pCancel)
2216  *pCancel = !fResult;
2217  }
2218  // Failed
2219  return false;
2220  }
2221 
2222  // Show message
2223  if (LeagueServerMessage.getLength())
2224  {
2225  StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_LEAGUEGAMESIGNUP"), pLeagueClient->getServerName(), LeagueServerMessage.getData());
2226  // Log message
2227  Log(Message.getData());
2228  // Show message
2229  if (!Application.isEditor)
2230  {
2231  // Show option to cancel, if possible
2232  bool fResult = ::pGUI->ShowMessageModal(
2233  Message.getData(),
2234  LoadResStr("IDS_NET_ERR_LEAGUE"),
2237  if (pCancel)
2238  *pCancel = !fResult;
2239  if (!fResult)
2240  {
2241  LeagueEnd(); DeinitLeague();
2242  return false;
2243  }
2244  }
2245  }
2246 
2247  // Set game parameters for league game
2248  Game.Parameters.League = League;
2249  Game.RandomSeed = Seed;
2250  if (MaxPlayersLeague)
2251  Game.Parameters.MaxPlayers = MaxPlayersLeague;
2252  if (!League.getLength())
2253  {
2256  }
2257  else
2258  {
2259  Game.Parameters.StreamAddress = StreamingAddr;
2260  }
2261 
2262  // All ok
2263  fLeagueEndSent = false;
2264  return true;
2265 }
2266 
2268 {
2269  // Not needed?
2270  if (!pLeagueClient || !fHost)
2271  return true;
2272 
2273  // League client currently busy?
2274  if (pLeagueClient->isBusy())
2275  return true;
2276 
2277  // Create reference
2278  C4Network2Reference Ref;
2279  Ref.InitLocal();
2280 
2281  // Do update
2282  if (!pLeagueClient->Update(Ref))
2283  {
2284  // Log
2285  LogF(LoadResStr("IDS_NET_ERR_LEAGUE_UPDATEGAME"), pLeagueClient->GetError());
2286  return false;
2287  }
2288 
2289  // Timing
2290  iLastLeagueUpdate = time(nullptr);
2292 
2293  return true;
2294 }
2295 
2297 {
2298  // safety: A reply must be present
2299  assert(pLeagueClient);
2300  assert(fHost);
2301  assert(!pLeagueClient->isBusy());
2303  // check reply success
2304  C4ClientPlayerInfos PlayerLeagueInfos;
2305  StdStrBuf LeagueServerMessage;
2306  bool fSucc = pLeagueClient->isSuccess() && pLeagueClient->GetUpdateReply(&LeagueServerMessage, &PlayerLeagueInfos);
2308  if (!fSucc)
2309  {
2310  const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
2311  LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2312  LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
2313  StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_UPDATEGAME"), pError);
2314  // Show message - no dialog, because it's not really fatal and might happen in the running game
2315  Log(Message.getData());
2316  return false;
2317  }
2318  // evaluate reply: Transfer data to players
2319  // Take round results
2320  C4PlayerInfoList &TargetList = Game.PlayerInfos;
2321  C4ClientPlayerInfos *pInfos; C4PlayerInfo *pInfo, *pResultInfo;
2322  for (int iClient = 0; (pInfos = TargetList.GetIndexedInfo(iClient)); iClient++)
2323  for (int iInfo = 0; (pInfo = pInfos->GetPlayerInfo(iInfo)); iInfo++)
2324  if ((pResultInfo = PlayerLeagueInfos.GetPlayerInfoByID(pInfo->GetID())))
2325  {
2326  int32_t iLeagueProjectedGain = pResultInfo->GetLeagueProjectedGain();
2327  if (iLeagueProjectedGain != pInfo->GetLeagueProjectedGain())
2328  {
2329  pInfo->SetLeagueProjectedGain(iLeagueProjectedGain);
2330  pInfos->SetUpdated();
2331  }
2332  }
2333  // transfer info update to other clients
2335  // if lobby is open, notify lobby of updated players
2336  if (pLobby) pLobby->OnPlayersChange();
2337  // OMFG SUCCESS!
2338  return true;
2339 }
2340 
2341 bool C4Network2::LeagueEnd(const char *szRecordName, const BYTE *pRecordSHA)
2342 {
2343  C4RoundResultsPlayers RoundResults;
2344  StdStrBuf sResultMessage;
2345  bool fIsError = true;
2346 
2347  // Not needed?
2348  if (!pLeagueClient || !fHost || fLeagueEndSent)
2349  return true;
2350 
2351  // Make sure league client is available
2353 
2354  // Try until either aborted or successful
2355  const int MAX_RETRIES = 10;
2356  for (int iRetry = 0; iRetry < MAX_RETRIES; iRetry++)
2357  {
2358 
2359  // Do update
2360  C4Network2Reference Ref;
2361  Ref.InitLocal();
2362  if (!pLeagueClient->End(Ref, szRecordName, pRecordSHA))
2363  {
2364  // Log message
2365  sResultMessage = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_FINISHGAME"), pLeagueClient->GetError());
2366  Log(sResultMessage.getData());
2367  // Show message, allow retry
2368  if (Application.isEditor) break;
2369  bool fRetry = ::pGUI->ShowMessageModal(sResultMessage.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2371  if (fRetry) continue;
2372  break;
2373  }
2374  // Let's wait for response
2375  StdStrBuf Message = FormatString(LoadResStr("IDS_NET_LEAGUE_SENDRESULT"), pLeagueClient->getServerName());
2376  Log(Message.getData());
2377  // Wait for response
2378  while (pLeagueClient->isBusy())
2379  {
2380  // Check if league server has responded
2381  if (!pLeagueClient->Execute(100))
2382  break;
2383  }
2384  // Error?
2385  StdStrBuf LeagueServerMessage;
2386  if (!pLeagueClient->isSuccess() || !pLeagueClient->GetEndReply(&LeagueServerMessage, &RoundResults))
2387  {
2388  const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
2389  LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2390  LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
2391  sResultMessage.Take(FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_SENDRESULT"), pError));
2392  if (Application.isEditor) continue;
2393  // Only retry if we didn't get an answer from the league server
2394  bool fRetry = !pLeagueClient->isSuccess();
2395  fRetry = ::pGUI->ShowMessageModal(sResultMessage.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2398  if (fRetry) continue;
2399  }
2400  else
2401  {
2402  // All OK!
2403  sResultMessage.Copy(LoadResStr(Game.Parameters.isLeague() ? "IDS_MSG_LEAGUEEVALUATIONSUCCESSFU" : "IDS_MSG_INTERNETGAMEEVALUATED"));
2404  fIsError = false;
2405  }
2406  // Done
2407  break;
2408  }
2409 
2410  // Show message
2411  Log(sResultMessage.getData());
2412 
2413  // Take round results
2414  Game.RoundResults.EvaluateLeague(sResultMessage.getData(), !fIsError, RoundResults);
2415 
2416  // Send round results to other clients
2417  C4PacketLeagueRoundResults LeagueUpdatePacket(sResultMessage.getData(), !fIsError, RoundResults);
2419 
2420  // All done
2421  fLeagueEndSent = true;
2422  return true;
2423 }
2424 
2426 {
2427 
2428  // Not possible?
2429  if (!pLeagueClient)
2430  return false;
2431 
2432  // Make sure league client is avilable
2434 
2435  // Official league?
2436  bool fOfficialLeague = SEqual(pLeagueClient->getServerName(), "league.openclonk.org");
2437 
2438  StdStrBuf Account, Password;
2439  bool fRememberLogin = false;
2440 
2441  // Default password from login token if present
2442  if (Config.Network.GetLeagueLoginData(pLeagueClient->getServerName(), pInfo->GetName(), &Account, &Password))
2443  {
2444  fRememberLogin = (Password.getLength()>0);
2445  }
2446  else
2447  {
2448  Account.Copy(pInfo->GetName());
2449  }
2450 
2451  for (;;)
2452  {
2453  // ask for account name and password
2454  if (!C4LeagueSignupDialog::ShowModal(pInfo->GetName(), Account.getData(), pLeagueClient->getServerName(), &Account, &Password, !fOfficialLeague, false, &fRememberLogin))
2455  return false;
2456 
2457  // safety (modal dlg may have deleted network)
2458  if (!pLeagueClient) return false;
2459 
2460  // Send authentication request
2461  if (!pLeagueClient->Auth(*pInfo, Account.getData(), Password.getData(), nullptr, nullptr, fRememberLogin))
2462  return false;
2463 
2464  // safety (modal dlg may have deleted network)
2465  if (!pLeagueClient) return false;
2466 
2467  // Wait for a response
2468  StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_TRYLEAGUESIGNUP"), pInfo->GetName(), Account.getData(), pLeagueClient->getServerName());
2469  Log(Message.getData());
2470  // Set up a dialog
2471  C4GUI::MessageDialog *pDlg = nullptr;
2472  if (!Application.isEditor)
2473  {
2474  // create & show
2476  if (!pDlg || !pDlg->Show(::pGUI, true)) return false;
2477  }
2478  // Wait for response
2479  while (pLeagueClient->isBusy())
2480  {
2481  // Execute GUI
2482  if (!Application.ScheduleProcs() ||
2483  (pDlg && pDlg->IsAborted()))
2484  {
2485  // Clear up
2486  if (pDlg) delete pDlg;
2487  return false;
2488  }
2489  // Check if league server has responded
2490  if (!pLeagueClient->Execute(0))
2491  break;
2492  }
2493  // Close dialog
2494  if (pDlg)
2495  {
2496  pDlg->Close(true);
2497  delete pDlg;
2498  }
2499 
2500  // Success?
2501  StdStrBuf AUID, AccountMaster, LoginToken; bool fUnregistered = false;
2502  if (pLeagueClient->GetAuthReply(&Message, &AUID, &AccountMaster, &fUnregistered, &LoginToken))
2503  {
2504 
2505  // Set AUID
2506  pInfo->SetAuthID(AUID.getData());
2507 
2508  // Remember login data; set or clear login token
2509  Config.Network.SetLeagueLoginData(pLeagueClient->getServerName(), pInfo->GetName(), Account.getData(), fRememberLogin ? LoginToken.getData() : "");
2510 
2511  // Show welcome message, if any
2512  bool fSuccess;
2513  if (Message.getLength())
2514  fSuccess = ::pGUI->ShowMessageModal(
2515  Message.getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
2517  else if (AccountMaster.getLength())
2518  fSuccess = ::pGUI->ShowMessageModal(
2519  FormatString(LoadResStr("IDS_MSG_LEAGUEPLAYERSIGNUPAS"), pInfo->GetName(), AccountMaster.getData(), pLeagueClient->getServerName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
2521  else
2522  fSuccess = ::pGUI->ShowMessageModal(
2523  FormatString(LoadResStr("IDS_MSG_LEAGUEPLAYERSIGNUP"), pInfo->GetName(), pLeagueClient->getServerName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
2525 
2526  // Approved?
2527  if (fSuccess)
2528  // Done
2529  return true;
2530  else
2531  // Sign-up was cancelled by user
2532  ::pGUI->ShowMessageModal(FormatString(LoadResStr("IDS_MSG_LEAGUESIGNUPCANCELLED"), pInfo->GetName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUP"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Notify);
2533 
2534  }
2535  else
2536  {
2537 
2538  // Authentification error
2539  LogF(LoadResStr("IDS_MSG_LEAGUESIGNUPERROR"), Message.getData());
2540  ::pGUI->ShowMessageModal(FormatString(LoadResStr("IDS_MSG_LEAGUESERVERMSG"), Message.getData()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPFAILED"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
2541  // after a league server error message, always fall-through to try again
2542  }
2543 
2544  // Try given account name as default next time
2545  if (AccountMaster.getLength())
2546  Account.Take(std::move(AccountMaster));
2547 
2548  // safety (modal dlg may have deleted network)
2549  if (!pLeagueClient) return false;
2550  }
2551 
2552 }
2553 
2555 {
2556 
2557  // Not possible?
2558  if (!pLeagueClient)
2559  return false;
2560 
2561  // Make sure league client is available
2563 
2564  // Ask league server to check the code
2565  if (!pLeagueClient->AuthCheck(*pInfo))
2566  return false;
2567 
2568  // Log
2569  StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_LEAGUEJOINING"), pInfo->GetName());
2570  Log(Message.getData());
2571 
2572  // Wait for response
2573  while (pLeagueClient->isBusy())
2574  if (!pLeagueClient->Execute(100))
2575  break;
2576 
2577  // Check response validity
2578  if (!pLeagueClient->isSuccess())
2579  {
2581  return false;
2582  }
2583 
2584  // Check if league server approves. pInfo will have league info if this call is successful.
2585  if (!pLeagueClient->GetAuthCheckReply(&Message, Game.Parameters.League.getData(), pInfo))
2586  {
2587  LeagueShowError(FormatString(LoadResStr("IDS_MSG_LEAGUEJOINREFUSED"), pInfo->GetName(), Message.getData()).getData());
2588  return false;
2589  }
2590 
2591  return true;
2592 }
2593 
2595 {
2596  // league active?
2597  if (!pLeagueClient || !Game.Parameters.isLeague()) return;
2598  // only in running game
2599  if (!Game.IsRunning || Game.GameOver) return;
2600  // clients send notifications for their own players; host sends for the affected client players
2601  if (!isHost()) { if (!Clients.GetLocal()) return; iClientID = Clients.GetLocal()->getID(); }
2602  // clients only need notifications if they have players in the game
2603  const C4ClientPlayerInfos *pInfos = Game.PlayerInfos.GetInfoByClientID(iClientID);
2604  if (!pInfos) return;
2605  int32_t i=0; C4PlayerInfo *pInfo;
2606  while ((pInfo = pInfos->GetPlayerInfo(i++))) if (pInfo->IsJoined() && !pInfo->IsRemoved()) break;
2607  if (!pInfo) return;
2608  // Make sure league client is avilable
2610  // report the disconnect!
2611  LogF(LoadResStr("IDS_LEAGUE_LEAGUEREPORTINGUNEXPECTED"), (int) eReason);
2612  pLeagueClient->ReportDisconnect(*pInfos, eReason);
2613  // wait for the reply
2615  // display it
2616  const char *szMsg;
2617  StdStrBuf sMessage;
2618  if (pLeagueClient->GetReportDisconnectReply(&sMessage))
2619  szMsg = LoadResStr("IDS_MSG_LEAGUEUNEXPECTEDDISCONNEC");
2620  else
2621  szMsg = LoadResStr("IDS_ERR_LEAGUEERRORREPORTINGUNEXP");
2622  LogF(szMsg, sMessage.getData());
2623 }
2624 
2626 {
2627  // league client busy?
2628  if (!pLeagueClient || !pLeagueClient->isBusy()) return;
2629  // wait for it
2630  Log(LoadResStr("IDS_LEAGUE_WAITINGFORLASTLEAGUESERVE"));
2631  while (pLeagueClient->isBusy())
2632  if (!pLeagueClient->Execute(100))
2633  break;
2634  // if last request was an update request, process it
2637 }
2638 
2640 {
2641  // there's currently no functionality to surrender in the league
2642  // just stop responding so other clients will notify the disconnect
2643  DeinitLeague();
2644 }
2645 
2646 void C4Network2::LeagueShowError(const char *szMsg)
2647 {
2648  if (!Application.isEditor)
2649  {
2650  ::pGUI->ShowErrorMessage(szMsg);
2651  }
2652  else
2653  {
2654  LogF(LoadResStr("IDS_LGA_SERVERFAILURE"), szMsg);
2655  }
2656 }
2657 
2658 void C4Network2::Vote(C4ControlVoteType eType, bool fApprove, int32_t iData)
2659 {
2660  // Original vote?
2661  if (!GetVote(C4ClientIDUnknown, eType, iData))
2662  {
2663  // Too fast?
2664  if (time(nullptr) < (time_t) (iLastOwnVoting + C4NetMinVotingInterval))
2665  {
2666  Log(LoadResStr("IDS_TEXT_YOUCANONLYSTARTONEVOTINGE"));
2667  if ((eType == VT_Kick && iData == Game.Clients.getLocalID()) || eType == VT_Cancel)
2668  OpenSurrenderDialog(eType, iData);
2669  return;
2670  }
2671  // Save timestamp
2672  iLastOwnVoting = time(nullptr);
2673  }
2674  // Already voted? Ignore
2675  if (GetVote(::Control.ClientID(), eType, iData))
2676  return;
2677  // Set pause mode if this is the host
2678  if (isHost() && isRunning())
2679  {
2680  Pause();
2681  fPausedForVote = true;
2682  }
2683  // send vote control
2684  ::Control.DoInput(CID_Vote, new C4ControlVote(eType, fApprove, iData), CDT_Direct);
2685 }
2686 
2688 {
2689  // Save back timestamp
2690  if (!Votes.firstPkt())
2691  iVoteStartTime = time(nullptr);
2692  // Save vote back
2693  Votes.Add(CID_Vote, new C4ControlVote(Vote));
2694  // Set pause mode if this is the host
2695  if (isHost() && isRunning())
2696  {
2697  Pause();
2698  fPausedForVote = true;
2699  }
2700  // Check if the dialog should be opened
2701  OpenVoteDialog();
2702 }
2703 
2704 C4IDPacket *C4Network2::GetVote(int32_t iClientID, C4ControlVoteType eType, int32_t iData)
2705 {
2706  C4ControlVote *pVote;
2707  for (C4IDPacket *pPkt = Votes.firstPkt(); pPkt; pPkt = Votes.nextPkt(pPkt))
2708  if (pPkt->getPktType() == CID_Vote)
2709  if ((pVote = static_cast<C4ControlVote *>(pPkt->getPkt())))
2710  if (iClientID == C4ClientIDUnknown || pVote->getByClient() == iClientID)
2711  if (pVote->getType() == eType && pVote->getData() == iData)
2712  return pPkt;
2713  return nullptr;
2714 }
2715 
2716 void C4Network2::EndVote(C4ControlVoteType eType, bool fApprove, int32_t iData)
2717 {
2718  // Remove all vote packets
2719  C4IDPacket *pPkt; int32_t iOrigin = C4ClientIDUnknown;
2720  while ((pPkt = GetVote(C4ClientIDAll, eType, iData)))
2721  {
2722  if (iOrigin == C4ClientIDUnknown)
2723  iOrigin = static_cast<C4ControlVote *>(pPkt->getPkt())->getByClient();
2724  Votes.Delete(pPkt);
2725  }
2726  // Reset timestamp
2727  iVoteStartTime = time(nullptr);
2728  // Approved own voting? Reset voting block
2729  if (fApprove && iOrigin == Game.Clients.getLocalID())
2730  iLastOwnVoting = 0;
2731  // Dialog open?
2732  if (pVoteDialog)
2733  if (pVoteDialog->getVoteType() == eType && pVoteDialog->getVoteData() == iData)
2734  {
2735  // close
2736  delete pVoteDialog;
2737  pVoteDialog = nullptr;
2738  }
2739  // Did we try to kick ourself? Ask if we'd like to surrender
2740  bool fCancelVote = (eType == VT_Kick && iData == Game.Clients.getLocalID()) || eType == VT_Cancel;
2741  if (!fApprove && fCancelVote && iOrigin == Game.Clients.getLocalID())
2742  OpenSurrenderDialog(eType, iData);
2743  // Check if the dialog should be opened
2744  OpenVoteDialog();
2745  // Pause/unpause voting?
2746  if (fApprove && eType == VT_Pause)
2747  fPausedForVote = !iData;
2748  // No voting left? Reset pause.
2749  if (!Votes.firstPkt())
2750  if (fPausedForVote)
2751  {
2752  Start();
2753  fPausedForVote = false;
2754  }
2755 }
2756 
2758 {
2759  // Dialog already open?
2760  if (pVoteDialog) return;
2761  // No vote available?
2762  if (!Votes.firstPkt()) return;
2763  // Can't vote?
2765  if (!pPlayerInfos || !pPlayerInfos->GetPlayerCount() || !pPlayerInfos->GetJoinedPlayerCount())
2766  return;
2767  // Search a voting we have to vote on
2768  for (C4IDPacket *pPkt = Votes.firstPkt(); pPkt; pPkt = Votes.nextPkt(pPkt))
2769  {
2770  // Already voted on this matter?
2771  C4ControlVote *pVote = static_cast<C4ControlVote *>(pPkt->getPkt());
2772  if (!GetVote(::Control.ClientID(), pVote->getType(), pVote->getData()))
2773  {
2774  // Compose message
2775  C4Client *pSrcClient = Game.Clients.getClientByID(pVote->getByClient());
2776  StdStrBuf Msg; Msg.Format(LoadResStr("IDS_VOTE_WANTSTOALLOW"), pSrcClient ? pSrcClient->getName() : "???", pVote->getDesc().getData());
2777  Msg.AppendChar('|');
2778  Msg.Append(pVote->getDescWarning());
2779 
2780  // Open dialog
2781  pVoteDialog = new C4VoteDialog(Msg.getData(), pVote->getType(), pVote->getData(), false);
2783  pVoteDialog->Show(::pGUI, true);
2784 
2785  break;
2786  }
2787  }
2788 }
2789 
2791 {
2792  if (!pVoteDialog)
2793  {
2794  pVoteDialog = new C4VoteDialog(
2795  LoadResStr("IDS_VOTE_SURRENDERWARNING"), eType, iData, true);
2797  pVoteDialog->Show(::pGUI, true);
2798  }
2799 }
2800 
2801 
2803 {
2804  pVoteDialog = nullptr;
2805 }
2806 
2807 
2808 // *** C4VoteDialog
2809 
2810 C4VoteDialog::C4VoteDialog(const char *szText, C4ControlVoteType eVoteType, int32_t iVoteData, bool fSurrender)
2811  : MessageDialog(szText, LoadResStr("IDS_DLG_VOTING"), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_Confirm, C4GUI::MessageDialog::dsRegular, nullptr, true),
2812  eVoteType(eVoteType), iVoteData(iVoteData), fSurrender(fSurrender)
2813 {
2814 
2815 }
2816 
2817 void C4VoteDialog::OnClosed(bool fOK)
2818 {
2819  bool fAbortGame = false;
2820  // notify that this object will be deleted shortly
2822  // Was league surrender dialog
2823  if (fSurrender)
2824  {
2825  // League surrender accepted
2826  if (fOK)
2827  {
2828  // set game leave reason, although round results dialog isn't showing it ATM
2829  Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, LoadResStr("IDS_ERR_YOUSURRENDEREDTHELEAGUEGA"));
2830  // leave game
2832  ::Network.Clear();
2833  // We have just league-surrendered. Abort the game - that is what we originally wanted.
2834  // Note: as we are losing league points and this is a relevant game, it would actually be
2835  // nice to show an evaluation dialog which tells us that we have lost and how many league
2836  // points we have lost. But until the evaluation dialog can actually do that, it is better
2837  // to abort completely.
2838  // Note2: The league dialog will never know that, because the game will usually not be over yet.
2839  // Scores are not calculated until after the game.
2840  fAbortGame = true;
2841  }
2842  }
2843  // Was normal vote dialog
2844  else
2845  {
2846  // Vote still active? Then vote.
2847  if (::Network.GetVote(C4ClientIDUnknown, eVoteType, iVoteData))
2848  ::Network.Vote(eVoteType, fOK, iVoteData);
2849  }
2850  // notify base class
2851  MessageDialog::OnClosed(fOK);
2852  // Abort game
2853  if (fAbortGame)
2854  Game.Abort(true);
2855 }
2856 
2857 
2858 /* Lobby countdown */
2859 
2860 void C4Network2::StartLobbyCountdown(int32_t iCountdownTime)
2861 {
2862  // abort previous
2863  if (pLobbyCountdown) AbortLobbyCountdown();
2864  // start new
2865  pLobbyCountdown = new C4GameLobby::Countdown(iCountdownTime);
2866 }
2867 
2869 {
2870  // aboert lobby countdown
2871  if (pLobbyCountdown)
2872  {
2873  pLobbyCountdown->Abort();
2874  delete pLobbyCountdown;
2875  pLobbyCountdown = nullptr;
2876  }
2877 }
2878 
2879 /* Streaming */
2880 
2882 {
2883  // Save back
2884  fStreaming = true;
2885  pStreamedRecord = pRecord;
2886  iLastStreamAttempt = time(nullptr);
2887 
2888  // Initialize compressor
2889  ZeroMem(&StreamCompressor, sizeof(StreamCompressor));
2890  if (deflateInit(&StreamCompressor, 9) != Z_OK)
2891  return false;
2892 
2893  // Create stream buffer
2894  StreamingBuf.New(C4NetStreamingMaxBlockSize);
2895  StreamCompressor.next_out = reinterpret_cast<BYTE*>(StreamingBuf.getMData());
2896  StreamCompressor.avail_out = C4NetStreamingMaxBlockSize;
2897 
2898  // Initialize HTTP client
2899  pStreamer = new C4Network2HTTPClient();
2900  if (!pStreamer->Init())
2901  return false;
2902  Application.Add(pStreamer);
2903 
2904  return true;
2905 }
2906 
2908 {
2909  if (!fStreaming) return false;
2910 
2911  // Stream
2912  StreamIn(true);
2913 
2914  // Reset record pointer
2915  pStreamedRecord = nullptr;
2916 
2917  // Try to get rid of remaining data immediately
2918  iLastStreamAttempt = 0;
2919  StreamOut();
2920 
2921  return true;
2922 }
2923 
2925 {
2926  if (!fStreaming) return false;
2927 
2928  // Clear
2929  Application.Remove(pStreamer);
2930  fStreaming = false;
2931  pStreamedRecord = nullptr;
2932  deflateEnd(&StreamCompressor);
2933  StreamingBuf.Clear();
2934  delete pStreamer;
2935  pStreamer = nullptr;
2936 
2937  // ... finalization?
2938  return true;
2939 }
2940 
2941 bool C4Network2::StreamIn(bool fFinish)
2942 {
2943  if (!pStreamedRecord) return false;
2944 
2945  // Get data from record
2946  const StdBuf &Data = pStreamedRecord->GetStreamingBuf();
2947  if (!fFinish)
2948  if (!Data.getSize() || !StreamCompressor.avail_out)
2949  return false;
2950 
2951  do
2952  {
2953 
2954  // Compress
2955  StreamCompressor.next_in = const_cast<BYTE *>(getBufPtr<BYTE>(Data));
2956  StreamCompressor.avail_in = Data.getSize();
2957  int ret = deflate(&StreamCompressor, fFinish ? Z_FINISH : Z_NO_FLUSH);
2958 
2959  // Anything consumed?
2960  unsigned int iInAmount = Data.getSize() - StreamCompressor.avail_in;
2961  if (iInAmount > 0)
2962  pStreamedRecord->ClearStreamingBuf(iInAmount);
2963 
2964  // Done?
2965  if (!fFinish || ret == Z_STREAM_END)
2966  break;
2967 
2968  // Error while finishing?
2969  if (ret != Z_OK)
2970  return false;
2971 
2972  // Enlarge buffer, if neccessary
2973  size_t iPending = getPendingStreamData();
2974  size_t iGrow = StreamingBuf.getSize();
2975  StreamingBuf.Grow(iGrow);
2976  StreamCompressor.avail_out += iGrow;
2977  StreamCompressor.next_out = getMBufPtr<BYTE>(StreamingBuf, iPending);
2978 
2979  }
2980  while (true);
2981 
2982  return true;
2983 }
2984 
2986 {
2987  // Streamer busy?
2988  if (!pStreamer || pStreamer->isBusy())
2989  return false;
2990 
2991  // Streamer done?
2992  if (pStreamer->isSuccess())
2993  {
2994 
2995  // Move new data to front of buffer
2996  if (getPendingStreamData() != iCurrentStreamAmount)
2997  StreamingBuf.Move(iCurrentStreamAmount, getPendingStreamData() - iCurrentStreamAmount);
2998 
2999  // Free buffer space
3000  StreamCompressor.next_out -= iCurrentStreamAmount;
3001  StreamCompressor.avail_out += iCurrentStreamAmount;
3002 
3003  // Advance stream
3004  iCurrentStreamPosition += iCurrentStreamAmount;
3005 
3006  // Get input
3007  StreamIn(false);
3008  }
3009 
3010  // Clear streamer
3011  pStreamer->Clear();
3012 
3013  // Record is still running?
3014  if (pStreamedRecord)
3015  {
3016 
3017  // Enough available to send?
3018  if (getPendingStreamData() < C4NetStreamingMinBlockSize)
3019  return false;
3020 
3021  // Overflow protection
3022  if (iLastStreamAttempt && iLastStreamAttempt + C4NetStreamingInterval >= time(nullptr))
3023  return false;
3024 
3025  }
3026  // All data finished?
3027  else if (!getPendingStreamData())
3028  {
3029  // Then we're done.
3030  StopStreaming();
3031  return false;
3032  }
3033 
3034  // Set stream address
3035  StdStrBuf StreamAddr;
3036  StreamAddr.Copy(Game.Parameters.StreamAddress);
3037  StreamAddr.AppendFormat("pos=%d&end=%d", iCurrentStreamPosition, !pStreamedRecord);
3038  pStreamer->SetServer(StreamAddr.getData());
3039 
3040  // Send data
3041  size_t iStreamAmount = getPendingStreamData();
3042  iCurrentStreamAmount = iStreamAmount;
3043  iLastStreamAttempt = time(nullptr);
3044  return pStreamer->Query(StdBuf(StreamingBuf.getData(), iStreamAmount), false);
3045 }
3046 
3048 {
3049  // Streaming must be active and there must still be anything to stream
3050  return fStreaming;
3051 }
char * GetFilename(char *szPath)
Definition: StdFile.cpp:45
void CheckStatusAck()
void RemoveDynamic()
const int32_t C4ClientIDAll
const char * GetInputText()
Definition: C4Gui.h:2421
bool isStreaming() const
bool InitNetwork(C4Network2ResList *pNetResList)
C4ControlVoteType getVoteType() const
Definition: C4Network2.h:402
const char * GetName() const
Definition: C4PlayerInfo.h:157
void SetObserver(bool fnObserver)
Definition: C4Client.h:65
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()
bool isHalfAccepted() const
Definition: C4Network2IO.h:288
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
const char * getDescription() const
Definition: C4Network2.cpp:68
void SetReference(class C4Network2Reference *pReference)
int32_t getByClient() const
Definition: C4Control.h:42
void OnClientDisconnect(C4Network2Client *pClient)
int32_t iLobbyTimeout
Definition: C4Game.h:120
C4Network2Client * GetLocal() const
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
void SetLocalID(int32_t iID)
Definition: C4Client.cpp:324
const int32_t & getClientID() const
Definition: C4Network2.h:443
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)
void RemoveConn(C4Network2IOConnection *pConn)
Definition: StdBuf.h:29
float Y
Definition: C4Facet.h:118
bool InitNetIO(bool fNoClientID, bool fHost)
const C4ClientCore & getLocalCore() const
Definition: C4Client.h:169
uint32_t iLastLeagueUpdate
Definition: C4Network2.h:165
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
void SetCtrlMode(int32_t iCtrlMode)
Definition: C4Network2.cpp:818
void Init(C4ClientList *pClientList, bool fHost)
C4IDPacket * firstPkt() const
Definition: C4Control.h:78
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()
int32_t getNextControlTick() const
void AddVote(const C4ControlVote &Vote)
bool isPasswordNeeded() const
int32_t ControlTick
Definition: C4GameControl.h:89
C4Console Console
Definition: C4Globals.cpp:45
bool LogSilent(const char *szMessage, bool fConsole)
Definition: C4Log.cpp:124
void SetGameStatus(const C4Network2Status &Status)
Definition: C4Network2.h:449
C4Network2Client * GetNextClient(C4Network2Client *pClient)
Definition: StdAdaptors.h:780
C4Client * getClient() const
bool SendMsg(C4NetIOPacket rPkt) const
C4Network2IO NetIO
Definition: C4Network2.h:110
void AbortLobbyCountdown()
bool LeaguePlrAuthCheck(C4PlayerInfo *pInfo)
StdStrBuf getDescWarning() const
Definition: C4Control.cpp:1704
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 char * getName() const
Definition: C4Client.h:69
bool IsRemoved() const
Definition: C4PlayerInfo.h:164
C4Game Game
Definition: C4Globals.cpp:52
void SetClientID(int32_t inClientID)
Definition: C4Network2.h:448
bool isAccepted() const
Definition: C4Network2IO.h:289
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
#define C4CFN_Material
Definition: C4Components.h:25
bool ThreadLogS(const char *szMessage,...) GNUC_FORMAT_ATTRIBUTE_O
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 C4Network2Status & getStatus() const
Definition: C4Network2.h:445
int32_t getVoteData() const
Definition: C4Network2.h:403
const int C4NetDeactivationDelay
Definition: C4Network2.h:42
bool isOK() const
Definition: C4Network2IO.h:383
bool IsAborted()
Definition: C4Gui.h:2149
bool isEnabled() const
Definition: C4Network2.h:203
void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2Client *pClient)
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
void Add(C4PacketType eType, C4ControlPacket *pCtrl)
Definition: C4Control.h:82
void AddAddrFromPuncher(const C4NetIO::addr_t &addr)
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)
bool Connect(const C4NetIO::addr_t &addr, C4Network2IOProtocol eProt, const C4ClientCore &nCCore, const char *szPassword=nullptr)
const StdBuf & GetStreamingBuf() const
Definition: C4Record.h:263
void Close(bool fOK)
C4ControlVoteType
Definition: C4Control.h:559
bool CreateDynamic(bool fInit)
const C4ClientCore & getCore() const
Definition: C4Client.h:104
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)
C4Client * getLocal() const
Definition: C4Client.h:161
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
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
StdStrBuf ToString(int flags=0) const
Definition: C4NetIO.cpp:604
const int32_t C4ClientCoreDL_IDMatch
Definition: C4Client.h:30
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:174
bool isConnected() const
C4NetIO * getNetClass() const
Definition: C4Network2IO.h:264
void HandleStatus(const C4Network2Status &nStatus)
bool C4Group_UnpackDirectory(const char *szFilename)
Definition: C4Group.cpp:306
bool fAllowJoin
Definition: C4Network2.h:130
StdCopyStrBuf MasterServerAddress
Definition: C4Network2.h:152
bool StreamIn(bool fFinish)
bool isLocal() const
const C4NetIO::addr_t & getConnectAddr() const
Definition: C4Network2IO.h:267
void SetCtrlMode(int32_t iCtrlMode)
Definition: C4Network2.cpp:86
C4Record * pStreamedRecord
Definition: C4Network2.h:189
void Execute()
Definition: C4Network2.cpp:648
bool isFrozen() const
#define GETPKT(type, name)
void OnStatusReached()
#define _MAX_PATH
C4ClientList Clients
#define a
void SetExclusiveConnMode(bool fExclusiveConn)
class C4ScenarioParameterDefs & ScenarioParameterDefs
Definition: C4Game.h:75
C4ClientPlayerInfos * GetIndexedInfo(int32_t iIndex) const
Definition: C4PlayerInfo.h:358
const char * getRevision() const
Definition: C4Client.h:72
bool IsReferenceNeeded()
const char * GetRevision() const
Definition: C4Application.h:72
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
C4NetpuncherID getNetpuncherGameID() const
C4GameParameters & Parameters
Definition: C4Game.h:67
void Add(StdSchedulerProc *pProc)
void SetDelOnClose(bool fToVal=true)
Definition: C4Gui.h:2190
class C4LeagueClient * pLeagueClient
Definition: C4Network2.h:169
const char * getServerName() const
int32_t getLocalID() const
Definition: C4Client.h:171
C4NetIO * MsgIO()
C4NetpuncherID getNetpuncherGameID() const
Definition: C4Network2.h:308
bool GetAuthReply(StdStrBuf *pMessage, StdStrBuf *pAUID, StdStrBuf *pAccount, bool *pRegister, StdStrBuf *pLoginToken)
Definition: C4League.cpp:425
bool isNetwork() const
Definition: C4GameControl.h:97
int32_t GetJoinedPlayerCount() const
bool IsShown()
Definition: C4Gui.h:2147
void HandleConn(const class C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient)
bool fOK
Definition: C4Gui.h:2082
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
bool IsJoined() const
Definition: C4PlayerInfo.h:166
bool isObserver() const
Definition: C4Client.h:60
void AppendFormat(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:190
int32_t ClientID() 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
void AppendChar(char cChar)
Definition: StdBuf.h:588
void AllowJoin(bool fAllow)
Definition: C4Network2.cpp:801
bool isHost() const
Definition: C4Client.h:58
bool FlushMessages()
Definition: C4AppSDL.cpp:115
~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
C4ControlVoteType getType() const
Definition: C4Control.h:580
C4Network2IOProtocol getProtocol() const
Definition: C4Network2IO.h:265
int getProtIRate(C4Network2IOProtocol eProt) const
Definition: C4Network2IO.h:153
void LeagueNotifyDisconnect(int32_t iClientID, enum C4LeagueDisconnectReason eReason)
void DeactivateInactiveClients()
bool Init(int32_t iClientID, C4Network2IO *pIOClass)
bool isActivated() const
Definition: C4Client.h:59
C4Network2IOConnection * getMsgConn() const
uint32_t getOutPacketCounter() const
Definition: C4Network2IO.h:284
bool Show(Screen *pOnScreen, bool fCB)
int32_t getAvgControlSendTime() const
bool ToggleClientListDlg()
Definition: C4Network2.cpp:765
void Punch(const C4NetIO::addr_t &)
int32_t ClientPerfStat(int32_t iClientID)
int32_t getTargetCtrlTick() const
Definition: C4Network2.h:82
bool isObserver() const
int32_t FrameCounter
Definition: C4Game.h:129
C4GUIScreen * pGUI
Definition: C4Gui.cpp:1191
void OpenVoteDialog()
bool isRemoved() const
const int32_t C4ClientIDStart
Definition: C4Client.h:26
const C4Network2ResCore & getCore() const
bool LogSilentF(const char *strMessage,...)
Definition: C4Log.cpp:270
C4IDPacket * nextPkt(C4IDPacket *pPkt) const
Definition: C4Control.h:79
void HandleJoinData(const class C4PacketJoinData &rPkt)
InitialConnect(const std::vector< C4Network2Address > &Addrs, const C4ClientCore &HostCore, const char *Password)
Definition: C4Network2.cpp:313
bool CheckAndReset()
Definition: StdScheduler.h:107
bool isNull() const
Definition: C4Network2Res.h:85
static std::vector< HostAddress > GetLocalAddresses()
Definition: C4NetIO.cpp:631
bool Close()
Definition: C4GameSave.cpp:447
C4Network2 Network
Definition: C4Globals.cpp:53
bool Pause()
Definition: C4Network2.cpp:513
void LeagueShowError(const char *szMsg)
C4Network2IOProtocol getNetIOProt(C4NetIO *pNetIO)
void OnConnectFail(C4Network2IOConnection *pConn)
int32_t getDiffLevel(const C4ClientCore &CCore2) const
Definition: C4Client.cpp:75
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)
bool hasJoinData() const
void SetName(const char *sznName)
Definition: C4Client.h:63
uint32_t getPacketCount() const
Definition: C4Network2IO.h:436
bool Init(int32_t iLocalClientID=C4ClientIDHost)
Definition: C4Client.cpp:243
int32_t iCtrlMode
Definition: C4Network2.h:76
C4Network2Res::Ref RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeout, const char *szResName, bool fWaitForCore=false)
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
bool isNull() const
Definition: StdBuf.h:441
bool ShowErrorMessage(const char *szMessage)
bool isLeague() const
const C4Network2Address & getAddr(int i) const
int getProtBCRate(C4Network2IOProtocol eProt) const
Definition: C4Network2IO.h:155
void CheckPortsForCollisions()
Definition: C4Config.cpp:600
int32_t getCtrlMode() const
Definition: C4Network2.h:81
C4GraphicsSystem GraphicsSystem
Definition: C4Globals.cpp:51
bool LeagueStart(bool *pCancel)
bool Open(const char *szGroupName, bool fCreate=false)
Definition: C4Group.cpp:514
size_t getSize() const
Definition: StdBuf.h:101
StdStrBuf getNetpuncherAddr() const
Definition: C4Network2.h:309
const char * AtNetworkPath(const char *szFilename)
Definition: C4Config.cpp:547
void SetStartCtrlTick(int32_t iTick)
Definition: C4Network2.h:451
const unsigned int C4NetMinVotingInterval
Definition: C4Network2.h:53
int32_t GetPlayerCount() const
Definition: C4PlayerInfo.h:251
uint32_t iLastReferenceUpdate
Definition: C4Network2.h:164
void SetDataConn(C4Network2IOConnection *pConn)
bool isPassworded() const
Definition: C4Network2.h:218
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
int32_t getStartCtrlTick() const
Definition: C4Network2.h:446
C4LeagueAction getCurrentAction() const
Definition: C4League.h:216
void Abort(bool fApproved=false)
Definition: C4Game.cpp:3667
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
bool C4Group_CopyItem(const char *szSource, const char *szTarget1, bool fNoSort, bool fResetAttributes)
Definition: C4Group.cpp:100
bool EraseDirectory(const char *szDirName)
Definition: StdFile.cpp:761
void OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
void Clear()
Definition: C4Control.cpp:85
bool isPasswordWrong() const
Definition: C4Network2IO.h:384
C4ConfigNetwork Network
Definition: C4Config.h:255
bool StartStreaming(C4Record *pRecord)
bool LeaguePlrAuth(C4PlayerInfo *pInfo)
class C4GameLobby::MainDlg * GetLobby() const
Definition: C4Network2.h:216
C4Network2ResCore ResDynamic
Definition: C4Network2.h:133
void HandleActivateReq(int32_t iTick, C4Network2Client *pClient)
void SetAcceptMode(bool fAcceptAll)
const int C4NetActivationReqInterval
Definition: C4Network2.h:40
const C4ClientCore & getCore() const
bool GetAuthCheckReply(StdStrBuf *pMessage, const char *szLeague, class C4PlayerInfo *pPlrInfo)
Definition: C4League.cpp:471
C4Network2Status Status
Definition: C4Network2.h:122
const char * getPassword() const
Definition: C4Network2IO.h:367
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 hasTCP() const
Definition: C4Network2IO.h:107
int32_t GetBehind(int32_t iTick) const
void HandleStatusAck(const C4Network2Status &nStatus, C4Network2Client *pClient)
const unsigned int C4NetChaseTargetUpdateInterval
Definition: C4Network2.h:45
const C4NetIO::addr_t & getPeerAddr() const
Definition: C4Network2IO.h:266
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)
C4Network2Res::Ref getRefRes(int32_t iResID)
void Value(const T &rStruct)
Definition: StdCompiler.h:161
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
bool isRunning() const
Definition: C4Network2.h:206
C4Network2Client * GetClientByID(int32_t iID) const
const char * getData() const
Definition: StdBuf.h:442
bool hasConn(C4Network2IOConnection *pConn)
bool FindTempResFileName(const char *szFilename, char *pTarget)
void EndVote(C4ControlVoteType eType, bool fApprove, int32_t iData)
C4NetIO * DataIO()
void LeagueSurrender()
int32_t getControlPreSend() const
int32_t LeagueServerSignUp
Definition: C4Config.h:151
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)
const char * getStateName() const
Definition: C4Network2.cpp:55
void Quit() override
void EvaluateLeague(const char *szResultMsg, bool fSuccess, const C4RoundResultsPlayers &rLeagueInfo)
bool FileExists(const char *szFileName)
int32_t iTargetCtrlTick
Definition: C4Network2.h:77
unsigned int GetStreamingPos() const
Definition: C4Record.h:262
int32_t MasterReferencePeriod
Definition: C4Config.h:150
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)
C4PlayerInfo * GetPlayerInfo(int32_t iIndex) const
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
C4Client * getHost() const
Definition: C4Client.h:163
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 GetLeagueProjectedGain() const
Definition: C4PlayerInfo.h:191
const C4ClientCore & getCCore() const
Definition: C4Network2IO.h:366
int GetScopeId() const
Definition: C4NetIO.cpp:296
C4Client * getClientByID(int32_t iID) const
Definition: C4Client.cpp:200
bool isHost() const
Definition: C4Network2.h:209
int32_t ClientNextControl(int32_t iClientID)
void DrawStatus(C4TargetFacet &cgo)
bool StreamOut()
void FlashMessage(const char *szMessage)
bool Start(const C4Network2Reference &Ref)
Definition: C4League.cpp:298
bool isHost() 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 FadeIn(Screen *pOnScreen)
void SetActivated(bool fnActivated)
Definition: C4Client.h:64
const C4NetIO::EndpointAddress & GetSourceAddress() const
StdStrBuf getDesc() const
Definition: C4Control.cpp:1675
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
const C4Network2ResCore & getDynamicCore() const
Definition: C4Network2.h:444
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
int getProtORate(C4Network2IOProtocol eProt) const
Definition: C4Network2IO.h:154
C4Network2Players Players
Definition: C4Network2.h:119
void OnGameSynchronized()
bool GetStartReply(StdStrBuf *pMessage, StdStrBuf *pLeague, StdStrBuf *pStreamingAddr, int32_t *pSeed, int32_t *pMaxPlayers)
Definition: C4League.cpp:312
void UpdateChaseTarget()
virtual const char * GetError() const
Definition: C4NetIO.h:285
StdStrBuf getNetpuncherAddr() const
bool Active
Definition: C4Window.h:274
bool Log(const char *szMessage)
Definition: C4Log.cpp:202
size_t getPendingStreamData() const
Definition: C4Network2.h:300
bool DoLobby()
Definition: C4Network2.cpp:432
C4ClientList & Clients
Definition: C4Game.h:69
const char * getMsg() const
Definition: C4Network2IO.h:385
const char * getName() const
int32_t NoRuntimeJoin
Definition: C4Config.h:142
EndpointAddress AsIPv4() const
Definition: C4NetIO.cpp:341
StdCopyStrBuf StreamAddress
bool fHost
Definition: C4Network2.h:127
bool GetReportDisconnectReply(StdStrBuf *pMessage)
Definition: C4League.cpp:500
int32_t getID() const
Definition: C4Client.h:57
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
const int C4NetMaxBehind4Activation
Definition: C4Network2.h:41
C4Control Votes
Definition: C4Network2.h:181
void OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection)
int32_t getID() const
Definition: C4Network2Res.h:86
bool CreatePostMortem(class C4PacketPostMortem *pPkt)
bool CtrlTickReached(int32_t iTick)
int32_t getData() const
Definition: C4Control.h:582
bool HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply)
bool ShowModalDlg(Dialog *pDlg, bool fDestruct=true)
C4ClientPlayerInfos * GetInfoByClientID(int32_t iClientID) const
Definition: C4PlayerInfo.h:361
bool FinishStreaming()
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
int32_t HaltCount
Definition: C4Game.h:112
bool Sync()
Definition: C4Network2.cpp:520
int32_t getVer() const
Definition: C4Network2IO.h:364
int32_t getID() const
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
bool StopStreaming()
const char * getFile() const
uint32_t iLastChaseTargetUpdate
Definition: C4Network2.h:158
bool HandlePuncherPacket(C4NetpuncherPacket::uptr, C4NetIO::HostAddress::AddressFamily family)
Definition: C4Network2.cpp:971
C4PacketBase * getPkt() const
Definition: C4PacketBase.h:258
const unsigned int C4NetVotingTimeout
Definition: C4Network2.h:52
C4Surface * Surface
Definition: C4Facet.h:117
bool InitPuncher(C4NetIO::addr_t PuncherAddr)
#define DirSep
void SetRunning(bool fnRunning, int32_t inTargetTick=-1)
bool isWaitedFor() const
StdCopyStrBuf LeagueAddress
int32_t GetID() const
Definition: C4PlayerInfo.h:194
void SetDefaultPort(uint16_t port)
Definition: C4NetIO.cpp:541
void RequestActivate()
const char * getNetIOName(C4NetIO *pNetIO)
bool isAutoAccepted() const
Definition: C4Network2IO.h:291
C4NetGameState eState
Definition: C4Network2.h:75
bool GetLeagueLoginData(const char *szServer, const char *szPlayerName, StdStrBuf *pAccount, StdStrBuf *pLoginToken) const
Definition: C4Config.cpp:627
const C4Network2ResCore * getResCore() const
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
void Copy()
Definition: StdBuf.h:467
size_t getLength() const
Definition: StdBuf.h:445
int32_t PortTCP
Definition: C4Config.h:153
C4Network2Client * GetClient(const char *szName) const
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 std::set< int > & getInterfaceIDs() const
void CheckStatusReached(bool fFromFinalInit=false)
StdStrBuf QueryClientPassword()
Definition: C4Network2.cpp:784
bool SetServer(const char *szServerAddress)
void AddLocalAddrs(int16_t iPortTCP, int16_t iPortUDP)
Definition: C4Network2IO.h:417
C4Application Application
Definition: C4Globals.cpp:44
C4InteractiveThread InteractiveThread
Definition: C4Application.h:45
void DeinitLeague()
bool AddAddr(const C4Network2Address &addr, bool fAnnounce)
bool hasUDP() const
Definition: C4Network2IO.h:108
const int16_t C4NetStdPortPuncher
Definition: C4Network2.h:33
void SetProgress(int32_t iToProgress)
Definition: C4Gui.h:2377
static C4TimeMilliseconds Now()
bool isLobbyActive() const
Definition: C4Network2.h:204
bool isLoading() const
void ChangeToLocal()
bool isActivated() const
C4GameParameters Parameters
Definition: C4Network2.h:440
int32_t ControlRate
Definition: C4GameControl.h:88
bool isChasing() const
void Add(C4Client *pClient)
Definition: C4Client.cpp:181
C4GameParameters Parameters
bool isActivated() const
Definition: C4Client.h:110
void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn)
Definition: C4Network2.cpp:878
bool isObserver() const
Definition: C4Client.h:111
int32_t getPresentPercent() const
bool Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply)
int32_t PortUDP
Definition: C4Config.h:153
const int32_t C4ClientIDHost
Definition: C4Client.h:25
class C4GameControlNetwork * pControl
Definition: C4Network2.h:144
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
C4VoteDialog(const char *szText, C4ControlVoteType eVoteType, int32_t iVoteData, bool fSurrender)
const C4NetIO::addr_t & getAddr() const
C4GameLobby::Countdown * pLobbyCountdown
Definition: C4Network2.h:149
bool fChasing
Definition: C4Network2.h:141
void SendAddresses(C4Network2IOConnection *pConn)
C4NetGameState getState() const
Definition: C4Network2.h:80
const char * getFileName() const
Definition: C4Network2Res.h:94