Pak.cpp 84 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************
  5. TODO: implement in the Editor Pak Generation / Publishing:
  6. Caching of compressed files for future re-use, perhaps in the Project's "Temp" folder, with user permission?
  7. Or better reuse existing PAK's, save to new and rename to old on finish.
  8. /******************************************************************************
  9. 'Pak._file_name' should always include normalized full path
  10. 'PakSet._lock' potentially could be a 'SimpleReadWriteSync', however tests show that 1 thread (most likely scenario) is faster with 'SyncLock'
  11. Test Results for doing simplified version of 'Paks.find' on multiple threads (in operations per second)
  12. SyncLock SimpleReadWriteSync
  13. 1 Thread : 50 33
  14. 2 Threads: 34 74
  15. 3 Threads: 34 90
  16. /******************************************************************************/
  17. #define CC4_PAK CC4('P', 'A', 'K', 0)
  18. /******************************************************************************/
  19. PakSet Paks;
  20. /******************************************************************************/
  21. File* DataSource::open(File &temp)C
  22. {
  23. switch(type)
  24. {
  25. default : return null;
  26. case FILE : return file;
  27. case PAK_FILE: if(pak_file && pak && temp.readTry (*pak_file, *pak ))return &temp; return null; // have to return null on fail
  28. case NAME : if( temp.readTry ( name ))return &temp; return null; // have to return null on fail
  29. case STD : if( temp.readStdTry( name ))return &temp; return null; // have to return null on fail
  30. case MEM : temp.readMem ( memory, memory_size);return &temp;
  31. }
  32. }
  33. File* DataSource::openRaw(File &temp)C
  34. {
  35. switch(type)
  36. {
  37. default : return null;
  38. case FILE : return file;
  39. case PAK_FILE: if(pak_file && pak && temp.readTryRaw(*pak_file, *pak ))return &temp; return null; // have to return null on fail
  40. case NAME : if( temp.readTry ( name ))return &temp; return null; // have to return null on fail
  41. case STD : if( temp.readStdTry( name ))return &temp; return null; // have to return null on fail
  42. case MEM : temp.readMem ( memory, memory_size);return &temp;
  43. }
  44. }
  45. Long DataSource::size()C
  46. {
  47. switch(type)
  48. {
  49. default : return 0;
  50. case FILE : return file ? file-> size() : 0;
  51. case PAK_FILE: return pak_file ? pak_file->data_size : 0;
  52. case NAME : {FileInfo fi(name); return fi.type ? fi.size : -1;} // have to return -1 on error
  53. case STD : {FileInfoSystem fi(name); return fi.type ? fi.size : -1;} // have to return -1 on error
  54. case MEM : return memory_size;
  55. }
  56. }
  57. Long DataSource::sizeCompressed()C
  58. {
  59. switch(type)
  60. {
  61. default : return 0;
  62. case FILE : return file ? file-> size() : 0;
  63. case PAK_FILE: return pak_file ? pak_file->data_size_compressed : 0;
  64. case NAME : {FileInfo fi(name); return fi.type ? fi.size : -1;} // have to return -1 on error
  65. case STD : {FileInfoSystem fi(name); return fi.type ? fi.size : -1;} // have to return -1 on error
  66. case MEM : return memory_size;
  67. }
  68. }
  69. Str DataSource::srcName()C
  70. {
  71. switch(type)
  72. {
  73. case PAK_FILE: if(pak && pak_file)return pak->fullName(*pak_file); break;
  74. case NAME:
  75. case STD : return name;
  76. }
  77. return S;
  78. }
  79. /******************************************************************************/
  80. Bool PakProgress::wantStop(Str *error_message)C
  81. {
  82. if(stop)
  83. {
  84. if(error_message)*error_message="Stopped on user request";
  85. return true;
  86. }
  87. return false;
  88. }
  89. /******************************************************************************/
  90. PakFile& PakFile::reset()
  91. {
  92. // !! if a new member is added to 'PakFile' then it must be set in 'PakCreator.create' !!
  93. name=null;
  94. flag=0;
  95. compression=COMPRESS_NONE;
  96. parent=-1;
  97. children_offset=children_num=0;
  98. data_offset=0;
  99. data_size=data_size_compressed=0;
  100. data_xxHash64_32=0;
  101. modify_time_utc.zero();
  102. return T;
  103. }
  104. PakFile& PakFile::type(FSTD_TYPE type)
  105. {
  106. FlagSet(flag, PF_STD_DIR , type==FSTD_DIR );
  107. FlagSet(flag, PF_STD_LINK, type==FSTD_LINK);
  108. return T;
  109. }
  110. /******************************************************************************/
  111. // PAK
  112. /******************************************************************************/
  113. void Pak::zero()
  114. {
  115. // !! if a new member is added to Pak then it must be set in PakCreator.create !!
  116. _root_files =0;
  117. _data_offset =0;
  118. _cipher_per_file =false;
  119. _file_type =0;
  120. _file_cipher_offset=0;
  121. _file_cipher =null;
  122. _data =null;
  123. }
  124. Pak& Pak::del()
  125. {
  126. _files .del();
  127. _names .del();
  128. _file_name .del();
  129. _data_decompressed.del();
  130. zero(); return T;
  131. }
  132. Pak::Pak() {zero();}
  133. /******************************************************************************/
  134. #pragma pack(push, 1) // use 1 because there are usually many files and any useless bytes contribute to bigger PAK headers, also we need the PAK headers hashes to always be the same, while gaps between members could result in undefined values which could make the hashes different
  135. struct PakFile4
  136. {
  137. Byte flag ;
  138. COMPRESS_TYPE compression ;
  139. Int name_offset ,
  140. parent ,
  141. children_offset ,
  142. children_num ;
  143. ULong data_offset ;
  144. UInt data_size ,
  145. data_size_compressed,
  146. data_xxHash64_32 ;
  147. Byte second ,
  148. minute ,
  149. hour ,
  150. day ,
  151. month ;
  152. U16 year ;
  153. };
  154. struct PakFile3
  155. {
  156. Byte flag ;
  157. Int name_offset ,
  158. parent ,
  159. children_offset ,
  160. children_num ;
  161. ULong data_offset ;
  162. UInt data_size ,
  163. data_size_compressed,
  164. data_xxHash32 ;
  165. DateTime modify_time_utc ;
  166. };
  167. #pragma pack(pop)
  168. #pragma pack(push, 4)
  169. struct PakFile2
  170. {
  171. Byte flag ;
  172. Int name_offset ,
  173. parent ,
  174. children_offset ,
  175. children_num ;
  176. ULong data_offset ;
  177. UInt data_size ,
  178. data_size_compressed,
  179. data_crc32 ;
  180. DateTime modify_time_utc ;
  181. };
  182. struct PakFile1
  183. {
  184. Byte flag ;
  185. Int name_offset ,
  186. parent ,
  187. children_offset,
  188. children_num ;
  189. ULong data_offset ,
  190. data_size ;
  191. DateTime modify_time_utc;
  192. };
  193. struct PakFile0
  194. {
  195. Int name_offset ,
  196. parent ,
  197. children_offset,
  198. children_num ,
  199. data_offset ,
  200. data_size ;
  201. DateTime modify_time_utc;
  202. };
  203. #pragma pack(pop)
  204. Byte GetOldFlag(Byte flag)
  205. {
  206. return FlagTest(flag, 1<<1)*PF_REMOVED
  207. | FlagTest(flag, 1<<2)*PF_STD_DIR
  208. //| FlagTest(flag, 1<<3)*PF_NO_COMPRESS
  209. | FlagTest(flag, 1<<4)*PF_STD_LINK;
  210. }
  211. /******************************************************************************/
  212. Bool Pak::saveHeader(File &f)C
  213. {
  214. Memt<PakFile4> filei; filei.setNum(totalFiles());
  215. FREPA(filei)
  216. {
  217. PakFile4 &dest=filei[i];
  218. C PakFile &src =file (i);
  219. _Unaligned(dest.name_offset , src.name-_names.data() );
  220. Unaligned(dest.flag , src.flag );
  221. Unaligned(dest.compression , src.compression );
  222. Unaligned(dest.parent , src.parent );
  223. Unaligned(dest.children_offset , src.children_offset );
  224. Unaligned(dest.children_num , src.children_num );
  225. Unaligned(dest.data_offset , src.data_offset );
  226. Unaligned(dest.data_size , src.data_size );
  227. Unaligned(dest.data_size_compressed, src.data_size_compressed );
  228. Unaligned(dest.data_xxHash64_32 , src.data_xxHash64_32 );
  229. Unaligned(dest.second , src.modify_time_utc.second);
  230. Unaligned(dest.minute , src.modify_time_utc.minute);
  231. Unaligned(dest.hour , src.modify_time_utc.hour );
  232. Unaligned(dest.day , src.modify_time_utc.day );
  233. Unaligned(dest.month , src.modify_time_utc.month );
  234. _Unaligned(dest.year , src.modify_time_utc.year );
  235. }
  236. f.putUInt (CC4_PAK); // CC4
  237. f.cmpUIntV( 4); // version
  238. f.cmpUIntV(_root_files);
  239. if(_names.saveRaw(f))
  240. if( filei.saveRaw(f))
  241. return f.ok();
  242. return false;
  243. }
  244. PAK_LOAD Pak::loadHeader(File &f, Long *expected_size, Long *actual_size)
  245. {
  246. if(expected_size)*expected_size=0;
  247. if( actual_size)* actual_size=0;
  248. del();
  249. PAK_LOAD result=PAK_LOAD_INCOMPLETE_HEADER;
  250. ULong data_size=0;
  251. Bool fix_compressed=false;
  252. UInt cc4=f.getUInt();
  253. if( cc4==CC4_PAK
  254. || cc4==CC4('P', 'A', 'K', 1) // don't remove this as there are still some *.EsenthelProject files using it
  255. )switch(f.decUIntV()) // version
  256. {
  257. default: result=PAK_LOAD_UNSUPPORTED_VERSION; break;
  258. case 4:
  259. {
  260. // main
  261. f.decUIntV(_root_files);
  262. // names
  263. if(_names.loadRaw(f))
  264. {
  265. // files
  266. Memt<PakFile4> filei; if(filei.loadRaw(f))
  267. {
  268. _files.setNum(filei.elms()); REPA(_files)
  269. {
  270. PakFile &dest=_files[i];
  271. C PakFile4 &src = filei[i];
  272. dest.name =_names.data()+Unaligned(src.name_offset);
  273. Unaligned(dest.flag , src.flag );
  274. Unaligned(dest.compression , src.compression );
  275. Unaligned(dest.parent , src.parent );
  276. Unaligned(dest.children_offset , src.children_offset );
  277. Unaligned(dest.children_num , src.children_num );
  278. Unaligned(dest.data_offset , src.data_offset );
  279. Unaligned(dest.data_size , src.data_size );
  280. Unaligned(dest.data_size_compressed , src.data_size_compressed);
  281. Unaligned(dest.data_xxHash64_32 , src.data_xxHash64_32 );
  282. Unaligned(dest.modify_time_utc.second, src.second );
  283. Unaligned(dest.modify_time_utc.minute, src.minute );
  284. Unaligned(dest.modify_time_utc.hour , src.hour );
  285. Unaligned(dest.modify_time_utc.day , src.day );
  286. Unaligned(dest.modify_time_utc.month , src.month );
  287. _Unaligned(dest.modify_time_utc.year , src.year );
  288. MAX(data_size, dest.data_offset+dest.data_size_compressed);
  289. }
  290. _cipher_per_file=true;
  291. goto ok;
  292. }
  293. }
  294. }break;
  295. case 3:
  296. {
  297. // main
  298. f>>_root_files;
  299. // names
  300. if(_names._loadRaw(f))
  301. {
  302. // files
  303. Memt<PakFile3> filei; if(filei._loadRaw(f))
  304. {
  305. _files.setNum(filei.elms()); REPA(_files)
  306. {
  307. PakFile &dest=_files[i];
  308. C PakFile3 &src = filei[i];
  309. dest.name =_names.data()+Unaligned(src.name_offset);
  310. Unaligned(dest.flag , GetOldFlag(src.flag) );
  311. Unaligned(dest.compression , COMPRESS_NONE );
  312. Unaligned(dest.parent , src.parent );
  313. Unaligned(dest.children_offset , src.children_offset );
  314. Unaligned(dest.children_num , src.children_num );
  315. Unaligned(dest.data_offset , src.data_offset );
  316. Unaligned(dest.data_size , src.data_size );
  317. Unaligned(dest.data_size_compressed, src.data_size_compressed);
  318. _Unaligned(dest.data_xxHash64_32 , 0); // src.data_xxHash32 - this version used xxHash32
  319. Unaligned(dest.modify_time_utc , src.modify_time_utc );
  320. MAX(data_size, dest.data_offset+dest.data_size_compressed);
  321. }
  322. _cipher_per_file=true;
  323. fix_compressed=true;
  324. goto ok;
  325. }
  326. }
  327. }break;
  328. case 2:
  329. {
  330. // main
  331. f>>_root_files;
  332. // names
  333. if(_names._loadRaw(f))
  334. {
  335. // files
  336. Memt<PakFile2> filei; if(filei._loadRaw(f))
  337. {
  338. _files.setNum(filei.elms()); REPA(_files)
  339. {
  340. PakFile &dest=_files[i];
  341. C PakFile2 &src = filei[i];
  342. dest.name =_names.data()+src.name_offset ;
  343. dest.flag = GetOldFlag(src.flag );
  344. dest.compression = COMPRESS_NONE;
  345. dest.parent = src.parent ;
  346. dest.children_offset = src.children_offset ;
  347. dest.children_num = src.children_num ;
  348. dest.data_offset = src.data_offset ;
  349. dest.data_size = src.data_size ;
  350. dest.data_size_compressed= src.data_size_compressed;
  351. dest.data_xxHash64_32 = 0; // src.data_crc32 - this version used CRC32
  352. dest.modify_time_utc = src.modify_time_utc ;
  353. MAX(data_size, dest.data_offset+dest.data_size_compressed);
  354. }
  355. _cipher_per_file=false;
  356. fix_compressed=true;
  357. goto ok;
  358. }
  359. }
  360. }break;
  361. case 1:
  362. {
  363. // main
  364. Int files=0;
  365. f>>_root_files>>files;
  366. // names
  367. if(_names._loadRaw(f))
  368. {
  369. // files
  370. _files.setNum(files);
  371. Memt<PakFile1> filei; filei.setNum(files); if(f.getN(filei.data(), filei.elms()))
  372. {
  373. REPA(_files)
  374. {
  375. PakFile &dest=_files[i];
  376. PakFile1 &src = filei[i];
  377. dest.name =_names.data()+src.name_offset ;
  378. dest.flag = GetOldFlag(src.flag );
  379. dest.compression = COMPRESS_NONE;
  380. dest.parent = src.parent ;
  381. dest.children_offset = src.children_offset;
  382. dest.children_num = src.children_num ;
  383. dest.data_offset = src.data_offset ;
  384. dest.data_size = src.data_size ;
  385. dest.data_size_compressed= src.data_size ;
  386. dest.data_xxHash64_32 = 0;
  387. dest.modify_time_utc = src.modify_time_utc;
  388. MAX(data_size, dest.data_offset+dest.data_size_compressed);
  389. }
  390. _cipher_per_file=false;
  391. fix_compressed=true;
  392. goto ok;
  393. }
  394. }
  395. }break;
  396. case 0:
  397. {
  398. // main
  399. Int files=0;
  400. f>>_root_files>>files; UInt dat_ofs=f.getUInt(); f.skip(4); // 4=data_size
  401. // names
  402. if(_names._loadRaw(f))
  403. {
  404. // files
  405. _files.setNum(files);
  406. Memt<PakFile0> filei; filei.setNum(files); if(f.getN(filei.data(), filei.elms()))
  407. {
  408. REPA(_files)
  409. {
  410. PakFile &dest=_files[i];
  411. PakFile0 &src = filei[i];
  412. dest.name =_names.data()+src.name_offset ;
  413. dest.flag = 0;
  414. dest.compression = COMPRESS_NONE;
  415. dest.parent = src.parent ;
  416. dest.children_offset = src.children_offset;
  417. dest.children_num = src.children_num ;
  418. dest.data_offset = Max(0, src.data_offset-dat_ofs); // old 'data_offset' already included Pak data_offset
  419. dest.data_size = src.data_size ;
  420. dest.data_size_compressed= src.data_size ;
  421. dest.data_xxHash64_32 = 0;
  422. dest.modify_time_utc = src.modify_time_utc;
  423. MAX(data_size, dest.data_offset+dest.data_size_compressed);
  424. }
  425. _cipher_per_file=false;
  426. fix_compressed=true;
  427. goto ok;
  428. }
  429. }
  430. }break;
  431. }else result=PAK_LOAD_NOT_PAK;
  432. del(); return result;
  433. ok:
  434. _file_type =f._type;
  435. _file_cipher_offset=f._cipher_offset+f.pos(); // use existing cipher offset adjusted by current position where data starts ('pos' and not 'posAbs')
  436. _file_cipher =f._cipher; if(!_file_cipher)_cipher_per_file=true; // if there's no cipher at all, then force '_cipher_per_file' because it speeds up '_cipher_offset' calculations in files, it's not going to be needed anyway
  437. _data_offset =f.posAbs();
  438. data_size +=f.pos (); // this needs to be 'pos' and not 'posAbs'
  439. if(fix_compressed) // old versions stored compressed files with an extra header per file
  440. {
  441. Long pos=f.pos(); FREPA(_files)
  442. {
  443. PakFile &pf=_files[i]; if(pf.data_size!=pf.data_size_compressed)
  444. {
  445. Long p=_data_offset+pf.data_offset;
  446. COMPRESS_TYPE compress; ULong compressed_size, decompressed_size;
  447. if((f._cipher && _cipher_per_file) // this can't work
  448. || !f.pos(p)
  449. || !DecompressHeader(f, compress, compressed_size, decompressed_size)){del(); return PAK_LOAD_UNSUPPORTED_VERSION;}
  450. pf.compression = compress ;
  451. pf.data_size_compressed= compressed_size;
  452. pf.data_size =decompressed_size;
  453. pf.data_offset +=f.pos()-p;
  454. }
  455. }
  456. f.pos(pos);
  457. }
  458. if(expected_size)*expected_size=data_size;
  459. if( actual_size)* actual_size= f.size();
  460. return (data_size>f.size()) ? PAK_LOAD_INCOMPLETE_DATA : PAK_LOAD_OK;
  461. }
  462. PAK_LOAD Pak::loadMemEx(CPtr data, Int size, Cipher *cipher, Long *expected_size, Long *actual_size)
  463. {
  464. File f; f.readMem(data, size, cipher);
  465. PAK_LOAD result=loadHeader(f, expected_size, actual_size);
  466. switch( result)
  467. {
  468. case PAK_LOAD_INCOMPLETE_DATA:
  469. case PAK_LOAD_OK : T._data=data; return result;
  470. }
  471. //del(); no need to call because 'loadHeader' does that already
  472. return result;
  473. }
  474. PAK_LOAD Pak::loadEx(C Str &name, Cipher *cipher, Long pak_offset, Long *expected_size, Long *actual_size)
  475. {
  476. if(expected_size)*expected_size=0;
  477. if( actual_size)* actual_size=0;
  478. PAK_LOAD result;
  479. File f; Bool processed; if(f.readTryEx(name, cipher, &processed))
  480. {
  481. if(processed) // if the Pak container had to be processed, then we need to copy it to a memory buffer and keep that memory for further usage
  482. {
  483. f.cipher(null); // we have to disable file cipher, because Pak may have cipher_per_file, in which case we need to handle cipher for each file separately
  484. Mems<Byte> data; // must use temporary memory, because in 'loadMem->loadHeader' first is called 'del' which would delete '_data_decompressed'
  485. if(!f.copyToAndDiscard(data))result=PAK_LOAD_NOT_PAK;else
  486. switch(result=loadMemEx(data.data(), data.elms(), cipher, expected_size, actual_size)) // have to use 'cipher' here, because it was disabled earlier
  487. {
  488. case PAK_LOAD_INCOMPLETE_DATA:
  489. case PAK_LOAD_OK : Swap(_data_decompressed, data); return result; // remember this data, because it will be used when reading files from this Pak (do not copy it, but just swap it, because 'loadMem' keeps pointer to existing data, and also swapping is faster than copying)
  490. }
  491. }else
  492. {
  493. if(pak_offset) // this is used only for Linux when embedding PAK's into the executable, in that case 'processed' is always false and encryption starts at current position
  494. {
  495. if(!f.pos(pak_offset))goto error;
  496. f.cipherOffsetClear();
  497. }
  498. switch(result=loadHeader(f, expected_size, actual_size))
  499. {
  500. case PAK_LOAD_INCOMPLETE_DATA:
  501. case PAK_LOAD_OK :
  502. {
  503. _file_name=(f._pak ? f._pak->_file_name : NormalizePath(MakeFullPath(name, f._path))); // if file comes from another Pak then remember that Pak's name, otherwise remember full path in case the path is relative and 'CurDir' or 'DataPath' will be changed later making that relative path no longer valid
  504. if(_file_cipher && !cipher)_cipher_per_file=false; // if there is a cipher, but it comes from parent file/container and not this Pak, then we always have to disable '_cipher_per_file', because that cipher was used to encrypt entire Pak as a one file, so when decrypting, we have to treat it the same, that the cipher affects entire Pak and its content continuously and not per Pak's files, here the Pak is one big file
  505. }return result;
  506. }
  507. }
  508. }else
  509. {
  510. error:
  511. result=PAK_LOAD_NOT_FOUND;
  512. }
  513. del(); return result;
  514. }
  515. Bool Pak::loadMem (CPtr data, Int size, Cipher *cipher) {return loadMemEx(data, size, cipher)==PAK_LOAD_OK;}
  516. Bool Pak::load (C Str &name, Cipher *cipher) {return loadEx (name, cipher)==PAK_LOAD_OK;}
  517. void Pak::operator=(C Str &name ) { if(!load (name))Exit(S+"Can't load Pak \""+name+"\"");}
  518. /******************************************************************************/
  519. Pak& Pak::pakFileName(C Str &name) {T._file_name=NormalizePath(MakeFullPath(name)); return T;}
  520. Str Pak::fullName(C PakFile &file)C
  521. {
  522. Str s=file.name; for(Int p=file.parent; InRange(p, T.files()); p=T.file(p).parent)s=S+T.file(p).name+'\\'+s;
  523. return s;
  524. }
  525. Long Pak::totalSize(C PakFile &file, Bool compressed)C
  526. {
  527. Long size=(compressed ? file.data_size_compressed : file.data_size); REP(file.children_num)size+=totalSize(file.children_offset+i, compressed);
  528. return size;
  529. }
  530. Long PakSet::totalSize(C PaksFile &file, Bool compressed)C
  531. {
  532. Long size=(file.file ? compressed ? file.file->data_size_compressed : file.file->data_size : 0); REP(file.children_num)size+=totalSize(file.children_offset+i, compressed);
  533. return size;
  534. }
  535. Str PaksFile::fullName( )C {return (pak && file) ? pak->fullName(*file ) : S;}
  536. Str Pak ::fullName( Int i )C {return InRange(i, files()) ? fullName( file(i)) : S;}
  537. Str PakSet ::fullName( Int i )C {return InRange(i, files()) ? file(i).fullName( ) : S;}
  538. Str PakSet ::fullName(C PaksFile &file)C {return file.fullName();}
  539. Long Pak ::totalSize(Int i, Bool compressed)C {return InRange(i, files()) ? totalSize(file(i), compressed) : 0;}
  540. Long PakSet::totalSize(Int i, Bool compressed)C {return InRange(i, files()) ? totalSize(file(i), compressed) : 0;}
  541. /******************************************************************************/
  542. static C PakFile* PakFind(C PakFile *file, CChar8 *name, Int from, Int elms)
  543. {
  544. for(Int l=from, r=from+elms; l<r; )
  545. {
  546. Int mid =UInt(l+r)/2,
  547. compare=Compare(name, file[mid].name);
  548. if(!compare )return &file[mid];
  549. if( compare<0)r=mid;
  550. else l=mid+1;
  551. }
  552. return null;
  553. }
  554. static C PakFile* PakFind(C PakFile *file, CChar *name, Int from, Int elms)
  555. {
  556. for(Int l=from, r=from+elms; l<r; )
  557. {
  558. Int mid =UInt(l+r)/2,
  559. compare=Compare(name, file[mid].name);
  560. if(!compare )return &file[mid];
  561. if( compare<0)r=mid;
  562. else l=mid+1;
  563. }
  564. return null;
  565. }
  566. static C PaksFile* PakFind(C PaksFile *file, CChar8 *name, Int from, Int elms)
  567. {
  568. for(Int l=from, r=from+elms; l<r; )
  569. {
  570. Int mid =UInt(l+r)/2,
  571. compare=Compare(name, file[mid].file->name);
  572. if(!compare )return &file[mid];
  573. if( compare<0)r=mid;
  574. else l=mid+1;
  575. }
  576. return null;
  577. }
  578. static C PaksFile* PakFind(C PaksFile *file, CChar *name, Int from, Int elms)
  579. {
  580. for(Int l=from, r=from+elms; l<r; )
  581. {
  582. Int mid =UInt(l+r)/2,
  583. compare=Compare(name, file[mid].file->name);
  584. if(!compare )return &file[mid];
  585. if( compare<0)r=mid;
  586. else l=mid+1;
  587. }
  588. return null;
  589. }
  590. /******************************************************************************/
  591. C PakFile* Pak::find(CChar8 *name, Bool include_removed)C
  592. {
  593. if(Is(name) && rootFiles())
  594. {
  595. Bool last =false;
  596. Char8 pf_name[MAX_LONG_PATH];
  597. Int pf_pos=0,
  598. pf_len=rootFiles();
  599. for(;;)
  600. {
  601. for(Int i=0; ; i++)
  602. {
  603. if(!InRange(i, pf_name))return null; // name too long
  604. Char8 c=*name++; if(!c)last=true;else if(IsSlash(c))c=0;
  605. if(!(pf_name[i]=c))break;
  606. }
  607. C PakFile *file=PakFind(files().data(), pf_name, pf_pos, pf_len);
  608. if(!file)return null;
  609. if( last)
  610. {
  611. if((file->flag&PF_REMOVED) && !include_removed)return null; // PF_REMOVED can be checked only for the last file, because if any parent is marked with PF_REMOVED during 'PakUpdate', then all its children are removed
  612. return file;
  613. }
  614. pf_pos=file->children_offset;
  615. pf_len=file->children_num;
  616. }
  617. }
  618. return null;
  619. }
  620. C PakFile* Pak::find(CChar *name, Bool include_removed)C
  621. {
  622. if(Is(name) && rootFiles())
  623. {
  624. Bool last =false;
  625. Char pf_name[MAX_LONG_PATH];
  626. Int pf_pos=0,
  627. pf_len=rootFiles();
  628. for(;;)
  629. {
  630. for(Int i=0; ; i++)
  631. {
  632. if(!InRange(i, pf_name))return null; // name too long
  633. Char c=*name++; if(!c)last=true;else if(IsSlash(c))c=0;
  634. if(!(pf_name[i]=c))break;
  635. }
  636. C PakFile *file=PakFind(files().data(), pf_name, pf_pos, pf_len);
  637. if(!file)return null;
  638. if( last)
  639. {
  640. if((file->flag&PF_REMOVED) && !include_removed)return null; // PF_REMOVED can be checked only for the last file, because if any parent is marked with PF_REMOVED during 'PakUpdate', then all its children are removed
  641. return file;
  642. }
  643. pf_pos=file->children_offset;
  644. pf_len=file->children_num;
  645. }
  646. }
  647. return null;
  648. }
  649. C PakFile* Pak::find(C UID &id, Bool include_removed)C
  650. {
  651. if(id.valid())
  652. if(C PakFile *file=PakFind(files().data(), _EncodeFileName(id), 0, rootFiles()))
  653. {
  654. if((file->flag&PF_REMOVED) && !include_removed)return null;
  655. return file;
  656. }
  657. return null;
  658. }
  659. C PaksFile* PakSet::find(CChar8 *name)C
  660. {
  661. if(Is(name) && rootFiles())
  662. {
  663. Bool last =false;
  664. Char8 pf_name[MAX_LONG_PATH];
  665. Int pf_pos=0,
  666. pf_len=rootFiles();
  667. for(;;)
  668. {
  669. for(Int i=0; ; i++)
  670. {
  671. if(!InRange(i, pf_name))return null; // name too long
  672. Char8 c=*name++; if(!c)last=true;else if(IsSlash(c))c=0;
  673. if(!(pf_name[i]=c))break;
  674. }
  675. C PaksFile *file=PakFind(files().data(), pf_name, pf_pos, pf_len);
  676. if(!file)return null;
  677. if( last)return file;
  678. pf_pos=file->children_offset;
  679. pf_len=file->children_num;
  680. }
  681. }
  682. return null;
  683. }
  684. C PaksFile* PakSet::find(CChar *name)C
  685. {
  686. if(Is(name) && rootFiles())
  687. {
  688. Bool last =false;
  689. Char pf_name[MAX_LONG_PATH];
  690. Int pf_pos=0,
  691. pf_len=rootFiles();
  692. for(;;)
  693. {
  694. for(Int i=0; ; i++)
  695. {
  696. if(!InRange(i, pf_name))return null; // name too long
  697. Char c=*name++; if(!c)last=true;else if(IsSlash(c))c=0;
  698. if(!(pf_name[i]=c))break;
  699. }
  700. C PaksFile *file=PakFind(files().data(), pf_name, pf_pos, pf_len);
  701. if(!file)return null;
  702. if( last)return file;
  703. pf_pos=file->children_offset;
  704. pf_len=file->children_num;
  705. }
  706. }
  707. return null;
  708. }
  709. C PaksFile* PakSet::find(C UID &id)C
  710. {
  711. return id.valid() ? PakFind(files().data(), _EncodeFileName(id), 0, rootFiles()) : null;
  712. }
  713. /******************************************************************************/
  714. // PAK SET
  715. /******************************************************************************/
  716. static Int ComparePath(C PaksFile &a, C PaksFile &b) {return Compare(a.file->name, b.file->name);}
  717. struct ChildrenInfo
  718. {
  719. Int children_offset,
  720. children_num;
  721. void set(Int offset, Int num) {children_offset=offset; children_num=num;}
  722. };
  723. struct PakChildrenInfo : ChildrenInfo
  724. {
  725. Int pak;
  726. void set(Int pak, Int offset, Int num) {T.pak=pak; children_offset=offset; children_num=num;}
  727. };
  728. struct PaksCreator
  729. {
  730. Int root_files; // number of root files
  731. Mems<Pak*> paks; // lookup table for the Pak's
  732. Memc<PaksFile> files, // continuously increased file array
  733. temp; // temporary container stored globally to improve performance of memory usage
  734. void add(Mems<ChildrenInfo> &pak_children, Int parent) // 'pak_children' is an array of length equal to number of Pak's, in each 'add' it will be modified to have a list of what files from paks to add
  735. {
  736. // init
  737. Memc<PaksFile> level; // files to be added in this level
  738. Memc< Memc<PakChildrenInfo> > level_children;
  739. // set list of files in this level
  740. FREPAD(p, paks) // for each Pak (order important, with every new Pak we're replacing old files)
  741. {
  742. Pak &pak=*paks [p]; // get p-th Pak
  743. ChildrenInfo &pc = pak_children[p]; // get requested children of that Pak to be added in this level
  744. FREPD(f, pc.children_num) // for each file (update or add)
  745. {
  746. C PakFile &src=pak.file(pc.children_offset+f);
  747. PaksFile *get=ConstCast(PakFind(level.data(), src.name, 0, level.elms())); // find existing one
  748. if(src.flag&PF_REMOVED)
  749. {
  750. level.removeData(get, true); // remove any previous information about the file (keep order as 'level' needs to be sorted)
  751. }else
  752. {
  753. if(!get)get=&temp.New(); // add new file
  754. get->pak =&pak;
  755. get->file=&src;
  756. if(src.children_num) // if the file has children, we need to store information about them, to add them later
  757. {
  758. // here 'PaksFile.children_offset' is used to store information about children in the 'level_children' container (storing index in 'level_children'+1)
  759. if( get->children_offset<=0)get->children_offset=level_children.addNum(1)+1; // if no children info was set yet, then create new and store index+1
  760. level_children[get->children_offset-1].New().set(p, src.children_offset, src.children_num);
  761. }
  762. }
  763. }
  764. if(temp.elms()) // add files from 'temp' to 'level'
  765. {
  766. FREPA(temp)level.add(temp[i]); temp.clear();
  767. level.sort(ComparePath); // 'level' needs to be sorted
  768. }
  769. }
  770. Int cur_files=files.elms(); // store the amount of files before adding those from this level, this also marks the starting index of files in this level
  771. // update parent
  772. if(parent<0)root_files=level.elms();else
  773. {
  774. PaksFile &pf=files[parent];
  775. pf.children_offset=cur_files;
  776. pf.children_num =level.elms();
  777. }
  778. // add files from this 'level' to 'files' list
  779. FREPA(level)files.add(level[i]);
  780. // add children
  781. FREPAD(f, level) // for each file that was added in this level
  782. {
  783. Int c=level[f].children_offset;
  784. if( c>0) // check if it has information about children in the 'level_children' container
  785. {
  786. Memc<PakChildrenInfo> &children=level_children[c-1]; // access it, index was stored as +1 so we need to use -1 here
  787. REPAO(pak_children).set(0, 0); FREPA(children){PakChildrenInfo &c=children[i]; pak_children[c.pak]=c;} // setup the 'pak_children' array
  788. add (pak_children, cur_files+f);
  789. }
  790. }
  791. }
  792. PaksCreator(Meml<PakSet::Src> &paks)
  793. {
  794. root_files=0;
  795. T.paks.setNum(paks.elms()); REPAO(T.paks)=&paks[i];
  796. }
  797. };
  798. /******************************************************************************/
  799. PakSet& PakSet::del()
  800. {
  801. _root_files=0;
  802. _files.del();
  803. _paks .del();
  804. return T;
  805. }
  806. PakSet::PakSet()
  807. {
  808. _root_files=0;
  809. }
  810. void PakSet::rebuild()
  811. {
  812. SyncLocker locker(_lock);
  813. // init
  814. PaksCreator pc(_paks);
  815. // add
  816. Mems<ChildrenInfo> pak_children; pak_children.setNum(pc.paks.elms());
  817. REPAO (pak_children).set(0, pc.paks[i]->rootFiles()); // set to add all root files from each Pak in the next 'add'
  818. pc.add(pak_children, -1);
  819. // create new
  820. _root_files=pc.root_files;
  821. Swap(_files, pc.files);
  822. }
  823. /******************************************************************************/
  824. Bool PakSet::addTry (C Str &name, Cipher *cipher, Bool auto_rebuild ) {return addTry(name, cipher, auto_rebuild, 0);}
  825. Bool PakSet::addTry (C Str &name, Cipher *cipher, Bool auto_rebuild, Long pak_offset) {Src temp; if(temp.loadEx (name , cipher , pak_offset)==PAK_LOAD_OK){temp.name=name; temp.pak_offset=pak_offset ; SyncLocker locker(_lock); Swap(temp, _paks.New()); if(auto_rebuild)T.rebuild(); return true;} return false;}
  826. Bool PakSet::addMemTry( CPtr data, Int size, Cipher *cipher, Bool auto_rebuild ) {Src temp; if(temp.loadMem (data, size, cipher ) ){ temp.pak_offset=IntPtr(data); SyncLocker locker(_lock); Swap(temp, _paks.New()); if(auto_rebuild)T.rebuild(); return true;} return false;}
  827. PakSet& PakSet::add (C Str &name, Cipher *cipher, Bool auto_rebuild ) { if( !addTry (name, cipher, auto_rebuild ) )Exit(S+"Can't load Pak \""+name+'"'); return T;}
  828. PakSet& PakSet::addMem ( CPtr data, Int size, Cipher *cipher, Bool auto_rebuild ) { if( !addMemTry(data, size, cipher, auto_rebuild ) )Exit(S+"Can't load Pak from memory"); return T;}
  829. Bool PakSet::remove(C Str &name/*, Long pak_offset*/) // keep 'pak_offset' in case we make this functionality available in the future
  830. {
  831. if(name.is())
  832. #if !SYNC_LOCK_SAFE
  833. if(_paks.elms())
  834. #endif
  835. {
  836. SyncLocker locker(_lock);
  837. MREP(_paks)
  838. {
  839. C Src &src=_paks[i]; if(EqualPath(src.name, name)/* && src.pak_offset==pak_offset*/)
  840. {
  841. _paks.remove(i, true);
  842. // !! here can't access 'i' MemlNode anymore after deletion !!
  843. rebuild();
  844. return true;
  845. }
  846. }
  847. }
  848. return false;
  849. }
  850. Bool PakSet::removeMem(CPtr data)
  851. {
  852. if(data)
  853. #if !SYNC_LOCK_SAFE
  854. if(_paks.elms())
  855. #endif
  856. {
  857. SyncLocker locker(_lock);
  858. MREP(_paks)
  859. {
  860. C Src &src=_paks[i]; if(!src.name.is() && src.pak_offset==IntPtr(data))
  861. {
  862. _paks.remove(i, true);
  863. // !! here can't access 'i' MemlNode anymore after deletion !!
  864. rebuild();
  865. return true;
  866. }
  867. }
  868. }
  869. return false;
  870. }
  871. /******************************************************************************/
  872. // PAK - Create
  873. /******************************************************************************/
  874. static UInt DecompressedFilexxHash64_32(File &file, File &temp, COMPRESS_TYPE compression, Long compressed_size, Long decompressed_size) // !! this assumes that "temp.writeMem()" was called !!
  875. {
  876. file.pos(0); // reset file position
  877. if(!compression)return file.xxHash64_32();
  878. xxHash64Calc hasher; UInt hash=(DecompressRaw(file, temp, compression, compressed_size, decompressed_size, false, &hasher) ? hasher.hash.hash32() : 0); // don't use memory optimization here, because we care more about fewer memory allocations
  879. temp.reset(); // reset to free memory, this is needed as 'f_temp' is always expected to be empty
  880. return hash;
  881. }
  882. /******************************************************************************/
  883. struct FileTemp
  884. {
  885. enum TYPE : Byte
  886. {
  887. NODE,
  888. STD ,
  889. };
  890. Str name; // for STD this will include full path (including drive and folders), for NODE it will be set to 'node.name'
  891. TYPE type;
  892. union
  893. {
  894. C PakNode *node; // used when "type==NODE"
  895. FileInfo fi ; // used when "type==STD"
  896. };
  897. Bool isDir()C
  898. {
  899. FSTD_TYPE type; switch(T.type)
  900. {
  901. case NODE: type=node->type; break;
  902. case STD : type=fi .type; break;
  903. default : type=FSTD_NONE ; break;
  904. }
  905. return type==FSTD_DRIVE || type==FSTD_DIR;
  906. }
  907. void set(C PakNode &node)
  908. {
  909. T.name= node.name;
  910. T.type= NODE;
  911. T.node=&node;
  912. }
  913. void set(C Str &name, C FileInfo &fi)
  914. {
  915. T.name=name;
  916. T.type=STD;
  917. T.fi =fi;
  918. }
  919. FileTemp() {} // needed because of union
  920. };
  921. static Int ComparePath(C FileTemp &f0, C FileTemp &f1) {return ComparePath(f0.name, f1.name);}
  922. /******************************************************************************/
  923. struct PakCreator
  924. {
  925. struct FileTempContainer
  926. {
  927. Memc<FileTemp> files;
  928. void add(C PakNode &node ) { files.New().set(node );}
  929. void add(Str name, C FileInfo &fi, Bool (*filter)(C Str &name) ) {name.tailSlash(false); if(!filter || filter(name))if(!Equal(_GetBase(name), ".DS_Store"))files.New().set(name, fi);}
  930. Bool add(Str name, Bool (*filter)(C Str &name), PakCreator &pc) {name.tailSlash(false); if(!filter || filter(name)){FileInfo fi; if(!fi.get(name))return pc.setErrorAccess(name); add(name, fi, null);} return true;}
  931. void sort()
  932. {
  933. files.sort(ComparePath);
  934. REPA(files)if(i && EqualPath(files[i].name, files[i-1].name))files.remove(i, true); // remove files with same names (in case someone provides incorrect input)
  935. }
  936. };
  937. STRUCT(PakFileEx , PakFile)
  938. //{
  939. Bool ready;
  940. COMPRESS_MODE compress_mode;
  941. Str name,
  942. data_name; // can come from STD
  943. C DataSource *data ; // can come from NODE
  944. File processed; // this is processed file data (that was decompressed/compressed and ready to use)
  945. // !! clear values in 'set' instead of constructor !!
  946. Bool needHash ()C {return data_xxHash64_32==0 && data_size> 0;} // we need hash only if it wasn't set yet, if hash is already set, then we don't need it
  947. Bool needCompress ()C {return compress_mode==COMPRESS_ENABLE && data_size>=MIN_COMPRESSABLE_SIZE;} // if file wants to be compressed or doesn't want to be
  948. Bool needDecompress()C {return compress_mode!=COMPRESS_KEEP_ORIGINAL && compression ;} // if file wants to be decompressed (we don't keep original and it's compressed)
  949. Str srcFullName ()C {return data ? data->srcName() : data_name;} // don't return 'name' because that's only 1 element without the path, but for this method we need the full version for debug purposes
  950. File* get(Cipher *src_cipher, File &temp) // this is called only if file has data (size!=0)
  951. {
  952. File *f=null;
  953. if(processed.is() )f=&processed ;else // if we've de/compressed data for storage, then we have to use it (this will be valid only if decompressed or compressed)
  954. if(data )f= data->openRaw(temp);else // open without decompressing, because we expect files as they are, and if they're compressed, it should be speicified with 'compression'
  955. if(temp.readTry(data_name, src_cipher))f=&temp ;
  956. if(f)f->pos(0);
  957. return f;
  958. }
  959. Bool set(C FileTemp &ft, Int parent_index, PakCreator &pc)
  960. {
  961. reset(); ready=false; compress_mode=COMPRESS_ENABLE; data=null;
  962. T.parent=parent_index;
  963. switch(ft.type)
  964. {
  965. default: return pc.setError("Invalid FileTemp.type");
  966. case FileTemp::STD:
  967. {
  968. name =_GetBase(ft.name);
  969. data_name = ft.name ; // here we leave 'data' as empty, because that's for NODE
  970. modify_time_utc=ft.fi.modify_time_utc;
  971. type (ft.fi.type);
  972. data_size =
  973. data_size_compressed=ft.fi.size;
  974. }break;
  975. case FileTemp::NODE:
  976. {
  977. name =ft.name;
  978. modify_time_utc=ft.node->modify_time_utc;
  979. type (ft.node->type);
  980. if(ft.node->exists)
  981. {
  982. compression = ft.node->compressed;
  983. compress_mode = ft.node->compress_mode;
  984. data =&ft.node->data; // here we leave 'data_name' as empty, because that's for STD
  985. data_xxHash64_32= ft.node->xxHash64_32;
  986. switch(data->type)
  987. {
  988. case DataSource::NAME: if(data->name.is()) // ignore empty names to avoid errors in 'FileInfo.get' (treat them as empty data)
  989. {
  990. FileInfo fi; if(!fi.get(data->name))return pc.setErrorAccess(data->name);
  991. type (fi.type ); // this is optional
  992. data_size_compressed=fi.size ; // this is required
  993. modify_time_utc =fi.modify_time_utc; // alternatively this could be performed if(!modify_time_utc.valid())
  994. }break;
  995. case DataSource::STD: if(data->name.is()) // ignore empty names to avoid errors in 'FileInfo.getSystem' (treat them as empty data)
  996. {
  997. FileInfo fi; if(!fi.getSystem(data->name))return pc.setErrorAccess(data->name);
  998. type (fi.type ); // this is optional
  999. data_size_compressed=fi.size ; // this is required
  1000. modify_time_utc =fi.modify_time_utc; // alternatively this could be performed if(!modify_time_utc.valid())
  1001. }break;
  1002. case DataSource::PAK_FILE: if(C PakFile *pf=data->pak_file)
  1003. {
  1004. // override values in case user didn't specify them
  1005. FlagCopy(flag ,pf->flag, PF_STD_DIR|PF_STD_LINK); // this is optional
  1006. compression =pf->compression ; // this is required
  1007. data_size =pf->data_size ; // this is required
  1008. data_size_compressed=pf->data_size_compressed ; // this is required
  1009. modify_time_utc =pf->modify_time_utc ; // alternatively this could be performed if(!modify_time_utc.valid())
  1010. if(!data_xxHash64_32)data_xxHash64_32 =pf->data_xxHash64_32 ; // override only if user didn't calculate it (because it's possible that 'data_xxHash64_32' is calculated but 'pf->data_xxHash64_32' left at 0)
  1011. goto size_ok; // we have to skip checking size, because if the user didn't provide 'decompressed_size', but the file is compressed then error will be returned
  1012. }break;
  1013. default:
  1014. {
  1015. Long size_compressed=data->sizeCompressed();
  1016. if( size_compressed<0)return pc.setErrorAccess(srcFullName());
  1017. data_size_compressed=size_compressed;
  1018. }break;
  1019. }
  1020. if(compression)
  1021. {
  1022. if(ft.node->decompressed_size<0)return pc.setError(S+"File \""+ft.name+"\" was marked as compressed, however its 'decompressed_size' is unspecified");
  1023. data_size=ft.node->decompressed_size;
  1024. }else data_size= data_size_compressed;
  1025. if(!data_size != !data_size_compressed)return pc.setError(S+"File \""+ft.name+"\" has invalid data size"); // both have to be zeros or not zeros
  1026. size_ok:;
  1027. if(ft.node->decompressed_size>=0 && ft.node->decompressed_size!=data_size)return pc.setError(S+"File \""+ft.name+"\" has specified 'decompressed_size' but it doesn't match source data");
  1028. }else
  1029. {
  1030. flag|=PF_REMOVED;
  1031. }
  1032. }break;
  1033. }
  1034. return true;
  1035. }
  1036. };
  1037. struct Compressor
  1038. {
  1039. File f_std, f_temp;
  1040. Str error_message;
  1041. SyncEvent waiting;
  1042. Compressor() {f_temp.writeMem();}
  1043. };
  1044. Bool error_occurred, header_changed, data_size_changed;
  1045. Int file_being_processed;
  1046. UInt pak_flag;
  1047. Long mem_available;
  1048. PakProgress *progress;
  1049. Str *error_message;
  1050. Memc<PakFileEx> files;
  1051. Pak &pak;
  1052. Cipher *src_cipher;
  1053. COMPRESS_TYPE compress;
  1054. Int compression_level;
  1055. SyncEvent ready;
  1056. Threads threads;
  1057. MemtN<Compressor, 16> compressors;
  1058. PakCreator(Pak &pak, UInt pak_flag, Cipher *src_cipher, COMPRESS_TYPE compress, Int compression_level, Str *error_message, PakProgress *progress) : pak(pak)
  1059. {
  1060. if(pak_flag&PAK_NO_FILE)pak_flag|=PAK_NO_DATA; // if we're not creating any file, then we can't save data either
  1061. if(pak_flag&PAK_NO_DATA)compress=COMPRESS_NONE; // if we're not saving data, then disable compression (because it's not needed, however we still may want to process files for calculating hash)
  1062. T.error_occurred =header_changed=data_size_changed=false;
  1063. T.file_being_processed=0;
  1064. T.pak_flag =pak_flag;
  1065. T.src_cipher =src_cipher;
  1066. T.compress =compress;
  1067. T.compression_level =compression_level;
  1068. T.error_message =error_message; if(error_message)error_message->clear();
  1069. T.progress =progress ; if(progress )progress ->progress=0;
  1070. }
  1071. Bool setErrorAccess(C Str &name ) {return setError(S+"Can't access \""+name+'"');}
  1072. Bool setError (C Str &error)
  1073. {
  1074. error_occurred=true;
  1075. if(error_message)*error_message=error;
  1076. return false;
  1077. }
  1078. Bool add(C FileTemp &ft, Int parent_index) {return files.New().set(ft, parent_index, T);}
  1079. void enter(FileTemp &parent, Int parent_index, Bool (*filter)(C Str &name))
  1080. {
  1081. FileTempContainer ftc;
  1082. // get files
  1083. switch(parent.type)
  1084. {
  1085. case FileTemp::STD : if(parent.isDir() )for(FileFind ff(parent.name); ff(); )ftc.add(ff.pathName(), ff, filter); break;
  1086. case FileTemp::NODE: if(parent.node->exists)FREPA(parent.node->children )ftc.add(parent.node->children[i] ); break; // don't add children for nodes that are marked as removed
  1087. }
  1088. ftc.sort();
  1089. // add files
  1090. Int file_elms=files.elms();
  1091. if(parent_index>=0)
  1092. {
  1093. files[parent_index].children_offset=file_elms;
  1094. files[parent_index].children_num =ftc.files.elms();
  1095. }
  1096. FREPA(ftc.files)add (ftc.files[i], parent_index );
  1097. FREPA(ftc.files)enter(ftc.files[i], file_elms+i, filter);
  1098. }
  1099. static Str SrcName (C PakFileEx &src, C PakFile &dest, C Pak &pak) {Str s=src.srcFullName(); return s.is() ? s : pak.fullName(dest);}
  1100. static Str CantFlush(C Str &file) {return S+"Can't write data to:\n\""+file+"\".\nPlease verify you have enough free disk space.";}
  1101. static Str CantCopy (C PakFileEx &src, C PakFile &dest, C Pak &pak) {return S+"Can't store:\n\""+SrcName(src, dest, pak)+"\"\nin:\n\""+pak.pakFileName()+"\".\nPlease verify that source is readable, and you have enough free disk space.";}
  1102. static Str CantOpen (C PakFileEx &src, C PakFile &dest, C Pak &pak) {return S+"Can't open file:\n\""+SrcName(src, dest, pak)+'"';}
  1103. static Str CantDec (C PakFileEx &src, C PakFile &dest, C Pak &pak) {return S+"Can't decompress file:\n\""+SrcName(src, dest, pak)+'"';}
  1104. static Str CantWrite(C Str &file)
  1105. {
  1106. Str path=MakeFullPath(GetPath(file)), error=S+"Can't write to:\n\""+file+"\".\nPath:\n\""+path+"\"\n";
  1107. if(FExistSystem(path))error+="does exist, perhaps there's no permission.";else error+="does not exist.";
  1108. return error;
  1109. }
  1110. static void CompressorFunc(PakFile &dest, PakCreator &pc, Int thread_index) {pc.compressor(pc.pak._files.index(&dest), pc.compressors[thread_index]);}
  1111. void compressor (Int i, Compressor &compressor)
  1112. {
  1113. PakFile &dest=pak._files[i];
  1114. if( dest.data_size)
  1115. {
  1116. PakFileEx &src=files[i];
  1117. Long mem_reserved=0; // memory currently reserved for this file
  1118. Bool decompress= src.needDecompress() , // if decompress
  1119. try_compress=(compress && src.needCompress ()), // if compress
  1120. set_hash =(FlagTest(pak_flag, PAK_SET_HASH) && src.needHash ()); // if set hash
  1121. if(error_occurred
  1122. || progress && progress->wantStop())goto finished;
  1123. if(decompress || try_compress || set_hash)
  1124. {
  1125. // wait until there's memory available
  1126. Long mem_needed= // estimate memory needed for this file
  1127. (
  1128. Long( decompress ? dest.data_size+DecompressionMemUsage(dest.compression, 255, dest.data_size) : 0)
  1129. +(try_compress ? dest.data_size : 0)
  1130. +((set_hash && dest.compression && !decompress && !try_compress) ? dest.data_size : 0) // if we need to set hash, but the file is compressed, then we will have to decompress it first to temp memory, we don't need to do this if we already decompress or compress because we can calculate hash over there too
  1131. );
  1132. for(;;)
  1133. {
  1134. if(error_occurred)goto finished; // if error occurred then always finish
  1135. if(file_being_processed==i) // if we're processing this file then we always need to proceed
  1136. {
  1137. AtomicSub(mem_available, mem_reserved=mem_needed); // reserve memory
  1138. break; // proceed
  1139. }
  1140. check_mem:
  1141. Long temp=AtomicGet(mem_available); if(temp>=mem_needed) // check if we have enough available memory
  1142. {
  1143. if(AtomicCAS(mem_available, temp, temp-mem_needed)) // if we were able to reserve the memory
  1144. {
  1145. mem_reserved=mem_needed; // mark it as reserved
  1146. break; // proceed
  1147. }
  1148. goto check_mem; // check memory again
  1149. }
  1150. compressor.waiting.wait(); // wait
  1151. }
  1152. // open file
  1153. File *f_src=src.get(src_cipher, compressor.f_std);
  1154. if( !f_src){compressor.error_message=CantOpen(src, dest, pak); error_occurred=true; goto finished;}
  1155. xxHash64Calc hasher;
  1156. // decompress (before compressing)
  1157. if(decompress)
  1158. {
  1159. if(DecompressRaw(*f_src, src.processed.writeMem(), dest.compression, dest.data_size_compressed, dest.data_size, false, set_hash ? &hasher : null))
  1160. {
  1161. if(set_hash)
  1162. {
  1163. if(dest.data_xxHash64_32=hasher.hash.hash32())header_changed=true; // if it's different than zero, then it means we've changed the value and header was changed
  1164. set_hash=false; // we now have it, so no need to calculate any more
  1165. }
  1166. dest.data_size_compressed=dest.data_size; dest.compression=COMPRESS_NONE;
  1167. data_size_changed=header_changed=true; // data size and header were changed
  1168. f_src=&src.processed; f_src->pos(0);
  1169. }else
  1170. {
  1171. compressor.error_message=CantDec(src, dest, pak); error_occurred=true; goto finished;
  1172. }
  1173. }
  1174. // compress (before setting hash, because we can do it here in one go)
  1175. if(try_compress)
  1176. {
  1177. File &compressed=((f_src!=&src.processed) ? src.processed.writeMem() : compressor.f_temp);
  1178. if(CompressRaw(*f_src, compressed, compress, compression_level, compressors.elms()<=1, set_hash ? &hasher : null)) // use multi-threaded compression only if we have up to 1 compressor (otherwise we have multiple compressors which call 'CompressRaw' on multiple threads)
  1179. {
  1180. if(set_hash)
  1181. {
  1182. if(dest.data_xxHash64_32=hasher.hash.hash32())header_changed=true; // if it's different than zero, then it means we've changed the value and header was changed
  1183. set_hash=false; // we now have it, so no need to calculate any more
  1184. }
  1185. if(compressed.size()<f_src->size()) // if compressed OK and the result is smaller, then use this version
  1186. {
  1187. dest.data_size_compressed=compressed.size(); dest.compression=compress; // use that version and mark file as compressed
  1188. data_size_changed=header_changed=true; // data size and header were changed
  1189. if(&src.processed!=&compressed)
  1190. {
  1191. src.processed.reset(); // this will be moved to 'f_temp' so reset it to release memory, this is needed as 'f_temp' is always expected to be empty
  1192. Swap(src.processed, compressed);
  1193. }
  1194. f_src=&src.processed; //f_src->pos(0); not needed
  1195. goto compress_ok;
  1196. }
  1197. }
  1198. if(&compressed==&src.processed)compressed.del();else compressed.reset(); // if we were compressing to 'src.processed' but we didn't use the result (compress failed or result is not smaller), then delete it, otherwise it's 'f_temp' so reset it
  1199. compress_ok:;
  1200. }
  1201. // set hash (after de/compressing)
  1202. if(set_hash)
  1203. if(dest.data_xxHash64_32=DecompressedFilexxHash64_32(*f_src, compressor.f_temp, dest.compression, dest.data_size_compressed, dest.data_size))
  1204. header_changed=true; // if it's different than zero, then it means we've changed the value and header was changed
  1205. }
  1206. finished:
  1207. mem_reserved-=src.processed.size(); // when releasing the reserved memory below, we need to keep the 'processed' memory that's going to be used later
  1208. if(mem_reserved)AtomicAdd(mem_available, mem_reserved); // release what was reserved
  1209. src.ready=true; ready.on(); // we need to set 'ready' always for files with data, because the main thread waits for it
  1210. }
  1211. }
  1212. void wakeUp() {REPAO(compressors).waiting.on();}
  1213. void stopThreads() {error_occurred=true; wakeUp(); threads.del();} // set error first, then wake up threads and finally delete them
  1214. Bool create(C Str &pak_name, Cipher *cipher)
  1215. {
  1216. // !! these are needed before any "goto error" !!
  1217. File f_dest;
  1218. pak.pakFileName(pak_name); // !! set this first because 'pak_name' can be a Pak member, don't use 'pak_name' after, but use 'pak.pakFileName' instead !!
  1219. if(error_occurred
  1220. || progress && progress->wantStop(error_message))goto error;
  1221. {
  1222. // create !! must set all members because we could be operating on Pak that's already created !!
  1223. Int names_elms=0; REPA(files)names_elms+=files[i].name.length()+1;
  1224. pak._names.setNum(names_elms );
  1225. pak._files.setNum(files.elms());
  1226. pak._cipher_per_file =true; // if changing then please remember that currently '_cipher_per_file' is set based on the Pak file format version
  1227. pak._file_type =FILE_STD_READ;
  1228. pak._file_cipher_offset=0;
  1229. pak._file_cipher =cipher;
  1230. pak._root_files =0;
  1231. pak._data_offset =0;
  1232. pak._data_decompressed.del();
  1233. pak._data =null;
  1234. names_elms =0;
  1235. Int files_to_process =0;
  1236. UInt max_data_size_compress =0;
  1237. Long thread_mem_usage =0,
  1238. total_data_size_compressed=0, total_data_size_decompressed=0, decompressed_processed=0;
  1239. FREPA(files)
  1240. {
  1241. C PakFileEx &src = files[i];
  1242. PakFile &dest=pak._files[i];
  1243. // !! must set all members because we could be operating on PakFile that's already created !!
  1244. Int src_name_chars=src.name.length()+1;
  1245. dest.name =Set(pak._names.data()+names_elms, src.name, src_name_chars); names_elms+=src_name_chars;
  1246. dest.flag = src.flag ;
  1247. dest.compression = src.compression ;
  1248. dest.parent = src.parent ; if(dest.parent<0)pak._root_files++;
  1249. dest.children_offset = src.children_offset ;
  1250. dest.children_num = src.children_num ;
  1251. dest.modify_time_utc = src.modify_time_utc ;
  1252. dest.data_xxHash64_32 = src.data_xxHash64_32 ;
  1253. dest.data_size = src.data_size ;
  1254. dest.data_size_compressed= src.data_size_compressed;
  1255. dest.data_offset =total_data_size_compressed;
  1256. total_data_size_compressed +=src.data_size_compressed;
  1257. total_data_size_decompressed+=src.data_size;
  1258. Bool file_decompress=( src.needDecompress()),
  1259. file_compress =(compress && src.needCompress ()),
  1260. file_hash =(FlagTest(pak_flag, PAK_SET_HASH) && src.needHash ());
  1261. Long mem_usage= // estimate memory needed for this file
  1262. (
  1263. Long( file_decompress ? dest.data_size+DecompressionMemUsage(dest.compression, 255, dest.data_size) : 0)
  1264. +( file_compress ? dest.data_size : 0)
  1265. +((file_hash && dest.compression && !file_decompress && !file_compress) ? dest.data_size : 0) // if we need to set hash, but the file is compressed, then we will have to decompress it first to temp memory, we don't need to do this if we already decompress or compress because we can calculate hash over there too
  1266. );
  1267. MAX(thread_mem_usage, mem_usage);
  1268. if(file_compress)MAX(max_data_size_compress, src.data_size);
  1269. if(file_decompress || file_compress || file_hash)files_to_process++;
  1270. }
  1271. if(progress && progress->wantStop(error_message))goto error;
  1272. // save header
  1273. if(!FlagTest(pak_flag, PAK_NO_FILE)) // create file
  1274. {
  1275. if(!pak.pakFileName().is() ){if(error_message)*error_message="Pak name was not specified"; goto error;}
  1276. if(!f_dest.writeTry(pak.pakFileName(), cipher)){if(error_message)*error_message=CantWrite(pak.pakFileName()); goto error;}
  1277. if(!pak.saveHeader(f_dest) ){if(error_message)*error_message=CantFlush(pak.pakFileName()); goto error;}
  1278. pak._file_cipher_offset=f_dest._cipher_offset+f_dest.pos();
  1279. pak._data_offset =f_dest.posAbs();
  1280. }
  1281. auto f_dest_cipher_offset=f_dest._cipher_offset;
  1282. // process files
  1283. if(!FlagTest(pak_flag, PAK_NO_DATA) || FlagTest(pak_flag, PAK_SET_HASH)) // write data or set hash
  1284. {
  1285. #if HAS_THREADS
  1286. if(files_to_process)
  1287. {
  1288. // calculate memory usage
  1289. UInt compression_usage=CompressionMemUsage(compress, compression_level, max_data_size_compress);
  1290. thread_mem_usage+=compression_usage;
  1291. MemStats mem_stats; mem_stats.get(); mem_available=mem_stats.avail_phys;
  1292. #if !X64
  1293. MIN(mem_available, INT_MAX); // limit to 32-bit on 32-bit platforms, use 2GB just in case
  1294. #endif
  1295. Int num=Min(Cpu.threads(), files_to_process);
  1296. if(thread_mem_usage)MIN(num, mem_available/thread_mem_usage);
  1297. compressors.setNum(Max(1, num)); if(compressors.elms())
  1298. {
  1299. mem_available-=compressors.elms()*compression_usage; // reserve memory needed for compression up-front, don't include others because they will be handled per-file
  1300. threads.create(true, compressors.elms()); // process in order
  1301. FREPA(pak)threads.queue(pak._files[i], CompressorFunc, T); // queue in order
  1302. }
  1303. }
  1304. #endif
  1305. if(!compressors.elms())compressors.New(); // we need at least one
  1306. // process all files
  1307. File f_std; // keep 'f_std' outside of loop to decrease overhead
  1308. FREPA(pak)
  1309. {
  1310. if(progress && progress->wantStop(error_message))goto error;
  1311. PakFileEx &src = files[i];
  1312. PakFile &dest=pak._files[i];
  1313. if(data_size_changed) // if data size of at least one file was changed due to compression, then
  1314. {
  1315. dest.data_offset=f_dest.posAbs()-pak._data_offset; // all files after it need to have their data offset adjusted (set this even for empty files with no data, so there are no files with 'data_offset' outside of the Pak file size)
  1316. //header_changed=true; we don't need to set this because it's already set along with 'data_size_changed'
  1317. }
  1318. if(dest.data_size)
  1319. {
  1320. // wait until file is processed
  1321. file_being_processed=i;
  1322. if(threads.threads())
  1323. {
  1324. if(!src.ready){wakeUp(); do ready.wait();while(!src.ready);}
  1325. }else compressor(i, compressors[0]);
  1326. // check for errors
  1327. if(error_occurred)
  1328. {
  1329. stopThreads(); // stop the threads first
  1330. if(error_message)REPA(compressors)if(compressors[i].error_message.is()) // grab any first error found
  1331. {
  1332. Swap(*error_message, compressors[i].error_message);
  1333. break;
  1334. }
  1335. goto error;
  1336. }
  1337. // write data
  1338. if(!FlagTest(pak_flag, PAK_NO_DATA))
  1339. {
  1340. if(progress && progress->wantStop(error_message))goto error;
  1341. // get source file
  1342. File *f_src=src.get(src_cipher, f_std);
  1343. if( !f_src){if(error_message)*error_message=CantOpen(src, dest, pak); goto error;}
  1344. // check for invalid data
  1345. if(!dest.compression && dest.data_size!=dest.data_size_compressed)
  1346. {
  1347. if(error_message)*error_message=S+"File is not compressed but its 'data_size' != 'data_size_compressed':\n\""+SrcName(src, dest, pak)+'"';
  1348. goto error;
  1349. }
  1350. if(f_src->size()!=dest.data_size_compressed)
  1351. {
  1352. if(error_message)*error_message=S+"'data_size_compressed' doesn't match file size:\n\""+SrcName(src, dest, pak)+'"';
  1353. goto error;
  1354. }
  1355. // save data
  1356. if(pak._cipher_per_file)f_dest.cipherOffsetClear(); // make encryption result always the same regardless of position in Pak file
  1357. if(!f_src->copy(f_dest))
  1358. {if(error_message)*error_message=CantCopy(src, dest, pak); goto error;} // don't flush here, flush only one time at the end
  1359. // release memory
  1360. Long size=src.processed.size(); src.processed.del(); AtomicAdd(mem_available, size); // release memory first, then increase the counter
  1361. wakeUp(); // notify compressors that memory was released
  1362. }
  1363. // update progress
  1364. decompressed_processed+=dest.data_size;
  1365. if(progress)progress->progress=Sat(total_data_size_decompressed ? Dbl(decompressed_processed)/total_data_size_decompressed : 0); // use Dbl because we operate on ULong
  1366. }
  1367. }
  1368. // flush and update header
  1369. if(f_dest.is())
  1370. {
  1371. if(!f_dest.flush()){if(error_message)*error_message=CantFlush(pak.pakFileName()); goto error;} // check for correct flush before eventual adjusting the Pak header on start of the file
  1372. if(header_changed) // if during file processing, the header was changed, then we need to resave it
  1373. {
  1374. Long pos=f_dest.pos();
  1375. if(pak._cipher_per_file)f_dest.cipherOffset(f_dest_cipher_offset); // reset the cipher offset here so that saving file header will use it
  1376. if(!f_dest.pos( 0) || !pak.saveHeader(f_dest)
  1377. || !f_dest.pos(pos)){if(error_message)*error_message=CantFlush(pak.pakFileName()); goto error;}
  1378. }
  1379. }
  1380. }
  1381. return true;
  1382. }
  1383. error:
  1384. stopThreads();
  1385. f_dest.del(); FDelFile(pak.pakFileName()); pak.del(); // release the file handle first, then delete Pak file, then delete Pak object, this is important to avoid having partial incomplete Pak files (which headers could be OK, but the data is missing), we delete Pak object last because we need its file name to delete the file
  1386. return false;
  1387. }
  1388. };
  1389. /******************************************************************************/
  1390. Bool Pak::create(C Str &file, C Str &pak_name, UInt flag, Cipher *dest_cipher, Cipher *src_cipher, COMPRESS_TYPE compress, Int compression_level, Bool (*filter)(C Str &name), Str *error_message, PakProgress *progress)
  1391. {
  1392. Str f=file;
  1393. return create(MemPtr<Str>(f), pak_name, flag, dest_cipher, src_cipher, compress, compression_level, filter, error_message, progress);
  1394. }
  1395. Bool Pak::create(C MemPtr<Str> &files, C Str &pak_name, UInt flag, Cipher *dest_cipher, Cipher *src_cipher, COMPRESS_TYPE compress, Int compression_level, Bool (*filter)(C Str &name), Str *error_message, PakProgress *progress)
  1396. {
  1397. if(progress && progress->wantStop(error_message))return false;
  1398. // !! don't delete Pak anywhere here because we still need 'pak_name' which can be a Pak member !!
  1399. PakCreator pc(T, flag, src_cipher, compress, compression_level, error_message, progress);
  1400. // get files
  1401. PakCreator::FileTempContainer ftc; FREPA(files)ftc.add(files[i], filter, pc); ftc.sort();
  1402. // add files
  1403. if(FlagTest(flag, PAK_SHORTEN) && ftc.files.elms()==1 && ftc.files[0].isDir())pc.enter(ftc.files[0], -1, filter);else
  1404. {
  1405. FREPA(ftc.files)pc.add (ftc.files[i], -1 );
  1406. FREPA(ftc.files)pc.enter(ftc.files[i], i, filter);
  1407. }
  1408. // adjust Pak name
  1409. Str pn=pak_name; // !! Warning: 'pak_name' can be a Pak member !!
  1410. if(!pn.is())
  1411. {
  1412. if(ftc.files.elms()==1) // one file
  1413. {
  1414. if(ftc.files[0].fi.type!=FSTD_DRIVE)
  1415. { // when packing "c:/esenthel" create "c:/esenthel.pak"
  1416. pn=ftc.files[0].name;
  1417. }
  1418. }else
  1419. if(ftc.files.elms()>1) // multiple files
  1420. {
  1421. if(ftc.files[0].fi.type!=FSTD_DRIVE)
  1422. { // when packing ["c:/esenthel/1", "c:/esenthel/2", ..] create "c:/esenthel/esenthel.pak"
  1423. pn=GetPath(ftc.files[0].name);
  1424. if(pn.is())pn.tailSlash(true)+=GetBase(pn);
  1425. }
  1426. }
  1427. if(pn.is())pn+=".pak";
  1428. }
  1429. // create
  1430. return pc.create(pn, dest_cipher);
  1431. }
  1432. Bool Pak::create(C MemPtr<PakNode> &files, C Str &pak_name, UInt flag, Cipher *dest_cipher, COMPRESS_TYPE compress, Int compression_level, Str *error_message, PakProgress *progress)
  1433. {
  1434. if(progress && progress->wantStop(error_message))return false;
  1435. // !! don't delete Pak anywhere here because we still need 'pak_name' which can be a Pak member !!
  1436. PakCreator pc(T, flag, null, compress, compression_level, error_message, progress);
  1437. // get files
  1438. PakCreator::FileTempContainer ftc; FREPA(files)ftc.add(files[i]); ftc.sort();
  1439. // add files
  1440. if(FlagTest(flag, PAK_SHORTEN) && ftc.files.elms()==1 && ftc.files[0].isDir())pc.enter(ftc.files[0], -1, null);else
  1441. {
  1442. FREPA(ftc.files)pc.add (ftc.files[i], -1 );
  1443. FREPA(ftc.files)pc.enter(ftc.files[i], i, null);
  1444. }
  1445. // create
  1446. return pc.create(pak_name, dest_cipher); // !! Warning: 'pak_name' can be a Pak member !!
  1447. }
  1448. /******************************************************************************/
  1449. // TODO: use binarySearch, make sure Editor passes files sorted in order and that name from UID is sorted too
  1450. static Int ComparePath(C PakFileData* &a, C PakFileData* &b)
  1451. {
  1452. return ComparePath(a->name, b->name);
  1453. }
  1454. static PakNode * FindElm (Memb<PakNode> &nodes, Str name) {REPA(nodes)if(nodes[i].name==name)return &nodes[i]; return null;}
  1455. static Memb<PakNode>* FindNodeChildren(Memb<PakNode> &nodes, Str path) // find 'children' container of 'path' node, null if not found (don't create new elements)
  1456. {
  1457. Str start=_GetStart(path); if(!start.is())return &nodes;
  1458. if(PakNode *node=FindElm(nodes, start))return FindNodeChildren(node->children, GetStartNot(path));
  1459. return null;
  1460. }
  1461. static Memb<PakNode>* FindNodeChildrenIfNotRemoved(Memb<PakNode> &nodes, C Str &path, DateTime &date_time_utc) // find 'children' container of 'path' node, New if path is not removed (given path can be null or 'exists'==true, but not 'exists'==false), null otherwise, we use this function to specify an element as MARK_REMOVED so we don't need to do this in already removed path (exists==false)
  1462. {
  1463. Str start=_GetStart(path); if(!start.is())return &nodes;
  1464. PakNode *node=FindElm(nodes, start);
  1465. if(!node )node=&nodes.New().newSetFolder(start, date_time_utc);
  1466. if(!node->exists)return null; // we don't want to add new things into removed node, so return null
  1467. return FindNodeChildrenIfNotRemoved(node->children, GetStartNot(path), date_time_utc);
  1468. }
  1469. static Memb<PakNode>& GetNodeChildren(Memb<PakNode> &nodes, C Str &path, DateTime &date_time_utc) // find 'children' container of 'path' node, New if not found, force 'exists'==true everywhere, we're setting this path to specify element with REPLACE mode
  1470. {
  1471. Str start=_GetStart(path); if(!start.is())return nodes;
  1472. PakNode *node=FindElm(nodes, start);
  1473. if(node)node->exists=true; // force as existing
  1474. else node=&nodes.New().newSetFolder(start, date_time_utc);
  1475. return GetNodeChildren(node->children, GetStartNot(path), date_time_utc);
  1476. }
  1477. Bool Pak::create(C Mems<C PakFileData*> &files, C Str &pak_name, UInt flag, Cipher *dest_cipher, COMPRESS_TYPE compress, Int compression_level, Str *error_message, PakProgress *progress)
  1478. {
  1479. DateTime date_time_utc; date_time_utc.getUTC();
  1480. Memb<PakNode> nodes;
  1481. FREPA(files) // process files in order in which they were given (in case there are multiple files of same full name, we will update them according to their order)
  1482. {
  1483. if(progress && progress->wantStop(error_message))return false;
  1484. C PakFileData &pfd =*files[i];
  1485. Str base=GetBase(pfd.name),
  1486. path=GetPath(pfd.name);
  1487. switch(pfd.mode)
  1488. {
  1489. case PakFileData::REMOVE:
  1490. {
  1491. if(Memb<PakNode> *parent_children=FindNodeChildren(nodes, path)) // find parent
  1492. if(PakNode *node=FindElm(*parent_children, base)) // if that node exists in parent
  1493. parent_children->removeData(node); // remove it from parent, we don't need to keep order, because we're processing files in order in this loop, and if a file is found with the same name, then it replaces it
  1494. }break;
  1495. case PakFileData::MARK_REMOVED:
  1496. {
  1497. if(Memb<PakNode> *parent_children=FindNodeChildrenIfNotRemoved(nodes, path, date_time_utc)) // find parent
  1498. {
  1499. PakNode *node=FindElm(*parent_children, base); // find that node in parent
  1500. if( !node)node=&parent_children->New(); // if doesn't exist then create new one
  1501. node->setRemoved(base, pfd.modify_time_utc, pfd.type);
  1502. }
  1503. }break;
  1504. case PakFileData::REPLACE:
  1505. {
  1506. Memb<PakNode> &parent_children=GetNodeChildren(nodes, path, date_time_utc);
  1507. PakNode *node=FindElm(parent_children, base); // find that node in parent
  1508. if( !node)node=&parent_children.New(); // if doesn't exist then create new one
  1509. node->set(base, pfd);
  1510. }break;
  1511. }
  1512. }
  1513. // create according to created nodes
  1514. return create(nodes, pak_name, flag, dest_cipher, compress, compression_level, error_message, progress);
  1515. }
  1516. Bool Pak::create(C MemPtr<PakFileData> &files, C Str &pak_name, UInt flag, Cipher *dest_cipher, COMPRESS_TYPE compress, Int compression_level, Str *error_message, PakProgress *progress)
  1517. {
  1518. Mems<C PakFileData*> f; f.setNum(files.elms()); REPAO(f)=&files[i];
  1519. return create(f, pak_name, flag, dest_cipher, compress, compression_level, error_message, progress);
  1520. }
  1521. /******************************************************************************/
  1522. // MAIN
  1523. /******************************************************************************/
  1524. static void ExcludeChildren(Pak &pak, C PakFile &pf, Memt<Bool> &is)
  1525. {
  1526. REP(pf.children_num)
  1527. {
  1528. Int child_i =pf.children_offset+i; // get index of i-th child in 'pf' file
  1529. is[child_i]=false; // exclude that child
  1530. ExcludeChildren(pak, pak.file(child_i), is); // exclude all children of that child too
  1531. }
  1532. }
  1533. Bool PakUpdate(Pak &src_pak, C MemPtr<PakFileData> &update_files, C Str &pak_name, Cipher *dest_cipher, COMPRESS_TYPE compress, Int compression_level, Str *error_message, PakProgress *progress)
  1534. {
  1535. if(error_message)error_message->clear();
  1536. // set 'src_pak' files as 'PakFileData'
  1537. Memc<PakFileData> src_files; // this container will include all files from 'src_pak' that weren't excluded (weren't replaced by newer versions from 'update_files')
  1538. {
  1539. Memt<Bool> is; is.setNum(src_pak.totalFiles()); SetMem(is.data(), true, is.elms()); // set 'is' array specifying which files from 'src_pak' should be placed in target file
  1540. REPA(update_files) // check all new elements (order is not important as we're comparing them to 'src_pak' files only)
  1541. {
  1542. C PakFileData &pfd=update_files[i]; // take new element
  1543. if(C PakFile *pf =src_pak.find(pfd.name, true)) // if there exists an original version (file in 'src_pak')
  1544. {
  1545. Int i=src_pak.files().index(pf); // take the index of file in 'src_pak'
  1546. is[i]=false; // exclude that file from 'src_pak' (instead of it, we'll use 'pfd' - the newer version from 'update_files')
  1547. if(pfd.mode!=PakFileData::REPLACE)ExcludeChildren(src_pak, *pf, is); // if the newer version removes old file then we need to exclude also all children of 'pf' in 'src_pak'
  1548. }
  1549. }
  1550. FREPA(src_pak)if(is[i])
  1551. {
  1552. C PakFile &pf =src_pak .file(i);
  1553. PakFileData &pfd=src_files.New ( );
  1554. pfd.mode =(FlagTest(pf.flag, PF_REMOVED) ? PakFileData::MARK_REMOVED : PakFileData::REPLACE);
  1555. pfd.type =pf.type();
  1556. pfd.compress_mode =COMPRESS_KEEP_ORIGINAL; // keep source files in original compression (for example if a Sound file was requested to have no compression before, to speed up streaming playback, then let's keep it)
  1557. pfd.compressed =pf.compression;
  1558. pfd.decompressed_size=pf.data_size;
  1559. pfd.name =src_pak.fullName(i);
  1560. pfd.xxHash64_32 =pf.data_xxHash64_32;
  1561. pfd.modify_time_utc =pf.modify_time_utc;
  1562. pfd.data.set(pf, src_pak);
  1563. }
  1564. // here 'is' is deleted
  1565. }
  1566. // move everything to one container, order is important
  1567. Mems<C PakFileData*> all_files; all_files.setNum(src_files.elms()+update_files.elms());
  1568. FREPA( src_files)all_files[i ]=& src_files[i]; // first the src files
  1569. FREPA(update_files)all_files[i+src_files.elms()]=&update_files[i]; // now the update files, in order in which they were given (in case there are multiple files of same full name)
  1570. // create Pak basing on all files
  1571. Pak temp; temp.pakFileName(pak_name); // this will normalize and make full path, we need the full name to make the comparison
  1572. Bool temp_file=(EqualPath(src_pak.pakFileName(), temp.pakFileName()) && temp.pakFileName().is()); // if dest name is the same as source name, then we have to write to a temporary file (but ignore if the name is empty, perhaps it wants to update Pak object only without operating on files)
  1573. if( temp.create(all_files, temp_file ? temp.pakFileName()+"@new" : temp.pakFileName(), 0, dest_cipher, compress, compression_level, error_message, progress)) // have to work on a temporary Pak, because during creation we may access files from the old Pak
  1574. {
  1575. if(temp_file) // if we've created a temp file, we need to rename it first
  1576. {
  1577. if(!FRename(temp.pakFileName(), src_pak.pakFileName())) // if failed to rename
  1578. {
  1579. FDelFile(temp.pakFileName()); // delete the new file
  1580. return false; // return without updating 'src_pak'
  1581. }
  1582. Swap(temp._file_name, src_pak._file_name); // swap file names, because we need 'temp' to have the original file name, and 'src_pak' is going to be deleted so we don't care
  1583. }
  1584. Swap(src_pak, temp); return true;
  1585. } // no need to do anything because 'Pak.create' will delete the file on failure
  1586. return false;
  1587. }
  1588. /******************************************************************************/
  1589. Bool Equal(C PakFile *a, C PakFile *b)
  1590. {
  1591. if(a && (a->flag&PF_REMOVED))a=null; // treat as if doesn't exist
  1592. if(b && (b->flag&PF_REMOVED))b=null; // treat as if doesn't exist
  1593. if(a && b) // both exist
  1594. {
  1595. return a->type() ==b->type() // same type
  1596. && a->data_size==b->data_size // same size
  1597. && ((a->data_size ) ? !Compare(a->modify_time_utc , b->modify_time_utc, 1) : true) // if they have data then their modification time must match (1 second tolerance due to Fat32)
  1598. && ((a->data_size && a->data_xxHash64_32 && b->data_xxHash64_32) ? a->data_xxHash64_32==b->data_xxHash64_32 : true); // if they have data and both have information about hash then it must match
  1599. }
  1600. return !a && !b; // true only if both don't exist (one exists and other doesn't -> they're different)
  1601. }
  1602. Bool Equal(C PakFileData *pfd, C PakFile *pf)
  1603. {
  1604. if(pfd && (pfd->mode==PakFileData::REMOVE || pfd->mode==PakFileData::MARK_REMOVED))pfd=null; // treat as if doesn't exist
  1605. if(pf && (pf ->flag&PF_REMOVED ))pf =null; // treat as if doesn't exist
  1606. if(pfd && pf) // both exist
  1607. {
  1608. if(pfd->type!=pf->type())return false; // different type
  1609. Long pfd_size=pfd->decompressed_size;
  1610. DateTime pfd_time=pfd->modify_time_utc;
  1611. UInt pfd_hash=pfd->xxHash64_32;
  1612. // always override values from PAK_FILE to keep consistency with 'Pak.create'
  1613. if(pfd->data.type==DataSource::PAK_FILE)if(C PakFile *pf=pfd->data.pak_file) // from PAK_FILE we can always extract the size, even if it's compressed
  1614. {
  1615. pfd_size=pf->data_size;
  1616. pfd_time=pf->modify_time_utc;
  1617. if(!pfd_hash)pfd_hash=pf->data_xxHash64_32; // override only if user didn't calculate it (because it's possible that 'pfd_hash' is calculated but 'pf->data_xxHash64_32' left at 0)
  1618. }
  1619. if(pfd_size<0 && !pfd->compressed)switch(pfd->data.type) // if size is unknown and source is not compressed
  1620. {
  1621. case DataSource::NAME:
  1622. {
  1623. FileInfo fi; if(fi.get(pfd->data.name))
  1624. {
  1625. pfd_size=fi.size;
  1626. pfd_time=fi.modify_time_utc;
  1627. }
  1628. }break;
  1629. case DataSource::STD:
  1630. {
  1631. FileInfo fi; if(fi.getSystem(pfd->data.name))
  1632. {
  1633. pfd_size=fi.size;
  1634. pfd_time=fi.modify_time_utc;
  1635. }
  1636. }break;
  1637. default: pfd_size=pfd->data.size(); break;
  1638. }
  1639. if(pfd_size!=pf->data_size)return false; // different size
  1640. if(pfd_size) // check time and hash only if have data (to skip empty files)
  1641. {
  1642. if(pfd_hash && pf->data_xxHash64_32 && pfd_hash!=pf->data_xxHash64_32)return false; // both hashes known and different
  1643. if(!pfd_time.valid())switch(pfd->data.type) // if time is unknown
  1644. {
  1645. case DataSource::NAME: {FileInfo fi; if(fi.get (pfd->data.name))pfd_time=fi.modify_time_utc;} break;
  1646. case DataSource::STD : {FileInfo fi; if(fi.getSystem(pfd->data.name))pfd_time=fi.modify_time_utc;} break;
  1647. }
  1648. if(Compare(pfd_time, pf->modify_time_utc, 1))return false; // different time (1 second tolerance due to Fat32)
  1649. }
  1650. }
  1651. return !pfd == !pf; // true only if both exist or both don't exist
  1652. }
  1653. /******************************************************************************/
  1654. static Int ComparePF(C PakFileData*C &a, C PakFileData*C &b) {return ComparePath(a->name, b->name);}
  1655. static Int ComparePF(C PakFileData*C &a, C Str &b) {return ComparePath(a->name, b );}
  1656. Bool PakEqual(C MemPtr<PakFileData> &files, C Pak &pak)
  1657. {
  1658. Memt<C PakFileData*> files_sorted; files_sorted.setNum(files.elms()); REPAO(files_sorted)=&files[i];
  1659. files_sorted.sort(ComparePF);
  1660. REPA(files)
  1661. {
  1662. C PakFileData &pfd=files[i];
  1663. if(!Equal(&pfd, pak.find(pfd.name)))return false;
  1664. }
  1665. REPA(pak)
  1666. {
  1667. C PakFile &pf=pak.file(i);
  1668. if(!FlagTest(pf.flag, PF_STD_DIR)) // skip folders because they're not required to be in the 'PakFileData' list
  1669. {
  1670. C PakFileData **files_pfd=files_sorted.binaryFind(pak.fullName(pf), ComparePF);
  1671. if(!Equal(files_pfd ? *files_pfd : null, &pf))return false;
  1672. }
  1673. }
  1674. return true;
  1675. }
  1676. Bool PakEqual(C MemPtr<PakFileData> &files, C Str &name, Cipher *cipher)
  1677. {
  1678. if(name.is())
  1679. {
  1680. Pak pak; if(pak.load(name, cipher))return PakEqual(files, pak);
  1681. }
  1682. return false;
  1683. }
  1684. /******************************************************************************/
  1685. }
  1686. /******************************************************************************/