Bläddra i källkod

Improved and documented LocaleManager module. Needs testing

Ivan Arkhipov 5 år sedan
förälder
incheckning
150ac60227
2 ändrade filer med 195 tillägg och 30 borttagningar
  1. 28 22
      include/DatSubsystems/DatLocaleManager.h
  2. 167 8
      src/DatSubsystems/DatLocaleManager.cpp

+ 28 - 22
include/DatSubsystems/DatLocaleManager.h

@@ -1,7 +1,3 @@
-//
-// Created by kikab on 04.06.2018.
-//
-
 #ifndef LOTRO_DAT_LIBRARY_DATLOCALEMANAGER_H
 #define LOTRO_DAT_LIBRARY_DATLOCALEMANAGER_H
 
@@ -13,8 +9,23 @@
 extern "C++" {
 namespace LOTRO_DAT {
     class DatFile;
+
     class SubFile;
 
+    /*!
+     * \brief Модуль работы с локалями
+     * \author Gi1dor
+     * \date 06.07.2018
+     *
+     * Класс для работы с искуственно внедряемымыми в dat контейнер копиями файлов. Позволяет независимо хранить
+     * информацию о двух версиях каждого файла, которую можно подставлять в файловую систему.
+     * В рамках русификации хранит информацию об оригинальной и русифицированной версии тех файлов, для которых
+     * существует русификация.
+     *
+     * \warning Данные локалей чувствительны к обновлениям игры и могут быть стёрты после них!
+     * \warning Объекты этого класса не должны создаваться отдельно! Созданием и управлением ими занимается класс DatFile
+     */
+
     class DatLocaleManager {
     public:
         enum LOCALE : int {
@@ -23,24 +34,33 @@ namespace LOTRO_DAT {
         };
 
         DatLocaleManager() = delete;
+
         DatLocaleManager(const DatLocaleManager &other) = delete;
-        DatLocaleManager& operator=(const DatLocaleManager &other) = delete;
+
+        DatLocaleManager &operator=(const DatLocaleManager &other) = delete;
+
         ~DatLocaleManager() = default;
 
         explicit DatLocaleManager(DatFile *datFilePtr);
 
         DatOperationResult<> Init();
+
         DatOperationResult<> SetLocale(LOCALE locale);
+
         DatOperationResult<> DeInit();
 
         LOCALE GetCurrentLocale();
-        void UpdateLocaleFile(LOCALE locale, const SubFile& file);
+
+        bool CheckLocaleCorrect();
+
+        void UpdateLocaleFile(LOCALE locale, const SubFile &file);
+
         DatOperationResult<SubFile> GetLocaleFile(long long file_id, LOCALE locale);
 
-        void PrintInformaion(FILE* file);
+        void PrintInformaion(FILE *file);
 
     private:
-        std::map<long long, SubFile>& GetLocaleDictReference(LOCALE locale);
+        std::map<long long, SubFile> &GetLocaleDictReference(LOCALE locale);
 
     private:
         DatFile *dat;
@@ -52,19 +72,5 @@ namespace LOTRO_DAT {
 }
 };
 
-/*
- * ======== LOCALE DICT STRUCTURE =========
- * 4                                bytes for block size (in bytes)
- * 4                                bytes for locale version
- * 4                                bytes for .dat file size (with patches)
- * 15                               bytes for "Hi from Gi1dor"
- * 4                                bytes for LOCALE
- * 4                                bytes for orig_dict.size()
- * (32 + 4) * orig_dict.size()      bytes for orig_dict data
- * 4                                bytes for patch_dict.size()
- * (32 + 4) * patch_dict.size()     bytes for patch_dict data
- * 4                                bytes for inactive_categories dict
- * 4 * inactive_categories.size()   bytes for inactive_categories data
- */
 
 #endif //LOTRO_DAT_LIBRARY_DATLOCALEMANAGER_H

+ 167 - 8
src/DatSubsystems/DatLocaleManager.cpp

@@ -1,7 +1,3 @@
-//
-// Created by kikab on 04.06.2018.
-//
-
 #include <DatSubsystems/DatLocaleManager.h>
 #include <EasyLogging++/easylogging++.h>
 #include <DatFile.h>
@@ -11,12 +7,45 @@ namespace LOTRO_DAT {
     DatLocaleManager::DatLocaleManager(DatFile *datFilePtr) : dat(datFilePtr), current_locale_(ORIGINAL) {
     }
 
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Инициализация модуля. Должна происходить после инициализации модуля ввода-вывода и файловой системы.
+     * Считывает словарь и проверяет корректность (если структура словаря нарушена и основной является локаль PATHCED,
+     * то dat файл считается некорреткным (любое обновлене dat файла с активной локалью PATCHED нарушает структуру файла
+     * и делает использование в игре невозможным.
+     *
+     * \warning Не должна вызываться вручную! Автоматически вызывается в функции Initialise класса DatFile
+     *
+     * Структура словарей локализации:
+     * ======== LOCALE DICT STRUCTURE =========
+     * 4                                bytes for dict size (in bytes)
+     * 4                                bytes for locale version
+     * 4                                bytes for .dat file size (with patches)
+     * 15                               bytes for "Hi from Gi1dor"
+     * 4                                bytes for LOCALE
+     * 4                                bytes for orig_dict.size()
+     * (32 + 4) * orig_dict.size()      bytes for orig_dict data
+     * 4                                bytes for patch_dict.size()
+     * (32 + 4) * patch_dict.size()     bytes for patch_dict data
+     * 4                                bytes for inactive_categories dict
+     * 4 * inactive_categories.size()   bytes for inactive_categories data
+     * ========================================
+     * Помимо этого:
+     * 0x128-0x12C - Флаг, установлена ли альтернативная версия (flag)
+     * 0x12C-0x130 - Офсет начала словаря локализации (offset)
+     *
+     * \warning Если flag != 0 и offset == 0 - то dat файл считается повреждённым. Проверка осуществляется функцией CheckLocaleCorrectж
+     */
+
     DatOperationResult<> DatLocaleManager::Init() {
         if (!dat)
             return DatOperationResult<>(ERROR, "LOCALEINIT: no connection with Dat (dat is nullptr)");
 
         orig_dict_.clear();
         patch_dict_.clear();
+        inactive_categories.clear();
+        current_locale_ = ORIGINAL;
 
         LOG(INFO) << "Initialising locales...";
         BinaryData locale_offset_data(4);
@@ -39,11 +68,14 @@ namespace LOTRO_DAT {
 
         if (dict_version != 101) {
             dat->getIO().WriteData(BinaryData::FromNumber<4>(0), 4, 300);
-            return DatOperationResult<>(SUCCESS, "Version of locales' dictionary is old (ver = " + std::to_string(dict_version) + "), cannot initialize locales.");
+            if (CheckLocaleCorrect())
+                return DatOperationResult<>(SUCCESS);
+            else
+                return DatOperationResult<>(ERROR, "Version of locales' dictionary is incorrect, through patched mark is standing. Dat file may be corrupted");
         }
 
         BinaryData dicts_data = BinaryData((unsigned)dict_size);
-        dat->getIO().ReadData(dicts_data, dict_size, locale_offset + 12);
+        dat->getIO().ReadData(dicts_data, dict_size - 12, locale_offset + 12);
 
         if (dicts_data.size() < 15) {
             dat->getIO().WriteData(BinaryData::FromNumber<4>(0), 4, 300);
@@ -87,9 +119,22 @@ namespace LOTRO_DAT {
         LOG(INFO) << "There are " << patch_dict_.size() << " files in patch locale dictionary";
         LOG(INFO) << "There are " << orig_dict_.size() << " files in original locale dictionary";
         LOG(INFO) << "Finished initialising locales";
-        return DatOperationResult<>(SUCCESS);
+
+        if (CheckLocaleCorrect())
+            return DatOperationResult<>(SUCCESS);
+        else
+            return DatOperationResult<>(ERROR, "Locale dict is incorrect, through patched mark is standing. Dat file may be corrupted");
     }
 
+
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Смена активной локали. Меняет информацию о файлах в файловой системе на соответствующую информацию в локали.
+     * \param[in] locale Устанавливаемая локаль
+     * \warning Изменения в файловую систему вносятся локально! В dat файл изменения будут записаны во время деинициализации модуля файловой системы
+     */
+
     DatOperationResult<> DatLocaleManager::SetLocale(DatLocaleManager::LOCALE locale) {
         LOG(INFO) << "Setting locale to " << (locale == PATCHED ? " PATCHED" : " ORIGINAL");
         if (!dat)
@@ -122,10 +167,46 @@ namespace LOTRO_DAT {
         return DatOperationResult<>(SUCCESS);
     }
 
+
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Получение текущей активной локали. Если локали не существуют, понимается, что используется оригинальная (ORIGINAL)
+     */
+
     DatLocaleManager::LOCALE DatLocaleManager::GetCurrentLocale() {
         return current_locale_;
     }
 
+
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Деинициализация модуля. Должна происходить перед деинициализацией модулей ввода-вывода и файловой системы.
+     * Записывает словарь. Если ранее словарь не существовал - новый будет записан в конец файла.
+     * Для словаря выделяется блок не менее 20мб
+     *
+     * \warning Не должна вызываться вручную! Автоматически вызывается в функции Deinitialise класса DatFile
+     *
+     * Структура словарей локализации:
+     * ======== LOCALE DICT STRUCTURE =========
+     * 4                                bytes for dict size (in bytes)
+     * 4                                bytes for locale version
+     * 4                                bytes for .dat file size (with patches)
+     * 15                               bytes for "Hi from Gi1dor"
+     * 4                                bytes for LOCALE
+     * 4                                bytes for orig_dict.size()
+     * (32 + 4) * orig_dict.size()      bytes for orig_dict data
+     * 4                                bytes for patch_dict.size()
+     * (32 + 4) * patch_dict.size()     bytes for patch_dict data
+     * 4                                bytes for inactive_categories dict
+     * 4 * inactive_categories.size()   bytes for inactive_categories data
+     * ========================================
+     * Помимо этого:
+     * 0x128-0x12C - Флаг, является ли активной альтернативная версия (PATCHED)
+     * 0x12C-0x130 - Офсет начала словаря локализации
+     */
+
     DatOperationResult<> DatLocaleManager::DeInit() {
         LOG(INFO) << "Committing locales...";
         if (!dat)
@@ -191,8 +272,10 @@ namespace LOTRO_DAT {
         if (binary_data.size() > dict_size || dict_offset == 0) {
             auto operation = dat->getIO().WriteData(binary_data, binary_data.size(), dat->getIO().file_size + 12);
             if (operation.result != SUCCESS)
-                return DatOperationResult<>(ERROR, "LOCALEDEINIT: Cannot write locales. ERRMSG: " + operation.msg);
+                return DatOperationResult<>(ERROR, "LOCALEDEINIT: Cannot write locales");
+
             dat->getIO().WriteData(BinaryData::FromNumber<4>(dat->getIO().file_size), 4, 300);
+            dat->getIO().WriteData(BinaryData::FromNumber<4>(current_locale_), 4, 296, 0);
 
             dat->getIO().file_size += binary_data.size();
 
@@ -201,6 +284,7 @@ namespace LOTRO_DAT {
             dat->getIO().WriteData(nulls, nulls.size(), dat->getIO().file_size);
             dat->getIO().file_size += nulls.size();
         } else {
+            dat->getIO().WriteData(BinaryData::FromNumber<4>(current_locale_), 4, 296, 0);
             auto operation = dat->getIO().WriteData(binary_data, binary_data.size(), dict_offset);
             if (operation.result != SUCCESS)
                 return DatOperationResult<>(ERROR, "LOCALEDEINIT: Cannot write locales. ERRMSG: " + operation.msg);
@@ -211,11 +295,27 @@ namespace LOTRO_DAT {
     }
 
 
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Обновление данных файла в локали
+     * \param[in] locale Локаль, для которой файл данных будет обновлён
+     * \param[in] file Новые данные файла. Старые данные будут перезаписаны
+     */
+
     void DatLocaleManager::UpdateLocaleFile(DatLocaleManager::LOCALE locale, const SubFile &file) {
         auto dict = GetLocaleDictReference(locale);
         dict[file.file_id()] = file;
     }
 
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Получение данных файла в локали. Вернёт DatOperationResult.result = ERROR, если файла с таким id не существует.
+     * \param[in] file_id id файла, для которого необходимо получить данных
+     * \param[in] locale Локаль, данные которой будут получены
+     */
+
     DatOperationResult<SubFile> DatLocaleManager::GetLocaleFile(long long file_id, DatLocaleManager::LOCALE locale) {
         auto dict = GetLocaleDictReference(locale);
         if (dict.count(file_id) != 0)
@@ -223,10 +323,27 @@ namespace LOTRO_DAT {
         return DatOperationResult<SubFile>(dict[file_id], SUCCESS);
     }
 
+
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Получение словаря файлов локали
+     * \param[in] locale Локаль, данные которой будут получены
+     */
+
     std::map<long long, SubFile> &DatLocaleManager::GetLocaleDictReference(DatLocaleManager::LOCALE locale) {
         return locale == PATCHED ? patch_dict_ : orig_dict_;
     }
 
+
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Выводит информацию о состоянии модуля в файл
+     * \param[in] file указатель на объект файла, в конец которого будет напечатана информация
+     * \warning Файл file должен быть открыт для записи. После завершения работы функции файл остаётся открытым
+     */
+
     void DatLocaleManager::PrintInformaion(FILE *file) {
         fprintf(file, "========= Locales info ========\n");
         BinaryData locale_offset_data(4);
@@ -235,6 +352,12 @@ namespace LOTRO_DAT {
 
         fprintf(file, "Locales' dictionary offset = %lld\n", locale_offset);
 
+        BinaryData locale_status_data(4);
+        dat->getIO().ReadData(locale_status_data, 4, 296);
+        long long locale_status = locale_offset_data.ToNumber<4>(0);
+
+        fprintf(file, "Locale status = %lld\n", locale_status);
+
         if (locale_offset != 0) {
             BinaryData locale_info(12);
             dat->getIO().ReadData(locale_info, 12, locale_offset);
@@ -251,4 +374,40 @@ namespace LOTRO_DAT {
         fprintf(file, "Patch dictionary size = %d\n", patch_dict_.size());
         fprintf(file, "Original dictionary size = %d\n", orig_dict_.size());
     }
+
+
+    /*!
+     * \Author Gi1dor
+     * \date 06.07.2018
+     * Осуществляет проверку корректности dat файла с позиции локалей
+     * Файл считается некорректным, если активной является альтернативная локаль, причём нет возможности вернуть оригинальную (блок локалей неверный/повреждён)
+     * Байты 0x128-0x12C показывают активную локаль на момент закрытия dat файла.
+     */
+
+    bool DatLocaleManager::CheckLocaleCorrect() {
+        BinaryData locale_info(4);
+        dat->getIO().ReadData(locale_info, 4, 296);
+        long long locale_status = locale_info.ToNumber<4>(0);
+
+        if (locale_status == ORIGINAL)
+            return true;
+
+        BinaryData locale_offset_data(4);
+        dat->getIO().ReadData(locale_offset_data, 4, 300);
+        long long locale_offset = locale_offset_data.ToNumber<4>(0);
+
+        if (locale_offset == 0 || locale_offset + 8 >= dat->getIO().GetActualDatSize().value)
+            return locale_status == ORIGINAL;
+
+        BinaryData dicts_data = BinaryData(4);
+        auto operation = dat->getIO().ReadData(dicts_data, 4, locale_offset + 12 + 15);
+        if (operation.result == ERROR)
+            return locale_status == ORIGINAL;
+
+        BinaryData locale_data = dicts_data + BinaryData("\0", 1);
+        std::string locale((char *)(locale_data.data()));
+
+        LOCALE dat_locale = (locale == "PATC" ? PATCHED : ORIGINAL);
+        return locale_status == dat_locale;
+    }
 }