/src/network/C4Network2Res.cpp
C++ | 1762 lines | 1277 code | 140 blank | 345 comment | 322 complexity | beb1ed90a472155ffb565bb4131b443a MD5 | raw file
Possible License(s): WTFPL, 0BSD, LGPL-2.1, CC-BY-3.0
Large files files are truncated, but you can click here to view the full file
1/*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2004-2007 Peter Wortmann
5 * Copyright (c) 2005-2007 Sven Eberhardt
6 * Copyright (c) 2005-2006, 2008 G?nther Brammer
7 * Copyright (c) 2007 Julian Raschke
8 * Copyright (c) 2008 Matthes Bender
9 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
10 *
11 * Portions might be copyrighted by other authors who have contributed
12 * to OpenClonk.
13 *
14 * Permission to use, copy, modify, and/or distribute this software for any
15 * purpose with or without fee is hereby granted, provided that the above
16 * copyright notice and this permission notice appear in all copies.
17 * See isc_license.txt for full license and disclaimer.
18 *
19 * "Clonk" is a registered trademark of Matthes Bender.
20 * See clonk_trademark_license.txt for full license.
21 */
22#include <C4Include.h>
23#include <C4Network2Res.h>
24
25#ifndef BIG_C4INCLUDE
26#include <C4Random.h>
27#include <C4Config.h>
28#include <C4Log.h>
29#include <C4Group.h>
30#include <C4Components.h>
31#include <C4Game.h>
32#include <C4GameControl.h>
33#endif
34
35#include <fcntl.h>
36#include <sys/types.h>
37#include <sys/stat.h>
38#ifdef _WIN32
39#include <direct.h>
40#endif
41#include <errno.h>
42
43#ifdef _MSC_VER
44#define snprintf _snprintf
45#pragma warning (disable : 4355)
46#endif
47
48// compile debug options
49// #define C4NET2RES_LOAD_ALL
50#define C4NET2RES_DEBUG_LOG
51
52// helper
53
54class DirSizeHelper {
55 static uint32_t iSize, iMaxSize;
56 static bool Helper(const char *szPath)
57 {
58 if(szPath[SLen(szPath)-1] == '.')
59 return false;
60 if(iSize > iMaxSize)
61 return false;
62 if(DirectoryExists(szPath))
63 ForEachFile(szPath, &Helper);
64 else if(FileExists(szPath))
65 iSize += FileSize(szPath);
66 return true;
67 }
68public:
69 static bool GetDirSize(const char *szPath, uint32_t *pSize, uint32_t inMaxSize = ~0)
70 {
71 // Security
72 if(!pSize) return false;
73 // Fold it
74 iSize = 0; iMaxSize = inMaxSize;
75 ForEachFile(szPath, &Helper);
76 // Return
77 *pSize = iSize;
78 return true;
79 }
80};
81uint32_t DirSizeHelper::iSize, DirSizeHelper::iMaxSize;
82
83// *** C4Network2ResCore
84
85C4Network2ResCore::C4Network2ResCore()
86 : eType(NRT_Null),
87 iID(-1), iDerID(-1),
88 fLoadable(false),
89 iFileSize(~0u), iFileCRC(~0u), iContentsCRC(~0u),
90 iChunkSize(C4NetResChunkSize),
91 fHasFileSHA(false)
92{
93}
94
95void C4Network2ResCore::Set(C4Network2ResType enType, int32_t iResID, const char *strFileName, uint32_t inContentsCRC, const char *strAuthor)
96{
97 // Initialize base data
98 eType = enType; iID = iResID; iDerID = -1;
99 fLoadable = false;
100 iFileSize = iFileCRC = ~0; iContentsCRC = inContentsCRC;
101 iChunkSize = C4NetResChunkSize;
102 FileName.Copy(strFileName);
103 Author.Copy(strAuthor);
104}
105
106void C4Network2ResCore::SetLoadable(uint32_t iSize, uint32_t iCRC)
107{
108 fLoadable = true;
109 iFileSize = iSize;
110 iFileCRC = iCRC;
111}
112
113void C4Network2ResCore::Clear()
114{
115 eType = NRT_Null;
116 iID = iDerID = -1;
117 fLoadable = false;
118 FileName.Clear();
119 Author.Clear();
120 iFileSize = iFileCRC = iContentsCRC = ~0;
121 fHasFileSHA = false;
122}
123
124// C4PacketBase virtuals
125
126void C4Network2ResCore::CompileFunc(StdCompiler *pComp)
127{
128 pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(eType, C4Network2ResType_EnumMap), "Type", NRT_Null));
129 pComp->Value(mkNamingAdapt(iID, "ID", -1));
130 pComp->Value(mkNamingAdapt(iDerID, "DerID", -1));
131 pComp->Value(mkNamingAdapt(fLoadable, "Loadable", true));
132 if(fLoadable)
133 {
134 pComp->Value(mkNamingAdapt(iFileSize, "FileSize", 0U));
135 pComp->Value(mkNamingAdapt(iFileCRC, "FileCRC", 0U));
136 pComp->Value(mkNamingAdapt(iChunkSize, "ChunkSize", C4NetResChunkSize));
137 if(!iChunkSize) pComp->excCorrupt("zero chunk size");
138 }
139 pComp->Value(mkNamingAdapt(iContentsCRC, "ContentsCRC", 0U));
140 pComp->Value(mkNamingCountAdapt(fHasFileSHA, "FileSHA"));
141 if(fHasFileSHA)
142 pComp->Value(mkNamingAdapt(mkHexAdapt(FileSHA), "FileSHA"));
143 pComp->Value(mkNamingAdapt(mkNetFilenameAdapt(FileName), "Filename", ""));
144 pComp->Value(mkNamingAdapt(mkNetFilenameAdapt(Author), "Author", ""));
145}
146
147// *** C4Network2ResLoad
148
149C4Network2ResLoad::C4Network2ResLoad(int32_t inChunk, int32_t inByClient)
150 : iChunk(inChunk), iByClient(inByClient), Timestamp(time(NULL)), pNext(NULL)
151{
152
153}
154
155C4Network2ResLoad::~C4Network2ResLoad()
156{
157
158}
159
160bool C4Network2ResLoad::CheckTimeout()
161{
162 return difftime(time(NULL), Timestamp) >= C4NetResLoadTimeout;
163}
164
165// *** C4Network2ResChunkData
166
167C4Network2ResChunkData::C4Network2ResChunkData()
168 : iChunkCnt(0), iPresentChunkCnt(0),
169 pChunkRanges(NULL), iChunkRangeCnt(0)
170{
171
172}
173
174C4Network2ResChunkData::C4Network2ResChunkData(const C4Network2ResChunkData &Data2)
175 : C4PacketBase(Data2),
176 iChunkCnt(Data2.getChunkCnt()), iPresentChunkCnt(0),
177 pChunkRanges(NULL), iChunkRangeCnt(0)
178{
179 // add ranges
180 Merge(Data2);
181}
182
183C4Network2ResChunkData::~C4Network2ResChunkData()
184{
185 Clear();
186}
187
188C4Network2ResChunkData &C4Network2ResChunkData::operator =(const C4Network2ResChunkData &Data2)
189{
190 // clear, merge
191 SetIncomplete(Data2.getChunkCnt());
192 Merge(Data2);
193 return *this;
194}
195
196void C4Network2ResChunkData::SetIncomplete(int32_t inChunkCnt)
197{
198 Clear();
199 // just set total chunk count
200 iChunkCnt = inChunkCnt;
201}
202
203void C4Network2ResChunkData::SetComplete(int32_t inChunkCnt)
204{
205 Clear();
206 // set total chunk count
207 iPresentChunkCnt = iChunkCnt = inChunkCnt;
208 // create one range
209 ChunkRange *pRange = new ChunkRange;
210 pRange->Start = 0; pRange->Length = iChunkCnt;
211 pRange->Next = NULL;
212 pChunkRanges = pRange;
213}
214
215void C4Network2ResChunkData::AddChunk(int32_t iChunk)
216{
217 AddChunkRange(iChunk, 1);
218}
219
220void C4Network2ResChunkData::AddChunkRange(int32_t iStart, int32_t iLength)
221{
222 // security
223 if(iStart < 0 || iStart + iLength > iChunkCnt || iLength <= 0) return;
224 // find position
225 ChunkRange *pRange, *pPrev;
226 for(pRange = pChunkRanges, pPrev = NULL; pRange; pPrev = pRange, pRange = pRange->Next)
227 if(pRange->Start >= iStart)
228 break;
229 // create new
230 ChunkRange *pNew = new ChunkRange;
231 pNew->Start = iStart; pNew->Length = iLength;
232 // add to list
233 pNew->Next = pRange;
234 (pPrev ? pPrev->Next : pChunkRanges) = pNew;
235 // counts
236 iPresentChunkCnt += iLength; iChunkRangeCnt++;
237 // check merges
238 if(pPrev && MergeRanges(pPrev))
239 while(MergeRanges(pPrev)) {}
240 else
241 while(MergeRanges(pNew)) {}
242}
243
244void C4Network2ResChunkData::Merge(const C4Network2ResChunkData &Data2)
245{
246 // must have same basis chunk count
247 assert(iChunkCnt == Data2.getChunkCnt());
248 // add ranges
249 for(ChunkRange *pRange = Data2.pChunkRanges; pRange; pRange = pRange->Next)
250 AddChunkRange(pRange->Start, pRange->Length);
251}
252
253void C4Network2ResChunkData::Clear()
254{
255 iChunkCnt = iPresentChunkCnt = iChunkRangeCnt = 0;
256 // remove all ranges
257 while(pChunkRanges)
258 {
259 ChunkRange *pDelete = pChunkRanges;
260 pChunkRanges = pDelete->Next;
261 delete pDelete;
262 }
263}
264
265int32_t C4Network2ResChunkData::GetChunkToRetrieve(const C4Network2ResChunkData &Available, int32_t iLoadingCnt, int32_t *pLoading) const
266{
267 // (this version is highly calculation-intensitive, yet the most satisfactory
268 // solution I could find)
269
270 // find everything that should not be retrieved
271 C4Network2ResChunkData ChData; Available.GetNegative(ChData);
272 ChData.Merge(*this);
273 for(int32_t i = 0; i < iLoadingCnt; i++)
274 ChData.AddChunk(pLoading[i]);
275 // nothing to retrieve?
276 if(ChData.isComplete()) return -1;
277 // invert to get everything that should be retrieved
278 C4Network2ResChunkData ChData2; ChData.GetNegative(ChData2);
279 // select chunk (random)
280 int32_t iRetrieveChunk = SafeRandom(ChData2.getPresentChunkCnt());
281 // return
282 return ChData2.getPresentChunk(iRetrieveChunk);
283}
284
285bool C4Network2ResChunkData::MergeRanges(ChunkRange *pRange)
286{
287 // no next entry?
288 if(!pRange || !pRange->Next) return false;
289 // do merge?
290 ChunkRange *pNext = pRange->Next;
291 if(pRange->Start + pRange->Length < pNext->Start) return false;
292 // get overlap
293 int32_t iOverlap = Min((pRange->Start + pRange->Length) - pNext->Start, pNext->Length);
294 // set new chunk range
295 pRange->Length += pNext->Length - iOverlap;
296 // remove range
297 pRange->Next = pNext->Next;
298 delete pNext;
299 // counts
300 iChunkRangeCnt--; iPresentChunkCnt -= iOverlap;
301 // ok
302 return true;
303}
304
305void C4Network2ResChunkData::GetNegative(C4Network2ResChunkData &Target) const
306{
307 // clear target
308 Target.SetIncomplete(iChunkCnt);
309 // add all ranges that are missing
310 int32_t iFreeStart = 0;
311 for(ChunkRange *pRange = pChunkRanges; pRange; pRange = pRange->Next)
312 {
313 // add range
314 Target.AddChunkRange(iFreeStart, pRange->Start - iFreeStart);
315 // safe new start
316 iFreeStart = pRange->Start + pRange->Length;
317 }
318 // add last range
319 Target.AddChunkRange(iFreeStart, iChunkCnt - iFreeStart);
320}
321
322int32_t C4Network2ResChunkData::getPresentChunk(int32_t iNr) const
323{
324 for(ChunkRange *pRange = pChunkRanges; pRange; pRange = pRange->Next)
325 if(iNr < pRange->Length)
326 return iNr + pRange->Start;
327 else
328 iNr -= pRange->Length;
329 return -1;
330}
331
332void C4Network2ResChunkData::CompileFunc(StdCompiler *pComp)
333{
334 bool fCompiler = pComp->isCompiler();
335 if(fCompiler) Clear();
336 // Data
337 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iChunkCnt), "ChunkCnt", 0));
338 pComp->Value(mkNamingAdapt(mkIntPackAdapt(iChunkRangeCnt), "ChunkRangeCnt", 0));
339 // Ranges
340 if(!pComp->Name("Ranges"))
341 pComp->excCorrupt("ResChunk ranges expected!");
342 ChunkRange *pRange = NULL;
343 for(int32_t i = 0; i < iChunkRangeCnt; i++)
344 {
345 // Create new range / go to next range
346 if(fCompiler)
347 pRange = (pRange ? pRange->Next : pChunkRanges) = new ChunkRange;
348 else
349 pRange = pRange ? pRange->Next : pChunkRanges;
350 // Seperate
351 if(i) pComp->Seperator();
352 // Compile range
353 pComp->Value(mkIntPackAdapt(pRange->Start));
354 pComp->Seperator(StdCompiler::SEP_PART2);
355 pComp->Value(mkIntPackAdapt(pRange->Length));
356 }
357 // Terminate list
358 if(fCompiler)
359 (pRange ? pRange->Next : pChunkRanges) = NULL;
360 pComp->NameEnd();
361}
362
363// *** C4Network2Res
364
365C4Network2Res::C4Network2Res(C4Network2ResList *pnParent)
366 : fDirty(false),
367 fTempFile(false), fStandaloneFailed(false),
368 iRefCnt(0), fRemoved(false),
369 iLastReqTime(0),
370 fLoading(false),
371 pCChunks(NULL), iDiscoverStartTime(0), pLoads(NULL), iLoadCnt(0),
372 pNext(NULL),
373 pParent(pnParent)
374{
375 szFile[0] = szStandalone[0] = '\0';
376}
377
378C4Network2Res::~C4Network2Res()
379{
380 assert(!pNext);
381 Clear();
382}
383
384bool C4Network2Res::SetByFile(const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName, bool fSilent)
385{
386 CStdLock FileLock(&FileCSec);
387 // default ressource name: relative path
388 if(!szResName) szResName = Config.AtRelativePath(strFilePath);
389 SCopy(strFilePath, szFile, sizeof(szFile)-1);
390 // group?
391 C4Group Grp;
392 if(Grp.Open(strFilePath))
393 return SetByGroup(&Grp, fTemp, eType, iResID, szResName, fSilent);
394 // so it needs to be a file
395 if(!FileExists(szFile))
396 { if(!fSilent) LogF("SetByFile: file %s not found!", strFilePath); return false; }
397 // calc checksum
398 uint32_t iCRC32;
399 if(!C4Group_GetFileCRC(szFile, &iCRC32)) return false;
400#ifdef C4NET2RES_DEBUG_LOG
401 // log
402 LogSilentF("Network: Resource: complete %d:%s is file %s (%s)", iResID, szResName, szFile, fTemp ? "temp" : "static");
403#endif
404 // set core
405 Core.Set(eType, iResID, szResName, iCRC32, "");
406 // set own data
407 fDirty = true;
408 fTempFile = fTemp;
409 fStandaloneFailed = false;
410 fRemoved = false;
411 iLastReqTime = time(NULL);
412 fLoading = false;
413 // ok
414 return true;
415}
416
417bool C4Network2Res::SetByGroup(C4Group *pGrp, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName, bool fSilent) // by main thread
418{
419 Clear();
420 CStdLock FileLock(&FileCSec);
421 // default ressource name: relative path
422 StdStrBuf sResName;
423 if (szResName)
424 sResName = szResName;
425 else
426 {
427 StdStrBuf sFullName = pGrp->GetFullName();
428 sResName.Copy(Config.AtRelativePath(sFullName.getData()));
429 }
430 SCopy(pGrp->GetFullName().getData(), szFile, sizeof(szFile)-1);
431 // set core
432 Core.Set(eType, iResID, sResName.getData(), pGrp->EntryCRC32(), pGrp->GetMaker());
433#ifdef C4NET2RES_DEBUG_LOG
434 // log
435 LogSilentF("Network: Resource: complete %d:%s is file %s (%s)", iResID, sResName.getData(), szFile, fTemp ? "temp" : "static");
436#endif
437 // set data
438 fDirty = true;
439 fTempFile = fTemp;
440 fStandaloneFailed = false;
441 fRemoved = false;
442 iLastReqTime = time(NULL);
443 fLoading = false;
444 // ok
445 return true;
446}
447
448bool C4Network2Res::SetByCore(const C4Network2ResCore &nCore, bool fSilent, const char *szAsFilename, int32_t iRecursion) // by main thread
449 {
450 StdStrBuf sFilename;
451 // try open local file
452 const char *szFilename = szAsFilename ? szAsFilename : GetC4Filename(nCore.getFileName());
453 if(SetByFile(szFilename, false, nCore.getType(), nCore.getID(), nCore.getFileName(), fSilent))
454 {
455 // check contents checksum
456 if(Core.getContentsCRC() == nCore.getContentsCRC())
457 {
458 // set core
459 fDirty = true;
460 Core = nCore;
461 // ok then
462 return true;
463 }
464 }
465 // get and search for filename without specified folder (e.g., Castle.c4s when the opened game is Easy.c4f\Castle.c4s)
466 const char *szFilenameOnly = GetFilename(szFilename);
467 const char *szFilenameC4 = GetC4Filename(szFilename);
468 if (szFilenameOnly != szFilenameC4)
469 {
470 sFilename.Copy(szFilename, SLen(szFilename) - SLen(szFilenameC4));
471 sFilename.Append(szFilenameOnly);
472 if (SetByCore(nCore, fSilent, szFilenameOnly, Config.Network.MaxResSearchRecursion)) return true;
473 }
474 // if it could still not be set, try within all folders of root (ignoring special folders), and try as file outside the folder
475 // but do not recurse any deeper than set by config (default: One folder)
476 if (iRecursion >= Config.Network.MaxResSearchRecursion) return false;
477 StdStrBuf sSearchPath; const char *szSearchPath;
478 if (!iRecursion)
479 szSearchPath = Config.General.ExePath;
480 else
481 {
482 sSearchPath.Copy(szFilename, SLen(szFilename) - SLen(szFilenameC4));
483 szSearchPath = sSearchPath.getData();
484 }
485 StdStrBuf sNetPath; sNetPath.Copy(Config.Network.WorkPath);
486 char *szNetPath = sNetPath.GrabPointer();
487 TruncateBackslash(szNetPath);
488 sNetPath.Take(szNetPath);
489 for (DirectoryIterator i(szSearchPath); *i; ++i)
490 if (DirectoryExists(*i))
491 if (!*GetExtension(*i)) // directories without extension only
492 if (!szNetPath || !*szNetPath || !ItemIdentical(*i, szNetPath)) // ignore network path
493 {
494 // search for complete name at subpath (e.g. MyFolder\Easy.c4f\Castle.c4s)
495 sFilename.Format("%s%c%s", *i, DirectorySeparator, szFilenameC4);
496 if (SetByCore(nCore, fSilent, sFilename.getData(), iRecursion + 1))
497 return true;
498 }
499 // file could not be found locally
500 return false;
501 }
502
503bool C4Network2Res::SetLoad(const C4Network2ResCore &nCore) // by main thread
504{
505 Clear();
506 CStdLock FileLock(&FileCSec);
507 // must be loadable
508 if(!nCore.isLoadable()) return false;
509 // save core, set chunks
510 Core = nCore;
511 Chunks.SetIncomplete(Core.getChunkCnt());
512 // create temporary file
513 if(!pParent->FindTempResFileName(Core.getFileName(), szFile))
514 return false;
515#ifdef C4NET2RES_DEBUG_LOG
516 // log
517 Application.InteractiveThread.ThreadLogS("Network: Resource: loading %d:%s to file %s", Core.getID(), Core.getFileName(), szFile);
518#endif
519 // set standalone (result is going to be binary-compatible)
520 SCopy(szFile, szStandalone, sizeof(szStandalone) - 1);
521 // set flags
522 fDirty = false;
523 fTempFile = true;
524 fStandaloneFailed = false;
525 fRemoved = false;
526 iLastReqTime = time(NULL);
527 fLoading = true;
528 // No discovery yet
529 iDiscoverStartTime = 0;
530 return true;
531}
532
533bool C4Network2Res::SetDerived(const char *strName, const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iDResID)
534{
535 Clear();
536 CStdLock FileLock(&FileCSec);
537 // set core
538 Core.Set(eType, C4NetResIDAnonymous, strName, ~0, "");
539 Core.SetDerived(iDResID);
540 // save file path
541 SCopy(strFilePath, szFile, _MAX_PATH);
542 *szStandalone = '\0';
543 // set flags
544 fDirty = false;
545 fTempFile = fTemp;
546 fStandaloneFailed = false;
547 fRemoved = false;
548 iLastReqTime = time(NULL);
549 fLoading = false;
550 // Do not set any chunk data - anonymous ressources are very likely to change.
551 // Wait for FinishDerived()-call.
552 return true;
553}
554
555void C4Network2Res::ChangeID(int32_t inID)
556{
557 Core.SetID(inID);
558}
559
560bool C4Network2Res::IsBinaryCompatible()
561{
562 // returns wether the standalone of this ressource is binary compatible
563 // to the official version (means: matches the file checksum)
564
565 CStdLock FileLock(&FileCSec);
566 // standalone set? ok then (see GetStandalone)
567 if(szStandalone[0]) return true;
568 // is a directory?
569 if(DirectoryExists(szFile))
570 // forget it - if the file is packed now, the creation time and author
571 // won't match.
572 return false;
573 // try to create the standalone
574 return GetStandalone(NULL, 0, false, false, true);
575}
576
577bool C4Network2Res::GetStandalone(char *pTo, int32_t iMaxL, bool fSetOfficial, bool fAllowUnloadable, bool fSilent)
578{
579 // already set?
580 if(szStandalone[0])
581 {
582 if(pTo) SCopy(szStandalone, pTo, iMaxL);
583 return true;
584 }
585 // already tried and failed? No point in retrying
586 if(fStandaloneFailed) return false;
587 // not loadable? Wo won't be able to check the standalone as the core will lack the needed information.
588 // the standalone won't be interesting in this case, anyway.
589 if(!fSetOfficial && !Core.isLoadable()) return false;
590 // set flag, so failure below will let future calls fail
591 fStandaloneFailed = true;
592 // lock file
593 CStdLock FileLock(&FileCSec);
594
595 // directory?
596 SCopy(szFile, szStandalone, sizeof(szStandalone)-1);
597 if(DirectoryExists(szFile))
598 {
599 // size check for the directory, if allowed
600 if(fAllowUnloadable)
601 {
602 uint32_t iDirSize;
603 if(!DirSizeHelper::GetDirSize(szFile, &iDirSize, Config.Network.MaxLoadFileSize))
604 { if(!fSilent) LogF("Network: could not get directory size of %s!", szFile); szStandalone[0] = '\0'; return false; }
605 if(iDirSize > uint32_t(Config.Network.MaxLoadFileSize))
606 { if(!fSilent) LogSilentF("Network: %s over size limit, will be marked unloadable!", szFile); szStandalone[0] = '\0'; return false; }
607 }
608 // log - this may take a few seconds
609 if(!fSilent) LogF(LoadResStr("IDS_PRC_NETPACKING"), GetFilename(szFile));
610 // pack inplace?
611 if(!fTempFile)
612 {
613 if(!pParent->FindTempResFileName(szFile, szStandalone))
614 { if(!fSilent) Log("GetStandalone: could not find free name for temporary file!"); szStandalone[0] = '\0'; return false; }
615 if(!C4Group_PackDirectoryTo(szFile, szStandalone))
616 { if(!fSilent) Log("GetStandalone: could not pack directory!"); szStandalone[0] = '\0'; return false; }
617 }
618 else if(!C4Group_PackDirectory(szStandalone))
619 { if(!fSilent) Log("GetStandalone: could not pack directory!"); if(!SEqual(szFile, szStandalone)) EraseDirectory(szStandalone); szStandalone[0] = '\0'; return false; }
620 // make sure directory is packed
621 if(DirectoryExists(szStandalone))
622 { if(!fSilent) Log("GetStandalone: directory hasn't been packed!"); if(!SEqual(szFile, szStandalone)) EraseDirectory(szStandalone); szStandalone[0] = '\0'; return false; }
623 // fallthru
624 }
625
626 // doesn't exist physically?
627 if(!FileExists(szStandalone))
628 {
629 // try C4Group (might be packed)
630 if(!pParent->FindTempResFileName(szFile, szStandalone))
631 { if(!fSilent) Log("GetStandalone: could not find free name for temporary file!"); szStandalone[0] = '\0'; return false; }
632 if(!C4Group_CopyItem(szFile, szStandalone))
633 { if(!fSilent) Log("GetStandalone: could not copy to temporary file!"); szStandalone[0] = '\0'; return false; }
634 }
635
636 // remains missing? give up.
637 if(!FileExists(szStandalone))
638 { if(!fSilent) Log("GetStandalone: file not found!"); szStandalone[0] = '\0'; return false; }
639
640 // do optimizations (delete unneeded entries)
641 if(!OptimizeStandalone(fSilent))
642 { if(!SEqual(szFile, szStandalone)) remove(szStandalone); szStandalone[0] = '\0'; return false; }
643
644 // get file size
645 size_t iSize = FileSize(szStandalone);
646 // size limit
647 if(fAllowUnloadable)
648 if(iSize > uint32_t(Config.Network.MaxLoadFileSize))
649 { if(!fSilent) LogSilentF("Network: %s over size limit, will be marked unloadable!", szFile); szStandalone[0] = '\0'; return false; }
650 // check
651 if(!fSetOfficial && iSize != Core.getFileSize())
652 {
653 // remove file
654 if(!SEqual(szFile, szStandalone)) remove(szStandalone); szStandalone[0] = '\0';
655 // sorry, this version isn't good enough :(
656 return false;
657 }
658
659 // calc checksum
660 uint32_t iCRC32;
661 if(!C4Group_GetFileCRC(szStandalone, &iCRC32))
662 { if(!fSilent) Log("GetStandalone: could not calculate checksum!"); return false; }
663 // set / check
664 if(!fSetOfficial && iCRC32 != Core.getFileCRC())
665 {
666 // remove file, return
667 if(!SEqual(szFile, szStandalone)) remove(szStandalone); szStandalone[0] = '\0';
668 return false;
669 }
670
671 // we didn't fail
672 fStandaloneFailed = false;
673 // mark resource as loadable and safe file information
674 Core.SetLoadable(iSize, iCRC32);
675 // set up chunk data
676 Chunks.SetComplete(Core.getChunkCnt());
677 // ok
678 return true;
679}
680
681bool C4Network2Res::CalculateSHA()
682{
683 // already present?
684 if(Core.hasFileSHA()) return true;
685 // get the file
686 char szStandalone[_MAX_PATH + 1];
687 if(!GetStandalone(szStandalone, _MAX_PATH, false))
688 SCopy(szFile, szStandalone, _MAX_PATH);
689 // get the hash
690 BYTE hash[SHA_DIGEST_LENGTH];
691 if(!C4Group_GetFileSHA1(szStandalone, hash))
692 return false;
693 // save it back
694 Core.SetFileSHA(hash);
695 // okay
696 return true;
697}
698
699
700C4Network2Res::Ref C4Network2Res::Derive()
701{
702 // Called before the file is changed. Rescues all files and creates a
703 // new ressource for the file. This ressource is flagged as "anonymous", as it
704 // has no official core (no res ID, to be exact).
705 // The resource gets its final core when FinishDerive() is called.
706
707 // For security: This doesn't make much sense if the resource is currently being
708 // loaded. So better assume the caller doesn't know what he's doing and check.
709 if(isLoading()) return NULL;
710
711 CStdLock FileLock(&FileCSec);
712 // Save back original file name
713 char szOrgFile[_MAX_PATH+1];
714 SCopy(szFile, szOrgFile, _MAX_PATH);
715 bool fOrgTempFile = fTempFile;
716
717 // Create a copy of the file, if neccessary
718 if(!*szStandalone || SEqual(szStandalone, szFile))
719 {
720 if(!pParent->FindTempResFileName(szOrgFile, szFile))
721 { Log("Derive: could not find free name for temporary file!"); return NULL; }
722 if(!C4Group_CopyItem(szOrgFile, szFile))
723 { Log("Derive: could not copy to temporary file!"); return NULL; }
724 // set standalone
725 if(*szStandalone)
726 SCopy(szFile, szStandalone, _MAX_PATH);
727 fTempFile = true;
728 }
729 else
730 {
731 // Standlone exists: Just set szFile to point on the standlone. It's
732 // assumed that the original file isn't of intrest after this point anyway.
733 SCopy(szStandalone, szFile, _MAX_PATH);
734 fTempFile = true;
735 }
736
737 Application.InteractiveThread.ThreadLogS("Network: Ressource: deriving from %d:%s, original at %s", getResID(), Core.getFileName(), szFile);
738
739 // (note: should remove temp file if something fails after this point)
740
741 // create new ressource
742 C4Network2Res::Ref pDRes = new C4Network2Res(pParent);
743 if(!pDRes) return NULL;
744
745 // initialize
746 if(!pDRes->SetDerived(Core.getFileName(), szOrgFile, fOrgTempFile, getType(), getResID()))
747 return NULL;
748
749 // add to list
750 pParent->Add(pDRes);
751
752 // return new ressource
753 return pDRes;
754}
755
756bool C4Network2Res::FinishDerive() // by main thread
757{
758 // All changes have been made. Register this ressource with a new ID.
759
760 // security
761 if(!isAnonymous()) return false;
762
763 CStdLock FileLock(&FileCSec);
764 // Save back data
765 int32_t iDerID = Core.getDerID();
766 char szName[_MAX_PATH+1]; SCopy(Core.getFileName(), szName, _MAX_PATH);
767 char szFileC[_MAX_PATH+1]; SCopy(szFile, szFileC, _MAX_PATH);
768 // Set by file
769 if(!SetByFile(szFileC, fTempFile, getType(), pParent->nextResID(), szName))
770 return false;
771 // create standalone
772 if(!GetStandalone(NULL, 0, true))
773 return false;
774 // Set ID
775 Core.SetDerived(iDerID);
776 // announce derive
777 pParent->getIOClass()->BroadcastMsg(MkC4NetIOPacket(PID_NetResDerive, Core));
778 // derivation is dirty bussines
779 fDirty = true;
780 // ok
781 return true;
782}
783
784bool C4Network2Res::FinishDerive(const C4Network2ResCore &nCore)
785{
786 // security
787 if(!isAnonymous()) return false;
788 // Set core
789 Core = nCore;
790 // Set chunks (assume the ressource is complete)
791 Chunks.SetComplete(Core.getChunkCnt());
792
793 // Note that the Contents-CRC is /not/ checked. Derivation needs to be
794 // synchronized outside of C4Network2Res.
795
796 // But note that the ressource /might/ be binary compatible (though very
797 // unlikely), so do not set fNotBinaryCompatible.
798
799 // ok
800 return true;
801}
802
803C4Group *C4Network2Res::OpenAsGrp() const
804{
805 C4Group *pnGrp = new C4Group();
806 if(!pnGrp->Open(szFile))
807 {
808 delete pnGrp;
809 return NULL;
810 }
811 return pnGrp;
812}
813
814void C4Network2Res::Remove()
815{
816 // schedule for removal
817 fRemoved = true;
818}
819
820bool C4Network2Res::SendStatus(C4Network2IOConnection *pTo)
821{
822 // pack status
823 C4NetIOPacket Pkt = MkC4NetIOPacket(PID_NetResStat, C4PacketResStatus(Core.getID(), Chunks));
824 // to one client?
825 if(pTo)
826 return pTo->Send(Pkt);
827 else
828 {
829 // reset dirty flag
830 fDirty = false;
831 // broadcast status
832 assert(pParent && pParent->getIOClass());
833 return pParent->getIOClass()->BroadcastMsg(Pkt);
834 }
835}
836
837bool C4Network2Res::SendChunk(uint32_t iChunk, int32_t iToClient)
838{
839 assert(pParent && pParent->getIOClass());
840 if(!szStandalone[0] || iChunk >= Core.getChunkCnt()) return false;
841 // find connection for given client (one of the rare uses of the data connection)
842 C4Network2IOConnection *pConn = pParent->getIOClass()->GetDataConnection(iToClient);
843 if(!pConn) return false;
844 // save last request time
845 iLastReqTime = time(NULL);
846 // create packet
847 CStdLock FileLock(&FileCSec);
848 C4Network2ResChunk ResChunk;
849 ResChunk.Set(this, iChunk);
850 // send
851 bool fSuccess = pConn->Send(MkC4NetIOPacket(PID_NetResData, ResChunk));
852 pConn->DelRef();
853 return fSuccess;
854}
855
856void C4Network2Res::AddRef()
857{
858 InterlockedIncrement(&iRefCnt);
859}
860
861void C4Network2Res::DelRef()
862{
863 if(!InterlockedDecrement(&iRefCnt))
864 delete this;
865}
866
867void C4Network2Res::OnDiscover(C4Network2IOConnection *pBy)
868{
869 if(!IsBinaryCompatible()) return;
870 // discovered
871 iLastReqTime = time(NULL);
872 // send status back
873 SendStatus(pBy);
874}
875
876void C4Network2Res::OnStatus(const C4Network2ResChunkData &rChunkData, C4Network2IOConnection *pBy)
877{
878 if(!fLoading) return;
879 // discovered a source: reset timeout
880 iDiscoverStartTime = 0;
881 // check if the chunk data is valid
882 if(rChunkData.getChunkCnt() != Chunks.getChunkCnt())
883 return;
884 // add chunk data
885 ClientChunks *pChunks;
886 for(pChunks = pCChunks; pChunks; pChunks = pChunks->Next)
887 if(pChunks->ClientID == pBy->getClientID())
888 break;
889 // not found? add
890 if(!pChunks)
891 {
892 pChunks = new ClientChunks();
893 pChunks->Next = pCChunks;
894 pCChunks = pChunks;
895 }
896 pChunks->ClientID = pBy->getClientID();
897 pChunks->Chunks = rChunkData;
898 // check load
899 if(!StartLoad(pChunks->ClientID, pChunks->Chunks))
900 RemoveCChunks(pCChunks);
901}
902
903void C4Network2Res::OnChunk(const C4Network2ResChunk &rChunk)
904{
905 if(!fLoading) return;
906 // correct ressource?
907 if(rChunk.getResID() != getResID()) return;
908 // add ressource data
909 CStdLock FileLock(&FileCSec);
910 bool fSuccess = rChunk.AddTo(this, pParent->getIOClass());
911#ifdef C4NET2RES_DEBUG_LOG
912 // log
913 Application.InteractiveThread.ThreadLogS("Network: Res: %s chunk %d to ressource %s (%s)%s", fSuccess ? "added" : "could not add", rChunk.getChunkNr(), Core.getFileName(), szFile, fSuccess ? "" : "!");
914#endif
915 if(fSuccess)
916 {
917 // status changed
918 fDirty = true;
919 // remove load waits
920 for(C4Network2ResLoad *pLoad = pLoads, *pNext, *pPrev = NULL; pLoad; pPrev = pLoad, pLoad = pNext)
921 {
922 pNext = pLoad->Next();
923 if(pLoad->getChunk() == rChunk.getChunkNr())
924 RemoveLoad(pLoad);
925 }
926 }
927 // complete?
928 if(Chunks.isComplete())
929 EndLoad();
930 // check: start new loads?
931 else
932 StartNewLoads();
933}
934
935bool C4Network2Res::DoLoad()
936{
937 if(!fLoading) return true;
938 // any loads currently active?
939 if(iLoadCnt)
940 {
941 // check for load timeouts
942 int32_t iLoadsRemoved = 0;
943 for(C4Network2ResLoad *pLoad = pLoads, *pNext; pLoad; pLoad = pNext)
944 {
945 pNext = pLoad->Next();
946 if(pLoad->CheckTimeout())
947 {
948 RemoveLoad(pLoad);
949 iLoadsRemoved++;
950 }
951 }
952 // start new loads
953 if(iLoadsRemoved) StartNewLoads();
954 }
955 else
956 {
957 // discover timeout?
958 if(iDiscoverStartTime)
959 if(difftime(time(NULL), iDiscoverStartTime) > C4NetResDiscoverTimeout)
960 return false;
961 }
962 // ok
963 return true;
964}
965
966bool C4Network2Res::NeedsDiscover()
967{
968 // loading, but no active load sources?
969 if(fLoading && !iLoadCnt)
970 {
971 // set timeout, if this is the first discover
972 if(!iDiscoverStartTime)
973 iDiscoverStartTime = time(NULL);
974 // do discover
975 return true;
976 }
977 return false;
978}
979
980void C4Network2Res::Clear()
981{
982 CStdLock FileLock(&FileCSec);
983 // delete files
984 if(fTempFile)
985 if(FileExists(szFile))
986 if(remove(szFile))
987 //Log(_strerror("Network: Could not delete temporary ressource file"));
988 LogSilentF("Network: Could not delete temporary resource file (%s)", strerror(errno));
989 if(szStandalone[0] && !SEqual(szFile, szStandalone))
990 if(FileExists(szStandalone))
991 if(remove(szStandalone))
992 //Log(_strerror("Network: Could not delete temporary ressource file"));
993 LogSilentF("Network: Could not delete temporary resource file (%s)", strerror(errno));
994 szFile[0] = szStandalone[0] = '\0';
995 fDirty = false;
996 fTempFile = false;
997 Core.Clear();
998 Chunks.Clear();
999 fRemoved = false;
1000 ClearLoad();
1001}
1002
1003int32_t C4Network2Res::OpenFileRead()
1004{
1005 CStdLock FileLock(&FileCSec);
1006 if(!GetStandalone(NULL, 0, false, false, true)) return -1;
1007 return open(szStandalone, _O_BINARY | O_RDONLY);
1008}
1009
1010int32_t C4Network2Res::OpenFileWrite()
1011{
1012 CStdLock FileLock(&FileCSec);
1013 return open(szStandalone, _O_BINARY | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE);
1014}
1015
1016void C4Network2Res::StartNewLoads()
1017{
1018 if(!pCChunks) return;
1019 // count clients
1020 int32_t iCChunkCnt = 0; ClientChunks *pChunks;
1021 for(pChunks = pCChunks; pChunks; pChunks = pChunks->Next)
1022 iCChunkCnt++;
1023 // create array
1024 ClientChunks **pC = new ClientChunks *[iCChunkCnt];
1025 // initialize
1026 int32_t i;
1027 for(i = 0; i < iCChunkCnt; i++) pC[i] = NULL;
1028 // create shuffled order
1029 for(pChunks = pCChunks, i = 0; i < iCChunkCnt; i++, pChunks = pChunks->Next)
1030 {
1031 // determine position
1032 int32_t iPos = SafeRandom(iCChunkCnt - i);
1033 // find & set
1034 for(int32_t j = 0; ;j++)
1035 if(!pC[j] && !iPos--)
1036 {
1037 pC[j] = pChunks;
1038 break;
1039 }
1040 }
1041 // start new load until maximum count reached
1042 while(iLoadCnt + 1 <= C4NetResMaxLoad)
1043 {
1044 int32_t ioLoadCnt = iLoadCnt;
1045 // search someone
1046 for(i = 0; i < iCChunkCnt; i++)
1047 if(pC[i])
1048 {
1049 // try to start load
1050 if(!StartLoad(pC[i]->ClientID, pC[i]->Chunks))
1051 { RemoveCChunks(pC[i]); pC[i] = NULL; continue; }
1052 // success?
1053 if(iLoadCnt > ioLoadCnt) break;
1054 }
1055 // not found?
1056 if(i >= iCChunkCnt)
1057 break;
1058 }
1059 // clear up
1060 delete [] pC;
1061}
1062
1063bool C4Network2Res::StartLoad(int32_t iFromClient, const C4Network2ResChunkData &Available)
1064{
1065 assert(pParent && pParent->getIOClass());
1066 // all slots used? ignore
1067 if(iLoadCnt + 1 >= C4NetResMaxLoad) return true;
1068 // is there already a load by this client? ignore
1069 for(C4Network2ResLoad *pPos = pLoads; pPos; pPos = pPos->Next())
1070 if(pPos->getByClient() == iFromClient)
1071 return true;
1072 // find chunk to retrieve
1073 int32_t iLoads[C4NetResMaxLoad]; int32_t i = 0;
1074 for(C4Network2ResLoad *pLoad = pLoads; pLoad; pLoad = pLoad->Next())
1075 iLoads[i++] = pLoad->getChunk();
1076 int32_t iRetrieveChunk = Chunks.GetChunkToRetrieve(Available, i, iLoads);
1077 // nothing? ignore
1078 if(iRetrieveChunk < 0 || (uint32_t)iRetrieveChunk >= Core.getChunkCnt())
1079 return true;
1080 // search message connection for client
1081 C4Network2IOConnection *pConn = pParent->getIOClass()->GetMsgConnection(iFromClient);
1082 if(!pConn) return false;
1083 // send request
1084 if(!pConn->Send(MkC4NetIOPacket(PID_NetResReq, C4PacketResRequest(Core.getID(), iRetrieveChunk))))
1085 { pConn->DelRef(); return false; }
1086 pConn->DelRef();
1087#ifdef C4NET2RES_DEBUG_LOG
1088 // log
1089 Application.InteractiveThread.ThreadLogS("Network: Res: requesting chunk %d of %d:%s (%s) from client %d",
1090 iRetrieveChunk, Core.getID(), Core.getFileName(), szFile, iFromClient);
1091#endif
1092 // create load class
1093 C4Network2ResLoad *pnLoad = new C4Network2ResLoad(iRetrieveChunk, iFromClient);
1094 // add to list
1095 pnLoad->pNext = pLoads;
1096 pLoads = pnLoad;
1097 iLoadCnt++;
1098 // ok
1099 return true;
1100}
1101
1102void C4Network2Res::EndLoad()
1103{
1104 // clear loading data
1105 ClearLoad();
1106 // set complete
1107 fLoading = false;
1108 // call handler
1109 assert(pParent);
1110 pParent->OnResComplete(this);
1111}
1112
1113void C4Network2Res::ClearLoad()
1114{
1115 // remove client chunks and loads
1116 fLoading = false;
1117 while(pCChunks) RemoveCChunks(pCChunks);
1118 while(pLoads) RemoveLoad(pLoads);
1119 iDiscoverStartTime = iLoadCnt = 0;
1120}
1121
1122void C4Network2Res::RemoveLoad(C4Network2ResLoad *pLoad)
1123{
1124 if(pLoad == pLoads)
1125 pLoads = pLoad->Next();
1126 else
1127 {
1128 // find previous entry
1129 C4Network2ResLoad *pPrev;
1130 for(pPrev = pLoads; pPrev && pPrev->Next() != pLoad; pPrev = pPrev->Next()) {}
1131 // remove
1132 if(pPrev)
1133 pPrev->pNext = pLoad->Next();
1134 }
1135 // delete
1136 delete pLoad;
1137 iLoadCnt--;
1138}
1139
1140void C4Network2Res::RemoveCChunks(ClientChunks *pChunks)
1141{
1142 if(pChunks == pCChunks)
1143 pCChunks = pChunks->Next;
1144 else
1145 {
1146 // find previous entry
1147 ClientChunks *pPrev;
1148 for(pPrev = pCChunks; pPrev && pPrev->Next != pChunks; pPrev = pPrev->Next) {}
1149 // remove
1150 if(pPrev)
1151 pPrev->Next = pChunks->Next;
1152 }
1153 // delete
1154 delete pChunks;
1155}
1156
1157bool C4Network2Res::OptimizeStandalone(bool fSilent)
1158{
1159 CStdLock FileLock(&FileCSec);
1160 // for now: player files only
1161 if(Core.getType() == NRT_Player)
1162 {
1163 // log - this may take a few seconds
1164 if(!fSilent) LogF(LoadResStr("IDS_PRC_NETPREPARING"), GetFilename(szFile));
1165 // copy to temp file, if needed
1166 if(!fTempFile && SEqual(szFile, szStandalone))
1167 {
1168 char szNewStandalone[_MAX_PATH + 1];
1169 if(!pParent->FindTempResFileName(szStandalone, szNewStandalone))
1170 { if(!fSilent) Log("OptimizeStandalone: could not find free name for temporary file!"); return false; }
1171 if(!C4Group_CopyItem(szStandalone, szNewStandalone))
1172 { if(!fSilent) Log("OptimizeStandalone: could not copy to temporary file!"); return false; } /* TODO: Test failure */
1173 SCopy(szNewStandalone, szStandalone, sizeof(szStandalone) - 1);
1174 }
1175 // open as group
1176 C4Group Grp;
1177 if(!Grp.Open(szStandalone))
1178 { if(!fSilent) Log("OptimizeStandalone: could not open player file!"); return false; }
1179 // remove portrais
1180 Grp.Delete(C4CFN_Portraits, true);
1181 // remove bigicon, if the file size is too large
1182 size_t iBigIconSize=0;
1183 if (Grp.FindEntry(C4CFN_BigIcon, NULL, &iBigIconSize))
1184 if (iBigIconSize > C4NetResMaxBigicon*1024)
1185 Grp.Delete(C4CFN_BigIcon);
1186 Grp.Close();
1187 }
1188 return true;
1189}
1190
1191// *** C4Network2ResChunk
1192
1193C4Network2ResChunk::C4Network2ResChunk()
1194{
1195
1196}
1197
1198C4Network2ResChunk::~C4Network2ResChunk()
1199{
1200
1201}
1202
1203bool C4Network2ResChunk::Set(C4Network2Res *pRes, uint32_t inChunk)
1204{
1205 const C4Network2ResCore &Core = pRes->getCore();
1206 iResID = pRes->getResID();
1207 iChunk = inChunk;
1208 // calculate offset and size
1209 int32_t iOffset = iChunk * Core.getChunkSize(),
1210 iSize = Min<int32_t>(Core.getFileSize() - iOffset, C4NetResChunkSize);
1211 if (iSize < 0) { LogF("Network: could not get chunk from offset %d from resource file %s: File size is only %d!", iOffset, pRes->getFile(), Core.getFileSize()); return false; }
1212 // open file
1213 int32_t f = pRes->OpenFileRead();
1214 if(f == -1) { LogF("Network: could not open resource file %s!", pRes->getFile()); return false; }
1215 // seek
1216 if(iOffset)
1217 if(lseek(f, iOffset, SEEK_SET) != iOffset)
1218 { close(f); LogF("Network: could not read resource file %s!", pRes->getFile()); return false; }
1219 // read chunk of data
1220 char *pBuf = new char[iSize];
1221 if(read(f, pBuf, iSize) != iSize)
1222 { delete [] pBuf; close(f); LogF("Network: could not read resource file %s!", pRes->getFile()); return false; }
1223 // set
1224 Data.Take(pBuf, iSize);
1225 // close
1226 close(f);
1227 // ok
1228 return true;
1229}
1230
1231bool C4Network2ResChunk::AddTo(C4Network2Res *pRes, C4Network2IO *pIO) const
1232{
1233 assert(pRes); assert(pIO);
1234 const C4Network2ResCore &Core = pRes->getCore();
1235 // check
1236 if(iResID != pRes->getResID())
1237 {
1238#ifdef C4NET2RES_DEBUG_LOG
1239 Application.InteractiveThread.ThreadLogS(FormatString("C4Network2ResChunk(%d)::AddTo(%s [%d]): Ressource ID mismatch!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID()).getData());
1240#endif
1241 return false;
1242 }
1243 // calculate offset and size
1244 int32_t iOffset = iChunk * Core.getChunkSize();
1245 if(iOffset + Data.getSize() > Core.getFileSize())
1246 {
1247#ifdef C4NET2RES_DEBUG_LOG
1248 Application.InteractiveThread.ThreadLogS(FormatString("C4Network2ResChunk(%d)::AddTo(%s [%d]): Adding %d bytes at offset %d exceeds expected file size of %d!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID(), (int) Data.getSize(), (int) iOffset, (int) Core.getFileSize()).getData());
1249#endif
1250 return false;
1251 }
1252 // open file
1253 int32_t f = pRes->OpenFileWrite();
1254 if(f == -1)
1255 {
1256#ifdef C4NET2RES_DEBUG_LOG
1257 Application.InteractiveThread.ThreadLogS(FormatString("C4Network2ResChunk(%d)::AddTo(%s [%d]): Open write file error: %s!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID(), strerror(errno)).getData());
1258#endif
1259 return false;
1260 }
1261 // seek
1262 if(iOffset)
1263 if(lseek(f, iOffset, SEEK_SET) != iOffset)
1264 {
1265#ifdef C4NET2RES_DEBUG_LOG
1266 Application.InteractiveThread.ThreadLogS(FormatString("C4Network2ResChunk(%d)::AddTo(%s [%d]): lseek file error: %s!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID(), strerror(errno)).getData());
1267#endif
1268 close(f);
1269 return false;
1270 }
1271 // write
1272 if(write(f, Data.getData(), Data.getSize()) != int32_t(Data.getSize()))
1273 {
1274#ifdef C4NET2RES_DEBUG_LOG
1275 Application.InteractiveThread.ThreadLogS(FormatString("C4Network2ResChunk(%d)::AddTo(%s [%d]): write error: %s!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID(), strerror(errno)).getData());
1276#endif
1277 close(f);
1278 return false;
1279 }
1280 // ok, add chunks
1281 close(f);
1282 pRes->Chunks.AddChunk(iChunk);
1283 return true;
1284}
1285
1286void C4Network2ResChunk::CompileFunc(StdCompiler *pComp)
1287{
1288 // pack header
1289 pComp->Value(mkNamingAdapt(iResID, "ResID", -1));
1290 pComp->Value(mkNamingAdapt(iChunk, "Chunk", ~0U));
1291 // Data
1292 pComp->Value(mkNamingAdapt(Data, "Data"));
1293}
1294
1295// *** C4Network2ResList
1296
1297C4Network2ResList::C4Network2ResList()
1298 : iClientID(-1),
1299 iNextResID((-1) << 16),
1300 pFirst(NULL),
1301 ResListCSec(this),
1302 iLastDiscover(0), iLastStatus(0),
1303 pIO(NULL)
1304{
1305
1306}
1307
1308C4Network2ResList::~C4Network2ResList()
1309{
1310 Clear();
1311}
1312
1313bool C4Network2ResList::Init(int32_t inClientID, C4Network2IO *pIOClass) // by main thread
1314{
1315 // clear old list
1316 Clear();
1317 // safe IO class
1318 pIO = pIOClass;
1319 // set client id
1320 iNextResID = iClientID = 0;
1321 SetLocalID(inClientID);
1322 // create network path
1323 if(!CreateNetworkFolder()) return false;
1324 // ok
1325 return true;
1326}
1327
1328void C4Network2ResList::SetLocalID(int32_t inClientID)
1329{
1330 CStdLock ResIDLock(&ResIDCSec);
1331 int32_t iOldClientID = iClientID;
1332 int32_t iIDDiff = (inClientID - iClientID) << 16;
1333 // set new counter
1334 iClientID = inClientID;
1335 iNextResID += iIDDiff;
1336 // change ressource ids
1337 CStdLock ResListLock(&ResListCSec);
1338 for(C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
1339 if(pRes->getResClient() == iOldClientID)
1340 pRes->ChangeID(pRes->getResID() + iIDDiff);
1341}
1342
1343int32_t C4Network2ResList::nextResID() // by main thread
1344{
1345 CStdLock ResIDLock(&ResIDCSec);
1346 assert(iNextResID >= (iClientID << 16));
1347 if(iNextResID >= ((iClientID+1) << 16) - 1)
1348 iNextResID = Max<int32_t>(0, iClientID) << 16;
1349 // find free
1350 while(getRes(iNextResID))
1351 iNextResID++;
1352 return iNextResID++;
1353}
1354
1355C4Network2Res *C4Network2ResList::getRes(int32_t iResID)
1356{
1357 CStdShareLock ResListLock(&ResListCSec);
1358 for(C4Network2Res *pCur = pFirst; pCur; pCur = pCur->pNext)
1359 if(pCur->getResID() == iResID)
1360 return pCur;
1361 return NULL;
1362}
1363
1364C4Network2Res *C4Network2ResList::getRes(const char *szFile, bool fLocalOnly)
1365{
1366 CStdShareLock ResListLock(&ResListCSec);
1367 for(C4Network2Res *pCur = pFirst; pCur; pCur = pCur->pNext)
1368 if(!pCur->isAnonymous())
1369 if(SEqual(pCur->getFile(), szFile))
1370 if (!fLocalOnly || pCur->getResClient()==iClientID)
1371 return pCur;
1372 return NULL;
1373}
1374
1375C4Network2Res::Ref C4Network2ResList::getRefRes(int32_t iResID)
1376{
1377 CStdShareLock ResListLock(&ResListCSec);
1378 return getRes(iResID);
1379}
1380
1381C4Network2Res::Ref C4Network2ResList::getRefRes(const char *szFile, bool fLocalOnly)
1382{
1383 CStdShareLock ResListLock(&ResListCSec);
1384 return getRes(szFile, fLocalOnly);
1385}
1386
1387C4Network2Res::Ref C4Network2ResList::getRefNextRes(int32_t iResID)
1388{
1389 CStdShareLock ResListLock(&ResListCSec);
1390 C4Network2Res *pRes = NULL;
1391 for(C4Network2Res *pCur = pFirst; pCur; pCur = pCur->pNext)
1392 if(!pCur->isRemoved() && pCur->getResID() >= iResID)
1393 if(!pRes || pRes->getResID() > pCur->getResID())
1394 pRes = pCur;
1395 return pRes;
1396}
1397
1398void C4Network2ResList::Add(C4Network2Res *pRes)
1399{
1400 // get locks
1401 CStdShareLock ResListLock(&ResListCSec);
1402 CStdLock ResListAddLock(&ResListAddCSec);
1403 // reference
1404 pRes->AddRef();
1405 // add
1406 pRes->pNext = pFirst;
1407 pFirst = pRes;
1408}
1409
1410C4Network2Res::Ref C4Network2ResList::AddByFile(const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName, bool fAllowUnloadable)
1411{
1412 // already in list?
1413 C4Network2Res::Ref pRes = getRefRes(strFilePath);
1414 if(pRes) return pRes;
1415 // get ressource ID
1416 if(iResID < 0) iResID = nextResID();
1417 if(iResID < 0) { Log("AddByFile: no more ressource IDs available!"); return NULL; }
1418 // create new
1419 pRes = new C4Network2Res(this);
1420 // initialize
1421 if(!pRes->SetByFile(strFilePath, fTemp, eType, iResID, szResName)) { return NULL; }
1422 // create standalone for non-system files
1423 // system files shouldn't create a standalone; they should never be marked loadable!
1424 if (eType != NRT_System)
1425 if(!pRes->GetStandalone(NULL, 0, true, fAllowUnloadable))
1426 if(!fAllowUnloadable)
1427 {
1428 delete pRes;
1429 return NULL;
1430 }
1431 // add to list
1432 Add(pRes);
1433 return pRes;
1434}
1435
1436C4Network2Res::Ref C4Network2ResList::AddByGroup(C4Group *pGrp, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName, bool fAllowUnloadable)
1437{
1438 // get ressource ID
1439 if(iResID < 0) iResID = nextResID();
1440 if(iResID < 0) { Log("AddByGroup: no more ressource IDs available!"); return NULL; }
1441 // create new
1442 C4Network2Res::Ref pRes = new C4Network2Res(this);
1443 // initialize
1444 if(!pRes->SetByGroup(pGrp, fTemp, eType, iResID, szResName))
1445 {
1446 delete pRes;
1447 return NULL;
1448 }
1449 // create standalone
1450 if(!pRes->GetStandalone(NULL, 0, true, fAllowUnloadable))
1451 if(!fAllowUnloadable)
1452 {
1453 delete pRes;
1454 return NULL;
1455 }
1456 // add to list
1457 Add(pRes);
1458 return pRes;
1459}
1460
1461C4Network2Res::Ref C4Network2ResList::AddByCore(const C4Network2ResCore &Core, bool fLoad) // by main thread
1462{
1463 // already in list?
1464 C4Network2Res::Ref pRes = getRefRes(Core.getID());
1465 if(pRes) return pRes;
1466#ifdef C4NET2RES_LOAD_ALL
1467 // load without check (if possible)
1468 if(Core.isLoadable()) return AddLoad(Core);
1469#endif
1470 // create new
1471 pRes = new C4Network2Res(this);
1472 // try set by core
1473 if(!pRes->SetByCore(Core, true))
1474 {
1475 pRes.Clear();
1476 // try load (if specified)
1477 return fLoad ? AddLoad(Core) : NULL;
1478 }
1479 // log
1480 Application.InteractiveThread.ThreadLogS("Network: Found identical %s. Not loading.", pRes->getCore().getFileName());
1481 // add to list
1482 Add(pRes);
1483 // ok
1484 return pRes;
1485}
1486
1487C4Network2Res::Ref C4Network2ResList::AddLoad(const C4Network2ResCore &Core) // by main thread
1488{
1489 // marked unloadable by creator?
1490 if(!Core.isLoadable())
1491 {
1492 // show error msg
1493 Application.InteractiveThread.ThreadLog("Network: Cannot load %s (marked unloadable)", Core.getFileName());
1494 return NULL;
1495 }
1496 // create new
1497 C4Network2Res::Ref pRes = new C4Network2Res(this);
1498 // initialize
1499 pRes->SetLoad(Core);
1500 // log
1501 Application.InteractiveThread.ThreadLogS("Network: loading %s...", Core.getFileName());
1502 // add to list
1503 Add(pRes);
1504 return pRes;
1505}
1506
1507void C4Network2ResList::RemoveAtClient(int32_t iClientID) // by main thread
1508{
1509 CStdShareLock ResListLock(&ResListCSec);
1510 for(C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
1511 if(pRes->getResClient() == iClientID)
1512 pRes->Remove();
1513}
1514
1515void C4Network2ResList::Clear()
1516{
1517 CStdShareLock ResListLock(&ResListCSec);
1518 for(C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
1519 {
1520 pRes->Remove();
1521 pRes->iLastReqTime = 0;
1522 }
1523 iClientID = C4ClientIDUnknown;
1524 iLastDiscover = iLastStatus = 0;
1525}
1526
1527void C4Network2ResList::OnClientConnect(C4Network2IOConnection *pConn) // by main thread
1528{
1529 // discover ressources
1530 SendDiscover(pConn);
1531}
1532
1533void C4Network2ResList::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
1534{
1535 // security
1536 if(!pConn) return;
1537
1538 #define GETPKT(type, name) \
1539 assert(pPacket); const type &name = \
1540 /*dynamic_cast*/ static_cast<const type &>(*pPacket);
1541
1542 switch(cStatus)
1543 {
1544
1545 case PID_NetResDis: // ressource discover
1546 {
1547 if(!pConn->isOpen()) break;
1548 GETPKT(C4PacketResDiscover, Pkt);
1549 // search matching ressources
1550 CStdShareLock ResListLock(&ResListCSec);
1551 for(C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
1552 if(Pkt.isIDPresent(pRes->getResID()))
1553 // must be binary compatible
1554 if(pRes->IsBinaryCompatible())
1555 pRes->OnDiscover(pConn);
1556 }
1557 break;
1558
1559 case PID_NetResStat: // ressource status
1560 {
1561 if(!pConn->isOpen()) break;
1562 GETPKT(C4PacketResStatus, Pkt);
1563 // matching ressource?
1564 CStdShareLock ResListLock(&ResListCSec);
1565 C4Network2Res *pRes = getRes(Pkt.getResID());
1566 // present / being loaded? call handler
1567 if(pRes)
1568 pRes->OnStatus(Pkt.getChunks(), pConn);
1569 }
1570 break;
1571
1572 case PID_NetResDerive: // ressource derive
1573 {
1574 GETPKT(C4Network2ResCore, Core);
1575 if(Core.getDerID() < 0) break;
1576 // Check if there is a anonymous derived ressource with matching parent.
1577 CStdShareLock ResListLock(&ResListCSec);
1578 for(C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
1579 if(pRes->isAnonymous() && pRes->getCore().getDerID() == Core.getDerID())
1580 pRes->FinishDerive(Core);
1581 }
1582 break;
1583
1584 case PID_NetResReq: // ressource request
1585 {
1586 GETPKT(C4PacketResRequest, Pkt);
1587 // find ressource
1588 CStdShareLock ResListLock(&ResListCSec);
1589 C4Network2Res *pRes = getRes(Pkt.getReqID());
1590 // send requested chunk
1591 if(pRes && pRes->IsBinaryCompatible()) pRes->SendChunk(Pkt.getReqChunk(), pConn->getClientID());
1592 }
1593 break;
1594
1595 case PID_NetResData: // a chunk of data is coming in
1596 {
1597 GETPKT(C4Network2ResChunk, Chunk);
1598 // find ressource
1599 CStdShareLock ResListLock(&ResListCSec);
1600 C4Network2Res *pRes = getRes(Chunk.getResID());
1601 // send requested chunk
1602 if(pRes) pRes->OnChunk(Chunk);
1603 }
1604 break;
1605 }
1606 #undef GETPKT
1607}
1608
1609void C4Network2ResList::OnTimer()
1610{
1611 CStdShareLock ResListLock(&ResListCSec);
1612 C4Network2Res *pRes;
1613 // do loads, check timeouts
1614 for(pRes = pFirst; pRes; pRes = pRes->pNext)
1615 if(pRes->isLoading() && !pRes->isRemoved())
1616 if(!pRes->DoLoad())
1617 pRes->Remove();
1618 // discovery time?
1619 if(!iLastDiscover || difftime(time(NULL), iLastDiscover) >= C4NetResDiscoverInterval)
1620 {
1621 // needed?
1622 bool fSendDiscover = false;
1623 for(C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
1624 if(pRes->isLoading() && !pRes->isRemoved())
1625 fSendDiscover |= pRes->NeedsDiscover();
1626 // send
1627 if(fSendDiscover)
1628 SendDiscover();
1629 }
1630 // status update?
1631 if(!iLastStatus || difftime(time(NULL), iLastStatus) >= C4NetResStatusInterval)
1632 {
1633 // any?
1634 bool fStatusUpdates = false;
1635 for(pRes = pFirst; pRes; pRes = pRes->pNext)
1636 if(pRes->isDirty() && !pRes->isRemoved())
1637 fStatusUpdates |= pRes->SendStatus();
1638 // set time accordingly
1639 iLastStatus = fStatusUpdates ? time(NULL) : 0;
1640 }
1641}
1642
1643void C4Network2ResList::OnShareFree(CStdCSecEx *pCSec)
1644{
1645 if(pCSec == &ResListCSec)
1646 {
1647 // remove entries
1648 for(C4Network2Res *pRes = pFirst, *pNext, *pPrev = NULL; pRes; pRes = pNext)
1649 {
1650 pNext = pRes->pNext;
1651 if(pRes->isRemoved() && (!pRes->getLastReqTime() || difftime(time(NULL), pRes->getLastReqTime()) > C4NetResDeleteTime))
1652 {
1653 // unlink
1654 (pPrev ? pPrev->pNext : pFirst) = pNext;
1655 // remove
1656 pRes->pNext = NULL;
1657 pRes->DelRef();
1658 }
1659 else
1660 pPrev = pRes;
1661 }
1662 }
1663}
1664
1665bool C4Network2ResList::SendDiscover(C4Network2IOConnection *pTo) // by both
1666{
1667 // make packet
1668 C4PacketResDiscover Pkt;
1669 // add special retrieves
1670 CStdShareLock ResListLock(&ResListCSec);
1671 for(C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
1672 if(!pRes->isRemoved())
1673 if(pRes->isLoading())
1674 Pkt.AddDisID(pRes->getResID());
1675 ResListLock.Clear();
1676 // empty?
1677 if(!Pkt.getDisIDCnt()) return false;
1678 // broadcast?
1679 if(!pTo)
1680 {
1681 // save time
1682 iLastDiscover = time(NULL);
1683 // send
1684 return pIO->BroadcastMsg(MkC4NetIOPacket(PID_NetResDis, Pkt));
1685 }
1686 else
1687 return pTo->Send(MkC4NetIOPacket(PID_NetResDis, Pkt));
1688}
1689
1690void C4Network2ResList::OnResComplete(C4Network2Res *pRes)
1691{
1692 // log (network thread -> ThreadLog)
1693 Application.InteractiveThread.ThreadLogS("Network: %s received.", pRes->getCore().getFileName());
1694 // call handler (ctrl might wait for this ressource)
1695 ::Control.Network.OnResComplete(pRes);
1696}
1697
1698bool C4Network2ResList::CreateNetworkFolder()
1699{
1700 // get network path without trailing backslash
1701 char szNetworkPath[_MAX_PATH+1];
1702 SCopy(Config.AtNetworkPath(""), szNetworkPath, _MAX_PATH);
1703 TruncateBackslash(szNetworkPath);
1704 // but make sure that the configured path has one
1705 AppendBackslash(Config.Network.WorkPath);
1706 // does not exist?
1707 if(access(szNetworkPath, 00))
1708 {
1709 if(!CreatePath(szNetworkPath))
1710 { LogFatal("Network: could not create network path!"); return false; }
1711 return true;
1712 }
1713 // stat
1714 struct stat s;
1715 if(stat(szNetworkPath, &s))
1716 { LogFatal("Network: could not stat network path!"); return false; }
1717 // not a subdir?
1718 if(!(s.st_mode & S_IFDIR))
1719 { LogFatal("Network: could not create network path: blocked by a file!"); return false; }
1720 // ok
1721 return true;
1722}
1723
1724bool C4Network2ResList::FindTempResFileName(const char *szFilename, char *pTarget)
1725{
1726 char safeFilename[_MAX_PATH];
1727 char* safePos = safeFilename;
1728 while (*szFilename)
1729 {
1730 if ((*szFilename >= 'a' && *szFilename <= 'z') ||
1731 (*szFilename >= 'A' && *szFilename <= 'Z') ||
1732 (*szFilename >= '0' && *szFilename <= '9') ||
1733 (*szFilename == '.') || (*s…
Large files files are truncated, but you can click here to view the full file