1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2021, The LegacyClonk 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// network ressource: data needed for the game (scenario, plr files, definitions...)
18
19#pragma once
20
21#include "C4ForwardDeclarations.h"
22#include <StdSha1.h>
23#include <StdSync.h>
24
25#include <atomic>
26
27const uint32_t C4NetResChunkSize = 100U * 1024U;
28
29const int32_t C4NetResDiscoverTimeout = 10, // (s)
30 C4NetResDiscoverInterval = 1, // (s)
31 C4NetResStatusInterval = 1, // (s)
32 C4NetResMaxLoadPerPeerPerFile = 3,
33 C4NetResMaxLoad = 20,
34 C4NetResLoadTimeout = 60, // (s)
35 C4NetResDeleteTime = 60, // (s)
36 C4NetResMaxBigicon = 20; // maximum size, in KB, of bigicon
37
38const int32_t C4NetResIDAnonymous = -2;
39
40enum C4Network2ResType
41{
42 NRT_Null = 0,
43 NRT_Scenario,
44 NRT_Dynamic,
45 NRT_Player,
46 NRT_Definitions,
47 NRT_System,
48 NRT_Material,
49};
50
51// damn circular dependencies
52#include "C4PacketBase.h"
53#include "C4Network2IO.h"
54class C4Network2ResList;
55class C4Network2ResChunk;
56
57// classes
58class C4Network2ResCore : public C4PacketBase
59{
60public:
61 C4Network2ResCore();
62
63protected:
64 C4Network2ResType eType;
65 int32_t iID, iDerID;
66 StdStrBuf FileName, Author;
67 bool fLoadable;
68 uint32_t iFileSize, iFileCRC, iContentsCRC;
69 uint8_t fHasFileSHA;
70 uint8_t FileSHA[StdSha1::DigestLength];
71 uint32_t iChunkSize;
72
73public:
74 C4Network2ResType getType() const { return eType; }
75 bool isNull() const { return eType == NRT_Null; }
76 int32_t getID() const { return iID; }
77 int32_t getDerID() const { return iDerID; }
78 bool isLoadable() const { return fLoadable; }
79 uint32_t getFileSize() const { return iFileSize; }
80 uint32_t getFileCRC() const { return iFileCRC; }
81 uint32_t getContentsCRC() const { return iContentsCRC; }
82 bool hasFileSHA() const { return !!fHasFileSHA; }
83 const char *getFileName() const { return FileName.getData(); }
84 uint32_t getChunkSize() const { return iChunkSize; }
85 uint32_t getChunkCnt() const { return iFileSize && iChunkSize ? (iFileSize - 1) / iChunkSize + 1 : 0; }
86
87 void Set(C4Network2ResType eType, int32_t iResID, const char *strFileName, uint32_t iContentsCRC, const char *szAutor);
88 void SetID(int32_t inID) { iID = inID; }
89 void SetDerived(int32_t inDerID) { iDerID = inDerID; }
90 void SetLoadable(uint32_t iSize, uint32_t iCRC);
91 void SetFileSHA(uint8_t *pSHA) { memcpy(dest: FileSHA, src: pSHA, n: StdSha1::DigestLength); fHasFileSHA = true; }
92 void Clear();
93
94 virtual void CompileFunc(StdCompiler *pComp) override;
95};
96
97class C4Network2ResLoad
98{
99 friend class C4Network2Res;
100
101public:
102 C4Network2ResLoad(int32_t iChunk, int32_t iByClient);
103 ~C4Network2ResLoad();
104
105protected:
106 // chunk download data
107 int32_t iChunk;
108 time_t Timestamp;
109 int32_t iByClient;
110
111 // list (C4Network2Res)
112 C4Network2ResLoad *pNext;
113
114public:
115 int32_t getChunk() const { return iChunk; }
116 int32_t getByClient() const { return iByClient; }
117
118 C4Network2ResLoad *Next() const { return pNext; }
119
120 bool CheckTimeout();
121};
122
123class C4Network2ResChunkData : public C4PacketBase
124{
125public:
126 C4Network2ResChunkData();
127 C4Network2ResChunkData(const C4Network2ResChunkData &Data2);
128 ~C4Network2ResChunkData();
129
130 C4Network2ResChunkData &operator=(const C4Network2ResChunkData &Data2);
131
132protected:
133 int32_t iChunkCnt, iPresentChunkCnt;
134
135 // present chunk ranges
136 struct ChunkRange { int32_t Start, Length; ChunkRange *Next; };
137 ChunkRange *pChunkRanges;
138 int32_t iChunkRangeCnt;
139
140public:
141 int32_t getChunkCnt() const { return iChunkCnt; }
142 int32_t getPresentChunkCnt() const { return iPresentChunkCnt; }
143 int32_t getPresentPercent() const { return iPresentChunkCnt * 100 / iChunkCnt; }
144 bool isComplete() const { return iPresentChunkCnt == iChunkCnt; }
145
146 void SetIncomplete(int32_t iChunkCnt);
147 void SetComplete(int32_t iChunkCnt);
148
149 void AddChunk(int32_t iChunk);
150 void AddChunkRange(int32_t iStart, int32_t iLength);
151 void Merge(const C4Network2ResChunkData &Data2);
152
153 void Clear();
154
155 int32_t GetChunkToRetrieve(const C4Network2ResChunkData &Available, int32_t iLoadingCnt, int32_t *pLoading) const;
156
157protected:
158 // helpers
159 bool MergeRanges(ChunkRange *pRange);
160 void GetNegative(C4Network2ResChunkData &Target) const;
161 int32_t getPresentChunk(int32_t iNr) const;
162
163public:
164 virtual void CompileFunc(StdCompiler *pComp) override;
165};
166
167class C4Network2Res
168{
169 friend class C4Network2ResList;
170 friend class C4Network2ResChunk;
171
172public:
173 // helper for reference-holding
174 class Ref
175 {
176 public:
177 Ref() : pRes(nullptr) {}
178 Ref(C4Network2Res *pRes) : pRes(pRes) { if (pRes) pRes->AddRef(); }
179 Ref(const Ref &rCopy) : pRes(rCopy.pRes) { if (pRes) pRes->AddRef(); }
180 ~Ref() { Clear(); }
181 Ref &operator=(C4Network2Res *pnRes) { Set(pnRes); return *this; }
182 Ref &operator=(const Ref &rCopy) { Set(rCopy.pRes); return *this; }
183
184 private:
185 C4Network2Res *pRes;
186
187 public:
188 operator C4Network2Res *() const { return pRes; }
189 bool operator!() const { return !pRes; }
190 C4Network2Res *operator->() const { return pRes; }
191 void Clear() { if (pRes) pRes->DelRef(); pRes = nullptr; }
192 void Set(C4Network2Res *pnRes) { if (pRes == pnRes) return; Clear(); pRes = pnRes; if (pRes) pRes->AddRef(); }
193 };
194
195 C4Network2Res(C4Network2ResList *pnParent);
196 ~C4Network2Res();
197
198protected:
199 // core, chunk data
200 C4Network2ResCore Core;
201 C4Network2ResChunkData Chunks; // (only valid while loading)
202 bool fDirty;
203
204 // local file data
205 CStdCSec FileCSec;
206 char szFile[_MAX_PATH + 1], szStandalone[_MAX_PATH + 1];
207 bool fTempFile, fStandaloneFailed;
208
209 // references
210 std::atomic<long> iRefCnt;
211 bool fRemoved;
212
213 // being load?
214 time_t iLastReqTime;
215
216 // loading
217 bool fLoading;
218
219 // not savable if true
220 bool local{false};
221 struct ClientChunks { C4Network2ResChunkData Chunks; int32_t ClientID; ClientChunks *Next; }
222 *pCChunks;
223 time_t iDiscoverStartTime;
224 C4Network2ResLoad *pLoads;
225 int32_t iLoadCnt;
226
227 // list (C4Network2ResList)
228 C4Network2Res *pNext;
229 C4Network2ResList *pParent;
230
231public:
232 C4Network2ResType getType() const { return Core.getType(); }
233 const C4Network2ResCore &getCore() const { return Core; }
234 bool isDirty() const { return fDirty; }
235 bool isAnonymous() const { return getResID() == C4NetResIDAnonymous; }
236 int32_t getResID() const { return Core.getID(); }
237 int32_t getResClient() const { return Core.getID() >> 16; }
238 const char *getFile() const { return szFile; }
239 time_t getLastReqTime() const { return iLastReqTime; }
240 bool isRemoved() const { return fRemoved; }
241 bool isLoading() const { return fLoading; }
242 bool isComplete() const { return !fLoading; }
243 bool isLocal() const { return local; }
244 int32_t getPresentPercent() const { return fLoading ? Chunks.getPresentPercent() : 100; }
245
246 bool SetByFile(const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName = nullptr, bool fSilent = false);
247 bool SetByGroup(C4Group *pGrp, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName = nullptr, bool fSilent = false);
248 bool SetByCore(const C4Network2ResCore &nCore, bool fSilent = false, const char *szAsFilename = nullptr, int32_t iRecursion = 0);
249 bool SetLoad(const C4Network2ResCore &nCore);
250
251 bool SetDerived(const char *strName, const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iDResID);
252
253 void ChangeID(int32_t inID);
254
255 bool IsBinaryCompatible();
256 bool GetStandalone(char *pTo, int32_t iMaxL, bool fSetOfficial, bool fAllowUnloadable = false, bool fSilent = false);
257 bool CalculateSHA();
258
259 C4Network2Res::Ref Derive();
260 bool FinishDerive();
261 bool FinishDerive(const C4Network2ResCore &nCore);
262
263 bool SendStatus(C4Network2IOConnection *pTo = nullptr);
264 bool SendChunk(uint32_t iChunk, int32_t iToClient);
265
266 // references
267 void AddRef(); void DelRef();
268
269 // events
270 void OnDiscover(C4Network2IOConnection *pBy);
271 void OnStatus(const C4Network2ResChunkData &rChunkData, C4Network2IOConnection *pBy);
272 void OnChunk(const C4Network2ResChunk &rChunk);
273 bool DoLoad();
274
275 bool NeedsDiscover();
276
277 void Remove();
278 void Clear();
279
280 bool GetClientProgress(int32_t clientID, int32_t &presentChunkCnt, int32_t &chunkCnt);
281
282protected:
283 int32_t OpenFileRead(); int32_t OpenFileWrite();
284
285 void StartNewLoads();
286 bool StartLoad(int32_t iFromClient, const C4Network2ResChunkData &Chunks);
287 void EndLoad();
288 void ClearLoad();
289
290 void RemoveLoad(C4Network2ResLoad *pLoad);
291 void RemoveCChunks(ClientChunks *pChunks);
292
293 bool OptimizeStandalone(bool fSilent);
294};
295
296class C4Network2ResChunk : public C4PacketBase
297{
298public:
299 C4Network2ResChunk();
300 ~C4Network2ResChunk();
301
302protected:
303 int32_t iResID;
304 uint32_t iChunk;
305 StdBuf Data;
306
307public:
308 int32_t getResID() const { return iResID; }
309 uint32_t getChunkNr() const { return iChunk; }
310
311 bool Set(C4Network2Res *pRes, uint32_t iChunk);
312 bool AddTo(C4Network2Res *pRes, C4Network2IO *pIO) const;
313
314 virtual void CompileFunc(StdCompiler *pComp) override;
315};
316
317class C4Network2ResList : protected CStdCSecExCallback // run by network thread
318{
319 friend class C4Network2Res;
320 friend class C4Network2;
321
322public:
323 C4Network2ResList();
324 virtual ~C4Network2ResList();
325
326protected:
327 C4Network2Res *pFirst;
328 CStdCSecEx ResListCSec;
329 CStdCSec ResListAddCSec;
330
331 int32_t iClientID, iNextResID;
332 CStdCSec ResIDCSec;
333
334 // timings
335 time_t iLastDiscover, iLastStatus;
336
337 // object used for network i/o
338 C4Network2IO *pIO;
339
340 // logger
341 std::shared_ptr<spdlog::logger> logger;
342
343public:
344 // initialization
345 bool Init(std::shared_ptr<spdlog::logger> logger, int32_t iClientID, C4Network2IO *pIOClass); // by main thread
346 void SetLocalID(int32_t iClientID); // by both
347
348protected:
349 int32_t nextResID(); // by main thread
350
351 C4Network2Res *getRes(int32_t iResID); // by both
352 C4Network2Res *getRes(const char *szFile, bool fLocalOnly); // by both
353
354public:
355 // returns referenced ressource ptrs
356 C4Network2Res::Ref getRefRes(int32_t iResID); // by both
357 C4Network2Res::Ref getRefRes(const char *szFile, bool fLocalOnly = false); // by both
358 C4Network2Res::Ref getRefNextRes(int32_t iResID); // by both
359
360 void Add(C4Network2Res *pRes); // by both
361 C4Network2Res::Ref AddByFile(const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iResID = -1, const char *szResName = nullptr, bool fAllowUnloadable = false); // by both
362 C4Network2Res::Ref AddByCore(const C4Network2ResCore &Core, bool fLoad = true); // by main thread
363 C4Network2Res::Ref AddLoad(const C4Network2ResCore &Core); // by main thread
364
365 void RemoveAtClient(int32_t iClientID); // by main thread
366 void Clear(); // by main thread
367
368 bool SendDiscover(C4Network2IOConnection *pTo = nullptr); // by both
369 void OnClientConnect(C4Network2IOConnection *pConn); // by main thread
370
371 // interface for C4Network2IO
372 void HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn);
373 void OnTimer();
374
375 // CStdCSecExCallback
376 void OnShareFree(CStdCSecEx *pCSec) override;
377
378 // for C4Network2Res
379 C4Network2IO *getIOClass() { return pIO; }
380
381 int32_t GetClientProgress(int32_t clientID);
382
383 const std::shared_ptr<spdlog::logger> &GetLogger() const noexcept { return logger; }
384
385protected:
386 void OnResComplete(C4Network2Res *pRes);
387
388 // misc
389 bool CreateNetworkFolder();
390 bool FindTempResFileName(const char *szFilename, char *pTarget);
391};
392
393// Packets
394
395class C4PacketResStatus : public C4PacketBase
396{
397public:
398 C4PacketResStatus();
399 C4PacketResStatus(int32_t iResID, const C4Network2ResChunkData &nChunks);
400
401protected:
402 int32_t iResID;
403 C4Network2ResChunkData Chunks;
404
405public:
406 int32_t getResID() const { return iResID; }
407 const C4Network2ResChunkData &getChunks() const { return Chunks; }
408
409 virtual void CompileFunc(StdCompiler *pComp) override;
410};
411
412class C4PacketResDiscover : public C4PacketBase
413{
414public:
415 C4PacketResDiscover();
416
417protected:
418 int32_t iDisIDs[16], iDisIDCnt;
419
420public:
421 int32_t getDisIDCnt() const { return iDisIDCnt; }
422 bool isIDPresent(int32_t iID) const;
423
424 bool AddDisID(int32_t iID);
425
426 virtual void CompileFunc(StdCompiler *pComp) override;
427};
428
429class C4PacketResRequest : public C4PacketBase
430{
431public:
432 C4PacketResRequest(int32_t iID = -1, int32_t iChunk = -1);
433
434protected:
435 int32_t iReqID, iReqChunk;
436
437public:
438 int32_t getReqID() const { return iReqID; }
439 int32_t getReqChunk() const { return iReqChunk; }
440
441 virtual void CompileFunc(StdCompiler *pComp) override;
442};
443