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