123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- #include <DatSubsystems/DatFileSystem.h>
- #include <EasyLogging++/easylogging++.h>
- #include <BinaryData.h>
- #include <DatFile.h>
- #include <SubFile.h>
- #include <SubDirectory.h>
- #include <functional>
- namespace LOTRO_DAT {
- /*!
- * \author Gi1dor
- * \date 30.06.2018
- * Конструктор объекта модуля. Вызывается только из класса DatFile. Создаёт указатель но объект DatFile, но
- * не инициализирует модуль. Инизиализация происходит в функции Init
- * \param[in] datFilePtr Указатель на объект управляющего класса DatFile
- */
- DatFileSystem::DatFileSystem(DatFile *datFilePtr) : dat(datFilePtr) {}
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Возвращает бинарные данные файла. Помимо самого файла, в бинарном содержимом находится также метаинформация о нём.
- * Изменение параметра offset позволяет игнорировать метаинформацию и извлечь только непосредственно содержимое файла
- * \param[in] file_id Идентификатор получаемого файла
- * \param[in] offset Отступ от начала файла. Данные до отступа не извлекаются.
- * \return Возвращает result = ERROR и value = BinaryData, если файл не найден или информаци в словаре некорректная
- */
- DatOperationResult<BinaryData> DatFileSystem::GetFileData(const SubFile& file, long long int offset) {
- if (!dat)
- return DatOperationResult<BinaryData>(BinaryData(), ERROR,
- "DatFileSystem error: no connection with Dat (dat is nullptr)");
- BinaryData mfile_id(20);
- auto operation = CheckCorrectSubfile(file);
- if (operation.result == ERROR || !operation.value)
- return DatOperationResult<BinaryData>(BinaryData(), ERROR,
- "DATFSGETFILEDATA: Incorrect file" + std::to_string(file.file_id()));
- BinaryData data((unsigned) (file.file_size() + (8 - offset)));
- if (file.block_size() >= file.file_size() + 8) {
- dat->GetIO().ReadData(data, file.file_size() + (8 - offset), file.file_offset() + offset);
- return DatOperationResult<BinaryData>(data, SUCCESS);
- }
- BinaryData fragments_count(4);
- dat->GetIO().ReadData(fragments_count, 4, file.file_offset());
- long long fragments_number = fragments_count.ToNumber<4>(0);
- long long current_block_size = file.block_size() - offset - 8 * fragments_number;
- dat->GetIO().ReadData(data, current_block_size, file.file_offset() + offset);
- BinaryData FragmentsDictionary(8 * unsigned(fragments_number));
- dat->GetIO().ReadData(FragmentsDictionary, 8 * unsigned(fragments_number),
- file.file_offset() + file.block_size() - 8 * fragments_number);
- for (long long i = 0; i < fragments_number; i++) {
- long long fragment_size = FragmentsDictionary.ToNumber<4>(8 * i);
- long long fragment_offset = FragmentsDictionary.ToNumber<4>(8 * i + 4);
- dat->GetIO().ReadData(data, std::min(fragment_size, file.file_size() - current_block_size),
- fragment_offset,
- current_block_size);
- current_block_size += fragment_size;
- }
- return DatOperationResult<BinaryData>(data, SUCCESS);
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Возвращает указатель на объект файла в словаре. Через этот указатель можно патчить/извлекать файл
- * \param[in] file_id идентификатор получаемого файла
- * \return Возвращает result = ERROR и value = nullptr, если файл не найден
- */
- DatOperationResult<std::shared_ptr<SubFile>> DatFileSystem::GetFile(long long file_id) {
- if (!dat)
- return DatOperationResult<std::shared_ptr<SubFile>>(nullptr, ERROR,
- "DATFSGETFILE: no connection with Dat (dat is nullptr)");
- if (dictionary_.count(file_id) == 0) {
- auto operation = InitSubFile(file_id);
- if (operation.result == ERROR)
- return DatOperationResult<std::shared_ptr<SubFile>>(nullptr, ERROR);
- }
- return DatOperationResult<std::shared_ptr<SubFile>>(dictionary_[file_id], SUCCESS);
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Обновляет данные файла
- * \param[in] preinit_file неинициализированный объект файла, данные которого обновляются в словаре
- * \warning Новые данные не будут записаны в dat файле, пока не будет вызвана функция CommitDirectories()
- */
- DatOperationResult<> DatFileSystem::UpdateFileInfo(const SubFile &preinit_file) {
- auto operation = GetFile(preinit_file.file_id());
- if (operation.result == ERROR)
- return DatOperationResult<>(ERROR, "DATFS: Unable to update file info - no file in dict with id = " +
- std::to_string(preinit_file.file_id()));
- auto& file = operation.value;
- file->unknown1_ = preinit_file.unknown1();
- file->file_id_ = preinit_file.file_id();
- file->file_offset_ = preinit_file.file_offset();
- file->file_size_ = preinit_file.file_size();
- file->timestamp_ = preinit_file.timestamp();
- file->version_ = preinit_file.version();
- file->block_size_ = preinit_file.block_size();
- file->unknown2_ = preinit_file.unknown2();
- subfile_pending_update.insert(preinit_file.file_id());
- return DatOperationResult<>();
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Проверяет корректность файла, полученного из словаря
- *
- * \return Объект DatOperationResult<bool> для которого value = true, если файл корректный, и false - иначе.
- */
- DatOperationResult<bool> DatFileSystem::CheckCorrectSubfile(const SubFile& file) {
- if (!dat)
- return DatOperationResult<bool>(false, ERROR,
- "DATFSCORRECTSUBFILE: no connection with Dat (dat is nullptr)");
- if (file.file_size() < 16)
- return DatOperationResult<bool>(false, SUCCESS);
- BinaryData mfile_id(20);
- auto operation = dat->GetIO().ReadData(mfile_id, 20, file.file_offset() + 8);
- if (operation.result == ERROR)
- return DatOperationResult<bool>(false, ERROR, "DATFSCORRECTSUBFILE: cannot read file header data");
- return DatOperationResult<bool>((mfile_id.CheckCompression() || file.file_id() == mfile_id.ToNumber<4>(0)) &&
- file.file_size() < 50ll * 1024ll * 1024ll, SUCCESS);
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Записывает все изменения структуры файловой системы в dat-файл.
- */
- DatOperationResult<> DatFileSystem::CommitDirectories() {
- if (!dat)
- return DatOperationResult<>(ERROR, "DATFSCOMMITDIRS: no connection with Dat (dat is nullptr)");
- for (auto file_id : subfile_pending_update) {
- if (dictionary_.count(file_id) == 0)
- continue;
- // auto operation = CheckCorrectSubfile(dictionary_[file_id]);
- // if (operation.result == ERROR) {
- // LOG(ERROR) << "Check subfile correctness failed";
- // continue;
- // }
- // if (!operation.value) {
- // LOG(ERROR) << "Incorrect SubFile " << file_id << " data: doesn't match";
- // continue;
- // }
- auto operation1 = dat->GetIO().WriteData(dictionary_[file_id]->MakeHeaderData(), 32,
- dictionary_[file_id]->dictionary_offset());
- if (operation1.result == ERROR)
- LOG(ERROR) << "Unable to write data to dictionary for file " << file_id;
- }
- subfile_pending_update.clear();
- return DatOperationResult<>(SUCCESS);
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Инициализация модуля. Подготавливает файловую систему для работы
- */
- DatOperationResult<> DatFileSystem::Init() {
- if (!dat)
- return DatOperationResult<>(ERROR, "DATFSINIT: no connection with Dat (dat is nullptr)");
- DeInit();
- patched_file_end = dat->GetIO().file_size;
- SubDirectory root_directory(0, (unsigned) dat->GetIO().root_directory_offset);
- subdir_init_queue_.insert(root_directory);
- return DatOperationResult<>(SUCCESS);
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Завершает работу файловой системы и записывает изменения в dat файл
- *
- * \return Вернёт DatOperationResult<>(ERROR), если случилась ошибка записи структуры файлов в dat файл
- */
- DatOperationResult<> DatFileSystem::DeInit() {
- if (!dat)
- return DatOperationResult<>(ERROR, "DATFSDEINIT: no connection with Dat (dat is nullptr)");
- auto operation = CommitDirectories();
- dictionary_.clear();
- visited_subdirectories_offsets_.clear();
- visited_subfiles_ids_.clear();
- subdir_init_queue_.clear();
- subfile_init_map_.clear();
- subfile_init_queue_.clear();
- subfile_pending_update.clear();
- if (operation.result == ERROR)
- return DatOperationResult<>(ERROR, "DEINITFS: unable to commit directories.");
- return DatOperationResult<>(SUCCESS);
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Записывает общую информацию о файловой системе. Требует полной инициализации всех папок и файлов.
- *
- * \param[in] file Объект файла, в конец которого будут записываться данные.
- */
- void DatFileSystem::PrintInformaion(FILE *file) {
- if (!dat) {
- LOG(ERROR) << "DATFSPRINTINFO: no connection with Dat (dat is nullptr)";
- return;
- }
- InitAllFiles();
- fprintf(file, "============= FS INFO SECTION =============\n");
- fprintf(file, "Files in dictionary number: %d\n", dictionary_.size());
- fprintf(file, "Files visited: %d\n", visited_subfiles_ids_.size());
- fprintf(file, "Folders visited: %d\n", visited_subdirectories_offsets_.size());
- fprintf(file, "File patched size: %u\n", patched_file_end);
- std::map<FILE_TYPE, size_t> filetypes_count{{TEXT, 0},
- {JPG, 0},
- {DDS, 0},
- {WAV, 0},
- {OGG, 0},
- {FONT, 0},
- {UNKNOWN, 0}};
- for (const auto &datfile : dictionary_) {
- filetypes_count[datfile.second->FileType()]++;
- }
- fprintf(file, "TEXT files number = %d\n", filetypes_count[TEXT]);
- fprintf(file, "JPG files number = %d\n", filetypes_count[JPG]);
- fprintf(file, "DDS files number = %d\n", filetypes_count[DDS]);
- fprintf(file, "WAV files number = %d\n", filetypes_count[WAV]);
- fprintf(file, "OGG files number = %d\n", filetypes_count[OGG]);
- fprintf(file, "FONT files number = %d\n", filetypes_count[FONT]);
- fprintf(file, "UNKNOWN files number = %d\n", filetypes_count[UNKNOWN]);
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Инициализация файла с заданным id. Ищет информацию о файле, инициализирует его в соответствии с типом файла и
- * помещает в словарь. Работает быстрее, если все папки уже были проинициализированы. В противном случае - пытается
- * инициализировать каждую из папок, пока не найдёт в ней файла с требуем id.
- *
- * \param[in] file_id Id файла, который нужно проинициализировать.
- * \return Объект DatOperationResult. Если файл не удалось проинициализировать - поле result будет равно ERROR
- */
- DatOperationResult<> DatFileSystem::InitSubFile(long long file_id) {
- if (!dat)
- return DatOperationResult<>(ERROR, "DATFSINITSUBFILE: no connection with Dat (dat is nullptr)");
- while (subfile_init_map_.count(file_id) == 0) {
- if (subdir_init_queue_.empty())
- return DatOperationResult<>(ERROR,
- "DATFSINITSUBFILE: Cannot init file with id = "
- + std::to_string(file_id) + " (NOT FOUND)");
- SubDirectory dir = *subdir_init_queue_.begin();
- subdir_init_queue_.erase(subdir_init_queue_.begin());
- InitSubDirectory(dir);
- }
- SubFile file = subfile_init_map_[file_id];
- subfile_init_queue_.erase(file);
- subfile_init_map_.erase(file_id);
- auto initialised_file = SubFile::MakeSubfile(*dat, file);
- if (!initialised_file)
- return DatOperationResult<>(ERROR, "DATFSINITSUBFILE: initialised subfile pointer is empty");
- if (!CheckCorrectSubfile(*initialised_file).value)
- return DatOperationResult<>(ERROR, "DATFSINITSUBFILE: initialised file " + std::to_string(file.file_id()) + " is incorrect");
- if (dictionary_.count(file_id) > 0) {
- LOG(WARNING) << "Dublicate files id = " << file_id << "dictionary offsets = "
- << dictionary_[file_id]->dictionary_offset() << " and "
- << initialised_file->dictionary_offset();
- if (CheckCorrectSubfile(*dictionary_[file_id]).value)
- LOG(ERROR) << " FILE IN DICTIONARY IS ALREADY CORRECT!";
- dictionary_[file_id] = initialised_file;
- }
- if (dictionary_.count(file_id) == 0)
- dictionary_[file_id] = initialised_file;
- return DatOperationResult<>();
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Инициализация всех папок. Собирает информацию обо всех файлах в .dat файле для быстрого доступа к ним.
- * \warning Сами файлы не инициализируются. Инициализация файла выполнится при первом вызове GetFile() или GetFileData()
- *
- * \return Объект DatOperationResult
- */
- DatOperationResult<> DatFileSystem::InitAllDirectories() {
- if (!dat)
- return DatOperationResult<>(ERROR, "DATFSINITALLDIRS: no connection with Dat (dat is nullptr)");
- while (!subdir_init_queue_.empty()) {
- SubDirectory dir = *subdir_init_queue_.begin();
- subdir_init_queue_.erase(subdir_init_queue_.begin());
- InitSubDirectory(dir);
- }
- return DatOperationResult<>();
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Инициализация всех файлов. Полностью инициализирует все внутренние папки и файлы и помещает последние в словарь.
- *
- * \return Объект DatOperationResult
- */
- DatOperationResult<> DatFileSystem::InitAllFiles() {
- if (!dat)
- return DatOperationResult<>(ERROR, "DATFSINITALLFILES: no connection with Dat (dat is nullptr)");
- InitAllDirectories();
- while (!subfile_init_queue_.empty()) {
- SubFile file = *subfile_init_queue_.begin();
- InitSubFile(file.file_id());
- }
- return DatOperationResult<>();
- }
- /*!
- * \author Gi1dor
- * \date 29.06.2018
- * Инициализация папки. Читает внутреннюю структуру папки и получает информацию о файлах и подпапках.
- *
- * Структура папки:
- * 63 * 8 байт под описание подпапок. По 8 байт на папку.
- * Из них по 4 байта на 2 поля:
- * unknown - неизвестное поле
- * offset - положение подпапки в .dat файле
- *
- * 4 байта - количество файлов в папке. В каждой папке не более 64 Файлов.
- *
- * 32 * 64 байта под описание файлов. По 32 байта на каждый файл.
- *
- * Из них по 4 байта на каждое из 8 полей:
- * unknown1 - неизвестное поле
- * file_id - уникальный идентификатор файла
- * file_offset - положение содержимого файла
- * file_size - размер файла в байтах
- * timestamp - timestamp последнего редактирования
- * version - версия файла
- * block_size - размер блока данных, выделенного под файл
- * unknown2 - неизвестное поле
- *
- * \param[in] dir объект папки SubDirectory
- * \return Объект DatOperationResult
- */
- DatOperationResult<> DatFileSystem::InitSubDirectory(SubDirectory dir) {
- if (!dat)
- return DatOperationResult<>(ERROR, "DATFSINITSUBDIR: no connection with Dat (dat is nullptr)");
- BinaryData subdir_data(63 * 8 + 4 + 32 * 64);
- auto operation = dat->GetIO().ReadData(subdir_data, 63 * 8 + 4 + 32 * 64, dir.offset());
- if (operation.result == ERROR)
- return DatOperationResult<>(ERROR, "DATFSINITSUBDIR: cannot init dir (read error) with offset = " +
- std::to_string((dir.offset())));
- if (subdir_data.ToNumber<4>(0) != 0 || subdir_data.ToNumber<4>(4) != 0)
- return DatOperationResult<>(ERROR, "DATFSINITSUBDIR: incorrect dir. Nulls must be at first 8 bytes of dir");
- // Subfiles section
- long long subfiles_number = subdir_data.ToNumber<4>(63 * 8);
- if (subfiles_number > 64)
- return DatOperationResult<>(ERROR, "DATINITSUBDIR: incorrect subdir: files number > 64");
- // Subdirs section
- for (unsigned i = 8; i < 63 * 8; i += 8) {
- if (subdir_data.ToNumber<4>(i) == 0 || subdir_data.ToNumber<4>(i + 4) == 0)
- break;
- long long unknown = subdir_data.ToNumber<4>(i);
- long long offset = subdir_data.ToNumber<4>(i + 4);
- if (unknown == 0 || offset == 0)
- break;
- if (visited_subdirectories_offsets_.count(offset) != 0)
- continue;
- visited_subdirectories_offsets_.insert(offset);
- subdir_init_queue_.insert(SubDirectory(unknown, offset));
- }
- // Subfiles section
- for (int i = 0; i < subfiles_number; i++) {
- long long header_start = 63 * 8 + 4 + 32 * i;
- SubFile file = SubFile(
- *dat,
- dir.offset() + header_start,
- subdir_data.ToNumber<4>(header_start + 0), // unknown1
- subdir_data.ToNumber<4>(header_start + 4), // file_id
- subdir_data.ToNumber<4>(header_start + 8), // file_offset
- subdir_data.ToNumber<4>(header_start + 12), // file_size
- subdir_data.ToNumber<4>(header_start + 16), // timestamp
- subdir_data.ToNumber<4>(header_start + 20), // version
- subdir_data.ToNumber<4>(header_start + 24), // block_size
- subdir_data.ToNumber<4>(header_start + 28) // unknown2
- );
- if (file.version() == 0 || file.unknown2() != 0)
- continue;
- if (visited_subfiles_ids_.count(file.file_id()) || file.file_id() == -1)
- break;
- visited_subfiles_ids_.insert(file.file_id());
- subfile_init_map_[file.file_id_] = file;
- subfile_init_queue_.insert(file);
- }
- return DatOperationResult<>();
- }
- /*!
- * \author Gi1dor
- * \date 03.07.2018
- * Выполнение функции function для всех файлов в dat контейнере.
- *
- * \param[in] function Функтор, принимающий в качестве аргумента ссылку на объект-указатель std::shared_ptr<SubFile>
- */
- DatOperationResult<> DatFileSystem::PerformOperationOnAllFiles(const std::function<void (std::shared_ptr<SubFile>&)>& function) {
- InitAllFiles();
- for (auto& file_pair: dictionary_)
- function(file_pair.second);
- return DatOperationResult<>();
- }
- int DatFileSystem::GetInitialisedFilesNumber() const {
- return dictionary_.size();
- }
- }
|