Patcher.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460
  1. /******************************************************************************/
  2. #include "stdafx.h"
  3. namespace EE{
  4. /******************************************************************************/
  5. // STATICS
  6. /******************************************************************************/
  7. Patcher::LocalFile& Patcher::LocalFile::set(C Str &full_name, C FileInfo &fi)
  8. {
  9. T.full_name =full_name;
  10. T.type =((fi.type==FSTD_FILE || fi.type==FSTD_LINK) ? Patcher::LocalFile::SYSTEM_FILE : Patcher::LocalFile::SYSTEM_DIR);
  11. T.file_size =fi.size;
  12. T.modify_time_utc=fi.modify_time_utc;
  13. T.xxHash64_32 =0;
  14. return T;
  15. }
  16. static void AddFiles(MemPtr<Patcher::LocalFile> &local_files, FileFind &ff, Str parent)
  17. {
  18. local_files.New().set(parent+ff.name, ff);
  19. if(ff.type==FSTD_DIR)
  20. {
  21. parent+=ff.name; parent+='\\';
  22. for(FileFind child(ff.pathName()); child(); )AddFiles(local_files, child, parent);
  23. }
  24. }
  25. void Patcher::SetList(MemPtr<LocalFile> local_files, C Str &dir)
  26. {
  27. local_files.clear();
  28. for(FileFind ff(dir); ff(); )AddFiles(local_files, ff, S);
  29. }
  30. void Patcher::SetList(MemPtr<LocalFile> local_files, C PakSet &paks)
  31. {
  32. local_files.clear();
  33. FREPA(paks)
  34. {
  35. LocalFile &lf= local_files.New();
  36. C PakFile &pf=*paks.file(i).file;
  37. lf.type =LocalFile::PAK_FILE;
  38. lf.full_name =paks.fullName(i);
  39. lf.file_size =pf.data_size;
  40. lf.xxHash64_32 =pf.data_xxHash64_32;
  41. lf.modify_time_utc=pf.modify_time_utc;
  42. }
  43. }
  44. /******************************************************************************/
  45. // PATCHER DOWNLOADED
  46. /******************************************************************************/
  47. void Patcher::Downloaded::create(C Pak &pak, Int index, Download &download, Cipher *cipher)
  48. {
  49. C PakFile &pf=pak.file(index);
  50. full_name=pak.fullName(pf);
  51. T.index =index;
  52. if(download.size()==pf.data_size_compressed)
  53. {
  54. File temp; temp.readMem(download.data(), download.size(), cipher);
  55. if(pf.compression){if(!DecompressRaw(temp, data, pf.compression, pf.data_size_compressed, pf.data_size, true))goto error;}
  56. else {temp.copy(data.writeMemFixed(temp.left()));}
  57. if(data.size()!=pf.data_size)goto error;
  58. data.pos(0);
  59. if(pf.data_xxHash64_32) // verify hash if available
  60. {
  61. if(pf.data_xxHash64_32!=data.xxHash64_32())goto error;
  62. data.pos(0);
  63. }
  64. success =true;
  65. type =pf.type();
  66. xxHash64_32 =pf.data_xxHash64_32;
  67. modify_time_utc=pf.modify_time_utc;
  68. return;
  69. }
  70. error:
  71. data.del();
  72. }
  73. void Patcher::Downloaded::createEmpty(C Pak &pak, Int index)
  74. {
  75. C PakFile &pf=pak.file(index);
  76. success =true;
  77. xxHash64_32 =pf.data_xxHash64_32;
  78. type =pf.type();
  79. modify_time_utc=pf.modify_time_utc;
  80. full_name =pak.fullName(pf);
  81. T.index =index;
  82. }
  83. void Patcher::Downloaded::createFail(C Pak &pak, Int index)
  84. {
  85. C PakFile &pf=pak.file(index);
  86. full_name=pak.fullName(pf);
  87. T.index =index;
  88. type =pf.type();
  89. }
  90. /******************************************************************************/
  91. Patcher::InstallerInfo::InstallerInfo() {zero();}
  92. /******************************************************************************/
  93. // PATCHER
  94. /******************************************************************************/
  95. void Patcher::zero()
  96. {
  97. _pak_available =false;
  98. _inst_info_available=false;
  99. _inst_available =false;
  100. _cipher =null;
  101. _files_left =0;
  102. _bytes_downloaded =0;
  103. REPAO(_file_download).index=-1;
  104. }
  105. Patcher::Patcher() {zero();}
  106. Patcher& Patcher::del()
  107. {
  108. _thread.del (); // first delete thread
  109. _pak_download.del (); REPAO(_file_download).del(); _inst_info_download.del(); _inst_download.del(); // delete all downloaders
  110. _pak .del ();
  111. _inst_info .zero();
  112. _inst .del ();
  113. _to_download .del ();
  114. _downloaded .del ();
  115. _http .del ();
  116. _name .del ();
  117. zero(); return T;
  118. }
  119. Patcher& Patcher::create(C Str &http_dir, C Str &upload_name, Cipher *cipher)
  120. {
  121. del();
  122. T._http =http_dir; T._http.tailSlash(true);
  123. T._name =upload_name;
  124. T._cipher=cipher;
  125. return T;
  126. }
  127. /******************************************************************************/
  128. Patcher& Patcher::downloadInstallerInfo()
  129. {
  130. if(is())
  131. {
  132. _inst_info_available=false;
  133. _inst_info.zero();
  134. _inst_info_download.create(_http+CaseDown(_name)+".installer.txt");
  135. }
  136. return T;
  137. }
  138. DWNL_STATE Patcher::installerInfoState()
  139. {
  140. if(_inst_info_available)return DWNL_DONE;
  141. DWNL_STATE state=_inst_info_download.state(); if(state==DWNL_DONE)
  142. {
  143. FileText f; f.readMem(_inst_info_download.data(), _inst_info_download.size()); TextData data; if(data.load(f))
  144. {
  145. if(TextNode *p=data.findNode("Size" ))_inst_info.size =p->asInt();
  146. //if(TextNode *p=data.findNode("xxHash32" ))_inst_info.xxHash32 =TextUInt (S+"0x"+p->value);
  147. if(TextNode *p=data.findNode("xxHash64" ))_inst_info.xxHash64_32=TextULong(S+"0x"+p->value)&UINT_MAX; // use 'TextULong' because 'TextUInt' may fail on large values
  148. //if(TextNode *p=data.findNode("xxHash64" ))_inst_info.xxHash64 =TextULong(S+"0x"+p->value);
  149. if(TextNode *p=data.findNode("ModifyTimeUTC"))_inst_info.modify_time_utc.fromText(p->value);
  150. _inst_info_available=true; _inst_info_download.del(); return DWNL_DONE;
  151. }
  152. _inst_info_download.del().error(); state=DWNL_ERROR;
  153. }
  154. return state;
  155. }
  156. C Patcher::InstallerInfo* Patcher::installerInfo() {return (installerInfoState()==DWNL_DONE) ? &_inst_info : null;}
  157. /******************************************************************************/
  158. Patcher& Patcher::downloadInstaller()
  159. {
  160. if(is())
  161. {
  162. if(_inst_info_download.state()==DWNL_NONE)downloadInstallerInfo(); // for "Installer" we will also need "Installer Info"
  163. _inst_available=false;
  164. _inst.del();
  165. _inst_download.create(_http+_name+" Installer.exe");
  166. }
  167. return T;
  168. }
  169. DWNL_STATE Patcher::installerState()
  170. {
  171. if(_inst_available)return DWNL_DONE;
  172. DWNL_STATE state=_inst_download.state(); if(state==DWNL_DONE)
  173. {
  174. DWNL_STATE inst_info_state=installerInfoState(); if(inst_info_state!=DWNL_DONE)return inst_info_state; // we need "Installer Info"
  175. if( _inst_info.size == _inst_download.size()
  176. && (_inst_info.xxHash64_32 ? _inst_info.xxHash64_32==xxHash64_32Mem(_inst_download.data(), _inst_download.size()) : true))
  177. {
  178. _inst.setNum(_inst_download.size()); CopyFast(_inst.data(), _inst_download.data(), _inst.elms()); // copy data
  179. _inst_available=true; _inst_download.del(); return DWNL_DONE;
  180. }
  181. _inst_download.del().error(); state=DWNL_ERROR;
  182. }
  183. return state;
  184. }
  185. Int Patcher::installerProgress() {return _inst_download.done();}
  186. C Mems<Byte>* Patcher::installer () {return (installerState()==DWNL_DONE) ? &_inst : null;}
  187. /******************************************************************************/
  188. Patcher& Patcher::downloadIndex()
  189. {
  190. if(is())
  191. {
  192. _thread.del(); // first delete thread
  193. _pak_available=false;
  194. _pak.del();
  195. _pak_download.create(_http+CaseDown(_name)+".index.pak"); REPAO(_file_download).del();
  196. _to_download .del();
  197. _downloaded .del();
  198. _files_left=0;
  199. }
  200. return T;
  201. }
  202. DWNL_STATE Patcher::indexState()
  203. {
  204. if(_pak_available)return DWNL_DONE;
  205. DWNL_STATE state=_pak_download.state(); if(state==DWNL_DONE)
  206. {
  207. File compressed(_pak_download.data(), _pak_download.size(), _cipher), decompressed;
  208. if(Decompress(compressed, decompressed, true))
  209. {
  210. decompressed.pos(0); switch(_pak.loadHeader(decompressed))
  211. {
  212. case PAK_LOAD_OK :
  213. case PAK_LOAD_INCOMPLETE_DATA: _pak_available=true; _pak_download.del(); return DWNL_DONE;
  214. }
  215. }
  216. _pak_download.del().error(); state=DWNL_ERROR;
  217. }
  218. return state;
  219. }
  220. C Pak* Patcher::index() {return (indexState()==DWNL_DONE) ? &_pak : null;}
  221. /******************************************************************************/
  222. static Str ServerPath(Str path, bool file=true) // !! this needs to be in sync with "Uploader" tool !!
  223. {
  224. Str out; if(file){Str ext=GetExt(path); if(ext=="pl" || ext=="php" || ext=="old" || ext=="dat" || ext=="dll" || ext=="bat" || ext=="cmd")path+='_';} // these file formats can't be downloaded normally, because they're treated as scripts or some can be blocked by servers
  225. for(;;)
  226. {
  227. Str base=GetBase(path); if(!base.is())break;
  228. out =S + (file ? "f-" : "d-") + CaseDown(base) + (out.is() ? '/' : '\0') + out;
  229. path=GetPath(path);
  230. file=false;
  231. }
  232. return out;
  233. }
  234. void Patcher::update()
  235. {
  236. REPA(_file_download)
  237. {
  238. FileDownload &download=_file_download[i];
  239. switch( download.state())
  240. {
  241. case DWNL_DONE: // finished downloading
  242. {
  243. Downloaded downloaded; downloaded.create(_pak, download.index, download, _cipher);
  244. {SyncLocker locker(_lock); Swap(downloaded, T._downloaded.New()); download.index=-1; _bytes_downloaded+=download.done(); download.del();} // adjust '_bytes_downloaded' and delete 'download' under lock to have correct knowledge of progress
  245. }break;
  246. case DWNL_DOWNLOAD: // verify while downloading
  247. {
  248. C PakFile &pf=_pak.file(download.index);
  249. if((download.size()>=0) ? (download.size()!=pf.data_size_compressed) // if file size is known and it's different than expected
  250. : (download.done()> pf.data_size_compressed)) // if file size is unknown but already exceeded what expected
  251. {
  252. goto set_failed; // set as failed download
  253. }
  254. }break;
  255. case DWNL_ERROR: // error encountered
  256. {
  257. set_failed:
  258. Downloaded downloaded; downloaded.createFail(_pak, download.index); // create 'downloaded' as failed
  259. {SyncLocker locker(_lock); Swap(downloaded, T._downloaded.New()); download.index=-1; _bytes_downloaded+=download.done(); download.del();} // adjust '_bytes_downloaded' and delete 'download' under lock to have correct knowledge of progress
  260. }break;
  261. case DWNL_NONE: // not downloading anything
  262. {
  263. SyncLockerEx locker(_lock); if(_to_download.elms()) // check for elements to download
  264. {
  265. Int file_index=_to_download.pop(); locker.off();
  266. #if 1 // direct download
  267. download.create(_http+CaseDown(_name)+'/'+ServerPath(_pak.fullName(file_index))); // start downloading
  268. #else // through PHP (had problems when tried accessing 6 downloads at the same time)
  269. Memt<TextParam> params; params.New().set("file", ServerPath(_pak.fullName(file_index)));
  270. download.create(_http+CaseDown(_name)+".download.php", params); // start downloading
  271. #endif
  272. download.index=file_index;
  273. }
  274. }break;
  275. }
  276. }
  277. }
  278. static Bool PatcherFunc(Thread &thread)
  279. {
  280. ((Patcher*)thread.user)->update();
  281. #if HAS_THREADS
  282. Time.wait(1);
  283. #endif
  284. return true;
  285. }
  286. /******************************************************************************/
  287. Patcher& Patcher::downloadFile(Int i)
  288. {
  289. if(C Pak *pak=index())if(InRange(i, pak->files()))
  290. {
  291. C PakFile &pf=pak->file(i);
  292. if(pf.data_size) // has data size -> needs to be downloaded
  293. {
  294. {SyncLocker locker(_lock); _to_download.add(i); _files_left++;}
  295. if(!_thread.created())_thread.create(PatcherFunc, this);
  296. }else
  297. {
  298. SyncLocker locker(_lock); _downloaded.New().createEmpty(*pak, i); _files_left++;
  299. }
  300. }
  301. return T;
  302. }
  303. Bool Patcher::getNextDownload(Patcher::Downloaded &downloaded)
  304. {
  305. SyncLocker locker(_lock);
  306. if(T._downloaded.elms())
  307. {
  308. Swap(downloaded, T._downloaded.last()); _downloaded.removeLast(); _files_left--;
  309. return true;
  310. }
  311. return false;
  312. }
  313. /******************************************************************************/
  314. Long Patcher::progress()C
  315. {
  316. SyncLocker locker(_lock);
  317. Long size=_bytes_downloaded; REPA(_file_download)size+=_file_download[i].done();
  318. return size;
  319. }
  320. Long Patcher::filesSize()C
  321. {
  322. SyncLocker locker(_lock);
  323. Long size=_bytes_downloaded; REPA(_to_download)size+=_pak.file(_to_download[i]).data_size_compressed; REPA(_file_download){Int file_index=_file_download[i].index; if(InRange(file_index, _pak))size+=_pak.file(file_index).data_size_compressed;}
  324. return size;
  325. }
  326. Int Patcher::filesLeft()C {return _files_left;}
  327. /******************************************************************************/
  328. struct LocalFilePtr
  329. {
  330. C Patcher::LocalFile *lf;
  331. Int org_index;
  332. void set(C MemPtr<Patcher::LocalFile> &local_files, Int index) {T.lf=&local_files[index]; T.org_index=index;}
  333. static Int Compare(C LocalFilePtr &a, C LocalFilePtr &b) {return ComparePath(a.lf->full_name, b.lf->full_name);}
  334. static Int Compare(C LocalFilePtr &a, C Str &b) {return ComparePath(a.lf->full_name, b );}
  335. };
  336. static Bool Equal(C PakFile *pf, C Patcher::LocalFile *lf)
  337. {
  338. if(pf && (pf->flag&PF_REMOVED))pf=null; // treat as if doesn't exist
  339. if(!pf && !lf)return true; // both don't exist = they're the same
  340. if( pf && lf) // both exist
  341. {
  342. // verify File/Dir for local files of SYSTEM_* type
  343. if(lf->type!=Patcher::LocalFile::PAK_FILE) // not PAK_FILE -> it's a SYSTEM_* type
  344. if(FlagTest(pf->flag, PF_STD_DIR) != (lf->type==Patcher::LocalFile::SYSTEM_DIR))
  345. return false;
  346. return pf->data_size==lf->file_size // files have same sizes
  347. && ((pf->data_size ) ? !Compare(pf->modify_time_utc , lf->modify_time_utc, 1) : true) // if they are files (have data) then their modification time must match (1 second tolerance due to Fat32)
  348. && ((pf->data_xxHash64_32 && lf->xxHash64_32) ? pf->data_xxHash64_32==lf->xxHash64_32 : true); // if they both have information about hash then it must match
  349. }
  350. return false; // one exists and other doesn't = they're different
  351. }
  352. static Bool Equal(C Patcher::LocalFile *a, C Patcher::LocalFile *b)
  353. {
  354. if(!a && !b)return true; // both don't exist = they're the same
  355. if( a && b) // both exist
  356. {
  357. if(a->type!=b->type) // if types are different
  358. if(a->type!=Patcher::LocalFile::PAK_FILE && b->type!=Patcher::LocalFile::PAK_FILE) // and both are not PAK_FILE -> they're SYSTEM_* type
  359. return false; // they must match
  360. return a->file_size==b->file_size // files have same sizes
  361. && ((a->file_size ) ? !Compare(a->modify_time_utc, b->modify_time_utc, 1) : true) // if they are files (have data) then their modification time must match (1 second tolerance due to Fat32)
  362. && ((a->xxHash64_32 && b->xxHash64_32) ? a->xxHash64_32 ==b->xxHash64_32 : true); // if they both have information about hash then it must match
  363. }
  364. return false; // one exists and other doesn't = they're different
  365. }
  366. /******************************************************************************/
  367. Bool Patcher::compare(C MemPtr<LocalFile> &local_files, MemPtr<Int> local_remove, MemPtr<Int> server_download)
  368. {
  369. local_remove .clear();
  370. server_download.clear();
  371. if(C Pak *pak=index())
  372. {
  373. // sort 'local_files', thanks to this we can use 'binarySearch' later
  374. Memt<LocalFilePtr> lfp; FREPA(local_files)lfp.New().set(local_files, i); lfp.sort(LocalFilePtr::Compare);
  375. // remove duplicate names
  376. REPA(lfp)if(i)
  377. {
  378. LocalFilePtr &l=lfp[i], &l_1=lfp[i-1];
  379. if(EqualPath(l.lf->full_name, l_1.lf->full_name)) // if 2 files on the list have the same full name
  380. lfp.remove((l.org_index<l_1.org_index) ? i : i-1, true); // remove the one with smaller index, greater index means it was added later, and replaces the "older version with smaller index"
  381. }
  382. // check which files should be removed
  383. FREPA(lfp) // 'lfp' is sorted by path, go from start to remove folders first
  384. {
  385. C LocalFilePtr &l =lfp[i];
  386. C LocalFile &lf=*l.lf;
  387. C PakFile *pf=pak->find(lf.full_name);
  388. if(!Equal(pf, &lf)) // add to list of files for deletion
  389. if(!(lf.type==LocalFile::PAK_FILE && pf)) // if the local file is stored in a Pak, and there exists server version of that file, then don't mark this as to be removed, because it will already be listed in 'server_download' list, and when downloaded, it will replace the existing file (having PAK_FILE listed in both lists - remove and download, could cause errors if in 'PakUpdate' we would update it first - because of download list, and remove it later - because of remove list), test this only for PAK_FILE and not for System, because for System we do need to remove it first, even if it will be updated later via download replace (for example it was originally a folder, but we're replacing it with a file, so we do need to include this in remove list to remove the folder, only after element was removed, the new file can be created)
  390. local_remove.add(l.org_index);
  391. }
  392. // check which files should be downloaded
  393. FREPA(*pak)
  394. {
  395. C PakFile &pf=pak->file(i);
  396. C LocalFile *lf=null; if(LocalFilePtr *lfp_elm=lfp.binaryFind(pak->fullName(pf), LocalFilePtr::Compare))lf=lfp_elm->lf;
  397. if(!Equal(&pf, lf))server_download.add(i); // add to list of files for download
  398. }
  399. return true;
  400. }
  401. return false;
  402. }
  403. /******************************************************************************/
  404. void Patcher::Compare(C MemPtr<LocalFile> &src_files, C MemPtr<LocalFile> &dest_files, MemPtr<Int> dest_remove, MemPtr<Int> src_copy)
  405. {
  406. dest_remove.clear();
  407. src_copy .clear();
  408. // sort files, thanks to this we can use 'binarySearch' later
  409. Memt<LocalFilePtr> dest_ptrs; FREPA(dest_files)dest_ptrs.New().set(dest_files, i); dest_ptrs.sort(LocalFilePtr::Compare);
  410. Memt<LocalFilePtr> src_ptrs; FREPA( src_files) src_ptrs.New().set( src_files, i); src_ptrs.sort(LocalFilePtr::Compare);
  411. // remove duplicate names
  412. REPA(dest_ptrs)if(i)
  413. {
  414. LocalFilePtr &l=dest_ptrs[i], &l_1=dest_ptrs[i-1];
  415. if(EqualPath(l.lf->full_name, l_1.lf->full_name)) // if 2 files on the list have the same full name
  416. dest_ptrs.remove((l.org_index<l_1.org_index) ? i : i-1, true); // remove the one with smaller index, greater index means it was added later, and replaces the "older version with smaller index"
  417. }
  418. REPA(src_ptrs)if(i)
  419. {
  420. LocalFilePtr &l=src_ptrs[i], &l_1=src_ptrs[i-1];
  421. if(EqualPath(l.lf->full_name, l_1.lf->full_name)) // if 2 files on the list have the same full name
  422. src_ptrs.remove((l.org_index<l_1.org_index) ? i : i-1, true); // remove the one with smaller index, greater index means it was added later, and replaces the "older version with smaller index"
  423. }
  424. // check which files should be removed
  425. FREPA(dest_ptrs) // 'dest_ptrs' is sorted by path, go from start to remove folders first
  426. {
  427. C LocalFilePtr &dest_ptr= dest_ptrs[i];
  428. C LocalFile &dest =*dest_ptr.lf;
  429. C LocalFile *src =null; if(LocalFilePtr *src_ptr=src_ptrs.binaryFind(dest.full_name, LocalFilePtr::Compare))src=src_ptr->lf;
  430. if(!Equal(src, &dest)) // add to list of files for deletion
  431. if(!(dest.type==LocalFile::PAK_FILE && src)) // if the dest file is stored in a Pak, and there exists src version of that file, then don't mark this as to be removed, because it will already be listed in 'src_copy' list, and when copied, it will replace the existing file (having PAK_FILE listed in both lists - remove and copy, could cause errors if in 'PakUpdate' we would update it first - because of copy list, and remove it later - because of remove list), test this only for PAK_FILE and not for System, because for System we do need to remove it first, even if it will be updated later via copy replace (for example it was originally a folder, but we're replacing it with a file, so we do need to include this in remove list to remove the folder, only after element was removed, the new file can be created)
  432. dest_remove.add(dest_ptr.org_index);
  433. }
  434. // check which files should be copied
  435. FREPA(src_ptrs)
  436. {
  437. C LocalFilePtr &src_ptr= src_ptrs[i];
  438. C LocalFile &src =*src_ptr.lf;
  439. C LocalFile *dest =null; if(LocalFilePtr *dest_ptr=dest_ptrs.binaryFind(src.full_name, LocalFilePtr::Compare))dest=dest_ptr->lf;
  440. if(!Equal(dest, &src))src_copy.add(src_ptr.org_index); // add to list of files for copy
  441. }
  442. }
  443. /******************************************************************************/
  444. }
  445. /******************************************************************************/