/* * 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 "game/C4Application.h" #include "lib/C4Random.h" #include "config/C4Config.h" #include "lib/C4Log.h" #include "c4group/C4Group.h" #include "c4group/C4Components.h" #include "game/C4Game.h" #include "control/C4GameControl.h" #include #include #include #ifdef _WIN32 #include #endif #include #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() : eType(NRT_Null), iID(-1), iDerID(-1), fLoadable(false), iFileSize(~0u), iFileCRC(~0u), iContentsCRC(~0u), fHasFileSHA(false), 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(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() { } bool C4Network2ResLoad::CheckTimeout() { return difftime(time(nullptr), Timestamp) >= C4NetResLoadTimeout; } // *** C4Network2ResChunkData C4Network2ResChunkData::C4Network2ResChunkData() : iChunkCnt(0), iPresentChunkCnt(0), pChunkRanges(nullptr), iChunkRangeCnt(0) { } C4Network2ResChunkData::C4Network2ResChunkData(const C4Network2ResChunkData &Data2) : C4PacketBase(Data2), iChunkCnt(Data2.getChunkCnt()), iPresentChunkCnt(0), pChunkRanges(nullptr), iChunkRangeCnt(0) { // 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 fCompiler = pComp->isCompiler(); if (fCompiler) 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 (fCompiler) 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 (fCompiler) (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() { InterlockedIncrement(&iRefCnt); } void C4Network2Res::DelRef() { if (!InterlockedDecrement(&iRefCnt)) 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(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() { } C4Network2ResChunk::~C4Network2ResChunk() { } 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(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() : pFirst(nullptr), ResListCSec(this), iClientID(-1), iNextResID((-1) << 16), iLastDiscover(0), iLastStatus(0), pIO(nullptr) { } 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(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(*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; }