DatFileSystem.cpp 20 KB

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