openclonk/src/network/C4Network2Res.cpp

1727 lines
49 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
#include "C4Include.h"
#include "network/C4Network2Res.h"
#include "c4group/C4Components.h"
#include "c4group/C4Group.h"
#include "control/C4GameControl.h"
#include "lib/C4Random.h"
#include "game/C4Application.h"
#include <sys/types.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#endif
#ifdef _MSC_VER
#define snprintf _snprintf
#pragma warning (disable : 4355)
#endif
// compile debug options
// #define C4NET2RES_LOAD_ALL
#define C4NET2RES_DEBUG_LOG
// helper
class DirSizeHelper
{
static uint32_t iSize, iMaxSize;
static bool Helper(const char *szPath)
{
if (szPath[SLen(szPath)-1] == '.')
return false;
if (iSize > iMaxSize)
return false;
if (DirectoryExists(szPath))
ForEachFile(szPath, &Helper);
else if (FileExists(szPath))
iSize += FileSize(szPath);
return true;
}
public:
static bool GetDirSize(const char *szPath, uint32_t *pSize, uint32_t inMaxSize = ~0)
{
// Security
if (!pSize) return false;
// Fold it
iSize = 0; iMaxSize = inMaxSize;
ForEachFile(szPath, &Helper);
// Return
*pSize = iSize;
return true;
}
};
uint32_t DirSizeHelper::iSize, DirSizeHelper::iMaxSize;
// *** C4Network2ResCore
C4Network2ResCore::C4Network2ResCore()
: iFileSize(~0u), iFileCRC(~0u), iContentsCRC(~0u),
iChunkSize(C4NetResChunkSize)
{
}
void C4Network2ResCore::Set(C4Network2ResType enType, int32_t iResID, const char *strFileName, uint32_t inContentsCRC)
{
// Initialize base data
eType = enType; iID = iResID; iDerID = -1;
fLoadable = false;
iFileSize = iFileCRC = ~0; iContentsCRC = inContentsCRC;
iChunkSize = C4NetResChunkSize;
FileName.Copy(strFileName);
}
void C4Network2ResCore::SetLoadable(uint32_t iSize, uint32_t iCRC)
{
fLoadable = true;
iFileSize = iSize;
iFileCRC = iCRC;
}
void C4Network2ResCore::Clear()
{
eType = NRT_Null;
iID = iDerID = -1;
fLoadable = false;
FileName.Clear();
iFileSize = iFileCRC = iContentsCRC = ~0;
fHasFileSHA = false;
}
// C4PacketBase virtuals
void C4Network2ResCore::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(eType, C4Network2ResType_EnumMap), "Type", NRT_Null));
pComp->Value(mkNamingAdapt(iID, "ID", -1));
pComp->Value(mkNamingAdapt(iDerID, "DerID", -1));
pComp->Value(mkNamingAdapt(fLoadable, "Loadable", true));
if (fLoadable)
{
pComp->Value(mkNamingAdapt(iFileSize, "FileSize", 0U));
pComp->Value(mkNamingAdapt(iFileCRC, "FileCRC", 0U));
pComp->Value(mkNamingAdapt(iChunkSize, "ChunkSize", C4NetResChunkSize));
if (!iChunkSize) pComp->excCorrupt("zero chunk size");
}
pComp->Value(mkNamingAdapt(iContentsCRC, "ContentsCRC", 0U));
pComp->Value(mkNamingCountAdapt(fHasFileSHA, "FileSHA"));
if (fHasFileSHA)
pComp->Value(mkNamingAdapt(mkHexAdapt(FileSHA), "FileSHA"));
pComp->Value(mkNamingAdapt(mkNetFilenameAdapt(FileName), "Filename", ""));
}
// *** C4Network2ResLoad
C4Network2ResLoad::C4Network2ResLoad(int32_t inChunk, int32_t inByClient)
: iChunk(inChunk), Timestamp(time(nullptr)), iByClient(inByClient), pNext(nullptr)
{
}
C4Network2ResLoad::~C4Network2ResLoad() = default;
bool C4Network2ResLoad::CheckTimeout()
{
return difftime(time(nullptr), Timestamp) >= C4NetResLoadTimeout;
}
// *** C4Network2ResChunkData
C4Network2ResChunkData::C4Network2ResChunkData() = default;
C4Network2ResChunkData::C4Network2ResChunkData(const C4Network2ResChunkData &Data2)
: C4PacketBase(Data2),
iChunkCnt(Data2.getChunkCnt())
{
// add ranges
Merge(Data2);
}
C4Network2ResChunkData::~C4Network2ResChunkData()
{
Clear();
}
C4Network2ResChunkData &C4Network2ResChunkData::operator =(const C4Network2ResChunkData &Data2)
{
// clear, merge
SetIncomplete(Data2.getChunkCnt());
Merge(Data2);
return *this;
}
void C4Network2ResChunkData::SetIncomplete(int32_t inChunkCnt)
{
Clear();
// just set total chunk count
iChunkCnt = inChunkCnt;
}
void C4Network2ResChunkData::SetComplete(int32_t inChunkCnt)
{
Clear();
// set total chunk count
iPresentChunkCnt = iChunkCnt = inChunkCnt;
// create one range
ChunkRange *pRange = new ChunkRange;
pRange->Start = 0; pRange->Length = iChunkCnt;
pRange->Next = nullptr;
pChunkRanges = pRange;
}
void C4Network2ResChunkData::AddChunk(int32_t iChunk)
{
AddChunkRange(iChunk, 1);
}
void C4Network2ResChunkData::AddChunkRange(int32_t iStart, int32_t iLength)
{
// security
if (iStart < 0 || iStart + iLength > iChunkCnt || iLength <= 0) return;
// find position
ChunkRange *pRange, *pPrev;
for (pRange = pChunkRanges, pPrev = nullptr; pRange; pPrev = pRange, pRange = pRange->Next)
if (pRange->Start >= iStart)
break;
// create new
ChunkRange *pNew = new ChunkRange;
pNew->Start = iStart; pNew->Length = iLength;
// add to list
pNew->Next = pRange;
(pPrev ? pPrev->Next : pChunkRanges) = pNew;
// counts
iPresentChunkCnt += iLength; iChunkRangeCnt++;
// check merges
if (pPrev && MergeRanges(pPrev))
while (MergeRanges(pPrev)) {}
else
while (MergeRanges(pNew)) {}
}
void C4Network2ResChunkData::Merge(const C4Network2ResChunkData &Data2)
{
// must have same basis chunk count
assert(iChunkCnt == Data2.getChunkCnt());
// add ranges
for (ChunkRange *pRange = Data2.pChunkRanges; pRange; pRange = pRange->Next)
AddChunkRange(pRange->Start, pRange->Length);
}
void C4Network2ResChunkData::Clear()
{
iChunkCnt = iPresentChunkCnt = iChunkRangeCnt = 0;
// remove all ranges
while (pChunkRanges)
{
ChunkRange *pDelete = pChunkRanges;
pChunkRanges = pDelete->Next;
delete pDelete;
}
}
int32_t C4Network2ResChunkData::GetChunkToRetrieve(const C4Network2ResChunkData &Available, int32_t iLoadingCnt, int32_t *pLoading) const
{
// (this version is highly calculation-intensitive, yet the most satisfactory
// solution I could find)
// find everything that should not be retrieved
C4Network2ResChunkData ChData; Available.GetNegative(ChData);
ChData.Merge(*this);
for (int32_t i = 0; i < iLoadingCnt; i++)
ChData.AddChunk(pLoading[i]);
// nothing to retrieve?
if (ChData.isComplete()) return -1;
// invert to get everything that should be retrieved
C4Network2ResChunkData ChData2; ChData.GetNegative(ChData2);
// select chunk (random)
int32_t iRetrieveChunk = UnsyncedRandom(ChData2.getPresentChunkCnt());
// return
return ChData2.getPresentChunk(iRetrieveChunk);
}
bool C4Network2ResChunkData::MergeRanges(ChunkRange *pRange)
{
// no next entry?
if (!pRange || !pRange->Next) return false;
// do merge?
ChunkRange *pNext = pRange->Next;
if (pRange->Start + pRange->Length < pNext->Start) return false;
// get overlap
int32_t iOverlap = std::min((pRange->Start + pRange->Length) - pNext->Start, pNext->Length);
// set new chunk range
pRange->Length += pNext->Length - iOverlap;
// remove range
pRange->Next = pNext->Next;
delete pNext;
// counts
iChunkRangeCnt--; iPresentChunkCnt -= iOverlap;
// ok
return true;
}
void C4Network2ResChunkData::GetNegative(C4Network2ResChunkData &Target) const
{
// clear target
Target.SetIncomplete(iChunkCnt);
// add all ranges that are missing
int32_t iFreeStart = 0;
for (ChunkRange *pRange = pChunkRanges; pRange; pRange = pRange->Next)
{
// add range
Target.AddChunkRange(iFreeStart, pRange->Start - iFreeStart);
// safe new start
iFreeStart = pRange->Start + pRange->Length;
}
// add last range
Target.AddChunkRange(iFreeStart, iChunkCnt - iFreeStart);
}
int32_t C4Network2ResChunkData::getPresentChunk(int32_t iNr) const
{
for (ChunkRange *pRange = pChunkRanges; pRange; pRange = pRange->Next)
if (iNr < pRange->Length)
return iNr + pRange->Start;
else
iNr -= pRange->Length;
return -1;
}
void C4Network2ResChunkData::CompileFunc(StdCompiler *pComp)
{
bool deserializing = pComp->isDeserializer();
if (deserializing) Clear();
// Data
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iChunkCnt), "ChunkCnt", 0));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iChunkRangeCnt), "ChunkRangeCnt", 0));
// Ranges
if (!pComp->Name("Ranges"))
pComp->excCorrupt("ResChunk ranges expected!");
ChunkRange *pRange = nullptr;
for (int32_t i = 0; i < iChunkRangeCnt; i++)
{
// Create new range / go to next range
if (deserializing)
pRange = (pRange ? pRange->Next : pChunkRanges) = new ChunkRange;
else
pRange = pRange ? pRange->Next : pChunkRanges;
// Separate
if (i) pComp->Separator();
// Compile range
pComp->Value(mkIntPackAdapt(pRange->Start));
pComp->Separator(StdCompiler::SEP_PART2);
pComp->Value(mkIntPackAdapt(pRange->Length));
}
// Terminate list
if (deserializing)
(pRange ? pRange->Next : pChunkRanges) = nullptr;
pComp->NameEnd();
}
// *** C4Network2Res
C4Network2Res::C4Network2Res(C4Network2ResList *pnParent)
: fDirty(false),
fTempFile(false), fStandaloneFailed(false),
iRefCnt(0), fRemoved(false),
iLastReqTime(0),
fLoading(false),
pCChunks(nullptr), iDiscoverStartTime(0), pLoads(nullptr), iLoadCnt(0),
pNext(nullptr),
pParent(pnParent)
{
szFile[0] = szStandalone[0] = '\0';
}
C4Network2Res::~C4Network2Res()
{
assert(!pNext);
Clear();
}
bool C4Network2Res::SetByFile(const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName, bool fSilent)
{
CStdLock FileLock(&FileCSec);
// default resource name: relative path
if (!szResName) szResName = Config.AtRelativePath(strFilePath);
SCopy(strFilePath, szFile, sizeof(szFile)-1);
// group?
C4Group Grp;
if (Reloc.Open(Grp, strFilePath))
return SetByGroup(&Grp, fTemp, eType, iResID, szResName, fSilent);
// so it needs to be a file
StdStrBuf szFullFile;
if (!Reloc.LocateItem(szFile, szFullFile))
{ if (!fSilent) LogF("SetByFile: file %s not found!", strFilePath); return false; }
// calc checksum
uint32_t iCRC32;
if (!GetFileCRC(szFullFile.getData(), &iCRC32)) return false;
#ifdef C4NET2RES_DEBUG_LOG
// log
LogSilentF("Network: Resource: complete %d:%s is file %s (%s)", iResID, szResName, szFile, fTemp ? "temp" : "static");
#endif
// set core
Core.Set(eType, iResID, Config.AtRelativePath(szFullFile.getData()), iCRC32);
// set own data
fDirty = true;
fTempFile = fTemp;
fStandaloneFailed = false;
fRemoved = false;
iLastReqTime = time(nullptr);
fLoading = false;
// ok
return true;
}
bool C4Network2Res::SetByGroup(C4Group *pGrp, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName, bool fSilent) // by main thread
{
Clear();
CStdLock FileLock(&FileCSec);
// default resource name: relative path
StdStrBuf sResName;
if (szResName)
sResName = szResName;
else
{
StdStrBuf sFullName = pGrp->GetFullName();
sResName.Copy(Config.AtRelativePath(sFullName.getData()));
}
SCopy(pGrp->GetFullName().getData(), szFile, sizeof(szFile)-1);
// set core
Core.Set(eType, iResID, sResName.getData(), pGrp->EntryCRC32());
#ifdef C4NET2RES_DEBUG_LOG
// log
LogSilentF("Network: Resource: complete %d:%s is file %s (%s)", iResID, sResName.getData(), szFile, fTemp ? "temp" : "static");
#endif
// set data
fDirty = true;
fTempFile = fTemp;
fStandaloneFailed = false;
fRemoved = false;
iLastReqTime = time(nullptr);
fLoading = false;
// ok
return true;
}
bool C4Network2Res::SetByCore(const C4Network2ResCore &nCore, bool fSilent, const char *szAsFilename, int32_t iRecursion) // by main thread
{
StdStrBuf sFilename;
// try open local file
const char *szFilename = szAsFilename ? szAsFilename : GetC4Filename(nCore.getFileName());
if (SetByFile(szFilename, false, nCore.getType(), nCore.getID(), nCore.getFileName(), fSilent))
{
// check contents checksum
if (Core.getContentsCRC() == nCore.getContentsCRC())
{
// set core
fDirty = true;
Core = nCore;
// ok then
return true;
}
}
// get and search for filename without specified folder (e.g., Castle.ocs when the opened game is Easy.ocf\Castle.ocs)
const char *szFilenameOnly = GetFilename(szFilename);
const char *szFilenameC4 = GetC4Filename(szFilename);
if (szFilenameOnly != szFilenameC4)
{
sFilename.Copy(szFilename, SLen(szFilename) - SLen(szFilenameC4));
sFilename.Append(szFilenameOnly);
if (SetByCore(nCore, fSilent, szFilenameOnly, Config.Network.MaxResSearchRecursion)) return true;
}
// if it could still not be set, try within all folders of root (ignoring special folders), and try as file outside the folder
// but do not recurse any deeper than set by config (default: One folder)
if (iRecursion >= Config.Network.MaxResSearchRecursion) return false;
StdStrBuf sSearchPath; const char *szSearchPath;
if (!iRecursion)
szSearchPath = Config.General.ExePath.getData();
else
{
sSearchPath.Copy(szFilename, SLen(szFilename) - SLen(szFilenameC4));
szSearchPath = sSearchPath.getData();
}
StdStrBuf sNetPath; sNetPath.Copy(Config.Network.WorkPath);
char *szNetPath = sNetPath.GrabPointer();
TruncateBackslash(szNetPath);
sNetPath.Take(szNetPath);
for (DirectoryIterator i(szSearchPath); *i; ++i)
if (DirectoryExists(*i))
if (!*GetExtension(*i)) // directories without extension only
if (!szNetPath || !*szNetPath || !ItemIdentical(*i, szNetPath)) // ignore network path
{
// search for complete name at subpath (e.g. MyFolder\Easy.ocf\Castle.ocs)
sFilename.Format("%s%c%s", *i, DirectorySeparator, szFilenameC4);
if (SetByCore(nCore, fSilent, sFilename.getData(), iRecursion + 1))
return true;
}
// file could not be found locally
return false;
}
bool C4Network2Res::SetLoad(const C4Network2ResCore &nCore) // by main thread
{
Clear();
CStdLock FileLock(&FileCSec);
// must be loadable
if (!nCore.isLoadable()) return false;
// save core, set chunks
Core = nCore;
Chunks.SetIncomplete(Core.getChunkCnt());
// create temporary file
if (!pParent->FindTempResFileName(Core.getFileName(), szFile))
return false;
#ifdef C4NET2RES_DEBUG_LOG
// log
Application.InteractiveThread.ThreadLogS("Network: Resource: loading %d:%s to file %s", Core.getID(), Core.getFileName(), szFile);
#endif
// set standalone (result is going to be binary-compatible)
SCopy(szFile, szStandalone, sizeof(szStandalone) - 1);
// set flags
fDirty = false;
fTempFile = true;
fStandaloneFailed = false;
fRemoved = false;
iLastReqTime = time(nullptr);
fLoading = true;
// No discovery yet
iDiscoverStartTime = 0;
return true;
}
bool C4Network2Res::SetDerived(const char *strName, const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iDResID)
{
Clear();
CStdLock FileLock(&FileCSec);
// set core
Core.Set(eType, C4NetResIDAnonymous, strName, ~0);
Core.SetDerived(iDResID);
// save file path
SCopy(strFilePath, szFile, _MAX_PATH);
*szStandalone = '\0';
// set flags
fDirty = false;
fTempFile = fTemp;
fStandaloneFailed = false;
fRemoved = false;
iLastReqTime = time(nullptr);
fLoading = false;
// Do not set any chunk data - anonymous resources are very likely to change.
// Wait for FinishDerived()-call.
return true;
}
void C4Network2Res::ChangeID(int32_t inID)
{
Core.SetID(inID);
}
bool C4Network2Res::IsBinaryCompatible()
{
// returns wether the standalone of this resource is binary compatible
// to the official version (means: matches the file checksum)
CStdLock FileLock(&FileCSec);
// standalone set? ok then (see GetStandalone)
if (szStandalone[0]) return true;
// is a directory?
if (DirectoryExists(szFile))
// forget it - if the file is packed now, the creation time and author
// won't match.
return false;
// try to create the standalone
return GetStandalone(nullptr, 0, false, false, true);
}
bool C4Network2Res::GetStandalone(char *pTo, int32_t iMaxL, bool fSetOfficial, bool fAllowUnloadable, bool fSilent)
{
// already set?
if (szStandalone[0])
{
if (pTo) SCopy(szStandalone, pTo, iMaxL);
return true;
}
// already tried and failed? No point in retrying
if (fStandaloneFailed) return false;
// not loadable? Wo won't be able to check the standalone as the core will lack the needed information.
// the standalone won't be interesting in this case, anyway.
if (!fSetOfficial && !Core.isLoadable()) return false;
// set flag, so failure below will let future calls fail
fStandaloneFailed = true;
// lock file
CStdLock FileLock(&FileCSec);
// directory?
SCopy(szFile, szStandalone, sizeof(szStandalone)-1);
if (DirectoryExists(szFile))
{
// size check for the directory, if allowed
if (fAllowUnloadable)
{
uint32_t iDirSize;
if (!DirSizeHelper::GetDirSize(szFile, &iDirSize, Config.Network.MaxLoadFileSize))
{ if (!fSilent) LogF("Network: could not get directory size of %s!", szFile); szStandalone[0] = '\0'; return false; }
if (iDirSize > uint32_t(Config.Network.MaxLoadFileSize))
{ if (!fSilent) LogSilentF("Network: %s over size limit, will be marked unloadable!", szFile); szStandalone[0] = '\0'; return false; }
}
// log - this may take a few seconds
if (!fSilent) LogF(LoadResStr("IDS_PRC_NETPACKING"), GetFilename(szFile));
// pack inplace?
if (!fTempFile)
{
if (!pParent->FindTempResFileName(szFile, szStandalone))
{ if (!fSilent) Log("GetStandalone: could not find free name for temporary file!"); szStandalone[0] = '\0'; return false; }
if (!C4Group_PackDirectoryTo(szFile, szStandalone))
{ if (!fSilent) Log("GetStandalone: could not pack directory!"); szStandalone[0] = '\0'; return false; }
}
else if (!C4Group_PackDirectory(szStandalone))
{ if (!fSilent) Log("GetStandalone: could not pack directory!"); if (!SEqual(szFile, szStandalone)) EraseDirectory(szStandalone); szStandalone[0] = '\0'; return false; }
// make sure directory is packed
if (DirectoryExists(szStandalone))
{ if (!fSilent) Log("GetStandalone: directory hasn't been packed!"); if (!SEqual(szFile, szStandalone)) EraseDirectory(szStandalone); szStandalone[0] = '\0'; return false; }
// fallthru
}
// doesn't exist physically?
if (!FileExists(szStandalone))
{
// try C4Group (might be packed)
if (!pParent->FindTempResFileName(szFile, szStandalone))
{ if (!fSilent) Log("GetStandalone: could not find free name for temporary file!"); szStandalone[0] = '\0'; return false; }
if (!C4Group_CopyItem(szFile, szStandalone))
{ if (!fSilent) Log("GetStandalone: could not copy to temporary file!"); szStandalone[0] = '\0'; return false; }
}
// remains missing? give up.
if (!FileExists(szStandalone))
{ if (!fSilent) Log("GetStandalone: file not found!"); szStandalone[0] = '\0'; return false; }
// do optimizations (delete unneeded entries)
if (!OptimizeStandalone(fSilent))
{ if (!SEqual(szFile, szStandalone)) EraseItem(szStandalone); szStandalone[0] = '\0'; return false; }
// get file size
size_t iSize = FileSize(szStandalone);
// size limit
if (fAllowUnloadable)
if (iSize > uint32_t(Config.Network.MaxLoadFileSize))
{ if (!fSilent) LogSilentF("Network: %s over size limit, will be marked unloadable!", szFile); szStandalone[0] = '\0'; return false; }
// check
if (!fSetOfficial && iSize != Core.getFileSize())
{
// remove file
if (!SEqual(szFile, szStandalone)) EraseItem(szStandalone);
szStandalone[0] = '\0';
// sorry, this version isn't good enough :(
return false;
}
// calc checksum
uint32_t iCRC32;
if (!GetFileCRC(szStandalone, &iCRC32))
{ if (!fSilent) Log("GetStandalone: could not calculate checksum!"); return false; }
// set / check
if (!fSetOfficial && iCRC32 != Core.getFileCRC())
{
// remove file, return
if (!SEqual(szFile, szStandalone)) EraseItem(szStandalone);
szStandalone[0] = '\0';
return false;
}
// we didn't fail
fStandaloneFailed = false;
// mark resource as loadable and safe file information
Core.SetLoadable(iSize, iCRC32);
// set up chunk data
Chunks.SetComplete(Core.getChunkCnt());
// ok
return true;
}
bool C4Network2Res::CalculateSHA()
{
// already present?
if (Core.hasFileSHA()) return true;
// get the file
char szStandalone[_MAX_PATH + 1];
if (!GetStandalone(szStandalone, _MAX_PATH, false))
SCopy(szFile, szStandalone, _MAX_PATH);
// get the hash
BYTE hash[SHA_DIGEST_LENGTH];
if (!GetFileSHA1(szStandalone, hash))
return false;
// save it back
Core.SetFileSHA(hash);
// okay
return true;
}
C4Network2Res::Ref C4Network2Res::Derive()
{
// Called before the file is changed. Rescues all files and creates a
// new resource for the file. This resource is flagged as "anonymous", as it
// has no official core (no res ID, to be exact).
// The resource gets its final core when FinishDerive() is called.
// For security: This doesn't make much sense if the resource is currently being
// loaded. So better assume the caller doesn't know what he's doing and check.
if (isLoading()) return nullptr;
CStdLock FileLock(&FileCSec);
// Save back original file name
char szOrgFile[_MAX_PATH+1];
SCopy(szFile, szOrgFile, _MAX_PATH);
bool fOrgTempFile = fTempFile;
// Create a copy of the file, if neccessary
if (!*szStandalone || SEqual(szStandalone, szFile))
{
if (!pParent->FindTempResFileName(szOrgFile, szFile))
{ Log("Derive: could not find free name for temporary file!"); return nullptr; }
if (!C4Group_CopyItem(szOrgFile, szFile))
{ Log("Derive: could not copy to temporary file!"); return nullptr; }
// set standalone
if (*szStandalone)
SCopy(szFile, szStandalone, _MAX_PATH);
fTempFile = true;
}
else
{
// Standlone exists: Just set szFile to point on the standlone. It's
// assumed that the original file isn't of intrest after this point anyway.
SCopy(szStandalone, szFile, _MAX_PATH);
fTempFile = true;
}
Application.InteractiveThread.ThreadLogS("Network: Resource: deriving from %d:%s, original at %s", getResID(), Core.getFileName(), szFile);
// (note: should remove temp file if something fails after this point)
// create new resource
C4Network2Res::Ref pDRes = new C4Network2Res(pParent);
if (!pDRes) return nullptr;
// initialize
if (!pDRes->SetDerived(Core.getFileName(), szOrgFile, fOrgTempFile, getType(), getResID()))
return nullptr;
// add to list
pParent->Add(pDRes);
// return new resource
return pDRes;
}
bool C4Network2Res::FinishDerive() // by main thread
{
// All changes have been made. Register this resource with a new ID.
// security
if (!isAnonymous()) return false;
CStdLock FileLock(&FileCSec);
// Save back data
int32_t iDerID = Core.getDerID();
char szName[_MAX_PATH+1]; SCopy(Core.getFileName(), szName, _MAX_PATH);
char szFileC[_MAX_PATH+1]; SCopy(szFile, szFileC, _MAX_PATH);
// Set by file
if (!SetByFile(szFileC, fTempFile, getType(), pParent->nextResID(), szName))
return false;
// create standalone
if (!GetStandalone(nullptr, 0, true))
return false;
// Set ID
Core.SetDerived(iDerID);
// announce derive
pParent->getIOClass()->BroadcastMsg(MkC4NetIOPacket(PID_NetResDerive, Core));
// derivation is dirty bussines
fDirty = true;
// ok
return true;
}
bool C4Network2Res::FinishDerive(const C4Network2ResCore &nCore)
{
// security
if (!isAnonymous()) return false;
// Set core
Core = nCore;
// Set chunks (assume the resource is complete)
Chunks.SetComplete(Core.getChunkCnt());
// Note that the Contents-CRC is /not/ checked. Derivation needs to be
// synchronized outside of C4Network2Res.
// But note that the resource /might/ be binary compatible (though very
// unlikely), so do not set fNotBinaryCompatible.
// ok
return true;
}
C4Group *C4Network2Res::OpenAsGrp() const
{
C4Group *pnGrp = new C4Group();
if (!pnGrp->Open(szFile))
{
delete pnGrp;
return nullptr;
}
return pnGrp;
}
void C4Network2Res::Remove()
{
// schedule for removal
fRemoved = true;
}
bool C4Network2Res::SendStatus(C4Network2IOConnection *pTo)
{
// pack status
C4NetIOPacket Pkt = MkC4NetIOPacket(PID_NetResStat, C4PacketResStatus(Core.getID(), Chunks));
// to one client?
if (pTo)
return pTo->Send(Pkt);
else
{
// reset dirty flag
fDirty = false;
// broadcast status
assert(pParent && pParent->getIOClass());
return pParent->getIOClass()->BroadcastMsg(Pkt);
}
}
bool C4Network2Res::SendChunk(uint32_t iChunk, int32_t iToClient)
{
assert(pParent && pParent->getIOClass());
if (!szStandalone[0] || iChunk >= Core.getChunkCnt()) return false;
// find connection for given client (one of the rare uses of the data connection)
C4Network2IOConnection *pConn = pParent->getIOClass()->GetDataConnection(iToClient);
if (!pConn) return false;
// save last request time
iLastReqTime = time(nullptr);
// create packet
CStdLock FileLock(&FileCSec);
C4Network2ResChunk ResChunk;
ResChunk.Set(this, iChunk);
// send
bool fSuccess = pConn->Send(MkC4NetIOPacket(PID_NetResData, ResChunk));
pConn->DelRef();
return fSuccess;
}
void C4Network2Res::AddRef()
{
++iRefCnt;
}
void C4Network2Res::DelRef()
{
if (--iRefCnt == 0)
delete this;
}
void C4Network2Res::OnDiscover(C4Network2IOConnection *pBy)
{
if (!IsBinaryCompatible()) return;
// discovered
iLastReqTime = time(nullptr);
// send status back
SendStatus(pBy);
}
void C4Network2Res::OnStatus(const C4Network2ResChunkData &rChunkData, C4Network2IOConnection *pBy)
{
if (!fLoading) return;
// discovered a source: reset timeout
iDiscoverStartTime = 0;
// check if the chunk data is valid
if (rChunkData.getChunkCnt() != Chunks.getChunkCnt())
return;
// add chunk data
ClientChunks *pChunks;
for (pChunks = pCChunks; pChunks; pChunks = pChunks->Next)
if (pChunks->ClientID == pBy->getClientID())
break;
// not found? add
if (!pChunks)
{
pChunks = new ClientChunks();
pChunks->Next = pCChunks;
pCChunks = pChunks;
}
pChunks->ClientID = pBy->getClientID();
pChunks->Chunks = rChunkData;
// check load
if (!StartLoad(pChunks->ClientID, pChunks->Chunks))
RemoveCChunks(pCChunks);
}
void C4Network2Res::OnChunk(const C4Network2ResChunk &rChunk)
{
if (!fLoading) return;
// correct resource?
if (rChunk.getResID() != getResID()) return;
// add resource data
CStdLock FileLock(&FileCSec);
bool fSuccess = rChunk.AddTo(this, pParent->getIOClass());
#ifdef C4NET2RES_DEBUG_LOG
// log
Application.InteractiveThread.ThreadLogS("Network: Res: %s chunk %d to resource %s (%s)%s", fSuccess ? "added" : "could not add", rChunk.getChunkNr(), Core.getFileName(), szFile, fSuccess ? "" : "!");
#endif
if (fSuccess)
{
// status changed
fDirty = true;
// remove load waits
for (C4Network2ResLoad *pLoad = pLoads, *pNext; pLoad; pLoad = pNext)
{
pNext = pLoad->Next();
if (static_cast<uint32_t>(pLoad->getChunk()) == rChunk.getChunkNr())
RemoveLoad(pLoad);
}
}
// complete?
if (Chunks.isComplete())
EndLoad();
// check: start new loads?
else
StartNewLoads();
}
bool C4Network2Res::DoLoad()
{
if (!fLoading) return true;
// any loads currently active?
if (iLoadCnt)
{
// check for load timeouts
int32_t iLoadsRemoved = 0;
for (C4Network2ResLoad *pLoad = pLoads, *pNext; pLoad; pLoad = pNext)
{
pNext = pLoad->Next();
if (pLoad->CheckTimeout())
{
RemoveLoad(pLoad);
iLoadsRemoved++;
}
}
// start new loads
if (iLoadsRemoved) StartNewLoads();
}
else
{
// discover timeout?
if (iDiscoverStartTime)
if (difftime(time(nullptr), iDiscoverStartTime) > C4NetResDiscoverTimeout)
return false;
}
// ok
return true;
}
bool C4Network2Res::NeedsDiscover()
{
// loading, but no active load sources?
if (fLoading && !iLoadCnt)
{
// set timeout, if this is the first discover
if (!iDiscoverStartTime)
iDiscoverStartTime = time(nullptr);
// do discover
return true;
}
return false;
}
void C4Network2Res::Clear()
{
CStdLock FileLock(&FileCSec);
// delete files
if (fTempFile)
if (FileExists(szFile))
if (!EraseFile(szFile))
LogSilentF("Network: Could not delete temporary resource file (%s)", strerror(errno));
if (szStandalone[0] && !SEqual(szFile, szStandalone))
if (FileExists(szStandalone))
if (!EraseFile(szStandalone))
LogSilentF("Network: Could not delete temporary resource file (%s)", strerror(errno));
szFile[0] = szStandalone[0] = '\0';
fDirty = false;
fTempFile = false;
Core.Clear();
Chunks.Clear();
fRemoved = false;
ClearLoad();
}
int32_t C4Network2Res::OpenFileRead()
{
CStdLock FileLock(&FileCSec);
if (!GetStandalone(nullptr, 0, false, false, true)) return -1;
// FIXME: Use standard OC file access api here
#ifdef _WIN32
return _wopen(GetWideChar(szStandalone), _O_BINARY | O_RDONLY);
#else
return open(szStandalone, _O_BINARY | O_CLOEXEC | O_RDONLY);
#endif
}
int32_t C4Network2Res::OpenFileWrite()
{
CStdLock FileLock(&FileCSec);
// FIXME: Use standard OC file access api here
#ifdef _WIN32
return _wopen(GetWideChar(szStandalone), _O_BINARY | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE);
#else
return open(szStandalone, _O_BINARY | O_CLOEXEC | O_CREAT | O_WRONLY, S_IREAD | S_IWRITE);
#endif
}
void C4Network2Res::StartNewLoads()
{
if (!pCChunks) return;
// count clients
int32_t iCChunkCnt = 0; ClientChunks *pChunks;
for (pChunks = pCChunks; pChunks; pChunks = pChunks->Next)
iCChunkCnt++;
// create array
ClientChunks **pC = new ClientChunks *[iCChunkCnt];
// initialize
int32_t i;
for (i = 0; i < iCChunkCnt; i++) pC[i] = nullptr;
// create shuffled order
for (pChunks = pCChunks, i = 0; i < iCChunkCnt; i++, pChunks = pChunks->Next)
{
// determine position
int32_t iPos = UnsyncedRandom(iCChunkCnt - i);
// find & set
for (int32_t j = 0; ; j++)
if (!pC[j] && !iPos--)
{
pC[j] = pChunks;
break;
}
}
// start new load until maximum count reached
while (iLoadCnt + 1 <= C4NetResMaxLoad)
{
int32_t ioLoadCnt = iLoadCnt;
// search someone
for (i = 0; i < iCChunkCnt; i++)
if (pC[i])
{
// try to start load
if (!StartLoad(pC[i]->ClientID, pC[i]->Chunks))
{ RemoveCChunks(pC[i]); pC[i] = nullptr; continue; }
// success?
if (iLoadCnt > ioLoadCnt) break;
}
// not found?
if (i >= iCChunkCnt)
break;
}
// clear up
delete [] pC;
}
bool C4Network2Res::StartLoad(int32_t iFromClient, const C4Network2ResChunkData &Available)
{
assert(pParent && pParent->getIOClass());
// all slots used? ignore
if (iLoadCnt + 1 >= C4NetResMaxLoad) return true;
// is there already a load by this client? ignore
for (C4Network2ResLoad *pPos = pLoads; pPos; pPos = pPos->Next())
if (pPos->getByClient() == iFromClient)
return true;
// find chunk to retrieve
int32_t iLoads[C4NetResMaxLoad]; int32_t i = 0;
for (C4Network2ResLoad *pLoad = pLoads; pLoad; pLoad = pLoad->Next())
iLoads[i++] = pLoad->getChunk();
int32_t iRetrieveChunk = Chunks.GetChunkToRetrieve(Available, i, iLoads);
// nothing? ignore
if (iRetrieveChunk < 0 || (uint32_t)iRetrieveChunk >= Core.getChunkCnt())
return true;
// search message connection for client
C4Network2IOConnection *pConn = pParent->getIOClass()->GetMsgConnection(iFromClient);
if (!pConn) return false;
// send request
if (!pConn->Send(MkC4NetIOPacket(PID_NetResReq, C4PacketResRequest(Core.getID(), iRetrieveChunk))))
{ pConn->DelRef(); return false; }
pConn->DelRef();
#ifdef C4NET2RES_DEBUG_LOG
// log
Application.InteractiveThread.ThreadLogS("Network: Res: requesting chunk %d of %d:%s (%s) from client %d",
iRetrieveChunk, Core.getID(), Core.getFileName(), szFile, iFromClient);
#endif
// create load class
C4Network2ResLoad *pnLoad = new C4Network2ResLoad(iRetrieveChunk, iFromClient);
// add to list
pnLoad->pNext = pLoads;
pLoads = pnLoad;
iLoadCnt++;
// ok
return true;
}
void C4Network2Res::EndLoad()
{
// clear loading data
ClearLoad();
// set complete
fLoading = false;
// call handler
assert(pParent);
pParent->OnResComplete(this);
}
void C4Network2Res::ClearLoad()
{
// remove client chunks and loads
fLoading = false;
while (pCChunks) RemoveCChunks(pCChunks);
while (pLoads) RemoveLoad(pLoads);
iDiscoverStartTime = iLoadCnt = 0;
}
void C4Network2Res::RemoveLoad(C4Network2ResLoad *pLoad)
{
if (pLoad == pLoads)
pLoads = pLoad->Next();
else
{
// find previous entry
C4Network2ResLoad *pPrev;
for (pPrev = pLoads; pPrev && pPrev->Next() != pLoad; pPrev = pPrev->Next()) {}
// remove
if (pPrev)
pPrev->pNext = pLoad->Next();
}
// delete
delete pLoad;
iLoadCnt--;
}
void C4Network2Res::RemoveCChunks(ClientChunks *pChunks)
{
if (pChunks == pCChunks)
pCChunks = pChunks->Next;
else
{
// find previous entry
ClientChunks *pPrev;
for (pPrev = pCChunks; pPrev && pPrev->Next != pChunks; pPrev = pPrev->Next) {}
// remove
if (pPrev)
pPrev->Next = pChunks->Next;
}
// delete
delete pChunks;
}
bool C4Network2Res::OptimizeStandalone(bool fSilent)
{
CStdLock FileLock(&FileCSec);
// for now: player files only
if (Core.getType() == NRT_Player)
{
// log - this may take a few seconds
if (!fSilent) LogF(LoadResStr("IDS_PRC_NETPREPARING"), GetFilename(szFile));
// copy to temp file, if needed
if (!fTempFile && SEqual(szFile, szStandalone))
{
char szNewStandalone[_MAX_PATH + 1];
if (!pParent->FindTempResFileName(szStandalone, szNewStandalone))
{ if (!fSilent) Log("OptimizeStandalone: could not find free name for temporary file!"); return false; }
if (!C4Group_CopyItem(szStandalone, szNewStandalone))
{ if (!fSilent) Log("OptimizeStandalone: could not copy to temporary file!"); return false; } /* TODO: Test failure */
SCopy(szNewStandalone, szStandalone, sizeof(szStandalone) - 1);
}
// open as group
C4Group Grp;
if (!Grp.Open(szStandalone))
{ if (!fSilent) Log("OptimizeStandalone: could not open player file!"); return false; }
// remove bigicon, if the file size is too large
size_t iBigIconSize=0;
if (Grp.FindEntry(C4CFN_BigIcon, nullptr, &iBigIconSize))
if (iBigIconSize > C4NetResMaxBigicon*1024)
Grp.Delete(C4CFN_BigIcon);
Grp.Close();
}
return true;
}
// *** C4Network2ResChunk
C4Network2ResChunk::C4Network2ResChunk() = default;
C4Network2ResChunk::~C4Network2ResChunk() = default;
bool C4Network2ResChunk::Set(C4Network2Res *pRes, uint32_t inChunk)
{
const C4Network2ResCore &Core = pRes->getCore();
iResID = pRes->getResID();
iChunk = inChunk;
// calculate offset and size
int32_t iOffset = iChunk * Core.getChunkSize(),
iSize = std::min<int32_t>(Core.getFileSize() - iOffset, C4NetResChunkSize);
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; }
// open file
int32_t f = pRes->OpenFileRead();
if (f == -1) { LogF("Network: could not open resource file %s!", pRes->getFile()); return false; }
// seek
if (iOffset)
if (lseek(f, iOffset, SEEK_SET) != iOffset)
{ close(f); LogF("Network: could not read resource file %s!", pRes->getFile()); return false; }
// read chunk of data
char *pBuf = (char *) malloc(iSize);
if (read(f, pBuf, iSize) != iSize)
{ free(pBuf); close(f); LogF("Network: could not read resource file %s!", pRes->getFile()); return false; }
// set
Data.Take(pBuf, iSize);
// close
close(f);
// ok
return true;
}
bool C4Network2ResChunk::AddTo(C4Network2Res *pRes, C4Network2IO *pIO) const
{
assert(pRes); assert(pIO);
const C4Network2ResCore &Core = pRes->getCore();
// check
if (iResID != pRes->getResID())
{
#ifdef C4NET2RES_DEBUG_LOG
Application.InteractiveThread.ThreadLogS("C4Network2ResChunk(%d)::AddTo(%s [%d]): Resource ID mismatch!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID());
#endif
return false;
}
// calculate offset and size
int32_t iOffset = iChunk * Core.getChunkSize();
if (iOffset + Data.getSize() > Core.getFileSize())
{
#ifdef C4NET2RES_DEBUG_LOG
Application.InteractiveThread.ThreadLogS("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());
#endif
return false;
}
// open file
int32_t f = pRes->OpenFileWrite();
if (f == -1)
{
#ifdef C4NET2RES_DEBUG_LOG
Application.InteractiveThread.ThreadLogS("C4Network2ResChunk(%d)::AddTo(%s [%d]): Open write file error: %s!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID(), strerror(errno));
#endif
return false;
}
// seek
if (iOffset)
if (lseek(f, iOffset, SEEK_SET) != iOffset)
{
#ifdef C4NET2RES_DEBUG_LOG
Application.InteractiveThread.ThreadLogS("C4Network2ResChunk(%d)::AddTo(%s [%d]): lseek file error: %s!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID(), strerror(errno));
#endif
close(f);
return false;
}
// write
if (write(f, Data.getData(), Data.getSize()) != int32_t(Data.getSize()))
{
#ifdef C4NET2RES_DEBUG_LOG
Application.InteractiveThread.ThreadLogS("C4Network2ResChunk(%d)::AddTo(%s [%d]): write error: %s!", (int) iResID, (const char *) Core.getFileName(), (int) pRes->getResID(), strerror(errno));
#endif
close(f);
return false;
}
// ok, add chunks
close(f);
pRes->Chunks.AddChunk(iChunk);
return true;
}
void C4Network2ResChunk::CompileFunc(StdCompiler *pComp)
{
// pack header
pComp->Value(mkNamingAdapt(iResID, "ResID", -1));
pComp->Value(mkNamingAdapt(iChunk, "Chunk", ~0U));
// Data
pComp->Value(mkNamingAdapt(Data, "Data"));
}
// *** C4Network2ResList
C4Network2ResList::C4Network2ResList()
: ResListCSec(this),
iNextResID((-1) << 16)
{
}
C4Network2ResList::~C4Network2ResList()
{
Clear();
}
bool C4Network2ResList::Init(int32_t inClientID, C4Network2IO *pIOClass) // by main thread
{
// clear old list
Clear();
// safe IO class
pIO = pIOClass;
// set client id
iNextResID = iClientID = 0;
SetLocalID(inClientID);
// create network path
if (!CreateNetworkFolder()) return false;
// ok
return true;
}
void C4Network2ResList::SetLocalID(int32_t inClientID)
{
CStdLock ResIDLock(&ResIDCSec);
int32_t iOldClientID = iClientID;
int32_t iIDDiff = (inClientID - iClientID) << 16;
// set new counter
iClientID = inClientID;
iNextResID += iIDDiff;
// change resource ids
CStdLock ResListLock(&ResListCSec);
for (C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
if (pRes->getResClient() == iOldClientID)
pRes->ChangeID(pRes->getResID() + iIDDiff);
}
int32_t C4Network2ResList::nextResID() // by main thread
{
CStdLock ResIDLock(&ResIDCSec);
assert(iNextResID >= (iClientID << 16));
if (iNextResID >= ((iClientID+1) << 16) - 1)
iNextResID = std::max<int32_t>(0, iClientID) << 16;
// find free
while (getRes(iNextResID))
iNextResID++;
return iNextResID++;
}
C4Network2Res *C4Network2ResList::getRes(int32_t iResID)
{
CStdShareLock ResListLock(&ResListCSec);
for (C4Network2Res *pCur = pFirst; pCur; pCur = pCur->pNext)
if (pCur->getResID() == iResID)
return pCur;
return nullptr;
}
C4Network2Res *C4Network2ResList::getRes(const char *szFile, bool fLocalOnly)
{
CStdShareLock ResListLock(&ResListCSec);
for (C4Network2Res *pCur = pFirst; pCur; pCur = pCur->pNext)
if (!pCur->isAnonymous())
if (SEqual(pCur->getFile(), szFile))
if (!fLocalOnly || pCur->getResClient()==iClientID)
return pCur;
return nullptr;
}
C4Network2Res::Ref C4Network2ResList::getRefRes(int32_t iResID)
{
CStdShareLock ResListLock(&ResListCSec);
return getRes(iResID);
}
C4Network2Res::Ref C4Network2ResList::getRefRes(const char *szFile, bool fLocalOnly)
{
CStdShareLock ResListLock(&ResListCSec);
return getRes(szFile, fLocalOnly);
}
C4Network2Res::Ref C4Network2ResList::getRefNextRes(int32_t iResID)
{
CStdShareLock ResListLock(&ResListCSec);
C4Network2Res *pRes = nullptr;
for (C4Network2Res *pCur = pFirst; pCur; pCur = pCur->pNext)
if (!pCur->isRemoved() && pCur->getResID() >= iResID)
if (!pRes || pRes->getResID() > pCur->getResID())
pRes = pCur;
return pRes;
}
void C4Network2ResList::Add(C4Network2Res *pRes)
{
// get locks
CStdShareLock ResListLock(&ResListCSec);
CStdLock ResListAddLock(&ResListAddCSec);
// reference
pRes->AddRef();
// add
pRes->pNext = pFirst;
pFirst = pRes;
}
C4Network2Res::Ref C4Network2ResList::AddByFile(const char *strFilePath, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName, bool fAllowUnloadable)
{
// already in list?
C4Network2Res::Ref pRes = getRefRes(strFilePath);
if (pRes) return pRes;
// get resource ID
if (iResID < 0) iResID = nextResID();
if (iResID < 0) { Log("AddByFile: no more resource IDs available!"); return nullptr; }
// create new
pRes = new C4Network2Res(this);
// initialize
if (!pRes->SetByFile(strFilePath, fTemp, eType, iResID, szResName)) { return nullptr; }
// create standalone for non-system files
// system files shouldn't create a standalone; they should never be marked loadable!
if (eType != NRT_System)
if (!pRes->GetStandalone(nullptr, 0, true, fAllowUnloadable))
if (!fAllowUnloadable)
{
delete pRes;
return nullptr;
}
// add to list
Add(pRes);
return pRes;
}
C4Network2Res::Ref C4Network2ResList::AddByGroup(C4Group *pGrp, bool fTemp, C4Network2ResType eType, int32_t iResID, const char *szResName, bool fAllowUnloadable)
{
// get resource ID
if (iResID < 0) iResID = nextResID();
if (iResID < 0) { Log("AddByGroup: no more resource IDs available!"); return nullptr; }
// create new
C4Network2Res::Ref pRes = new C4Network2Res(this);
// initialize
if (!pRes->SetByGroup(pGrp, fTemp, eType, iResID, szResName))
{
delete pRes;
return nullptr;
}
// create standalone
if (!pRes->GetStandalone(nullptr, 0, true, fAllowUnloadable))
if (!fAllowUnloadable)
{
delete pRes;
return nullptr;
}
// add to list
Add(pRes);
return pRes;
}
C4Network2Res::Ref C4Network2ResList::AddByCore(const C4Network2ResCore &Core, bool fLoad) // by main thread
{
// already in list?
C4Network2Res::Ref pRes = getRefRes(Core.getID());
if (pRes) return pRes;
#ifdef C4NET2RES_LOAD_ALL
// load without check (if possible)
if (Core.isLoadable()) return AddLoad(Core);
#endif
// create new
pRes = new C4Network2Res(this);
// try set by core
if (!pRes->SetByCore(Core, true))
{
pRes.Clear();
// try load (if specified)
return fLoad ? AddLoad(Core) : nullptr;
}
// log
Application.InteractiveThread.ThreadLogS("Network: Found identical %s. Not loading.", pRes->getCore().getFileName());
// add to list
Add(pRes);
// ok
return pRes;
}
C4Network2Res::Ref C4Network2ResList::AddLoad(const C4Network2ResCore &Core) // by main thread
{
// marked unloadable by creator?
if (!Core.isLoadable())
{
// show error msg
Application.InteractiveThread.ThreadLog("Network: Cannot load %s (marked unloadable)", Core.getFileName());
return nullptr;
}
// create new
C4Network2Res::Ref pRes = new C4Network2Res(this);
// initialize
pRes->SetLoad(Core);
// log
Application.InteractiveThread.ThreadLogS("Network: loading %s...", Core.getFileName());
// add to list
Add(pRes);
return pRes;
}
void C4Network2ResList::RemoveAtClient(int32_t iClientID) // by main thread
{
CStdShareLock ResListLock(&ResListCSec);
for (C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
if (pRes->getResClient() == iClientID)
pRes->Remove();
}
void C4Network2ResList::Clear()
{
CStdShareLock ResListLock(&ResListCSec);
for (C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
{
pRes->Remove();
pRes->iLastReqTime = 0;
}
iClientID = C4ClientIDUnknown;
iLastDiscover = iLastStatus = 0;
}
void C4Network2ResList::OnClientConnect(C4Network2IOConnection *pConn) // by main thread
{
// discover resources
SendDiscover(pConn);
}
void C4Network2ResList::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
{
// security
if (!pConn) return;
#define GETPKT(type, name) \
assert(pPacket); const type &name = \
static_cast<const type &>(*pPacket);
switch (cStatus)
{
case PID_NetResDis: // resource discover
{
if (!pConn->isOpen()) break;
GETPKT(C4PacketResDiscover, Pkt);
// search matching resources
CStdShareLock ResListLock(&ResListCSec);
for (C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
if (Pkt.isIDPresent(pRes->getResID()))
// must be binary compatible
if (pRes->IsBinaryCompatible())
pRes->OnDiscover(pConn);
}
break;
case PID_NetResStat: // resource status
{
if (!pConn->isOpen()) break;
GETPKT(C4PacketResStatus, Pkt);
// matching resource?
CStdShareLock ResListLock(&ResListCSec);
C4Network2Res *pRes = getRes(Pkt.getResID());
// present / being loaded? call handler
if (pRes)
pRes->OnStatus(Pkt.getChunks(), pConn);
}
break;
case PID_NetResDerive: // resource derive
{
GETPKT(C4Network2ResCore, Core);
if (Core.getDerID() < 0) break;
// Check if there is a anonymous derived resource with matching parent.
CStdShareLock ResListLock(&ResListCSec);
for (C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
if (pRes->isAnonymous() && pRes->getCore().getDerID() == Core.getDerID())
pRes->FinishDerive(Core);
}
break;
case PID_NetResReq: // resource request
{
GETPKT(C4PacketResRequest, Pkt);
// find resource
CStdShareLock ResListLock(&ResListCSec);
C4Network2Res *pRes = getRes(Pkt.getReqID());
// send requested chunk
if (pRes && pRes->IsBinaryCompatible()) pRes->SendChunk(Pkt.getReqChunk(), pConn->getClientID());
}
break;
case PID_NetResData: // a chunk of data is coming in
{
GETPKT(C4Network2ResChunk, Chunk);
// find resource
CStdShareLock ResListLock(&ResListCSec);
C4Network2Res *pRes = getRes(Chunk.getResID());
// send requested chunk
if (pRes) pRes->OnChunk(Chunk);
}
break;
}
#undef GETPKT
}
void C4Network2ResList::OnTimer()
{
CStdShareLock ResListLock(&ResListCSec);
C4Network2Res *pRes;
// do loads, check timeouts
for (pRes = pFirst; pRes; pRes = pRes->pNext)
if (pRes->isLoading() && !pRes->isRemoved())
if (!pRes->DoLoad())
pRes->Remove();
// discovery time?
if (!iLastDiscover || difftime(time(nullptr), iLastDiscover) >= C4NetResDiscoverInterval)
{
// needed?
bool fSendDiscover = false;
for (C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
if (pRes->isLoading() && !pRes->isRemoved())
fSendDiscover |= pRes->NeedsDiscover();
// send
if (fSendDiscover)
SendDiscover();
}
// status update?
if (!iLastStatus || difftime(time(nullptr), iLastStatus) >= C4NetResStatusInterval)
{
// any?
bool fStatusUpdates = false;
for (pRes = pFirst; pRes; pRes = pRes->pNext)
if (pRes->isDirty() && !pRes->isRemoved())
fStatusUpdates |= pRes->SendStatus();
// set time accordingly
iLastStatus = fStatusUpdates ? time(nullptr) : 0;
}
}
void C4Network2ResList::OnShareFree(CStdCSecEx *pCSec)
{
if (pCSec == &ResListCSec)
{
// remove entries
for (C4Network2Res *pRes = pFirst, *pNext, *pPrev = nullptr; pRes; pRes = pNext)
{
pNext = pRes->pNext;
if (pRes->isRemoved() && (!pRes->getLastReqTime() || difftime(time(nullptr), pRes->getLastReqTime()) > C4NetResDeleteTime))
{
// unlink
(pPrev ? pPrev->pNext : pFirst) = pNext;
// remove
pRes->pNext = nullptr;
pRes->DelRef();
}
else
pPrev = pRes;
}
}
}
bool C4Network2ResList::SendDiscover(C4Network2IOConnection *pTo) // by both
{
// make packet
C4PacketResDiscover Pkt;
// add special retrieves
CStdShareLock ResListLock(&ResListCSec);
for (C4Network2Res *pRes = pFirst; pRes; pRes = pRes->pNext)
if (!pRes->isRemoved())
if (pRes->isLoading())
Pkt.AddDisID(pRes->getResID());
ResListLock.Clear();
// empty?
if (!Pkt.getDisIDCnt()) return false;
// broadcast?
if (!pTo)
{
// save time
iLastDiscover = time(nullptr);
// send
return pIO->BroadcastMsg(MkC4NetIOPacket(PID_NetResDis, Pkt));
}
else
return pTo->Send(MkC4NetIOPacket(PID_NetResDis, Pkt));
}
void C4Network2ResList::OnResComplete(C4Network2Res *pRes)
{
// log (network thread -> ThreadLog)
Application.InteractiveThread.ThreadLogS("Network: %s received.", pRes->getCore().getFileName());
// call handler (ctrl might wait for this resource)
::Control.Network.OnResComplete(pRes);
}
bool C4Network2ResList::CreateNetworkFolder()
{
// get network path without trailing backslash
char szNetworkPath[_MAX_PATH+1];
SCopy(Config.AtNetworkPath(""), szNetworkPath, _MAX_PATH);
TruncateBackslash(szNetworkPath);
// but make sure that the configured path has one
AppendBackslash(Config.Network.WorkPath);
// does not exist?
if (!DirectoryExists(szNetworkPath))
{
if (!CreatePath(szNetworkPath))
{ LogFatal("Network: could not create network path!"); return false; }
return true;
}
return true;
}
bool C4Network2ResList::FindTempResFileName(const char *szFilename, char *pTarget)
{
char safeFilename[_MAX_PATH];
char* safePos = safeFilename;
while (*szFilename)
{
if ((*szFilename >= 'a' && *szFilename <= 'z') ||
(*szFilename >= 'A' && *szFilename <= 'Z') ||
(*szFilename >= '0' && *szFilename <= '9') ||
(*szFilename == '.') || (*szFilename == '/'))
*safePos = *szFilename;
else
*safePos = '_';
++safePos;
++szFilename;
}
*safePos = 0;
szFilename = safeFilename;
// create temporary file
SCopy(Config.AtNetworkPath(GetFilename(szFilename)), pTarget, _MAX_PATH);
// file name is free?
if (!ItemExists(pTarget)) return true;
// find another file name
char szFileMask[_MAX_PATH+1];
SCopy(pTarget, szFileMask, GetExtension(pTarget)-1-pTarget);
SAppend("_%d", szFileMask, _MAX_PATH);
SAppend(GetExtension(pTarget)-1, szFileMask, _MAX_PATH);
for (int32_t i = 2; i < 1000; i++)
{
snprintf(pTarget, _MAX_PATH, szFileMask, i);
// doesn't exist?
if (!ItemExists(pTarget))
return true;
}
// not found
return false;
}