DatFile.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  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. std::vector<SubfileData> export_data;
  77. try {
  78. export_data = dictionary_[file_id]->PrepareForExport(file_data);
  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. for (int i = 0; i < export_data.size(); ++i) {
  85. export_data[i].binary_data.WriteToFile(path + "_" + std::to_string(i) + export_data[i].options["ext"].as<std::string>());
  86. }
  87. return true;
  88. }
  89. /// Extracts file with file_id to database "db".
  90. /// DATABASE SHOULD BE ALREADY CREATED; Otherwise DatException will be called.
  91. /// NOTICE: The directory, mentioned in "std::string path" variable SHOULD BE ALREADY CREATED;
  92. /// Otherwise DatException() will be thrown.
  93. /// Returns true, if file was successfully extracted;
  94. /// Throws DatException() if undefined behaviour happened
  95. bool DatFile::ExtractFile(long long file_id, Database *db) {
  96. if (dat_state_ != READY) {
  97. throw DatException("Bad DatFile::ExtractFile() - invalid DatFile state!", EXPORT_EXCEPTION);
  98. }
  99. BinaryData file_data;
  100. try {
  101. file_data = GetFileData(dictionary_[file_id], 8);
  102. } catch (std::exception &e) {
  103. fprintf(stderr, "Caught %s exception.", e.what());
  104. fprintf(stderr, "Unable to extract file due to uncaught exception while getting file data. Passing...\n");
  105. return false;
  106. }
  107. std::vector<SubfileData> export_data;
  108. try {
  109. export_data = dictionary_[file_id]->PrepareForExport(file_data);
  110. } catch (std::exception &e) {
  111. fprintf(stderr, "Caught %s exception.", e.what());
  112. fprintf(stderr, "Unable to extract file due to uncaught exception while preparing file for export. Passing...\n");
  113. return false;
  114. }
  115. for (int i = 0; i < export_data.size(); ++i) {
  116. try {
  117. db->PushFile(export_data[i]);
  118. } catch (std::exception &e) {
  119. fprintf(stderr, "Caught %s exception.", e.what());
  120. printf("Caught %s exception.", e.what());
  121. fflush(stdout);
  122. fprintf(stderr, "Unable to put file or it's part to database. Continuing without this part. Database may be not complete\n");
  123. }
  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 and IMPLEMENT
  196. bool DatFile::PatchAllDatabase(Database *db) {
  197. return false;
  198. }
  199. /// DatFile::WriteUnorderedDictionary(...);
  200. /// Prints list of all found files with some information about them to file.
  201. /// Gets std::string path - path to directory, where the file will be written with name "dict.txt"
  202. void DatFile::WriteUnorderedDictionary(std::string path) const {
  203. FILE *f;
  204. fopen_s(&f, (path + "dict.txt").c_str(), "w");
  205. fprintf(f, "file_id offset size size2 extension\n");
  206. for (auto i : dictionary_) {
  207. fprintf(f, "%lld %lld %lld %lld %s\n", i.second->file_id(), i.second->file_offset(), i.second->file_size(),
  208. i.second->block_size(), i.second->Extension().c_str());
  209. }
  210. fclose(f);
  211. }
  212. /// DatFile::files_number();
  213. /// Returns amount of files, found in dictionaries of DatFile. Some if them may be empty or erased.
  214. long long DatFile::files_number() const {
  215. return dictionary_.size();
  216. }
  217. /// DatFile::GetFileData()
  218. /// Returns BinaryData, which contains of subfile data, made from parts of file in DatFile
  219. BinaryData DatFile::GetFileData(const Subfile *file, long long int offset) {
  220. BinaryData mfile_id(4);
  221. ReadData(mfile_id, 4, file->file_offset() + 8);
  222. if (file->file_id() != mfile_id.ToNumber<4>(0))
  223. throw DatException("Bad DatFile::GetFileData() - file_id in Subfile doesn't match to file_id in DatFile.", READ_EXCEPTION);
  224. BinaryData data((unsigned)(file->file_size()));
  225. if (file->block_size() >= file->file_size() + 8) {
  226. ReadData(data, file->file_size(), file->file_offset() + offset);
  227. return data;
  228. }
  229. BinaryData fragments_count(4);
  230. ReadData(fragments_count, 4, file->file_offset());
  231. long long fragments_number = fragments_count.ToNumber<4>(0);
  232. long long current_block_size = file->block_size() - offset - 8 * fragments_number;
  233. ReadData(data, current_block_size , file->file_offset() + offset);
  234. BinaryData FragmentsDictionary(8 * unsigned(fragments_number));
  235. ReadData(FragmentsDictionary, 8 * unsigned(fragments_number), file->file_offset() + file->block_size() - 8 * fragments_number);
  236. for (long long i = 0; i < fragments_number; i++) {
  237. long long fragment_size = FragmentsDictionary.ToNumber<4>(8 * i);
  238. long long fragment_offset = FragmentsDictionary.ToNumber<4>(8 * i + 4);
  239. ReadData(data, std::min(fragment_size, file->file_size() - current_block_size), fragment_offset, current_block_size );
  240. current_block_size += fragment_size;
  241. }
  242. return data;
  243. }
  244. /// DatFile constants' getters.
  245. long long DatFile::constant1() const {
  246. return constant1_;
  247. }
  248. long long DatFile::constant2() const {
  249. return constant2_;
  250. }
  251. long long DatFile::file_size() const {
  252. return file_size_;
  253. }
  254. long long DatFile::version1() const {
  255. return version1_;
  256. }
  257. long long DatFile::version2() const {
  258. return version2_;
  259. }
  260. /// DatFile special functions for opening and reading/writing raw data.
  261. /// Shouldn't be used by any external classes except Subfile and Subdirectory.
  262. void DatFile::OpenDatFile(const char *dat_name) {
  263. if (dat_state_ != CLOSED)
  264. throw DatException("Bad initialisation of DatFile - current DatFile isn't in correct state!",
  265. INIT_EXCEPTION);
  266. fopen_s(&file_handler_, dat_name, "r+b");
  267. if (file_handler_ == nullptr) {
  268. std::string err = "Bad DatFile::OpenDatFile. Unable to open file ";
  269. err += dat_name;
  270. throw DatException(err.c_str(), INIT_EXCEPTION);
  271. }
  272. fseek(file_handler_, 0, SEEK_END);
  273. file_size_ = ftell(file_handler_);
  274. fseek(file_handler_, 0, SEEK_SET);
  275. dat_state_ = SUCCESS_OPENED;
  276. }
  277. void DatFile::ReadSuperBlock() {
  278. if (dat_state_ != SUCCESS_OPENED)
  279. throw DatException("Bad DatFile::ReadSuperBlock() - DatFile isn't in valid state!", INIT_EXCEPTION);
  280. BinaryData data(1024);
  281. ReadData(data, 1024);
  282. constant1_ = data.ToNumber<4>(0x100);
  283. constant2_ = data.ToNumber<4>(0x140);
  284. version1_ = data.ToNumber<4>(0x14C);
  285. version2_ = data.ToNumber<4>(0x150);
  286. fragmentation_journal_offset_ = data.ToNumber<4>(0x154);
  287. root_directory_offset_ = data.ToNumber<4>(0x160);
  288. auto size1 = data.ToNumber<4>(0x148);
  289. if (constant1_ != 0x4C5000)
  290. throw DatException(
  291. "Bad DatFile::ReadSuperBlock - variable at position 0x100 is not equal to .dat file constant!",
  292. INIT_EXCEPTION);
  293. if (constant2_ != 0x5442)
  294. throw DatException(
  295. "Bad DatFile::ReadSuperBlock - variable at position 0x140 is not equal to .dat file constant!",
  296. INIT_EXCEPTION);
  297. if (file_size_ != size1)
  298. throw DatException(
  299. "Bad DatFile::ReadSuperBlock - variable at 0x148 position is not equal to .dat file size!",
  300. INIT_EXCEPTION);
  301. dat_state_ = SUCCESS_SUPERBLOCK;
  302. }
  303. void DatFile::MakeDirectories() {
  304. if (dat_state_ != SUCCESS_SUPERBLOCK)
  305. throw DatException("Bad DatFile::MakeDirectories() - DatFile isn't in valid state!", INIT_EXCEPTION);
  306. root_directory_ = new SubDirectory((unsigned) root_directory_offset_, this);
  307. dat_state_ = SUCCESS_DIRECTORIES;
  308. }
  309. void DatFile::MakeDictionary() {
  310. if (dat_state_ != SUCCESS_DIRECTORIES)
  311. throw DatException("Bad DatFile::MakeDictionary() - DatFile isn't in valid state!", INIT_EXCEPTION);
  312. try {
  313. root_directory_->MakeDictionary(dictionary_);
  314. } catch (std::exception &e) {
  315. fprintf(stderr, "Caught %s exception.", e.what());
  316. fprintf(stderr, "Bad DatFile::MakeDictionary() - File is corrupted?\n");
  317. return;
  318. }
  319. dat_state_ = SUCCESS_DICTIONARY;
  320. }
  321. void DatFile::ReadData(BinaryData &data, long long size, long long offset, long long data_offset) {
  322. if (dat_state_ == CLOSED)
  323. throw DatException("Bad DatFile::ReadData() - DatFile isn't in valid state!", READ_EXCEPTION);
  324. if (data_offset + size > data.size()) {
  325. std::string err = "Bad DatFile::ReadData - trying to read more than BinaryData size\n";
  326. err += std::string("Reading ") + std::to_string(size) + std::string(" bytes from ")
  327. + std::to_string(offset) + std::string(" position in dat file.");
  328. throw DatException(err.c_str(), READ_EXCEPTION);
  329. }
  330. if (offset + size > file_size()) {
  331. std::string err = "Bad DatFile::ReadData - trying to read more than DatFile size elapsed\n";
  332. err += std::string("Reading ") + std::to_string(size) + std::string(" bytes from ")
  333. + std::to_string(offset) + std::string(" position in dat file.");
  334. throw DatException(err.c_str(), READ_EXCEPTION);
  335. }
  336. _fseeki64(file_handler_, offset, SEEK_SET);
  337. fread(data.data() + data_offset, unsigned(size), 1, file_handler_);
  338. data.CheckCompression();
  339. }
  340. void DatFile::WriteData(const BinaryData &data, long long size, long long offset, long long data_offset) {
  341. if (dat_state_ != READY)
  342. throw DatException("Bad DatFile::WriteData() - DatFile isn't in valid state!", WRITE_EXCEPTION);
  343. _fseeki64(file_handler_, offset, SEEK_SET);
  344. if (data_offset + size > data.size())
  345. throw DatException("Bad DatFile::WriteData - trying to write more than BinaryData size", WRITE_EXCEPTION);
  346. int x = fwrite(data.data() + data_offset, unsigned(size), 1, file_handler_);
  347. }
  348. /// Special functions used by patch process.
  349. /// Shouldn't be used by any external class.
  350. void DatFile::ApplyFilePatch(Subfile *file, const BinaryData &data) {
  351. if (patched_list.count(file->file_id()) != 0) {
  352. fprintf(stderr, "Warning: DatFile::ApplyFilePatch - found 2 files in patch with the same file_id. Passing last...\n");
  353. return;
  354. }
  355. patched_list[file->file_id()] = new BinaryData(file->MakeHeaderData());
  356. patched_ = true;
  357. auto journal = GetFragmentationJournal();
  358. file->file_size_ = data.size() - 8;
  359. //file->fragments_count_ = 0;
  360. if (data.size() > file->block_size()) {
  361. file->file_offset_ = journal[0].second;
  362. file->block_size_ = data.size();
  363. journal[0].second += data.size();
  364. BinaryData nulls(data.size());
  365. WriteData(nulls, nulls.size(), file_size_);
  366. this->file_size_ += data.size();
  367. }
  368. BinaryData fragments_count(4);
  369. fragments_count.FromNumber<4>(0);
  370. BinaryData file_data = fragments_count + data.CutData(4);
  371. if (file->file_id() != file_data.ToNumber<4>(8))
  372. throw DatException("Bad DatFile::ApplyFilePatch() - Created data's file_id doesn't match to original! "
  373. "Patch wasn't written to .dat file");
  374. WriteData(file_data, file_data.size(), file->file_offset());
  375. UpdateFragmentationJournal(journal);
  376. }
  377. void DatFile::UpdateSubdirectories() {
  378. root_directory_->UpdateDirectories(patched_list, dictionary_);
  379. }
  380. std::vector<std::pair<long long, long long> > DatFile::GetFragmentationJournal() {
  381. BinaryData data(8);
  382. ReadData(data, 8, fragmentation_journal_offset_ + 8);
  383. std::vector<std::pair<long long, long long> > result;
  384. result.emplace_back(std::make_pair(data.ToNumber<4>(0), data.ToNumber<4>(4)));
  385. return result;
  386. }
  387. void DatFile::UpdateHeader() {
  388. BinaryData data(4);
  389. data.FromNumber<4>(constant1_);
  390. WriteData(data, 4, 0x100);
  391. data.FromNumber<4>(constant2_);
  392. WriteData(data, 4, 0x140);
  393. data.FromNumber<4>(file_size_);
  394. WriteData(data, 4, 0x148);
  395. data.FromNumber<4>(version1_);
  396. WriteData(data, 4, 0x14C);
  397. data.FromNumber<4>(version2_);
  398. WriteData(data, 4, 0x150);
  399. data.FromNumber<4>(fragmentation_journal_offset_);
  400. WriteData(data, 4, 0x154);
  401. data.FromNumber<4>(root_directory_offset_);
  402. WriteData(data, 4, 0x160);
  403. }
  404. void DatFile::UpdateFragmentationJournal(const std::vector<std::pair<long long, long long> > &journal) {
  405. for (unsigned i = 0; i < journal.size(); i++) {
  406. long long size = journal[i].first;
  407. long long offset = journal[i].second;
  408. BinaryData data(4);
  409. data.FromNumber<4>(size);
  410. WriteData(data, 4, fragmentation_journal_offset_ + 8 * (i + 1));
  411. data.FromNumber<4>(offset);
  412. WriteData(data, 4, fragmentation_journal_offset_ + 8 * (i + 1) + 4);
  413. }
  414. }
  415. }
  416. }