#include "BinaryData.h" #include "DatFile.h" #include "EasyLogging++/easylogging++.h" #include #include #include #include #include #ifdef WIN32 #define fseek _fseeki64 #define ftell _ftelli64 #endif unsigned int RSHash(const std::string& str) { unsigned int b = 378551; unsigned int a = 63689; unsigned int hash = 0; for (char i : str) { hash = hash * a + i; a = a * b; } return hash; } extern "C++" { namespace LOTRO_DAT { /*! * \author Gi1dor * \date 30.06.2018 * Конструктор объекта модуля. Вызывается только из класса DatFile. Создаёт указатель но объект DatFile, но * не инициализирует модуль. Инизиализация происходит в функции Init * \param[in] datFile Указатель на объект управляющего класса DatFile */ DatIO::DatIO(DatFile *datFile) : dat(datFile), file_handler_(nullptr), filename_("none"), actual_dat_size_(0) { } DatIO::~DatIO() { DeInit(); } //------------------------------------------------// // INIT SECTION //------------------------------------------------// /*! * \author Gi1dor * \date 30.06.2018 * Инициализация модуля. Открывает файл на чтение и блокирует его для доступа из других программ. * Читает заголовочную информацию, необходимую для инициализации остальных модулей. Не должна вызываться где-либо, * кроме как из управляющего объекта DatFile. * \param[in] filename имя файла для открытия (включая путь) * \return Возвращает result = ERROR в случае, если не удалось открыть файл или файл некорректный */ DatOperationResult<> DatIO::Init(const std::string &filename) { LOG(INFO) << "Initializing IO: " << filename; filename_ = filename; auto result = OpenDatFile(); if (result.result == ERROR) { LOG(ERROR) << "Error in OpenDatFile: " << result.msg; DeInit(); return result; } result = ReadSuperBlock(); if (result.result == ERROR) { LOG(ERROR) << "Error in ReadSuperBlock: " << result.msg; DeInit(); return result; } LOG(INFO) << "Successfull initializing IO: " << filename; return DatOperationResult<>(SUCCESS, "DatIO initialized successfully"); } /*! * \author Gi1dor * \date 30.06.2018 * Открытие файла, имя которого записано в переменной file_handler_ * \return Возвращает result = ERROR в случае, если не удалось открыть файл */ DatOperationResult<> DatIO::OpenDatFile() { LOG(DEBUG) << "DatIO: Started opening DatFile"; file_handler_ = fopen(filename_.c_str(), "r+b"); if (file_handler_ == nullptr) { LOG(ERROR) << "DatIO: Unable to open file " << filename_ << ". Presumably - no file found..."; return DatOperationResult<>(ERROR, std::string("Unable to locate and open file " + filename_)); } fseek(file_handler_, 0, SEEK_END); actual_dat_size_ = ftell(file_handler_); fseek(file_handler_, 0, SEEK_SET); LOG(INFO) << "DatIO: file opened"; return DatOperationResult<>(SUCCESS, std::string("Successfully opened file " + filename_)); } /*! * \author Gi1dor * \date 30.06.2018 * Инициализация основных переменных файла, располагающихся в начале. * * Известные переменные (каждая по 4 байта): * * Офсет - описание переменной * ========= * 0x100 - константа, одинаковая для всех .dat файлов * 0x140 - ещё одна константа, одинаковая для всех .dat файлов * 0x14C - Первая переменная версии .dat файла * 0x148 - размер .dat файла. Указывает на начало свободного места. Необязательно должен являться физическим концом * 0x150 - Вторая переменная версии dat файла * 0x154 - Положение журнала фрагментации (который представляет собой список свободных блоков в dat-файле, куда можно писать новые данные * 0x158 - Позиция окончания журнала фрагментации (?) * 0x15C - Кол-во записей в журнале фрагментации (?) * 0x160 - Офсет корневой папки * 0x19C - Свободное место в dat файле (?) * * \return Возвращает result = ERROR в случае, если константы не совпадают с требуемыми значениями */ DatOperationResult<> DatIO::ReadSuperBlock() { LOG(INFO) << "DatIO: Started reading superblock"; BinaryData data(1024); ReadData(data, 1024); constant1 = data.ToNumber<4>(0x100); constant2 = data.ToNumber<4>(0x140); version1 = data.ToNumber<4>(0x14C); file_size = data.ToNumber<4>(0x148); version2 = data.ToNumber<4>(0x150); fragmentation_journal_offset = data.ToNumber<4>(0x154); fragmentation_journal_end = data.ToNumber<4>(0x158); fragmentation_journal_size = data.ToNumber<4>(0x15C); root_directory_offset = data.ToNumber<4>(0x160); free_dat_size = data.ToNumber<4>(0x19C); if (constant1 != 0x4C5000) return DatOperationResult<>(ERROR, "Variable at position 0x100 is not equal to .dat file constant!"); if (constant2 != 0x5442) return DatOperationResult<>(ERROR, "Variable at position 0x140 is not equal to .dat file constant!"); LOG(INFO) << "DatIO: Superblock read successfully"; return DatOperationResult<>(SUCCESS, "Superblock read successfully."); } //------------------------------------------------// // PUBLIC READ/WRITE SECTION //------------------------------------------------// /*! * \author Gi1dor * \date 30.06.2018 * Чтение бинарных данных из файла. Считает size байт dat файла, начиная с положения offset, и запишет их в * объект data, причём положение первого байта считанных данных будет равно значению data_offset * * \param[in, out] data Объект класса BinaryData, в который будут считаны данные * \param[in] size Количество байт, которые будут считаны * \param[in] offset Положение начала данных в dat файле, откуда считать * \param[in] data_offset Положение записи. Считанные данные будут записаны с отступом data_offset в data * * \return Возвращает result = ERROR в случае, если считать не удалось (неверные входные данные) */ DatOperationResult<> DatIO::ReadData(BinaryData &data, long long size, long long offset, long long data_offset) const { if (file_handler_ == nullptr) return DatOperationResult<>(ERROR, "IOREADDATA: file handler is null pointer on reading data."); if (data_offset + size > data.size()) return DatOperationResult<>(ERROR, "IOREADDATA: Parameters: offset, size are out of range."); if (offset + size > actual_dat_size_) return DatOperationResult<>(ERROR, "IOREADDATA: Reading more than DatFile size elapsed."); if (offset != ftell(file_handler_)) fseek(file_handler_, offset, SEEK_SET); fread(data.data() + data_offset, unsigned(size), 1, file_handler_); return DatOperationResult<>(SUCCESS, "Read data successful."); } /*! * \author Gi1dor * \date 10.07.2018 * * Проверка буфера перед записью новых size_to_write байт в конец файла. Если буфер мал - то дозаписывается * новый чистый блок размера MAX_EOF_BUFFER (константное поле класса DatIO); * * \param[in] size_to_write Размер данных в байтах, которые будут сейчас записаны в конец файла * */ void DatIO::UpdateBufferIfNeeded(long long size_to_write) { BinaryData empty_buffer(std::max(MAX_EOF_BUFFER, unsigned(size_to_write))); fseek(file_handler_, 0, SEEK_END); fwrite(empty_buffer.data(), empty_buffer.size(), 1, file_handler_); actual_dat_size_ += empty_buffer.size(); } /*! * \author Gi1dor * \date 30.06.2018 * Запись бинарных данных в dat файл. Запишет size байт данных из data, начиная с позиции data_offset, * в позицию offset .dat файла. * * \param[in] data Объект класса BinaryData, откуда будут взяты данные для записи * \param[in] size Количество байт для записи * \param[in] offset Положение начала данных в dat файле, куда записывать * \param[in] data_offset Положение начала данных в data, от которого будет взято size байт и записано в dat файл * * \return Возвращает result = ERROR в случае, если записать не удалось (неверные входные данные) */ DatOperationResult<> DatIO::WriteData(const BinaryData &data, long long size, long long offset, long long data_offset) { if (file_handler_ == nullptr) return DatOperationResult<>(ERROR, "IOWRITEDATA: file handler is null pointer on writing data."); if (data_offset + size > data.size()) return DatOperationResult<>(ERROR, "IOWRITEDATA: writing more than BinaryData size."); if (offset + size > actual_dat_size_) UpdateBufferIfNeeded(offset + size - actual_dat_size_); if (offset != ftell(file_handler_)) fseek(file_handler_, offset, SEEK_SET); fwrite(data.data() + data_offset, unsigned(size), 1, file_handler_); return DatOperationResult<>(SUCCESS, "Data writing successful."); } //------------------------------------------------// // DEINIT SECTION //------------------------------------------------// /*! * \author Gi1dor * \date 30.06.2018 * Деинициализация модуля. Не должна вызываться где-либо, кроме как из управляющего объекта DatFile. */ DatOperationResult<> DatIO::DeInit() { if (!dat->Initialized()) { ClearData(); return DatOperationResult<>(SUCCESS); } if (file_handler_ != nullptr) fclose(file_handler_); truncate64(filename_.c_str(), actual_dat_size_); filename_ = "none"; file_handler_ = nullptr; constant1 = 0; constant2 = 0; file_size = 0; version1 = 0; version2 = 0; fragmentation_journal_size = 0; fragmentation_journal_end = 0; root_directory_offset = 0; fragmentation_journal_offset = 0; return DatOperationResult<>(SUCCESS, "File deinitialisation successfull"); } DatOperationResult<> DatIO::ModifyFragmentationJournal() { if (fragmentation_journal_size == 0) return DatOperationResult<>(SUCCESS, "DatIO: Fragmentation journal is empty. Nothing to do."); LOG(DEBUG) << "Modifying fragmentation journal"; BinaryData data(4); ReadData(data, 4, fragmentation_journal_offset + 8 * fragmentation_journal_size); LOG(INFO) << "FREE_SIZE BLOCK = " << data.ToNumber<4>(0); long long free_size = data.ToNumber<4>(0); long long free_offset = file_size; BinaryData nulldata = BinaryData(unsigned(free_size)); WriteData(nulldata, nulldata.size(), file_size); file_size += nulldata.size(); WriteData(BinaryData::FromNumber<4>(free_size), 4, fragmentation_journal_offset + 8 * fragmentation_journal_size); WriteData(BinaryData::FromNumber<4>(free_offset), 4, fragmentation_journal_offset + 8 * fragmentation_journal_size + 4); //nulldata = BinaryData(8); //WriteData(nulldata, nulldata.size(), fragmentation_journal_offset + 16); LOG(DEBUG) << "Finished modifying fragmentation journal"; return DatOperationResult<>(SUCCESS, "Fragmentation journal patched successfully!"); } DatOperationResult<> DatIO::UpdateHeader() { LOG(DEBUG) << "Updating header"; WriteData(BinaryData::FromNumber<4>(constant1), 4, 0x100); WriteData(BinaryData::FromNumber<4>(constant2), 4, 0x140); //WriteData(BinaryData::FromNumber<4>( 0 ), 4, 0x144); WriteData(BinaryData::FromNumber<4>(file_size), 4, 0x148); WriteData(BinaryData::FromNumber<4>(version1), 4, 0x14C); WriteData(BinaryData::FromNumber<4>(version2), 4, 0x150); WriteData(BinaryData::FromNumber<4>(fragmentation_journal_offset), 4, 0x154); WriteData(BinaryData::FromNumber<4>(fragmentation_journal_end), 4, 0x158); WriteData(BinaryData::FromNumber<4>(fragmentation_journal_size), 4, 0x15C); WriteData(BinaryData::FromNumber<4>(root_directory_offset), 4, 0x160); WriteData(BinaryData::FromNumber<4>(free_dat_size), 4, 0x19C); LOG(DEBUG) << "Finished updating header"; return DatOperationResult<>(SUCCESS, "File header patched successfully");; } /*! * \author Gi1dor * \date 30.06.2018 * Возвращает размер dat файла в байтах (может отличаться от значения внутренней переменной file_size dat файла */ DatOperationResult DatIO::GetActualDatSize() { return DatOperationResult(actual_dat_size_, SUCCESS); } /*! * \author Gi1dor * \date 30.06.2018 * Возвращает имя dat файла, которое было передано при инициализации */ DatOperationResult DatIO::GetFilename() { if (filename_ == "none") return DatOperationResult("", ERROR, "DATIOGETFILENAME: not initialised"); return DatOperationResult(filename_, SUCCESS); } /*! * \author Gi1dor * \date 30.06.2018 * Выводит информацию о состоянии модуля в файл * \param[in] file указатель на объект файла, в конец которого будет напечатана информация * \warning Файл file должен быть открыт для записи. После завершения работы функции файл остаётся открытым */ void DatIO::PrintInformaion(FILE *file) { if (!file) return; fprintf(file, "========== IO info ==========\n"); fprintf(file, "Filename = %s\n", filename_.c_str()); fprintf(file, "File header data:\n"); fprintf(file, "constant1 = %lld\n", constant1); fprintf(file, "constant2 = %lld\n", constant2); fprintf(file, "file_size = %lld\n", file_size); fprintf(file, "version1 = %lld\n", version1); fprintf(file, "version2 = %lld\n", version2); fprintf(file, "fragment_journal_offset = %lld\n", fragmentation_journal_offset); fprintf(file, "fragment_journal_end = %lld\n", fragmentation_journal_end); fprintf(file, "fragment_journal_size = %lld\n", fragmentation_journal_size); fprintf(file, "root_dir_offset = %lld\n", root_directory_offset); fprintf(file, "free_dat_size = %lld\n", free_dat_size); fprintf(file, "DatLibrary: EOF buffer size constant = %d\n", MAX_EOF_BUFFER); } unsigned int DatIO::getHeaderHash() { std::string values = std::to_string(constant1) + std::to_string(constant2) + std::to_string(file_size) + std::to_string(version1) + std::to_string(version2) + std::to_string(fragmentation_journal_offset) + std::to_string(fragmentation_journal_end) + std::to_string(fragmentation_journal_size) + std::to_string(root_directory_offset) + std::to_string(free_dat_size); return RSHash(values); } void DatIO::ClearData() { constant1 = -1; constant2 = -1; file_size = -1; version1 = -1; version2 = -1; fragmentation_journal_size = -1; fragmentation_journal_end = -1; root_directory_offset = -1; fragmentation_journal_offset = -1; free_dat_size = -1; actual_dat_size_ = -1; } } }