DatFileSystem.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. #include <DatSubsystems/DatFileSystem.h>
  2. #include <EasyLogging++/easylogging++.h>
  3. #include <BinaryData.h>
  4. #include <DatFile.h>
  5. #include <SubFile.h>
  6. #include <SubDirectory.h>
  7. #include <functional>
  8. namespace LOTRO_DAT {
  9. /*!
  10. * \author Gi1dor
  11. * \date 30.06.2018
  12. * Конструктор объекта модуля. Вызывается только из класса DatFile. Создаёт указатель но объект DatFile, но
  13. * не инициализирует модуль. Инизиализация происходит в функции Init
  14. * \param[in] datFilePtr Указатель на объект управляющего класса DatFile
  15. */
  16. DatFileSystem::DatFileSystem(DatFile *datFilePtr) : dat(datFilePtr) {}
  17. /*!
  18. * \author Gi1dor
  19. * \date 29.06.2018
  20. * Возвращает бинарные данные файла. Помимо самого файла, в бинарном содержимом находится также метаинформация о нём.
  21. * Изменение параметра offset позволяет игнорировать метаинформацию и извлечь только непосредственно содержимое файла
  22. * \param[in] file_id Идентификатор получаемого файла
  23. * \param[in] offset Отступ от начала файла. Данные до отступа не извлекаются.
  24. * \return Возвращает result = ERROR и value = BinaryData, если файл не найден или информаци в словаре некорректная
  25. */
  26. DatOperationResult<BinaryData> DatFileSystem::GetFileData(const SubFile& file, long long int offset) {
  27. if (!dat)
  28. return DatOperationResult<BinaryData>(BinaryData(), ERROR,
  29. "DatFileSystem error: no connection with Dat (dat is nullptr)");
  30. BinaryData mfile_id(20);
  31. auto operation = CheckCorrectSubfile(file);
  32. if (operation.result == ERROR || !operation.value)
  33. return DatOperationResult<BinaryData>(BinaryData(), ERROR,
  34. "DATFSGETFILEDATA: Incorrect file" + std::to_string(file.file_id()));
  35. BinaryData data((unsigned) (file.file_size() + (8 - offset)));
  36. if (file.block_size() >= file.file_size() + 8) {
  37. dat->GetIO().ReadData(data, file.file_size() + (8 - offset), file.file_offset() + offset);
  38. return DatOperationResult<BinaryData>(data, SUCCESS);
  39. }
  40. BinaryData fragments_count(4);
  41. dat->GetIO().ReadData(fragments_count, 4, file.file_offset());
  42. long long fragments_number = fragments_count.ToNumber<4>(0);
  43. long long current_block_size = file.block_size() - offset - 8 * fragments_number;
  44. dat->GetIO().ReadData(data, current_block_size, file.file_offset() + offset);
  45. BinaryData FragmentsDictionary(8 * unsigned(fragments_number));
  46. dat->GetIO().ReadData(FragmentsDictionary, 8 * unsigned(fragments_number),
  47. file.file_offset() + file.block_size() - 8 * fragments_number);
  48. for (long long i = 0; i < fragments_number; i++) {
  49. long long fragment_size = FragmentsDictionary.ToNumber<4>(8 * i);
  50. long long fragment_offset = FragmentsDictionary.ToNumber<4>(8 * i + 4);
  51. dat->GetIO().ReadData(data, std::min(fragment_size, file.file_size() - current_block_size),
  52. fragment_offset,
  53. current_block_size);
  54. current_block_size += fragment_size;
  55. }
  56. return DatOperationResult<BinaryData>(data, SUCCESS);
  57. }
  58. /*!
  59. * \author Gi1dor
  60. * \date 29.06.2018
  61. * Возвращает указатель на объект файла в словаре. Через этот указатель можно патчить/извлекать файл
  62. * \param[in] file_id идентификатор получаемого файла
  63. * \return Возвращает result = ERROR и value = nullptr, если файл не найден
  64. */
  65. DatOperationResult<std::shared_ptr<SubFile>> DatFileSystem::GetFile(long long file_id) {
  66. if (!dat)
  67. return DatOperationResult<std::shared_ptr<SubFile>>(nullptr, ERROR,
  68. "DATFSGETFILE: no connection with Dat (dat is nullptr)");
  69. if (dictionary_.count(file_id) == 0) {
  70. auto operation = InitSubFile(file_id);
  71. if (operation.result == ERROR)
  72. return DatOperationResult<std::shared_ptr<SubFile>>(nullptr, ERROR);
  73. }
  74. return DatOperationResult<std::shared_ptr<SubFile>>(dictionary_[file_id], SUCCESS);
  75. }
  76. /*!
  77. * \author Gi1dor
  78. * \date 29.06.2018
  79. * Обновляет данные файла
  80. * \param[in] preinit_file неинициализированный объект файла, данные которого обновляются в словаре
  81. * \warning Новые данные не будут записаны в dat файле, пока не будет вызвана функция CommitDirectories()
  82. */
  83. DatOperationResult<> DatFileSystem::UpdateFileInfo(const SubFile &preinit_file) {
  84. auto operation = GetFile(preinit_file.file_id());
  85. if (operation.result == ERROR)
  86. return DatOperationResult<>(ERROR, "DATFS: Unable to update file info - no file in dict with id = " +
  87. std::to_string(preinit_file.file_id()));
  88. auto& file = operation.value;
  89. file->unknown1_ = preinit_file.unknown1();
  90. file->file_id_ = preinit_file.file_id();
  91. file->file_offset_ = preinit_file.file_offset();
  92. file->file_size_ = preinit_file.file_size();
  93. file->timestamp_ = preinit_file.timestamp();
  94. file->version_ = preinit_file.version();
  95. file->block_size_ = preinit_file.block_size();
  96. file->unknown2_ = preinit_file.unknown2();
  97. subfile_pending_update.insert(preinit_file.file_id());
  98. return DatOperationResult<>();
  99. }
  100. /*!
  101. * \author Gi1dor
  102. * \date 29.06.2018
  103. * Проверяет корректность файла, полученного из словаря
  104. *
  105. * \return Объект DatOperationResult<bool> для которого value = true, если файл корректный, и false - иначе.
  106. */
  107. DatOperationResult<bool> DatFileSystem::CheckCorrectSubfile(const SubFile& file) {
  108. if (!dat)
  109. return DatOperationResult<bool>(false, ERROR,
  110. "DATFSCORRECTSUBFILE: no connection with Dat (dat is nullptr)");
  111. if (file.file_size() < 16)
  112. return DatOperationResult<bool>(false, SUCCESS);
  113. BinaryData mfile_id(20);
  114. auto operation = dat->GetIO().ReadData(mfile_id, 20, file.file_offset() + 8);
  115. if (operation.result == ERROR)
  116. return DatOperationResult<bool>(false, ERROR, "DATFSCORRECTSUBFILE: cannot read file header data");
  117. return DatOperationResult<bool>((mfile_id.CheckCompression() || file.file_id() == mfile_id.ToNumber<4>(0)) &&
  118. file.file_size() < 50ll * 1024ll * 1024ll, SUCCESS);
  119. }
  120. /*!
  121. * \author Gi1dor
  122. * \date 29.06.2018
  123. * Записывает все изменения структуры файловой системы в dat-файл.
  124. */
  125. DatOperationResult<> DatFileSystem::CommitDirectories() {
  126. if (!dat)
  127. return DatOperationResult<>(ERROR, "DATFSCOMMITDIRS: no connection with Dat (dat is nullptr)");
  128. for (auto file_id : subfile_pending_update) {
  129. if (dictionary_.count(file_id) == 0)
  130. continue;
  131. // auto operation = CheckCorrectSubfile(dictionary_[file_id]);
  132. // if (operation.result == ERROR) {
  133. // LOG(ERROR) << "Check subfile correctness failed";
  134. // continue;
  135. // }
  136. // if (!operation.value) {
  137. // LOG(ERROR) << "Incorrect SubFile " << file_id << " data: doesn't match";
  138. // continue;
  139. // }
  140. auto operation1 = dat->GetIO().WriteData(dictionary_[file_id]->MakeHeaderData(), 32,
  141. dictionary_[file_id]->dictionary_offset());
  142. if (operation1.result == ERROR)
  143. LOG(ERROR) << "Unable to write data to dictionary for file " << file_id;
  144. }
  145. subfile_pending_update.clear();
  146. return DatOperationResult<>(SUCCESS);
  147. }
  148. /*!
  149. * \author Gi1dor
  150. * \date 29.06.2018
  151. * Инициализация модуля. Подготавливает файловую систему для работы
  152. */
  153. DatOperationResult<> DatFileSystem::Init() {
  154. if (!dat)
  155. return DatOperationResult<>(ERROR, "DATFSINIT: no connection with Dat (dat is nullptr)");
  156. DeInit();
  157. patched_file_end = dat->GetIO().file_size;
  158. SubDirectory root_directory(0, (unsigned) dat->GetIO().root_directory_offset);
  159. subdir_init_queue_.insert(root_directory);
  160. return DatOperationResult<>(SUCCESS);
  161. }
  162. /*!
  163. * \author Gi1dor
  164. * \date 29.06.2018
  165. * Завершает работу файловой системы и записывает изменения в dat файл
  166. *
  167. * \return Вернёт DatOperationResult<>(ERROR), если случилась ошибка записи структуры файлов в dat файл
  168. */
  169. DatOperationResult<> DatFileSystem::DeInit() {
  170. if (!dat)
  171. return DatOperationResult<>(ERROR, "DATFSDEINIT: no connection with Dat (dat is nullptr)");
  172. auto operation = CommitDirectories();
  173. dictionary_.clear();
  174. visited_subdirectories_offsets_.clear();
  175. visited_subfiles_ids_.clear();
  176. subdir_init_queue_.clear();
  177. subfile_init_map_.clear();
  178. subfile_init_queue_.clear();
  179. subfile_pending_update.clear();
  180. if (operation.result == ERROR)
  181. return DatOperationResult<>(ERROR, "DEINITFS: unable to commit directories.");
  182. return DatOperationResult<>(SUCCESS);
  183. }
  184. /*!
  185. * \author Gi1dor
  186. * \date 29.06.2018
  187. * Записывает общую информацию о файловой системе. Требует полной инициализации всех папок и файлов.
  188. *
  189. * \param[in] file Объект файла, в конец которого будут записываться данные.
  190. */
  191. void DatFileSystem::PrintInformaion(FILE *file) {
  192. if (!dat) {
  193. LOG(ERROR) << "DATFSPRINTINFO: no connection with Dat (dat is nullptr)";
  194. return;
  195. }
  196. InitAllFiles();
  197. fprintf(file, "============= FS INFO SECTION =============\n");
  198. fprintf(file, "Files in dictionary number: %d\n", dictionary_.size());
  199. fprintf(file, "Files visited: %d\n", visited_subfiles_ids_.size());
  200. fprintf(file, "Folders visited: %d\n", visited_subdirectories_offsets_.size());
  201. fprintf(file, "File patched size: %u\n", patched_file_end);
  202. std::map<FILE_TYPE, size_t> filetypes_count{{TEXT, 0},
  203. {JPG, 0},
  204. {DDS, 0},
  205. {WAV, 0},
  206. {OGG, 0},
  207. {FONT, 0},
  208. {UNKNOWN, 0}};
  209. for (const auto &datfile : dictionary_) {
  210. filetypes_count[datfile.second->FileType()]++;
  211. }
  212. fprintf(file, "TEXT files number = %d\n", filetypes_count[TEXT]);
  213. fprintf(file, "JPG files number = %d\n", filetypes_count[JPG]);
  214. fprintf(file, "DDS files number = %d\n", filetypes_count[DDS]);
  215. fprintf(file, "WAV files number = %d\n", filetypes_count[WAV]);
  216. fprintf(file, "OGG files number = %d\n", filetypes_count[OGG]);
  217. fprintf(file, "FONT files number = %d\n", filetypes_count[FONT]);
  218. fprintf(file, "UNKNOWN files number = %d\n", filetypes_count[UNKNOWN]);
  219. }
  220. /*!
  221. * \author Gi1dor
  222. * \date 29.06.2018
  223. * Инициализация файла с заданным id. Ищет информацию о файле, инициализирует его в соответствии с типом файла и
  224. * помещает в словарь. Работает быстрее, если все папки уже были проинициализированы. В противном случае - пытается
  225. * инициализировать каждую из папок, пока не найдёт в ней файла с требуем id.
  226. *
  227. * \param[in] file_id Id файла, который нужно проинициализировать.
  228. * \return Объект DatOperationResult. Если файл не удалось проинициализировать - поле result будет равно ERROR
  229. */
  230. DatOperationResult<> DatFileSystem::InitSubFile(long long file_id) {
  231. if (!dat)
  232. return DatOperationResult<>(ERROR, "DATFSINITSUBFILE: no connection with Dat (dat is nullptr)");
  233. while (subfile_init_map_.count(file_id) == 0) {
  234. if (subdir_init_queue_.empty())
  235. return DatOperationResult<>(ERROR,
  236. "DATFSINITSUBFILE: Cannot init file with id = "
  237. + std::to_string(file_id) + " (NOT FOUND)");
  238. SubDirectory dir = *subdir_init_queue_.begin();
  239. subdir_init_queue_.erase(subdir_init_queue_.begin());
  240. InitSubDirectory(dir);
  241. }
  242. SubFile file = subfile_init_map_[file_id];
  243. subfile_init_queue_.erase(file);
  244. subfile_init_map_.erase(file_id);
  245. auto initialised_file = SubFile::MakeSubfile(*dat, file);
  246. if (!initialised_file)
  247. return DatOperationResult<>(ERROR, "DATFSINITSUBFILE: initialised subfile pointer is empty");
  248. if (!CheckCorrectSubfile(*initialised_file).value)
  249. return DatOperationResult<>(ERROR, "DATFSINITSUBFILE: initialised file " + std::to_string(file.file_id()) + " is incorrect");
  250. if (dictionary_.count(file_id) > 0) {
  251. LOG(WARNING) << "Dublicate files id = " << file_id << "dictionary offsets = "
  252. << dictionary_[file_id]->dictionary_offset() << " and "
  253. << initialised_file->dictionary_offset();
  254. if (CheckCorrectSubfile(*dictionary_[file_id]).value)
  255. LOG(ERROR) << " FILE IN DICTIONARY IS ALREADY CORRECT!";
  256. dictionary_[file_id] = initialised_file;
  257. }
  258. if (dictionary_.count(file_id) == 0)
  259. dictionary_[file_id] = initialised_file;
  260. return DatOperationResult<>();
  261. }
  262. /*!
  263. * \author Gi1dor
  264. * \date 29.06.2018
  265. * Инициализация всех папок. Собирает информацию обо всех файлах в .dat файле для быстрого доступа к ним.
  266. * \warning Сами файлы не инициализируются. Инициализация файла выполнится при первом вызове GetFile() или GetFileData()
  267. *
  268. * \return Объект DatOperationResult
  269. */
  270. DatOperationResult<> DatFileSystem::InitAllDirectories() {
  271. if (!dat)
  272. return DatOperationResult<>(ERROR, "DATFSINITALLDIRS: no connection with Dat (dat is nullptr)");
  273. while (!subdir_init_queue_.empty()) {
  274. SubDirectory dir = *subdir_init_queue_.begin();
  275. subdir_init_queue_.erase(subdir_init_queue_.begin());
  276. InitSubDirectory(dir);
  277. }
  278. return DatOperationResult<>();
  279. }
  280. /*!
  281. * \author Gi1dor
  282. * \date 29.06.2018
  283. * Инициализация всех файлов. Полностью инициализирует все внутренние папки и файлы и помещает последние в словарь.
  284. *
  285. * \return Объект DatOperationResult
  286. */
  287. DatOperationResult<> DatFileSystem::InitAllFiles() {
  288. if (!dat)
  289. return DatOperationResult<>(ERROR, "DATFSINITALLFILES: no connection with Dat (dat is nullptr)");
  290. InitAllDirectories();
  291. while (!subfile_init_queue_.empty()) {
  292. SubFile file = *subfile_init_queue_.begin();
  293. InitSubFile(file.file_id());
  294. }
  295. return DatOperationResult<>();
  296. }
  297. /*!
  298. * \author Gi1dor
  299. * \date 29.06.2018
  300. * Инициализация папки. Читает внутреннюю структуру папки и получает информацию о файлах и подпапках.
  301. *
  302. * Структура папки:
  303. * 63 * 8 байт под описание подпапок. По 8 байт на папку.
  304. * Из них по 4 байта на 2 поля:
  305. * unknown - неизвестное поле
  306. * offset - положение подпапки в .dat файле
  307. *
  308. * 4 байта - количество файлов в папке. В каждой папке не более 64 Файлов.
  309. *
  310. * 32 * 64 байта под описание файлов. По 32 байта на каждый файл.
  311. *
  312. * Из них по 4 байта на каждое из 8 полей:
  313. * unknown1 - неизвестное поле
  314. * file_id - уникальный идентификатор файла
  315. * file_offset - положение содержимого файла
  316. * file_size - размер файла в байтах
  317. * timestamp - timestamp последнего редактирования
  318. * version - версия файла
  319. * block_size - размер блока данных, выделенного под файл
  320. * unknown2 - неизвестное поле
  321. *
  322. * \param[in] dir объект папки SubDirectory
  323. * \return Объект DatOperationResult
  324. */
  325. DatOperationResult<> DatFileSystem::InitSubDirectory(SubDirectory dir) {
  326. if (!dat)
  327. return DatOperationResult<>(ERROR, "DATFSINITSUBDIR: no connection with Dat (dat is nullptr)");
  328. BinaryData subdir_data(63 * 8 + 4 + 32 * 64);
  329. auto operation = dat->GetIO().ReadData(subdir_data, 63 * 8 + 4 + 32 * 64, dir.offset());
  330. if (operation.result == ERROR)
  331. return DatOperationResult<>(ERROR, "DATFSINITSUBDIR: cannot init dir (read error) with offset = " +
  332. std::to_string((dir.offset())));
  333. if (subdir_data.ToNumber<4>(0) != 0 || subdir_data.ToNumber<4>(4) != 0)
  334. return DatOperationResult<>(ERROR, "DATFSINITSUBDIR: incorrect dir. Nulls must be at first 8 bytes of dir");
  335. // Subfiles section
  336. long long subfiles_number = subdir_data.ToNumber<4>(63 * 8);
  337. if (subfiles_number > 64)
  338. return DatOperationResult<>(ERROR, "DATINITSUBDIR: incorrect subdir: files number > 64");
  339. // Subdirs section
  340. for (unsigned i = 8; i < 63 * 8; i += 8) {
  341. if (subdir_data.ToNumber<4>(i) == 0 || subdir_data.ToNumber<4>(i + 4) == 0)
  342. break;
  343. long long unknown = subdir_data.ToNumber<4>(i);
  344. long long offset = subdir_data.ToNumber<4>(i + 4);
  345. if (unknown == 0 || offset == 0)
  346. break;
  347. if (visited_subdirectories_offsets_.count(offset) != 0)
  348. continue;
  349. visited_subdirectories_offsets_.insert(offset);
  350. subdir_init_queue_.insert(SubDirectory(unknown, offset));
  351. }
  352. // Subfiles section
  353. for (int i = 0; i < subfiles_number; i++) {
  354. long long header_start = 63 * 8 + 4 + 32 * i;
  355. SubFile file = SubFile(
  356. *dat,
  357. dir.offset() + header_start,
  358. subdir_data.ToNumber<4>(header_start + 0), // unknown1
  359. subdir_data.ToNumber<4>(header_start + 4), // file_id
  360. subdir_data.ToNumber<4>(header_start + 8), // file_offset
  361. subdir_data.ToNumber<4>(header_start + 12), // file_size
  362. subdir_data.ToNumber<4>(header_start + 16), // timestamp
  363. subdir_data.ToNumber<4>(header_start + 20), // version
  364. subdir_data.ToNumber<4>(header_start + 24), // block_size
  365. subdir_data.ToNumber<4>(header_start + 28) // unknown2
  366. );
  367. if (file.version() == 0 || file.unknown2() != 0)
  368. continue;
  369. if (visited_subfiles_ids_.count(file.file_id()) || file.file_id() == -1)
  370. break;
  371. visited_subfiles_ids_.insert(file.file_id());
  372. subfile_init_map_[file.file_id_] = file;
  373. subfile_init_queue_.insert(file);
  374. }
  375. return DatOperationResult<>();
  376. }
  377. /*!
  378. * \author Gi1dor
  379. * \date 03.07.2018
  380. * Выполнение функции function для всех файлов в dat контейнере.
  381. *
  382. * \param[in] function Функтор, принимающий в качестве аргумента ссылку на объект-указатель std::shared_ptr<SubFile>
  383. */
  384. DatOperationResult<> DatFileSystem::PerformOperationOnAllFiles(const std::function<void (std::shared_ptr<SubFile>&)>& function) {
  385. InitAllFiles();
  386. for (auto& file_pair: dictionary_)
  387. function(file_pair.second);
  388. return DatOperationResult<>();
  389. }
  390. int DatFileSystem::GetInitialisedFilesNumber() const {
  391. return dictionary_.size();
  392. }
  393. }