PageRenderTime 229ms CodeModel.GetById 16ms app.highlight 193ms RepoModel.GetById 1ms app.codeStats 0ms

/src/network/C4Network2Res.cpp

https://bitbucket.org/randrian/openclonk2
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