#include #include #include #include namespace LOTRO_DAT { DatPatcher::DatPatcher(DatFile *datFilePtr) : dat(datFilePtr) { } /*! * \Author Gi1dor * \date 07.07.2018 * Обновление файла. Записывает данные файла в dat контейнер, используя информацию в SubfileData. Если ранее файл не существовал - он не будет создан (вернёт ошибку). * \warning В процессе применения локаль будет автоматически переключена в положение PATCHED * \param[in] data Новые данные файла * \param[in] single_file Флаг, который означает, что применяемый патч состоит из одного файла. * Если true, то функция будет управлять состоянием DatStatus и обновлять его. * Иначе эта обязанность делегируется вызывающей функции */ DatOperationResult<> DatPatcher::PatchFile(const SubfileData &data, bool single_file) { auto file_id = data.options["fid"].as(); if (single_file) { dat->GetStatusModule().SetStatus(DatStatus::E_PATCHING); dat->GetStatusModule().SetPercentage(0); } dat->GetStatusModule().SetDebugMessage("Patching file with id " + std::to_string(file_id)); auto getfile_operation = dat->GetFileSystem().GetFile(file_id); if (getfile_operation.result == ERROR) { if (single_file) dat->GetStatusModule().ClearAll(); return DatOperationResult<>(ERROR, "PATCHSUBFILEDATA: Unable to find file with id " + std::to_string(file_id)); } auto &file = getfile_operation.value; // If file has inactive category, then we should set it to patched state in order to commit patch and // then in ApplyFilePatch() function, if new category is still inactive, return dictionary to its original state; if (dat->GetLocaleManager().CategoryIsInactive(file->category) != 0) { auto operation = dat->GetLocaleManager().GetLocaleFile(file->file_id(), DatLocaleManager::PATCHED); if (operation.result == SUCCESS) dat->GetFileSystem().UpdateFileInfo(operation.value); } if (data.options["cat"].IsDefined()) file->category = data.options["cat"].as(); else file->category = 1; auto getdata_operation = dat->GetFileSystem().GetFileData(*file, 0); if (getdata_operation.result == ERROR) { if (single_file) dat->GetStatusModule().ClearAll(); return DatOperationResult<>(ERROR, "PATCHSUBFILEDATA: can't get file data for id = " + std::to_string(file_id)); } auto &file_data = getdata_operation.value; BinaryData patch_data = file->MakeForImport(file_data, data); auto result = ApplyFilePatch(file, patch_data); if (single_file) dat->GetStatusModule().ClearAll(); if (result.result == ERROR) return DatOperationResult<>(ERROR, "PATCHSUBFILEDATA: applyfilepatch failed for id = " + std::to_string(file_id)); return DatOperationResult<>(SUCCESS); } /*! * \Author Gi1dor * \date 07.07.2018 * Обновление всех файлов в Database. * \warning В процессе применения локаль будет автоматически переключена в положение PATCHED * \param[in] db Указатель на базу данных. База должна быть проинициализирована */ DatOperationResult DatPatcher::PatchAllDatabase(Database *db) { if (!db) return DatOperationResult(0, ERROR, "PATCHALLDATABASE: db is nullptr"); dat->GetStatusModule().SetStatus(DatStatus::E_PATCHING); dat->GetStatusModule().SetPercentage(0); dat->GetStatusModule().SetDebugMessage("Patching database..."); SubfileData data; data = db->GetNextFile(); int successfully_patched = 0; unsigned db_rows = db->CountRows(); for (unsigned i = 0; i < db_rows; ++i) { auto operation = PatchFile(data, false); if (operation.result == SUCCESS) successfully_patched++; dat->GetStatusModule().SetPercentage(i * 100 / db_rows); data = db->GetNextFile(); } LOG(INFO) << "Database import success: patched " + std::to_string(successfully_patched) + " out of " + std::to_string(db_rows) + " files"; dat->GetStatusModule().ClearAll(); return DatOperationResult(successfully_patched, SUCCESS); } /*! * \Author Gi1dor * \date 07.07.2018 * Функция, вызываемая функцией PatchFile, отвечающая за корректную запись файла в dat контейнер и обновление * информации о нём в файловой системе * \param[in] file Указатель на объект файла в файловой системе (полученный через DatFileSystem::GetFileInfo) * \param[in] data Бинарные данные собранного нового файла, готовые к записи в dat */ DatOperationResult<> DatPatcher::ApplyFilePatch(std::shared_ptr file, BinaryData &data) { long long file_id = file->file_id(); if (dat->GetLocaleManager().GetCurrentLocale() != DatLocaleManager::PATCHED) { LOG(INFO) << "Changing locale to PATCHED(RU) in order to patch file"; dat->GetLocaleManager().SetLocale(DatLocaleManager::PATCHED); } //LOG(INFO) << "Patching file with id = " << file_id; if (dat->GetLocaleManager().GetLocaleFile(file_id, DatLocaleManager::ORIGINAL).result == ERROR) dat->GetLocaleManager().UpdateLocaleFile(DatLocaleManager::ORIGINAL, SubFile(*file)); SubFile new_file = SubFile(*file); if (dat->GetLocaleManager().GetLocaleFile(file_id, DatLocaleManager::PATCHED).result == ERROR || data.size() > file->block_size()) { new_file.file_offset_ = dat->GetIO().file_size; new_file.block_size_ = std::max(data.size(), 256u); dat->GetIO().file_size += new_file.block_size_ + 8; } new_file.file_size_ = data.size() - 8; data.Append(BinaryData::FromNumber<4>(0), 0); // set additional fragments count to zero if (file_id != data.ToNumber<4>(8)) LOG(WARNING) << "Created data's file_id " << file_id << "doesn't match to original: " << data.ToNumber<4>(8); auto operation = dat->GetIO().WriteData(data, data.size(), new_file.file_offset()); if (operation.result == ERROR) return DatOperationResult<>(ERROR, "APPLYPATCHFILE: Unable to write data for file with id " + std::to_string(file_id)); dat->GetFileSystem().UpdateFileInfo(new_file); dat->GetLocaleManager().UpdateLocaleFile(DatLocaleManager::PATCHED, new_file); // If file category is inactive, then return file header data in dictionary to original state if (dat->GetLocaleManager().CategoryIsInactive(new_file.category)) dat->GetFileSystem().UpdateFileInfo( dat->GetLocaleManager().GetLocaleFile(file_id, DatLocaleManager::ORIGINAL).value); dat->GetLocaleManager().UpdateCategory(file_id, new_file.category); //LOG(INFO) << "Successfully patched file with id = " << file_id; return DatOperationResult<>(SUCCESS); } };