@@ -18,7 +18,13 @@ struct tImgHeader
1818};
1919
2020CClientIMG::CClientIMG (class CClientManager * pManager, ElementID ID )
21- : ClassInit(this ), CClientEntity(ID ), m_pImgManager(pManager->GetIMGManager ()), m_ucArchiveID(INVALID_ARCHIVE_ID ), m_LargestFileSizeBlocks(0 )
21+ : ClassInit(this ),
22+ CClientEntity(ID ),
23+ m_pImgManager(pManager->GetIMGManager ()),
24+ m_ucArchiveID(INVALID_ARCHIVE_ID ),
25+ m_LargestFileSizeBlocks(0 ),
26+ m_bFolderMode(false ),
27+ m_bTempImgCreated(false )
2228{
2329 m_pManager = pManager;
2430 SetTypeName (" img" );
@@ -36,7 +42,7 @@ void CClientIMG::Unlink()
3642 if (IsStreamed ())
3743 StreamDisable ();
3844
39- if (m_ifs.is_open ())
45+ if (m_ifs.is_open () || m_bFolderMode )
4046 Unload ();
4147}
4248
@@ -101,6 +107,39 @@ void CClientIMG::Unload()
101107 m_fileInfos.clear ();
102108 m_fileInfos.shrink_to_fit ();
103109 m_ifs.close ();
110+
111+ if (m_bFolderMode)
112+ {
113+ m_folderFilePaths.clear ();
114+ m_folderFilePaths.shrink_to_fit ();
115+
116+ if (m_bTempImgCreated && !m_tempImgPath.empty ())
117+ {
118+ std::error_code ec;
119+ fs::remove (m_tempImgPath, ec);
120+ m_tempImgPath.clear ();
121+ }
122+
123+ m_bTempImgCreated = false ;
124+ m_bFolderMode = false ;
125+ }
126+
127+ if (!m_tempImgDirPath.empty ())
128+ {
129+ std::error_code ec;
130+ for (const auto & entry : fs::directory_iterator (m_tempImgDirPath, ec))
131+ {
132+ if (ec)
133+ break ;
134+
135+ if (!entry.is_regular_file ())
136+ continue ;
137+
138+ const auto filename = entry.path ().filename ().string ();
139+ if (filename.find (" runtime-image" ) != std::string::npos)
140+ fs::remove (entry.path (), ec);
141+ }
142+ }
104143}
105144
106145bool CClientIMG::GetFile (size_t fileID, std::string& buffer)
@@ -120,6 +159,17 @@ bool CClientIMG::GetFile(size_t fileID, std::string& buffer)
120159 throw std::invalid_argument (" Out of memory" );
121160 }
122161
162+ if (!m_ifs.is_open ())
163+ {
164+ m_ifs = std::ifstream (m_filePath, std::ios::binary);
165+ if (m_ifs.fail ())
166+ {
167+ m_ifs.close ();
168+ return false ;
169+ }
170+ }
171+
172+ m_ifs.clear ();
123173 m_ifs.seekg ((std::streampos)pFileInfo->uiOffset * 2048 );
124174 m_ifs.read (buffer.data (), toReadBytes);
125175
@@ -148,6 +198,192 @@ bool CClientIMG::IsStreamed()
148198 return m_ucArchiveID != INVALID_ARCHIVE_ID ;
149199}
150200
201+ bool CClientIMG::LoadFolder (fs::path folderPath, fs::path tempDirPath)
202+ {
203+ if (!m_fileInfos.empty ())
204+ return false ;
205+
206+ if (folderPath.empty ())
207+ return false ;
208+
209+ if (!fs::is_directory (folderPath))
210+ return false ;
211+
212+ m_tempImgDirPath = std::move (tempDirPath);
213+
214+ if (!m_tempImgDirPath.empty ())
215+ {
216+ std::error_code ec;
217+ fs::create_directories (m_tempImgDirPath, ec);
218+ if (ec)
219+ return false ;
220+
221+ for (const auto & entry : fs::directory_iterator (m_tempImgDirPath, ec))
222+ {
223+ if (ec)
224+ break ;
225+
226+ if (!entry.is_regular_file ())
227+ continue ;
228+
229+ const auto filename = entry.path ().filename ().string ();
230+ if (filename.find (" runtime-image" ) != std::string::npos)
231+ fs::remove (entry.path (), ec);
232+ }
233+ }
234+
235+ // Collect regular files and sort alphabetically for deterministic ordering
236+ std::vector<fs::directory_entry> entries;
237+ for (const auto & entry : fs::directory_iterator (folderPath))
238+ {
239+ if (entry.is_regular_file ())
240+ entries.push_back (entry);
241+ }
242+ std::sort (entries.begin (), entries.end (), [](const auto & a, const auto & b) { return a.path ().filename () < b.path ().filename (); });
243+
244+ CResourceManager* pResourceManager = m_pManager ? m_pManager->GetResourceManager () : nullptr ;
245+
246+ unsigned int uiCurrentOffset = 0 ;
247+ for (const auto & entry : entries)
248+ {
249+ const auto & filePath = entry.path ();
250+ const auto filename = filePath.filename ().string ();
251+
252+ // tImgFileInfo::szFileName is 24 bytes (23 usable chars + null terminator)
253+ if (filename.size () > 23 )
254+ continue ;
255+
256+ if (pResourceManager)
257+ {
258+ SString strFilePath = SharedUtil::ToUTF8 (filePath.wstring ());
259+ SString strConformed = PathConform (strFilePath).ToLower ();
260+
261+ // Include only files known to the resource download manager
262+ if (!pResourceManager->GetDownloadableResourceFile (strConformed))
263+ continue ;
264+ }
265+
266+ std::error_code ec;
267+ const auto fileSize = entry.file_size (ec);
268+ if (ec || fileSize == 0 )
269+ continue ;
270+
271+ const auto sizeInBlocks = static_cast <unsigned short >((fileSize + 2047 ) / 2048 );
272+
273+ tImgFileInfo fileInfo{};
274+ fileInfo.uiOffset = uiCurrentOffset;
275+ fileInfo.usSize = sizeInBlocks;
276+ fileInfo.usUnpackedSize = 0 ;
277+ std::strncpy (fileInfo.szFileName , filename.c_str (), 23 );
278+ fileInfo.szFileName [23 ] = ' \0 ' ;
279+
280+ m_fileInfos.push_back (fileInfo);
281+ m_folderFilePaths.push_back (filePath);
282+
283+ uiCurrentOffset += sizeInBlocks;
284+ }
285+
286+ if (m_fileInfos.empty ())
287+ return false ;
288+
289+ m_bFolderMode = true ;
290+ return true ;
291+ }
292+
293+ bool CClientIMG::BuildTempIMG ()
294+ {
295+ if (m_tempImgDirPath.empty ())
296+ return false ;
297+
298+ std::error_code ec;
299+ fs::create_directories (m_tempImgDirPath, ec);
300+ if (ec)
301+ return false ;
302+
303+ const unsigned long long base = static_cast <unsigned long long >(GetTickCount64_ ());
304+ const unsigned long long pid = static_cast <unsigned long long >(GetCurrentProcessId ());
305+ const std::wstring fileName = L" runtime-image-" + std::to_wstring (base) + L" -" + std::to_wstring (pid) + L" .tmp" ;
306+ m_tempImgPath = m_tempImgDirPath / fileName;
307+
308+ ec.clear ();
309+ if (fs::exists (m_tempImgPath, ec) || ec)
310+ return false ;
311+
312+ std::ofstream ofs (m_tempImgPath, std::ios::binary | std::ios::trunc);
313+ if (!ofs.is_open ())
314+ {
315+ fs::remove (m_tempImgPath);
316+ return false ;
317+ }
318+
319+ std::string readBuf;
320+ std::vector<char > zeroPad;
321+
322+ for (size_t i = 0 ; i < m_folderFilePaths.size (); i++)
323+ {
324+ const auto & filePath = m_folderFilePaths[i];
325+ const auto & fileInfo = m_fileInfos[i];
326+
327+ std::error_code ec;
328+ const auto fileSize = fs::file_size (filePath, ec);
329+ if (ec)
330+ {
331+ ofs.close ();
332+ fs::remove (m_tempImgPath);
333+ return false ;
334+ }
335+
336+ try
337+ {
338+ readBuf.resize (fileSize);
339+ }
340+ catch (const std::bad_alloc&)
341+ {
342+ ofs.close ();
343+ fs::remove (m_tempImgPath);
344+ return false ;
345+ }
346+
347+ std::ifstream ifs (filePath, std::ios::binary);
348+ if (!ifs.is_open ())
349+ {
350+ ofs.close ();
351+ fs::remove (m_tempImgPath);
352+ return false ;
353+ }
354+
355+ ifs.read (readBuf.data (), fileSize);
356+ if (ifs.fail ())
357+ {
358+ ofs.close ();
359+ fs::remove (m_tempImgPath);
360+ return false ;
361+ }
362+
363+ ofs.write (readBuf.data (), fileSize);
364+
365+ const size_t paddedSize = static_cast <size_t >(fileInfo.usSize ) * 2048 ;
366+ const size_t paddingNeeded = paddedSize - fileSize;
367+ if (paddingNeeded > 0 )
368+ {
369+ zeroPad.assign (paddingNeeded, ' \0 ' );
370+ ofs.write (zeroPad.data (), paddingNeeded);
371+ }
372+
373+ if (ofs.fail ())
374+ {
375+ ofs.close ();
376+ fs::remove (m_tempImgPath);
377+ return false ;
378+ }
379+ }
380+
381+ ofs.close ();
382+ m_filePath = m_tempImgPath;
383+ m_bTempImgCreated = true ;
384+ return true ;
385+ }
386+
151387bool CClientIMG::StreamEnable ()
152388{
153389 if (m_fileInfos.empty ())
@@ -156,6 +392,12 @@ bool CClientIMG::StreamEnable()
156392 if (IsStreamed ())
157393 return false ;
158394
395+ if (m_bFolderMode && !m_bTempImgCreated)
396+ {
397+ if (!BuildTempIMG ())
398+ return false ;
399+ }
400+
159401 if (m_LargestFileSizeBlocks == 0 )
160402 {
161403 for (const auto & fileInfo : m_fileInfos)
0 commit comments