1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2006, PeterW
6 * Copyright (c) 2017-2022, The LegacyClonk Team and contributors
7 *
8 * Distributed under the terms of the ISC license; see accompanying file
9 * "COPYING" for details.
10 *
11 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12 * See accompanying file "TRADEMARK" for details.
13 *
14 * To redistribute this file separately, substitute the full license texts
15 * for the above references.
16 */
17
18/* engine handler of league system */
19
20#include "C4GuiEdit.h"
21#include "C4GuiResource.h"
22#include <C4Include.h>
23#include <C4League.h>
24
25#include <C4Game.h>
26#include <C4RoundResults.h>
27#include <C4Wrappers.h>
28
29// *** C4LeagueRequestHead
30
31void C4LeagueRequestHead::CompileFunc(StdCompiler *pComp)
32{
33 StdEnumEntry<C4LeagueAction> Actions[] =
34 {
35 { .Name: "Start", .Val: C4LA_Start },
36 { .Name: "Update", .Val: C4LA_Update },
37 { .Name: "End", .Val: C4LA_End },
38 { .Name: "Join", .Val: C4LA_PlrAuthCheck },
39
40 { .Name: "", .Val: C4LA_RefQuery },
41 { .Name: "Auth", .Val: C4LA_PlrAuth },
42
43 { .Name: "ReportDisconnect", .Val: C4LA_ReportDisconnect },
44 };
45
46 pComp->Value(rStruct: mkNamingAdapt(rValue: mkEnumAdaptT<uint8_t>(rVal&: eAction, pNames: Actions), szName: "Action", rDefault: C4LA_RefQuery));
47 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: CSID, rPar: StdCompiler::RCT_IdtfAllowEmpty), szName: "CSID", rDefault: ""));
48 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: AUID, rPar: StdCompiler::RCT_IdtfAllowEmpty), szName: "AUID", rDefault: ""));
49 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Checksum, rPar: StdCompiler::RCT_IdtfAllowEmpty), szName: "Checksum", rDefault: ""));
50
51 // Auth
52 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Account, rPar: StdCompiler::RCT_All), szName: "Account", rDefault: ""));
53 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Password, rPar: StdCompiler::RCT_All), szName: "Password", rDefault: ""));
54 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: NewAccount, rPar: StdCompiler::RCT_All), szName: "NewAccount", rDefault: ""));
55 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: NewPassword, rPar: StdCompiler::RCT_All), szName: "NewPassword", rDefault: ""));
56}
57
58void C4LeagueRequestHead::SetAuth(const char *szAccount, const char *szPassword)
59{
60 Account = szAccount;
61 Password = szPassword;
62}
63
64void C4LeagueRequestHead::SetNewAccount(const char *szNewAccount)
65{
66 NewAccount = szNewAccount;
67}
68
69void C4LeagueRequestHead::SetNewPassword(const char *szNewPassword)
70{
71 NewPassword = szNewPassword;
72}
73
74// *** C4LeagueReportDisconnectHead
75
76void C4LeagueReportDisconnectHead::CompileFunc(StdCompiler *pComp)
77{
78 // inherited fields
79 C4LeagueRequestHead::CompileFunc(pComp);
80 // reason
81 StdEnumEntry<C4LeagueDisconnectReason> Reasons[] =
82 {
83 { .Name: "", .Val: C4LDR_Unknown },
84 { .Name: "ConnectionFailed", .Val: C4LDR_ConnectionFailed },
85 { .Name: "Desync", .Val: C4LDR_Desync },
86 };
87 pComp->Value(rStruct: mkNamingAdapt(rValue: mkEnumAdaptT<uint8_t>(rVal&: eReason, pNames: Reasons), szName: "Reason", rDefault: C4LDR_Unknown));
88}
89
90// *** C4LeagueRequestHeadEnd
91
92void C4LeagueRequestHeadEnd::CompileFunc(StdCompiler *pComp)
93{
94 C4LeagueRequestHead::CompileFunc(pComp);
95 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: RecordName, rPar: StdCompiler::RCT_All), szName: "RecordName", rDefault: ""));
96 if (RecordName.getLength())
97 pComp->Value(rStruct: mkNamingAdapt(rValue: mkHexAdapt(pData: RecordSHA, iSize: sizeof(RecordSHA)), szName: "RecordSHA"));
98}
99
100// *** C4LeagueResponseHead
101
102void C4LeagueResponseHead::CompileFunc(StdCompiler *pComp)
103{
104 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Status, rPar: StdCompiler::RCT_IdtfAllowEmpty), szName: "Status", rDefault: ""));
105 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: CSID, rPar: StdCompiler::RCT_IdtfAllowEmpty), szName: "CSID", rDefault: ""));
106 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Message, rPar: StdCompiler::RCT_All), szName: "Message", rDefault: ""));
107
108 // Auth
109 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: Account, rPar: StdCompiler::RCT_All), szName: "Account", rDefault: ""));
110 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: AUID, rPar: StdCompiler::RCT_All), szName: "AUID", rDefault: ""));
111 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: FBID, rPar: StdCompiler::RCT_All), szName: "FBID", rDefault: ""));
112}
113
114// *** C4LeagueResponseHeadStart
115
116void C4LeagueResponseHeadStart::CompileFunc(StdCompiler *pComp)
117{
118 // Base members
119 C4LeagueResponseHead::CompileFunc(pComp);
120
121 // League name
122 pComp->Value(rStruct: mkNamingAdapt(rValue&: League, szName: "League", rDefault: ""));
123 pComp->Value(rStruct: mkNamingAdapt(rValue&: StreamingAddr, szName: "StreamTo", rDefault: ""));
124 pComp->Value(rStruct: mkNamingCountAdapt(iCount&: fHaveSeed, szName: "Seed"));
125 if (fHaveSeed)
126 pComp->Value(rStruct: mkNamingAdapt(rValue&: iSeed, szName: "Seed", rDefault: 0));
127 pComp->Value(rStruct: mkNamingAdapt(rValue&: iMaxPlayers, szName: "MaxPlayers", rDefault: 0));
128}
129
130// *** C4LeagueResponseHeadUpdate
131
132void C4LeagueResponseHeadUpdate::CompileFunc(StdCompiler *pComp)
133{
134 // Base members
135 C4LeagueResponseHead::CompileFunc(pComp);
136
137 // League name
138 pComp->Value(rStruct: mkNamingAdapt(rValue&: League, szName: "League", rDefault: ""));
139
140 // player infos
141 pComp->Value(rStruct: mkNamingAdapt(rValue&: PlrInfos, szName: "PlayerInfos"));
142}
143
144// *** C4LeagueRequestHeadAuthCheck
145
146int32_t C4LeagueResponseHeadAuthCheck::getScore(const char *szLeague) const
147{
148 for (int32_t i = 0; i < C4NetMaxLeagues; i++)
149 if (Leagues[i] == szLeague)
150 return Scores[i];
151 return 0;
152}
153
154int32_t C4LeagueResponseHeadAuthCheck::getRank(const char *szLeague) const
155{
156 for (int32_t i = 0; i < C4NetMaxLeagues; i++)
157 if (Leagues[i] == szLeague)
158 return Ranks[i];
159 return 0;
160}
161
162int32_t C4LeagueResponseHeadAuthCheck::getRankSymbol(const char *szLeague) const
163{
164 for (int32_t i = 0; i < C4NetMaxLeagues; i++)
165 if (Leagues[i] == szLeague)
166 return RankSymbols[i];
167 return 0;
168}
169
170const char *C4LeagueResponseHeadAuthCheck::getProgressData(const char *szLeague) const
171{
172 for (int32_t i = 0; i < C4NetMaxLeagues; i++)
173 if (Leagues[i] == szLeague)
174 return ProgressData[i].getData();
175 return nullptr;
176}
177
178void C4LeagueResponseHeadAuthCheck::CompileFunc(StdCompiler *pComp)
179{
180 // Base members
181 C4LeagueResponseHead::CompileFunc(pComp);
182
183 // Leagues, Scores, Ranks
184 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: Leagues, default_: ""), szName: "League"));
185 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: Scores, default_: 0), szName: "Score"));
186 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: Ranks, default_: 0), szName: "Rank"));
187 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: RankSymbols, default_: 0), szName: "RankSymbol"));
188 pComp->Value(rStruct: mkNamingAdapt(rValue: mkArrayAdapt(array&: ProgressData, default_: ""), szName: "ProgressData"));
189
190 // Clan tag
191 pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: ClanTag, rPar: StdCompiler::RCT_All), szName: "ClanTag", rDefault: ""));
192}
193
194// *** C4LeagueFBIDList
195
196void C4LeagueFBIDList::Clear()
197{
198 while (pFirst)
199 {
200 FBIDItem *pDel = pFirst;
201 pFirst = pDel->pNext;
202 delete pDel;
203 }
204}
205
206bool C4LeagueFBIDList::FindFBIDByAccount(const char *szAccount, StdStrBuf *pFBIDOut)
207{
208 assert(szAccount);
209 if (!szAccount) return false;
210 for (FBIDItem *pItem = pFirst; pItem; pItem = pItem->pNext)
211 if (pItem->Account == szAccount)
212 {
213 if (pFBIDOut) pFBIDOut->Copy(Buf2: pItem->FBID);
214 return true;
215 }
216 return false;
217}
218
219void C4LeagueFBIDList::RemoveFBIDByAccount(const char *szAccount)
220{
221 FBIDItem *pPrev = nullptr, *pItem = pFirst;
222 while (pItem)
223 {
224 // Delete?
225 if (pItem->Account == szAccount)
226 {
227 (pPrev ? pPrev->pNext : pFirst) = pItem->pNext;
228 delete pItem;
229 return;
230 }
231 // Next
232 pPrev = pItem; pItem = pItem->pNext;
233 }
234}
235
236void C4LeagueFBIDList::AddFBID(const char *szFBID, const char *szAccount)
237{
238 // add new FBID item to head of list
239 assert(szFBID); assert(szAccount);
240 // remove any existing FBIDs
241 RemoveFBIDByAccount(szAccount);
242 // add new entry
243 FBIDItem *pNewItem = new FBIDItem();
244 pNewItem->FBID.Copy(pnData: szFBID);
245 pNewItem->Account.Copy(pnData: szAccount);
246 pNewItem->pNext = pFirst;
247 pFirst = pNewItem;
248}
249
250class DisconnectData
251{
252private:
253 C4LeagueFBIDList &rFBIDList;
254 const C4ClientPlayerInfos &rPlayerInfos;
255
256public:
257 DisconnectData(C4LeagueFBIDList &rFBIDList, const C4ClientPlayerInfos &rPlayerInfos)
258 : rFBIDList(rFBIDList), rPlayerInfos(rPlayerInfos) {}
259
260 void CompileFunc(StdCompiler *pComp)
261 {
262 if (pComp->isCompiler())
263 {
264 // compiling not yet needed
265 assert(!"DisconnectData::CompileFunc not defined for compiler!");
266 }
267 else
268 {
269 // decompiling: Compile all joined and not removed player infos.
270 // Compile them even if they're not in the FBID-List, but omit
271 // the FBID (used for host message)
272 int32_t i = 0; C4PlayerInfo *pInfo;
273 while (pInfo = rPlayerInfos.GetPlayerInfo(iIndex: i++))
274 if (pInfo->IsJoined() && !pInfo->IsRemoved())
275 {
276 const auto name = pComp->Name(szName: "Player");
277 pComp->Value(rStruct: mkNamingAdapt(rValue: mkDecompileAdapt(rValue: pInfo->GetID()), szName: "ID"));
278 StdStrBuf sFBID;
279 if (rFBIDList.FindFBIDByAccount(szAccount: pInfo->getLeagueAccount(), pFBIDOut: &sFBID)) pComp->Value(rStruct: mkNamingAdapt(rValue: mkParAdapt(rObj&: sFBID, rPar: StdCompiler::RCT_IdtfAllowEmpty), szName: "FBID"));
280 }
281 }
282 }
283};
284
285// *** C4LeagueClient
286
287bool C4LeagueClient::Start(const C4Network2Reference &Ref)
288{
289 // Create query
290 eCurrAction = C4LA_Start;
291 C4LeagueRequestHead Head(eCurrAction);
292 Head.SetChecksum("-----");
293
294 std::string queryText{DecompileToBuf<StdCompilerINIWrite>(
295 SrcStruct: mkInsertAdapt(
296 rObj: mkNamingAdapt(rValue&: Head, szName: "Request"),
297 rIns: mkNamingAdapt(rValue: mkDecompileAdapt(rValue: Ref), szName: "Reference"),
298 fBefore: false))};
299 ModifyForChecksum(data&: queryText, replace: "-----");
300 // Perform query
301 return Query(data: queryText, binary: false);
302}
303
304bool C4LeagueClient::GetStartReply(StdStrBuf *pMessage, StdStrBuf *pLeague, StdStrBuf *pStreamingAddr, int32_t *pSeed, int32_t *pMaxPlayers)
305{
306 if (!isSuccess() || eCurrAction != C4LA_Start) return false;
307 // Parse response head
308 C4LeagueResponseHeadStart Head;
309 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue&: Head, szName: "Response"), SrcBuf: getResultString(), szName: "Start Reply"))
310 return false;
311 // Get message, league and seed
312 if (pMessage)
313 pMessage->Copy(pnData: Head.getMessage());
314 if (pLeague)
315 pLeague->Copy(pnData: Head.getLeague());
316 if (pStreamingAddr)
317 pStreamingAddr->Copy(pnData: Head.getStreamingAddr());
318 if (pSeed && Head.haveSeed())
319 *pSeed = Head.getSeed();
320 if (pMaxPlayers)
321 *pMaxPlayers = Head.getMaxPlayers();
322 // No success?
323 if (!Head.isSuccess())
324 return false;
325 // Got no CSID?
326 if (!Head.getCSID() || !*Head.getCSID())
327 {
328 if (pMessage)
329 pMessage->Copy(pnData: LoadResStr(id: C4ResStrTableKey::IDS_LGA_INVALIDRESPONSE3));
330 return false;
331 }
332 // So save back CSID
333 CSID = Head.getCSID();
334 return true;
335}
336
337bool C4LeagueClient::Update(const C4Network2Reference &Ref)
338{
339 assert(CSID.getLength());
340 // Create query
341 eCurrAction = C4LA_Update;
342 C4LeagueRequestHead Head(eCurrAction, CSID.getData());
343 Head.SetChecksum("-----");
344 std::string queryText{DecompileToBuf<StdCompilerINIWrite>(
345 SrcStruct: mkInsertAdapt(
346 rObj: mkNamingAdapt(rValue&: Head, szName: "Request"),
347 rIns: mkNamingAdapt(rValue: mkDecompileAdapt(rValue: Ref), szName: "Reference"),
348 fBefore: false))};
349 ModifyForChecksum(data&: queryText, replace: "-----");
350 // Perform query
351 return Query(data: queryText, binary: false);
352}
353
354bool C4LeagueClient::GetUpdateReply(StdStrBuf *pMessage, C4ClientPlayerInfos *pPlayerLeagueInfos)
355{
356 C4LeagueResponseHeadUpdate Reply;
357 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue&: Reply, szName: "Response"), SrcBuf: getResultString(), szName: "Update Reply"))
358 return false;
359 // Get message
360 if (pMessage)
361 pMessage->Copy(pnData: Reply.getMessage());
362 // get plr infos
363 if (pPlayerLeagueInfos)
364 *pPlayerLeagueInfos = Reply.GetPlrInfos();
365 // Success
366 return true;
367}
368
369bool C4LeagueClient::End(const C4Network2Reference &Ref, const char *szRecordName, const uint8_t *pRecordSHA)
370{
371 assert(CSID.getLength());
372 // Create query
373 eCurrAction = C4LA_End;
374 C4LeagueRequestHeadEnd Head(eCurrAction, CSID.getData(), szRecordName, pRecordSHA);
375 Head.SetChecksum("-----");
376 std::string queryText{DecompileToBuf<StdCompilerINIWrite>(
377 SrcStruct: mkInsertAdapt(
378 rObj: mkNamingAdapt(rValue&: Head, szName: "Request"),
379 rIns: mkNamingAdapt(rValue: mkDecompileAdapt(rValue: Ref), szName: "Reference"),
380 fBefore: false))};
381 ModifyForChecksum(data&: queryText, replace: "-----");
382 // Perform query
383 return Query(data: queryText, binary: false);
384}
385
386bool C4LeagueClient::GetEndReply(StdStrBuf *pMessage, C4RoundResultsPlayers *pRoundResults)
387{
388 // Parse response head
389 C4LeagueResponseHead Head;
390 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue&: Head, szName: "Response"), SrcBuf: getResultString(), szName: "End Reply"))
391 return false;
392 // Get message
393 if (pMessage)
394 pMessage->Copy(pnData: Head.getMessage());
395 if (pRoundResults)
396 CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue: mkNamingAdapt(rValue&: *pRoundResults, szName: "PlayerInfos"), szName: "Response"), SrcBuf: getResultString(), szName: "Round Results");
397 // Done
398 return Head.isSuccess();
399}
400
401bool C4LeagueClient::Auth(const C4PlayerInfo &PlrInfo, const char *szAccount, const char *szPassword, const char *szNewAccount, const char *szNewPassword)
402{
403 // Build header
404 eCurrAction = C4LA_PlrAuth;
405 C4LeagueRequestHead Head(eCurrAction);
406 Head.SetChecksum("-----");
407 Head.SetAuth(szAccount, szPassword);
408 if (szNewAccount)
409 Head.SetNewAccount(szNewAccount);
410 if (szNewPassword)
411 Head.SetNewPassword(szNewPassword);
412 // Create query
413 std::string queryText{DecompileToBuf<StdCompilerINIWrite>(
414 SrcStruct: mkInsertAdapt(
415 rObj: mkNamingAdapt(rValue&: Head, szName: "Request"),
416 rIns: mkNamingAdapt(rValue: mkDecompileAdapt(rValue: PlrInfo), szName: "PlrInfo"),
417 fBefore: false))};
418 ModifyForChecksum(data&: queryText, replace: "-----");
419 // Perform query
420 return Query(data: queryText, binary: false);
421}
422
423bool C4LeagueClient::GetAuthReply(StdStrBuf *pMessage, StdStrBuf *pAUID, StdStrBuf *pAccount, bool *pRegister)
424{
425 C4LeagueResponseHead Head;
426 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue&: Head, szName: "Response"), SrcBuf: getResultString(), szName: "Auth Reply"))
427 return false;
428 // Get message & account
429 if (pMessage)
430 pMessage->Copy(pnData: Head.getMessage());
431 if (pAccount)
432 pAccount->Copy(pnData: Head.getAccount());
433 if (pRegister)
434 *pRegister = Head.isStatusRegister();
435 // No success?
436 if (!Head.isSuccess())
437 return false;
438 // Check AUID
439 if (!Head.getAUID() || !*Head.getAUID())
440 {
441 pMessage->Ref(pnData: LoadResStr(id: C4ResStrTableKey::IDS_MSG_LEAGUESERVERREPLYWITHOUTA));
442 return false;
443 }
444 // Success
445 if (pAUID)
446 pAUID->Copy(pnData: Head.getAUID());
447 FBIDList.AddFBID(szFBID: Head.getFBID(), szAccount: Head.getAccount());
448 return true;
449}
450
451bool C4LeagueClient::AuthCheck(const C4PlayerInfo &PlrInfo)
452{
453 assert(CSID.getLength());
454 // Build header
455 eCurrAction = C4LA_PlrAuthCheck;
456 C4LeagueRequestHead Head(eCurrAction, CSID.getData(), PlrInfo.getAuthID());
457 Head.SetChecksum("-----");
458 // Create query
459 std::string queryText{DecompileToBuf<StdCompilerINIWrite>(
460 SrcStruct: mkInsertAdapt(
461 rObj: mkNamingAdapt(rValue&: Head, szName: "Request"),
462 rIns: mkNamingAdapt(rValue: mkDecompileAdapt(rValue: PlrInfo), szName: "PlrInfo"),
463 fBefore: false))};
464 ModifyForChecksum(data&: queryText, replace: "-----");
465 // Perform query
466 return Query(data: queryText, binary: false);
467}
468
469bool C4LeagueClient::GetAuthCheckReply(StdStrBuf *pMessage, const char *szLeague, C4PlayerInfo *pPlrInfo)
470{
471 // Parse response head
472 C4LeagueResponseHeadAuthCheck Head;
473 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue&: Head, szName: "Response"), SrcBuf: getResultString(), szName: "Auth Check Reply"))
474 return false;
475 // Get message and additional data
476 if (pMessage)
477 pMessage->Copy(pnData: Head.getMessage());
478 if (szLeague && pPlrInfo)
479 pPlrInfo->SetLeagueData(szAccount: Head.getAccount(), szNewClanTag: Head.getClanTag(), iScore: Head.getScore(szLeague), iRank: Head.getRank(szLeague), iRankSymbol: Head.getRankSymbol(szLeague), szProgressData: Head.getProgressData(szLeague));
480 return Head.isSuccess();
481}
482
483bool C4LeagueClient::ReportDisconnect(const C4ClientPlayerInfos &rFeedbackClient, C4LeagueDisconnectReason eReason)
484{
485 // Build header
486 eCurrAction = C4LA_ReportDisconnect;
487 C4LeagueReportDisconnectHead Head(CSID.getData(), eReason);
488 Head.SetChecksum("-----");
489 // Create query
490 std::string queryText{DecompileToBuf<StdCompilerINIWrite>(
491 SrcStruct: mkInsertAdapt(
492 rObj: mkNamingAdapt(rValue&: Head, szName: "Request"),
493 rIns: mkNamingAdapt(rValue: DisconnectData(FBIDList, rFeedbackClient), szName: "PlayerInfos"),
494 fBefore: false))};
495 ModifyForChecksum(data&: queryText, replace: "-----");
496 // Perform query
497 return Query(data: queryText, binary: false);
498}
499
500bool C4LeagueClient::GetReportDisconnectReply(StdStrBuf *pMessage)
501{
502 // Parse response head
503 C4LeagueResponseHead Head;
504 if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(TargetStruct: mkNamingAdapt(rValue&: Head, szName: "Response"), SrcBuf: getResultString(), szName: "Report Disconnect"))
505 return false;
506 // Get message
507 if (pMessage)
508 pMessage->Copy(pnData: Head.getMessage());
509 // Done
510 return Head.isSuccess();
511}
512
513void C4LeagueClient::ModifyForChecksum(std::string &data, const char *replace)
514{
515 char *const ptr{std::strstr(haystack: data.data(), needle: replace)};
516 ModifyForChecksum(pData: data.c_str(), iDataSize: data.size(), pReplace: ptr, iChecksum: 0x7A69, iCheckMask: 0xF0FF);
517}
518
519void C4LeagueClient::ModifyForChecksum(const void *pData, size_t iDataSize, char *pReplace, uint32_t iChecksum, uint32_t iCheckMask)
520{
521 // Base 64 table
522 const char Base64Tbl[] =
523 {
524 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
525 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
526 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
527 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', '-'
528 };
529 // Random start value
530 uint32_t iStart = rand() | rand() << 16;
531 StdSha1 sha1;
532 for (uint32_t i = 0; i < (1 << 30); i++)
533 {
534 // Write number
535 for (uint32_t j = 0; j < 5; j++)
536 pReplace[j] = Base64Tbl[((i ^ iStart) >> j * 5) & 63];
537 // Calculcate SHA
538 uint8_t sha[StdSha1::DigestLength];
539 sha1.Update(buffer: pData, len: iDataSize);
540 sha1.GetHash(result: sha);
541 // Correct checksum?
542 if (!((*reinterpret_cast<uint32_t *>(&sha) ^ iChecksum) & iCheckMask))
543 return;
544 sha1.Reset();
545 }
546}
547
548// *** C4LeagueSignupDialog
549
550C4LeagueSignupDialog::C4LeagueSignupDialog(const char *szPlayerName, const char *szLeagueName, const char *szLeagueServerName, const char *szAccountPref, const char *szPassPref, bool fWarnThirdParty, bool fRegister)
551 : C4GUI::Dialog(C4GUI_MessageDlgWdt, 100 /* will be resized as needed */, LoadResStr(id: C4ResStrTableKey::IDS_DLG_LEAGUESIGNUPON, args&: szLeagueServerName).c_str(), false), strPlayerName(szPlayerName, false)
552{
553 // get positions
554 C4GUI::ComponentAligner caMain(GetClientRect(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true);
555 // place icon
556 C4Rect rcIcon = caMain.GetFromLeft(C4GUI_IconWdt); rcIcon.Hgt = C4GUI_IconHgt;
557 C4GUI::Icon *pIcon = new C4GUI::Icon(rcIcon, C4GUI::Ico_Ex_League); AddElement(pChild: pIcon);
558 caMain.GetFromRight(C4GUI_IconWdt / 2);
559 // place message label
560 // use text with line breaks
561 const std::string msg{LoadResStrChoice(condition: fRegister, ifTrue: C4ResStrTableKey::IDS_MSG_LEAGUE_REGISTRATION, ifFalse: C4ResStrTableKey::IDS_MSG_PASSWORDFORPLAYER, args&: szPlayerName)};
562 StdStrBuf sMsgBroken;
563 int32_t iLabelHgt = C4GUI::GetRes()->TextFont.BreakMessage(szMsg: msg.c_str(), iWdt: caMain.GetInnerWidth(), pOut: &sMsgBroken, fCheckMarkup: true);
564 C4GUI::Label *pLblMessage = new C4GUI::Label(sMsgBroken.getData(), caMain.GetFromTop(iHgt: iLabelHgt), ALeft, C4GUI_MessageFontClr, &C4GUI::GetRes()->TextFont);
565 AddElement(pChild: pLblMessage);
566 // registering and no account pref available
567 if (fRegister && (!szAccountPref || !szAccountPref[0]))
568 // use player name as default for league name
569 szAccountPref = szPlayerName;
570 // place username input box
571 bool fSideEdits = true; int iCtrlHeight;
572 StdStrBuf sAccountTxt; sAccountTxt.Copy(pnData: LoadResStr(id: C4ResStrTableKey::IDS_CTL_LEAGUE_ACCOUNT));
573 C4GUI::LabeledEdit::GetControlSize(piWdt: nullptr, piHgt: &iCtrlHeight, szForText: sAccountTxt.getData(), pForFont: nullptr, fMultiline: fSideEdits);
574 AddElement(pChild: pEdtAccount = new C4GUI::LabeledEdit(caMain.GetFromTop(iHgt: iCtrlHeight), sAccountTxt.getData(), fSideEdits, szAccountPref));
575 // registering? Make password field optional
576 if (fRegister)
577 {
578 // place the checkbox
579 const char *szChkPasswordCaption = LoadResStr(id: C4ResStrTableKey::IDS_CTL_LEAGUE_CHK_PLRPW);
580 C4GUI::CheckBox::GetStandardCheckBoxSize(piWdt: nullptr, piHgt: &iCtrlHeight, szForCaptionText: szChkPasswordCaption, pUseFont: nullptr);
581 AddElement(pChild: pChkPassword = new C4GUI::CheckBox(caMain.GetFromTop(iHgt: iCtrlHeight), szChkPasswordCaption, false));
582 pChkPassword->SetOnChecked(new C4GUI::CallbackHandlerNoPar<C4LeagueSignupDialog>(this, &C4LeagueSignupDialog::OnChkPassword));
583 pChkPassword->SetToolTip(LoadResStr(id: C4ResStrTableKey::IDS_DESC_LEAGUECHECKPASSWORD));
584 // place password edit boxes
585 C4GUI::ComponentAligner caTemp = caMain;
586 const char *szEdtPassCaption = LoadResStr(id: C4ResStrTableKey::IDS_CTL_LEAGUE_PLRPW);
587 const char *szEdtPass2Caption = LoadResStr(id: C4ResStrTableKey::IDS_CTL_LEAGUE_PLRPW2);
588 C4GUI::LabeledEdit::GetControlSize(piWdt: nullptr, piHgt: &iCtrlHeight, szForText: szEdtPassCaption, pForFont: nullptr, fMultiline: fSideEdits);
589 AddElement(pChild: pEdtPass = new C4GUI::LabeledEdit(caTemp.GetFromTop(iHgt: iCtrlHeight), szEdtPassCaption, fSideEdits, szPassPref));
590 AddElement(pChild: pEdtPass2 = new C4GUI::LabeledEdit(caTemp.GetFromTop(iHgt: iCtrlHeight), szEdtPass2Caption, fSideEdits, szPassPref));
591 // hide them
592 pEdtPass->SetVisibility(false);
593 pEdtPass2->SetVisibility(false);
594 // save how much to move the controls later
595 iEdtPassSpace = caTemp.GetHeight() - caMain.GetHeight();
596 }
597 else
598 {
599 // No password checkbox
600 pChkPassword = nullptr;
601 // But a password edit box
602 const char *szEdtPassCaption = LoadResStr(id: C4ResStrTableKey::IDS_CTL_LEAGUE_PLRPW);
603 C4GUI::LabeledEdit::GetControlSize(piWdt: nullptr, piHgt: &iCtrlHeight, szForText: szEdtPassCaption, pForFont: nullptr, fMultiline: fSideEdits);
604 AddElement(pChild: pEdtPass = new C4GUI::LabeledEdit(caMain.GetFromTop(iHgt: iCtrlHeight), szEdtPassCaption, fSideEdits, szPassPref));
605 // No second password edit box
606 pEdtPass2 = nullptr;
607 }
608 // Set password box options
609 pEdtPass->GetEdit()->SetPasswordMask('*');
610 if (pEdtPass2)
611 {
612 pEdtPass2->GetEdit()->SetPasswordMask('*');
613 }
614 // place confirmation buttons
615 C4GUI::ComponentAligner caButtonArea(caMain.GetFromTop(C4GUI_ButtonAreaHgt), 0, 0);
616 C4Rect rcBtn = caButtonArea.GetCentered(iWdt: 2 * C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace, C4GUI_ButtonHgt);
617 rcBtn.Wdt = C4GUI_DefButton2Wdt;
618 pBtnOK = C4GUI::newOKButton(bounds: rcBtn);
619 AddElement(pChild: pBtnOK);
620 rcBtn.x += C4GUI_DefButton2Wdt + C4GUI_DefButton2HSpace;
621 pBtnAbort = C4GUI::newCancelButton(bounds: rcBtn);
622 AddElement(pChild: pBtnAbort);
623 // resize to actually needed size
624 SetClientSize(iToWdt: GetClientRect().Wdt, iToHgt: GetClientRect().Hgt - caMain.GetHeight());
625 // initial focus
626 SetFocus(pCtrl: fRegister ? pEdtAccount->GetEdit() : pEdtPass->GetEdit(), fByMouse: false);
627}
628
629void C4LeagueSignupDialog::UserClose(bool fOK)
630{
631 // Abort? That's always okay
632 if (!fOK)
633 {
634 Dialog::UserClose(fOK);
635 Game.pGUI->ShowMessageModal(szMessage: LoadResStr(id: C4ResStrTableKey::IDS_MSG_LEAGUESIGNUPCANCELLED, args: strPlayerName.getData()).c_str(), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_LEAGUESIGNUP), dwButtons: C4GUI::MessageDialog::btnOK, icoIcon: C4GUI::Ico_Notify);
636 return;
637 }
638 // Check for empty account name
639 const char *szAccount = pEdtAccount->GetText();
640 if (!szAccount || !*szAccount)
641 {
642 SetFocus(pCtrl: pEdtAccount->GetEdit(), fByMouse: false);
643 Game.pGUI->ShowMessageModal(szMessage: LoadResStr(id: C4ResStrTableKey::IDS_MSG_LEAGUEMISSINGUSERNAME), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_INVALIDENTRY), dwButtons: C4GUI::MessageDialog::btnOK, icoIcon: C4GUI::Ico_Error);
644 return;
645 }
646 // Username contains invalid characters
647 if (SCharCountEx(szString: szAccount, C4League_Name_Valid_Characters) != SLen(sptr: szAccount))
648 {
649 SetFocus(pCtrl: pEdtAccount->GetEdit(), fByMouse: false);
650 Game.pGUI->ShowMessageModal(szMessage: LoadResStr(id: C4ResStrTableKey::IDS_MSG_LEAGUEINVALIDUSERNAME), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_INVALIDENTRY), dwButtons: C4GUI::MessageDialog::btnOK, icoIcon: C4GUI::Ico_Error);
651 return;
652 }
653 // Username is too short
654 if (SLen(sptr: szAccount) < 3)
655 {
656 SetFocus(pCtrl: pEdtAccount->GetEdit(), fByMouse: false);
657 Game.pGUI->ShowMessageModal(szMessage: LoadResStr(id: C4ResStrTableKey::IDS_MSG_LEAGUEUSERNAMETOOSHORT), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_INVALIDENTRY), dwButtons: C4GUI::MessageDialog::btnOK, icoIcon: C4GUI::Ico_Error);
658 return;
659 }
660 // Check password
661 if (!pChkPassword || pChkPassword->GetChecked())
662 {
663 // Check for empty password
664 const char *szPassword = pEdtPass->GetText();
665 if (!szPassword || !*szPassword)
666 {
667 SetFocus(pCtrl: pEdtPass->GetEdit(), fByMouse: false);
668 Game.pGUI->ShowMessageModal(szMessage: LoadResStr(id: C4ResStrTableKey::IDS_MSG_LEAGUEMISSINGPASSWORD), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_INVALIDENTRY), dwButtons: C4GUI::MessageDialog::btnOK, icoIcon: C4GUI::Ico_Error);
669 return;
670 }
671 // Check second password
672 if (pEdtPass2 && !SEqual(szStr1: szPassword, szStr2: pEdtPass2->GetText()))
673 {
674 SetFocus(pCtrl: pEdtPass2->GetEdit(), fByMouse: false);
675 pEdtPass2->GetEdit()->SetText(text: "", fUser: false);
676 Game.pGUI->ShowMessageModal(szMessage: LoadResStr(id: C4ResStrTableKey::IDS_MSG_LEAGUEMISMATCHPASSWORD), szCaption: LoadResStr(id: C4ResStrTableKey::IDS_DLG_INVALIDENTRY), dwButtons: C4GUI::MessageDialog::btnOK, icoIcon: C4GUI::Ico_Error);
677 return;
678 }
679 }
680 // Okay then
681 Dialog::UserClose(fOK);
682}
683
684bool C4LeagueSignupDialog::ShowModal(const char *szPlayerName, const char *szLeagueName, const char *szLeagueServerName, StdStrBuf *psAccount, StdStrBuf *psPass, bool fWarnThirdParty, bool fRegister)
685{
686 // show league signup dlg modally; return whether user pressed OK and change user and pass buffers in that case
687 assert(psAccount); assert(psPass);
688 if (!psAccount || !psPass || !Game.pGUI) return false;
689 C4LeagueSignupDialog *pDlg = new C4LeagueSignupDialog(szPlayerName, szLeagueName, szLeagueServerName, psAccount->getData(), psPass->getData(), fWarnThirdParty, fRegister);
690 bool fResult = Game.pGUI->ShowModalDlg(pDlg, fDestruct: false);
691 if (fResult)
692 {
693 psAccount->Copy(pnData: pDlg->GetAccount());
694 if (pDlg->HasPass())
695 psPass->Copy(pnData: pDlg->GetPass());
696 else
697 psPass->Clear();
698 }
699 delete pDlg;
700 return fResult;
701}
702
703void C4LeagueSignupDialog::OnChkPassword()
704{
705 if (!pChkPassword) return;
706 // Show password elements?
707 if (!pChkPassword->GetChecked())
708 {
709 // Enlarge dialog
710 C4Rect bnds = GetClientRect();
711 SetClientSize(iToWdt: bnds.Wdt, iToHgt: bnds.Hgt + iEdtPassSpace);
712 // Show edit controls
713 pEdtPass->SetVisibility(false);
714 pEdtPass2->SetVisibility(false);
715 // Move controls down
716 bnds = pBtnOK->GetBounds();
717 pBtnOK->SetPos(iXPos: bnds.x, iYPos: bnds.y + iEdtPassSpace);
718 bnds = pBtnAbort->GetBounds();
719 pBtnAbort->SetPos(iXPos: bnds.x, iYPos: bnds.y + iEdtPassSpace);
720 }
721 else
722 {
723 // Shrink dialog
724 C4Rect bnds = GetClientRect();
725 SetClientSize(iToWdt: bnds.Wdt, iToHgt: bnds.Hgt - iEdtPassSpace);
726 // Hide edit controls
727 pEdtPass->SetVisibility(true);
728 pEdtPass2->SetVisibility(true);
729 // Move controls down
730 bnds = pBtnOK->GetBounds();
731 pBtnOK->SetPos(iXPos: bnds.x, iYPos: bnds.y - iEdtPassSpace);
732 bnds = pBtnAbort->GetBounds();
733 pBtnAbort->SetPos(iXPos: bnds.x, iYPos: bnds.y - iEdtPassSpace);
734 }
735}
736
737const char *C4LeagueSignupDialog::GetAccount()
738{
739 return pEdtAccount->GetText();
740}
741
742const char *C4LeagueSignupDialog::GetPass()
743{
744 return pEdtPass->GetText();
745}
746
747bool C4LeagueSignupDialog::HasPass()
748{
749 return !pChkPassword || pChkPassword->GetChecked();
750}
751