| 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 | |
| 27 | const uint32_t C4NetResChunkSize = 100U * 1024U; |
| 28 | |
| 29 | const 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 | |
| 38 | const int32_t C4NetResIDAnonymous = -2; |
| 39 | |
| 40 | enum 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" |
| 54 | class C4Network2ResList; |
| 55 | class C4Network2ResChunk; |
| 56 | |
| 57 | // classes |
| 58 | class C4Network2ResCore : public C4PacketBase |
| 59 | { |
| 60 | public: |
| 61 | C4Network2ResCore(); |
| 62 | |
| 63 | protected: |
| 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 | |
| 73 | public: |
| 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 | |
| 97 | class C4Network2ResLoad |
| 98 | { |
| 99 | friend class C4Network2Res; |
| 100 | |
| 101 | public: |
| 102 | C4Network2ResLoad(int32_t iChunk, int32_t iByClient); |
| 103 | ~C4Network2ResLoad(); |
| 104 | |
| 105 | protected: |
| 106 | // chunk download data |
| 107 | int32_t iChunk; |
| 108 | time_t Timestamp; |
| 109 | int32_t iByClient; |
| 110 | |
| 111 | // list (C4Network2Res) |
| 112 | C4Network2ResLoad *pNext; |
| 113 | |
| 114 | public: |
| 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 | |
| 123 | class C4Network2ResChunkData : public C4PacketBase |
| 124 | { |
| 125 | public: |
| 126 | C4Network2ResChunkData(); |
| 127 | C4Network2ResChunkData(const C4Network2ResChunkData &Data2); |
| 128 | ~C4Network2ResChunkData(); |
| 129 | |
| 130 | C4Network2ResChunkData &operator=(const C4Network2ResChunkData &Data2); |
| 131 | |
| 132 | protected: |
| 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 | |
| 140 | public: |
| 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 | |
| 157 | protected: |
| 158 | // helpers |
| 159 | bool MergeRanges(ChunkRange *pRange); |
| 160 | void GetNegative(C4Network2ResChunkData &Target) const; |
| 161 | int32_t getPresentChunk(int32_t iNr) const; |
| 162 | |
| 163 | public: |
| 164 | virtual void CompileFunc(StdCompiler *pComp) override; |
| 165 | }; |
| 166 | |
| 167 | class C4Network2Res |
| 168 | { |
| 169 | friend class C4Network2ResList; |
| 170 | friend class C4Network2ResChunk; |
| 171 | |
| 172 | public: |
| 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 | |
| 198 | protected: |
| 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 | |
| 231 | public: |
| 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 | |
| 282 | protected: |
| 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 | |
| 296 | class C4Network2ResChunk : public C4PacketBase |
| 297 | { |
| 298 | public: |
| 299 | C4Network2ResChunk(); |
| 300 | ~C4Network2ResChunk(); |
| 301 | |
| 302 | protected: |
| 303 | int32_t iResID; |
| 304 | uint32_t iChunk; |
| 305 | StdBuf Data; |
| 306 | |
| 307 | public: |
| 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 | |
| 317 | class C4Network2ResList : protected CStdCSecExCallback // run by network thread |
| 318 | { |
| 319 | friend class C4Network2Res; |
| 320 | friend class C4Network2; |
| 321 | |
| 322 | public: |
| 323 | C4Network2ResList(); |
| 324 | virtual ~C4Network2ResList(); |
| 325 | |
| 326 | protected: |
| 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 | |
| 343 | public: |
| 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 | |
| 348 | protected: |
| 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 | |
| 354 | public: |
| 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 | |
| 385 | protected: |
| 386 | void OnResComplete(C4Network2Res *pRes); |
| 387 | |
| 388 | // misc |
| 389 | bool CreateNetworkFolder(); |
| 390 | bool FindTempResFileName(const char *szFilename, char *pTarget); |
| 391 | }; |
| 392 | |
| 393 | // Packets |
| 394 | |
| 395 | class C4PacketResStatus : public C4PacketBase |
| 396 | { |
| 397 | public: |
| 398 | C4PacketResStatus(); |
| 399 | C4PacketResStatus(int32_t iResID, const C4Network2ResChunkData &nChunks); |
| 400 | |
| 401 | protected: |
| 402 | int32_t iResID; |
| 403 | C4Network2ResChunkData Chunks; |
| 404 | |
| 405 | public: |
| 406 | int32_t getResID() const { return iResID; } |
| 407 | const C4Network2ResChunkData &getChunks() const { return Chunks; } |
| 408 | |
| 409 | virtual void CompileFunc(StdCompiler *pComp) override; |
| 410 | }; |
| 411 | |
| 412 | class C4PacketResDiscover : public C4PacketBase |
| 413 | { |
| 414 | public: |
| 415 | C4PacketResDiscover(); |
| 416 | |
| 417 | protected: |
| 418 | int32_t iDisIDs[16], iDisIDCnt; |
| 419 | |
| 420 | public: |
| 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 | |
| 429 | class C4PacketResRequest : public C4PacketBase |
| 430 | { |
| 431 | public: |
| 432 | C4PacketResRequest(int32_t iID = -1, int32_t iChunk = -1); |
| 433 | |
| 434 | protected: |
| 435 | int32_t iReqID, iReqChunk; |
| 436 | |
| 437 | public: |
| 438 | int32_t getReqID() const { return iReqID; } |
| 439 | int32_t getReqChunk() const { return iReqChunk; } |
| 440 | |
| 441 | virtual void CompileFunc(StdCompiler *pComp) override; |
| 442 | }; |
| 443 | |