DatFile.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549
  1. //
  2. // Created by Иван_Архипов on 31.10.2017.
  3. //
  4. #include "DatFile.h"
  5. #include "BinaryData.h"
  6. #include "Common/DatException.h"
  7. #include "SubDirectory.h"
  8. #include "Subfile.h"
  9. #include "SubfileData.h"
  10. #include <locale>
  11. extern "C++"
  12. {
  13. namespace LOTRO_DAT {
  14. DatFile::DatFile() {
  15. dat_state_ = CLOSED;
  16. patched_ = false;
  17. }
  18. DatFile::DatFile(const char *filename, int dat_id) {
  19. dat_id_ = dat_id;
  20. dat_state_ = CLOSED;
  21. patched_ = false;
  22. OpenDatFile(filename);
  23. ReadSuperBlock();
  24. MakeDirectories();
  25. try {
  26. MakeDictionary();
  27. } catch (std::exception &e) {
  28. fprintf(stderr, "Caught %s exception.", e.what());
  29. fprintf(stderr, "Unable to make dictionary!! Unable to init DatFile!!!");
  30. return;
  31. }
  32. if (dat_state_ == SUCCESS_DICTIONARY)
  33. dat_state_ = READY;
  34. else
  35. throw DatException("Bad DatFile initialization! Not all init states were successfully passed!",
  36. INIT_EXCEPTION);
  37. //patched_ = true;
  38. //for (auto i : dictionary_)
  39. // patched_list[i.second->file_id()] = new BinaryData(i.second->MakeHeaderData());
  40. }
  41. DatFile::~DatFile() {
  42. if (patched_) {
  43. std::cout << "There are some updated files. Rewriting dictionary..." << std::endl << std::flush;
  44. UpdateHeader();
  45. std::cout << "Updated header..." << std::endl << std::flush;
  46. UpdateSubdirectories();
  47. std::cout << "Updated subdirectories..." << std::endl << std::flush;
  48. std::cout << "Changed " << patched_list.size() << " files..." << std::endl << std::flush;
  49. for (auto i : patched_list)
  50. delete i.second;
  51. }
  52. if (file_handler_ != nullptr)
  53. fclose(file_handler_);
  54. delete file_handler_;
  55. delete root_directory_;
  56. }
  57. /// Extracts file with file_id.
  58. /// If path is undefined then it will be recognised as current working directory
  59. /// Output file path consists of "path + file_id + file_extension";
  60. /// NOTICE: The directory, mentioned in "std::string path" variable SHOULD BE ALREADY CREATED;
  61. /// Otherwise DatException() will be thrown.
  62. /// Returns true, if file was successfully extracted;
  63. /// Throws DatException() if undefined behaviour happened
  64. bool DatFile::ExtractFile(long long file_id, const std::string &path) {
  65. if (dat_state_ != READY) {
  66. throw DatException("Bad DatFile::ExtractFile() - invalid DatFile state!", EXPORT_EXCEPTION);
  67. }
  68. BinaryData file_data;
  69. try {
  70. file_data = GetFileData(dictionary_[file_id], 8);
  71. } catch (std::exception &e) {
  72. fprintf(stderr, "Caught %s exception.", e.what());
  73. fprintf(stderr, "Unable to extract file due to uncaught exception while getting file data. Passing...\n");
  74. return false;
  75. }
  76. try {
  77. SubfileData export_data = dictionary_[file_id]->PrepareForExport(file_data);
  78. export_data.binary_data.WriteToFile(path + export_data.options["ext"].as<std::string>());
  79. } catch (std::exception &e) {
  80. fprintf(stderr, "Caught %s exception.", e.what());
  81. fprintf(stderr, "Unable to extract file due to uncaught exception while preparing file for export. Passing...\n");
  82. return false;
  83. }
  84. return true;
  85. }
  86. /// Extracts file with file_id to database "db".
  87. /// DATABASE SHOULD BE ALREADY CREATED; Otherwise DatException will be called.
  88. /// NOTICE: The directory, mentioned in "std::string path" variable SHOULD BE ALREADY CREATED;
  89. /// Otherwise DatException() will be thrown.
  90. /// Returns true, if file was successfully extracted;
  91. /// Throws DatException() if undefined behaviour happened
  92. bool DatFile::ExtractFile(long long file_id, Database *db) {
  93. if (dat_state_ != READY) {
  94. throw DatException("Bad DatFile::ExtractFile() - invalid DatFile state!", EXPORT_EXCEPTION);
  95. }
  96. BinaryData file_data;
  97. try {
  98. file_data = GetFileData(dictionary_[file_id], 8);
  99. } catch (std::exception &e) {
  100. fprintf(stderr, "Caught %s exception.", e.what());
  101. fprintf(stderr, "Unable to extract file due to uncaught exception while getting file data. Passing...\n");
  102. return false;
  103. }
  104. SubfileData export_data;
  105. try {
  106. export_data = dictionary_[file_id]->PrepareForExport(file_data);
  107. export_data.options["did"] = dat_id_;
  108. } catch (std::exception &e) {
  109. fprintf(stderr, "Caught %s exception.", e.what());
  110. fprintf(stderr, "Unable to extract file due to uncaught exception while preparing file for export. Passing...\n");
  111. return false;
  112. }
  113. if (export_data == SubfileData()) {
  114. fprintf(stderr, "WARNING: file with id %lld is too small. Passing it\n", dictionary_[file_id]->file_id());
  115. return true;
  116. }
  117. try {
  118. db->PushFile(export_data);
  119. } catch (std::exception &e) {
  120. fprintf(stderr, "Caught %s exception.", e.what());
  121. printf("Caught %s exception.", e.what());
  122. fflush(stdout);
  123. fprintf(stderr, "Unable to put file or it's part to database. Continuing without this part. Database may be not complete\n");
  124. }
  125. return true;
  126. }
  127. /// Extracts all files with specific type to "path + type + file_id + file_part + extension" files;
  128. /// If path is undefined then it will be recognised as current working directory
  129. /// NOTICE: The directory, mentioned in "std::string path" variable SHOULD BE ALREADY CREATED;
  130. /// Otherwise DatException() will be thrown.
  131. /// Returns number of successfully extracted files
  132. /// Throws DatException() if undefined behaviour happened
  133. int DatFile::ExtractAllFilesByType(FILE_TYPE type, std::string path) {
  134. if (dat_state_ != READY) {
  135. throw DatException("Bad DatFile::ExtractAllFilesByType() - invalid DatFile state!", EXPORT_EXCEPTION);
  136. }
  137. int success = 0;
  138. for (auto i : dictionary_) {
  139. FILE_TYPE file_type = i.second->FileType();
  140. if (file_type == type) {
  141. success += ExtractFile(i.second->file_id(), (path + std::to_string(i.second->file_id())));
  142. }
  143. }
  144. return success;
  145. }
  146. /// Extracts all files with specific type to database "db";
  147. /// DATABASE SHOULD BE ALREADY CREATED; Otherwise DatException will be called.
  148. /// Returns number of successfully extracted files
  149. /// Throws DatException() if undefined behaviour happened
  150. int DatFile::ExtractAllFilesByType(FILE_TYPE type, Database *db) {
  151. if (dat_state_ != READY) {
  152. throw DatException("Bad DatFile::ExtractAllFilesByType() - invalid DatFile state!", EXPORT_EXCEPTION);
  153. }
  154. int success = 0;
  155. for (auto i : dictionary_) {
  156. FILE_TYPE file_type = i.second->FileType();
  157. if (file_type == type) {
  158. success += ExtractFile(i.second->file_id(), db);
  159. }
  160. }
  161. return success;
  162. }
  163. /// TODO: Write description and make asserts
  164. bool DatFile::PatchFile(const char *filename, YAML::Node options) {
  165. if (options["did"].IsDefined() && options["did"].as<int>() != dat_id_)
  166. return false;
  167. BinaryData data;
  168. data.ReadFromFile(filename);
  169. auto file_id = options["fid"].as<long long>();
  170. BinaryData old_data = GetFileData(dictionary_[file_id]);
  171. data = dictionary_[file_id]->MakeForImport(old_data, SubfileData(data, u"", options));
  172. try {
  173. ApplyFilePatch(dictionary_[file_id], data);
  174. } catch (std::exception &e) {
  175. fprintf(stderr, "Caught %s exception.", e.what());
  176. fprintf(stderr,
  177. "Some errors happened while patching file with id = %lld. Continuing process without this file..\n"
  178. "WARNING: DAT FILE CAN BE CORRUPTED!\n", file_id);
  179. printf("Some errors happened while patching file with id = %lld. Continuing process without this file..\n"
  180. "WARNING: DAT FILE CAN BE CORRUPTED!\n", file_id);
  181. fflush(stdout);
  182. return false;
  183. }
  184. return true;
  185. }
  186. // TODO: Write description and make asserts
  187. bool DatFile::PatchFile(const SubfileData &data) {
  188. auto file_id = data.options["fid"].as<long long>();
  189. Subfile *file = dictionary_[file_id];
  190. BinaryData old_data = GetFileData(file);
  191. BinaryData patch_data = file->MakeForImport(old_data, data);
  192. ApplyFilePatch(file, patch_data);
  193. return true;
  194. }
  195. // TODO: Write description and make asserts
  196. bool DatFile::PatchAllDatabase(Database *db) {
  197. SubfileData data;
  198. try {
  199. data = db->GetNextFile();
  200. } catch (std::exception &e) {
  201. fprintf(stderr, "Caught %s exception.\n", e.what());
  202. fprintf(stderr, "DatFile::PatchAllDatabase() error! Caught exception while fetching file from database! Stopping...\n");
  203. return false;
  204. }
  205. while (data != SubfileData()) {
  206. try {
  207. PatchFile(data);
  208. } catch (std::exception &e) {
  209. fprintf(stderr, "Caught %s exception.\n", e.what());
  210. fprintf(stderr, "DatFile::PatchAllDatabase() error! Caught exception while patching file! Passing...\n");
  211. }
  212. try {
  213. data = db->GetNextFile();
  214. } catch (std::exception &e) {
  215. fprintf(stderr, "Caught %s exception.\n", e.what());
  216. fprintf(stderr, "DatFile::PatchAllDatabase() error! Caught exception while fetching file from database! Stopping...\n");
  217. return false;
  218. }
  219. }
  220. return true;
  221. }
  222. /// DatFile::WriteUnorderedDictionary(...);
  223. /// Prints list of all found files with some information about them to file.
  224. /// Gets std::string path - path to directory, where the file will be written with name "dict.txt"
  225. void DatFile::WriteUnorderedDictionary(std::string path) const {
  226. FILE *f;
  227. fopen_s(&f, (path + "dict.txt").c_str(), "w");
  228. fprintf(f, "file_id offset size size2 extension\n");
  229. for (auto i : dictionary_) {
  230. fprintf(f, "%lld %lld %lld %lld %s\n", i.second->file_id(), i.second->file_offset(), i.second->file_size(),
  231. i.second->block_size(), i.second->Extension().c_str());
  232. }
  233. fclose(f);
  234. }
  235. /// DatFile::files_number();
  236. /// Returns amount of files, found in dictionaries of DatFile. Some if them may be empty or erased.
  237. long long DatFile::files_number() const {
  238. return dictionary_.size();
  239. }
  240. /// DatFile::GetFileData()
  241. /// Returns BinaryData, which contains of subfile data, made from parts of file in DatFile
  242. BinaryData DatFile::GetFileData(const Subfile *file, long long int offset) {
  243. BinaryData mfile_id(4);
  244. ReadData(mfile_id, 4, file->file_offset() + 8);
  245. if (file->file_id() != mfile_id.ToNumber<4>(0))
  246. throw DatException("Bad DatFile::GetFileData() - file_id in Subfile doesn't match to file_id in DatFile.", READ_EXCEPTION);
  247. BinaryData data((unsigned)(file->file_size() + (8 - offset)));
  248. if (file->block_size() >= file->file_size() + 8) {
  249. ReadData(data, file->file_size() + (8 - offset), file->file_offset() + offset);
  250. return data;
  251. }
  252. BinaryData fragments_count(4);
  253. ReadData(fragments_count, 4, file->file_offset());
  254. long long fragments_number = fragments_count.ToNumber<4>(0);
  255. long long current_block_size = file->block_size() - offset - 8 * fragments_number;
  256. ReadData(data, current_block_size , file->file_offset() + offset);
  257. BinaryData FragmentsDictionary(8 * unsigned(fragments_number));
  258. ReadData(FragmentsDictionary, 8 * unsigned(fragments_number), file->file_offset() + file->block_size() - 8 * fragments_number);
  259. for (long long i = 0; i < fragments_number; i++) {
  260. long long fragment_size = FragmentsDictionary.ToNumber<4>(8 * i);
  261. long long fragment_offset = FragmentsDictionary.ToNumber<4>(8 * i + 4);
  262. ReadData(data, std::min(fragment_size, file->file_size() - current_block_size), fragment_offset, current_block_size );
  263. current_block_size += fragment_size;
  264. }
  265. return data;
  266. }
  267. /// DatFile constants' getters.
  268. long long DatFile::constant1() const {
  269. return constant1_;
  270. }
  271. long long DatFile::constant2() const {
  272. return constant2_;
  273. }
  274. long long DatFile::file_size() const {
  275. return file_size_;
  276. }
  277. long long DatFile::version1() const {
  278. return version1_;
  279. }
  280. long long DatFile::version2() const {
  281. return version2_;
  282. }
  283. /// DatFile special functions for opening and reading/writing raw data.
  284. /// Shouldn't be used by any external classes except Subfile and Subdirectory.
  285. void DatFile::OpenDatFile(const char *dat_name) {
  286. if (dat_state_ != CLOSED)
  287. throw DatException("Bad initialisation of DatFile - current DatFile isn't in correct state!",
  288. INIT_EXCEPTION);
  289. fopen_s(&file_handler_, dat_name, "r+b");
  290. if (file_handler_ == nullptr) {
  291. std::string err = "Bad DatFile::OpenDatFile. Unable to open file ";
  292. err += dat_name;
  293. throw DatException(err.c_str(), INIT_EXCEPTION);
  294. }
  295. fseek(file_handler_, 0, SEEK_END);
  296. file_size_ = ftell(file_handler_);
  297. fseek(file_handler_, 0, SEEK_SET);
  298. dat_state_ = SUCCESS_OPENED;
  299. }
  300. void DatFile::ReadSuperBlock() {
  301. if (dat_state_ != SUCCESS_OPENED)
  302. throw DatException("Bad DatFile::ReadSuperBlock() - DatFile isn't in valid state!", INIT_EXCEPTION);
  303. BinaryData data(1024);
  304. ReadData(data, 1024);
  305. constant1_ = data.ToNumber<4>(0x100);
  306. constant2_ = data.ToNumber<4>(0x140);
  307. version1_ = data.ToNumber<4>(0x14C);
  308. version2_ = data.ToNumber<4>(0x150);
  309. fragmentation_journal_offset_ = data.ToNumber<4>(0x154);
  310. root_directory_offset_ = data.ToNumber<4>(0x160);
  311. auto size1 = data.ToNumber<4>(0x148);
  312. if (constant1_ != 0x4C5000)
  313. throw DatException(
  314. "Bad DatFile::ReadSuperBlock - variable at position 0x100 is not equal to .dat file constant!",
  315. INIT_EXCEPTION);
  316. if (constant2_ != 0x5442)
  317. throw DatException(
  318. "Bad DatFile::ReadSuperBlock - variable at position 0x140 is not equal to .dat file constant!",
  319. INIT_EXCEPTION);
  320. if (file_size_ != size1)
  321. throw DatException(
  322. "Bad DatFile::ReadSuperBlock - variable at 0x148 position is not equal to .dat file size!",
  323. INIT_EXCEPTION);
  324. dat_state_ = SUCCESS_SUPERBLOCK;
  325. }
  326. void DatFile::MakeDirectories() {
  327. if (dat_state_ != SUCCESS_SUPERBLOCK)
  328. throw DatException("Bad DatFile::MakeDirectories() - DatFile isn't in valid state!", INIT_EXCEPTION);
  329. root_directory_ = new SubDirectory((unsigned) root_directory_offset_, this);
  330. dat_state_ = SUCCESS_DIRECTORIES;
  331. }
  332. void DatFile::MakeDictionary() {
  333. if (dat_state_ != SUCCESS_DIRECTORIES)
  334. throw DatException("Bad DatFile::MakeDictionary() - DatFile isn't in valid state!", INIT_EXCEPTION);
  335. try {
  336. root_directory_->MakeDictionary(dictionary_);
  337. } catch (std::exception &e) {
  338. fprintf(stderr, "Caught %s exception.", e.what());
  339. fprintf(stderr, "Bad DatFile::MakeDictionary() - File is corrupted?\n");
  340. return;
  341. }
  342. dat_state_ = SUCCESS_DICTIONARY;
  343. }
  344. void DatFile::ReadData(BinaryData &data, long long size, long long offset, long long data_offset) {
  345. if (dat_state_ == CLOSED)
  346. throw DatException("Bad DatFile::ReadData() - DatFile isn't in valid state!", READ_EXCEPTION);
  347. if (data_offset + size > data.size()) {
  348. std::string err = "Bad DatFile::ReadData - trying to read more than BinaryData size\n";
  349. err += std::string("Reading ") + std::to_string(size) + std::string(" bytes from ")
  350. + std::to_string(offset) + std::string(" position in dat file.");
  351. throw DatException(err.c_str(), READ_EXCEPTION);
  352. }
  353. if (offset + size > file_size()) {
  354. std::string err = "Bad DatFile::ReadData - trying to read more than DatFile size elapsed\n";
  355. err += std::string("Reading ") + std::to_string(size) + std::string(" bytes from ")
  356. + std::to_string(offset) + std::string(" position in dat file.");
  357. throw DatException(err.c_str(), READ_EXCEPTION);
  358. }
  359. _fseeki64(file_handler_, offset, SEEK_SET);
  360. fread(data.data() + data_offset, unsigned(size), 1, file_handler_);
  361. data.CheckCompression();
  362. }
  363. void DatFile::WriteData(const BinaryData &data, long long size, long long offset, long long data_offset) {
  364. if (dat_state_ != READY)
  365. throw DatException("Bad DatFile::WriteData() - DatFile isn't in valid state!", WRITE_EXCEPTION);
  366. _fseeki64(file_handler_, offset, SEEK_SET);
  367. if (data_offset + size > data.size())
  368. throw DatException("Bad DatFile::WriteData - trying to write more than BinaryData size", WRITE_EXCEPTION);
  369. int x = fwrite(data.data() + data_offset, unsigned(size), 1, file_handler_);
  370. }
  371. /// Special functions used by patch process.
  372. /// Shouldn't be used by any external class.
  373. void DatFile::ApplyFilePatch(Subfile *file, const BinaryData &data) {
  374. if (patched_list.count(file->file_id()) != 0) {
  375. fprintf(stderr, "Warning: DatFile::ApplyFilePatch - found 2 files in patch with the same file_id. Passing last...\n");
  376. return;
  377. }
  378. patched_list[file->file_id()] = new BinaryData(file->MakeHeaderData());
  379. patched_ = true;
  380. auto journal = GetFragmentationJournal();
  381. file->file_size_ = data.size() - 8;
  382. //file->fragments_count_ = 0;
  383. if (data.size() > file->block_size()) {
  384. file->file_offset_ = journal[0].second;
  385. file->block_size_ = data.size();
  386. journal[0].second += data.size();
  387. BinaryData nulls(data.size());
  388. WriteData(nulls, nulls.size(), file_size_);
  389. this->file_size_ += data.size();
  390. }
  391. BinaryData fragments_count(4);
  392. fragments_count.FromNumber<4>(0);
  393. BinaryData file_data = fragments_count + data.CutData(4);
  394. if (file->file_id() != file_data.ToNumber<4>(8))
  395. throw DatException("Bad DatFile::ApplyFilePatch() - Created data's file_id doesn't match to original! "
  396. "Patch wasn't written to .dat file");
  397. WriteData(file_data, file_data.size(), file->file_offset());
  398. UpdateFragmentationJournal(journal);
  399. }
  400. void DatFile::UpdateSubdirectories() {
  401. root_directory_->UpdateDirectories(patched_list, dictionary_);
  402. }
  403. std::vector<std::pair<long long, long long> > DatFile::GetFragmentationJournal() {
  404. BinaryData data(8);
  405. ReadData(data, 8, fragmentation_journal_offset_ + 8);
  406. std::vector<std::pair<long long, long long> > result;
  407. result.emplace_back(std::make_pair(data.ToNumber<4>(0), data.ToNumber<4>(4)));
  408. return result;
  409. }
  410. void DatFile::UpdateHeader() {
  411. BinaryData data(4);
  412. data.FromNumber<4>(constant1_);
  413. WriteData(data, 4, 0x100);
  414. data.FromNumber<4>(constant2_);
  415. WriteData(data, 4, 0x140);
  416. data.FromNumber<4>(file_size_);
  417. WriteData(data, 4, 0x148);
  418. data.FromNumber<4>(version1_);
  419. WriteData(data, 4, 0x14C);
  420. data.FromNumber<4>(version2_);
  421. WriteData(data, 4, 0x150);
  422. data.FromNumber<4>(fragmentation_journal_offset_);
  423. WriteData(data, 4, 0x154);
  424. data.FromNumber<4>(root_directory_offset_);
  425. WriteData(data, 4, 0x160);
  426. }
  427. void DatFile::UpdateFragmentationJournal(const std::vector<std::pair<long long, long long> > &journal) {
  428. for (unsigned i = 0; i < journal.size(); i++) {
  429. long long size = journal[i].first;
  430. long long offset = journal[i].second;
  431. BinaryData data(4);
  432. data.FromNumber<4>(size);
  433. WriteData(data, 4, fragmentation_journal_offset_ + 8 * (i + 1));
  434. data.FromNumber<4>(offset);
  435. WriteData(data, 4, fragmentation_journal_offset_ + 8 * (i + 1) + 4);
  436. }
  437. }
  438. }
  439. }