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