1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2018, The OpenClonk Team and contributors
6 * Copyright (c) 2017-2020, 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#include "C4Include.h"
19
20#include "C4Game.h"
21#include "C4Network2Res.h"
22#include "C4Version.h"
23#include "C4GameLobby.h"
24
25// constants
26
27// workaround
28template <class T> struct unpack_class
29{
30 static C4PacketBase *unpack(StdCompiler *pComp)
31 {
32 assert(pComp->isCompiler());
33 T *pPkt = new T();
34 try
35 {
36 pComp->Value(*pPkt);
37 }
38 catch (...)
39 {
40 delete pPkt;
41 throw;
42 }
43 return pPkt;
44 }
45};
46
47#define PKT_UNPACK(T) unpack_class<T>::unpack
48
49const C4PktHandlingData PktHandlingData[] =
50{
51 // C4Network2IO (network thread)
52 { .ID: PID_Conn, .Class: PC_Network, .Name: "Connection Request", .AcceptedOnly: true, .ProcByThread: true, .HandlerID: PH_C4Network2IO, PKT_UNPACK(C4PacketConn) },
53 { .ID: PID_ConnRe, .Class: PC_Network, .Name: "Connection Request Reply", .AcceptedOnly: true, .ProcByThread: true, .HandlerID: PH_C4Network2IO, PKT_UNPACK(C4PacketConnRe) },
54
55 { .ID: PID_Ping, .Class: PC_Network, .Name: "Ping", .AcceptedOnly: true, .ProcByThread: true, .HandlerID: PH_C4Network2IO, PKT_UNPACK(C4PacketPing) },
56 { .ID: PID_Pong, .Class: PC_Network, .Name: "Pong", .AcceptedOnly: true, .ProcByThread: true, .HandlerID: PH_C4Network2IO, PKT_UNPACK(C4PacketPing) },
57
58 { .ID: PID_FwdReq, .Class: PC_Network, .Name: "Forward Request", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4Network2IO, PKT_UNPACK(C4PacketFwd) },
59 { .ID: PID_Fwd, .Class: PC_Network, .Name: "Forward", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4Network2IO, PKT_UNPACK(C4PacketFwd) },
60
61 { .ID: PID_PostMortem, .Class: PC_Network, .Name: "Post Mortem", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4Network2IO, PKT_UNPACK(C4PacketPostMortem) },
62
63 // C4Network2 (main thread)
64 { .ID: PID_Conn, .Class: PC_Network, .Name: "Connection Request", .AcceptedOnly: true, .ProcByThread: false, .HandlerID: PH_C4Network2, PKT_UNPACK(C4PacketConn) },
65 { .ID: PID_ConnRe, .Class: PC_Network, .Name: "Connection Request Reply", .AcceptedOnly: true, .ProcByThread: false, .HandlerID: PH_C4Network2, PKT_UNPACK(C4PacketConnRe) },
66
67 { .ID: PID_Status, .Class: PC_Network, .Name: "Game Status", .AcceptedOnly: true, .ProcByThread: false, .HandlerID: PH_C4Network2, PKT_UNPACK(C4Network2Status) },
68 { .ID: PID_StatusAck, .Class: PC_Network, .Name: "Game Status Acknowledgement", .AcceptedOnly: true, .ProcByThread: false, .HandlerID: PH_C4Network2, PKT_UNPACK(C4Network2Status) },
69
70 { .ID: PID_ClientActReq, .Class: PC_Network, .Name: "Client Activation Request", .AcceptedOnly: false, .ProcByThread: false, .HandlerID: PH_C4Network2, PKT_UNPACK(C4PacketActivateReq) },
71
72 { .ID: PID_JoinData, .Class: PC_Network, .Name: "Join Data", .AcceptedOnly: false, .ProcByThread: false, .HandlerID: PH_C4Network2, PKT_UNPACK(C4PacketJoinData) },
73 { .ID: PID_ReadyCheck, .Class: PC_Network, .Name: "Ready Check", .AcceptedOnly: false, .ProcByThread: false, .HandlerID: PH_C4Network2, PKT_UNPACK(C4PacketReadyCheck) },
74
75 // C4Network2PlayerList (main thread)
76 { .ID: PID_PlayerInfoUpdReq, .Class: PC_Network, .Name: "Player info update request", .AcceptedOnly: true, .ProcByThread: false, .HandlerID: PH_C4Network2Players, PKT_UNPACK(C4PacketPlayerInfoUpdRequest) },
77
78 { .ID: PID_LeagueRoundResults, .Class: PC_Network, .Name: "League round results", .AcceptedOnly: true, .ProcByThread: false, .HandlerID: PH_C4Network2Players, PKT_UNPACK(C4PacketLeagueRoundResults) },
79
80 // C4GameLobby (main thread)
81 { .ID: PID_LobbyCountdown, .Class: PC_Network, .Name: "Lobby countdown", .AcceptedOnly: false, .ProcByThread: false, .HandlerID: PH_C4GUIMainDlg, PKT_UNPACK(C4GameLobby::C4PacketCountdown) },
82
83 // C4Network2ClientList (main thread)
84 { .ID: PID_Addr, .Class: PC_Network, .Name: "Client Address", .AcceptedOnly: false, .ProcByThread: false, .HandlerID: PH_C4Network2ClientList, PKT_UNPACK(C4PacketAddr) },
85 { .ID: PID_TCPSimOpen, .Class: PC_Network, .Name: "TCP simultaneous open req", .AcceptedOnly: false, .ProcByThread: false, .HandlerID: PH_C4Network2ClientList, PKT_UNPACK(C4PacketTCPSimOpen) },
86
87 // C4Network2ResList (network thread)
88 { .ID: PID_NetResDis, .Class: PC_Network, .Name: "Resource Discover", .AcceptedOnly: true, .ProcByThread: true, .HandlerID: PH_C4Network2ResList, PKT_UNPACK(C4PacketResDiscover) },
89 { .ID: PID_NetResStat, .Class: PC_Network, .Name: "Resource Status", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4Network2ResList, PKT_UNPACK(C4PacketResStatus) },
90 { .ID: PID_NetResDerive, .Class: PC_Network, .Name: "Resource Derive", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4Network2ResList, PKT_UNPACK(C4Network2ResCore) },
91 { .ID: PID_NetResReq, .Class: PC_Network, .Name: "Resource Request", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4Network2ResList, PKT_UNPACK(C4PacketResRequest) },
92 { .ID: PID_NetResData, .Class: PC_Network, .Name: "Resource Data", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4Network2ResList, PKT_UNPACK(C4Network2ResChunk) },
93
94 // C4GameControlNetwork (network thread)
95 { .ID: PID_Control, .Class: PC_Network, .Name: "Control", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4GameControlNetwork, PKT_UNPACK(C4GameControlPacket) },
96 { .ID: PID_ControlReq, .Class: PC_Network, .Name: "Control Request", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: PH_C4GameControlNetwork, PKT_UNPACK(C4PacketControlReq) },
97 // main thread
98 { .ID: PID_ControlPkt, .Class: PC_Network, .Name: "Control Paket", .AcceptedOnly: false, .ProcByThread: false, .HandlerID: PH_C4GameControlNetwork, PKT_UNPACK(C4PacketControlPkt) },
99 { .ID: PID_ExecSyncCtrl, .Class: PC_Network, .Name: "Execute Sync Control", .AcceptedOnly: false, .ProcByThread: false, .HandlerID: PH_C4GameControlNetwork, PKT_UNPACK(C4PacketExecSyncCtrl) },
100
101 // Control (Isn't send over network, handled only as part of a control list)
102 { .ID: CID_ClientJoin, .Class: PC_Control, .Name: "Client Join", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlClientJoin) },
103 { .ID: CID_ClientUpdate, .Class: PC_Control, .Name: "Client Update", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlClientUpdate) },
104 { .ID: CID_ClientRemove, .Class: PC_Control, .Name: "Client Remove", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlClientRemove) },
105 { .ID: CID_Vote, .Class: PC_Control, .Name: "Voting", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlVote) },
106 { .ID: CID_VoteEnd, .Class: PC_Control, .Name: "Voting End", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlVoteEnd) },
107 { .ID: CID_SyncCheck, .Class: PC_Control, .Name: "Sync Check", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlSyncCheck) },
108 { .ID: CID_Synchronize, .Class: PC_Control, .Name: "Synchronize", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlSynchronize) },
109 { .ID: CID_Set, .Class: PC_Control, .Name: "Set", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlSet) },
110 { .ID: CID_Script, .Class: PC_Control, .Name: "Script", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlScript) },
111 { .ID: CID_PlrInfo, .Class: PC_Control, .Name: "Player Info", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlPlayerInfo) },
112 { .ID: CID_JoinPlr, .Class: PC_Control, .Name: "Join Player", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlJoinPlayer) },
113 { .ID: CID_RemovePlr, .Class: PC_Control, .Name: "Remove Player", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlRemovePlr) },
114 { .ID: CID_PlrSelect, .Class: PC_Control, .Name: "Player Select", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlPlayerSelect) },
115 { .ID: CID_PlrControl, .Class: PC_Control, .Name: "Player Control", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlPlayerControl) },
116 { .ID: CID_PlrCommand, .Class: PC_Control, .Name: "Player Command", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlPlayerCommand) },
117 { .ID: CID_Message, .Class: PC_Control, .Name: "Message", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlMessage) },
118 { .ID: CID_EMMoveObj, .Class: PC_Control, .Name: "EM Move Obj", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlEMMoveObject) },
119 { .ID: CID_EMDrawTool, .Class: PC_Control, .Name: "EM Draw Tool", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlEMDrawTool) },
120 { .ID: CID_EMDropDef, .Class: PC_Control, .Name: "EM Drop Def", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlEMDropDef) },
121
122 { .ID: CID_MessageBoardAnswer, .Class: PC_Control, .Name: "Message Board Answer", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlMessageBoardAnswer) },
123 { .ID: CID_CustomCommand, .Class: PC_Control, .Name: "Custom Command", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlCustomCommand) },
124 { .ID: CID_InitScenarioPlayer, .Class: PC_Control, .Name: "Init Scenario Player", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlInitScenarioPlayer) },
125 { .ID: CID_ActivateGameGoalMenu, .Class: PC_Control, .Name: "Activate Game Goal Menu", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlActivateGameGoalMenu) },
126 { .ID: CID_ToggleHostility, .Class: PC_Control, .Name: "Toggle Hostility", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlToggleHostility) },
127 { .ID: CID_SurrenderPlayer, .Class: PC_Control, .Name: "Surrender Player", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlSurrenderPlayer) },
128 { .ID: CID_ActivateGameGoalRule, .Class: PC_Control, .Name: "Activate Game Goal/Rule", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlActivateGameGoalRule) },
129 { .ID: CID_SetPlayerTeam, .Class: PC_Control, .Name: "Set Player Team", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlSetPlayerTeam) },
130 { .ID: CID_EliminatePlayer, .Class: PC_Control, .Name: "Eliminate Player", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlEliminatePlayer) },
131
132 { .ID: CID_DebugRec, .Class: PC_Control, .Name: "Debug Rec", .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, PKT_UNPACK(C4ControlDebugRec) },
133
134 // EOL
135 { .ID: PID_None, .Class: PC_Network, .Name: nullptr, .AcceptedOnly: false, .ProcByThread: true, .HandlerID: 0, .FnUnpack: nullptr }
136};
137
138// C4PacketBase
139
140C4NetIOPacket C4PacketBase::pack(uint8_t cStatus, const C4NetIO::addr_t &addr) const
141{
142 return C4NetIOPacket(DecompileToBuf<StdCompilerBinWrite>(SrcStruct: mkInsertAdapt(rObj: mkDecompileAdapt(rValue: *this), rIns&: cStatus)), addr);
143}
144
145void C4PacketBase::unpack(const C4NetIOPacket &Pkt, char *pStatus)
146{
147 if (pStatus) *pStatus = Pkt.getStatus();
148 CompileFromBuf<StdCompilerBinRead>(TargetStruct&: *this, SrcBuf: pStatus ? Pkt.getPBuf() : Pkt.getRef());
149}
150
151// C4IDPacket
152
153C4IDPacket::C4IDPacket()
154 : eID(PID_None), pPkt(nullptr), fOwnPkt(true), pNext(nullptr) {}
155
156C4IDPacket::C4IDPacket(C4PacketType eID, C4PacketBase *pPkt, bool fTakePkt)
157 : eID(eID), pPkt(pPkt), fOwnPkt(fTakePkt), pNext(nullptr) {}
158
159C4IDPacket::C4IDPacket(const C4IDPacket &Packet2)
160 : C4PacketBase(Packet2),
161 eID(PID_None), pPkt(nullptr), fOwnPkt(true), pNext(nullptr)
162{
163 // kinda hacky (note this might throw an uncaught exception)
164 CompileFromBuf<StdCompilerBinRead>(TargetStruct&: *this,
165 SrcBuf: DecompileToBuf<StdCompilerBinWrite>(SrcStruct: Packet2));
166}
167
168C4IDPacket::~C4IDPacket()
169{
170 Clear();
171}
172
173const char *C4IDPacket::getPktName() const
174{
175 // Use map
176 for (const C4PktHandlingData *pPData = PktHandlingData; pPData->ID != PID_None; pPData++)
177 if (pPData->ID == eID && pPData->Name)
178 return pPData->Name;
179 return "Unknown Packet Type";
180}
181
182void C4IDPacket::Default()
183{
184 eID = PID_None; pPkt = nullptr;
185}
186
187void C4IDPacket::Clear()
188{
189 if (fOwnPkt) delete pPkt; pPkt = nullptr;
190 eID = PID_None;
191}
192
193void C4IDPacket::CompileFunc(StdCompiler *pComp)
194{
195 // Packet ID
196 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntAdaptT<uint8_t>(rValue&: eID), szName: "ID", rDefault: PID_None));
197 // Compiling or Decompiling?
198 if (pComp->isCompiler())
199 {
200 const auto name = pComp->Name(szName: getPktName());
201 if (!name)
202 {
203 pComp->excCorrupt(message: "C4IDPacket: Data value needed! Packet data missing!"); return;
204 }
205 // Delete old packet
206 if (fOwnPkt) delete pPkt; pPkt = nullptr;
207 if (eID == PID_None) return;
208 // Search unpacking function
209 for (const C4PktHandlingData *pPData = PktHandlingData; pPData->ID != PID_None; pPData++)
210 if (pPData->ID == eID && pPData->FnUnpack)
211 {
212 pPkt = pPData->FnUnpack(pComp);
213 break;
214 }
215 if (!pPkt)
216 pComp->excCorrupt(message: "C4IDPacket: could not unpack packet id {:02x}!", args: std::to_underlying(value: eID));
217 }
218 else if (eID != PID_None)
219 // Just write
220 pComp->Value(rStruct: mkNamingAdapt(rValue&: *pPkt, szName: getPktName()));
221}
222
223// C4PacketList
224
225C4PacketList::C4PacketList()
226 : pFirst(nullptr), pLast(nullptr) {}
227
228C4PacketList::C4PacketList(const C4PacketList &List2)
229 : C4PacketBase(List2),
230 pFirst(nullptr), pLast(nullptr)
231{
232 Append(List: List2);
233}
234
235C4PacketList::~C4PacketList()
236{
237 Clear();
238}
239
240void C4PacketList::Add(C4IDPacket *pPkt)
241{
242 assert(!pPkt->pNext);
243 (pLast ? pLast->pNext : pFirst) = pPkt;
244 pLast = pPkt;
245}
246
247void C4PacketList::Add(C4PacketType eType, C4PacketBase *pPkt)
248{
249 Add(pPkt: new C4IDPacket(eType, pPkt));
250}
251
252void C4PacketList::Take(C4PacketList &List)
253{
254 pFirst = List.pFirst;
255 pLast = List.pLast;
256 List.pFirst = List.pLast = nullptr;
257}
258
259void C4PacketList::Append(const C4PacketList &List)
260{
261 for (C4IDPacket *pPkt = List.firstPkt(); pPkt; pPkt = List.nextPkt(pPkt))
262 Add(pPkt: new C4IDPacket(*pPkt));
263}
264
265void C4PacketList::Clear()
266{
267 while (pFirst)
268 Delete(pPkt: pFirst);
269}
270
271void C4PacketList::Remove(C4IDPacket *pPkt)
272{
273 if (pPkt == pFirst)
274 {
275 pFirst = pPkt->pNext;
276 if (pPkt == pLast)
277 pLast = nullptr;
278 }
279 else
280 {
281 C4IDPacket *pPrev;
282 for (pPrev = pFirst; pPrev && pPrev->pNext != pPkt; pPrev = pPrev->pNext);
283 if (pPrev)
284 {
285 pPrev->pNext = pPkt->pNext;
286 if (pPkt == pLast)
287 pLast = pPrev;
288 }
289 }
290}
291
292void C4PacketList::Delete(C4IDPacket *pPkt)
293{
294 Remove(pPkt);
295 delete pPkt;
296}
297
298void C4PacketList::CompileFunc(StdCompiler *pComp)
299{
300 // unpack packets
301 if (pComp->isCompiler())
302 {
303 // Read until no further sections available
304 for (;;)
305 {
306 const auto name = pComp->Name(szName: "IDPacket");
307 if (!name)
308 {
309 break;
310 }
311 // Read the packet
312 auto pkt = std::make_unique<C4IDPacket>();
313 pComp->Value(rStruct&: *pkt);
314
315 // Terminator?
316 if (!pkt->getPkt())
317 {
318 break;
319 }
320 // Add to list
321 Add(pPkt: pkt.get());
322 pkt.release();
323 }
324 }
325 else
326 {
327 // Write all packets
328 for (C4IDPacket *pPkt = pFirst; pPkt; pPkt = pPkt->pNext)
329 pComp->Value(rStruct: mkNamingAdapt(rValue&: *pPkt, szName: "IDPacket"));
330 // Terminate, if no naming is available
331 if (!pComp->hasNaming())
332 {
333 C4IDPacket Pkt;
334 pComp->Value(rStruct: mkNamingAdapt(rValue&: Pkt, szName: "IDPacket"));
335 }
336 }
337}
338